198 lines
8.9 KiB
Markdown
198 lines
8.9 KiB
Markdown
|
|
# Drive
|
||
|
|
|
||
|
|
An S3 file browser with WOPI-based document editing, built for [La Suite Numérique](https://lasuite.numerique.gouv.fr/). One Deno binary. No Django, no Celery, no Next.js — files, folders, and Collabora. That's the whole thing.
|
||
|
|
|
||
|
|
Built by [Sunbeam Studios](https://sunbeam.pt) as a drop-in replacement for the upstream [drive](https://github.com/suitenumerique/drive). The original is a Django/Celery/Next.js stack. This is a single binary that does the same job.
|
||
|
|
|
||
|
|
> **Status:** Running in production. WOPI editing, pre-signed uploads, folder sizes, game asset hooks, full Ory integration — all shipping. We're replacing upstream drive one feature at a time.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## What it does
|
||
|
|
|
||
|
|
| Feature | How it works |
|
||
|
|
|---------|-------------|
|
||
|
|
| **File browser** | Navigate folders, sort, search, multi-select. react-aria underneath for keyboard + screen reader support. |
|
||
|
|
| **Document editing** | Double-click a .docx/.odt/.xlsx → Collabora Online opens via WOPI. Full-screen, no chrome. |
|
||
|
|
| **Pre-signed uploads** | Browser uploads straight to S3. File bytes never touch the server. Multi-part for large files. |
|
||
|
|
| **Game asset support** | Type detection for FBX, glTF, textures (DDS, KTX), audio, video. Icons + badges now, previews later. |
|
||
|
|
| **Folder sizes** | Recursive PostgreSQL functions. Size updates propagate up the ancestor chain on every file change. |
|
||
|
|
| **OIDC auth** | Ory Kratos sessions + Ory Hydra OAuth2. Same identity stack as every other La Suite app. |
|
||
|
|
| **Permissions** | Ory Keto (Zanzibar-style). Hierarchical: bucket → folder → file, with group support. |
|
||
|
|
| **Theming** | La Suite integration service provides runtime CSS. Dark mode, custom fonts, waffle menu — one URL. |
|
||
|
|
| **S3 backfill** | Files dropped directly into SeaweedFS? Hit the backfill endpoint and they show up in the browser with correct metadata. |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
```
|
||
|
|
Browser ──→ Deno/Hono Server ──→ SeaweedFS (S3)
|
||
|
|
│ PostgreSQL (metadata)
|
||
|
|
│ Valkey (WOPI locks)
|
||
|
|
│ Ory Keto (permissions)
|
||
|
|
├──→ Collabora Online (WOPI callbacks)
|
||
|
|
└──→ Ory Kratos (session validation)
|
||
|
|
```
|
||
|
|
|
||
|
|
One Deno binary (~450KB JS + static UI). Hono routes requests, the UI is a Vite-built React SPA from `ui/dist`. `deno compile` packs it all into a single executable.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Quick start
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Prerequisites: Deno 2.x, Node 20+, PostgreSQL, SeaweedFS (or weed mini)
|
||
|
|
|
||
|
|
# Install UI deps + build
|
||
|
|
cd ui && npm install && npx vite build && cd ..
|
||
|
|
|
||
|
|
# Create database + run migrations
|
||
|
|
createdb driver_db
|
||
|
|
DATABASE_URL="postgres://localhost/driver_db" deno run -A server/migrate.ts
|
||
|
|
|
||
|
|
# Start
|
||
|
|
DATABASE_URL="postgres://localhost/driver_db" \
|
||
|
|
SEAWEEDFS_S3_URL="http://localhost:8333" \
|
||
|
|
deno run -A main.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
Open `http://localhost:3000`. That's it.
|
||
|
|
|
||
|
|
For the full stack with Collabora editing, see [docs/local-dev.md](docs/local-dev.md).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Project structure
|
||
|
|
|
||
|
|
```
|
||
|
|
drive/
|
||
|
|
├── main.ts Hono app entry — all routes
|
||
|
|
├── deno.json Tasks, imports
|
||
|
|
├── compose.yaml Docker Compose for WOPI integration testing
|
||
|
|
├── server/
|
||
|
|
│ ├── auth.ts Kratos session middleware
|
||
|
|
│ ├── csrf.ts CSRF protection (HMAC double-submit)
|
||
|
|
│ ├── telemetry.ts OpenTelemetry tracing + metrics middleware
|
||
|
|
│ ├── db.ts PostgreSQL client
|
||
|
|
│ ├── migrate.ts Schema migrations
|
||
|
|
│ ├── s3.ts S3 client (AWS SigV4, no SDK)
|
||
|
|
│ ├── s3-presign.ts Pre-signed URL generation
|
||
|
|
│ ├── files.ts File CRUD + user state handlers
|
||
|
|
│ ├── folders.ts Folder operations
|
||
|
|
│ ├── keto.ts Ory Keto HTTP client
|
||
|
|
│ ├── permissions.ts Permission middleware + tuple lifecycle
|
||
|
|
│ ├── backfill.ts S3 → DB backfill API
|
||
|
|
│ └── wopi/
|
||
|
|
│ ├── handler.ts WOPI endpoints (CheckFileInfo, GetFile, PutFile, locks)
|
||
|
|
│ ├── token.ts JWT access tokens for WOPI
|
||
|
|
│ ├── lock.ts Valkey-backed lock service
|
||
|
|
│ └── discovery.ts Collabora discovery XML cache
|
||
|
|
├── ui/
|
||
|
|
│ ├── src/
|
||
|
|
│ │ ├── main.tsx Vite entry point
|
||
|
|
│ │ ├── App.tsx CunninghamProvider + Router
|
||
|
|
│ │ ├── layouts/ AppLayout (header + sidebar + main)
|
||
|
|
│ │ ├── pages/ Explorer, Recent, Favorites, Trash, Editor
|
||
|
|
│ │ ├── components/ FileBrowser, FileUpload, CollaboraEditor, ProfileMenu, etc.
|
||
|
|
│ │ ├── api/ React Query hooks + fetch client
|
||
|
|
│ │ ├── stores/ Zustand (selection, upload queue)
|
||
|
|
│ │ ├── hooks/ Asset type detection, preview capabilities
|
||
|
|
│ │ └── cunningham/ Cunningham theme integration
|
||
|
|
│ └── e2e/ Playwright tests (driver, wopi, integration-service)
|
||
|
|
├── keto/
|
||
|
|
│ └── namespaces.ts OPL permission model
|
||
|
|
└── tests/
|
||
|
|
└── server/ Deno test files (10 files)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Stack
|
||
|
|
|
||
|
|
| Layer | Technology |
|
||
|
|
|-------|-----------|
|
||
|
|
| Server | [Deno](https://deno.land/) + [Hono](https://hono.dev/) |
|
||
|
|
| Frontend | React 19 + [Cunningham v4](https://github.com/suitenumerique/cunningham) + [react-aria](https://react-spectrum.adobe.com/react-aria/) |
|
||
|
|
| Storage | [SeaweedFS](https://github.com/seaweedfs/seaweedfs) (S3-compatible) |
|
||
|
|
| Database | PostgreSQL (file registry, folder sizes) |
|
||
|
|
| Cache | Valkey (WOPI locks with TTL) |
|
||
|
|
| Auth | [Ory Kratos](https://www.ory.sh/kratos/) (identity) + [Ory Hydra](https://www.ory.sh/hydra/) (OAuth2/OIDC) |
|
||
|
|
| Permissions | [Ory Keto](https://www.ory.sh/keto/) (Zanzibar-style ReBAC) |
|
||
|
|
| Document editing | [Collabora Online](https://www.collaboraoffice.com/) via WOPI |
|
||
|
|
| Theming | [La Suite integration service](https://github.com/suitenumerique/integration) |
|
||
|
|
| Build | `deno compile` → single binary |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Testing
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Server unit tests (Deno)
|
||
|
|
deno task test
|
||
|
|
|
||
|
|
# UI unit tests (Vitest)
|
||
|
|
cd ui && npx vitest run
|
||
|
|
|
||
|
|
# UI unit tests with coverage
|
||
|
|
cd ui && npx vitest run --coverage
|
||
|
|
|
||
|
|
# E2E tests (Playwright — needs running server + weed mini + PostgreSQL)
|
||
|
|
cd ui && npx playwright test e2e/driver.spec.ts
|
||
|
|
|
||
|
|
# Integration service tests (Playwright — hits production integration.sunbeam.pt)
|
||
|
|
cd ui && INTEGRATION_URL=https://integration.sunbeam.pt npx playwright test e2e/integration-service.spec.ts
|
||
|
|
|
||
|
|
# WOPI integration tests (Playwright — needs docker compose stack)
|
||
|
|
docker compose up -d
|
||
|
|
# start server pointed at compose services, then:
|
||
|
|
cd ui && DRIVER_URL=http://localhost:3200 npx playwright test e2e/wopi.spec.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
90%+ line coverage on both server and UI. See [docs/testing.md](docs/testing.md) for the full breakdown.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Environment variables
|
||
|
|
|
||
|
|
| Variable | Default | Description |
|
||
|
|
|----------|---------|-------------|
|
||
|
|
| `PORT` | `3000` | Server listen port |
|
||
|
|
| `PUBLIC_URL` | `http://localhost:3000` | Public-facing URL (used in WOPI callbacks + redirects) |
|
||
|
|
| `DATABASE_URL` | `postgres://driver:driver@localhost:5432/driver_db` | PostgreSQL connection string |
|
||
|
|
| `SEAWEEDFS_S3_URL` | `http://seaweedfs-filer.storage.svc.cluster.local:8333` | S3 endpoint |
|
||
|
|
| `SEAWEEDFS_ACCESS_KEY` | *(empty)* | S3 access key |
|
||
|
|
| `SEAWEEDFS_SECRET_KEY` | *(empty)* | S3 secret key |
|
||
|
|
| `S3_BUCKET` | `sunbeam-driver` | S3 bucket name |
|
||
|
|
| `S3_REGION` | `us-east-1` | S3 region for signing |
|
||
|
|
| `VALKEY_URL` | `redis://localhost:6379/2` | Valkey/Redis URL for WOPI locks (falls back to in-memory if unavailable) |
|
||
|
|
| `KRATOS_PUBLIC_URL` | `http://kratos-public.ory.svc.cluster.local:80` | Kratos public API |
|
||
|
|
| `KETO_READ_URL` | `http://keto-read.ory.svc.cluster.local:4466` | Keto read API |
|
||
|
|
| `KETO_WRITE_URL` | `http://keto-write.ory.svc.cluster.local:4467` | Keto write API |
|
||
|
|
| `COLLABORA_URL` | `http://collabora.lasuite.svc.cluster.local:9980` | Collabora Online |
|
||
|
|
| `WOPI_JWT_SECRET` | `dev-wopi-secret-change-in-production` | HMAC secret for WOPI access tokens |
|
||
|
|
| `CSRF_COOKIE_SECRET` | `dev-secret-change-in-production` | HMAC secret for CSRF tokens |
|
||
|
|
| `DRIVER_TEST_MODE` | *(unset)* | Set to `1` to bypass auth (E2E testing only) |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Docs
|
||
|
|
|
||
|
|
| Doc | What's in it |
|
||
|
|
|-----|-------------|
|
||
|
|
| [Architecture](docs/architecture.md) | How the pieces fit together and why there aren't many of them |
|
||
|
|
| [WOPI](docs/wopi.md) | Collabora integration — discovery, tokens, locks, the iframe dance |
|
||
|
|
| [Permissions](docs/permissions.md) | Keto OPL model — Zanzibar-style, hierarchical traversal |
|
||
|
|
| [S3 Layout](docs/s3-layout.md) | Human-readable keys, backfill, the metadata layer |
|
||
|
|
| [Testing](docs/testing.md) | Five test suites, coverage targets, Docker Compose for WOPI |
|
||
|
|
| [Local Dev](docs/local-dev.md) | Zero to running in 2 minutes |
|
||
|
|
| [Deployment](docs/deployment.md) | Kubernetes, Collabora config, deployment checklist |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## License
|
||
|
|
|
||
|
|
[MIT](LICENSE) — do whatever you want with it.
|
||
|
|
|
||
|
|
Questions? `hello@sunbeam.pt`
|