From fa2f17e34e4eee75626ce84cf9435859e5a30f84 Mon Sep 17 00:00:00 2001 From: I-Info Date: Thu, 26 Dec 2024 14:10:17 +0800 Subject: [PATCH] fix: disable org role control --- packages/global/common/error/code/team.ts | 68 +++-- packages/global/support/permission/type.d.ts | 4 - .../global/support/user/team/org/api.d.ts | 26 +- .../global/support/user/team/org/constant.ts | 12 +- .../global/support/user/team/org/type.d.ts | 12 +- .../service/support/permission/auth/org.ts | 58 ++++ .../service/support/permission/controller.ts | 14 +- .../support/permission/org/controllers.ts | 271 +++++++++--------- .../support/permission/org/orgMemberSchema.ts | 26 +- .../support/permission/org/orgSchema.ts | 14 +- packages/service/support/permission/schema.ts | 7 + .../service/support/user/team/controller.ts | 4 +- packages/web/i18n/en/common.json | 7 + packages/web/i18n/zh-CN/common.json | 7 + packages/web/i18n/zh-Hant/common.json | 7 + .../MemberManager/AddMemberModal.tsx | 41 ++- .../permission/MemberManager/ManageModal.tsx | 11 +- .../MemberManager/MemberListCard.tsx | 9 +- .../components/OrgManage/OrgInfoModal.tsx | 2 +- .../components/OrgManage/OrgMemberModal.tsx | 113 +------- .../components/OrgManage/OrgMoveModal.tsx | 4 +- .../team/components/OrgManage/OrgTree.tsx | 3 +- .../team/components/OrgManage/index.tsx | 96 +++---- .../pages/account/team/components/context.tsx | 11 +- .../app/src/web/support/user/team/org/api.ts | 2 - 25 files changed, 393 insertions(+), 436 deletions(-) create mode 100644 packages/service/support/permission/auth/org.ts diff --git a/packages/global/common/error/code/team.ts b/packages/global/common/error/code/team.ts index 7063695a14dd..e1b7a80d1f64 100644 --- a/packages/global/common/error/code/team.ts +++ b/packages/global/common/error/code/team.ts @@ -14,20 +14,13 @@ export enum TeamErrEnum { groupNameEmpty = 'groupNameEmpty', groupNameDuplicate = 'groupNameDuplicate', groupNotExist = 'groupNotExist', - orgNameEmpty = 'orgNameEmpty', - orgOwnerNotExist = 'orgOwnerNotExist', orgMemberNotExist = 'orgMemberNotExist', - orgMemberExist = 'orgMemberExist', + orgMemberDuplicated = 'orgMemberDuplicated', orgNotExist = 'orgNotExist', - orgMoveSameParent = 'orgMoveSameParent', - orgMoveToChildren = 'orgMoveToChildren', orgParentNotExist = 'orgParentNotExist', - deletingOrgWithChildren = 'deletingOrgWithChildren', - deletingRootOrg = 'deletingRootOrg', - updatingRootOrg = 'updatingRootOrg', - deletingOwner = 'deletingOwner', - movingOwner = 'movingOwner', - orgNameDuplicate = 'orgNameDuplicate', + cannotMoveToSubPath = 'cannotMoveToSubPath', + cannotModifyRootOrg = 'cannotModifyRootOrg', + cannotDeleteNonEmptyOrg = 'cannotDeleteNonEmptyOrg', cannotDeleteDefaultGroup = 'cannotDeleteDefaultGroup', userNotActive = 'userNotActive' } @@ -85,20 +78,45 @@ 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') } ]; -export default teamErr.reduce( - (acc, cur, index) => { - return { - ...acc, - [cur.statusText]: { - code: 500000 + index, - statusText: cur.statusText, - message: cur.message, - data: null - } - }; - }, - {} as ErrType<`${TeamErrEnum}`> -); +export default teamErr.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: 500000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${TeamErrEnum}`>); diff --git a/packages/global/support/permission/type.d.ts b/packages/global/support/permission/type.d.ts index df2ba91250b7..d09cb286e629 100644 --- a/packages/global/support/permission/type.d.ts +++ b/packages/global/support/permission/type.d.ts @@ -39,10 +39,6 @@ export type ResourcePerWithGroup = Omit & { groupId: MemberGroupSchemaType; }; -export type ResourcePerWithOrg = Omit & { - orgId: OrgSchemaType; -}; - export type PermissionSchemaType = { defaultPermission: PermissionValueType; inheritPermission: boolean; diff --git a/packages/global/support/user/team/org/api.d.ts b/packages/global/support/user/team/org/api.d.ts index eb6080b2997d..605e9940de88 100644 --- a/packages/global/support/user/team/org/api.d.ts +++ b/packages/global/support/user/team/org/api.d.ts @@ -1,40 +1,38 @@ -import type { OrgMemberRole } from './constant'; - -type postCreateOrgData = { +export type postCreateOrgData = { name: string; parentId: string; description?: string; avatar?: string; }; -type putUpdateOrgMembersData = { +export type putUpdateOrgMembersData = { orgId: string; members: { tmbId: string; - role: `${OrgMemberRole}`; + // role: `${OrgMemberRole}`; }[]; }; -type putChnageOrgOwnerData = { - orgId: string; - tmbId: string; - toAdmin?: boolean; -}; - -type putUpdateOrgData = { +export type putUpdateOrgData = { orgId: string; name?: string; avatar?: string; description?: string; }; -type putMoveOrgData = { +export type putMoveOrgData = { orgId: string; parentId: string; }; -type putMoveOrgMemberData = { +export type putMoveOrgMemberData = { orgId: string; tmbId: string; newOrgId: string; }; + +// type putChnageOrgOwnerData = { +// orgId: string; +// tmbId: string; +// toAdmin?: boolean; +// }; diff --git a/packages/global/support/user/team/org/constant.ts b/packages/global/support/user/team/org/constant.ts index 3825534932bc..5b764877b225 100644 --- a/packages/global/support/user/team/org/constant.ts +++ b/packages/global/support/user/team/org/constant.ts @@ -1,10 +1,8 @@ export const OrgCollectionName = 'team_orgs'; export const OrgMemberCollectionName = 'team_org_members'; -export enum OrgMemberRole { - owner = 'owner', - admin = 'admin', - member = 'member' -} - -export const RootOrgName = 'ROOT'; +// export enum OrgMemberRole { +// owner = 'owner', +// admin = 'admin', +// member = 'member' +// } diff --git a/packages/global/support/user/team/org/type.d.ts b/packages/global/support/user/team/org/type.d.ts index 88f23dd2e7b8..25cea835fb45 100644 --- a/packages/global/support/user/team/org/type.d.ts +++ b/packages/global/support/user/team/org/type.d.ts @@ -1,24 +1,24 @@ import type { TeamPermission } from 'support/permission/user/controller'; import { ResourcePermissionType } from '../type'; -import type { OrgMemberRole } from './constant'; type OrgSchemaType = { _id: string; teamId: string; path: string; name: string; - avatar: string | undefined; - description: string | undefined; + avatar?: string; + description?: string; updateTime: Date; }; type OrgMemberSchemaType = { + teamId: string; orgId: string; tmbId: string; - role: `${OrgMemberRole}`; }; -type OrgType = OrgSchemaType & { +type OrgType = Omit & { + avatar: string; members: OrgMemberSchemaType[]; - permission: TeamPermission | undefined; + permission?: TeamPermission; }; diff --git a/packages/service/support/permission/auth/org.ts b/packages/service/support/permission/auth/org.ts new file mode 100644 index 000000000000..4262a8565323 --- /dev/null +++ b/packages/service/support/permission/auth/org.ts @@ -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 => { + 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 + // }; +}; diff --git a/packages/service/support/permission/controller.ts b/packages/service/support/permission/controller.ts index 90a336cedf2d..86da97e3ec37 100644 --- a/packages/service/support/permission/controller.ts +++ b/packages/service/support/permission/controller.ts @@ -11,10 +11,9 @@ import { import { Permission } from '@fastgpt/global/support/permission/controller'; import type { PermissionValueType, + ResourcePermissionType, ResourcePerWithGroup, - ResourcePerWithOrg, - ResourcePerWithTmbWithUser, - ResourcePermissionType + ResourcePerWithTmbWithUser } from '@fastgpt/global/support/permission/type'; import Cookie from 'cookie'; import { addMinutes } from 'date-fns'; @@ -25,6 +24,7 @@ import { authOpenApiKey } from '../openapi/auth'; import { getGroupsByTmbId } from './memberGroup/controllers'; import { MongoResourcePermission } from './schema'; import type { AuthModeType, ReqHeaderAuthType } from './type.d'; +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 @@ -190,14 +190,16 @@ export const getClbsAndGroupsWithInfo = async ({ path: 'groupId', select: 'name avatar' })) as ResourcePerWithGroup[], - (await MongoResourcePermission.find({ + MongoResourcePermission.find({ teamId, resourceId, resourceType, orgId: { $exists: true } - }).populate({ path: 'orgId', select: 'name avatar' })) as ResourcePerWithOrg[] + }) + .populate<{ org: OrgSchemaType }>({ path: 'org', select: 'name avatar' }) + .lean() ]); export const delResourcePermissionById = (id: string) => { @@ -218,7 +220,7 @@ export const delResourcePermission = ({ groupId?: string; orgId?: string; }) => { - // tmbId, groupId, orgId 三选一 + // either tmbId or groupId or orgId must be provided if (!tmbId && !groupId && !orgId) { return Promise.reject(CommonErrEnum.missingParams); } diff --git a/packages/service/support/permission/org/controllers.ts b/packages/service/support/permission/org/controllers.ts index 2be7740fa569..1a0aecef2d29 100644 --- a/packages/service/support/permission/org/controllers.ts +++ b/packages/service/support/permission/org/controllers.ts @@ -1,175 +1,174 @@ import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; -import { TeamPermission } from '@fastgpt/global/support/permission/user/controller'; -import { OrgMemberRole } from '@fastgpt/global/support/user/team/org/constant'; -import type { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type'; +import type { + OrgMemberSchemaType, + OrgSchemaType +} from '@fastgpt/global/support/user/team/org/type'; import type { ClientSession } from 'mongoose'; -import { getTmbInfoByTmbId } from '../../user/team/controller'; -import { parseHeaderCert } from '../controller'; -import type { AuthModeType, AuthResponseType } from '../type'; -import { MongoOrgMemberModel } from './orgMemberSchema'; 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 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 checkOrgRole = (role: OrgMemberRole, targetRole: OrgMemberRole) => { +// return compareRole(role, targetRole) >= 0; +// }; export const getOrgsByTeamId = async (teamId: string) => { const orgs = await MongoOrgModel.find({ teamId }) - .populate('members') + .populate<{ members: OrgMemberSchemaType }>('members') .lean(); return orgs; }; -export const getOrgAndChildren = async ({ - orgId, +export const getOrgsByTmbId = async ({ teamId, tmbId }: { teamId: string; tmbId: string }) => + MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean(); + +export const getChildrenByOrg = async ({ + org, + teamId, session }: { - orgId: string; + org: OrgSchemaType; + teamId: string; session?: ClientSession; }) => { - const org = await MongoOrgModel.findById(orgId, undefined, { session }).lean(); - if (!org) { - return Promise.reject(TeamErrEnum.orgNotExist); - } const children = await MongoOrgModel.find( - { - path: { - $regex: `^${org.path}/${org._id}` - } - }, + { teamId, path: { $regex: `^${org.path}/${org._id}` } }, undefined, - { session } + { + session + } ).lean(); - return { org, children }; + return children; }; -export const getOrgMemberRole = async ({ +export const getOrgAndChildren = async ({ orgId, - tmbId + teamId, + session }: { orgId: string; - tmbId: string; -}): Promise => { - 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; + teamId: string; + session?: ClientSession; +}) => { + const org = await MongoOrgModel.findOne({ _id: orgId, teamId }, undefined, { session }).lean(); 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; + const children = await getChildrenByOrg({ org, teamId, session }); + return { org, children }; }; -export const authOrgMember = async ({ - orgIds, - role, - req, - authToken = false, - authRoot = false, - authApiKey = false +export async function createRootOrg({ + teamId, + session }: { - orgIds: string | string[]; - role: `${OrgMemberRole}` | OrgMemberRole; -} & AuthModeType): Promise => { - 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 })); + 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); + // } - const [tmb, ...orgRoles] = await Promise.all([getTmbInfoByTmbId({ tmbId }), ...promises]); - if (tmb.permission.hasManagePer) { - return { - ...result, - permission: tmb.permission - }; - } + // Set the owner as the org admin + // await MongoOrgMemberModel.create( + // [ + // { + // orgId: org._id, + // tmbId: owner._id - const targetRole = OrgMemberRole[role]; - for (const orgRole of orgRoles) { - if (!orgRole || checkOrgRole(orgRole, targetRole)) { - return Promise.reject(TeamErrEnum.unAuthTeam); - } - } + // } + // ], + // { session } + // ); +} - return { - ...result, - permission: tmb.permission - }; -}; +// export const getOrgMemberRole = async ({ +// orgId, +// tmbId +// }: { +// orgId: string; +// tmbId: string; +// }): Promise => { +// 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; +// }; diff --git a/packages/service/support/permission/org/orgMemberSchema.ts b/packages/service/support/permission/org/orgMemberSchema.ts index b7ad34159b01..1b70c5556517 100644 --- a/packages/service/support/permission/org/orgMemberSchema.ts +++ b/packages/service/support/permission/org/orgMemberSchema.ts @@ -1,12 +1,20 @@ -import { OrgCollectionName, OrgMemberRole } from '@fastgpt/global/support/user/team/org/constant'; +import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant'; import { connectionMongo, getMongoModel } from '../../../common/mongo'; -import { TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant'; +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, @@ -16,18 +24,19 @@ export const OrgMemberSchema = new Schema({ type: Schema.Types.ObjectId, ref: TeamMemberCollectionName, required: true - }, - role: { - type: String, - enum: Object.values(OrgMemberRole), - required: true, - default: OrgMemberRole.member } + // role: { + // type: String, + // enum: Object.values(OrgMemberRole), + // required: true, + // default: OrgMemberRole.member + // } }); try { OrgMemberSchema.index( { + teamId: 1, orgId: 1, tmbId: 1 }, @@ -36,6 +45,7 @@ try { } ); OrgMemberSchema.index({ + teamId: 1, tmbId: 1 }); } catch (error) { diff --git a/packages/service/support/permission/org/orgSchema.ts b/packages/service/support/permission/org/orgSchema.ts index 3d5657f5276a..58e2e705b863 100644 --- a/packages/service/support/permission/org/orgSchema.ts +++ b/packages/service/support/permission/org/orgSchema.ts @@ -19,18 +19,14 @@ export const OrgSchema = new Schema( }, path: { type: String, - required: requiredStringPath + required: requiredStringPath // allow empty string, but not null }, name: { type: String, required: true }, - avatar: { - type: String - }, - description: { - type: String - }, + avatar: String, + description: String, updateTime: { type: Date, default: () => new Date() @@ -60,14 +56,12 @@ try { OrgSchema.index( { teamId: 1, - path: 1, - name: 1 + path: 1 }, { unique: true } ); - OrgSchema.index({ path: 1 }); } catch (error) { console.log(error); } diff --git a/packages/service/support/permission/schema.ts b/packages/service/support/permission/schema.ts index f914e1afab47..8e1bf5fc9c4b 100644 --- a/packages/service/support/permission/schema.ts +++ b/packages/service/support/permission/schema.ts @@ -44,6 +44,13 @@ export const ResourcePermissionSchema = new Schema({ } }); +ResourcePermissionSchema.virtual('org', { + ref: OrgCollectionName, + localField: 'orgId', + foreignField: '_id', + justOne: true +}); + try { ResourcePermissionSchema.index( { diff --git a/packages/service/support/user/team/controller.ts b/packages/service/support/user/team/controller.ts index bc72a908977b..63a90c3f4bce 100644 --- a/packages/service/support/user/team/controller.ts +++ b/packages/service/support/user/team/controller.ts @@ -15,6 +15,7 @@ import { TeamDefaultPermissionVal } from '@fastgpt/global/support/permission/use import { MongoMemberGroupModel } from '../../permission/memberGroup/memberGroupSchema'; import { mongoSessionRun } from '../../../common/mongo/sessionRun'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; +import { createRootOrg } from '../../permission/org/controllers'; async function getTeamMember(match: Record): Promise { const tmb = (await MongoTeamMember.findOne(match).populate('teamId')) as TeamMemberWithTeamSchema; @@ -128,7 +129,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); diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 7b04bd544ab0..910a3d840d58 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -84,6 +84,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", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index f120ed2e7b3f..ce4b99b7a71e 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -84,6 +84,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": "您的身份校验未通过", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index e9f692fc1b7d..2bcb5e0d87da 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -84,6 +84,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": "身份驗證未通過", diff --git a/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx index ac2b860a5440..24611a644ba7 100644 --- a/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx +++ b/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx @@ -10,7 +10,6 @@ import { ModalBody, ModalFooter } from '@chakra-ui/react'; -import { DEFAULT_ORG_AVATAR } from '@fastgpt/global/common/system/constants'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import MyAvatar from '@fastgpt/web/components/common/Avatar'; import MyIcon from '@fastgpt/web/components/common/Icon'; @@ -38,23 +37,21 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) { useContextSelector(CollaboratorContext, (v) => v); const [searchText, setSearchText] = useState(''); - const { - data: [members = [], groups = [], orgs = []] = [], - loading: loadingMembersAndGroups - } = useRequest2( - async () => { - if (!userInfo?.team?.teamId) return [[], []]; - return await Promise.all([ - loadAndGetTeamMembers(true), - loadAndGetGroups(true), - loadAndGetOrgs(true) - ]); - }, - { - manual: false, - refreshDeps: [userInfo?.team?.teamId] - } - ); + const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } = + useRequest2( + async () => { + if (!userInfo?.team?.teamId) return [[], []]; + return Promise.all([ + loadAndGetTeamMembers(true), + loadAndGetGroups(true), + loadAndGetOrgs(true) + ]); + }, + { + manual: false, + refreshDeps: [userInfo?.team?.teamId] + } + ); const filterMembers = useMemo(() => { return members.filter((item) => { @@ -165,11 +162,7 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) { onClick={onChange} > - + {org.name} @@ -262,7 +255,7 @@ function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) { - {t('user:has_chosen') + ': '}{' '} + {`${t('user:has_chosen')}: `} {selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length} diff --git a/projects/app/src/components/support/permission/MemberManager/ManageModal.tsx b/projects/app/src/components/support/permission/MemberManager/ManageModal.tsx index 9376dde338dd..b7e95d1a2dd4 100644 --- a/projects/app/src/components/support/permission/MemberManager/ManageModal.tsx +++ b/projects/app/src/components/support/permission/MemberManager/ManageModal.tsx @@ -1,6 +1,5 @@ import { useUserStore } from '@/web/support/user/useUserStore'; import { Flex, ModalBody, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@chakra-ui/react'; -import { DEFAULT_ORG_AVATAR } from '@fastgpt/global/common/system/constants'; import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import Avatar from '@fastgpt/web/components/common/Avatar'; @@ -66,15 +65,7 @@ function ManageModal({ onClose }: ManageModalProps) { > - + {item.name === DefaultGroupName ? userInfo?.team.teamName : item.name} diff --git a/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx b/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx index 89e7bacdd540..fe85f36a3eaa 100644 --- a/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx +++ b/projects/app/src/components/support/permission/MemberManager/MemberListCard.tsx @@ -1,6 +1,5 @@ import { useUserStore } from '@/web/support/user/useUserStore'; import { Box, type BoxProps, Flex } from '@chakra-ui/react'; -import { DEFAULT_ORG_AVATAR } from '@fastgpt/global/common/system/constants'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import Avatar from '@fastgpt/web/components/common/Avatar'; import MyBox from '@fastgpt/web/components/common/MyBox'; @@ -37,13 +36,7 @@ const MemberListCard = ({ tagStyle, ...props }: MemberListCardProps) => { colorSchema="white" {...tagStyle} > - + {member.name === DefaultGroupName ? userInfo?.team.teamName : member.name} diff --git a/projects/app/src/pages/account/team/components/OrgManage/OrgInfoModal.tsx b/projects/app/src/pages/account/team/components/OrgManage/OrgInfoModal.tsx index 56b7850cbdf5..5fcb2c16e21d 100644 --- a/projects/app/src/pages/account/team/components/OrgManage/OrgInfoModal.tsx +++ b/projects/app/src/pages/account/team/components/OrgManage/OrgInfoModal.tsx @@ -54,7 +54,7 @@ function OrgInfoModal({ (data: OrgFormType, parentId: string) => { return postCreateOrg({ name: data.name, - avatar: data.avatar, + avatar: data.avatar !== DEFAULT_ORG_AVATAR ? data.avatar : undefined, parentId, description: data.description }); diff --git a/projects/app/src/pages/account/team/components/OrgManage/OrgMemberModal.tsx b/projects/app/src/pages/account/team/components/OrgManage/OrgMemberModal.tsx index d36c0837ae80..1bab507501a2 100644 --- a/projects/app/src/pages/account/team/components/OrgManage/OrgMemberModal.tsx +++ b/projects/app/src/pages/account/team/components/OrgManage/OrgMemberModal.tsx @@ -1,5 +1,4 @@ import { putUpdateOrgMembers } from '@/web/support/user/team/org/api'; -import { useUserStore } from '@/web/support/user/useUserStore'; import { Box, Button, @@ -12,15 +11,12 @@ import { } from '@chakra-ui/react'; import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants'; import type { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant'; -import type { OrgMemberRole } from '@fastgpt/global/support/user/team/org/constant'; import Avatar from '@fastgpt/web/components/common/Avatar'; import MyIcon from '@fastgpt/web/components/common/Icon'; import type { IconNameType } from '@fastgpt/web/components/common/Icon/type'; import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import MyModal from '@fastgpt/web/components/common/MyModal'; -import Tag from '@fastgpt/web/components/common/Tag'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { useToast } from '@fastgpt/web/hooks/useToast'; import { useTranslation } from 'next-i18next'; import type React from 'react'; import { useEffect, useMemo, useState } from 'react'; @@ -49,8 +45,6 @@ function OrgMemberModal({ onClose, editOrgId }: { onClose: () => void; editOrgId // 2. Owner/Admin can manage members // 3. Owner can add/remove admins const { t } = useTranslation(); - const { userInfo } = useUserStore(); - const { toast } = useToast(); const [hoveredMemberId, setHoveredMemberId] = useState(undefined); const { members: allMembers, @@ -61,9 +55,7 @@ function OrgMemberModal({ onClose, editOrgId }: { onClose: () => void; editOrgId const org = useMemo(() => orgs.find((item) => item._id === editOrgId), [editOrgId, orgs]); - const [members, setMembers] = useState<{ tmbId: string; role: `${OrgMemberRole}` }[]>( - org?.members || [] - ); + const [members, setMembers] = useState<{ tmbId: string }[]>(org?.members || []); useEffect(() => { setMembers(org?.members || []); @@ -96,51 +88,11 @@ function OrgMemberModal({ onClose, editOrgId }: { onClose: () => void; editOrgId return members.find((item) => item.tmbId === memberId); }; - const myRole = useMemo(() => { - if (userInfo?.team.permission.hasManagePer) { - return 'owner'; - } - return members.find((item) => item.tmbId === userInfo?.team.tmbId)?.role ?? 'member'; - }, [members, userInfo]); - const handleToggleSelect = (memberId: string) => { - if ( - myRole === 'owner' && - memberId === org?.members.find((item) => item.role === 'owner')?.tmbId - ) { - toast({ - title: t('user:team.group.toast.can_not_delete_owner'), - status: 'error' - }); - return; - } - - if ( - myRole === 'admin' && - org?.members.find((item) => String(item.tmbId) === memberId)?.role !== 'member' - ) { - return; - } - if (isSelected(memberId)) { setMembers(members.filter((item) => item.tmbId !== memberId)); } else { - setMembers([...members, { tmbId: memberId, role: 'member' }]); - } - }; - - const handleToggleAdmin = (memberId: string) => { - if (myRole === 'owner' && isSelected(memberId)) { - const oldRole = members.find((item) => item.tmbId === memberId)?.role; - if (oldRole === 'admin') { - setMembers( - members.map((item) => (item.tmbId === memberId ? { ...item, role: 'member' } : item)) - ); - } else { - setMembers( - members.map((item) => (item.tmbId === memberId ? { ...item, role: 'admin' } : item)) - ); - } + setMembers([...members, { tmbId: memberId }]); } }; @@ -214,7 +166,7 @@ function OrgMemberModal({ onClose, editOrgId }: { onClose: () => void; editOrgId py="2" px={3} borderRadius={'md'} - key={member.tmbId + member.role} + key={member.tmbId} _hover={{ bg: 'myGray.50' }} _notLast={{ mb: 2 }} > @@ -228,58 +180,13 @@ function OrgMemberModal({ onClose, editOrgId }: { onClose: () => void; editOrgId {allMembers.find((item) => item.tmbId === member.tmbId)?.memberName} - - {(() => { - if (member.role === 'owner') { - return ( - - {t('user:team.group.role.owner')} - - ); - } - if (member.role === 'admin') { - return ( - - {t('user:team.group.role.admin')} - {myRole === 'owner' && ( - handleToggleAdmin(member.tmbId)} - /> - )} - - ); - } - if (member.role === 'member') { - return ( - myRole === 'owner' && - hoveredMemberId === member.tmbId && ( - handleToggleAdmin(member.tmbId)} - > - {t('user:team.group.set_as_admin')} - - ) - ); - } - })()} - - {(myRole === 'owner' || (myRole === 'admin' && member.role === 'member')) && ( - handleToggleSelect(member.tmbId)} - /> - )} + handleToggleSelect(member.tmbId)} + /> ); })} diff --git a/projects/app/src/pages/account/team/components/OrgManage/OrgMoveModal.tsx b/projects/app/src/pages/account/team/components/OrgManage/OrgMoveModal.tsx index 5a356bef728e..d80a4e98de0d 100644 --- a/projects/app/src/pages/account/team/components/OrgManage/OrgMoveModal.tsx +++ b/projects/app/src/pages/account/team/components/OrgManage/OrgMoveModal.tsx @@ -1,7 +1,7 @@ import { putMoveOrg, putMoveOrgMember } from '@/web/support/user/team/org/api'; -import { Button, Modal, ModalBody, ModalFooter } from '@chakra-ui/react'; +import { Button, ModalBody, ModalFooter } from '@chakra-ui/react'; import type { OrgType } from '@fastgpt/global/support/user/team/org/type'; -import type { TeamMemberItemType, TeamTmbItemType } from '@fastgpt/global/support/user/team/type'; +import type { TeamTmbItemType } from '@fastgpt/global/support/user/team/type'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useTranslation } from 'next-i18next'; diff --git a/projects/app/src/pages/account/team/components/OrgManage/OrgTree.tsx b/projects/app/src/pages/account/team/components/OrgManage/OrgTree.tsx index 7d91f6fe82ad..7fab83762891 100644 --- a/projects/app/src/pages/account/team/components/OrgManage/OrgTree.tsx +++ b/projects/app/src/pages/account/team/components/OrgManage/OrgTree.tsx @@ -1,5 +1,4 @@ import { Box, HStack, Text, VStack } from '@chakra-ui/react'; -import { DEFAULT_ORG_AVATAR } from '@fastgpt/global/common/system/constants'; import type { OrgType } from '@fastgpt/global/support/user/team/org/type'; import Avatar from '@fastgpt/web/components/common/Avatar'; import { useToggle } from 'ahooks'; @@ -46,7 +45,7 @@ function OrgTreeNode({ )} selectOrg?.(org)} cursor="pointer"> - + {org.name} diff --git a/projects/app/src/pages/account/team/components/OrgManage/index.tsx b/projects/app/src/pages/account/team/components/OrgManage/index.tsx index f089d7bdef79..01756a1a65cb 100644 --- a/projects/app/src/pages/account/team/components/OrgManage/index.tsx +++ b/projects/app/src/pages/account/team/components/OrgManage/index.tsx @@ -19,7 +19,6 @@ import { VStack, useDisclosure } from '@chakra-ui/react'; -import { DEFAULT_ORG_AVATAR } from '@fastgpt/global/common/system/constants'; import type { OrgType } from '@fastgpt/global/support/user/team/org/type'; import Avatar from '@fastgpt/web/components/common/Avatar'; import MyIcon from '@fastgpt/web/components/common/Icon'; @@ -72,23 +71,18 @@ function MemberTable() { const { t } = useTranslation(); const { userInfo } = useUserStore(); - const { orgs, refetchOrgs, members, refetchMembers, initOrg, isLoading } = useContextSelector( + const { orgs, refetchOrgs, members, refetchMembers, isLoading } = useContextSelector( TeamContext, (v) => v ); - const [currentOrg, setCurrentOrg] = useState(); + const [currentOrg, setCurrentOrg] = useState(); // Set current org by hash useEffect(() => { - if (orgs.length > 0) { - const hash = window.location.hash.substring(1); - const initialOrg = orgs.find((org) => org._id === hash) || orgs[0]; - setCurrentOrg(initialOrg); - } else if (!isLoading) { - console.log('initOrg'); - initOrg(); - } - }, [orgs, initOrg, isLoading]); + const hash = window.location.hash.substring(1); + const initialOrg = orgs.find((org) => org._id === hash) || orgs[0]; + setCurrentOrg(initialOrg); + }, [orgs, isLoading]); // Update hash when current org changes useEffect(() => { if (currentOrg) { @@ -194,7 +188,7 @@ function MemberTable() { setCurrentOrg(org)}> - + {org.count} - {member.role !== 'owner' && ( - + + } + menuList={[ + { + children: [ + // { + // icon: 'edit', + // label: t('account_team:remark'), + // onClick: () => { + // // TODO + // console.log(member.tmbId); + // } + // }, + { + icon: 'common/file/move', + label: t('common:Move'), + onClick: () => + setMovingTmb({ tmbId: member.tmbId, orgId: currentOrg!._id }) + }, + { + icon: 'delete', + label: t('account_team:delete'), + type: 'danger', + onClick: () => deleteMemberHandler(currentOrg!._id, member.tmbId) + } + ] } - menuList={[ - { - children: [ - // { - // icon: 'edit', - // label: t('account_team:remark'), - // onClick: () => { - // // TODO - // console.log(member.tmbId); - // } - // }, - { - icon: 'common/file/move', - label: t('common:Move'), - onClick: () => - setMovingTmb({ tmbId: member.tmbId, orgId: currentOrg!._id }) - }, - { - icon: 'delete', - label: t('account_team:delete'), - type: 'danger', - onClick: () => deleteMemberHandler(currentOrg!._id, member.tmbId) - } - ] - } - ]} - /> - )} + ]} + /> ); @@ -297,11 +283,7 @@ function MemberTable() { void; refetchGroups: () => void; refetchOrgs: () => void; - initOrg: () => void; searchKey: string; setSearchKey: React.Dispatch>; teamSize: number; @@ -58,9 +57,6 @@ export const TeamContext = createContext({ refetchOrgs: function (): void { throw new Error('Function not implemented.'); }, - initOrg: function (): void { - throw new Error('Function not implemented.'); - }, searchKey: '', setSearchKey: function (_value: React.SetStateAction): void { @@ -128,10 +124,6 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode }) refreshDeps: [userInfo?.team?.teamId] }); - const { runAsync: initOrg } = useRequest2(getInitOrg, { - onSuccess: refetchOrgs - }); - const isLoading = isLoadingTeams || isSwitchingTeam || loadingMembers || isLoadingGroups || isLoadingOrgs; @@ -151,7 +143,6 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode }) refetchGroups, orgs, refetchOrgs, - initOrg, teamSize: members.length }; diff --git a/projects/app/src/web/support/user/team/org/api.ts b/projects/app/src/web/support/user/team/org/api.ts index 15e865ee2e97..a8b2fbc74327 100644 --- a/projects/app/src/web/support/user/team/org/api.ts +++ b/projects/app/src/web/support/user/team/org/api.ts @@ -8,8 +8,6 @@ import type { OrgType } from '@fastgpt/global/support/user/team/org/type'; export const getOrgList = () => GET('/proApi/support/user/team/org/list'); -export const getInitOrg = () => GET('/proApi/support/user/team/org/init'); - export const postCreateOrg = (data: postCreateOrgData) => POST('/proApi/support/user/team/org/create', data);