Group role (#2993)

* feat: app/dataset support group (#2898)

* pref: member-group (#2862)

* feat: group list ordered by updateTime

* fix: transfer ownership of group when deleting member

* fix: i18n fix

* feat: can not set member as admin/owner when user is not active

* fix: GroupInfoModal hover input do not change color

* fix(fe): searchinput do not scroll

* feat: app collaborator with group, remove default permission

* feat: dataset collaborator with group, remove default permission

* chore(test): pref mock

* chore: remove useless code

* chore: adjust

* fix: add self as collaborator when creating folder

* fix(fe): folder manage menu do not show when user has write permission
only

* fix: dataset folder create

* feat: Add code comment

* Pref: app move (#2952)

* perf: app schema

* doc

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
Archer
2024-10-25 19:39:11 +08:00
committed by GitHub
parent 74d58d562b
commit f89452acdd
60 changed files with 1142 additions and 1094 deletions

View File

@@ -1,4 +1,8 @@
import { UpdateClbPermissionProps } from '../../support/permission/collaborator';
import { RequireOnlyOne } from '../../common/type/utils';
import {
UpdateClbPermissionProps,
UpdatePermissionBody
} from '../../support/permission/collaborator';
import { PermissionValueType } from '../../support/permission/type';
export type UpdateAppCollaboratorBody = UpdateClbPermissionProps & {
@@ -7,5 +11,7 @@ export type UpdateAppCollaboratorBody = UpdateClbPermissionProps & {
export type AppCollaboratorDeleteParams = {
appId: string;
} & RequireOnlyOne<{
tmbId: string;
};
groupId: string;
}>;

View File

@@ -10,7 +10,6 @@ import { SelectedDatasetType } from '../workflow/api';
import { DatasetSearchModeEnum } from '../dataset/constants';
import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d';
import { StoreEdgeItemType } from '../workflow/type/edge';
import { PermissionSchemaType, PermissionValueType } from '../../support/permission/type';
import { AppPermission } from '../../support/permission/app/controller';
import { ParentIdType } from '../../common/parentFolder/type';
import { FlowNodeInputTypeEnum } from 'core/workflow/node/constant';
@@ -45,7 +44,11 @@ export type AppSchema = {
inited?: boolean;
teamTags: string[];
} & PermissionSchemaType;
inheritPermission?: boolean;
// abandon
defaultPermission?: number;
};
export type AppListItemType = {
_id: string;
@@ -57,7 +60,9 @@ export type AppListItemType = {
updateTime: Date;
pluginData?: AppSchema['pluginData'];
permission: AppPermission;
} & PermissionSchemaType;
inheritPermission?: boolean;
private?: boolean;
};
export type AppDetailType = AppSchema & {
permission: AppPermission;

View File

@@ -1,5 +1,6 @@
import { UpdateClbPermissionProps } from '../../support/permission/collaborator';
import { PermissionValueType } from '../../support/permission/type';
import { RequireOnlyOne } from '../../common/type/utils';
export type UpdateDatasetCollaboratorBody = UpdateClbPermissionProps & {
datasetId: string;
@@ -7,5 +8,7 @@ export type UpdateDatasetCollaboratorBody = UpdateClbPermissionProps & {
export type DatasetCollaboratorDeleteParams = {
datasetId: string;
} & RequireOnlyOne<{
tmbId: string;
};
groupId: string;
}>;

View File

@@ -1,4 +1,3 @@
import { PermissionSchemaType } from '../../support/permission/type';
import type { LLMModelItemType, VectorModelItemType } from '../../core/ai/model.d';
import { PermissionTypeEnum } from '../../support/permission/constant';
import { PushDatasetDataChunkProps } from './api';
@@ -32,8 +31,11 @@ export type DatasetSchemaType = {
selector: string;
};
externalReadUrl?: string;
} & PermissionSchemaType;
// } & PermissionSchemaType;
inheritPermission: boolean;
// abandon
defaultPermission?: number;
};
export type DatasetCollectionSchemaType = {
_id: string;
@@ -146,7 +148,9 @@ export type DatasetListItemType = {
type: `${DatasetTypeEnum}`;
permission: DatasetPermission;
vectorModel: VectorModelItemType;
} & PermissionSchemaType;
inheritPermission: boolean;
private?: boolean;
};
export type DatasetItemType = Omit<DatasetSchemaType, 'vectorModel' | 'agentModel'> & {
vectorModel: VectorModelItemType;

View File

@@ -4,11 +4,13 @@ import { PermissionValueType } from './type';
export type CollaboratorItemType = {
teamId: string;
tmbId: string;
permission: Permission;
name: string;
avatar: string;
};
} & RequireOnlyOne<{
tmbId: string;
groupId: string;
}>;
export type UpdateClbPermissionProps = {
members?: string[];

View File

@@ -1,4 +1,3 @@
import { Permission } from './controller';
import { PermissionListType } from './type';
import { i18nT } from '../../../web/i18n/utils';
export enum AuthUserTypeEnum {

View File

@@ -1,6 +1,7 @@
import { RequireOnlyOne } from '../../common/type/utils';
import { TeamMemberWithUserSchema } from '../user/team/type';
import { AuthUserTypeEnum, PermissionKeyEnum, PerResourceTypeEnum } from './constant';
import { MemberGroupSchemaType } from './memberGroup/type';
// 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.
@@ -33,6 +34,10 @@ export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> &
tmbId: TeamMemberWithUserSchema;
};
export type ResourcePerWithGroup = Omit<ResourcePermissionType, 'groupId'> & {
groupId: MemberGroupSchemaType;
};
export type PermissionSchemaType = {
defaultPermission: PermissionValueType;
inheritPermission: boolean;

View File

@@ -5,8 +5,6 @@ import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
import { getPermissionSchema } from '@fastgpt/global/support/permission/utils';
export const AppCollectionName = 'apps';
@@ -111,8 +109,13 @@ const AppSchema = new Schema({
inited: {
type: Boolean
},
inheritPermission: {
type: Boolean,
default: true
},
...getPermissionSchema(AppDefaultPermissionVal)
// abandoned
defaultPermission: Number
});
AppSchema.index({ teamId: 1, updateTime: -1 });

View File

@@ -9,8 +9,6 @@ import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
import { getPermissionSchema } from '@fastgpt/global/support/permission/utils';
import type { DatasetSchemaType } from '@fastgpt/global/core/dataset/type.d';
export const DatasetCollectionName = 'datasets';
@@ -88,7 +86,13 @@ const DatasetSchema = new Schema({
externalReadUrl: {
type: String
},
...getPermissionSchema(DatasetDefaultPermissionVal)
inheritPermission: {
type: Boolean,
default: true
},
// abandoned
defaultPermission: Number
});
try {

View File

@@ -13,6 +13,7 @@ import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { splitCombinePluginId } from '../../../core/app/plugin/controller';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { AuthModeType, AuthResponseType } from '../type';
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
export const authPluginByTmbId = async ({
tmbId,
@@ -60,7 +61,6 @@ export const authAppByTmbId = async ({
if (isRoot) {
return {
...app,
defaultPermission: app.defaultPermission,
permission: new AppPermission({ isOwner: true })
};
}
@@ -71,7 +71,7 @@ export const authAppByTmbId = async ({
const isOwner = tmbPer.isOwner || String(app.tmbId) === String(tmbId);
const { Per, defaultPermission } = await (async () => {
const { Per } = await (async () => {
if (
AppFolderTypeList.includes(app.type) ||
app.inheritPermission === false ||
@@ -86,10 +86,9 @@ export const authAppByTmbId = async ({
resourceId: appId,
resourceType: PerResourceTypeEnum.app
});
const Per = new AppPermission({ per: rp ?? app.defaultPermission, isOwner });
const Per = new AppPermission({ per: rp ?? AppDefaultPermissionVal, isOwner });
return {
Per,
defaultPermission: app.defaultPermission
Per
};
} else {
// is not folder and inheritPermission is true and is not root folder.
@@ -104,8 +103,7 @@ export const authAppByTmbId = async ({
isOwner
});
return {
Per,
defaultPermission: parent.defaultPermission
Per
};
}
})();
@@ -116,7 +114,6 @@ export const authAppByTmbId = async ({
return {
...app,
defaultPermission,
permission: Per
};
})();

View File

@@ -10,12 +10,17 @@ import { MongoResourcePermission } from './schema';
import { ClientSession } from 'mongoose';
import {
PermissionValueType,
ResourcePermissionType
ResourcePermissionType,
ResourcePerWithGroup,
ResourcePerWithTmbWithUser
} from '@fastgpt/global/support/permission/type';
import { bucketNameMap } from '@fastgpt/global/common/file/constants';
import { addMinutes } from 'date-fns';
import { getGroupsByTmbId } from './memberGroup/controllers';
import { Permission } from '@fastgpt/global/support/permission/controller';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
/** get resource permission for a team member
* If there is no permission for the team member, it will return undefined
@@ -123,20 +128,94 @@ export async function getResourceAllClbs({
).lean();
}
export async function getResourceClbsAndGroups({
resourceId,
resourceType,
teamId,
session
}: {
resourceId: ParentIdType;
resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>;
teamId: string;
session: ClientSession;
}) {
return MongoResourcePermission.find(
{
resourceId,
resourceType,
teamId
},
undefined,
{ session }
).lean();
}
export const getClbsAndGroupsWithInfo = async ({
resourceId,
resourceType,
teamId
}: {
resourceId: ParentIdType;
resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>;
teamId: string;
}) =>
Promise.all([
(await MongoResourcePermission.find({
teamId,
resourceId,
resourceType,
tmbId: {
$exists: true
}
}).populate({
path: 'tmbId',
select: 'name userId',
populate: {
path: 'userId',
select: 'avatar'
}
})) as ResourcePerWithTmbWithUser[],
(await MongoResourcePermission.find({
teamId,
resourceId,
resourceType,
groupId: {
$exists: true
}
}).populate({
path: 'groupId',
select: 'name avatar'
})) as ResourcePerWithGroup[]
]);
export const delResourcePermissionById = (id: string) => {
return MongoResourcePermission.findByIdAndRemove(id);
};
export const delResourcePermission = ({
session,
tmbId,
groupId,
...props
}: {
resourceType: PerResourceTypeEnum;
teamId: string;
resourceId: string;
tmbId: string;
session?: ClientSession;
tmbId?: string;
groupId?: string;
}) => {
return MongoResourcePermission.deleteOne(props, { session });
// tmbId or groupId only one and not both
if (!!tmbId === !!groupId) {
return Promise.reject(CommonErrEnum.missingParams);
}
return MongoResourcePermission.deleteOne(
{
...(tmbId ? { tmbId } : {}),
...(groupId ? { groupId } : {}),
...props
},
{ session }
);
};
/* 下面代码等迁移 */

View File

@@ -20,6 +20,7 @@ import { MongoDatasetData } from '../../../core/dataset/data/schema';
import { AuthModeType, AuthResponseType } from '../type';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
export const authDatasetByTmbId = async ({
tmbId,
@@ -62,7 +63,7 @@ export const authDatasetByTmbId = async ({
const isOwner = tmbPer.isOwner || String(dataset.tmbId) === String(tmbId);
// get dataset permission or inherit permission from parent folder.
const { Per, defaultPermission } = await (async () => {
const { Per } = await (async () => {
if (
dataset.type === DatasetTypeEnum.folder ||
dataset.inheritPermission === false ||
@@ -78,12 +79,11 @@ export const authDatasetByTmbId = async ({
resourceType: PerResourceTypeEnum.dataset
});
const Per = new DatasetPermission({
per: rp ?? dataset.defaultPermission,
per: rp ?? DatasetDefaultPermissionVal,
isOwner
});
return {
Per,
defaultPermission: dataset.defaultPermission
Per
};
} else {
// is not folder and inheritPermission is true and is not root folder.
@@ -100,8 +100,7 @@ export const authDatasetByTmbId = async ({
});
return {
Per,
defaultPermission: parent.defaultPermission
Per
};
}
})();
@@ -112,7 +111,6 @@ export const authDatasetByTmbId = async ({
return {
...dataset,
defaultPermission,
permission: Per
};
})();
@@ -179,14 +177,15 @@ export async function authDatasetCollection({
tmbId,
datasetId: collection.datasetId._id,
per,
isRoot: isRootFromHeader || isRoot
isRoot: isRootFromHeader
});
return {
teamId,
tmbId,
collection,
permission: dataset.permission
permission: dataset.permission,
isRoot: isRootFromHeader
};
}
@@ -231,7 +230,8 @@ export async function authDatasetFile({
teamId,
tmbId,
file,
permission
permission,
isRoot
};
} catch (error) {
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);

View File

@@ -1,9 +1,9 @@
import { mongoSessionRun } from '../../common/mongo/sessionRun';
import { MongoResourcePermission } from './schema';
import { ClientSession, Model } from 'mongoose';
import { NullPermission, PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { getResourceAllClbs } from './controller';
import { getResourceClbsAndGroups } from './controller';
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
@@ -28,7 +28,6 @@ export async function syncChildrenPermission({
resourceModel,
session,
defaultPermission,
collaborators
}: {
resource: SyncChildrenPermissionResourceType;
@@ -42,7 +41,6 @@ export async function syncChildrenPermission({
// should be provided when inheritPermission is true
session: ClientSession;
defaultPermission?: PermissionValueType;
collaborators?: UpdateCollaboratorItem[];
}) {
// only folder has permission
@@ -76,19 +74,6 @@ export async function syncChildrenPermission({
}
if (!children.length) return;
// Sync default permission
if (defaultPermission !== undefined) {
await resourceModel.updateMany(
{
_id: { $in: children }
},
{
defaultPermission
},
{ session }
);
}
// sync the resource permission
if (collaborators) {
// Update the collaborators of all children
@@ -124,28 +109,20 @@ export async function resumeInheritPermission({
const isFolder = folderTypeList.includes(resource.type);
const fn = async (session: ClientSession) => {
const parentResource = await resourceModel
.findById(resource.parentId, 'defaultPermission')
.lean<SyncChildrenPermissionResourceType & { defaultPermission: PermissionValueType }>()
.session(session);
const parentDefaultPermissionVal = parentResource?.defaultPermission ?? NullPermission;
// update the resource permission
await resourceModel.updateOne(
{
_id: resource._id
},
{
inheritPermission: true,
defaultPermission: parentDefaultPermissionVal
inheritPermission: true
},
{ session }
);
// Folder resource, need to sync children
if (isFolder) {
const parentClbs = await getResourceAllClbs({
const parentClbsAndGroups = await getResourceClbsAndGroups({
resourceId: resource.parentId,
teamId: resource.teamId,
resourceType,
@@ -155,7 +132,7 @@ export async function resumeInheritPermission({
// sync self
await syncCollaborators({
resourceType,
collaborators: parentClbs,
collaborators: parentClbsAndGroups,
teamId: resource.teamId,
resourceId: resource._id,
session
@@ -169,8 +146,7 @@ export async function resumeInheritPermission({
folderTypeList,
resourceType,
session,
defaultPermission: parentDefaultPermissionVal,
collaborators: parentClbs
collaborators: parentClbsAndGroups
});
} else {
// Not folder, delete all clb
@@ -215,6 +191,7 @@ export async function syncCollaborators({
resourceId,
resourceType: resourceType,
tmbId: item.tmbId,
groupId: item.groupId,
permission: item.permission
})),
{

View File

@@ -64,7 +64,7 @@ export const getGroupsByTmbId = async ({
groupId: {
$exists: true
},
role: role ? { $in: role } : undefined
...(role ? { role: { $in: role } } : {})
})
.populate('groupId')
.lean()

View File

@@ -28,5 +28,6 @@ export type AuthResponseType<T extends Permission = Permission> = {
authType?: `${AuthUserTypeEnum}`;
appId?: string;
apikey?: string;
isRoot: boolean;
permission: T;
};

View File

@@ -8,7 +8,7 @@ import { TeamPermission } from '@fastgpt/global/support/permission/user/controll
/* auth user role */
export async function authUserPer(props: AuthModeType): Promise<
AuthResponseType & {
AuthResponseType<TeamPermission> & {
tmb: TeamTmbItemType;
}
> {

View File

@@ -71,6 +71,7 @@
"modules.Title is required": "模块名不能为空",
"month.unit": "号",
"move_app": "移动应用",
"move.hint": "移动后,所选应用/文件夹将继承新文件夹的权限设置,原先的权限设置失效。",
"not_json_file": "请选择JSON文件",
"or_drag_JSON": "或拖入JSON文件",
"paste_config": "粘贴配置",

View File

@@ -20,6 +20,7 @@
"Folder": "文件夹",
"Login": "登录",
"Move": "移动",
"move.confirm": "确认移动",
"Name": "名称",
"None": "无",
"Rename": "重命名",
@@ -82,6 +83,8 @@
"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.group_name_duplicate": "群组名称重复",
"code_error.team_error.user_not_active": "用户未接受或已离开团队",
"code_error.token_error_code.403": "登录状态无效,请重新登录",
"code_error.user_error.balance_not_enough": "账号余额不足~",
"code_error.user_error.bin_visitor": "您的身份校验未通过",
@@ -915,7 +918,7 @@
"permission.Permission config": "权限配置",
"permission.Private": "私有",
"permission.Private Tip": "仅自己可用",
"permission.Public": "团队",
"permission.Public": "协作",
"permission.Public Tip": "团队所有成员可使用",
"permission.Remove InheritPermission Confirm": "此操作会导致权限继承失效,是否进行?",
"permission.Resume InheritPermission Confirm": "是否恢复为继承父级文件夹的权限?",
@@ -1194,7 +1197,7 @@
"user.team.invite.Reject Confirm": "确认拒绝该邀请?",
"user.team.invite.accept": "接受",
"user.team.invite.reject": "拒绝",
"user.team.member.Confirm Leave": "确认离开该团队?",
"user.team.member.Confirm Leave": "确认离开该团队?\n退出后您在该团队所有的资源 应用、知识库、文件夹、管理的群组等)均转让给团队所有者。",
"user.team.member.active": "已加入",
"user.team.member.reject": "拒绝",
"user.team.member.waiting": "待接受",

View File

@@ -34,5 +34,6 @@
"website_dataset_desc": "Web 站点同步允许你直接使用一个网页链接构建知识库",
"permission.des.read": "可查看知识库内容",
"permission.des.write": "可增加和变更知识库内容",
"permission.des.manage": "可管理整个知识库数据和信息"
}
"permission.des.manage": "可管理整个知识库数据和信息",
"move.hint": "移动后,所选知识库/文件夹将继承新文件夹的权限设置,原先的权限设置失效。"
}