Data Red Room
A virtual data room for due-diligence documents — nested folders, PDF files, and sharing by link or by person.

A data room is where a company puts the documents an outside party needs to read before a deal closes — contracts, financials, corporate records — organised into folders and shared under control. Think Google Drive with the sharing rules taken seriously: who sees what, by link or by name, and the ability to take access back.
I built it solo over about a week to see how much of a real product I could get to production in that window: Google sign-in, nested folders, PDF upload and preview, sharing both ways, and a deployment that actually runs rather than a README promising it would.
Sharing is the whole product, and it's where the modelling gets interesting. A folder can be shared by public link or with a specific person; a person can be invited before they've ever signed in; access has to be revocable without erasing the record that it existed; and permission on a folder has to reach everything nested inside it. Getting that wrong in a document system doesn't produce a bug report — it produces a leak.
The time budget was the second constraint. A week is enough to build the feature set or enough to build it carefully, and I wanted the second — which meant deciding early which parts got real modelling and which parts were allowed to stay simple.
An Nx monorepo holding a React SPA on Vite and a NestJS API, with a ts-rest and zod contract shared between them so the types are the same object on both sides rather than two definitions that agree by convention. Prisma over PostgreSQL on Supabase, files in a private Google Cloud Storage bucket reached through signed URLs. The API runs on Cloud Run, the frontend on Vercel, and TanStack Query and Router with shadcn/ui carry the client.
Materialized paths instead of recursive queries
Each folder stores its full ancestry as a path string. Breadcrumbs, subtree totals and inherited permissions all become one indexed prefix query instead of a recursive walk, and permission on a parent reaches every descendant without a separate propagation step. The cost is that moving a folder rewrites the paths beneath it — which is why moving folders is deliberately not in the product.
Every data room has a real root folder
The obvious model makes top-level items belong to no folder, which means a nullable foreign key and a uniqueness constraint the database can't enforce. Creating an explicit root instead means every file and folder has a parent, `UNIQUE (folderId, name)` works, and there is no branch anywhere in the code for the top level being special.
Invite by email, authorize by user
You can share with someone who has never signed in, so a share carries the invited email address. But the email is only ever the invitation — when that person signs in with Google, the provider-verified address resolves to a user id, and every access check uses the id alone. An email in a database row is a claim; an id from an identity provider is a fact.
One authorization point, and 404 over 403
No handler compares owner ids. Every check goes through a single service, so there is exactly one place where the rules live and one place to audit. When access is denied, the API returns 404 rather than 403 — a 403 confirms the resource exists, which in a document system leaks the thing you were protecting.
Upload straight to storage, not through the API
The client asks the API for a signed PUT URL and sends the file directly to Cloud Storage, then confirms. Nothing large passes through the API, which keeps it clear of platform body-size limits and memory pressure. The tradeoff is that a file can exist in the bucket and never be confirmed, so pending records are swept.
Revoke by marking, not by deleting
Revoking a share sets a timestamp rather than removing the row. Access stops immediately, and the record that access once existed — to whom, and until when — survives. In a system whose purpose is controlled disclosure, deleting the history of who had access defeats half the point.
Frontend on Vercel, API on Cloud Run, database on Supabase, files in a private GCS bucket — all live, with the session cookie first-party and no CORS layer to maintain.
Moving folders is out of scope, and that's a consequence of the storage model rather than a decision made on its own. Materialized paths make reads cheap and moves expensive, so a product that needed drag-and-drop reorganisation would want the opposite tradeoff — closure tables, or paths with a background rewrite.
A React SPA was the right call for an app entirely behind a login, but it closes a door: there's no server rendering, so anything that later needed to be public and indexable — a shared document landing page, say — would be a different application shape, not a route I could add.
Let's talk
Open to full stack roles, remote or on-site in Varna. The fastest way to reach me is email or Telegram.
