Skip to content

Commit

Permalink
fix: table list permission (#603)
Browse files Browse the repository at this point in the history
  • Loading branch information
boris-w committed May 13, 2024
1 parent 278e4b4 commit b32d7cc
Show file tree
Hide file tree
Showing 27 changed files with 304 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import type {
RecordUpdateEvent,
ViewUpdateEvent,
FieldUpdateEvent,
FieldCreateEvent,
} from '../events';
import { Events } from '../events';

type IViewEvent = ViewUpdateEvent;
type IRecordEvent = RecordCreateEvent | RecordDeleteEvent | RecordUpdateEvent;
type IListenerEvent = IViewEvent | IRecordEvent | FieldUpdateEvent;
type IListenerEvent = IViewEvent | IRecordEvent | FieldUpdateEvent | FieldCreateEvent;

@Injectable()
export class ActionTriggerListener {
Expand All @@ -26,6 +27,7 @@ export class ActionTriggerListener {

@OnEvent(Events.TABLE_VIEW_UPDATE, { async: true })
@OnEvent(Events.TABLE_FIELD_UPDATE, { async: true })
@OnEvent(Events.TABLE_FIELD_CREATE, { async: true })
@OnEvent('table.record.*', { async: true })
private async listener(listenerEvent: IListenerEvent): Promise<void> {
// Handling table view update events
Expand All @@ -38,6 +40,11 @@ export class ActionTriggerListener {
await this.handleTableFieldUpdate(listenerEvent as FieldUpdateEvent);
}

// Handling table field create events
if (this.isTableFieldCreateEvent(listenerEvent)) {
await this.handleTableFieldCreate(listenerEvent as FieldCreateEvent);
}

// Handling table record events (create, delete, update)
if (this.isTableRecordEvent(listenerEvent)) {
await this.handleTableRecordEvent(listenerEvent as IRecordEvent);
Expand Down Expand Up @@ -100,6 +107,13 @@ export class ActionTriggerListener {
});
}

private async handleTableFieldCreate(event: FieldCreateEvent): Promise<void> {
const { tableId } = event.payload;
return this.emitActionTrigger(tableId, {
addField: [tableId],
});
}

private async handleTableRecordEvent(event: IRecordEvent): Promise<void> {
const { tableId } = event.payload;

Expand All @@ -123,6 +137,10 @@ export class ActionTriggerListener {
return Events.TABLE_FIELD_UPDATE === event.name;
}

private isTableFieldCreateEvent(event: IListenerEvent): boolean {
return Events.TABLE_FIELD_CREATE === event.name;
}

private isValidViewUpdateOperation(event: ViewUpdateEvent): boolean | undefined {
const propertyKeys = ['filter', 'group'];
const { name, propertyKey } = event.context.opMeta || {};
Expand Down
7 changes: 7 additions & 0 deletions apps/nestjs-backend/src/features/base/base.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import type {
ICreateBaseVo,
IDbConnectionVo,
IGetBasePermissionVo,
IGetBaseVo,
IUpdateBaseVo,
ListBaseCollaboratorVo,
Expand Down Expand Up @@ -131,4 +132,10 @@ export class BaseController {
async listCollaborator(@Param('baseId') baseId: string): Promise<ListBaseCollaboratorVo> {
return await this.collaboratorService.getListByBase(baseId);
}

@Permissions('base|read')
@Get(':baseId/permission')
async getPermission(@Param('baseId') baseId: string): Promise<IGetBasePermissionVo> {
return await this.baseService.getPermission(baseId);
}
}
18 changes: 17 additions & 1 deletion apps/nestjs-backend/src/features/base/base.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { generateBaseId } from '@teable/core';
import {
ActionPrefix,
RoleType,
actionPrefixMap,
generateBaseId,
getPermissionMap,
} from '@teable/core';
import { PrismaService } from '@teable/db-main-prisma';
import type {
ICreateBaseFromTemplateRo,
Expand All @@ -8,6 +14,7 @@ import type {
IUpdateBaseRo,
IUpdateOrderRo,
} from '@teable/openapi';
import { pick } from 'lodash';
import { ClsService } from 'nestjs-cls';
import { IThresholdConfig, ThresholdConfig } from '../../configs/threshold.config';
import { InjectDbProvider } from '../../db-provider/db.provider';
Expand Down Expand Up @@ -263,4 +270,13 @@ export class BaseService {
});
});
}

async getPermission(baseId: string) {
const { role } = await this.getBaseById(baseId);
const permissionMap = getPermissionMap(RoleType.Base, role);
return pick(permissionMap, [
...actionPrefixMap[ActionPrefix.Table],
...actionPrefixMap[ActionPrefix.Base],
]);
}
}
55 changes: 53 additions & 2 deletions apps/nestjs-backend/src/features/table/table-permission.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { Injectable } from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import type { BaseRole, ExcludeAction, TableActions } from '@teable/core';
import { ActionPrefix, RoleType, actionPrefixMap, getPermissionMap } from '@teable/core';
import { PrismaService } from '@teable/db-main-prisma';
import { pick } from 'lodash';
import { ClsService } from 'nestjs-cls';
import type { IClsStore } from '../../types/cls';

@Injectable()
export class TablePermissionService {
constructor(private readonly cls: ClsService<IClsStore>) {}
constructor(
private readonly cls: ClsService<IClsStore>,
private readonly prismaService: PrismaService
) {}

async getProjectionTableIds(_baseId: string): Promise<string[] | undefined> {
const shareViewId = this.cls.get('shareViewId');
Expand All @@ -16,4 +23,48 @@ export class TablePermissionService {
protected async getViewQueryWithSharePermission() {
return [];
}

async getTablePermissionMapByBaseId(
baseId: string,
tableIds?: string[]
): Promise<Record<string, Record<ExcludeAction<TableActions, 'table|create'>, boolean>>> {
const userId = this.cls.get('user.id');
const base = await this.prismaService
.txClient()
.base.findUniqueOrThrow({
where: { id: baseId },
})
.catch(() => {
throw new NotFoundException('Base not found');
});
const collaborator = await this.prismaService
.txClient()
.collaborator.findFirstOrThrow({
where: {
deletedTime: null,
userId,
OR: [{ baseId }, { spaceId: base.spaceId }],
},
})
.catch(() => {
throw new NotFoundException('Collaborator not found');
});
const roleName = collaborator.roleName;
const tables = await this.prismaService.txClient().tableMeta.findMany({
where: { baseId, deletedTime: null, id: { in: tableIds } },
});

return tables.reduce(
(acc, table) => {
acc[table.id] = pick(
getPermissionMap(RoleType.Base, roleName as BaseRole),
actionPrefixMap[ActionPrefix.Table].filter(
(action) => action !== 'table|create'
) as ExcludeAction<TableActions, 'table|create'>[]
);
return acc;
},
{} as Record<string, Record<ExcludeAction<TableActions, 'table|create'>, boolean>>
);
}
}
5 changes: 5 additions & 0 deletions apps/nestjs-backend/src/features/table/table.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,10 @@ export class TableService implements IReadonlyAdapterService {
where: { baseId, id: { in: ids }, deletedTime: null },
orderBy: { order: 'asc' },
});
const tablePermissionMap = await this.tablePermissionService.getTablePermissionMapByBaseId(
baseId,
ids
);
const tableTime = await this.getTableLastModifiedTime(ids);
const tableDefaultViewIds = await this.getTableDefaultViewId(ids);
return tables
Expand All @@ -371,6 +375,7 @@ export class TableService implements IReadonlyAdapterService {
icon: table.icon ?? undefined,
lastModifiedTime: tableTime[i] || table.createdTime.toISOString(),
defaultViewId: tableDefaultViewIds[i],
permission: tablePermissionMap[table.id],
},
};
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Gauge, Lock, PackageCheck } from '@teable/icons';
import { useBasePermission } from '@teable/sdk/hooks';
import { cn } from '@teable/ui-lib/shadcn';
import { Button } from '@teable/ui-lib/shadcn/ui/button';
import Link from 'next/link';
Expand All @@ -12,6 +13,7 @@ export const BaseSideBar = () => {
const router = useRouter();
const { baseId } = router.query;
const { t } = useTranslation(tableConfig.i18nNamespaces);
const basePermission = useBasePermission();
const pageRoutes: {
href: string;
text: string;
Expand All @@ -29,11 +31,15 @@ export const BaseSideBar = () => {
Icon: PackageCheck,
disabled: true,
},
{
href: `/base/${baseId}/authority-matrix`,
text: t('common:noun.authorityMatrix'),
Icon: Lock,
},
...(basePermission?.['base|authority_matrix_config']
? [
{
href: `/base/${baseId}/authority-matrix`,
text: t('common:noun.authorityMatrix'),
Icon: Lock,
},
]
: []),
];
return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export const DbConnectionPanel = ({ className }: { className?: string }) => {
<CardDescription>{t('table:connection.description')}</CardDescription>
</CardHeader>
<CardContent className="flex flex-col">
{permissions['base|db_connection'] ? <ContentCard /> : t('table:connection.noPermission')}
{permissions?.['base|db_connection'] ? <ContentCard /> : t('table:connection.noPermission')}
</CardContent>
</Card>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const DraggableList = () => {
<DndKitContext onDragEnd={onDragEnd}>
<Droppable items={innerTables.map(({ id }) => ({ id }))}>
{innerTables.map((table) => (
<Draggable key={table.id} id={table.id}>
<Draggable key={table.id} id={table.id} disabled={!table.permission?.['table|update']}>
{({ setNodeRef, attributes, listeners, style, isDragging }) => (
<div
ref={setNodeRef}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { File, FileCsv, FileExcel } from '@teable/icons';
import { SUPPORTEDTYPE } from '@teable/openapi';
import { useConnection, useTablePermission } from '@teable/sdk';
import { useBasePermission, useConnection } from '@teable/sdk';
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -22,7 +22,7 @@ import { useAddTable } from './useAddTable';
export const TableList: React.FC = () => {
const { connected } = useConnection();
const addTable = useAddTable();
const permission = useTablePermission();
const permission = useBasePermission();
const { t } = useTranslation(['table']);
const [dialogVisible, setDialogVisible] = useState(false);
const [fileType, setFileType] = useState<SUPPORTEDTYPE>(SUPPORTEDTYPE.CSV);
Expand All @@ -36,7 +36,7 @@ export const TableList: React.FC = () => {
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<div className="px-3">
{permission['table|create'] && (
{permission?.['table|create'] && (
<Button variant={'outline'} size={'xs'} className={`${GUIDE_CREATE_TABLE} w-full`}>
<AddBoldIcon />
</Button>
Expand Down Expand Up @@ -84,7 +84,7 @@ export const TableList: React.FC = () => {
)}

<div className="overflow-y-auto px-3">
{connected && permission['table|update'] ? <DraggableList /> : <NoDraggableList />}
{connected && permission?.['table|update'] ? <DraggableList /> : <NoDraggableList />}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Table2 } from '@teable/icons';
import { useTablePermission } from '@teable/sdk/hooks';
import type { Table } from '@teable/sdk/model';
import { Button, cn } from '@teable/ui-lib/shadcn';
import { Input } from '@teable/ui-lib/shadcn/ui/input';
Expand All @@ -22,7 +21,6 @@ export const TableListItem: React.FC<IProps> = ({ table, isActive, className, is
const router = useRouter();
const { baseId } = router.query;
const viewId = router.query.viewId;
const permission = useTablePermission();

const navigateHandler = () => {
router.push(
Expand Down Expand Up @@ -66,7 +64,7 @@ export const TableListItem: React.FC<IProps> = ({ table, isActive, className, is
<EmojiPicker
className="flex size-5 items-center justify-center hover:bg-muted-foreground/60"
onChange={(icon: string) => table.updateIcon(icon)}
disabled={!permission['table|update']}
disabled={!table.permission?.['table|update']}
>
{table.icon ? (
<Emoji emoji={table.icon} size={'1rem'} />
Expand All @@ -78,7 +76,7 @@ export const TableListItem: React.FC<IProps> = ({ table, isActive, className, is
<p
className="grow truncate"
onDoubleClick={() => {
permission['table|update'] && setIsEditing(true);
table.permission?.['table|update'] && setIsEditing(true);
}}
>
{' ' + table.name}
Expand Down

0 comments on commit b32d7cc

Please sign in to comment.