feat(backend): Dokumente und Bilder mit lokalem Datei-Upload (Modul 29)
DocumentsModule (Document: entityType/entityId, Dateiname, MIME-Typ, Groesse, Speicherpfad, Hochgeladen-von) fuer Bienenstand/Volk/Koenigin/ Durchsicht/Honigcharge. Upload als multipart/form-data (Multer, 10 MB Limit, MIME-Allowlist jpeg/png/webp/pdf), Speicherung unter DOCUMENTS_STORAGE_PATH (austauschbar). ARCHITECTURE.md Annahme 7 dokumentiert die Multi-Replica-Einschraenkung ohne geteiltes Volume.
This commit is contained in:
1 parent
e4d5af5fa7
commit
4a9fdd5c71
16 files changed
+728
No files matched your search
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
Res,
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { ApiBearerAuth, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||
import type { Response } from 'express';
|
||||
import { DocumentEntityType, Role } from '../../generated/prisma';
|
||||
import { Roles } from '../auth/decorators/roles.decorator';
|
||||
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||
import type { AuthenticatedUser } from '../auth/types/authenticated-user';
|
||||
import { isAllowedDocumentMimeType } from './document-mime-types';
|
||||
import { CreateDocumentDto } from './dto/create-document.dto';
|
||||
import { DocumentsService } from './documents.service';
|
||||
|
||||
const MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
||||
|
||||
function sanitizeFilenameHeader(fileName: string): string {
|
||||
return fileName.replace(/[\r\n"]/g, '_');
|
||||
}
|
||||
|
||||
@ApiTags('documents')
|
||||
@ApiBearerAuth()
|
||||
@Controller('documents')
|
||||
export class DocumentsController {
|
||||
constructor(private readonly documentsService: DocumentsService) {}
|
||||
|
||||
@Get()
|
||||
findAll(
|
||||
@Query('entityType') entityType: string,
|
||||
@Query('entityId') entityId: string,
|
||||
) {
|
||||
if (!entityType || !entityId) {
|
||||
throw new BadRequestException('entityType and entityId are required');
|
||||
}
|
||||
if (
|
||||
!Object.values(DocumentEntityType).includes(
|
||||
entityType as DocumentEntityType,
|
||||
)
|
||||
) {
|
||||
throw new BadRequestException('Invalid entityType');
|
||||
}
|
||||
return this.documentsService.findAll(
|
||||
entityType as DocumentEntityType,
|
||||
entityId,
|
||||
);
|
||||
}
|
||||
|
||||
@Roles(Role.ADMIN, Role.BEEKEEPER, Role.STAFF)
|
||||
@Post()
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@UseInterceptors(
|
||||
FileInterceptor('file', {
|
||||
limits: { fileSize: MAX_FILE_SIZE_BYTES },
|
||||
fileFilter: (_req, file, callback) => {
|
||||
if (!isAllowedDocumentMimeType(file.mimetype)) {
|
||||
callback(
|
||||
new BadRequestException('Nicht unterstützter Dateityp'),
|
||||
false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
callback(null, true);
|
||||
},
|
||||
}),
|
||||
)
|
||||
create(
|
||||
@UploadedFile() file: Express.Multer.File | undefined,
|
||||
@Body() dto: CreateDocumentDto,
|
||||
@CurrentUser() actor: AuthenticatedUser,
|
||||
) {
|
||||
if (!file) {
|
||||
throw new BadRequestException('No file uploaded');
|
||||
}
|
||||
return this.documentsService.create(
|
||||
file,
|
||||
dto.entityType,
|
||||
dto.entityId,
|
||||
actor.id,
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id/download')
|
||||
async download(@Param('id') id: string, @Res() res: Response): Promise<void> {
|
||||
const { buffer, fileName, mimeType } =
|
||||
await this.documentsService.readFile(id);
|
||||
res.setHeader('Content-Type', mimeType);
|
||||
res.setHeader(
|
||||
'Content-Disposition',
|
||||
`attachment; filename="${sanitizeFilenameHeader(fileName)}"`,
|
||||
);
|
||||
res.send(buffer);
|
||||
}
|
||||
|
||||
@Roles(Role.ADMIN, Role.BEEKEEPER, Role.STAFF)
|
||||
@Delete(':id')
|
||||
@HttpCode(204)
|
||||
delete(@Param('id') id: string, @CurrentUser() actor: AuthenticatedUser) {
|
||||
return this.documentsService.delete(id, actor.id);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user