A collaborative cloud platform built for my class at high school, centralizing course documents, shared resources, and academic tools in one place.
Built and maintained by Samy M. — live at bfi-cloud.samymihoubi.fr
BFI Cloud lets the whole class upload, browse, and access files organized in folders, with an integrated agenda for tracking assignments, and a built-in PDF reader. Authentication is handled via Clerk, and file write operations are gated behind organization-level permissions — only authorized users can upload or delete content.
| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router) |
| Language | TypeScript |
| Styling | Tailwind CSS |
| Auth & Permissions | Clerk (org-level permissions) |
| File Storage | Backblaze B2 (older version with Vercel Blob, but too limited) |
| Database | PostgreSQL via Prisma |
| Deployment | Vercel (custom domain) |
File management
- Upload files via click or drag & drop
- Browse files organized in virtual folders (built on top of B2's flat key structure)
- Download via generated links
- Delete files (with confirmation modal)
- Create new folders
- Integrated PDF viewer for in-browser reading
Access control
- Public browsing: anyone can view and download files without signing in
- Write operations (upload, delete, create folder) require authentication
- Fine-grained control via Clerk organization permissions (
org:files:write), not just roles — users without the permission see disabled UI elements
Agenda
- Displays upcoming class assignments stored in PostgreSQL
- Fetched server-side via Prisma with raw SQL queries
- Groups assignments by date with relative labels ("Tomorrow", "In 3 days", etc.)
File storage (Backblaze B2)
Files are stored in a flat B2 bucket. Folder structure is simulated client-side by parsing file name prefixes (e.g. maths/chapter3/notes.pdf). The API route handles:
- B2 authentication (Basic auth → token)
- Upload URL retrieval
- SHA-1 checksum computation before upload
- File deletion by
fileId+fileName
POST /api/file?filename=... → upload to B2
DELETE /api/file → delete from B2 by fileId
Auth flow
Clerk middleware marks most routes as public. Write operations go through authenticatedFetch, which attaches a short-lived Bearer token to each request. Server-side, clerkClient.authenticateRequest() validates the token before any B2 operation is performed.
UI components use Clerk's <Protect> with the org:files:write permission to conditionally render upload/delete/folder controls.
Database
Prisma with a PostgreSQL adapter (@prisma/adapter-pg) manages the agenda. The assignment table stores subject (enum), date, and optional description. Subjects include: Maths, Physics, Literature, History & Geography, Spanish, French, SVT, and others.
# 1. Copy environment variables
cp .env.example .env
# Fill in: POSTGRES_PRISMA_URL, B2_KEY_ID, B2_APPLICATION_KEY,
# B2_BUCKET_ID, B2_BUCKET_NAME, CLERK_SECRET_KEY, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
# 2. Install dependencies
npm install
# 3. Push DB schema and generate Prisma client
npx prisma db push
npx prisma generate
# 4. Start dev server
npm run devApp runs at http://localhost:3000
src/
├── app/
│ ├── api/file/route.ts # B2 upload & delete endpoints
│ ├── files/page.tsx # File browser (server component)
│ ├── files/read/page.tsx # PDF viewer page
│ └── agenda/page.tsx # Assignment agenda
├── components/
│ ├── upload-button.tsx # Drag & drop uploader
│ ├── delete-button.tsx # Delete with confirm modal
│ ├── newFolder-button.tsx # Folder creation
│ ├── download-button.tsx # Link generator
│ ├── DocViewer.tsx # PDF viewer (react-pdf-viewer)
│ ├── Navbar.tsx # Responsive nav with Clerk auth
│ └── Agenda/agenda.tsx # Assignment table component
└── lib/
├── prisma.ts # Prisma client singleton
└── utils.ts # Date formatting helpers
Why Backblaze B2 over Vercel Blob or S3? Cost — B2 is significantly cheaper for storage and egress at small scale, and the API is S3-compatible enough to migrate later if needed.
Why virtual folders? B2 uses a flat key-value structure. Rather than maintaining a separate folder metadata layer in the database, folder hierarchy is derived from file name prefixes at render time. This keeps the system simple and stateless.
Why Clerk permissions over simple role checks?
Clerk's organization permission system (org:files:write) allows fine-grained access control without hardcoding role names in the UI. Any user in the organization can be granted or revoked write access independently of their role.
- bfi-simulator.samymihoubi.fr — Bac grade simulator, also built for the class