File Storage
PO documents and delivery receipts are uploaded files. To keep large files off
the app server, uploads use presigned URLs in production; development uses a
local equivalent so no Cloudflare credentials are needed. The switch is
automatic on NODE_ENV, centralised in lib/storage.ts.
| Production | Development | |
|---|---|---|
| Backend | Cloudflare R2 (S3-compatible) | .dev-uploads/ (git-ignored) |
| Upload | Client → presigned PUT → R2 |
Client → PUT /api/files/dev/<key> → disk |
| Download | R2 presigned GET | GET /api/files/dev/<key> (auth-gated, 404 in prod) |
Upload flow (production — R2)
Client App Server Cloudflare R2
│── POST /api/files/sign ───────▶│ │
│ { fileName, mimeType } │── generate presigned ───▶│
│◀── { uploadUrl, key } ─────────│◀───── presigned URL ─────│
│── PUT uploadUrl (file bytes) ─────────────────────────────▶│
│── Server Action: link ────────▶│── INSERT PODocument ─────▶ (DB)
│ { poId, key, meta } │
Upload flow (development — local FS)
Client App Server .dev-uploads/
│── POST /api/files/sign ───────▶│ │
│◀── { uploadUrl, key } ─────────│ uploadUrl = /api/files/dev/<key>
│── PUT /api/files/dev/<key> ───▶│── write to disk ────────▶│
│── Server Action: link ────────▶│── INSERT PODocument ─────▶ (DB)
Downloads mirror this: generateDownloadUrl returns a /api/files/dev/<key>
GET URL in dev and an R2 presigned URL in prod. The
app/api/files/dev/[...key]/route.ts handler is auth-gated and returns 404 in
production.
Where files attach
- PO documents (
PODocument) — uploaded from the PO form's Documents section and at delivery confirmation. On the PO detail page they are grouped by type (lib/attachments.ts). - Delivery receipt (
Receipt) — uploaded at receipt confirmation; one per PO (@unique poId), upserted on repeat confirmations (notes preserved). - Approval signature (
User.signatureKey) — uploaded by approvers from the profile page; appears on exported PO documents. - Company branding (
Company.logoKey,Company.stampKey) — a logo and a stamp/seal uploaded per company from Admin → Companies → Edit → Branding (permissionmanage_vessels_accounts). Keys are deterministic (company-assets/<companyId>/{logo,stamp}.<ext>) so a re-upload overwrites in place. Both appear on exported POs — logo top-left, stamp in the approver's signatory block. Upload via theuploadCompanyAssetserver action (≤ 4 MB, PNG/JPG/WebP), not the presigned-URL flow.
Client-side upload is helped by lib/upload-files.ts. The export endpoint embeds
the approver's signature, the company logo and stamp, and a fixed-colour brand
bar at the bottom of the generated PDF/XLSX.
Required env (production)
R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME, R2_PUBLIC_URL
In development these can be left as placeholders. See Environment Variables.
Pelagia Portal (PPMS)
Overview
Build & Run
System
Product
- Feature Catalogue
- Pages and Navigation
- Workflows
- Purchase Orders
- Vendors and GST Lookup
- Inventory and Catalogue
- Inventory on Approval
- Notifications
- File Storage
- Design System
Planned
Quality
Ops
Engineering
Pelagia Portal (PPMS) — internal purchase-order management. Self-hosted on pms1, live at pms.pelagiamarine.com. This wiki tracks the shipped product; authoritative sources are the repo code, App/CLAUDE.md, Docs/, and CHANGELOG.md.