Skip to content

Commit

Permalink
feat: org CRUD (#3380)
Browse files Browse the repository at this point in the history
* feat: add org schema

* feat: org manage UI

* feat: OrgInfoModal

* feat: org tree view

* feat: org management

* fix: init root org

* feat: org permission for app

* feat: org support for dataset

* fix: disable org role control

* styles: opt type signatures

* fix: remove unused permission

* feat: delete org collaborator
  • Loading branch information
I-Info authored and c121914yu committed Jan 6, 2025
1 parent bb669ca commit 1fc77a1
Show file tree
Hide file tree
Showing 46 changed files with 1,933 additions and 190 deletions.
37 changes: 36 additions & 1 deletion packages/global/common/error/code/team.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils';
import type { ErrType } from '../errorCode';
/* team: 500000 */
export enum TeamErrEnum {
teamOverSize = 'teamOverSize',
Expand All @@ -14,6 +14,13 @@ export enum TeamErrEnum {
groupNameEmpty = 'groupNameEmpty',
groupNameDuplicate = 'groupNameDuplicate',
groupNotExist = 'groupNotExist',
orgMemberNotExist = 'orgMemberNotExist',
orgMemberDuplicated = 'orgMemberDuplicated',
orgNotExist = 'orgNotExist',
orgParentNotExist = 'orgParentNotExist',
cannotMoveToSubPath = 'cannotMoveToSubPath',
cannotModifyRootOrg = 'cannotModifyRootOrg',
cannotDeleteNonEmptyOrg = 'cannotDeleteNonEmptyOrg',
cannotDeleteDefaultGroup = 'cannotDeleteDefaultGroup',
userNotActive = 'userNotActive'
}
Expand Down Expand Up @@ -71,6 +78,34 @@ const teamErr = [
{
statusText: TeamErrEnum.userNotActive,
message: i18nT('common:code_error.team_error.user_not_active')
},
{
statusText: TeamErrEnum.orgMemberNotExist,
message: i18nT('common:code_error.team_error.org_member_not_exist')
},
{
statusText: TeamErrEnum.orgMemberDuplicated,
message: i18nT('common:code_error.team_error.org_member_duplicated')
},
{
statusText: TeamErrEnum.orgNotExist,
message: i18nT('common:code_error.team_error.org_not_exist')
},
{
statusText: TeamErrEnum.orgParentNotExist,
message: i18nT('common:code_error.team_error.org_parent_not_exist')
},
{
statusText: TeamErrEnum.cannotMoveToSubPath,
message: i18nT('common:code_error.team_error.cannot_move_to_sub_path')
},
{
statusText: TeamErrEnum.cannotModifyRootOrg,
message: i18nT('common:code_error.team_error.cannot_modify_root_org')
},
{
statusText: TeamErrEnum.cannotDeleteNonEmptyOrg,
message: i18nT('common:code_error.team_error.cannot_delete_non_empty_org')
}
];

Expand Down
5 changes: 5 additions & 0 deletions packages/global/common/file/image/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export enum MongoImageTypeEnum {
userAvatar = 'userAvatar',
teamAvatar = 'teamAvatar',
groupAvatar = 'groupAvatar',
orgAvatar = 'orgAvatar',

chatImage = 'chatImage',
collectionImage = 'collectionImage'
Expand Down Expand Up @@ -41,6 +42,10 @@ export const mongoImageTypeMap = {
label: 'groupAvatar',
unique: true
},
[MongoImageTypeEnum.orgAvatar]: {
label: 'orgAvatar',
unique: true
},

[MongoImageTypeEnum.chatImage]: {
label: 'chatImage',
Expand Down
1 change: 1 addition & 0 deletions packages/global/common/system/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export const HUMAN_ICON = `/icon/human.svg`;
export const LOGO_ICON = `/icon/logo.svg`;
export const HUGGING_FACE_ICON = `/imgs/model/huggingface.svg`;
export const DEFAULT_TEAM_AVATAR = `/imgs/avatar/defaultTeamAvatar.svg`;
export const DEFAULT_ORG_AVATAR = '/imgs/avatar/defaultOrgAvatar.svg';

export const isProduction = process.env.NODE_ENV === 'production';
5 changes: 3 additions & 2 deletions packages/global/core/app/collaborator.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RequireOnlyOne } from '../../common/type/utils';
import type { RequireOnlyOne } from '../../common/type/utils';
import {
UpdateClbPermissionProps,
type UpdateClbPermissionProps,
UpdatePermissionBody
} from '../../support/permission/collaborator';
import { PermissionValueType } from '../../support/permission/type';
Expand All @@ -14,4 +14,5 @@ export type AppCollaboratorDeleteParams = {
} & RequireOnlyOne<{
tmbId: string;
groupId: string;
orgId: string;
}>;
1 change: 1 addition & 0 deletions packages/global/core/dataset/collaborator.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export type DatasetCollaboratorDeleteParams = {
} & RequireOnlyOne<{
tmbId: string;
groupId: string;
orgId: string;
}>;
4 changes: 4 additions & 0 deletions packages/global/support/permission/collaborator.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,26 @@ export type CollaboratorItemType = {
} & RequireOnlyOne<{
tmbId: string;
groupId: string;
orgId: string;
}>;

export type UpdateClbPermissionProps = {
members?: string[];
groups?: string[];
orgs?: string[];
permission: PermissionValueType;
};

export type DeleteClbPermissionProps = RequireOnlyOne<{
tmbId: string;
groupId: string;
orgId: string;
}>;

export type UpdatePermissionBody = {
permission: PermissionValueType;
} & RequireOnlyOne<{
memberId: string;
groupId: string;
orgId: string;
}>;
4 changes: 3 additions & 1 deletion packages/global/support/permission/type.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { UserModelSchema } from '../user/type';
import { RequireOnlyOne } from '../../common/type/utils';
import { TeamMemberSchema } from '../user/team/type';
import { AuthUserTypeEnum, PermissionKeyEnum, PerResourceTypeEnum } from './constant';
import { MemberGroupSchemaType } from './memberGroup/type';
import type { TeamMemberWithUserSchema } from '../user/team/type';
import { AuthUserTypeEnum, type PermissionKeyEnum, type PerResourceTypeEnum } from './constant';

// PermissionValueType, the type of permission's value is a number, which is a bit field actually.
// It is spired by the permission system in Linux.
Expand All @@ -29,6 +30,7 @@ export type ResourcePermissionType = {
} & RequireOnlyOne<{
tmbId: string;
groupId: string;
orgId: string;
}>;

export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> & {
Expand Down
38 changes: 38 additions & 0 deletions packages/global/support/user/team/org/api.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export type postCreateOrgData = {
name: string;
parentId: string;
description?: string;
avatar?: string;
};

export type putUpdateOrgMembersData = {
orgId: string;
members: {
tmbId: string;
// role: `${OrgMemberRole}`;
}[];
};

export type putUpdateOrgData = {
orgId: string;
name?: string;
avatar?: string;
description?: string;
};

export type putMoveOrgData = {
orgId: string;
parentId: string;
};

export type putMoveOrgMemberData = {
orgId: string;
tmbId: string;
newOrgId: string;
};

// type putChnageOrgOwnerData = {
// orgId: string;
// tmbId: string;
// toAdmin?: boolean;
// };
8 changes: 8 additions & 0 deletions packages/global/support/user/team/org/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const OrgCollectionName = 'team_orgs';
export const OrgMemberCollectionName = 'team_org_members';

// export enum OrgMemberRole {
// owner = 'owner',
// admin = 'admin',
// member = 'member'
// }
23 changes: 23 additions & 0 deletions packages/global/support/user/team/org/type.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { TeamPermission } from 'support/permission/user/controller';
import { ResourcePermissionType } from '../type';

type OrgSchemaType = {
_id: string;
teamId: string;
path: string;
name: string;
avatar?: string;
description?: string;
updateTime: Date;
};

type OrgMemberSchemaType = {
teamId: string;
orgId: string;
tmbId: string;
};

type OrgType = Omit<OrgSchemaType, 'avatar'> & {
avatar: string;
members: OrgMemberSchemaType[];
};
58 changes: 58 additions & 0 deletions packages/service/support/permission/auth/org.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
import { AuthModeType, AuthResponseType } from '../type';
import { parseHeaderCert } from '../controller';
import { getTmbInfoByTmbId } from '../../user/team/controller';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';

export const authOrgMember = async ({
orgIds,
req,
authToken = false,
authRoot = false,
authApiKey = false
}: {
orgIds: string | string[];
} & AuthModeType): Promise<AuthResponseType> => {
const result = await parseHeaderCert({ req, authToken, authApiKey, authRoot });
const { teamId, tmbId, isRoot } = result;
if (isRoot) {
return {
teamId,
tmbId,
userId: result.userId,
appId: result.appId,
apikey: result.apikey,
isRoot,
authType: result.authType,
permission: new TeamPermission({ isOwner: true })
};
}

if (!Array.isArray(orgIds)) {
orgIds = [orgIds];
}

// const promises = orgIds.map((orgId) => getOrgMemberRole({ orgId, tmbId }));

const tmb = await getTmbInfoByTmbId({ tmbId });
if (tmb.permission.hasManagePer) {
return {
...result,
permission: tmb.permission
};
}

return Promise.reject(TeamErrEnum.unAuthTeam);

// const targetRole = OrgMemberRole[role];
// for (const orgRole of orgRoles) {
// if (!orgRole || checkOrgRole(orgRole, targetRole)) {
// return Promise.reject(TeamErrEnum.unAuthTeam);
// }
// }

// return {
// ...result,
// permission: tmb.permission
// };
};
23 changes: 19 additions & 4 deletions packages/service/support/permission/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
import { UserModelSchema } from '@fastgpt/global/support/user/type';
import { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';

/** get resource permission for a team member
* If there is no permission for the team member, it will return undefined
Expand Down Expand Up @@ -186,6 +187,16 @@ export const getClbsAndGroupsWithInfo = async ({
}
})
.populate<{ group: MemberGroupSchemaType }>('group', 'name avatar')
.lean(),
MongoResourcePermission.find({
teamId,
resourceId,
resourceType,
orgId: {
$exists: true
}
})
.populate<{ org: OrgSchemaType }>({ path: 'org', select: 'name avatar' })
.lean()
]);

Expand All @@ -196,6 +207,7 @@ export const delResourcePermission = ({
session,
tmbId,
groupId,
orgId,
...props
}: {
resourceType: PerResourceTypeEnum;
Expand All @@ -204,15 +216,18 @@ export const delResourcePermission = ({
session?: ClientSession;
tmbId?: string;
groupId?: string;
orgId?: string;
}) => {
// tmbId or groupId only one and not both
if (!!tmbId === !!groupId) {
// either tmbId or groupId or orgId must be provided
if (!tmbId && !groupId && !orgId) {
return Promise.reject(CommonErrEnum.missingParams);
}

return MongoResourcePermission.deleteOne(
{
...(tmbId ? { tmbId } : {}),
...(groupId ? { groupId } : {}),
...(orgId ? { orgId } : {}),
...props
},
{ session }
Expand Down Expand Up @@ -250,7 +265,7 @@ export function authJWT(token: string) {
}>((resolve, reject) => {
const key = process.env.TOKEN_KEY as string;

jwt.verify(token, key, function (err, decoded: any) {
jwt.verify(token, key, (err, decoded: any) => {
if (err || !decoded?.userId) {
reject(ERROR_ENUM.unAuthorization);
return;
Expand Down Expand Up @@ -436,7 +451,7 @@ export const authFileToken = (token?: string) =>
}
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';

jwt.verify(token, key, function (err, decoded: any) {
jwt.verify(token, key, (err, decoded: any) => {
if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.fileId) {
reject(ERROR_ENUM.unAuthFile);
return;
Expand Down
13 changes: 7 additions & 6 deletions packages/service/support/permission/inheritPermission.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { mongoSessionRun } from '../../common/mongo/sessionRun';
import { MongoResourcePermission } from './schema';
import { ClientSession, Model } from 'mongoose';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import type { ClientSession, Model } from 'mongoose';
import type { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { getResourceClbsAndGroups } from './controller';
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';

export type SyncChildrenPermissionResourceType = {
_id: string;
Expand All @@ -18,6 +18,7 @@ export type UpdateCollaboratorItem = {
} & RequireOnlyOne<{
tmbId: string;
groupId: string;
orgId: string;
}>;

// sync the permission to all children folders.
Expand Down Expand Up @@ -161,7 +162,7 @@ export async function resumeInheritPermission({
}
}

/*
/*
Delete all the collaborators and then insert the new collaborators.
*/
export async function syncCollaborators({
Expand Down
Loading

0 comments on commit 1fc77a1

Please sign in to comment.