Two problems reported by the user:
1. File downloads broken on the lab side.
The link in JobFilesPanel pointed straight at Appwrite's
/storage/.../view URL. Storage permissions are scoped to the job's two
teams, but the browser only has a session cookie for our app domain,
not for db.kovaksoft.com — so the cross-origin request hit Appwrite
as a guest and 401'd.
New /api/jobs/[jobId]/files/[fileId]/download route. requireTenant()
first, then verify the caller's tenant is one of (clinicTenantId,
labTenantId) on the parent job, then storage.getFileDownload via the
admin SDK and stream the buffer back with Content-Disposition:
attachment so the browser saves it under the original filename.
listJobFiles now hands out that relative URL instead of the Appwrite
one — same anchor in the panel, just routed through us.
2. Saves and edits feel slow whenever a notification is involved.
Every mutation was awaiting logAudit, createNotification and
syncFinanceForJob in sequence. None of these need to block the user
response — audit is best-effort logging, notifications are async UX,
and the finance sync is idempotent and re-runs on the next mutation
anyway. Switched all 46 call sites across the action modules to
void-fire-and-forget (matching the pattern we already used in
clinic-pricing-actions). Net effect: each mutation drops ~3 sequential
Appwrite roundtrips before the server action returns.
Cluster: Appwrite container _APP_STORAGE_LIMIT 30000000 → 209715200
(200MB) in /root/services/appwrite/.env on kovaksoft-coolify, then
docker compose up -d to roll the worker pool with the new value.
Backup of the .env left at .env.bak.<date>.
Bucket: job-files maximumFileSize updated to 209715200 via Appwrite MCP
(storage_update_bucket).
App: MAX_FILE_BYTES in both the upload API route and the original server
action raised to 200MB. Client-side panel guard relaxed accordingly —
one large file is now allowed to fill the entire batch (the 200MB
proxy/serverActions cap is the bottleneck, not the per-file rule).
Error copy updated.
isletmem and any other tenants on the cluster also get the new limit,
which is the desired behaviour — old 30MB ceiling was a relic of an
Appwrite default that no DLS workflow can actually live with.
node-appwrite's storage.createFile happily takes the Web File API today,
but Next.js's multipart parser had already consumed the request body by
the time the SDK tries to stream it again — the SDK's second pass dies
with 'Unexpected end of form'. isletmem-kovakcrm's logo-actions uses the
documented pattern: arrayBuffer → Buffer → InputFile.fromBuffer(buf,
name). Adopting the same approach in uploadJobFilesAction.
The middlewareClientMaxBodySize bump from the previous commit still
matters (lifts the 10MB cap so the body even reaches us), but on its own
it wasn't enough: the Web File handoff itself was broken.
InputFile is exported from 'node-appwrite/file' (separate entry point —
the helper isn't on the main package export).
Next 16 caps any request body that flows through middleware at 10MB by
default. Our auth middleware matches every path, so /jobs/:id POSTs from
the file upload form hit 'Request body exceeded 10MB / Unexpected end of
form' the moment a user picked anything bigger than ~10MB total — the
server action never even ran.
serverActions.bodySizeLimit alone isn't enough; the new
middlewareClientMaxBodySize knob (Next 16) is the one that gates
middleware-handled bodies. Set both to 100mb so the 30MB-per-file bucket
limit is what actually matters.
The key isn't in NextConfig's TS types yet (Next 16.1), so it's assigned
via a narrow cast on the side rather than dropped into the object literal.
Also added console.log/error breadcrumbs to uploadJobFilesAction so the
next mystery upload failure shows up in the dev server log immediately
instead of silently bouncing back as 'Bağlantı hatası'.