diff --git a/paperless-backend/src/email/email.controller.spec.ts b/paperless-backend/src/email/email.controller.spec.ts index 9c1f1f7..a34b363 100644 --- a/paperless-backend/src/email/email.controller.spec.ts +++ b/paperless-backend/src/email/email.controller.spec.ts @@ -7,6 +7,9 @@ import { Attachment } from '../database/entities/attachment.entity'; import { Content } from '../database/entities/content.entity'; import { PaperlessService } from '../paperless/paperless.service'; import { ImapFolderService } from './imap-folder.service'; +import { Reflector } from '@nestjs/core'; +import { PERMISSIONS_KEY } from '../auth/permissions.decorator'; +import { Permission } from '../auth/permissions.enum'; const mockEmails: Partial[] = [ { @@ -173,6 +176,47 @@ describe('EmailController', () => { }); }); + describe('ignore', () => { + it.each([ + ['Neu', 0], + ['Fehler', 2], + ['zur Nachbearbeitung', 4], + ])( + 'markiert eine E-Mail mit Status %s als ignoriert', + async (_l, status) => { + emailRepo.findOneOrFail.mockResolvedValue({ Id: 1, Status: status }); + + const result = await controller.ignore('1', { user: {} }); + + expect(emailRepo.save).toHaveBeenCalledWith( + expect.objectContaining({ Id: 1, Status: 3 }), + ); + expect(result).toEqual(expect.objectContaining({ Status: 3 })); + }, + ); + + it('lehnt eine bereits verarbeitete E-Mail ab', async () => { + emailRepo.findOneOrFail.mockResolvedValue({ Id: 1, Status: 1 }); + + await expect(controller.ignore('1', { user: {} })).rejects.toThrow( + BadRequestException, + ); + expect(emailRepo.save).not.toHaveBeenCalled(); + }); + + it('ist für alle mit VIEW_MAIL freigegeben', () => { + const handler = Object.getOwnPropertyDescriptor( + EmailController.prototype, + 'ignore', + )?.value as () => unknown; + const permissions = new Reflector().get( + PERMISSIONS_KEY, + handler, + ); + expect(permissions).toEqual([Permission.VIEW_MAIL]); + }); + }); + it('updateStatus speichert den neuen Status', async () => { await controller.updateStatus('1', 2); expect(emailRepo.save).toHaveBeenCalledWith( diff --git a/paperless-backend/src/email/email.controller.ts b/paperless-backend/src/email/email.controller.ts index eaa66b9..2278bd4 100644 --- a/paperless-backend/src/email/email.controller.ts +++ b/paperless-backend/src/email/email.controller.ts @@ -32,6 +32,8 @@ import { Permission } from '../auth/permissions.enum'; * würde sie sonst sofort wieder auf "Verarbeitet" setzen. */ const STATUS_NEU = 0; +const STATUS_VERARBEITET = 1; +const STATUS_IGNORIERT = 3; const STATUS_NACHBEARBEITUNG = 4; @Controller('api/emails') @@ -123,6 +125,39 @@ export class EmailController { return { message: 'Status aktualisiert' }; } + /** + * Markiert eine E-Mail als ignoriert. Anders als `PATCH :id/status` darf das + * jeder mit Zugriff aufs Mailpostfach – der Endpunkt kann aber nur Status 3 + * setzen. Bereits verarbeitete E-Mails bleiben unangetastet. + */ + @Post(':id/ignore') + @RequirePermissions(Permission.VIEW_MAIL) + async ignore( + @Param('id') id: string, + @Request() req: { user?: { email?: string; userId?: string } }, + ) { + const email = await this.emailRepo.findOneOrFail({ + where: { Id: parseInt(id, 10) }, + }); + + if (email.Status === STATUS_VERARBEITET) { + throw new BadRequestException( + 'Eine bereits verarbeitete E-Mail kann nicht ignoriert werden.', + ); + } + + const vorherigerStatus = email.Status; + email.Status = STATUS_IGNORIERT; + await this.emailRepo.save(email); + + this.logger.log( + `E-Mail ${id} von Status ${vorherigerStatus} als ignoriert markiert ` + + `(durch ${req.user?.email ?? req.user?.userId ?? 'unbekannt'})`, + ); + + return email; + } + @Post(':id/reimport') @RequirePermissions(Permission.MANAGE_ALL) async reimport( diff --git a/paperless-frontend/src/api/emails.ts b/paperless-frontend/src/api/emails.ts index d780117..e42386d 100644 --- a/paperless-frontend/src/api/emails.ts +++ b/paperless-frontend/src/api/emails.ts @@ -49,6 +49,10 @@ export const emailsApi = { updateStatus: (id: number, status: number) => api.patch<{ message?: string }>(`/api/emails/${id}/status`, { status }).then((r) => r.data), + // Markiert eine E-Mail als ignoriert (für alle mit Zugriff aufs Mailpostfach). + ignore: (id: number) => + api.post(`/api/emails/${id}/ignore`).then((r) => r.data), + // Gibt eine bereits abgeschlossene E-Mail wieder zur Bearbeitung frei (nur Admins). reimport: (id: number) => api.post(`/api/emails/${id}/reimport`).then((r) => r.data), diff --git a/paperless-frontend/src/pages/MailDetailPage.tsx b/paperless-frontend/src/pages/MailDetailPage.tsx index 66025f4..c9db563 100644 --- a/paperless-frontend/src/pages/MailDetailPage.tsx +++ b/paperless-frontend/src/pages/MailDetailPage.tsx @@ -47,10 +47,10 @@ export default function MailDetailPage() { const handleIgnore = async () => { if (!email) return; try { - await emailsApi.updateStatus(email.Id, 3); + await emailsApi.ignore(email.Id); message.success('E-Mail als ignoriert markiert'); navigate('/mailpostfach'); - } catch (err) { + } catch { message.error('Fehler beim Markieren der E-Mail'); } };