4 File Storage
Hardik edited this page 2026-06-21 01:17:28 +05:30

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 (permission manage_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 the uploadCompanyAsset server 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.