mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 05:12:39 +00:00
* feat: team permission refine (#4402) * chore: team permission extend * feat: manage team permission * chore: api auth * fix: i18n * feat: add initv493 * fix: test, org auth manager * test: app test for refined permission * update init sh * fix: add/remove manage permission (#4427) * fix: add/remove manage permission * fix: github action fastgpt-test * fix: mock create model * fix: team write permission * fix: ts * account permission --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
@@ -1,20 +1,24 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Checkbox, HStack, VStack } from '@chakra-ui/react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import PermissionTags from './PermissionTags';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import OrgTags from '../../user/team/OrgTags';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
|
||||
function MemberItemCard({
|
||||
avatar,
|
||||
key,
|
||||
onChange,
|
||||
onChange: _onChange,
|
||||
isChecked,
|
||||
onDelete,
|
||||
name,
|
||||
permission,
|
||||
orgs
|
||||
orgs,
|
||||
addOnly,
|
||||
rightSlot
|
||||
}: {
|
||||
avatar: string;
|
||||
key: string;
|
||||
@@ -23,44 +27,66 @@ function MemberItemCard({
|
||||
onDelete?: () => void;
|
||||
name: string;
|
||||
permission?: PermissionValueType;
|
||||
addOnly?: boolean;
|
||||
orgs?: string[];
|
||||
rightSlot?: React.ReactNode;
|
||||
}) {
|
||||
const isAdded = addOnly && !!permission;
|
||||
const onChange = () => {
|
||||
if (!isAdded) _onChange();
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
key={key}
|
||||
px="3"
|
||||
py="2"
|
||||
borderRadius="sm"
|
||||
_hover={{
|
||||
bgColor: 'myGray.50',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={onChange}
|
||||
>
|
||||
{isChecked !== undefined && <Checkbox isChecked={isChecked} pointerEvents="none" />}
|
||||
<Avatar src={avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
key={key}
|
||||
px="3"
|
||||
py="2"
|
||||
borderRadius="sm"
|
||||
_hover={{
|
||||
bgColor: 'myGray.50',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={onChange}
|
||||
>
|
||||
{isChecked !== undefined && (
|
||||
<Checkbox isChecked={isChecked} pointerEvents="none" disabled={isAdded} />
|
||||
)}
|
||||
<Avatar src={avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
|
||||
<Box w="full">
|
||||
<Box fontSize={'sm'}>{name}</Box>
|
||||
<Box lineHeight={1}>{orgs && orgs.length > 0 && <OrgTags orgs={orgs} />}</Box>
|
||||
<Box w="full">
|
||||
<Box fontSize={'sm'} className="textEllipsis" maxW="300px">
|
||||
{name}
|
||||
</Box>
|
||||
{permission && <PermissionTags permission={permission} />}
|
||||
{onDelete !== undefined && (
|
||||
<MyIcon
|
||||
name="common/closeLight"
|
||||
w="1rem"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
</>
|
||||
<Box lineHeight={1}>{orgs && orgs.length > 0 && <OrgTags orgs={orgs} />}</Box>
|
||||
</Box>
|
||||
{!isAdded && permission && <PermissionTags permission={permission} />}
|
||||
{isAdded && (
|
||||
<Tag
|
||||
mixBlendMode={'multiply'}
|
||||
colorSchema="blue"
|
||||
border="none"
|
||||
py={2}
|
||||
px={3}
|
||||
fontSize={'xs'}
|
||||
>
|
||||
{t('user:team.collaborator.added')}
|
||||
</Tag>
|
||||
)}
|
||||
{onDelete !== undefined && (
|
||||
<MyIcon
|
||||
name="common/closeLight"
|
||||
w="1rem"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
)}
|
||||
{rightSlot}
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -1,17 +1,6 @@
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Flex,
|
||||
Grid,
|
||||
HStack,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Tag,
|
||||
Text
|
||||
} from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, Grid, HStack, ModalBody, ModalFooter, Text } from '@chakra-ui/react';
|
||||
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';
|
||||
@@ -19,27 +8,26 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import PermissionSelect from './PermissionSelect';
|
||||
import PermissionTags from './PermissionTags';
|
||||
import {
|
||||
DEFAULT_ORG_AVATAR,
|
||||
DEFAULT_TEAM_AVATAR,
|
||||
DEFAULT_USER_AVATAR
|
||||
} from '@fastgpt/global/common/system/constants';
|
||||
import Path from '@/components/common/folder/Path';
|
||||
import { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import { OrgListItemType } from '@fastgpt/global/support/user/team/org/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { CollaboratorContext } from './context';
|
||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||
import { getGroupList } from '@/web/support/user/team/group/api';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import MemberItemCard from './MemberItemCard';
|
||||
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
||||
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
|
||||
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||
import _ from 'lodash';
|
||||
import { UpdateClbPermissionProps } from '@fastgpt/global/support/permission/collaborator';
|
||||
import { ValueOf } from 'next/dist/shared/lib/constants';
|
||||
|
||||
const HoverBoxStyle = {
|
||||
bgColor: 'myGray.50',
|
||||
@@ -131,8 +119,8 @@ function MemberModal({
|
||||
members: selectedMemberList.map((item) => item.tmbId),
|
||||
groups: selectedGroupList.map((item) => item._id),
|
||||
orgs: selectedOrgList.map((item) => item._id),
|
||||
permission: selectedPermission!
|
||||
}),
|
||||
permission: addOnly ? undefined : selectedPermission!
|
||||
} as UpdateClbPermissionProps<ValueOf<typeof addOnly>>),
|
||||
{
|
||||
successToast: t('common:common.Add Success'),
|
||||
onSuccess() {
|
||||
@@ -285,6 +273,7 @@ function MemberModal({
|
||||
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
|
||||
return (
|
||||
<MemberItemCard
|
||||
addOnly={addOnly}
|
||||
avatar={member.avatar}
|
||||
key={member.tmbId}
|
||||
name={member.memberName}
|
||||
@@ -321,49 +310,33 @@ function MemberModal({
|
||||
};
|
||||
const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
|
||||
return (
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
<MemberItemCard
|
||||
avatar={org.avatar}
|
||||
key={org._id}
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={HoverBoxStyle}
|
||||
onClick={onChange}
|
||||
>
|
||||
<Checkbox
|
||||
isChecked={!!selectedOrgList.find((v) => v._id === org._id)}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<HStack w="full">
|
||||
<Text>{org.name}</Text>
|
||||
{org.total && (
|
||||
<>
|
||||
<Tag size="sm" my="auto">
|
||||
{org.total}
|
||||
</Tag>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
<PermissionTags permission={collaborator?.permission.value} />
|
||||
{org.total && (
|
||||
<MyIcon
|
||||
name="core/chat/chevronRight"
|
||||
w="16px"
|
||||
p="4px"
|
||||
rounded={'6px'}
|
||||
_hover={{
|
||||
bgColor: 'myGray.200'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
onClickOrg(org);
|
||||
// setPath(getOrgChildrenPath(org));
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
name={org.name}
|
||||
onChange={onChange}
|
||||
addOnly={addOnly}
|
||||
permission={collaborator?.permission.value}
|
||||
isChecked={!!selectedOrgList.find((v) => String(v._id) === String(org._id))}
|
||||
rightSlot={
|
||||
org.total && (
|
||||
<MyIcon
|
||||
name="core/chat/chevronRight"
|
||||
w="16px"
|
||||
p="4px"
|
||||
rounded={'6px'}
|
||||
_hover={{
|
||||
bgColor: 'myGray.200'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
onClickOrg(org);
|
||||
// setPath(getOrgChildrenPath(org));
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return searchKey ? (
|
||||
@@ -372,6 +345,9 @@ function MemberModal({
|
||||
<OrgMemberScrollData>
|
||||
{Orgs}
|
||||
{orgMembers.map((member) => {
|
||||
const isChecked = !!selectedMemberList.find(
|
||||
(v) => v.tmbId === member.tmbId
|
||||
);
|
||||
return (
|
||||
<MemberItemCard
|
||||
avatar={member.avatar}
|
||||
@@ -385,7 +361,9 @@ function MemberModal({
|
||||
return [...state, member];
|
||||
});
|
||||
}}
|
||||
isChecked={!!selectedMemberList.find((v) => v.tmbId === member.tmbId)}
|
||||
isChecked={isChecked}
|
||||
permission={member.permission.value}
|
||||
addOnly={addOnly && !!member.permission.value}
|
||||
orgs={member.orgs}
|
||||
/>
|
||||
);
|
||||
@@ -414,6 +392,7 @@ function MemberModal({
|
||||
permission={collaborator?.permission.value}
|
||||
onChange={onChange}
|
||||
isChecked={!!selectedGroupList.find((v) => v._id === group._id)}
|
||||
addOnly={addOnly}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@@ -110,7 +110,15 @@ const CollaboratorContextProvider = ({
|
||||
} = useRequest2(
|
||||
async () => {
|
||||
if (feConfigs.isPlus) {
|
||||
return onGetCollaboratorList();
|
||||
const data = await onGetCollaboratorList();
|
||||
return data.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
permission: new Permission({
|
||||
per: item.permission.value
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
@@ -10,7 +10,14 @@ function OrgTags({ orgs, type = 'simple' }: { orgs?: string[]; type?: 'simple' |
|
||||
label={
|
||||
<VStack gap="1" alignItems={'start'}>
|
||||
{orgs.map((org, index) => (
|
||||
<Box key={index} fontSize="sm" fontWeight={400} color="myGray.500">
|
||||
<Box
|
||||
key={index}
|
||||
fontSize="sm"
|
||||
fontWeight={400}
|
||||
color="myGray.500"
|
||||
maxW={'300px'}
|
||||
className="textEllipsis"
|
||||
>
|
||||
{org.slice(1)}
|
||||
</Box>
|
||||
))}
|
||||
|
@@ -91,7 +91,7 @@ const AccountContainer = ({
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(userInfo?.team?.permission.hasManagePer
|
||||
...(userInfo?.team?.permission.hasApikeyCreatePer
|
||||
? [
|
||||
{
|
||||
icon: 'key',
|
||||
|
@@ -27,6 +27,9 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MemberTag from '../../../../components/support/user/team/Info/MemberTag';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import {
|
||||
TeamApikeyCreatePermissionVal,
|
||||
TeamAppCreatePermissionVal,
|
||||
TeamDatasetCreatePermissionVal,
|
||||
TeamManagePermissionVal,
|
||||
TeamPermissionList,
|
||||
TeamWritePermissionVal
|
||||
@@ -42,6 +45,9 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
|
||||
function PermissionManage({
|
||||
Tabs,
|
||||
@@ -104,19 +110,18 @@ function PermissionManage({
|
||||
}, [collaboratorList, searchResult, searchKey]);
|
||||
|
||||
const { runAsync: onUpdatePermission, loading: addLoading } = useRequest2(
|
||||
async ({ id, type, per }: { id: string; type: 'add' | 'remove'; per: 'write' | 'manage' }) => {
|
||||
async ({ id, type, per }: { id: string; type: 'add' | 'remove'; per: PermissionValueType }) => {
|
||||
const clb = collaboratorList.find(
|
||||
(clb) => clb.tmbId === id || clb.groupId === id || clb.orgId === id
|
||||
);
|
||||
|
||||
if (!clb) return;
|
||||
|
||||
const updatePer = per === 'write' ? TeamWritePermissionVal : TeamManagePermissionVal;
|
||||
const permission = new TeamPermission({ per: clb.permission.value });
|
||||
if (type === 'add') {
|
||||
permission.addPer(updatePer);
|
||||
permission.addPer(per);
|
||||
} else {
|
||||
permission.removePer(updatePer);
|
||||
permission.removePer(per);
|
||||
}
|
||||
|
||||
return onUpdateCollaborators({
|
||||
@@ -132,12 +137,48 @@ function PermissionManage({
|
||||
useRequest2(onDelOneCollaborator);
|
||||
|
||||
const userManage = userInfo?.permission.hasManagePer;
|
||||
const hasDeletePer = (per: TeamPermission) => {
|
||||
const hasDeletePer = (per: Permission) => {
|
||||
if (userInfo?.permission.isOwner) return true;
|
||||
if (userManage && !per.hasManagePer) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
function PermissionCheckBox({
|
||||
isDisabled,
|
||||
per,
|
||||
clbPer,
|
||||
id
|
||||
}: {
|
||||
isDisabled: boolean;
|
||||
per: PermissionValueType;
|
||||
clbPer: Permission;
|
||||
id: string;
|
||||
}) {
|
||||
return (
|
||||
<Td>
|
||||
<Box mx="auto" w="fit-content">
|
||||
<Checkbox
|
||||
isDisabled={isDisabled}
|
||||
isChecked={clbPer.checkPer(per)}
|
||||
onChange={(e) =>
|
||||
e.target.checked
|
||||
? onUpdatePermission({
|
||||
id,
|
||||
type: 'add',
|
||||
per
|
||||
})
|
||||
: onUpdatePermission({
|
||||
id,
|
||||
type: 'remove',
|
||||
per
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Td>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||
@@ -174,13 +215,26 @@ function PermissionManage({
|
||||
</Th>
|
||||
<Th bg="myGray.100">
|
||||
<Box mx="auto" w="fit-content">
|
||||
{t('user:team.group.permission.write')}
|
||||
{t('account_team:permission_appCreate')}
|
||||
<QuestionTip ml="1" label={t('account_team:permission_appCreate_tip')} />
|
||||
</Box>
|
||||
</Th>
|
||||
<Th bg="myGray.100">
|
||||
<Box mx="auto" w="fit-content">
|
||||
{t('user:team.group.permission.manage')}
|
||||
<QuestionTip ml="1" label={t('user:team.group.manage_tip')} />
|
||||
{t('account_team:permission_datasetCreate')}
|
||||
<QuestionTip ml="1" label={t('account_team:permission_datasetCreate_Tip')} />
|
||||
</Box>
|
||||
</Th>
|
||||
<Th bg="myGray.100">
|
||||
<Box mx="auto" w="fit-content">
|
||||
{t('account_team:permission_apikeyCreate')}
|
||||
<QuestionTip ml="1" label={t('account_team:permission_apikeyCreate_Tip')} />
|
||||
</Box>
|
||||
</Th>
|
||||
<Th bg="myGray.100">
|
||||
<Box mx="auto" w="fit-content">
|
||||
{t('account_team:permission_manage')}
|
||||
<QuestionTip ml="1" label={t('account_team:permission_manage_tip')} />
|
||||
</Box>
|
||||
</Th>
|
||||
<Th bg="myGray.100" borderRightRadius="md">
|
||||
@@ -210,48 +264,30 @@ function PermissionManage({
|
||||
<Box>{member.name}</Box>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Box mx="auto" w="fit-content">
|
||||
<Checkbox
|
||||
isDisabled={member.permission.isOwner || !userManage}
|
||||
isChecked={member.permission.hasWritePer}
|
||||
onChange={(e) =>
|
||||
e.target.checked
|
||||
? onUpdatePermission({
|
||||
id: member.tmbId!,
|
||||
type: 'add',
|
||||
per: 'write'
|
||||
})
|
||||
: onUpdatePermission({
|
||||
id: member.tmbId!,
|
||||
type: 'remove',
|
||||
per: 'write'
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Td>
|
||||
<Td>
|
||||
<Box mx="auto" w="fit-content">
|
||||
<Checkbox
|
||||
isDisabled={member.permission.isOwner || !userInfo?.permission.isOwner}
|
||||
isChecked={member.permission.hasManagePer}
|
||||
onChange={(e) =>
|
||||
e.target.checked
|
||||
? onUpdatePermission({
|
||||
id: member.tmbId!,
|
||||
type: 'add',
|
||||
per: 'manage'
|
||||
})
|
||||
: onUpdatePermission({
|
||||
id: member.tmbId!,
|
||||
type: 'remove',
|
||||
per: 'manage'
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Td>
|
||||
<PermissionCheckBox
|
||||
isDisabled={member.permission.isOwner || !userManage}
|
||||
per={TeamAppCreatePermissionVal}
|
||||
clbPer={member.permission}
|
||||
id={member.tmbId!}
|
||||
/>
|
||||
<PermissionCheckBox
|
||||
isDisabled={member.permission.isOwner || !userManage}
|
||||
per={TeamDatasetCreatePermissionVal}
|
||||
clbPer={member.permission}
|
||||
id={member.tmbId!}
|
||||
/>
|
||||
<PermissionCheckBox
|
||||
isDisabled={member.permission.isOwner || !userManage}
|
||||
per={TeamApikeyCreatePermissionVal}
|
||||
clbPer={member.permission}
|
||||
id={member.tmbId!}
|
||||
/>
|
||||
<PermissionCheckBox
|
||||
isDisabled={member.permission.isOwner || !userInfo?.permission.isOwner}
|
||||
per={TeamManagePermissionVal}
|
||||
clbPer={member.permission}
|
||||
id={member.tmbId!}
|
||||
/>
|
||||
<Td>
|
||||
{hasDeletePer(member.permission) &&
|
||||
userInfo?.team.tmbId !== member.tmbId && (
|
||||
@@ -268,7 +304,6 @@ function PermissionManage({
|
||||
</Tr>
|
||||
))}
|
||||
</>
|
||||
|
||||
<>
|
||||
<Tr borderBottom={'1px solid'} borderColor={'myGray.200'} />
|
||||
<Tr userSelect={'none'}>
|
||||
@@ -286,40 +321,30 @@ function PermissionManage({
|
||||
<Td pl={10}>
|
||||
<MemberTag name={org.name} avatar={org.avatar} />
|
||||
</Td>
|
||||
<Td>
|
||||
<Box mx="auto" w="fit-content">
|
||||
<Checkbox
|
||||
isDisabled={!userManage}
|
||||
isChecked={org.permission.hasWritePer}
|
||||
onChange={(e) =>
|
||||
e.target.checked
|
||||
? onUpdatePermission({ id: org.orgId!, type: 'add', per: 'write' })
|
||||
: onUpdatePermission({
|
||||
id: org.orgId!,
|
||||
type: 'remove',
|
||||
per: 'write'
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Td>
|
||||
<Td>
|
||||
<Box mx="auto" w="fit-content">
|
||||
<Checkbox
|
||||
isDisabled={!userInfo?.permission.isOwner}
|
||||
isChecked={org.permission.hasManagePer}
|
||||
onChange={(e) =>
|
||||
e.target.checked
|
||||
? onUpdatePermission({ id: org.orgId!, type: 'add', per: 'manage' })
|
||||
: onUpdatePermission({
|
||||
id: org.orgId!,
|
||||
type: 'remove',
|
||||
per: 'manage'
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Td>
|
||||
<PermissionCheckBox
|
||||
isDisabled={org.permission.isOwner || !userManage}
|
||||
per={TeamAppCreatePermissionVal}
|
||||
clbPer={org.permission}
|
||||
id={org.orgId!}
|
||||
/>
|
||||
<PermissionCheckBox
|
||||
isDisabled={org.permission.isOwner || !userManage}
|
||||
per={TeamDatasetCreatePermissionVal}
|
||||
clbPer={org.permission}
|
||||
id={org.orgId!}
|
||||
/>
|
||||
<PermissionCheckBox
|
||||
isDisabled={org.permission.isOwner || !userManage}
|
||||
per={TeamApikeyCreatePermissionVal}
|
||||
clbPer={org.permission}
|
||||
id={org.orgId!}
|
||||
/>
|
||||
<PermissionCheckBox
|
||||
isDisabled={org.permission.isOwner || !userInfo?.permission.isOwner}
|
||||
per={TeamManagePermissionVal}
|
||||
clbPer={org.permission}
|
||||
id={org.orgId!}
|
||||
/>
|
||||
<Td>
|
||||
{hasDeletePer(org.permission) && (
|
||||
<Box mx="auto" w="fit-content">
|
||||
@@ -358,48 +383,30 @@ function PermissionManage({
|
||||
avatar={group.avatar}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Box mx="auto" w="fit-content">
|
||||
<Checkbox
|
||||
isDisabled={!userManage}
|
||||
isChecked={group.permission.hasWritePer}
|
||||
onChange={(e) =>
|
||||
e.target.checked
|
||||
? onUpdatePermission({
|
||||
id: group.groupId!,
|
||||
type: 'add',
|
||||
per: 'write'
|
||||
})
|
||||
: onUpdatePermission({
|
||||
id: group.groupId!,
|
||||
type: 'remove',
|
||||
per: 'write'
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Td>
|
||||
<Td>
|
||||
<Box mx="auto" w="fit-content">
|
||||
<Checkbox
|
||||
isDisabled={!userInfo?.permission.isOwner}
|
||||
isChecked={group.permission.hasManagePer}
|
||||
onChange={(e) =>
|
||||
e.target.checked
|
||||
? onUpdatePermission({
|
||||
id: group.groupId!,
|
||||
type: 'add',
|
||||
per: 'manage'
|
||||
})
|
||||
: onUpdatePermission({
|
||||
id: group.groupId!,
|
||||
type: 'remove',
|
||||
per: 'manage'
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Td>
|
||||
<PermissionCheckBox
|
||||
isDisabled={group.permission.isOwner || !userManage}
|
||||
per={TeamAppCreatePermissionVal}
|
||||
clbPer={group.permission}
|
||||
id={group.groupId!}
|
||||
/>
|
||||
<PermissionCheckBox
|
||||
isDisabled={group.permission.isOwner || !userManage}
|
||||
per={TeamDatasetCreatePermissionVal}
|
||||
clbPer={group.permission}
|
||||
id={group.groupId!}
|
||||
/>
|
||||
<PermissionCheckBox
|
||||
isDisabled={group.permission.isOwner || !userManage}
|
||||
per={TeamApikeyCreatePermissionVal}
|
||||
clbPer={group.permission}
|
||||
id={group.groupId!}
|
||||
/>
|
||||
<PermissionCheckBox
|
||||
isDisabled={group.permission.isOwner || !userInfo?.permission.isOwner}
|
||||
per={TeamManagePermissionVal}
|
||||
clbPer={group.permission}
|
||||
id={group.groupId!}
|
||||
/>
|
||||
<Td>
|
||||
{hasDeletePer(group.permission) && (
|
||||
<Box mx="auto" w="fit-content">
|
||||
|
@@ -36,6 +36,7 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import UserBox from '@fastgpt/web/components/common/UserBox';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
const HttpEditModal = dynamic(() => import('./HttpPluginEditModal'));
|
||||
|
||||
const ListItem = () => {
|
||||
@@ -429,7 +430,7 @@ const ListItem = () => {
|
||||
members?: string[];
|
||||
groups?: string[];
|
||||
orgs?: string[];
|
||||
permission: number;
|
||||
permission: PermissionValueType;
|
||||
}) =>
|
||||
postUpdateAppCollaborators({
|
||||
...props,
|
||||
|
@@ -4,6 +4,7 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { onCreateApp } from './create';
|
||||
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
|
||||
export type copyAppQuery = {};
|
||||
|
||||
@@ -17,19 +18,16 @@ async function handler(
|
||||
req: ApiRequestProps<copyAppBody, copyAppQuery>,
|
||||
res: ApiResponseType<any>
|
||||
): Promise<copyAppResponse> {
|
||||
const [{ app, tmbId }] = await Promise.all([
|
||||
authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
per: WritePermissionVal,
|
||||
appId: req.body.appId
|
||||
}),
|
||||
authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: WritePermissionVal
|
||||
})
|
||||
]);
|
||||
const { app } = await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
per: WritePermissionVal,
|
||||
appId: req.body.appId
|
||||
});
|
||||
|
||||
const { tmbId } = app.parentId
|
||||
? await authApp({ req, appId: app.parentId, per: TeamAppCreatePermissionVal, authToken: true })
|
||||
: await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal });
|
||||
|
||||
const appId = await onCreateApp({
|
||||
parentId: app.parentId,
|
||||
|
@@ -17,6 +17,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||
import { refreshSourceAvatar } from '@fastgpt/service/common/file/image/controller';
|
||||
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
|
||||
export type CreateAppBody = {
|
||||
parentId?: ParentIdType;
|
||||
@@ -36,18 +37,15 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const [{ teamId, tmbId, userId }] = await Promise.all([
|
||||
authUserPer({ req, authToken: true, per: WritePermissionVal }),
|
||||
...(parentId
|
||||
? [authApp({ req, appId: parentId, per: WritePermissionVal, authToken: true })]
|
||||
: [])
|
||||
]);
|
||||
const { teamId, tmbId, userId } = parentId
|
||||
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true })
|
||||
: await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal });
|
||||
|
||||
// 上限校验
|
||||
await checkTeamAppLimit(teamId);
|
||||
const tmb = await MongoTeamMember.findById({ _id: tmbId }, 'userId').populate<{
|
||||
user: { avatar: string; username: string };
|
||||
}>('user', 'avatar username');
|
||||
user: { username: string };
|
||||
}>('user', 'username');
|
||||
|
||||
// 创建app
|
||||
const appId = await onCreateApp({
|
||||
@@ -60,7 +58,7 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
||||
chatConfig,
|
||||
teamId,
|
||||
tmbId,
|
||||
userAvatar: tmb?.user?.avatar,
|
||||
userAvatar: tmb?.avatar,
|
||||
username: tmb?.user?.username
|
||||
});
|
||||
|
||||
|
@@ -16,7 +16,7 @@ import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { syncCollaborators } from '@fastgpt/service/support/permission/inheritPermission';
|
||||
import { getResourceClbsAndGroups } from '@fastgpt/service/support/permission/controller';
|
||||
import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
|
||||
|
||||
export type CreateAppFolderBody = {
|
||||
@@ -33,21 +33,9 @@ async function handler(req: ApiRequestProps<CreateAppFolderBody>) {
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const { teamId, tmbId } = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: TeamWritePermissionVal
|
||||
});
|
||||
|
||||
if (parentId) {
|
||||
// if it is not a root folder
|
||||
await authApp({
|
||||
req,
|
||||
appId: parentId,
|
||||
per: WritePermissionVal,
|
||||
authToken: true
|
||||
});
|
||||
}
|
||||
const { teamId, tmbId } = parentId
|
||||
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true })
|
||||
: await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal });
|
||||
|
||||
// Create app
|
||||
await mongoSessionRun(async (session) => {
|
||||
|
@@ -9,6 +9,8 @@ import { onCreateApp, type CreateAppBody } from '../create';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
|
||||
export type createHttpPluginQuery = {};
|
||||
|
||||
@@ -29,11 +31,9 @@ async function handler(
|
||||
return Promise.reject('缺少参数');
|
||||
}
|
||||
|
||||
const { teamId, tmbId, userId } = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: WritePermissionVal
|
||||
});
|
||||
const { teamId, tmbId, userId } = parentId
|
||||
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true })
|
||||
: await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal });
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
// create http plugin folder
|
||||
|
@@ -20,7 +20,7 @@ import { ClientSession } from 'mongoose';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { getResourceClbsAndGroups } from '@fastgpt/service/support/permission/controller';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||
import { refreshSourceAvatar } from '@fastgpt/service/common/file/image/controller';
|
||||
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
|
||||
@@ -79,7 +79,7 @@ async function handler(req: ApiRequestProps<AppUpdateBody, AppUpdateQuery>) {
|
||||
await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: TeamWritePermissionVal
|
||||
per: TeamAppCreatePermissionVal
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@@ -6,11 +6,9 @@ import {
|
||||
getLLMModel,
|
||||
getEmbeddingModel,
|
||||
getDatasetModel,
|
||||
getDefaultEmbeddingModel,
|
||||
getVlmModel
|
||||
getDefaultEmbeddingModel
|
||||
} from '@fastgpt/service/core/ai/model';
|
||||
import { checkTeamDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import type { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
@@ -18,6 +16,7 @@ import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { refreshSourceAvatar } from '@fastgpt/service/common/file/image/controller';
|
||||
import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
|
||||
export type DatasetCreateQuery = {};
|
||||
export type DatasetCreateBody = CreateDatasetParams;
|
||||
@@ -41,25 +40,20 @@ async function handler(
|
||||
} = req.body;
|
||||
|
||||
// auth
|
||||
const [{ teamId, tmbId, userId }] = await Promise.all([
|
||||
authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: WritePermissionVal
|
||||
}),
|
||||
...(parentId
|
||||
? [
|
||||
authDataset({
|
||||
req,
|
||||
datasetId: parentId,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: WritePermissionVal
|
||||
})
|
||||
]
|
||||
: [])
|
||||
]);
|
||||
const { teamId, tmbId, userId } = parentId
|
||||
? await authDataset({
|
||||
req,
|
||||
datasetId: parentId,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: TeamDatasetCreatePermissionVal
|
||||
})
|
||||
: await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: TeamDatasetCreatePermissionVal
|
||||
});
|
||||
|
||||
// check model valid
|
||||
const vectorModelStore = getEmbeddingModel(vectorModel);
|
||||
|
@@ -5,8 +5,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import {
|
||||
OwnerPermissionVal,
|
||||
PerResourceTypeEnum,
|
||||
WritePermissionVal
|
||||
PerResourceTypeEnum
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
@@ -16,6 +15,7 @@ import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { getResourceClbsAndGroups } from '@fastgpt/service/support/permission/controller';
|
||||
import { syncCollaborators } from '@fastgpt/service/support/permission/inheritPermission';
|
||||
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
|
||||
import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
export type DatasetFolderCreateQuery = {};
|
||||
export type DatasetFolderCreateBody = {
|
||||
parentId?: string;
|
||||
@@ -33,20 +33,20 @@ async function handler(
|
||||
return Promise.reject(CommonErrEnum.missingParams);
|
||||
}
|
||||
|
||||
const { tmbId, teamId } = await authUserPer({
|
||||
req,
|
||||
per: WritePermissionVal,
|
||||
authToken: true
|
||||
});
|
||||
|
||||
if (parentId) {
|
||||
await authDataset({
|
||||
datasetId: parentId,
|
||||
per: WritePermissionVal,
|
||||
req,
|
||||
authToken: true
|
||||
});
|
||||
}
|
||||
const { teamId, tmbId } = parentId
|
||||
? await authDataset({
|
||||
req,
|
||||
datasetId: parentId,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: TeamDatasetCreatePermissionVal
|
||||
})
|
||||
: await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: TeamDatasetCreatePermissionVal
|
||||
});
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
const dataset = await MongoDataset.create({
|
||||
|
@@ -23,7 +23,7 @@ import {
|
||||
syncCollaborators
|
||||
} from '@fastgpt/service/support/permission/inheritPermission';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
@@ -104,7 +104,7 @@ async function handler(
|
||||
await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: TeamWritePermissionVal
|
||||
per: TeamDatasetCreatePermissionVal
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@@ -4,12 +4,10 @@ import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import type { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import {
|
||||
ManagePermissionVal,
|
||||
WritePermissionVal
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { OpenApiErrEnum } from '@fastgpt/global/common/error/code/openapi';
|
||||
import { TeamApikeyCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
|
||||
async function handler(req: ApiRequestProps<EditApiKeyProps>): Promise<string> {
|
||||
const { appId, name, limit } = req.body;
|
||||
@@ -19,7 +17,7 @@ async function handler(req: ApiRequestProps<EditApiKeyProps>): Promise<string> {
|
||||
const { teamId, tmbId } = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: WritePermissionVal
|
||||
per: TeamApikeyCreatePermissionVal
|
||||
});
|
||||
return { teamId, tmbId };
|
||||
} else {
|
||||
|
@@ -31,6 +31,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import TemplateMarketModal from '@/pageComponents/app/list/TemplateMarketModal';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import JsonImportModal from '@/pageComponents/app/list/JsonImportModal';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
|
||||
const CreateModal = dynamic(() => import('@/pageComponents/app/list/CreateModal'));
|
||||
const EditFolderModal = dynamic(
|
||||
@@ -213,7 +214,7 @@ const MyApps = () => {
|
||||
|
||||
{(folderDetail
|
||||
? folderDetail.permission.hasWritePer && folderDetail?.type !== AppTypeEnum.httpPlugin
|
||||
: userInfo?.team.permission.hasWritePer) && (
|
||||
: userInfo?.team.permission.hasAppCreatePer) && (
|
||||
<MyMenu
|
||||
size="md"
|
||||
Button={
|
||||
@@ -327,7 +328,7 @@ const MyApps = () => {
|
||||
}: {
|
||||
members?: string[];
|
||||
groups?: string[];
|
||||
permission: number;
|
||||
permission: PermissionValueType;
|
||||
}) => {
|
||||
return postUpdateAppCollaborators({
|
||||
members,
|
||||
|
@@ -29,6 +29,7 @@ import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
|
||||
const EditFolderModal = dynamic(
|
||||
() => import('@fastgpt/web/components/common/MyModal/EditFolderModal')
|
||||
@@ -138,7 +139,7 @@ const Dataset = () => {
|
||||
|
||||
{(folderDetail
|
||||
? folderDetail.permission.hasWritePer
|
||||
: userInfo?.team?.permission.hasWritePer) && (
|
||||
: userInfo?.team?.permission.hasDatasetCreatePer) && (
|
||||
<Box pl={[0, 4]}>
|
||||
<MyMenu
|
||||
size="md"
|
||||
@@ -248,7 +249,7 @@ const Dataset = () => {
|
||||
}: {
|
||||
members?: string[];
|
||||
groups?: string[];
|
||||
permission: number;
|
||||
permission: PermissionValueType;
|
||||
}) =>
|
||||
postUpdateDatasetCollaborators({
|
||||
members,
|
||||
|
57
projects/app/test/api/core/app/create.test.ts
Normal file
57
projects/app/test/api/core/app/create.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import * as createapi from '@/pages/api/core/app/create';
|
||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
|
||||
import { getFakeUsers } from '@test/datas/users';
|
||||
import { Call } from '@test/utils/request';
|
||||
import { expect, it, describe } from 'vitest';
|
||||
|
||||
describe('create api', () => {
|
||||
it('should return 200 when create app success', async () => {
|
||||
const users = await getFakeUsers(2);
|
||||
await MongoResourcePermission.create({
|
||||
resourceType: 'team',
|
||||
teamId: users.members[0].teamId,
|
||||
resourceId: null,
|
||||
tmbId: users.members[0].tmbId,
|
||||
permission: TeamAppCreatePermissionVal
|
||||
});
|
||||
const res = await Call<createapi.CreateAppBody, {}, {}>(createapi.default, {
|
||||
auth: users.members[0],
|
||||
body: {
|
||||
modules: [],
|
||||
name: 'testfolder',
|
||||
type: AppTypeEnum.folder
|
||||
}
|
||||
});
|
||||
expect(res.error).toBeUndefined();
|
||||
expect(res.code).toBe(200);
|
||||
const folderId = res.data as string;
|
||||
|
||||
const res2 = await Call<createapi.CreateAppBody, {}, {}>(createapi.default, {
|
||||
auth: users.members[0],
|
||||
body: {
|
||||
modules: [],
|
||||
name: 'testapp',
|
||||
type: AppTypeEnum.simple,
|
||||
parentId: String(folderId)
|
||||
}
|
||||
});
|
||||
expect(res2.error).toBeUndefined();
|
||||
expect(res2.code).toBe(200);
|
||||
expect(res2.data).toBeDefined();
|
||||
|
||||
const res3 = await Call<createapi.CreateAppBody, {}, {}>(createapi.default, {
|
||||
auth: users.members[1],
|
||||
body: {
|
||||
modules: [],
|
||||
name: 'testapp',
|
||||
type: AppTypeEnum.simple,
|
||||
parentId: String(folderId)
|
||||
}
|
||||
});
|
||||
expect(res3.error).toBe(AppErrEnum.unAuthApp);
|
||||
expect(res3.code).toBe(500);
|
||||
});
|
||||
});
|
38
projects/app/test/api/core/app/version/list.test.ts
Normal file
38
projects/app/test/api/core/app/version/list.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
import { getRootUser } from '@test/datas/users';
|
||||
import { Call } from '@test/utils/request';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import handler, {
|
||||
type versionListBody,
|
||||
type versionListResponse
|
||||
} from '@/pages/api/core/app/version/list';
|
||||
|
||||
describe('app version list test', () => {
|
||||
it('should return app version list', async () => {
|
||||
const root = await getRootUser();
|
||||
const app = await MongoApp.create({
|
||||
name: 'test',
|
||||
tmbId: root.tmbId,
|
||||
teamId: root.teamId
|
||||
});
|
||||
await MongoAppVersion.create(
|
||||
[...Array(10).keys()].map((i) => ({
|
||||
tmbId: root.tmbId,
|
||||
appId: app._id,
|
||||
versionName: `v${i}`
|
||||
}))
|
||||
);
|
||||
const res = await Call<versionListBody, {}, versionListResponse>(handler, {
|
||||
auth: root,
|
||||
body: {
|
||||
pageSize: 10,
|
||||
offset: 0,
|
||||
appId: app._id
|
||||
}
|
||||
});
|
||||
expect(res.code).toBe(200);
|
||||
expect(res.data.total).toBe(10);
|
||||
expect(res.data.list.length).toBe(10);
|
||||
});
|
||||
});
|
87
projects/app/test/api/core/dataset/collection/paths.test.ts
Normal file
87
projects/app/test/api/core/dataset/collection/paths.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { getDatasetCollectionPaths } from '@/pages/api/core/dataset/collection/paths';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
|
||||
vi.mock('@fastgpt/service/core/dataset/collection/schema', () => ({
|
||||
MongoDatasetCollection: {
|
||||
findOne: vi.fn()
|
||||
},
|
||||
DatasetColCollectionName: 'dataset_collections'
|
||||
}));
|
||||
|
||||
describe('getDatasetCollectionPaths', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return empty array for empty parentId', async () => {
|
||||
const result = await getDatasetCollectionPaths({});
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array if collection not found', async () => {
|
||||
vi.mocked(MongoDatasetCollection.findOne).mockResolvedValueOnce(null);
|
||||
|
||||
const result = await getDatasetCollectionPaths({ parentId: 'nonexistent-id' });
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return single path for collection without parent', async () => {
|
||||
vi.mocked(MongoDatasetCollection.findOne).mockResolvedValueOnce({
|
||||
_id: 'col1',
|
||||
name: 'Collection 1',
|
||||
parentId: ''
|
||||
});
|
||||
|
||||
const result = await getDatasetCollectionPaths({ parentId: 'col1' });
|
||||
expect(result).toEqual([{ parentId: 'col1', parentName: 'Collection 1' }]);
|
||||
});
|
||||
|
||||
it('should return full path for nested collections', async () => {
|
||||
vi.mocked(MongoDatasetCollection.findOne)
|
||||
.mockResolvedValueOnce({
|
||||
_id: 'col3',
|
||||
name: 'Collection 3',
|
||||
parentId: 'col2'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
_id: 'col2',
|
||||
name: 'Collection 2',
|
||||
parentId: 'col1'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
_id: 'col1',
|
||||
name: 'Collection 1',
|
||||
parentId: ''
|
||||
});
|
||||
|
||||
const result = await getDatasetCollectionPaths({ parentId: 'col3' });
|
||||
|
||||
expect(result).toEqual([
|
||||
{ parentId: 'col1', parentName: 'Collection 1' },
|
||||
{ parentId: 'col2', parentName: 'Collection 2' },
|
||||
{ parentId: 'col3', parentName: 'Collection 3' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle circular references gracefully', async () => {
|
||||
vi.mocked(MongoDatasetCollection.findOne)
|
||||
.mockResolvedValueOnce({
|
||||
_id: 'col1',
|
||||
name: 'Collection 1',
|
||||
parentId: 'col2'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
_id: 'col2',
|
||||
name: 'Collection 2',
|
||||
parentId: 'col1'
|
||||
});
|
||||
|
||||
const result = await getDatasetCollectionPaths({ parentId: 'col1' });
|
||||
|
||||
expect(result).toEqual([
|
||||
{ parentId: 'col2', parentName: 'Collection 2' },
|
||||
{ parentId: 'col1', parentName: 'Collection 1' }
|
||||
]);
|
||||
});
|
||||
});
|
54
projects/app/test/api/core/dataset/create.test.ts
Normal file
54
projects/app/test/api/core/dataset/create.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as createapi from '@/pages/api/core/dataset/create';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
|
||||
import { getFakeUsers } from '@test/datas/users';
|
||||
import { Call } from '@test/utils/request';
|
||||
import { vi, describe, it, expect } from 'vitest';
|
||||
|
||||
describe('create dataset', () => {
|
||||
it('should return 200 when create dataset success', async () => {
|
||||
const users = await getFakeUsers(2);
|
||||
await MongoResourcePermission.create({
|
||||
resourceType: 'team',
|
||||
teamId: users.members[0].teamId,
|
||||
resourceId: null,
|
||||
tmbId: users.members[0].tmbId,
|
||||
permission: TeamDatasetCreatePermissionVal
|
||||
});
|
||||
const res = await Call<
|
||||
createapi.DatasetCreateBody,
|
||||
createapi.DatasetCreateQuery,
|
||||
createapi.DatasetCreateResponse
|
||||
>(createapi.default, {
|
||||
auth: users.members[0],
|
||||
body: {
|
||||
name: 'folder',
|
||||
intro: 'intro',
|
||||
avatar: 'avatar',
|
||||
type: DatasetTypeEnum.folder
|
||||
}
|
||||
});
|
||||
expect(res.error).toBeUndefined();
|
||||
expect(res.code).toBe(200);
|
||||
const folderId = res.data as string;
|
||||
|
||||
const res2 = await Call<
|
||||
createapi.DatasetCreateBody,
|
||||
createapi.DatasetCreateQuery,
|
||||
createapi.DatasetCreateResponse
|
||||
>(createapi.default, {
|
||||
auth: users.members[0],
|
||||
body: {
|
||||
name: 'test',
|
||||
intro: 'intro',
|
||||
avatar: 'avatar',
|
||||
type: DatasetTypeEnum.dataset,
|
||||
parentId: folderId
|
||||
}
|
||||
});
|
||||
|
||||
expect(res2.error).toBeUndefined();
|
||||
expect(res2.code).toBe(200);
|
||||
});
|
||||
});
|
83
projects/app/test/api/core/dataset/paths.test.ts
Normal file
83
projects/app/test/api/core/dataset/paths.test.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { getParents } from '@/pages/api/core/dataset/paths';
|
||||
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
|
||||
|
||||
vi.mock('@fastgpt/service/core/dataset/schema', () => ({
|
||||
MongoDataset: {
|
||||
findById: vi.fn()
|
||||
},
|
||||
ChunkSettings: {},
|
||||
DatasetCollectionName: 'datasets'
|
||||
}));
|
||||
|
||||
describe('getParents', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return empty array if parentId is undefined', async () => {
|
||||
const result = await getParents(undefined);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array if parent not found', async () => {
|
||||
vi.mocked(MongoDataset.findById).mockResolvedValueOnce(null);
|
||||
|
||||
const result = await getParents('non-existent-id');
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return single parent path if no further parents', async () => {
|
||||
vi.mocked(MongoDataset.findById).mockResolvedValueOnce({
|
||||
name: 'Parent1',
|
||||
parentId: undefined
|
||||
});
|
||||
|
||||
const result = await getParents('parent1-id');
|
||||
|
||||
expect(result).toEqual([{ parentId: 'parent1-id', parentName: 'Parent1' }]);
|
||||
});
|
||||
|
||||
it('should return full parent path for nested parents', async () => {
|
||||
vi.mocked(MongoDataset.findById)
|
||||
.mockResolvedValueOnce({
|
||||
name: 'Child',
|
||||
parentId: 'parent1-id'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
name: 'Parent1',
|
||||
parentId: 'parent2-id'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
name: 'Parent2',
|
||||
parentId: undefined
|
||||
});
|
||||
|
||||
const result = await getParents('child-id');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ parentId: 'parent2-id', parentName: 'Parent2' },
|
||||
{ parentId: 'parent1-id', parentName: 'Parent1' },
|
||||
{ parentId: 'child-id', parentName: 'Child' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle circular references gracefully', async () => {
|
||||
vi.mocked(MongoDataset.findById)
|
||||
.mockResolvedValueOnce({
|
||||
name: 'Node1',
|
||||
parentId: 'node2-id'
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
name: 'Node2',
|
||||
parentId: 'node1-id' // Circular reference
|
||||
});
|
||||
|
||||
const result = await getParents('node1-id');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ parentId: 'node2-id', parentName: 'Node2' },
|
||||
{ parentId: 'node1-id', parentName: 'Node1' }
|
||||
]);
|
||||
});
|
||||
});
|
64
projects/app/test/api/support/openapi/create.test.ts
Normal file
64
projects/app/test/api/support/openapi/create.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { EditApiKeyProps } from '@/global/support/openapi/api';
|
||||
import * as createapi from '@/pages/api/support/openapi/create';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import {
|
||||
TeamApikeyCreatePermissionVal,
|
||||
TeamDatasetCreatePermissionVal
|
||||
} from '@fastgpt/global/support/permission/user/constant';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
|
||||
import { getFakeUsers } from '@test/datas/users';
|
||||
import { Call } from '@test/utils/request';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('create dataset', () => {
|
||||
it('should return 200 when create dataset success', async () => {
|
||||
const users = await getFakeUsers(2);
|
||||
await MongoResourcePermission.create({
|
||||
resourceType: 'team',
|
||||
teamId: users.members[0].teamId,
|
||||
resourceId: null,
|
||||
tmbId: users.members[0].tmbId,
|
||||
permission: TeamApikeyCreatePermissionVal
|
||||
});
|
||||
const res = await Call<EditApiKeyProps>(createapi.default, {
|
||||
auth: users.members[0],
|
||||
body: {
|
||||
name: 'test',
|
||||
limit: {
|
||||
maxUsagePoints: 1000
|
||||
}
|
||||
}
|
||||
});
|
||||
expect(res.error).toBeUndefined();
|
||||
expect(res.code).toBe(200);
|
||||
|
||||
await MongoResourcePermission.create({
|
||||
resourceType: 'app',
|
||||
teamId: users.members[1].teamId,
|
||||
resourceId: null,
|
||||
tmbId: users.members[1].tmbId,
|
||||
permission: ManagePermissionVal
|
||||
});
|
||||
|
||||
const app = await MongoApp.create({
|
||||
name: 'a',
|
||||
type: 'simple',
|
||||
tmbId: users.members[1].tmbId,
|
||||
teamId: users.members[1].teamId
|
||||
});
|
||||
const res2 = await Call<EditApiKeyProps>(createapi.default, {
|
||||
auth: users.members[1],
|
||||
body: {
|
||||
appId: app._id,
|
||||
name: 'test',
|
||||
limit: {
|
||||
maxUsagePoints: 1000
|
||||
}
|
||||
}
|
||||
});
|
||||
expect(res2.error).toBeUndefined();
|
||||
expect(res2.code).toBe(200);
|
||||
});
|
||||
});
|
26
projects/app/test/tsconfig.json
Normal file
26
projects/app/test/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["../src/*"],
|
||||
"@fastgpt/*": ["../../../packages/*"],
|
||||
"@test/*": ["../../../test/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.test.ts"],
|
||||
"exclude": ["**/node_modules"]
|
||||
}
|
Reference in New Issue
Block a user