dataset inheritance permission (#2151)

* refactor: dataset create and update api

* chore: defaultpermission & resume fe

* refactor: database auth

* fix(ts): add inheritPermission into default data types

* chore: adjust the code

* fix: list api type filter

* fix: query condition
This commit is contained in:
Finley Ge
2024-07-25 19:03:24 +08:00
committed by GitHub
parent 5906daff9f
commit 65515e7952
20 changed files with 481 additions and 199 deletions

View File

@@ -1,23 +1,30 @@
import type { NextApiRequest } from 'next';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import type { CreateDatasetParams } from '@/global/core/dataset/api.d';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { getLLMModel, getVectorModel, getDatasetModel } from '@fastgpt/service/core/ai/model';
import { checkTeamDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
import { NullPermission, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { NextAPI } from '@/service/middleware/entry';
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
async function handler(req: NextApiRequest) {
export type DatasetCreateQuery = {};
export type DatasetCreateBody = CreateDatasetParams;
export type DatasetCreateResponse = string;
async function handler(
req: ApiRequestProps<DatasetCreateBody, DatasetCreateQuery>
): Promise<DatasetCreateResponse> {
const {
parentId,
name,
type = DatasetTypeEnum.dataset,
avatar,
vectorModel = global.vectorModels[0].model,
agentModel = getDatasetModel().model,
defaultPermission = NullPermission
} = req.body as CreateDatasetParams;
agentModel = getDatasetModel().model
} = req.body;
// auth
const { teamId, tmbId } = await authUserPer({
@@ -31,25 +38,23 @@ async function handler(req: NextApiRequest) {
const vectorModelStore = getVectorModel(vectorModel);
const agentModelStore = getLLMModel(agentModel);
if (!vectorModelStore || !agentModelStore) {
throw new Error('vectorModel or qaModel is invalid'); // TODO: use enum code
return Promise.reject(DatasetErrEnum.invalidVectorModelOrQAModel);
}
// check limit
await checkTeamDatasetLimit(teamId);
const { _id } = await MongoDataset.create({
...parseParentIdInMongo(parentId),
name,
teamId,
tmbId,
vectorModel,
agentModel,
avatar,
parentId: parentId || null,
type,
defaultPermission
type
});
return _id;
}
export default NextAPI(handler);

View File

@@ -1,4 +1,3 @@
import type { NextApiRequest } from 'next';
import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
@@ -14,29 +13,70 @@ import { MongoResourcePermission } from '@fastgpt/service/support/permission/sch
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
export type GetDatasetListBody = { parentId: ParentIdType; type?: DatasetTypeEnum };
export type GetDatasetListBody = {
parentId: ParentIdType;
type?: DatasetTypeEnum;
searchKey?: string;
};
async function handler(req: NextApiRequest) {
const { parentId, type } = req.body as GetDatasetListBody;
async function handler(req: ApiRequestProps<GetDatasetListBody>) {
const { parentId, type, searchKey } = req.body;
// 凭证校验
const {
dataset: parentDataset,
teamId,
tmbId,
permission: tmbPer
} = await authUserPer({
req,
authToken: true,
authApiKey: true,
per: ReadPermissionVal
});
} = await (async () => {
if (parentId) {
return await authDataset({
req,
authToken: true,
per: ReadPermissionVal,
datasetId: parentId
});
}
return {
...(await authUserPer({
req,
authToken: true,
authApiKey: true,
per: ReadPermissionVal
})),
dataset: undefined
};
})();
const findDatasetQuery = (() => {
const searchMatch = searchKey
? {
$or: [
{ name: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } },
{ intro: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } }
]
}
: {};
if (searchKey) {
return {
teamId,
...searchMatch
};
}
return {
teamId,
...(type ? (Array.isArray(type) ? { type: { $in: type } } : { type }) : {}),
...parseParentIdInMongo(parentId)
};
})();
const [myDatasets, rpList] = await Promise.all([
MongoDataset.find({
teamId,
...parseParentIdInMongo(parentId),
...(type && { type })
})
MongoDataset.find(findDatasetQuery)
.sort({
updateTime: -1
})
@@ -50,14 +90,26 @@ async function handler(req: NextApiRequest) {
const filterDatasets = myDatasets
.map((dataset) => {
const perVal = rpList.find(
(item) => String(item.resourceId) === String(dataset._id)
)?.permission;
const Per = new DatasetPermission({
per: perVal ?? dataset.defaultPermission,
isOwner: String(dataset.tmbId) === tmbId || tmbPer.isOwner
});
const Per = (() => {
if (dataset.inheritPermission && parentDataset && dataset.type !== DatasetTypeEnum.folder) {
dataset.defaultPermission = parentDataset.defaultPermission;
const perVal = rpList.find(
(item) => String(item.resourceId) === String(parentDataset._id)
)?.permission;
return new DatasetPermission({
per: perVal ?? parentDataset.defaultPermission,
isOwner: String(parentDataset.tmbId) === tmbId || tmbPer.isOwner
});
} else {
const perVal = rpList.find(
(item) => String(item.resourceId) === String(dataset._id)
)?.permission;
return new DatasetPermission({
per: perVal ?? dataset.defaultPermission,
isOwner: String(dataset.tmbId) === tmbId || tmbPer.isOwner
});
}
})();
return {
...dataset,
permission: Per
@@ -68,14 +120,14 @@ async function handler(req: NextApiRequest) {
const data = await Promise.all(
filterDatasets.map<DatasetListItemType>((item) => ({
_id: item._id,
parentId: item.parentId,
avatar: item.avatar,
name: item.name,
intro: item.intro,
type: item.type,
permission: item.permission,
vectorModel: getVectorModel(item.vectorModel),
defaultPermission: item.defaultPermission ?? DatasetDefaultPermissionVal
defaultPermission: item.defaultPermission ?? DatasetDefaultPermissionVal,
inheritPermission: item.inheritPermission
}))
);

View File

@@ -0,0 +1,45 @@
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
import {
ManagePermissionVal,
PerResourceTypeEnum
} from '@fastgpt/global/support/permission/constant';
import { resumeInheritPermission } from '@fastgpt/service/support/permission/inheritPermission';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
export type ResumeInheritPermissionQuery = {
datasetId: string;
};
export type ResumeInheritPermissionBody = {};
// resume the dataset's inherit permission.
async function handler(
req: ApiRequestProps<ResumeInheritPermissionBody, ResumeInheritPermissionQuery>
) {
const { datasetId } = req.query;
const { dataset } = await authDataset({
datasetId,
req,
authToken: true,
per: ManagePermissionVal
});
if (dataset.parentId) {
await resumeInheritPermission({
resource: dataset,
folderTypeList: [DatasetTypeEnum.folder],
resourceType: PerResourceTypeEnum.dataset,
resourceModel: MongoDataset
});
} else {
await MongoDataset.updateOne(
{
_id: datasetId
},
{
inheritPermission: true
}
);
}
}
export default NextAPI(handler);

View File

@@ -1,15 +1,34 @@
import type { NextApiRequest } from 'next';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import type { DatasetUpdateBody } from '@fastgpt/global/core/dataset/api.d';
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
import { NextAPI } from '@/service/middleware/entry';
import {
OwnerPermissionVal,
ManagePermissionVal,
PerResourceTypeEnum,
WritePermissionVal
} from '@fastgpt/global/support/permission/constant';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { ClientSession } from 'mongoose';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
import { DatasetSchemaType } from '@fastgpt/global/core/dataset/type';
import { getResourceAllClbs } from '@fastgpt/service/support/permission/controller';
import {
syncChildrenPermission,
syncCollaborators
} from '@fastgpt/service/support/permission/inheritPermission';
async function handler(req: NextApiRequest) {
export type DatasetUpdateQuery = {};
export type DatasetUpdateResponse = any;
async function handler(
req: ApiRequestProps<DatasetUpdateBody, DatasetUpdateQuery>,
_res: ApiResponseType<any>
): Promise<DatasetUpdateResponse> {
const {
id,
parentId,
@@ -21,34 +40,131 @@ async function handler(req: NextApiRequest) {
externalReadUrl,
defaultPermission,
status
} = req.body as DatasetUpdateBody;
} = req.body;
if (!id) {
return Promise.reject(CommonErrEnum.missingParams);
}
if (defaultPermission) {
await authDataset({ req, authToken: true, datasetId: id, per: OwnerPermissionVal });
} else {
await authDataset({ req, authToken: true, datasetId: id, per: WritePermissionVal });
}
await MongoDataset.findOneAndUpdate(
{
_id: id
},
{
...(parentId !== undefined && { parentId: parentId || null }),
...(name && { name }),
...(avatar && { avatar }),
...(agentModel && { agentModel: agentModel.model }),
...(websiteConfig && { websiteConfig }),
...(status && { status }),
...(intro && { intro }),
...(externalReadUrl && { externalReadUrl }),
...(defaultPermission !== undefined && { defaultPermission })
const { dataset } = (await (async () => {
if (defaultPermission !== undefined) {
return await authDataset({ req, authToken: true, datasetId: id, per: ManagePermissionVal });
} else {
return await authDataset({ req, authToken: true, datasetId: id, per: WritePermissionVal });
}
);
}
})()) as { dataset: DatasetSchemaType };
const isDefaultPermissionChanged =
defaultPermission !== undefined && dataset.defaultPermission !== defaultPermission;
const isFolder = dataset.type === DatasetTypeEnum.folder;
const onUpdate = async (
session?: ClientSession,
updatedDefaultPermission?: PermissionValueType
) => {
await MongoDataset.findByIdAndUpdate(
id,
{
...parseParentIdInMongo(parentId),
...(name && { name }),
...(avatar && { avatar }),
...(agentModel && { agentModel: agentModel.model }),
...(websiteConfig && { websiteConfig }),
...(status && { status }),
...(intro !== undefined && { intro }),
...(externalReadUrl && { externalReadUrl }),
// move
...(updatedDefaultPermission !== undefined && {
defaultPermission: updatedDefaultPermission
}),
// update the defaultPermission
...(isDefaultPermissionChanged && { inheritPermission: false })
},
{ session }
);
};
// move
if (parentId !== undefined) {
await mongoSessionRun(async (session) => {
const parentDefaultPermission = await (async () => {
if (parentId) {
const { dataset: parentDataset } = await authDataset({
req,
authToken: true,
datasetId: parentId,
per: WritePermissionVal
});
return parentDataset.defaultPermission;
}
return DatasetDefaultPermissionVal;
})();
if (isFolder && dataset.inheritPermission) {
const parentClbs = await getResourceAllClbs({
teamId: dataset.teamId,
resourceId: parentId,
resourceType: PerResourceTypeEnum.dataset,
session
});
await syncCollaborators({
teamId: dataset.teamId,
resourceId: id,
resourceType: PerResourceTypeEnum.dataset,
collaborators: parentClbs,
session
});
await syncChildrenPermission({
resource: dataset,
resourceType: PerResourceTypeEnum.dataset,
resourceModel: MongoDataset,
folderTypeList: [DatasetTypeEnum.folder],
collaborators: parentClbs,
defaultPermission: parentDefaultPermission,
session
});
return onUpdate(session, parentDefaultPermission);
}
return onUpdate(session);
});
} else if (isDefaultPermissionChanged) {
await mongoSessionRun(async (session) => {
if (isFolder) {
await syncChildrenPermission({
defaultPermission,
resource: {
_id: dataset._id,
type: dataset.type,
teamId: dataset.teamId,
parentId: dataset.parentId
},
resourceType: PerResourceTypeEnum.dataset,
resourceModel: MongoDataset,
folderTypeList: [DatasetTypeEnum.folder],
session
});
} else if (dataset.inheritPermission && dataset.parentId) {
const parentClbs = await getResourceAllClbs({
teamId: dataset.teamId,
resourceId: parentId,
resourceType: PerResourceTypeEnum.dataset,
session
});
await syncCollaborators({
teamId: dataset.teamId,
resourceId: id,
resourceType: PerResourceTypeEnum.dataset,
collaborators: parentClbs,
session
});
}
return onUpdate(session, defaultPermission);
});
} else {
return onUpdate();
}
}
export default NextAPI(handler);