From b3b3caa5546684957e980047b6573b3397ddcbf8 Mon Sep 17 00:00:00 2001 From: caoxing Date: Fri, 3 Jan 2025 18:15:57 +0800 Subject: [PATCH] fix: export should limit by permission --- .../open-api/export-open-api.controller.ts | 2 +- .../export/open-api/export-open-api.module.ts | 4 +-- .../open-api/export-open-api.service.ts | 34 +++++++++++++++---- .../app/blocks/view/list/ViewListItem.tsx | 2 +- packages/core/src/auth/role/table.ts | 4 +-- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/apps/nestjs-backend/src/features/export/open-api/export-open-api.controller.ts b/apps/nestjs-backend/src/features/export/open-api/export-open-api.controller.ts index 177e2381ad..fa15c503ab 100644 --- a/apps/nestjs-backend/src/features/export/open-api/export-open-api.controller.ts +++ b/apps/nestjs-backend/src/features/export/open-api/export-open-api.controller.ts @@ -6,7 +6,7 @@ import { ExportOpenApiService } from './export-open-api.service'; @Controller('api/export') @UseGuards(PermissionGuard) -export class ExportController { +export class ExportOpenApiController { constructor(private readonly exportOpenService: ExportOpenApiService) {} @Get(':tableId') @Permissions('table|export', 'view|read') diff --git a/apps/nestjs-backend/src/features/export/open-api/export-open-api.module.ts b/apps/nestjs-backend/src/features/export/open-api/export-open-api.module.ts index 4ec4825688..2b18c6b1af 100644 --- a/apps/nestjs-backend/src/features/export/open-api/export-open-api.module.ts +++ b/apps/nestjs-backend/src/features/export/open-api/export-open-api.module.ts @@ -1,12 +1,12 @@ import { Module } from '@nestjs/common'; import { FieldModule } from '../../field/field.module'; import { RecordModule } from '../../record/record.module'; -import { ExportController } from './export-open-api.controller'; +import { ExportOpenApiController } from './export-open-api.controller'; import { ExportOpenApiService } from './export-open-api.service'; @Module({ imports: [RecordModule, FieldModule], - controllers: [ExportController], + controllers: [ExportOpenApiController], providers: [ExportOpenApiService], exports: [ExportOpenApiService], }) diff --git a/apps/nestjs-backend/src/features/export/open-api/export-open-api.service.ts b/apps/nestjs-backend/src/features/export/open-api/export-open-api.service.ts index 730b8622cb..3f9488a7ae 100644 --- a/apps/nestjs-backend/src/features/export/open-api/export-open-api.service.ts +++ b/apps/nestjs-backend/src/features/export/open-api/export-open-api.service.ts @@ -1,7 +1,7 @@ import { Readable } from 'stream'; import { BadRequestException, Injectable, Logger } from '@nestjs/common'; -import type { IAttachmentCellValue } from '@teable/core'; -import { FieldType, ViewType } from '@teable/core'; +import type { IAttachmentCellValue, IFilter } from '@teable/core'; +import { FieldType, mergeFilter, ViewType } from '@teable/core'; import { PrismaService } from '@teable/db-main-prisma'; import type { Response } from 'express'; import Papa from 'papaparse'; @@ -17,7 +17,15 @@ export class ExportOpenApiService { private readonly recordService: RecordService, private readonly prismaService: PrismaService ) {} - async exportCsvFromTable(response: Response, tableId: string, viewId?: string) { + async exportCsvFromTable( + response: Response, + tableId: string, + viewId?: string, + exportQuery?: { + projection?: string[]; + recordFilter?: IFilter; + } + ) { let count = 0; let isOver = false; const csvStream = new Readable({ @@ -47,6 +55,7 @@ export class ExportOpenApiService { name: true, id: true, type: true, + filter: true, }, }) .catch((e) => { @@ -68,9 +77,17 @@ export class ExportOpenApiService { csvStream.pipe(response); // set headers as first row - const headers = await this.fieldService.getFieldsByQuery(tableId, { - viewId: viewRaw?.id ? viewRaw?.id : undefined, - filterHidden: viewRaw?.id ? true : undefined, + const headers = ( + await this.fieldService.getFieldsByQuery(tableId, { + viewId: viewRaw?.id ? viewRaw?.id : undefined, + filterHidden: viewRaw?.id ? true : undefined, + }) + ).filter((field) => { + if (exportQuery?.projection?.length) { + return exportQuery?.projection.includes(field.id); + } + + return true; }); const headerData = Papa.unparse([headers.map((h) => h.name)]); @@ -89,12 +106,17 @@ export class ExportOpenApiService { csvStream.push('\uFEFF'); csvStream.push(headerData); + const mergedFilter = viewRaw?.filter + ? mergeFilter(JSON.parse(viewRaw?.filter), exportQuery?.recordFilter) + : exportQuery?.recordFilter; + try { while (!isOver) { const { records } = await this.recordService.getRecords(tableId, { take: 1000, skip: count, viewId: viewRaw?.id ? viewRaw?.id : undefined, + filter: mergedFilter, }); if (records.length === 0) { isOver = true; diff --git a/apps/nextjs-app/src/features/app/blocks/view/list/ViewListItem.tsx b/apps/nextjs-app/src/features/app/blocks/view/list/ViewListItem.tsx index 7d841a83c5..eaa7193c33 100644 --- a/apps/nextjs-app/src/features/app/blocks/view/list/ViewListItem.tsx +++ b/apps/nextjs-app/src/features/app/blocks/view/list/ViewListItem.tsx @@ -164,7 +164,7 @@ export const ViewListItem: React.FC = ({ view, removable, isActive }) => {t('view.action.rename')} )} - {view.type === 'grid' && permission['view|read'] && ( + {view.type === 'grid' && permission['table|export'] && (