Skip to main content
← All notes
Building

The attachment record stores an R2 key, not a URL, so the download link is always fresh

software

The Attachment model has a column called url, but it does not hold a URL. It holds a key — a string like uploads/1714000000-mastered-track.wav. The key is generated at upload time by the upload route at /api/portal/upload. It prepends Date.now to the original filename after sanitising it with a regex that replaces every character outside the set of alphanumerics, dots, dashes, and underscores with an underscore. The timestamp prevents collisions — two files with the same name uploaded a second apart get different keys. The regex prevents path traversal, encoding issues, and characters that would break Content-Disposition headers on download. Spaces become underscores. Forward slashes become underscores. Unicode characters become underscores. The resulting key is safe for R2, for Postgres, and for every HTTP header it will ever appear in. The upload itself is a three-step flow. Step one — the admin requests a presigned upload URL from the server. The server generates the key, calls getUploadUrl with a one-hour expiry, and returns both the signed URL and the key. Step two — the browser PUTs the file directly to R2 using the signed URL. The file never passes through the Next.js server. Step three — the browser calls /api/portal/upload/complete with the key, filename, size, MIME type, project ID, and optional session ID. The server creates the Attachment record in Prisma with the key in the url column and uploadedBy set to james. If step two fails — network error, timeout, the file exceeds the five-hundred-megabyte limit — step three never fires and the database stays clean. No orphaned records. No references to files that do not exist. When a client clicks download, the file download route reads the key from the Attachment record and generates a fresh signed URL with a twenty-four-hour expiry. The URL expires but the key never does. The same key survives R2 endpoint changes, bucket renames, and region migrations. Only the URL generation function needs to know the current R2 configuration. One key, three steps, zero permanent URLs.

Comments coming soon

Sign in with TikTok to leave a comment. Coming soon.