feat: org CRUD (#3380)
* 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
This commit is contained in:
@@ -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',
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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')
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ export enum MongoImageTypeEnum {
|
||||
userAvatar = 'userAvatar',
|
||||
teamAvatar = 'teamAvatar',
|
||||
groupAvatar = 'groupAvatar',
|
||||
orgAvatar = 'orgAvatar',
|
||||
|
||||
chatImage = 'chatImage',
|
||||
collectionImage = 'collectionImage'
|
||||
@@ -41,6 +42,10 @@ export const mongoImageTypeMap = {
|
||||
label: 'groupAvatar',
|
||||
unique: true
|
||||
},
|
||||
[MongoImageTypeEnum.orgAvatar]: {
|
||||
label: 'orgAvatar',
|
||||
unique: true
|
||||
},
|
||||
|
||||
[MongoImageTypeEnum.chatImage]: {
|
||||
label: 'chatImage',
|
||||
|
||||
@@ -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
packages/global/core/app/collaborator.d.ts
vendored
5
packages/global/core/app/collaborator.d.ts
vendored
@@ -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';
|
||||
@@ -14,4 +14,5 @@ export type AppCollaboratorDeleteParams = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
@@ -11,4 +11,5 @@ export type DatasetCollaboratorDeleteParams = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
@@ -10,17 +10,20 @@ 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 = {
|
||||
@@ -28,4 +31,5 @@ export type UpdatePermissionBody = {
|
||||
} & RequireOnlyOne<{
|
||||
memberId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
4
packages/global/support/permission/type.d.ts
vendored
4
packages/global/support/permission/type.d.ts
vendored
@@ -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.
|
||||
@@ -29,6 +30,7 @@ export type ResourcePermissionType = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> & {
|
||||
|
||||
38
packages/global/support/user/team/org/api.d.ts
vendored
Normal file
38
packages/global/support/user/team/org/api.d.ts
vendored
Normal file
@@ -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
packages/global/support/user/team/org/constant.ts
Normal file
8
packages/global/support/user/team/org/constant.ts
Normal file
@@ -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
packages/global/support/user/team/org/type.d.ts
vendored
Normal file
23
packages/global/support/user/team/org/type.d.ts
vendored
Normal file
@@ -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
packages/service/support/permission/auth/org.ts
Normal file
58
packages/service/support/permission/auth/org.ts
Normal file
@@ -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
|
||||
// };
|
||||
};
|
||||
@@ -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
|
||||
@@ -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()
|
||||
]);
|
||||
|
||||
@@ -196,6 +207,7 @@ export const delResourcePermission = ({
|
||||
session,
|
||||
tmbId,
|
||||
groupId,
|
||||
orgId,
|
||||
...props
|
||||
}: {
|
||||
resourceType: PerResourceTypeEnum;
|
||||
@@ -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 }
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -18,6 +18,7 @@ export type UpdateCollaboratorItem = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
// sync the permission to all children folders.
|
||||
@@ -161,7 +162,7 @@ export async function resumeInheritPermission({
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
Delete all the collaborators and then insert the new collaborators.
|
||||
*/
|
||||
export async function syncCollaborators({
|
||||
|
||||
174
packages/service/support/permission/org/controllers.ts
Normal file
174
packages/service/support/permission/org/controllers.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||
import type {
|
||||
OrgMemberSchemaType,
|
||||
OrgSchemaType
|
||||
} from '@fastgpt/global/support/user/team/org/type';
|
||||
import type { ClientSession } from 'mongoose';
|
||||
import { MongoOrgModel } from './orgSchema';
|
||||
import { MongoOrgMemberModel } from './orgMemberSchema';
|
||||
|
||||
// if role1 > role2, return 1
|
||||
// if role1 < role2, return -1
|
||||
// else return 0
|
||||
// export const compareRole = (role1: OrgMemberRole, role2: OrgMemberRole) => {
|
||||
// if (role1 === OrgMemberRole.owner) {
|
||||
// if (role2 === OrgMemberRole.owner) {
|
||||
// return 0;
|
||||
// }
|
||||
// return 1;
|
||||
// }
|
||||
// if (role2 === OrgMemberRole.owner) {
|
||||
// return -1;
|
||||
// }
|
||||
// if (role1 === OrgMemberRole.admin) {
|
||||
// if (role2 === OrgMemberRole.admin) {
|
||||
// return 0;
|
||||
// }
|
||||
// return 1;
|
||||
// }
|
||||
// if (role2 === OrgMemberRole.admin) {
|
||||
// return -1;
|
||||
// }
|
||||
// return 0;
|
||||
// };
|
||||
|
||||
// export const checkOrgRole = (role: OrgMemberRole, targetRole: OrgMemberRole) => {
|
||||
// return compareRole(role, targetRole) >= 0;
|
||||
// };
|
||||
|
||||
export const getOrgsByTeamId = async (teamId: string) => {
|
||||
const orgs = await MongoOrgModel.find({
|
||||
teamId
|
||||
})
|
||||
.populate<{ members: OrgMemberSchemaType }>('members')
|
||||
.lean();
|
||||
|
||||
return orgs;
|
||||
};
|
||||
|
||||
export const getOrgsByTmbId = async ({ teamId, tmbId }: { teamId: string; tmbId: string }) =>
|
||||
MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean();
|
||||
|
||||
export const getChildrenByOrg = async ({
|
||||
org,
|
||||
teamId,
|
||||
session
|
||||
}: {
|
||||
org: OrgSchemaType;
|
||||
teamId: string;
|
||||
session?: ClientSession;
|
||||
}) => {
|
||||
const children = await MongoOrgModel.find(
|
||||
{ teamId, path: { $regex: `^${org.path}/${org._id}` } },
|
||||
undefined,
|
||||
{
|
||||
session
|
||||
}
|
||||
).lean();
|
||||
return children;
|
||||
};
|
||||
|
||||
export const getOrgAndChildren = async ({
|
||||
orgId,
|
||||
teamId,
|
||||
session
|
||||
}: {
|
||||
orgId: string;
|
||||
teamId: string;
|
||||
session?: ClientSession;
|
||||
}) => {
|
||||
const org = await MongoOrgModel.findOne({ _id: orgId, teamId }, undefined, { session }).lean();
|
||||
if (!org) {
|
||||
return Promise.reject(TeamErrEnum.orgNotExist);
|
||||
}
|
||||
const children = await getChildrenByOrg({ org, teamId, session });
|
||||
return { org, children };
|
||||
};
|
||||
|
||||
export async function createRootOrg({
|
||||
teamId,
|
||||
session
|
||||
}: {
|
||||
teamId: string;
|
||||
session?: ClientSession;
|
||||
}) {
|
||||
// Create the root org
|
||||
const [org] = await MongoOrgModel.create(
|
||||
[
|
||||
{
|
||||
teamId,
|
||||
name: 'ROOT',
|
||||
path: ''
|
||||
}
|
||||
],
|
||||
{ session }
|
||||
);
|
||||
// Find the team's owner
|
||||
// const owner = await MongoTeamMember.findOne({ teamId, role: 'owner' }, undefined);
|
||||
// if (!owner) {
|
||||
// return Promise.reject(TeamErrEnum.unAuthTeam);
|
||||
// }
|
||||
|
||||
// Set the owner as the org admin
|
||||
// await MongoOrgMemberModel.create(
|
||||
// [
|
||||
// {
|
||||
// orgId: org._id,
|
||||
// tmbId: owner._id
|
||||
|
||||
// }
|
||||
// ],
|
||||
// { session }
|
||||
// );
|
||||
}
|
||||
|
||||
// export const getOrgMemberRole = async ({
|
||||
// orgId,
|
||||
// tmbId
|
||||
// }: {
|
||||
// orgId: string;
|
||||
// tmbId: string;
|
||||
// }): Promise<OrgMemberRole | undefined> => {
|
||||
// let role: OrgMemberRole | undefined;
|
||||
// const orgMember = await MongoOrgMemberModel.findOne({
|
||||
// orgId,
|
||||
// tmbId
|
||||
// })
|
||||
// .populate('orgId')
|
||||
// .lean();
|
||||
// if (orgMember) {
|
||||
// role = OrgMemberRole[orgMember.role];
|
||||
// } else {
|
||||
// return role;
|
||||
// }
|
||||
// if (role === OrgMemberRole.owner) {
|
||||
// return role;
|
||||
// }
|
||||
// // Check the parent orgs
|
||||
// const org = orgMember.orgId as unknown as OrgSchemaType;
|
||||
// if (!org) {
|
||||
// return Promise.reject(TeamErrEnum.orgNotExist);
|
||||
// }
|
||||
// const parentIds = org.path.split('/').filter((id) => id);
|
||||
// if (parentIds.length === 0) {
|
||||
// return role;
|
||||
// }
|
||||
// const parentOrgMembers = await MongoOrgMemberModel.find({
|
||||
// orgId: {
|
||||
// $in: parentIds
|
||||
// },
|
||||
// tmbId
|
||||
// }).lean();
|
||||
// // Update the role to the highest role
|
||||
// for (const parentOrgMember of parentOrgMembers) {
|
||||
// const parentRole = OrgMemberRole[parentOrgMember.role];
|
||||
// if (parentRole === OrgMemberRole.owner) {
|
||||
// role = parentRole;
|
||||
// break;
|
||||
// }
|
||||
// if (parentRole === OrgMemberRole.admin && role === OrgMemberRole.member) {
|
||||
// role = parentRole;
|
||||
// }
|
||||
// }
|
||||
// return role;
|
||||
// };
|
||||
58
packages/service/support/permission/org/orgMemberSchema.ts
Normal file
58
packages/service/support/permission/org/orgMemberSchema.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant';
|
||||
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
||||
import {
|
||||
TeamCollectionName,
|
||||
TeamMemberCollectionName
|
||||
} from '@fastgpt/global/support/user/team/constant';
|
||||
import { OrgMemberSchemaType } from '@fastgpt/global/support/user/team/org/type';
|
||||
const { Schema } = connectionMongo;
|
||||
|
||||
export const OrgMemberCollectionName = 'team_org_members';
|
||||
|
||||
export const OrgMemberSchema = new Schema({
|
||||
teamId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamCollectionName,
|
||||
required: true
|
||||
},
|
||||
orgId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: OrgCollectionName,
|
||||
required: true
|
||||
},
|
||||
tmbId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamMemberCollectionName,
|
||||
required: true
|
||||
}
|
||||
// role: {
|
||||
// type: String,
|
||||
// enum: Object.values(OrgMemberRole),
|
||||
// required: true,
|
||||
// default: OrgMemberRole.member
|
||||
// }
|
||||
});
|
||||
|
||||
try {
|
||||
OrgMemberSchema.index(
|
||||
{
|
||||
teamId: 1,
|
||||
orgId: 1,
|
||||
tmbId: 1
|
||||
},
|
||||
{
|
||||
unique: true
|
||||
}
|
||||
);
|
||||
OrgMemberSchema.index({
|
||||
teamId: 1,
|
||||
tmbId: 1
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
export const MongoOrgMemberModel = getMongoModel<OrgMemberSchemaType>(
|
||||
OrgMemberCollectionName,
|
||||
OrgMemberSchema
|
||||
);
|
||||
69
packages/service/support/permission/org/orgSchema.ts
Normal file
69
packages/service/support/permission/org/orgSchema.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
|
||||
import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant';
|
||||
import type { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
||||
import { ResourcePermissionCollectionName } from '../schema';
|
||||
import { OrgMemberCollectionName } from './orgMemberSchema';
|
||||
const { Schema } = connectionMongo;
|
||||
|
||||
function requiredStringPath(this: OrgSchemaType) {
|
||||
return typeof this.path !== 'string';
|
||||
}
|
||||
|
||||
export const OrgSchema = new Schema(
|
||||
{
|
||||
teamId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamCollectionName,
|
||||
required: true
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: requiredStringPath // allow empty string, but not null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
avatar: String,
|
||||
description: String,
|
||||
updateTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
}
|
||||
},
|
||||
{
|
||||
// Auto update updateTime
|
||||
timestamps: {
|
||||
updatedAt: 'updateTime'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
OrgSchema.virtual('members', {
|
||||
ref: OrgMemberCollectionName,
|
||||
localField: '_id',
|
||||
foreignField: 'orgId'
|
||||
});
|
||||
OrgSchema.virtual('permission', {
|
||||
ref: ResourcePermissionCollectionName,
|
||||
localField: '_id',
|
||||
foreignField: 'orgId',
|
||||
justOne: true
|
||||
});
|
||||
|
||||
try {
|
||||
OrgSchema.index(
|
||||
{
|
||||
teamId: 1,
|
||||
path: 1
|
||||
},
|
||||
{
|
||||
unique: true
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
export const MongoOrgModel = getMongoModel<OrgSchemaType>(OrgCollectionName, OrgSchema);
|
||||
@@ -6,6 +6,7 @@ import { connectionMongo, getMongoModel } from '../../common/mongo';
|
||||
import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
|
||||
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { MemberGroupCollectionName } from './memberGroup/memberGroupSchema';
|
||||
import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant';
|
||||
const { Schema } = connectionMongo;
|
||||
|
||||
export const ResourcePermissionCollectionName = 'resource_permissions';
|
||||
@@ -23,6 +24,10 @@ export const ResourcePermissionSchema = new Schema({
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: MemberGroupCollectionName
|
||||
},
|
||||
orgId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: OrgCollectionName
|
||||
},
|
||||
resourceType: {
|
||||
type: String,
|
||||
enum: Object.values(PerResourceTypeEnum),
|
||||
@@ -51,6 +56,12 @@ ResourcePermissionSchema.virtual('group', {
|
||||
foreignField: '_id',
|
||||
justOne: true
|
||||
});
|
||||
ResourcePermissionSchema.virtual('org', {
|
||||
ref: OrgCollectionName,
|
||||
localField: 'orgId',
|
||||
foreignField: '_id',
|
||||
justOne: true
|
||||
});
|
||||
|
||||
try {
|
||||
ResourcePermissionSchema.index(
|
||||
@@ -70,6 +81,23 @@ try {
|
||||
}
|
||||
);
|
||||
|
||||
ResourcePermissionSchema.index(
|
||||
{
|
||||
resourceType: 1,
|
||||
teamId: 1,
|
||||
resourceId: 1,
|
||||
orgId: 1
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
partialFilterExpression: {
|
||||
orgId: {
|
||||
$exists: true
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
ResourcePermissionSchema.index(
|
||||
{
|
||||
resourceType: 1,
|
||||
|
||||
@@ -16,6 +16,7 @@ import { MongoMemberGroupModel } from '../../permission/memberGroup/memberGroupS
|
||||
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import { getAIApi, openaiBaseUrl } from '../../../core/ai/config';
|
||||
import { createRootOrg } from '../../permission/org/controllers';
|
||||
|
||||
async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemType> {
|
||||
const tmb = await MongoTeamMember.findOne(match).populate<{ team: TeamSchema }>('team').lean();
|
||||
@@ -132,7 +133,8 @@ export async function createDefaultTeam({
|
||||
],
|
||||
{ session }
|
||||
);
|
||||
console.log('create default team and group', userId);
|
||||
await createRootOrg({ teamId: tmb.teamId, session });
|
||||
console.log('create default team, group and root org', userId);
|
||||
return tmb;
|
||||
} else {
|
||||
console.log('default team exist', userId);
|
||||
|
||||
@@ -73,6 +73,7 @@ export const iconPaths = {
|
||||
'common/resultLight': () => import('./icons/common/resultLight.svg'),
|
||||
'common/retryLight': () => import('./icons/common/retryLight.svg'),
|
||||
'common/rightArrowFill': () => import('./icons/common/rightArrowFill.svg'),
|
||||
'common/downArrowFill': () => import('./icons/common/downArrowFill.svg'),
|
||||
'common/rightArrowLight': () => import('./icons/common/rightArrowLight.svg'),
|
||||
'common/routePushLight': () => import('./icons/common/routePushLight.svg'),
|
||||
'common/saveFill': () => import('./icons/common/saveFill.svg'),
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon/solid/chevron-down">
|
||||
<path id="Rectangle 3101" d="M11.4695 5.33325C12.6574 5.33325 13.2523 6.76944 12.4123 7.60939L9.01223 11.0095C8.49154 11.5302 7.64732 11.5302 7.12662 11.0095L3.72653 7.60939C2.88657 6.76944 3.48146 5.33325 4.66933 5.33325H11.4695Z" fill="#667085"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 390 B |
@@ -2,11 +2,23 @@
|
||||
"action": "operate",
|
||||
"confirm_delete_group": "Confirm to delete group?",
|
||||
"confirm_leave_team": "Confirmed to leave the team? \n \nAfter you log out, all your resources in the team (applications, knowledge bases, folders, managed groups, etc.) will be transferred to the team owner.",
|
||||
"confirm_delete_org": "Confirm to delete organization?",
|
||||
"confirm_delete_member": "Confirm to delete member?",
|
||||
"create_group": "Create group",
|
||||
"delete": "delete",
|
||||
"edit_info": "Edit information",
|
||||
"group": "group",
|
||||
"group_name": "Group name",
|
||||
"org": "organization",
|
||||
"org_name": "Organization name",
|
||||
"org_description": "Organization description",
|
||||
"create_org": "Create organization",
|
||||
"create_sub_org": "Create sub-organization",
|
||||
"edit_org_info": "Edit organization information",
|
||||
"move_org": "Move organization",
|
||||
"move_member": "Move member",
|
||||
"delete_org": "Delete organization",
|
||||
"remark": "remark",
|
||||
"label_sync": "Tag sync",
|
||||
"leave_team_failed": "Leaving the team exception",
|
||||
"manage_member": "Managing members",
|
||||
|
||||
@@ -85,6 +85,13 @@
|
||||
"code_error.team_error.un_auth": "Unauthorized to Operate This Team",
|
||||
"code_error.team_error.user_not_active": "The user did not accept or has left the team",
|
||||
"code_error.team_error.website_sync_not_enough": "Unauthorized to Use Website Sync",
|
||||
"code_error.team_error.org_member_not_exist": "Organization member does not exist",
|
||||
"code_error.team_error.org_member_duplicated": "Duplicate organization member",
|
||||
"code_error.team_error.org_not_exist": "Organization does not exist",
|
||||
"code_error.team_error.org_parent_not_exist": "Parent organization does not exist",
|
||||
"code_error.team_error.cannot_move_to_sub_path": "Cannot move to same or subdirectory",
|
||||
"code_error.team_error.cannot_modify_root_org": "Cannot modify root organization",
|
||||
"code_error.team_error.cannot_delete_non_empty_org": "Cannot delete non-empty organization",
|
||||
"code_error.token_error_code.403": "Invalid Login Status, Please Re-login",
|
||||
"code_error.user_error.balance_not_enough": "Insufficient Account Balance",
|
||||
"code_error.user_error.bin_visitor": "Identity Verification Failed",
|
||||
|
||||
@@ -1,29 +1,41 @@
|
||||
{
|
||||
"total_team_members": "共 {{amount}} 名成员",
|
||||
"member": "成员",
|
||||
"group": "群组",
|
||||
"permission": "权限",
|
||||
"user_name": "用户名",
|
||||
"member_group": "所属成员组",
|
||||
"action": "操作",
|
||||
"waiting": "待接受",
|
||||
"remove_tip": "确认将 {{username}} 移出团队?",
|
||||
|
||||
"confirm_leave_team": "确认离开该团队? \n 退出后,您在该团队所有的资源( 应用、知识库、文件夹、管理的群组等)均转让给团队所有者。",
|
||||
"leave_team_failed": "离开团队异常",
|
||||
"label_sync": "标签同步",
|
||||
"user_team_invite_member": "邀请成员",
|
||||
"user_team_leave_team": "离开团队",
|
||||
"user_team_leave_team_failed": "离开团队失败",
|
||||
"create_group": "创建群组",
|
||||
"search_member_group_name": "搜索成员/群组名称",
|
||||
"confirm_delete_group": "确认删除群组?",
|
||||
"group_name": "群组名称",
|
||||
"owner": "所有者",
|
||||
"manage_member": "管理成员",
|
||||
"edit_info": "编辑信息",
|
||||
|
||||
"transfer_ownership": "转让所有者",
|
||||
"delete": "删除",
|
||||
"retain_admin_permissions": "保留管理员权限"
|
||||
}
|
||||
{
|
||||
"total_team_members": "共 {{amount}} 名成员",
|
||||
"member": "成员",
|
||||
"group": "群组",
|
||||
"org": "组织",
|
||||
"org_name": "组织名称",
|
||||
"org_description": "介绍",
|
||||
"permission": "权限",
|
||||
"user_name": "用户名",
|
||||
"member_group": "所属成员组",
|
||||
"action": "操作",
|
||||
"remark": "备注",
|
||||
"waiting": "待接受",
|
||||
"remove_tip": "确认将 {{username}} 移出团队?",
|
||||
|
||||
"confirm_leave_team": "确认离开该团队? \n 退出后,您在该团队所有的资源( 应用、知识库、文件夹、管理的群组等)均转让给团队所有者。",
|
||||
"leave_team_failed": "离开团队异常",
|
||||
"label_sync": "标签同步",
|
||||
"user_team_invite_member": "邀请成员",
|
||||
"user_team_leave_team": "离开团队",
|
||||
"user_team_leave_team_failed": "离开团队失败",
|
||||
"create_group": "创建群组",
|
||||
"search_member_group_name": "搜索成员/群组名称",
|
||||
"confirm_delete_group": "确认删除群组?",
|
||||
"group_name": "群组名称",
|
||||
"owner": "所有者",
|
||||
"manage_member": "管理成员",
|
||||
"edit_info": "编辑信息",
|
||||
"create_org": "创建组织",
|
||||
"create_sub_org": "创建子组织",
|
||||
"edit_org_info": "编辑组织信息",
|
||||
"move_org": "移动组织",
|
||||
"move_member": "移动成员",
|
||||
"delete_org": "删除组织",
|
||||
"confirm_delete_org": "确认删除组织?",
|
||||
"confirm_delete_member": "确认删除成员?",
|
||||
|
||||
"transfer_ownership": "转让所有者",
|
||||
"delete": "删除",
|
||||
"retain_admin_permissions": "保留管理员权限"
|
||||
}
|
||||
|
||||
@@ -89,6 +89,13 @@
|
||||
"code_error.team_error.un_auth": "无权操作该团队",
|
||||
"code_error.team_error.user_not_active": "用户未接受或已离开团队",
|
||||
"code_error.team_error.website_sync_not_enough": "无权使用Web站点同步~",
|
||||
"code_error.team_error.org_member_not_exist": "组织成员不存在",
|
||||
"code_error.team_error.org_member_duplicated": "重复的组织成员",
|
||||
"code_error.team_error.org_not_exist": "组织不存在",
|
||||
"code_error.team_error.org_parent_not_exist": "父组织不存在",
|
||||
"code_error.team_error.cannot_move_to_sub_path": "不能移动到相同或子目录",
|
||||
"code_error.team_error.cannot_modify_root_org": "不能修改根组织",
|
||||
"code_error.team_error.cannot_delete_non_empty_org": "不能删除非空组织",
|
||||
"code_error.token_error_code.403": "登录状态无效,请重新登录",
|
||||
"code_error.user_error.balance_not_enough": "账号余额不足~",
|
||||
"code_error.user_error.bin_visitor": "您的身份校验未通过",
|
||||
|
||||
@@ -2,11 +2,23 @@
|
||||
"action": "操作",
|
||||
"confirm_delete_group": "確認刪除群組?",
|
||||
"confirm_leave_team": "確認離開該團隊? \n \n退出後,您在該團隊所有的資源( 應用程式、知識庫、資料夾、管理的群組等)均轉讓給團隊所有者。",
|
||||
"confirm_delete_org": "確認刪除組織?",
|
||||
"confirm_delete_member": "確認刪除成員?",
|
||||
"create_group": "建立群組",
|
||||
"delete": "刪除",
|
||||
"edit_info": "編輯訊息",
|
||||
"group": "群組",
|
||||
"group_name": "群組名稱",
|
||||
"org": "組織",
|
||||
"org_name": "組織名稱",
|
||||
"org_description": "介紹",
|
||||
"create_org": "建立組織",
|
||||
"create_sub_org": "建立子組織",
|
||||
"edit_org_info": "編輯組織訊息",
|
||||
"move_org": "移動組織",
|
||||
"move_member": "移動成員",
|
||||
"delete_org": "刪除組織",
|
||||
"remark": "備註",
|
||||
"label_sync": "標籤同步",
|
||||
"leave_team_failed": "離開團隊異常",
|
||||
"manage_member": "管理成員",
|
||||
|
||||
@@ -85,6 +85,13 @@
|
||||
"code_error.team_error.un_auth": "無權操作此團隊",
|
||||
"code_error.team_error.user_not_active": "使用者未接受或已離開團隊",
|
||||
"code_error.team_error.website_sync_not_enough": "無權使用網站同步",
|
||||
"code_error.team_error.org_member_not_exist": "組織成員不存在",
|
||||
"code_error.team_error.org_member_duplicated": "重複的組織成員",
|
||||
"code_error.team_error.org_not_exist": "組織不存在",
|
||||
"code_error.team_error.org_parent_not_exist": "父組織不存在",
|
||||
"code_error.team_error.cannot_move_to_sub_path": "無法移動到相同或子目錄",
|
||||
"code_error.team_error.cannot_modify_root_org": "無法修改根組織",
|
||||
"code_error.team_error.cannot_delete_non_empty_org": "無法刪除非空組織",
|
||||
"code_error.token_error_code.403": "登入狀態無效,請重新登入",
|
||||
"code_error.user_error.balance_not_enough": "帳戶餘額不足",
|
||||
"code_error.user_error.bin_visitor": "身份驗證未通過",
|
||||
|
||||
Reference in New Issue
Block a user