File Storage (Supabase/S3)
Relevant source files
The following files were used as context for generating this wiki page:
The GUC Resume Enhancer application utilizes Supabase Storage for managing user-uploaded resume files. Supabase provides a scalable and secure object storage solution, which uses AWS S3 as its underlying infrastructure. This system is responsible for the entire lifecycle of resume files, including uploading, storing, retrieving for display, and deletion, all while ensuring data privacy and security through a combination of application logic and database-level policies.
File metadata, such as the original filename and the path to the stored object, is maintained in a PostgreSQL database managed via Prisma, linking the stored files to user accounts and enhancement records. The integration relies on private storage buckets and Row Level Security (RLS) policies to restrict file access, ensuring that users can only interact with their own documents.
Sources: README.md, SUPABASE_SETUP.md, schema.prisma
Architecture Overview
The file storage architecture integrates the Next.js application with Supabase's storage and database services. On the server side, a dedicated supabaseAdmin client uses a service_role key for privileged operations like file uploads, while a client-side instance uses a public anon key for less sensitive tasks. All resume files are stored in a private bucket named resumes.
Sources: lib/supabase-storage.ts:3-15
The following diagram illustrates the high-level architecture of the file storage system.
Setup and Configuration
To enable file storage, the Supabase project must be configured correctly. This involves setting up environment variables, creating a storage bucket, and applying security policies.
Sources: SUPABASE_SETUP.md
Environment Variables
The application requires the following environment variables to connect to Supabase.
| Variable | Description |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | The public URL of the Supabase project. |
NEXT_PUBLIC_SUPABASE_ANON_KEY | The public, anonymous key for client-side access. |
SUPABASE_SERVICE_ROLE_KEY | The secret service role key for admin-level API access. |
Sources: SUPABASE_SETUP.md:70-72, lib/supabase-storage.ts:3-5
Storage Bucket
A single storage bucket named resumes is used. It must be configured as private to prevent public access and rely on RLS policies for security.
Sources: SUPABASE_SETUP.md:27-33, lib/supabase-storage.ts:18
Row Level Security (RLS) Policies
RLS policies are critical for securing the resumes bucket. They ensure that authenticated users can only access files within their own designated folder, which is named after their userId.
| Policy Action | Description |
|---|---|
INSERT | Allows authenticated users to upload files into a folder matching their own auth.uid(). |
SELECT | Allows authenticated users to read files from a folder matching their own auth.uid(). |
DELETE | Allows authenticated users to delete files from a folder matching their own auth.uid(). |
Sources: SUPABASE_SETUP.md:40-66
The following SQL statement is an example of the policy used to control uploads:
CREATE POLICY "Users can upload their own resumes"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (
bucket_id = 'resumes'
AND auth.uid()::text = (storage.foldername(name))[1]
);
Sources: SUPABASE_SETUP.md:41-47
File Operations
The core functionality is managed by helper functions in lib/supabase-storage.ts and exposed through various API endpoints.
Core Storage Functions
The lib/supabase-storage.ts module provides a set of functions to interact with the Supabase storage bucket.
| Function | Description |
|---|---|
uploadFileToSupabase | Uploads a file buffer to the resumes bucket under a user-specific path. |
downloadFileFromSupabase | Downloads a file as a Blob from a given path. |
getSignedUrl | Creates a temporary, signed URL for secure access to a private file. |
deleteFileFromSupabase | Deletes a file from the storage bucket. |
getPublicUrl | Retrieves the public URL for a file (only useful if the bucket were public). |
Sources: lib/supabase-storage.ts
File Upload
The file upload process is initiated from the frontend and handled by the POST /api/resumes/upload API route. The flow involves authentication, validation, text extraction, storage, and database record creation.
The diagram below shows the sequence of events during a file upload.
Files are stored using a path structure that isolates files by user ID: {userId}/{timestamp}-{sanitizedFileName}. This structure is essential for the RLS policies to function correctly.
Sources: SUPABASE_SETUP.md:120-125, lib/supabase-storage.ts:29
File Access and Download
Files are accessed in two primary ways: proxied for in-app PDF viewing and direct download.
-
PDF Proxy for Viewing: To avoid CORS issues and securely display private PDFs, the
enhance-interface.tsxcomponent requests the file from/api/proxy-pdf/[id]. This server-side route fetches the file from Supabase usingdownloadFileFromSupabaseand streams it back to the client with the correctContent-Typeheader.
Sources: api/proxy-pdf/[id]/route.ts, enhance/[id]/_components/enhance-interface.tsx:148-151 -
Enhanced PDF Download: The
GET /api/download-resume/[id]endpoint does not download the original file from storage. Instead, it generates a new PDF document on-the-fly usingjsPDFcontaining the AI-enhanced text content stored in the database.
Sources: api/download-resume/[id]/route.ts, enhance/[id]/_components/enhance-interface.tsx:265-291 -
Original File Download (Non-PDF): For file types like DOCX, the
enhance-interface.tsxcomponent fetches a signed URL from an API endpoint (/api/resume-file/[id]) and provides a direct download link to the user.
Sources: enhance/[id]/_components/enhance-interface.tsx:154-160, 400-410
Database Integration
The Resume table in the database, defined in schema.prisma, stores metadata for each uploaded file. The cloudStoragePath field is the key that links a database record to its corresponding object in Supabase Storage.
// Source: schema.prisma:93-106
model Resume {
id String @id @default(cuid())
userId String
originalName String
cloudStoragePath String
fileType String // 'pdf' or 'docx'
originalContent String @db.Text
status String @default("uploaded") // uploaded, processing, enhanced, error
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
enhancements ResumeEnhancement[]
}
The relationship between users and their resumes is defined in the schema, with a one-to-many relationship from User to Resume.
This diagram shows the entity relationship for file storage.
Sources: schema.prisma:56-70, 93-106
Security
Security is a cornerstone of the file storage system, implemented through several layers.
- Private Bucket: The
resumesbucket is configured to be private, preventing any anonymous or public access to its contents.
Sources: SUPABASE_SETUP.md:31 - Row Level Security: As detailed above, RLS policies on the
storage.objectstable restrictINSERT,SELECT, andDELETEoperations, ensuring users can only manage files within their own user-specific folder.
Sources: SUPABASE_SETUP.md:35-66 - Server-Side Admin Client: All file manipulations (upload, download, delete) are performed on the server using an admin client (
supabaseAdmin) initialized with theSUPABASE_SERVICE_ROLE_KEY. This key bypasses RLS, but access is controlled by the application's API logic, which first authenticates the user session.
Sources: lib/supabase-storage.ts:6-11, api/resumes/upload/route.ts:46-49 - File Name Sanitization: Before uploading, filenames are sanitized by the
sanitizeFileNamefunction to remove path traversal characters (/,\), null bytes, and other potentially malicious characters, mitigating security risks.
Sources: api/resumes/upload/route.ts:13-23, 109