- Created a .cursorignore file to manage local environment files. - Updated .env.example to reflect changes in the public app URL. - Modified the gmail workspace configuration to include the drive-suite path. - Enhanced email view components to support attachment handling and fallback for plain text bodies. - Improved user experience by updating attachment display logic and integrating inline attachment support.
410 lines
19 KiB
Markdown
410 lines
19 KiB
Markdown
---
|
||
name: Drive Suite Build
|
||
overview: "Build UltiDrive as a Google Drive–like frontend in `drive-suite`, with all backend/infra in `ulti-backend` (ultid API, Nextcloud, OnlyOffice, Authentik, deploy). Mail stays frontend-only in `gmail-interface-clone`. Production routing: same domain at `/drive`."
|
||
todos:
|
||
- id: phase0-provisioning
|
||
content: Fix drive EnsurePrincipal + extend drive API (quota, shares CRUD, restore, favorite, create blank file)
|
||
status: completed
|
||
- id: phase0-onlyoffice
|
||
content: Deploy OnlyOffice Document Server, enable NC onlyoffice app, add /api/v1/office session+callback endpoints
|
||
status: completed
|
||
- id: phase1-scaffold
|
||
content: Scaffold drive-suite Next.js app (auth, API client, basePath /drive, app shell, URL routing)
|
||
status: completed
|
||
- id: phase2-mvp
|
||
content: Build Google Drive file browser MVP wired to real /api/v1/drive (upload, browse, share, trash, recent, starred)
|
||
status: completed
|
||
- id: phase3-editor
|
||
content: Integrate OnlyOffice editor embed for docx/xlsx/pptx via /drive/edit/{id}
|
||
status: completed
|
||
- id: phase4-chrome
|
||
content: "Google-like editor chrome: OnlyOffice theme first, then custom wrapper using drive-dump specs if needed"
|
||
status: completed
|
||
- id: phase5-suite
|
||
content: "Wire suite integration: nginx /drive route, launcher hrefs, mail attachment picker, Authentik callbacks"
|
||
status: completed
|
||
isProject: false
|
||
---
|
||
|
||
# UltiDrive (Drive Suite) — Build Plan
|
||
|
||
## Repository roles
|
||
|
||
| Repo | Responsibility |
|
||
|------|----------------|
|
||
| **[`ulti-backend`](file:///Users/red/workdev/ulti-backend)** | **All backend infrastructure and server-side code:** custom Go API (`ultid`), Docker Compose, edge nginx, Authentik, Postgres/KeyDB/RustFS, headless Nextcloud, mail stack (IMAP/SMTP/sync), OnlyOffice Document Server, Jitsi, observability, deploy scripts, env templates, Authentik blueprints. This is the single ops + API hub for the suite. |
|
||
| **[`gmail-interface-clone`](file:///Users/red/workdev/gmail-interface-clone)** | **Frontend only — Ultimail.** Next.js UI for mail (list, compose, settings, contacts). Talks to ulti-backend via `/api/v1` proxy; no server infrastructure lives here. |
|
||
| **[`drive-suite`](file:///Users/red/workdev/drive-suite)** | **Frontend only — UltiDrive.** Next.js UI for the Google Drive–like file browser and editor shell. Same pattern as mail: API calls go to ulti-backend; deploy wiring (nginx route, compose service) is defined in ulti-backend. |
|
||
|
||
Suite frontends are thin clients; every persistent service and integration runs from **ulti-backend**.
|
||
|
||
---
|
||
|
||
## Current state
|
||
|
||
| Layer | Status |
|
||
|-------|--------|
|
||
| [`drive-suite/`](file:///Users/red/workdev/drive-suite) | Empty placeholder — greenfield |
|
||
| [`ulti-backend`](file:///Users/red/workdev/ulti-backend) | Headless NC 30 deployed; `/api/v1/drive` proxy (list, upload, download, move, copy, rename, trash, recent, starred, shares) |
|
||
| [`gmail-interface-clone`](file:///Users/red/workdev/gmail-interface-clone) | Auth stack, shadcn UI, app shell patterns; UltiDrive tile exists but has no `href` |
|
||
| [`ultimail/apps/web/src/components/drive/`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive) | Mock UI components (file-browser, share-dialog, upload-zone) — reference only |
|
||
| [`drive-dump/`](file:///Users/red/workdev/drive-dump) | Google Docs chrome research (titlebar, toolbar CSS specs) — useful for editor skinning, not for file browser |
|
||
| OnlyOffice | Not deployed; mentioned only in [`project-plan/ultidrive.md`](file:///Users/red/workdev/ulti-backend/project-plan/ultidrive.md) |
|
||
|
||
**Critical backend bug to fix first:** drive handlers pass `claims.Sub` as NC user ID, but NC OIDC maps `preferred_username` (email). Contacts already use `EnsurePrincipal(email, sub, name)` — drive must match or provisioning fails.
|
||
|
||
---
|
||
|
||
## Target architecture
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph frontends [Frontend repos]
|
||
MailFE["gmail-interface-clone"]
|
||
DriveFE["drive-suite"]
|
||
end
|
||
|
||
subgraph ultiBackend [ulti-backend deploy and API]
|
||
Nginx["edge nginx"]
|
||
Ultid["ultid Go API"]
|
||
Auth["Authentik"]
|
||
NC["Nextcloud headless"]
|
||
OO["OnlyOffice"]
|
||
MailSvc["mail IMAP SMTP sync"]
|
||
S3["RustFS S3"]
|
||
end
|
||
|
||
MailFE -->|"/mail"| Nginx
|
||
DriveFE -->|"/drive"| Nginx
|
||
Nginx --> Ultid
|
||
Nginx --> Auth
|
||
Ultid --> DriveAPI["/api/v1/drive"]
|
||
Ultid --> OfficeAPI["/api/v1/office"]
|
||
Ultid --> MailAPI["/api/v1/mail"]
|
||
Ultid --> WS["/ws"]
|
||
DriveAPI --> NC
|
||
OfficeAPI --> NC
|
||
OfficeAPI --> OO
|
||
NC --> OO
|
||
NC --> S3
|
||
MailAPI --> MailSvc
|
||
```
|
||
|
||
**Principles:**
|
||
- Browser never talks to Nextcloud or OnlyOffice directly — all via **ultid** in ulti-backend (auth, RBAC, stable API).
|
||
- Nextcloud UI stays hidden (`/cloud/` admin-only); users only see UltiDrive.
|
||
- Same Authentik OIDC session; production path `/drive` on the suite domain (shared cookies with `/mail`).
|
||
|
||
---
|
||
|
||
## Phase 0 — Backend hardening ([`ulti-backend`](file:///Users/red/workdev/ulti-backend) only)
|
||
|
||
All work in this phase stays in ulti-backend (Go API + `deploy/`). No infrastructure or API logic belongs in drive-suite or gmail-interface-clone.
|
||
|
||
Fix and extend the existing drive layer before building UI.
|
||
|
||
### 0.1 User provisioning fix
|
||
In [`internal/api/drive/handlers.go`](file:///Users/red/workdev/ulti-backend/internal/api/drive/handlers.go) and `service.go`, replace raw `claims.Sub` with:
|
||
|
||
```go
|
||
userID, err := nc.EnsurePrincipal(ctx, claims.Email, claims.Sub, claims.Name)
|
||
```
|
||
|
||
Mirror the contacts pattern in [`internal/nextcloud/users.go`](file:///Users/red/workdev/ulti-backend/internal/nextcloud/users.go).
|
||
|
||
### 0.2 API gaps (priority order)
|
||
|
||
| Endpoint | Purpose |
|
||
|----------|---------|
|
||
| `GET /drive/quota` | Storage bar in UI |
|
||
| `GET /drive/shares?path=` | List existing shares |
|
||
| `PUT /drive/shares/{id}` | Update role / expiry / password |
|
||
| `DELETE /drive/shares/{id}` | Revoke share |
|
||
| `POST /drive/trash/restore` | Restore from trash |
|
||
| `POST /drive/favorite` | Star/unstar |
|
||
| `POST /drive/files/new` | Create empty doc/sheet/slide (NC template or OO blank) |
|
||
| `GET /drive/search` | Full-text (v1: name filter; v2: Meilisearch) |
|
||
|
||
Extend [`internal/nextcloud/drive.go`](file:///Users/red/workdev/ulti-backend/internal/nextcloud/drive.go) with OCS share list/update/delete and favorite WebDAV properties.
|
||
|
||
### 0.3 WebSocket events
|
||
Add `drive.file_changed`, `drive.share_updated` to ultid WS hub so drive UI can invalidate TanStack Query cache (same pattern as mail events in [`gmail-interface-clone/lib/api/ws.ts`](file:///Users/red/workdev/gmail-interface-clone/lib/api/ws.ts)).
|
||
|
||
### 0.4 OnlyOffice + Nextcloud deployment
|
||
|
||
New files under [`deploy/onlyoffice/`](file:///Users/red/workdev/ulti-backend/deploy):
|
||
|
||
- `docker-compose.onlyoffice.yml` — Document Server container
|
||
- Update [`deploy/nextcloud/init.sh`](file:///Users/red/workdev/ulti-backend/deploy/nextcloud/init.sh) — `$OCC app:enable onlyoffice`
|
||
- Update [`deploy/compose-up.sh`](file:///Users/red/workdev/ulti-backend/deploy/compose-up.sh) — conditional include when `ONLYOFFICE_ENABLED=true`
|
||
- Update [`deploy/nginx/default.conf.template`](file:///Users/red/workdev/ulti-backend/deploy/nginx/default.conf.template):
|
||
- `/drive/` → drive-suite container
|
||
- `/office/` → OnlyOffice (internal, JWT-protected; not exposed to users directly)
|
||
|
||
Env vars to add to `.env.example`:
|
||
|
||
```
|
||
ONLYOFFICE_ENABLED=true
|
||
ONLYOFFICE_URL=http://onlyoffice:80
|
||
ONLYOFFICE_JWT_SECRET=...
|
||
ONLYOFFICE_PUBLIC_URL=https://{DOMAIN}/office
|
||
```
|
||
|
||
### 0.5 Office API (new package `internal/api/office/`)
|
||
|
||
| Endpoint | Role |
|
||
|----------|------|
|
||
| `POST /office/session` | Given `{ path, mode: view\|edit }`, return OnlyOffice editor config + signed JWT |
|
||
| `GET /office/callback` | OnlyOffice save callback → NC WebDAV PUT |
|
||
| `POST /office/create` | Create blank docx/xlsx/pptx in user folder |
|
||
|
||
Flow: drive UI opens `/drive/edit/{fileId}` → backend builds OnlyOffice config pointing at NC file via internal URL → OnlyOffice loads document → saves via callback.
|
||
|
||
---
|
||
|
||
## Phase 1 — Scaffold [`drive-suite`](file:///Users/red/workdev/drive-suite) (Google Drive shell)
|
||
|
||
Frontend-only repo. Copy **UI/auth/client patterns** from [`gmail-interface-clone`](file:///Users/red/workdev/gmail-interface-clone) (the mail frontend), not from ultimail (different router). Match stack: **Next.js 16, React 19, Tailwind 4, shadcn new-york, Zustand 5, TanStack Query 5, pnpm**.
|
||
|
||
### 1.1 Repo bootstrap
|
||
```
|
||
drive-suite/
|
||
├── app/
|
||
│ ├── layout.tsx # QueryProvider + AuthProvider
|
||
│ ├── page.tsx # redirect → /drive
|
||
│ ├── api/auth/ # copy OIDC routes from mail
|
||
│ ├── auth/complete/
|
||
│ ├── login/
|
||
│ └── drive/
|
||
│ ├── layout.tsx # DriveAppShell
|
||
│ ├── [[...segments]]/page.tsx
|
||
│ ├── edit/[fileId]/page.tsx # OnlyOffice embed (Phase 3)
|
||
│ └── settings/[[...section]]/
|
||
├── components/drive/ # file browser, sidebar, header, share, upload
|
||
├── lib/
|
||
│ ├── auth/ # session, pkce, jwt-claims
|
||
│ ├── api/client.ts # Bearer + 401 refresh
|
||
│ ├── api/hooks/use-drive-queries.ts
|
||
│ ├── drive-url.ts # URL = source of truth
|
||
│ └── stores/ # drive-ui-store, drive-settings-store
|
||
├── next.config.mjs # basePath: '/drive' in prod; /api/v1 rewrite
|
||
└── Dockerfile # same multi-stage pattern as mail
|
||
```
|
||
|
||
**`basePath: '/drive'`** in production so assets and routes work behind nginx path routing.
|
||
|
||
### 1.2 Auth
|
||
Copy auth stack from mail; change:
|
||
- Default `returnTo` → `/drive`
|
||
- Persist key → `ultidrive-auth`
|
||
- Keep `ulti_*` cookie names (same origin)
|
||
|
||
### 1.3 App shell (Google Drive UX)
|
||
|
||
Three-zone layout mirroring [`mail-app-shell.tsx`](file:///Users/red/workdev/gmail-interface-clone/app/mail/mail-app-shell.tsx):
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────┐
|
||
│ Header: search, view toggle, account, suite launcher │
|
||
├──────────┬───────────────────────────────────────────────┤
|
||
│ Sidebar │ Main: breadcrumb + file grid/list │
|
||
│ My Drive │ │
|
||
│ Recent │ (optional detail panel for file preview) │
|
||
│ Starred │ │
|
||
│ Trash │ │
|
||
│ Storage │ │
|
||
└──────────┴───────────────────────────────────────────────┘
|
||
```
|
||
|
||
Port/adapt from ultimail mock components:
|
||
- [`file-browser.tsx`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive/file-browser.tsx) — grid/list, sort, multi-select
|
||
- [`breadcrumb-nav.tsx`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive/breadcrumb-nav.tsx)
|
||
- [`upload-zone.tsx`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive/upload-zone.tsx) — drag-drop + chunked upload via `X-Upload-*` headers
|
||
- [`share-dialog.tsx`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive/share-dialog.tsx)
|
||
- [`quota-bar.tsx`](file:///Users/red/workdev/ultimail/apps/web/src/components/drive/quota-bar.tsx)
|
||
|
||
Add `--drive-*` CSS tokens in `globals.css` parallel to mail's `--mail-*`.
|
||
|
||
### 1.4 URL routing
|
||
|
||
[`lib/drive-url.ts`](file:///Users/red/workdev/drive-suite/lib/drive-url.ts) + `use-drive-route.ts`:
|
||
|
||
| URL segment | View |
|
||
|-------------|------|
|
||
| `/drive` | My Drive root |
|
||
| `/drive/folders/documents/page/2` | Folder + pagination |
|
||
| `/drive/recent` | Recent files |
|
||
| `/drive/starred` | Favorites |
|
||
| `/drive/trash` | Trash |
|
||
| `/drive/search?q=report` | Search results |
|
||
| `/drive/file/{id}` | Preview panel |
|
||
| `/drive/edit/{id}` | Editor (Phase 3) |
|
||
|
||
Wire real API from day one — no mock data.
|
||
|
||
---
|
||
|
||
## Phase 2 — Core Drive features (MVP)
|
||
|
||
Ship a usable Google Drive clone before editors.
|
||
|
||
| Feature | Backend | Frontend |
|
||
|---------|---------|----------|
|
||
| Browse folders | `GET /drive/files/*` | Grid/list + breadcrumb |
|
||
| Upload (simple + chunked) | `POST /drive/files/*` | Progress, retry |
|
||
| Create folder | `POST /drive/folders/*` | New folder dialog |
|
||
| Rename / move / copy | existing endpoints | Context menu + drag-drop move |
|
||
| Delete / trash / restore | DELETE + trash endpoints | Trash view |
|
||
| Starred / recent | existing endpoints | Sidebar views |
|
||
| Share (link + users) | shares CRUD | Share dialog (owner/editor/viewer) |
|
||
| Download | `GET /drive/download/*` | Context menu |
|
||
| Preview | download + mime routing | Images, PDF inline; others download |
|
||
| Storage quota | `GET /drive/quota` | Sidebar bar |
|
||
| Multi-select bulk ops | batch endpoints or sequential | Toolbar actions |
|
||
|
||
**New file affordance (Google-style):**
|
||
- Dropdown: Blank document / spreadsheet / presentation / folder / upload
|
||
- Creates file via `POST /office/create` or NC template, then opens editor
|
||
|
||
---
|
||
|
||
## Phase 3 — OnlyOffice editor integration
|
||
|
||
### 3.1 Embed pattern
|
||
[`app/drive/edit/[fileId]/page.tsx`](file:///Users/red/workdev/drive-suite/app/drive/edit/[fileId]/page.tsx):
|
||
|
||
1. Fetch editor config from `POST /api/v1/office/session`
|
||
2. Load `@onlyoffice/document-editor-react` (or iframe with DocsAPI)
|
||
3. Full-viewport editor; minimal chrome initially
|
||
|
||
### 3.2 File type routing
|
||
|
||
| MIME / extension | Editor |
|
||
|------------------|--------|
|
||
| `.docx`, `.odt` | Document |
|
||
| `.xlsx`, `.ods` | Spreadsheet |
|
||
| `.pptx`, `.odp` | Presentation |
|
||
| Other | Preview or download only |
|
||
|
||
### 3.3 Save / co-editing
|
||
OnlyOffice handles real-time co-editing natively when connected to NC via the OnlyOffice app. Backend callback ensures saves land in NC WebDAV. No custom CRDT needed for v1.
|
||
|
||
---
|
||
|
||
## Phase 4 — Google-like editor chrome (theme → wrapper → fork)
|
||
|
||
Decision gate after Phase 3 ships with stock OnlyOffice UI.
|
||
|
||
### Step A — OnlyOffice theme (try first, ~1–2 weeks)
|
||
- Custom JSON skin: toolbar colors, fonts (Roboto/Google Sans), icon set
|
||
- CSS overrides via OnlyOffice `customization` config
|
||
- Hide OnlyOffice branding; match light/dark to suite theme
|
||
|
||
### Step B — Custom chrome wrapper (if theme insufficient)
|
||
Leverage [`drive-dump`](file:///Users/red/workdev/drive-dump) research:
|
||
- [`docs/CHROME-SPEC.md`](file:///Users/red/workdev/drive-dump/docs/CHROME-SPEC.md) — titlebar, menubar, toolbar DOM/CSS specs
|
||
- [`web/components/chrome/`](file:///Users/red/workdev/drive-dump/web/components/chrome) — React chrome components
|
||
- Build suite-owned titlebar (doc title, share, star, account) **above** OnlyOffice iframe
|
||
- Pass `customization.layout` to hide OnlyOffice's own header/toolbar
|
||
- Separate chrome per app type: Docs, Sheets, Slides (toolbar sets differ)
|
||
|
||
### Step C — Fork (last resort)
|
||
Only if theme + wrapper cannot reach Google parity on toolbars/menus. Fork OnlyOffice Document Server frontend (AGPL) — high maintenance cost. **Defer until A+B evaluated.**
|
||
|
||
Per [`drive-dump/docs/EDITOR-CORE-DECISION.md`](file:///Users/red/workdev/drive-dump/docs/EDITOR-CORE-DECISION.md): do **not** rebuild the editing engine — OnlyOffice remains the core; only chrome is customized.
|
||
|
||
---
|
||
|
||
## Phase 5 — Suite integration
|
||
|
||
### 5.1 Cross-app navigation
|
||
- [`header-account-actions.tsx`](file:///Users/red/workdev/gmail-interface-clone/components/gmail/header-account-actions.tsx): add `href: "/drive"` to UltiDrive tile
|
||
- Reciprocal launcher in drive-suite header back to `/mail`, Agenda, etc.
|
||
- Shared suite favicon/branding via `pnpm run brand:authentik` pattern
|
||
|
||
### 5.2 Mail ↔ Drive
|
||
- **Mail UI** (gmail-interface-clone): compose attachment picker "Insert from UltiDrive"; "Save attachment to Drive" on received mail
|
||
- **API** (ulti-backend): mail attachment upload → `POST /drive/files/...` in ultid
|
||
|
||
### 5.3 Deploy wiring (ulti-backend)
|
||
|
||
All routing and compose changes live in **ulti-backend** [`deploy/`](file:///Users/red/workdev/ulti-backend/deploy). The drive-suite repo supplies the Docker image / build context only.
|
||
|
||
Update [`deploy/nginx/default.conf.template`](file:///Users/red/workdev/ulti-backend/deploy/nginx/default.conf.template):
|
||
|
||
```nginx
|
||
location /drive/ {
|
||
proxy_pass http://drive-suite:3000/;
|
||
# WebSocket + standard headers
|
||
}
|
||
```
|
||
|
||
Add a `drive-suite` service to ulti-backend compose (port 3000 internal; dev: build from `../drive-suite` or host :3001). Mail frontend is deployed the same way today — gmail-interface-clone is not part of ulti-backend source tree, but its container is orchestrated from ulti-backend deploy.
|
||
|
||
Update Authentik blueprints / OIDC redirect URIs in ulti-backend for `/drive/api/auth/callback`.
|
||
|
||
---
|
||
|
||
## Phase 6 — Advanced (post-MVP)
|
||
|
||
| Feature | Approach |
|
||
|---------|----------|
|
||
| Full-text search | Meilisearch indexer on NC files + `/drive/search` |
|
||
| Version history | NC versions API wrapper |
|
||
| Shared drives / team folders | NC `groupfolders` app + API |
|
||
| Public link sharing page | `/drive/shared/{token}` read-only view |
|
||
| Desktop mount | rclone sidecar in Tauri (per ultidrive.md) |
|
||
| Offline uploads | IDB queue pattern from mail offline-queue |
|
||
| Comments on files | OnlyOffice comments (built-in) or NC comments API |
|
||
|
||
---
|
||
|
||
## Suggested delivery order
|
||
|
||
```mermaid
|
||
gantt
|
||
title UltiDrive delivery phases
|
||
dateFormat YYYY-MM-DD
|
||
section ulti backend
|
||
API and provisioning :a1, 2026-06-02, 14d
|
||
OnlyOffice deploy :a2, after a1, 7d
|
||
section drive suite
|
||
Scaffold :a3, 2026-06-02, 7d
|
||
Drive MVP :a4, after a3, 21d
|
||
Editor embed :a5, after a2, 14d
|
||
Google chrome :a6, after a5, 21d
|
||
section integration
|
||
Suite wiring :a7, after a4, 7d
|
||
```
|
||
|
||
Parallel tracks: **a1→a2** (ulti-backend) runs alongside **a3→a4** (drive-suite); **a5** starts after OnlyOffice (**a2**); **a7** (suite wiring) after Drive MVP (**a4**).
|
||
|
||
**First shippable milestone (4–5 weeks):** Phase 0 + 1 + 2 + 5 nginx/auth — full Drive file browser on real backend.
|
||
|
||
**Second milestone (+3 weeks):** Phase 3 — open and edit docs/sheets/slides in OnlyOffice.
|
||
|
||
**Third milestone (+3 weeks):** Phase 4 — Google-like editor chrome.
|
||
|
||
---
|
||
|
||
## Key files to create/modify
|
||
|
||
| Repo | Role | Files |
|
||
|------|------|-------|
|
||
| **ulti-backend** | Backend + all infra | `internal/api/drive/*`, `internal/api/office/*`, `deploy/onlyoffice/*`, `deploy/nginx/*`, compose, Authentik blueprints |
|
||
| **drive-suite** | Drive frontend only | Entire Next.js app; copy auth + API client patterns from gmail-interface-clone |
|
||
| **gmail-interface-clone** | Mail frontend only | `header-account-actions.tsx`, compose drive picker (UI links to `/drive`) |
|
||
| **Reference only** | — | `ultimail/.../drive/*`, `drive-dump/docs/CHROME-SPEC.md` |
|
||
|
||
---
|
||
|
||
## Risks and mitigations
|
||
|
||
| Risk | Mitigation |
|
||
|------|------------|
|
||
| NC user ID mismatch | Fix EnsurePrincipal in Phase 0 before any UI work |
|
||
| OnlyOffice JWT / NC connector misconfig | Test with single doc open before building chrome |
|
||
| Chunk upload edge cases | Reuse existing `X-Upload-*` protocol; add integration tests |
|
||
| Google UI parity scope creep | Theme first; wrapper second; fork only as last resort |
|
||
| Path-based routing (`/drive`) asset breaks | `basePath` in next.config from day one |
|