From 55bcb0d95c63b30cf465c45199354d350ffb6143 Mon Sep 17 00:00:00 2001 From: Harlekin Date: Wed, 2 Sep 2026 20:27:22 +0200 Subject: [PATCH] =?UTF-8?q?Phase=205=20abgeschlossen:=20Dateien=20mit=20vo?= =?UTF-8?q?llst=C3=A4ndiger=20Pr=C3=BCfkette?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upload → quarantine/ → Typprüfung → ClamAV → Neukodierung → store/. Es gibt keinen Pfad, auf dem eine Datei ungeprüft in store/ landet, und keinen Schalter, der die Kette abschaltet. Die Typprüfung liest Magic Bytes aus einer eigenen Signaturtabelle statt aus file-type: bei einer Positivliste aus fünf Formaten hilft es nicht, hundert erkennen zu können. Die Signatur muss an Position 0 stehen - ein Vorspann vor %PDF- ist der klassische Weg, eine Datei für zwei Programme zugleich lesbar zu machen. Wer trotzdem durchkommt, kommt als text/plain durch und wird unter der Endung des erkannten Typs ausgeliefert, nicht unter der hochgeladenen. Drei Ausgänge statt zwei: BLOCKED heisst "ist gefährlich", FAILED heisst "wissen wir nicht". Nur FAILED lässt sich freigeben, und die Freigabe wiederholt die Kette ohne den Scanner - Typprüfung und Neukodierung laufen dabei mit. Ein fehlender clamd macht damit keine Datei sauber; ein Test hält das fest. Auslieferung nur über /api/files/[id], mit Sichtbarkeitsprüfung der zugehörigen Akte, Protokoll ab Stufe 3 und den harten Kopfzeilen aus docs/07. Vorschaubilder dürfen inline, weil sie serverseitig als WebP neu kodiert wurden. Das Lichtbild im Aktenblatt ist damit gefüllt: der Feldwert ist die Kennung eines geprüften Anhangs, serverseitig zu einer Adresse aufgelöst. /api/events liefert attachment.scanned über pg_notify - die Grundlage, auf der Phase 6 den Aktenchat aufsetzt. Die Sichtbarkeit wird je Ereignis geprüft, sonst verriete schon die Kennung die Existenz. Nebenbei riss das Bündelbudget (121 kB gegen 120 erlaubte): lib/i18n/de.ts war ein Objekt, und eine Client-Komponente zieht die ganze Datei mit. Die Texte stehen jetzt in sechs Dateien nach Bereichen, de.ts setzt sie nur zusammen. Grösste Route wieder bei 118 kB, und sie wächst nicht mehr mit jedem neuen Bereich mit. 133 Tests im Web-Paket, 15 in der Datenbank, 28 e2e. --- docker/web.Dockerfile | 4 +- .../20260902220000_dateien/migration.sql | 27 + packages/db/prisma/schema.prisma | 23 +- pnpm-lock.yaml | 319 +++++------- start.sh | 16 + web/app/(app)/admin/richtlinie/PolicyForm.tsx | 17 +- web/app/(app)/admin/rollen/RoleRow.tsx | 22 +- .../(app)/admin/vorlagen/TemplateEditor.tsx | 35 +- web/app/(app)/dateien/actions.ts | 38 ++ .../[documentNumber]/DocumentActions.tsx | 30 +- .../(app)/dokumente/[documentNumber]/page.tsx | 52 ++ .../(app)/faelle/[caseNumber]/CaseActions.tsx | 23 +- .../(app)/faelle/[caseNumber]/CaseLinks.tsx | 23 +- .../faelle/[caseNumber]/EntryActions.tsx | 19 +- .../faelle/[caseNumber]/EntryComposer.tsx | 10 +- .../faelle/[caseNumber]/Participants.tsx | 31 +- web/app/(app)/faelle/[caseNumber]/page.tsx | 19 +- web/app/(app)/faelle/neu/NewCaseForm.tsx | 15 +- .../(app)/personalakten/[id]/Assignments.tsx | 24 +- web/app/(app)/personalakten/[id]/LinkCase.tsx | 20 +- .../personalakten/[id]/PersonnelHead.tsx | 46 +- .../personalakten/neu/NewPersonnelForm.tsx | 25 +- web/app/api/events/route.ts | 111 ++++ web/app/api/files/[id]/route.ts | 108 ++++ web/app/api/files/route.ts | 86 ++++ web/app/error.tsx | 8 +- web/components/documents/NewDocument.tsx | 19 +- web/components/documents/Sheet.tsx | 26 +- web/components/documents/SheetSwitch.tsx | 8 +- web/components/files/Attachments.tsx | 250 +++++++++ web/components/files/PhotoField.tsx | 169 +++++++ web/components/files/useScanEvents.ts | 43 ++ web/components/sections/SectionEditor.tsx | 20 +- web/components/sections/Sections.tsx | 34 +- web/lib/access/attachmentFilter.ts | 25 + web/lib/audit.ts | 3 + web/lib/files/clamav.ts | 93 ++++ web/lib/files/delivery.ts | 32 ++ web/lib/files/limits.ts | 26 + web/lib/files/magic.ts | 118 +++++ web/lib/files/paths.ts | 58 +++ web/lib/files/process.ts | 168 ++++++ web/lib/files/reencode.ts | 160 ++++++ web/lib/files/service.ts | 284 +++++++++++ web/lib/files/view.ts | 39 ++ web/lib/health/check.ts | 2 +- web/lib/health/clamav.ts | 24 - web/lib/i18n/admin.ts | 87 ++++ web/lib/i18n/cases.ts | 153 ++++++ web/lib/i18n/common.ts | 68 +++ web/lib/i18n/de.ts | 477 ++---------------- web/lib/i18n/documents.ts | 88 ++++ web/lib/i18n/people.ts | 59 +++ web/lib/i18n/shell.ts | 86 ++++ web/lib/realtime/listener.ts | 38 +- web/lib/realtime/publish.ts | 22 + web/package.json | 2 + web/tests/anhaenge.test.ts | 152 ++++++ web/tests/dateitypen.test.ts | 159 ++++++ web/tests/health.test.ts | 4 +- web/tests/neukodierung.test.ts | 94 ++++ web/tests/pruefkette.test.ts | 180 +++++++ 62 files changed, 3564 insertions(+), 857 deletions(-) create mode 100644 packages/db/prisma/migrations/20260902220000_dateien/migration.sql create mode 100644 web/app/(app)/dateien/actions.ts create mode 100644 web/app/api/events/route.ts create mode 100644 web/app/api/files/[id]/route.ts create mode 100644 web/app/api/files/route.ts create mode 100644 web/components/files/Attachments.tsx create mode 100644 web/components/files/PhotoField.tsx create mode 100644 web/components/files/useScanEvents.ts create mode 100644 web/lib/access/attachmentFilter.ts create mode 100644 web/lib/files/clamav.ts create mode 100644 web/lib/files/delivery.ts create mode 100644 web/lib/files/limits.ts create mode 100644 web/lib/files/magic.ts create mode 100644 web/lib/files/paths.ts create mode 100644 web/lib/files/process.ts create mode 100644 web/lib/files/reencode.ts create mode 100644 web/lib/files/service.ts create mode 100644 web/lib/files/view.ts delete mode 100644 web/lib/health/clamav.ts create mode 100644 web/lib/i18n/admin.ts create mode 100644 web/lib/i18n/cases.ts create mode 100644 web/lib/i18n/common.ts create mode 100644 web/lib/i18n/documents.ts create mode 100644 web/lib/i18n/people.ts create mode 100644 web/lib/i18n/shell.ts create mode 100644 web/lib/realtime/publish.ts create mode 100644 web/tests/anhaenge.test.ts create mode 100644 web/tests/dateitypen.test.ts create mode 100644 web/tests/neukodierung.test.ts create mode 100644 web/tests/pruefkette.test.ts diff --git a/docker/web.Dockerfile b/docker/web.Dockerfile index cf076d4..a7f2073 100644 --- a/docker/web.Dockerfile +++ b/docker/web.Dockerfile @@ -59,7 +59,9 @@ ENV NODE_ENV=production \ PORT=3000 \ HOSTNAME=0.0.0.0 -RUN apk add --no-cache wget openssl && \ +# qpdf säubert hochgeladene PDF-Dateien, siehe docs/07-dateien-und-sicherheit.md. +# Fehlt es, bleiben PDF-Uploads in der Quarantäne statt ungeprüft durchzugehen. +RUN apk add --no-cache wget openssl qpdf && \ addgroup -g 1001 -S afis && \ adduser -u 1001 -S afis -G afis diff --git a/packages/db/prisma/migrations/20260902220000_dateien/migration.sql b/packages/db/prisma/migrations/20260902220000_dateien/migration.sql new file mode 100644 index 0000000..8cebc07 --- /dev/null +++ b/packages/db/prisma/migrations/20260902220000_dateien/migration.sql @@ -0,0 +1,27 @@ +-- Phase 5: Dateien. +-- +-- Anhänge können jetzt auch an einem Dokument hängen (Lichtbild im +-- Aktenblatt), tragen die Maße des Bildes und merken sich, wer eine Datei +-- freigegeben hat, deren Prüfung nicht abgeschlossen werden konnte. + +ALTER TABLE "Attachment" + ADD COLUMN "documentId" TEXT, + ADD COLUMN "width" INTEGER, + ADD COLUMN "height" INTEGER, + ADD COLUMN "releasedById" TEXT, + ADD COLUMN "releasedAt" TIMESTAMP(3); + +CREATE INDEX "Attachment_documentId_idx" ON "Attachment"("documentId"); + +ALTER TABLE "Attachment" ADD CONSTRAINT "Attachment_documentId_fkey" + FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- Ein Anhang gehört zu höchstens einem Ort. Dieselbe Begründung wie beim +-- Dokument: zwei Besitzer wären eine Aussage über die Sichtbarkeit, die +-- niemand prüfen könnte. +ALTER TABLE "Attachment" ADD CONSTRAINT "Attachment_one_owner" + CHECK ( + (CASE WHEN "entryId" IS NULL THEN 0 ELSE 1 END) + + (CASE WHEN "caseId" IS NULL THEN 0 ELSE 1 END) + + (CASE WHEN "documentId" IS NULL THEN 0 ELSE 1 END) <= 1 + ); diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 70a16b1..10960af 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -583,6 +583,7 @@ model Document { case Case? @relation(fields: [caseId], references: [id], onDelete: Restrict) personnelFile PersonnelFile? @relation(fields: [personnelFileId], references: [id], onDelete: Restrict) sections DocumentSection[] + attachments Attachment[] @@index([caseId, createdAt(sort: Desc)]) @@index([personnelFileId, createdAt(sort: Desc)]) @@ -639,29 +640,45 @@ model DocumentSectionVersion { // Dateien // --------------------------------------------------------------------------- +/// Eine hochgeladene Datei. Sie ist erst dann etwas wert, wenn sie die +/// Pruefkette aus docs/07-dateien-und-sicherheit.md ganz durchlaufen hat: +/// Typpruefung ueber Magic Bytes, ClamAV, Neukodierung. Bis dahin liegt sie in +/// quarantine/ und wird nicht ausgeliefert. model Attachment { id String @id @default(uuid(7)) entryId String? caseId String? + documentId String? originalName String + /// Der **erkannte** Typ, nicht der gemeldete. Siehe lib/files/magic.ts. mimeType String sizeBytes Int sha256 String scanStatus ScanStatus @default(PENDING) scanMessage String? - storageKey String // UUID-Pfad in store/ + /// Pfad unterhalb von FILES_DIR, inklusive quarantine/ oder store/. + storageKey String thumbKey String? blurhash String? + width Int? + height Int? uploadedById String createdAt DateTime @default(now()) - entry CaseEntry? @relation(fields: [entryId], references: [id], onDelete: Cascade) - case Case? @relation(fields: [caseId], references: [id], onDelete: Cascade) + /// Freigabe einer Datei, die die Pruefung nicht abschliessen konnte. + /// Nur mit Recht 'admin', immer protokolliert. + releasedById String? + releasedAt DateTime? + + entry CaseEntry? @relation(fields: [entryId], references: [id], onDelete: Cascade) + case Case? @relation(fields: [caseId], references: [id], onDelete: Cascade) + document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade) @@index([sha256]) @@index([scanStatus]) @@index([caseId]) @@index([entryId]) + @@index([documentId]) @@index([uploadedById, createdAt]) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47ff250..5bab958 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,12 +85,15 @@ importers: '@afis/db': specifier: workspace:* version: link:../packages/db + blurhash: + specifier: 2.0.5 + version: 2.0.5 next: specifier: 15.5.25 - version: 15.5.25(@babel/core@7.29.7)(@playwright/test@1.62.1)(@types/node@22.20.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 15.5.25(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) next-auth: specifier: 5.0.0-beta.32 - version: 5.0.0-beta.32(next@15.5.25(@babel/core@7.29.7)(@playwright/test@1.62.1)(@types/node@22.20.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) + version: 5.0.0-beta.32(next@15.5.25(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) pg: specifier: 8.13.1 version: 8.13.1 @@ -100,6 +103,9 @@ importers: react-dom: specifier: ^19.0.0 version: 19.2.8(react@19.2.8) + sharp: + specifier: 0.34.4 + version: 0.34.4 uuidv7: specifier: 1.0.2 version: 1.0.2 @@ -628,145 +634,125 @@ packages: resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.35.4': - resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==} - engines: {node: '>=20.9.0'} + '@img/sharp-darwin-arm64@0.34.4': + resolution: {integrity: sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.35.4': - resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==} - engines: {node: '>=20.9.0'} + '@img/sharp-darwin-x64@0.34.4': + resolution: {integrity: sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-freebsd-wasm32@0.35.4': - resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==} - engines: {node: '>=20.9.0'} - os: [freebsd] - - '@img/sharp-libvips-darwin-arm64@1.3.3': - resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==} + '@img/sharp-libvips-darwin-arm64@1.2.3': + resolution: {integrity: sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.3.3': - resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==} + '@img/sharp-libvips-darwin-x64@1.2.3': + resolution: {integrity: sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.3.3': - resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==} + '@img/sharp-libvips-linux-arm64@1.2.3': + resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.3.3': - resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==} + '@img/sharp-libvips-linux-arm@1.2.3': + resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-ppc64@1.3.3': - resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==} + '@img/sharp-libvips-linux-ppc64@1.2.3': + resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==} cpu: [ppc64] os: [linux] - '@img/sharp-libvips-linux-riscv64@1.3.3': - resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==} - cpu: [riscv64] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.3.3': - resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==} + '@img/sharp-libvips-linux-s390x@1.2.3': + resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.3.3': - resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==} + '@img/sharp-libvips-linux-x64@1.2.3': + resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.3.3': - resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==} + '@img/sharp-libvips-linuxmusl-arm64@1.2.3': + resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.3.3': - resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==} + '@img/sharp-libvips-linuxmusl-x64@1.2.3': + resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.35.4': - resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==} - engines: {node: '>=20.9.0'} + '@img/sharp-linux-arm64@0.34.4': + resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.35.4': - resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==} - engines: {node: '>=20.9.0'} + '@img/sharp-linux-arm@0.34.4': + resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-ppc64@0.35.4': - resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==} - engines: {node: '>=20.9.0'} + '@img/sharp-linux-ppc64@0.34.4': + resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] - '@img/sharp-linux-riscv64@0.35.4': - resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==} - engines: {node: '>=20.9.0'} - cpu: [riscv64] - os: [linux] - - '@img/sharp-linux-s390x@0.35.4': - resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==} - engines: {node: '>=20.9.0'} + '@img/sharp-linux-s390x@0.34.4': + resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.35.4': - resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==} - engines: {node: '>=20.9.0'} + '@img/sharp-linux-x64@0.34.4': + resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.35.4': - resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==} - engines: {node: '>=20.9.0'} + '@img/sharp-linuxmusl-arm64@0.34.4': + resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.35.4': - resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==} - engines: {node: '>=20.9.0'} + '@img/sharp-linuxmusl-x64@0.34.4': + resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.35.4': - resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==} - engines: {node: '>=20.9.0'} - - '@img/sharp-webcontainers-wasm32@0.35.4': - resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==} - engines: {node: '>=20.9.0'} + '@img/sharp-wasm32@0.34.4': + resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-arm64@0.35.4': - resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==} - engines: {node: '>=20.9.0'} + '@img/sharp-win32-arm64@0.34.4': + resolution: {integrity: sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.35.4': - resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==} - engines: {node: ^20.9.0} + '@img/sharp-win32-ia32@0.34.4': + resolution: {integrity: sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.35.4': - resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==} - engines: {node: '>=20.9.0'} + '@img/sharp-win32-x64@0.34.4': + resolution: {integrity: sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] @@ -1699,6 +1685,9 @@ packages: better-result@2.10.0: resolution: {integrity: sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==} + blurhash@2.0.5: + resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==} + brace-expansion@1.1.18: resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} @@ -3243,14 +3232,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - sharp@0.35.4: - resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==} - engines: {node: '>=20.9.0'} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true + sharp@0.34.4: + resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -4075,111 +4059,92 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@img/colour@1.1.0': - optional: true + '@img/colour@1.1.0': {} - '@img/sharp-darwin-arm64@0.35.4': + '@img/sharp-darwin-arm64@0.34.4': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.3.3 + '@img/sharp-libvips-darwin-arm64': 1.2.3 optional: true - '@img/sharp-darwin-x64@0.35.4': + '@img/sharp-darwin-x64@0.34.4': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.3.3 + '@img/sharp-libvips-darwin-x64': 1.2.3 optional: true - '@img/sharp-freebsd-wasm32@0.35.4': - dependencies: - '@img/sharp-wasm32': 0.35.4 + '@img/sharp-libvips-darwin-arm64@1.2.3': optional: true - '@img/sharp-libvips-darwin-arm64@1.3.3': + '@img/sharp-libvips-darwin-x64@1.2.3': optional: true - '@img/sharp-libvips-darwin-x64@1.3.3': + '@img/sharp-libvips-linux-arm64@1.2.3': optional: true - '@img/sharp-libvips-linux-arm64@1.3.3': + '@img/sharp-libvips-linux-arm@1.2.3': optional: true - '@img/sharp-libvips-linux-arm@1.3.3': + '@img/sharp-libvips-linux-ppc64@1.2.3': optional: true - '@img/sharp-libvips-linux-ppc64@1.3.3': + '@img/sharp-libvips-linux-s390x@1.2.3': optional: true - '@img/sharp-libvips-linux-riscv64@1.3.3': + '@img/sharp-libvips-linux-x64@1.2.3': optional: true - '@img/sharp-libvips-linux-s390x@1.3.3': + '@img/sharp-libvips-linuxmusl-arm64@1.2.3': optional: true - '@img/sharp-libvips-linux-x64@1.3.3': + '@img/sharp-libvips-linuxmusl-x64@1.2.3': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.3.3': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.3.3': - optional: true - - '@img/sharp-linux-arm64@0.35.4': + '@img/sharp-linux-arm64@0.34.4': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.3.3 + '@img/sharp-libvips-linux-arm64': 1.2.3 optional: true - '@img/sharp-linux-arm@0.35.4': + '@img/sharp-linux-arm@0.34.4': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.3.3 + '@img/sharp-libvips-linux-arm': 1.2.3 optional: true - '@img/sharp-linux-ppc64@0.35.4': + '@img/sharp-linux-ppc64@0.34.4': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.3.3 + '@img/sharp-libvips-linux-ppc64': 1.2.3 optional: true - '@img/sharp-linux-riscv64@0.35.4': + '@img/sharp-linux-s390x@0.34.4': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.3.3 + '@img/sharp-libvips-linux-s390x': 1.2.3 optional: true - '@img/sharp-linux-s390x@0.35.4': + '@img/sharp-linux-x64@0.34.4': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.3.3 + '@img/sharp-libvips-linux-x64': 1.2.3 optional: true - '@img/sharp-linux-x64@0.35.4': + '@img/sharp-linuxmusl-arm64@0.34.4': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.3.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.3 optional: true - '@img/sharp-linuxmusl-arm64@0.35.4': + '@img/sharp-linuxmusl-x64@0.34.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + '@img/sharp-libvips-linuxmusl-x64': 1.2.3 optional: true - '@img/sharp-linuxmusl-x64@0.35.4': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.3.3 - optional: true - - '@img/sharp-wasm32@0.35.4': + '@img/sharp-wasm32@0.34.4': dependencies: '@emnapi/runtime': 1.11.3 optional: true - '@img/sharp-webcontainers-wasm32@0.35.4': - dependencies: - '@img/sharp-wasm32': 0.35.4 + '@img/sharp-win32-arm64@0.34.4': optional: true - '@img/sharp-win32-arm64@0.35.4': + '@img/sharp-win32-ia32@0.34.4': optional: true - '@img/sharp-win32-ia32@0.35.4': - optional: true - - '@img/sharp-win32-x64@0.35.4': + '@img/sharp-win32-x64@0.34.4': optional: true '@jridgewell/gen-mapping@0.3.13': @@ -5114,6 +5079,8 @@ snapshots: better-result@2.10.0: {} + blurhash@2.0.5: {} + brace-expansion@1.1.18: dependencies: balanced-match: 1.0.2 @@ -5545,8 +5512,8 @@ snapshots: '@typescript-eslint/parser': 8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) eslint: 9.39.5(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(jiti@2.7.0)) eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@2.7.0)) eslint-plugin-react-hooks: 5.2.0(eslint@9.39.5(jiti@2.7.0)) @@ -5565,7 +5532,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -5576,22 +5543,22 @@ snapshots: tinyglobby: 0.2.17 unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3) eslint: 9.39.5(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -5602,7 +5569,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.5(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.69.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -6370,13 +6337,13 @@ snapshots: natural-compare@1.4.0: {} - next-auth@5.0.0-beta.32(next@15.5.25(@babel/core@7.29.7)(@playwright/test@1.62.1)(@types/node@22.20.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8): + next-auth@5.0.0-beta.32(next@15.5.25(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8): dependencies: '@auth/core': 0.41.3 - next: 15.5.25(@babel/core@7.29.7)(@playwright/test@1.62.1)(@types/node@22.20.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + next: 15.5.25(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 - next@15.5.25(@babel/core@7.29.7)(@playwright/test@1.62.1)(@types/node@22.20.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + next@15.5.25(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: '@next/env': 15.5.25 '@swc/helpers': 0.5.15 @@ -6395,10 +6362,9 @@ snapshots: '@next/swc-win32-arm64-msvc': 15.5.25 '@next/swc-win32-x64-msvc': 15.5.25 '@playwright/test': 1.62.1 - sharp: 0.35.4(@types/node@22.20.1) + sharp: 0.34.4 transitivePeerDependencies: - '@babel/core' - - '@types/node' - babel-plugin-macros node-exports-info@1.6.2: @@ -6805,39 +6771,34 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.2 - sharp@0.35.4(@types/node@22.20.1): + sharp@0.34.4: dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.35.4 - '@img/sharp-darwin-x64': 0.35.4 - '@img/sharp-freebsd-wasm32': 0.35.4 - '@img/sharp-libvips-darwin-arm64': 1.3.3 - '@img/sharp-libvips-darwin-x64': 1.3.3 - '@img/sharp-libvips-linux-arm': 1.3.3 - '@img/sharp-libvips-linux-arm64': 1.3.3 - '@img/sharp-libvips-linux-ppc64': 1.3.3 - '@img/sharp-libvips-linux-riscv64': 1.3.3 - '@img/sharp-libvips-linux-s390x': 1.3.3 - '@img/sharp-libvips-linux-x64': 1.3.3 - '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 - '@img/sharp-libvips-linuxmusl-x64': 1.3.3 - '@img/sharp-linux-arm': 0.35.4 - '@img/sharp-linux-arm64': 0.35.4 - '@img/sharp-linux-ppc64': 0.35.4 - '@img/sharp-linux-riscv64': 0.35.4 - '@img/sharp-linux-s390x': 0.35.4 - '@img/sharp-linux-x64': 0.35.4 - '@img/sharp-linuxmusl-arm64': 0.35.4 - '@img/sharp-linuxmusl-x64': 0.35.4 - '@img/sharp-webcontainers-wasm32': 0.35.4 - '@img/sharp-win32-arm64': 0.35.4 - '@img/sharp-win32-ia32': 0.35.4 - '@img/sharp-win32-x64': 0.35.4 - '@types/node': 22.20.1 - optional: true + '@img/sharp-darwin-arm64': 0.34.4 + '@img/sharp-darwin-x64': 0.34.4 + '@img/sharp-libvips-darwin-arm64': 1.2.3 + '@img/sharp-libvips-darwin-x64': 1.2.3 + '@img/sharp-libvips-linux-arm': 1.2.3 + '@img/sharp-libvips-linux-arm64': 1.2.3 + '@img/sharp-libvips-linux-ppc64': 1.2.3 + '@img/sharp-libvips-linux-s390x': 1.2.3 + '@img/sharp-libvips-linux-x64': 1.2.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.3 + '@img/sharp-libvips-linuxmusl-x64': 1.2.3 + '@img/sharp-linux-arm': 0.34.4 + '@img/sharp-linux-arm64': 0.34.4 + '@img/sharp-linux-ppc64': 0.34.4 + '@img/sharp-linux-s390x': 0.34.4 + '@img/sharp-linux-x64': 0.34.4 + '@img/sharp-linuxmusl-arm64': 0.34.4 + '@img/sharp-linuxmusl-x64': 0.34.4 + '@img/sharp-wasm32': 0.34.4 + '@img/sharp-win32-arm64': 0.34.4 + '@img/sharp-win32-ia32': 0.34.4 + '@img/sharp-win32-x64': 0.34.4 shebang-command@2.0.0: dependencies: diff --git a/start.sh b/start.sh index 3e060e7..1357e1b 100755 --- a/start.sh +++ b/start.sh @@ -89,6 +89,22 @@ pnpm db:deploy >/dev/null info "Grundzustand einspielen" pnpm db:seed >/dev/null +# --- Dateiablage ------------------------------------------------------------- +# Im Stack ist das ein Volume, hier ein Verzeichnis. Ohne es scheitert der +# erste Upload mit einem Fehler, der nichts erklärt. +FILES_DIR_LOKAL="$(grep -E '^FILES_DIR=' .env 2>/dev/null | cut -d= -f2- || true)" +FILES_DIR_LOKAL="${FILES_DIR_LOKAL:-/tmp/afis-files}" +mkdir -p "${FILES_DIR_LOKAL}/quarantine" "${FILES_DIR_LOKAL}/store" +info "Dateiablage unter ${FILES_DIR_LOKAL}" + +# Die Prüfkette braucht beides. Fehlt eines, bleiben die betroffenen Uploads in +# der Quarantäne — sie gehen nicht ungeprüft durch. Siehe docs/07. +CLAMAV_SOCKET_LOKAL="$(grep -E '^CLAMAV_SOCKET=' .env 2>/dev/null | cut -d= -f2- || true)" +[ -S "${CLAMAV_SOCKET_LOKAL:-/var/run/clamav/clamd.sock}" ] \ + || warn "Kein clamd erreichbar: Uploads landen auf 'Prüfung unvollständig' und brauchen die Freigabe von Hand" +command -v qpdf >/dev/null \ + || warn "qpdf fehlt: PDF-Uploads landen auf 'Prüfung unvollständig' (im Container ist es enthalten)" + if [ "$NUR_DB" = "1" ]; then info "Datenbank läuft auf localhost:${DB_PORT}. Beenden mit: docker stop ${DB_CONTAINER}" exit 0 diff --git a/web/app/(app)/admin/richtlinie/PolicyForm.tsx b/web/app/(app)/admin/richtlinie/PolicyForm.tsx index cda3a21..6162a49 100644 --- a/web/app/(app)/admin/richtlinie/PolicyForm.tsx +++ b/web/app/(app)/admin/richtlinie/PolicyForm.tsx @@ -1,7 +1,8 @@ 'use client' import { useState, useTransition } from 'react' -import { de } from '@/lib/i18n/de' +import { settingsText } from '@/lib/i18n/admin' +import { caseText } from '@/lib/i18n/cases' import { savePolicy } from './actions' const POLICIES = ['APPEND_ONLY', 'GRACE_WINDOW', 'OPEN'] as const @@ -24,7 +25,7 @@ export function PolicyForm({ const [pending, startTransition] = useTransition() function submit() { - if (!confirm(de.settings.confirm)) return + if (!confirm(settingsText.confirm)) return setError(null) startTransition(async () => { const result = await savePolicy({ @@ -40,7 +41,7 @@ export function PolicyForm({ return (
- {de.settings.policy} + {settingsText.policy}
{POLICIES.map((value) => ( ))}
@@ -96,10 +97,10 @@ export function PolicyForm({ disabled={pending} className="border border-line bg-raised px-4 py-2 font-condensed text-xs text-accent disabled:text-text-dim" > - {pending ? de.case.saving : de.settings.save} + {pending ? caseText.saving : settingsText.save} {saved ? ( - {de.settings.saved} + {settingsText.saved} ) : null} {error ? {error} : null}
diff --git a/web/app/(app)/admin/rollen/RoleRow.tsx b/web/app/(app)/admin/rollen/RoleRow.tsx index 9ea301f..065ceff 100644 --- a/web/app/(app)/admin/rollen/RoleRow.tsx +++ b/web/app/(app)/admin/rollen/RoleRow.tsx @@ -1,7 +1,7 @@ 'use client' import { useState, useTransition } from 'react' -import { de } from '@/lib/i18n/de' +import { roleAdminText } from '@/lib/i18n/admin' import { OPT_IN_PERMISSIONS, PERMISSIONS, type Permission } from '@/lib/auth/permissions' import { saveRoleMapping } from './actions' @@ -52,7 +52,7 @@ export function RoleRow({ } function submit() { - if (!confirm(de.roleAdmin.confirm)) return + if (!confirm(roleAdminText.confirm)) return setError(null) startTransition(async () => { const result = await saveRoleMapping({ @@ -77,14 +77,14 @@ export function RoleRow({ {role.discordRoleId} {!role.isPresent ? ( - {de.roleAdmin.missingFromGuild} + {roleAdminText.missingFromGuild} ) : null}
diff --git a/web/app/(app)/admin/vorlagen/TemplateEditor.tsx b/web/app/(app)/admin/vorlagen/TemplateEditor.tsx index 1eb7a6c..85c6c7e 100644 --- a/web/app/(app)/admin/vorlagen/TemplateEditor.tsx +++ b/web/app/(app)/admin/vorlagen/TemplateEditor.tsx @@ -1,7 +1,8 @@ 'use client' import { useMemo, useState, useTransition } from 'react' -import { de } from '@/lib/i18n/de' +import { templateAdminText } from '@/lib/i18n/admin' +import { caseText, sectionText } from '@/lib/i18n/cases' import { isInputField, looksLikeFields, type TemplateField } from '@/lib/templates/fields' import { saveTemplate } from './actions' @@ -41,11 +42,11 @@ export function TemplateEditor({ vorlage }: { vorlage: Vorlage }) { // Nur eine grobe Formprüfung für die Vorschau. Ob die Vorlage // gespeichert werden darf, entscheidet allein der Server. if (!looksLikeFields(roh)) { - return { ok: false as const, fehler: de.templateAdmin.fieldsInvalid } + return { ok: false as const, fehler: templateAdminText.fieldsInvalid } } return { ok: true as const, fields: roh } } catch { - return { ok: false as const, fehler: de.templateAdmin.invalid } + return { ok: false as const, fehler: templateAdminText.invalid } } }, [json]) @@ -62,7 +63,7 @@ export function TemplateEditor({ vorlage }: { vorlage: Vorlage }) { numberPadding, fields: geparst.fields, }) - if (antwort.ok) setResult(de.templateAdmin.saved(antwort.version)) + if (antwort.ok) setResult(templateAdminText.saved(antwort.version)) else setError(antwort.error) }) } @@ -73,14 +74,14 @@ export function TemplateEditor({ vorlage }: { vorlage: Vorlage }) {

{vorlage.name}

{vorlage.key} - {de.templateAdmin.version(vorlage.version)} + {templateAdminText.version(vorlage.version)}