mirror of
https://github.com/docmost/docmost.git
synced 2026-08-29 10:05:03 +08:00
refactor(base): drop dedicated /bases/files/upload endpoint, reuse /files/upload
Base file-cell uploads were hitting a parallel POST /bases/files/upload endpoint that re-implemented the multipart parse, the size limit handling, and the spaceId resolution that the standard page-attachment endpoint (POST /files/upload) already does. The two diverged on minor points (no audit log, no attachmentId support, slightly different permission check) without good reason — bases are pages (isBase=true), so the existing endpoint already handles them correctly. Server: delete uploadBaseFile and the now-unused BaseRepo injection. Client: route the file-cell uploader through the existing uploadFile helper in page-service. The base's pageId is a valid page id, so the server's pageRepo.findById succeeds and pageAccessService.validateCanEdit runs — which lines up with the Base edit ability at the space-role level (Manage Page and Manage Base track together for admins/writers, Read for readers).
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
|||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { IBaseProperty } from "@/features/base/types/base.types";
|
import { IBaseProperty } from "@/features/base/types/base.types";
|
||||||
import cellClasses from "@/features/base/styles/cells.module.css";
|
import cellClasses from "@/features/base/styles/cells.module.css";
|
||||||
import api from "@/lib/api-client";
|
import { uploadFile } from "@/features/page/services/page-service";
|
||||||
|
|
||||||
export type FileValue = {
|
export type FileValue = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -68,21 +68,15 @@ export function CellFile({
|
|||||||
|
|
||||||
const newFiles: FileValue[] = [...files];
|
const newFiles: FileValue[] = [...files];
|
||||||
|
|
||||||
|
// Reuse the page-attachment upload pipeline. A base IS a page
|
||||||
|
// (isBase=true) — the server's /files/upload endpoint accepts the
|
||||||
|
// base's pageId, runs the standard pageAccessService.validateCanEdit
|
||||||
|
// check (which lines up with Base edit at the space-role level per
|
||||||
|
// the casl rules), and stores the attachment via the same flow as
|
||||||
|
// any other page attachment.
|
||||||
for (const file of Array.from(fileList)) {
|
for (const file of Array.from(fileList)) {
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const attachment = await uploadFile(file, property.pageId);
|
||||||
formData.append("file", file);
|
|
||||||
formData.append("pageId", property.pageId);
|
|
||||||
|
|
||||||
const res = await api.post<FileValue>(
|
|
||||||
"/bases/files/upload",
|
|
||||||
formData,
|
|
||||||
{
|
|
||||||
headers: { "Content-Type": "multipart/form-data" },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const attachment = res as unknown as FileValue;
|
|
||||||
newFiles.push({
|
newFiles.push({
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
fileName: attachment.fileName,
|
fileName: attachment.fileName,
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ import {
|
|||||||
} from '../casl/interfaces/workspace-ability.type';
|
} from '../casl/interfaces/workspace-ability.type';
|
||||||
import WorkspaceAbilityFactory from '../casl/abilities/workspace-ability.factory';
|
import WorkspaceAbilityFactory from '../casl/abilities/workspace-ability.factory';
|
||||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||||
import { BaseRepo } from '@docmost/db/repos/base/base.repo';
|
|
||||||
import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo';
|
import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo';
|
||||||
import { validate as isValidUUID } from 'uuid';
|
import { validate as isValidUUID } from 'uuid';
|
||||||
import { EnvironmentService } from '../../integrations/environment/environment.service';
|
import { EnvironmentService } from '../../integrations/environment/environment.service';
|
||||||
@@ -72,7 +71,6 @@ export class AttachmentController {
|
|||||||
private readonly workspaceAbility: WorkspaceAbilityFactory,
|
private readonly workspaceAbility: WorkspaceAbilityFactory,
|
||||||
private readonly spaceAbility: SpaceAbilityFactory,
|
private readonly spaceAbility: SpaceAbilityFactory,
|
||||||
private readonly pageRepo: PageRepo,
|
private readonly pageRepo: PageRepo,
|
||||||
private readonly baseRepo: BaseRepo,
|
|
||||||
private readonly attachmentRepo: AttachmentRepo,
|
private readonly attachmentRepo: AttachmentRepo,
|
||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
private readonly tokenService: TokenService,
|
private readonly tokenService: TokenService,
|
||||||
@@ -165,87 +163,6 @@ export class AttachmentController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
@HttpCode(HttpStatus.OK)
|
|
||||||
@Post('bases/files/upload')
|
|
||||||
@UseInterceptors(FileInterceptor)
|
|
||||||
async uploadBaseFile(
|
|
||||||
@Req() req: any,
|
|
||||||
@Res() res: FastifyReply,
|
|
||||||
@AuthUser() user: User,
|
|
||||||
@AuthWorkspace() workspace: Workspace,
|
|
||||||
) {
|
|
||||||
const maxFileSize = bytes(this.environmentService.getFileUploadSizeLimit());
|
|
||||||
|
|
||||||
let file = null;
|
|
||||||
try {
|
|
||||||
file = await req.file({
|
|
||||||
limits: { fileSize: maxFileSize, fields: 3, files: 1 },
|
|
||||||
});
|
|
||||||
} catch (err: any) {
|
|
||||||
this.logger.error(err.message);
|
|
||||||
if (err?.statusCode === 413) {
|
|
||||||
throw new BadRequestException(
|
|
||||||
`File too large. Exceeds the ${this.environmentService.getFileUploadSizeLimit()} limit`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
throw new BadRequestException('Failed to upload file');
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageId = file.fields?.pageId?.value;
|
|
||||||
|
|
||||||
if (!pageId) {
|
|
||||||
throw new BadRequestException('pageId is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValidUUID(pageId)) {
|
|
||||||
throw new BadRequestException('Invalid pageId');
|
|
||||||
}
|
|
||||||
|
|
||||||
const base = await this.baseRepo.findById(pageId);
|
|
||||||
|
|
||||||
if (!base) {
|
|
||||||
throw new NotFoundException('Base not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const spaceId = base.spaceId;
|
|
||||||
|
|
||||||
const spaceAbilityCheck = await this.spaceAbility.createForUser(
|
|
||||||
user,
|
|
||||||
spaceId,
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
spaceAbilityCheck.cannot(
|
|
||||||
SpaceCaslAction.Edit,
|
|
||||||
SpaceCaslSubject.Base,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fileResponse = await this.attachmentService.uploadFile({
|
|
||||||
filePromise: file,
|
|
||||||
spaceId: spaceId,
|
|
||||||
userId: user.id,
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.send(fileResponse);
|
|
||||||
} catch (err: any) {
|
|
||||||
if (err?.statusCode === 413) {
|
|
||||||
const errMessage = `File too large. Exceeds the ${this.environmentService.getFileUploadSizeLimit()} limit`;
|
|
||||||
this.logger.error(errMessage);
|
|
||||||
throw new BadRequestException(errMessage);
|
|
||||||
}
|
|
||||||
this.logger.error(err);
|
|
||||||
throw new BadRequestException('Error processing file upload.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Get('/files/:fileId/:fileName')
|
@Get('/files/:fileId/:fileName')
|
||||||
async getFile(
|
async getFile(
|
||||||
|
|||||||
Reference in New Issue
Block a user