Repository navigation
Upload invoice PDFs into a private Directus folder - #287
Conversation
Invoice PDFs (Rechnung, Rechnungsberichtigung, Stornorechnung) were uploaded into the general file library, which the Public role can read, so anyone with a file id could download them via /assets/<id>. New invoice documents are now uploaded into the folder named by the INVOICE_FILES_FOLDER env var; the Public policy excludes that folder (setup steps in docs/tickets.md). If the variable is unset, invoicing keeps working, but a warning is logged and a Slack alert is sent once per process. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBHiR6NFRe6QR5SBjSv4nw
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
The failing check is
So a fresh Generated by Claude Code |
|
@claude What happens if the provided folder id is invalid and the folder cannot be found / used as a target destination ? |
|
Update: this describes 01b3520. Since 10476fb, an invalid folder no longer aborts anything. The upload is retried without the folder (stored publicly, warning plus Slack alert), and both the paid hook and
I checked this against the @directus/api 35.2.0 source and our call sites. Short answer: the upload fails cleanly, nothing is written, and the error is only logged.
Following the "no runtime guards for once-verified setup" rule, I'm not adding validation. After the deploy, verify once:
Separate follow-up idea, not part of this PR: send a Slack alert when the paid hook aborts, since today that failure only shows up in the logs. Generated by Claude Code |
|
@claude That way, the user-flow would not be broken, and we would simply need to remedy this after the fact (that's what the slack message is for) |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01b3520fe7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
🟡 Changes recommended
Missing folder configuration still allows customer invoices to be uploaded into the publicly readable library.
1 open issue
What changed in this PR
Routes ticket invoice PDFs into a configured Directus folder, with privacy enforced through deployment permissions.
Changes:
- Passes the invoice folder through all invoice upload paths.
- Adds missing-configuration alerts and folder propagation tests.
- Documents access restrictions and migration of existing invoices.
| File | Description |
|---|---|
| docs/tickets.md | Documents private-folder setup and verification. |
| directus-cms/extensions/directus-extension-programmierbar-bundle/src/ticket-order-processing/index.ts | Passes the folder for original invoices. |
| directus-cms/extensions/directus-extension-programmierbar-bundle/src/ticket-order-processing/__tests__/index.test.ts | Tests original-invoice folder propagation. |
| directus-cms/extensions/directus-extension-programmierbar-bundle/src/shared/secure-invoice-folder.ts | Resolves configuration and alerts when missing. |
| directus-cms/extensions/directus-extension-programmierbar-bundle/src/shared/invoice-service.ts | Adds folder metadata to uploads. |
| directus-cms/extensions/directus-extension-programmierbar-bundle/src/shared/__tests__/secure-invoice-folder.test.ts | Tests configuration, alerts, and upload metadata. |
| directus-cms/extensions/directus-extension-programmierbar-bundle/src/invoice-lifecycle/index.ts | Sets the folder for lifecycle uploads. |
| directus-cms/extensions/directus-extension-programmierbar-bundle/src/invoice-lifecycle/__tests__/index.test.ts | Tests folder propagation across lifecycle operations. |
| directus-cms/.env.example | Documents the folder environment variable. |
Review effort: Balanced
If uploading an invoice PDF into INVOICE_FILES_FOLDER fails (e.g. an unknown or malformed folder id), retry once without the folder so the invoice flow keeps working, log a warning with the invoice number and alert Slack, like an unset folder. Without a folder there is no retry; a failing retry throws as before. Both alerts are now retried with the next invoice when the Slack message could not be delivered, instead of being suppressed for the process. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBHiR6NFRe6QR5SBjSv4nw
|
The fallback is in 10476fb. An invalid Generated by Claude Code |
The fallback to the public file library dropped the folder after any upload error, so a transient storage or DB failure followed by a successful retry would have published a correctly configured invoice. Fall back only when Directus rejected the folder itself (an InvalidForeignKeyError for `folder`, or the Postgres invalid-uuid error for the configured value); rethrow every other error unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBHiR6NFRe6QR5SBjSv4nw
Other open PRs log unrelated render warnings (e.g. a missing Leistungsdatum) through the same logger. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBHiR6NFRe6QR5SBjSv4nw

Requested by Richard Hartmann · Slack thread
Before: invoice PDFs (Rechnung, Rechnungsberichtigung, Stornorechnung) land in the public file library and can be downloaded by anyone with the link (
/assets/<file-id>).After: new invoices land in a dedicated folder that the Public role cannot read.
How
INVOICE_FILES_FOLDER(id of adirectus_filesfolder), read from the extension contextenvlikeSTORAGE_LOCATIONS.renderAndUploadInvoice/issueOriginalInvoicetake an optionalfolderand pass it toFilesService.uploadOne. All upload paths set it: the paid hook (ticket-order-processing) and all fourinvoice-lifecycleroutes (regenerate invoice/correction, correction, cancellation).shared/secure-invoice-folder.tsresolves the folder. If unset, the upload still happens (invoicing must not break), but a warning is logged and a Slack alert is sent once per process, since this needs a human to fix the configuration.docs/tickets.md→ "Invoice File Privacy".folderin the upload metadata (and absent when unset), and the folder on every call site.Deployment
Rechnungen); its id is the last URL segment. No subfolders.INVOICE_FILES_FOLDER=<folder-id>in the Directus environment.directus_files→ Read → Use Custom → Item Permissions:{ "_or": [{ "folder": { "_null": true } }, { "folder": { "_neq": "<folder-id>" } }] }_nullbranch is required: Directus 11 runs_neqas SQLNOT folder = '<id>', which excludes files without a folder, so_neqalone would hide all root-level files (website images). Apply the same filter to any other policy that grants Public read ondirectus_files.Rechnung→ select → Move to Folder). File ids do not change, so existing references stay valid./assets/<invoice-file-id>returns 403,/fileslists no invoices, a regular public image still loads.Generated by Claude Code