docs: update roadmap for analytics cli and sync

This commit is contained in:
saltbo
2026-07-08 23:24:31 -04:00
parent 10748cd018
commit ccdab7814d
8 changed files with 808 additions and 504 deletions
+54 -25
View File
@@ -1,7 +1,7 @@
# ZPan 2.0 Roadmap
> **The open-source, S3-native file hosting platform.**
> Free to self-host. One-click deploy to Cloudflare. Image hosting, file sharing, and backup — all in one.
> Free to self-host. One-click deploy to Cloudflare. Image hosting, file sharing, automation, and sync foundations — all in one.
## Product Positioning
@@ -11,14 +11,15 @@ Three scenarios, one platform:
- **Image Hosting** — Upload via PicGo / uPic / ShareX / API, get a permanent URL on your own domain
- **File Sharing** — Upload, generate a link, distribute
- **File Backup & Sync** — CLI agent syncs local directories to S3/R2
- **Automation & Sync** — Agent-friendly CLI for scripted file management, plus a server-side sync protocol for future desktop clients
## Target Users
- Developers and bloggers who need a reliable image bed
- Indie devs / small teams distributing files and releases
- Screenshot workflow users (ShareX, Flameshot, PicGo)
- NAS / desktop users who want off-site backup to S3
- Agents, CI workflows, and power users that need scriptable file upload and sharing
- Desktop users who want future sync clients backed by S3-compatible storage
- Self-hosters who want a web UI for their S3 buckets
## Release Plan
@@ -33,11 +34,13 @@ Each version ships 12 major features. Ship small, ship often.
| [v2.3](docs/roadmap/v2.3.md) | **Sharing** | Share links, direct links, password / expiration / limits |
| [v2.4](docs/roadmap/v2.4.md) | **Image Hosting** | Upload API, PicGo / uPic / ShareX integration, custom domain |
| [v2.5](docs/roadmap/v2.5.md) | **Multi-Platform Deployment** | 7 first-class targets via Turso (CF, Docker, AWS Lambda, Vercel, Netlify, Azure, GCP); avatar upload |
| [v2.6](docs/roadmap/v2.6.md) | **Pro Launch** | Cloud binding, entitlement system, quota store, white-label, audit log, site announcements, retroactive Pro gates |
| [v2.6](docs/roadmap/v2.6.md) | **Pro / Business Launch** | Cloud binding, entitlement system, white-label, audit log, quota store machinery, site announcements, retroactive gates |
| [v2.7](docs/roadmap/v2.7.md) | **WebDAV & File Processing** | WebDAV protocol access, small-file zip compression/extraction, and Community remote-download orchestration through Aria2, qBittorrent, and future adapters |
| [v2.8](docs/roadmap/v2.8.md) | **Pro Analytics & Identity** | Analytics dashboard, SSO enterprise (multi-IdP OIDC + SAML), LDAP / SCIM |
| [v2.9](docs/roadmap/v2.9.md) | **Backup** | zpan-cli (Rust) one-way backup for NAS / desktop |
| [v2.10](docs/roadmap/v2.10.md) | **Sync & Desktop** | Bidirectional sync + Tauri desktop tray app |
| [v2.8](docs/roadmap/v2.8.md) | **Admin Analytics & Dashboard** | Admin overview, usage/cost/reliability metrics, share analytics, and report exports |
| [v2.9](docs/roadmap/v2.9.md) | **Agent CLI** | Scriptable CLI for agents and CI: upload/manage files, shares, spaces, quota, and tasks |
| [v2.10](docs/roadmap/v2.10.md) | **Desktop Sync Foundation** | Sync device model, change log, sync API contract, conflict model, and protocol tests for external clients |
| Future | **Native Client Projects** | macOS File Provider, Windows/Linux sync clients, Flutter/mobile clients, and other OS-specific apps in separate repositories |
| Future | **Enterprise Identity & Governance** | SAML, LDAP / SCIM, advanced RBAC/custom roles, retention, and admin support mode if demand proves real |
Managed cloud services (large archive processing, content moderation, server-side media processing, managed custom domains) live in a separate closed-source repo and version independently. They are consumed by ZPan via HTTP, and do not affect self-hosted deployments that don't opt in. Remote download is not a Cloud execution service; ZPan integrates with user-owned download engines instead.
@@ -55,9 +58,11 @@ v2.0v2.4 ship on two runtimes. v2.5 expands to seven.
Same codebase, same features, seven runtimes. Turso provides the database for every non-CF target (9 GB free tier, no credit card). Object storage is decoupled — users bring any S3-compatible bucket (R2, S3, B2, Tigris), which is what makes Azure and GCP viable targets without adapting to their non-S3 native blob stores.
## Community vs Pro
## Community vs Pro vs Business
ZPan is split into two editions. Both run the same open-source codebase — Pro features are unlocked through ZPan Cloud account binding, Managed is Pro operated by us.
ZPan uses one open-source codebase with three entitlement levels. Community is
free. Pro and Business are unlocked through ZPan Cloud account binding and local
entitlement certificates. Managed is the hosted operation of the same product.
### ZPan Community (free, open source)
@@ -70,40 +75,64 @@ Everything an individual, family, or small team needs to self-host:
- WebDAV access for external file managers and sync tools
- One personal workspace plus one extra team workspace, shared folders, and basic roles
- Small-file zip compression and extraction within local runtime limits
- Remote-download orchestration through user-configured Aria2, qBittorrent, and future compatible adapters
- Backup CLI + desktop app + bidirectional sync
- Remote-download orchestration through one user-configured downloader, with Aria2, qBittorrent, and future compatible adapters
- Agent-friendly CLI once v2.9 ships
- Desktop sync protocol foundation once v2.10 ships; actual native clients live in separate projects
- All 7 deployment targets
No basic personal file workflows are gated behind paywalls. Operator controls and hosted compute-heavy workflows are Pro because they carry operational cost or platform-abuse risk.
No basic personal file workflows are gated behind paywalls. Operator controls,
commercial workflows, and hosted compute-heavy workflows are paid-tier features
because they carry operational cost or platform-abuse risk.
### ZPan Pro (paid license)
Operator-grade features for running ZPan as a service (internally at a company, publicly as a product, or commercially for customers). The rule of thumb: **features that help you *use* ZPan are free; features that help you *operate* ZPan for others are Pro.**
Operator-grade features for running ZPan as a service (internally at a company, publicly as a product, or commercially for customers). The rule of thumb: **features that help you *use* ZPan are free; features that help you *operate* ZPan for others start in Pro.**
- Open registration (the public signup mode) + anti-abuse tooling (captcha, rate limit, email verification)
- Audit logs + site announcements + webhook notifications + analytics dashboard
- White-label (logo / favicon / wordmark / custom branding)
- Large archive processing and future server-side media/document processing
- SSO enterprise (multi-IdP OIDC + SAML), LDAP / SCIM provisioning
- Advanced RBAC / custom roles, retention policies
- Admin impersonation, per-user quotas, multi-tenant isolation
- Audit logs
- Higher included limits for team workspaces, storage backends, social / OIDC providers, and downloaders
Pro is one product with two delivery modes:
### ZPan Business (paid license)
- **Self-hosted Pro** — buy a license, run ZPan yourself. The instance binds to ZPan Cloud for Pro entitlement and verifies the issued certificate locally.
- **Managed Pro** — same features, we operate the ZPan instance and storage. You stop caring about ops.
Commercial and enterprise operations for teams that sell or centrally operate
ZPan.
Pro is purchased or redeemed on ZPan Cloud, then mirrored to a bound ZPan instance by signed entitlement certificates. See [v2.6](docs/roadmap/v2.6.md) for the launch plan.
- Quota store, subscription-plan catalog, gift-card / credit flows, and credit-backed traffic billing
- Site announcements
- Analytics dashboard and reporting exports
- Future webhook notifications and integration automation
- Future enterprise / legacy identity such as SAML, LDAP / SCIM, and group mapping if demand proves real
- Future advanced governance such as custom roles, retention policies, and admin support mode
- Future multi-tenant isolation and larger managed-processing quotas
**Retroactive gates.** A small number of features shipped in v2.0v2.5 are operator-grade and become Pro-only starting in v2.6: `open` registration mode (from v2.1), teams beyond the included extra team (from v2.2), and storage backends beyond the free limit. Closed / invite-only registration, single-IdP OIDC, all sharing and image-hosting features, all small-file archive processing, and all deployment targets remain Community. See [v2.6](docs/roadmap/v2.6.md) for the complete list.
Paid ZPan has two delivery modes:
- **Self-hosted Pro / Business** — buy a license, run ZPan yourself. The instance binds to ZPan Cloud for entitlement and verifies the issued certificate locally.
- **Managed** — Pro or Business features operated by us. You stop caring about ops.
Pro and Business are purchased or redeemed on ZPan Cloud, then mirrored to a
bound ZPan instance by signed entitlement certificates. See
[v2.6](docs/roadmap/v2.6.md) for the launch plan.
**Retroactive gates.** A small number of features shipped before or around the
Pro / Business launch are operator-grade and become paid-tier gated: `open`
registration mode, teams beyond the included extra team, storage backends beyond
the free limit, social / OIDC providers beyond the free limit, and downloaders
beyond the free limit. Closed / invite-only registration, one social / OIDC
provider, all sharing and image-hosting features, WebDAV, one downloader, small
archive processing, and all deployment targets remain Community.
### Managed Cloud Services (separate repo)
A small set of Pro features depend on backend infrastructure we host: large archive processing, content moderation (third-party NSFW scanning), server-side media/document processing, and managed custom domains (SSL provisioning). Those services live in a separate closed-source repo (`zpan-cloud`) and version independently. Self-hosted Pro users call these services via HTTPS; usage included up to a monthly quota per plan, post-included usage paid with unitless ZPan Cloud credits. Stripe subscriptions remain normal USD subscriptions; ZPan only receives entitlements and usage accept/reject decisions. Cloud does not run remote-download workers in the v2.7 plan.
A small set of paid-tier features depend on backend infrastructure we host: large archive processing, content moderation (third-party NSFW scanning), server-side media/document processing, and managed custom domains (SSL provisioning). Those services live in a separate closed-source repo (`zpan-cloud`) and version independently. Self-hosted paid instances call these services via HTTPS; usage included up to a monthly quota per plan, post-included usage paid with unitless ZPan Cloud credits. Stripe subscriptions remain normal USD subscriptions; ZPan only receives entitlements and usage accept/reject decisions. Cloud does not run remote-download workers in the v2.7 plan.
### Pricing
Initial pricing target: a single Pro SKU with generous monthly quotas covering ~90% of users. Tiered plans (Team, Business) follow once Pro validates. Full pricing is in [v2.6](docs/roadmap/v2.6.md).
Pricing starts with clear Pro and Business tiers: Pro covers operator limits and
basic paid self-hosting, while Business covers commerce, analytics, and higher
operational controls. Full pricing mechanics start in
[v2.6](docs/roadmap/v2.6.md) and expand with v2.8 analytics/reporting.
## v1 Issues Addressed
+306 -356
View File
@@ -1,443 +1,393 @@
# Architecture & Technical Decisions
Technical reference for ZPan v2 development. For product roadmap, see [V2_ROADMAP.md](../V2_ROADMAP.md).
Technical reference for the current ZPan implementation. Product sequencing and
future scope live in [V2_ROADMAP.md](../V2_ROADMAP.md) and
[`docs/roadmap/`](roadmap/), not in this document.
## Global Architecture
## System Shape
```
CF Workers (wrangler) Docker (Node.js)
┌─────────────────────┐
│ [assets] → dist/ │
│ SPA fallback │ entry-node.ts
│ main: workers/ │ (serves both static + API)
│ exports { fetch } │ │
└──────────┬──────────┘ │
│ Single deployment │
└──────────┬──────────────────────┘
Hono (app)
┌─────────────┼──────────────┐
▼ ▼ ▼
Better Auth Routes/Services Drizzle ORM
(auth) (business logic) (database)
@aws-sdk/client-s3
(presigned URLs)
S3 / R2 (file storage)
ZPan is a single-package full-stack TypeScript application. The server is the
control plane for auth, metadata, quotas, sharing, WebDAV, background jobs, and
paid-tier entitlement checks. File bytes live in S3-compatible object storage and
are uploaded or downloaded through presigned URLs whenever possible.
```text
Browser SPA / WebDAV / API clients / downloader agents
|
v
Hono application
|
middleware + route modules
|
v
usecases
|
ports / Deps object
|
+----------------+----------------+
| | |
repos gateways providers
| | |
Drizzle DB S3 / Cloud / mail CF APIs / changelog
```
## Core Tech Stack
Primary deployment target is Cloudflare Workers. Node.js/Docker is the backup
runtime, and the same Hono app is adapted for other serverless targets.
| Layer | Choice | Alternatives Considered | Why This One |
|-------|--------|------------------------|--------------|
| Web Framework | **Hono** | Express, Fastify, ElysiaJS | Native CF Workers + Node.js dual runtime. Lightweight, fast, web-standard Request/Response API |
| ORM | **Drizzle** | Prisma, Kysely | First-class D1 support, type-safe, SQL-like syntax, lightweight bundle for CF Workers |
| Auth | **Better Auth** | Lucia, Auth.js, custom JWT | Drizzle adapter, D1 support, built-in social login / OIDC / organization plugin, active development |
| S3 SDK | **@aws-sdk/client-s3** | minio-js, custom fetch | Industry standard, works with every S3-compatible provider including R2 |
| Package Manager | **pnpm** | npm, yarn, bun | Fast, strict dependency installs with a committed lockfile; Volta pins the version |
| Frontend Framework | **React 19 + Vite** | Vue, Solid, Svelte | Largest ecosystem, Capacitor-ready for future native apps if needed |
| UI Components | **shadcn/ui + Tailwind CSS 4** | Ant Design, MUI | Zero runtime overhead (source code, not npm dep), best responsive/mobile support, modern aesthetic preferred by international users |
| Admin Scaffold | **[shadcn-admin](https://github.com/satnaing/shadcn-admin)** (11.6k stars) | Custom build | Fork as starting point for the **admin backend panel**. Includes sidebar, navigation, search, dark mode, responsive layout, settings pages. Same stack: Vite + TanStack Router + shadcn/ui. The user-facing frontend is custom-built |
| File Manager | **Custom** (shadcn/ui + @tanstack/react-table + @dnd-kit) | SVAR, @cubone/react-file-manager | List/grid views, directory tree, breadcrumb, context menu, drag-and-drop, file preview, type-safe, full control |
| Routing | **TanStack Router** | React Router, Next.js | Type-safe, lightweight, no framework lock-in. Included in shadcn-admin |
| Data Table | **TanStack Table** (via shadcn/ui Data Table) | AG Grid | Share management, user management, admin pages. Included in shadcn/ui |
| Icons | **Lucide** | Heroicons, Phosphor | Default icon set for shadcn/ui, consistent style |
| Forms | **react-hook-form + zod** | Formik | Lightweight, type-safe validation, shadcn/ui has built-in form components |
| File Upload UI | **react-dropzone** | Uppy, Filepond | Minimal, headless, composable with shadcn/ui. Community has ready-made shadcn upload blocks |
| Charts | **Recharts** (via shadcn/ui Charts) | Chart.js, D3 | For analytics dashboard (v2.8). Included in shadcn/ui |
| Notifications | **Sonner** (via shadcn/ui) | react-toastify | Upload feedback, copy-to-clipboard confirmations. Included in shadcn/ui |
## Core Decisions
## Frontend Architecture
| Area | Decision | Reason |
|------|----------|--------|
| Server framework | Hono | Runs cleanly on Workers and Node with web-standard Request/Response |
| API typing | Hono RPC for frontend, OpenAPI for external clients | Frontend gets compile-time path/type safety; non-TS clients get a stable REST contract |
| Runtime database | D1 on Workers, SQLite or Turso/libSQL elsewhere | One SQLite-family schema across every deployment target |
| ORM | Drizzle | Type-safe SQLite/D1 schema and migrations with low runtime overhead |
| Auth | Better Auth | Email/password, social/OIDC, organizations, API keys, device flow |
| Object storage | S3-compatible protocol | Works with R2, S3, MinIO, B2, Tigris, RustFS, and similar providers |
| Frontend | React 19 + Vite + TanStack Router | SPA with typed file routes and no server-rendering framework lock-in |
| UI | shadcn/ui-style components, Radix UI, Tailwind CSS 4, Lucide | Local component ownership with consistent interaction primitives |
| Tests | Vitest projects + Playwright | Unit, integration, Workers runtime, libSQL, and browser-level coverage |
### Off-the-shelf vs Custom
## Repository Layout
| Component | Source | Custom Work |
|-----------|--------|-------------|
| Layout, sidebar, navigation, dark mode | shadcn-admin | Minimal — adapt routes and menu items |
| File manager (list, grid, tree, drag-drop) | Custom (shadcn/ui + react-table + dnd-kit) | Built-in, path-based navigation |
| Upload dropzone | react-dropzone + shadcn blocks | Compose with presigned URL upload logic |
| Data tables (shares, users, storage) | shadcn/ui Data Table | Define columns and connect to API |
| Dialogs (share settings, file detail) | shadcn/ui Dialog/Sheet | Build forms inside pre-made shells |
| Global search | shadcn/ui Command | Connect to search API |
| Toast / notifications | shadcn/ui Sonner | Wire to upload events |
| **Share landing page** | **Custom** | Public page with file preview + download |
| **Upload history panel** | **Custom** | Recent uploads with URL copy |
| **Storage backend config forms** | **Custom** | S3 endpoint, credentials, path template |
| **Image bed quick-upload view** | **Custom** | Paste/drop → get URL, minimal UI |
Most of the heavy UI work is handled by existing components. Custom development focuses on ZPan-specific business pages.
## Project Structure
Single-package structure. Cargo workspace for Rust native tools (planned).
```
```text
zpan/
├── server/ # Hono API
│ ├── routes/ # API route handlers
│ ├── services/ # Business logic
│ ├── middleware/ # Hono middleware
│ ├── db/ # Drizzle schema
│ ├── platform/ # CF vs Node.js adapters
│ ├── entry-node.ts # Node.js entry point
── auth.ts # Better Auth config
├── src/ # React frontend (Vite + shadcn/ui)
│ ├── components/ # UI components (files/, preview/, upload/, admin/, layout/)
│ ├── routes/ # TanStack Router file-based routing
── lib/ # API client (Hono RPC), utils
│ └── i18n/ # Translations (en.json, zh.json)
├── shared/ # Shared types, Zod schemas, constants
├── workers/ # CF Workers entry
── app.ts # CF entry point
├── migrations/ # D1/SQLite migrations (drizzle-kit generated)
├── e2e/ # Playwright E2E tests
├── wrangler.toml # Cloudflare Workers config
├── biome.json # Lint + format config
── package.json # Single package, pnpm
├── server/
│ ├── adapters/ # Concrete repos, gateways, and providers
│ ├── db/ # Drizzle schemas and DB helpers
│ ├── domain/ # Pure domain helpers and policy logic
│ ├── http/ # Hono route modules
│ ├── middleware/ # Hono middleware and principal resolution
│ ├── platform/ # Runtime adapters: Workers, Node, libSQL
── usecases/ # Business workflows and port interfaces
│ ├── app.ts # Hono app composition and route mounting
│ ├── auth.ts # Better Auth configuration
│ ├── bootstrap.ts # Runtime-independent app bootstrap
── entry-*.ts # Node/serverless entry points
├── src/
│ ├── components/ # UI components and feature views
│ ├── hooks/ # Frontend hooks
── i18n/ # Frontend translations
│ ├── lib/ # RPC clients, API wrappers, formatters, utilities
│ └── routes/ # TanStack Router file routes
├── shared/ # Shared schemas, constants, feature registry, types
├── workers/ # Cloudflare Worker fetch/scheduled/queue entry
── migrations/ # drizzle-kit generated SQL migrations
├── e2e/ # Playwright tests
├── wrangler.toml # Cloudflare Workers configuration
└── package.json # Single pnpm package
```
## API Communication
There is no native desktop or mobile client workspace in this repository.
Native client implementations are expected to live in separate projects. This
repository can still own server-side API contracts and isolated automation
clients when they are part of the ZPan server/web product boundary.
### Internal: Hono RPC (type-safe)
## Runtime Entry Points
Frontend-to-backend calls use Hono's built-in RPC for end-to-end type safety. Changing a backend route automatically surfaces type errors in the frontend at compile time.
### Cloudflare Workers
### External: Standard REST
[`workers/bootstrap.ts`](../workers/bootstrap.ts) exports the Worker handlers:
PicGo, ShareX, Flameshot, zpan-cli, and third-party integrations use standard REST endpoints with token authentication. Same routes, just accessed without the RPC client.
- `fetch` creates a Cloudflare platform from the request environment, reuses a
cached Better Auth instance per isolate, injects Open Graph tags for share
pages, then delegates to `createApp`.
- `scheduled` delegates to [`workers/scheduled.ts`](../workers/scheduled.ts) for
licensing refresh, traffic sync, quota reset, trash purge, and telemetry.
- `queue` runs archive-job messages through the archive jobs gateway.
### Data Fetching: TanStack Query
### Node / Docker / Cloud Run
All frontend data fetching goes through TanStack Query for caching, loading states, optimistic updates, and pagination.
[`server/entry-node.ts`](../server/entry-node.ts) serves the same Hono app plus
the built SPA from `dist/`. It chooses the platform at boot:
## Internationalization (i18n)
- `TURSO_DATABASE_URL` set: libSQL/Turso via `createLibsqlPlatform`
- otherwise: local SQLite via `createNodePlatform`
**react-i18next** for frontend translations. Default language: English. Bundled translations: English, Chinese (Simplified).
Because Node has no platform scheduler, the entry starts interval-based jobs for
license refresh, traffic sync, quota reset, trash purge, and telemetry.
Translation files in `src/i18n/locales/{en,zh}.json`. Community can contribute additional languages via PR.
### Other Serverless Targets
Backend error messages returned as i18n keys, frontend resolves to localized strings.
## Testing
| Layer | Tool | Scope |
|-------|------|-------|
| Unit tests | **Vitest** | Shared schemas, constants, utilities |
| Integration tests (Node) | **Vitest** + Hono `app.request()` | Route handlers, middleware, auth flows (better-sqlite3) |
| Integration tests (CF) | **Vitest** + `@cloudflare/vitest-pool-workers` | Same routes on Cloudflare Workers runtime (Miniflare + D1) |
| E2E tests | **Playwright** | Full user flows: login, navigation, file management |
Coverage gate: 90% on server. Playwright tests live in `e2e/` at the repo root, run against a local dev server.
## CI/CD
GitHub Actions:
**On PR:**
- `pnpm lint` — Biome lint + format check
- `pnpm typecheck` — TypeScript compilation
- `pnpm test` — Vitest unit + API tests
- `pnpm e2e` — Playwright tests
**On merge to main:**
- Build + deploy to CF Workers (preview / production)
- Build Docker image + push to Docker Hub (planned)
`server/entry-lambda.ts`, `server/entry-vercel.ts`,
`server/entry-netlify.ts`, and `server/entry-azure.ts` are thin adapters around
the same app and platform model. Business logic does not fork by deployment
target.
## Platform Abstraction
The only code that differs between CF and Docker:
[`server/platform/interface.ts`](../server/platform/interface.ts) is the runtime
boundary:
```
src/platform/
├── interface.ts # Platform interface definition
├── cloudflare.ts # CF Workers: D1 binding
└── node.ts # Node.js: better-sqlite3 / pg
```
### Interface
```typescript
```ts
interface Platform {
db: Database // D1 (CF) or better-sqlite3 (Node)
db: Database
getEnv(key: string): string | undefined
getBinding<T = unknown>(key: string): T | undefined
}
```
Note: `s3` and `cron` will be added in later versions.
Concrete implementations:
### Entry Points
- `cloudflare.ts` wraps D1 and platform bindings.
- `node.ts` wraps `better-sqlite3`.
- `libsql.ts` wraps `@libsql/client` and runs migrations against Turso/libSQL.
- `context.ts` provides an `AsyncLocalStorage` proxy so shared objects can read
the active request platform safely.
- `workers/bootstrap.ts` — exports `default { fetch }`, wrangler bundles it directly. Static frontend served by wrangler's `[assets]` config with SPA fallback.
- `server/entry-node.ts` — starts Hono via `@hono/node-server`, serves both API and static frontend from `./dist` on the same port.
Only runtime infrastructure belongs in `platform/`. Business decisions stay in
usecases and domain modules.
## Per-Version Technical Decisions
## Server Layering
---
The server follows a ports-and-adapters shape.
### v2.0 — Foundation
### `server/app.ts`
**Database: Drizzle + D1 / SQLite**
Creates the Hono app, installs global middleware, exposes OpenAPI/Scalar docs,
mounts WebDAV, and mounts each API resource. It also merges Better Auth's
generated OpenAPI schema into `/api/openapi.json`.
Schema definition in `src/db/schema.ts`, single source of truth. Drizzle Kit for migrations.
### `server/http/`
Tables: `matters`, `storages`, `storage_quotas`, `system_options` + Better Auth managed tables (`user`, `session`, `account`, `verification`).
Route modules own HTTP concerns:
**Auth: Better Auth (email/password only)**
- path shape and Hono mounting
- request validation and response serialization
- route-level auth guards
- OpenAPI route metadata
Mounted at `/api/auth/*`. Session-based auth with secure cookies. Better Auth manages its own user/session tables — ZPan references the user ID in its own tables.
Routes should call usecases or narrow domain helpers. They should not reimplement
business workflows.
**Storage Provider: Unified S3 protocol**
### `server/usecases/`
One `@aws-sdk/client-s3` instance per storage backend. No per-provider SDKs (unlike v1 which had 8 separate implementations). All providers accessed via S3-compatible API.
Usecases own business workflows and coordination:
Key operations:
- `PutObjectCommand` presigned URL for uploads
- `GetObjectCommand` presigned URL for downloads
- `HeadObjectCommand` for upload verification
- `CopyObjectCommand` for file copy
- `DeleteObjectCommand` / `DeleteObjectsCommand` for deletion
- file object create/upload/complete/trash/restore/transfer
- quota checks and traffic metering
- sharing and save-to-drive behavior
- team and user administration
- site settings, licensing, announcements, branding, audit
- archive processing and remote download task orchestration
**File Path Templating**
Usecases receive `deps` as their first argument and reach the outside world only
through ports.
Carried over from v1. Storage config includes `file_path` template with variables: `$UID`, `$UUID`, `$RAW_NAME`, `$RAW_EXT`, `$NOW_DATE`, `$RAND_16KEY`, etc.
### `server/usecases/ports*.ts`
**CORS**
Ports define the contracts usecases need from persistence and external systems:
repos, S3, email, zip, Cloud licensing, image upload, download tokens, and
similar dependencies.
v1 auto-configured CORS on storage setup via provider-specific SDKs. v2 drops this — document the required CORS config instead, since S3 CORS can be set via any S3 client or cloud console. Less magic, fewer provider-specific issues.
### `server/adapters/`
---
Adapters implement ports:
### v2.1 — Auth & Access
- `repos/` persists to Drizzle tables.
- `gateways/` talks to S3, email, ZPan Cloud, archive queues, and zip handling.
- `providers/` wraps platform/provider-specific APIs such as Cloudflare custom
hostnames or changelog fetching.
**Social Login: Better Auth built-in**
[`server/composition.ts`](../server/composition.ts) is the composition root and
the only place concrete adapters are assembled into the `Deps` object.
Configure via `socialProviders` in Better Auth config. Each provider needs `clientId` and `clientSecret` from env vars.
### `server/domain/`
**OIDC: Better Auth Generic OAuth plugin**
Pure policy and transformation helpers live here when they do not need I/O:
licensing checks, path templating, WebDAV XML helpers, share rules, quota math,
name-conflict planning, and similar logic.
```typescript
import { genericOAuth } from "better-auth/plugins"
## Authentication And Principals
genericOAuth({
config: [{
providerId: "company-sso",
discoveryUrl: "https://idp.example.com/.well-known/openid-configuration",
clientId: env.OIDC_CLIENT_ID,
clientSecret: env.OIDC_CLIENT_SECRET,
}]
})
```
Better Auth owns the core auth tables and session lifecycle. ZPan adds route
guards and principal normalization in [`server/middleware/auth.ts`](../server/middleware/auth.ts).
OIDC provider config stored in `system_options` table, editable via admin panel.
The auth middleware is intentionally soft-fail: it tries to identify the caller
and sets context variables, while each route decides whether anonymous access is
allowed.
**Invite Codes**
Supported principals:
New table `invite_codes` (`code`, `created_by`, `used_by`, `used_at`, `expires_at`). Registration mode stored in `system_options`. Better Auth `before` hook on sign-up validates invite code.
- `user` — browser cookie session or bearer session
- `api-key` — Better Auth API key plugin, including org-scoped keys
- `downloader` — remote downloader agent token
- `download-task-upload` — scoped token that lets a downloader upload one task's
completed output
---
Route guards then enforce `requireAuth`, `requireAdmin`, `requireDownloader`, or
`requireTeamRole('viewer' | 'editor' | 'owner')`.
### v2.2 — Teams
## Data Model
**Better Auth Organization Plugin**
Drizzle schemas live in [`server/db/schema.ts`](../server/db/schema.ts) and
[`server/db/auth-schema.ts`](../server/db/auth-schema.ts). Migrations live at the
repo root in `migrations/` and are generated with `pnpm db:generate`.
Direct use, no custom implementation needed.
Major table groups:
```typescript
import { organization } from "better-auth/plugins"
- **Auth and organizations**: `user`, `session`, `account`, `organization`,
`member`, `invitation`, `apikey`, `deviceCode`
- **Objects and storage**: `matters`, `storages`, `object_upload_sessions`
- **Quota and billing**: `org_quotas`, `org_quota_entitlements`,
`cloud_traffic_reports`, `webhook_events`
- **Sharing and notifications**: `shares`, `share_recipients`, `notifications`
- **Site administration**: `system_options`, `license_bindings`,
`announcements`, `activity_events`, invite tables
- **WebDAV**: `webdav_dead_properties`, `webdav_locks`
- **Jobs and downloaders**: `background_jobs`, `downloaders`, `download_tasks`,
`remote_download_usage_reports`
- **Image hosting**: `image_hosting_configs`, `image_hostings`
organization({
teams: { enabled: true },
// custom roles for ZPan
})
```
`matters` is the file tree. Every object belongs to an organization (`orgId`),
not directly to a user. Storage object keys are implementation details hidden
behind ZPan's object and storage abstractions.
**Custom Roles:**
## File And Object Flow
```typescript
const statement = {
file: ["upload", "download", "delete", "share"],
workspace: ["manage", "invite"],
} as const
### Browser Upload
const ac = createAccessControl(statement)
const viewer = ac.newRole({ file: ["download"] })
const editor = ac.newRole({ file: ["upload", "download", "delete", "share"] })
const owner = ac.newRole({
file: ["upload", "download", "delete", "share"],
workspace: ["manage", "invite"],
})
```
1. Frontend calls `POST /api/objects` through the Hono RPC wrapper.
2. Server creates a draft `matters` row plus an `object_upload_sessions` row.
3. Server returns presigned upload instructions.
4. Browser uploads bytes directly to S3/R2 with raw `fetch` to the presigned URL.
5. Frontend calls the completion endpoint.
6. Server verifies and activates the `matters` row, updates quota/usage, and
records activity.
**Data Model**
Abort paths discard the draft and try to clean up storage-side multipart state
where possible.
Organization plugin creates its own tables (`organization`, `member`, `invitation`). ZPan's `matters` table gets an optional `org_id` field — when set, the file belongs to the team workspace instead of the individual user.
### Download
**User Share Homepage**
Authenticated object downloads and public share downloads resolve metadata in
ZPan, enforce access/quota/credit rules, and return or redirect to a presigned
object-storage URL. ZPan does not proxy file bytes unless a feature explicitly
requires it.
Route: `GET /u/{username}` — server-side rendered page listing user's public shares. Data from `shares` table filtered by `uid` + a `public_profile` flag.
### Remote Download
---
1. A user creates a `download_tasks` row.
2. A registered downloader agent heartbeats to `/api/downloads/downloaders`.
3. The server assigns queued tasks based on availability/capabilities.
4. The downloader fetches source bytes outside the main ZPan runtime.
5. The downloader uploads completed output back through a scoped task-upload
token and the normal object upload path.
6. ZPan records status, activity, and optional remote-download usage reports.
### v2.3 — Sharing
### WebDAV
**Share Token Auth**
WebDAV paths resolve to `matters` records through the WebDAV usecases and repos.
Object keys remain private implementation details. Browser UI, API, and WebDAV
all share the same authorization and storage model.
Public shares: no auth needed, accessible by alias.
Private shares: client sends password via `POST /api/shares/{alias}/token`, gets a short-lived JWT stored in cookie. Subsequent requests include this cookie.
### Archive Jobs
**Direct Links**
Archive operations use `background_jobs` for durable status and progress. On
Workers, queue messages execute archive work. On Node, archive work runs through
the gateway path available to the current runtime.
`GET /s/{alias}` → returns the file itself (redirect to presigned S3 URL or public URL).
`GET /s/{alias}/info` → returns the share landing page with preview.
## Frontend Architecture
---
The frontend is a Vite React SPA.
### v2.4 — Image Bed
- File routes live in `src/routes/` via TanStack Router.
- Data fetching uses TanStack Query.
- API wrappers live in `src/lib/api.ts`; each wrapper is tested in
`src/lib/api.test.ts`.
- RPC clients live in `src/lib/rpc.ts` and use Hono RPC types exported from
`server/app.ts`.
- Shared types and schemas must come from `shared/`.
- UI components live under `src/components/`, grouped by feature and common UI
primitives.
- i18n strings live in `src/i18n/locales/en.json` and `zh.json`.
Frontend code must call ZPan APIs through Hono RPC wrappers. The exception is
external URLs that are not ZPan APIs, such as presigned S3 upload URLs.
## API Surfaces
- `/api/*` — primary JSON API, mounted from route modules in `server/http/`
- `/api/auth/*` — Better Auth routes
- `/api/openapi.json` — combined OpenAPI document for ZPan routes and Better
Auth routes
- `/api/docs` — Scalar API reference
- `/dav/*` — WebDAV endpoint
- `/api/events` — server-sent events for notifications, jobs, and download-task
updates
- `/r/*` and `/s/*` — public redirect/share surfaces
- `/api/store/*` — quota store and Cloud webhook endpoints
Public, authenticated, admin, downloader, and webhook routes can share a path
prefix. Authorization is per route or per mounted sub-app, so mount order matters
when a public/user router and admin router share a prefix.
## Background Work
Cloudflare Workers:
- `scheduled()` handles license refresh, traffic report sync, quota reset, trash
purge, and telemetry.
- `queue()` handles archive job messages.
Node/Docker:
- `server/entry-node.ts` runs equivalent recurring work with `setInterval`.
Background jobs must be idempotent where possible. External delivery and Cloud
reporting tables use stable event ids to prevent duplicate effects.
**Upload API**
## Paid-Tier Architecture
New endpoint `POST /api/upload` — simplified single-endpoint upload for tool integrations. Accepts `multipart/form-data` or presigned URL flow. Returns URL in requested format (raw, markdown, html, bbcode).
Paid-tier availability is certificate based.
Separate from the existing `POST /api/matters` which is for the file manager UI (creates matter record first, then returns presigned URL).
- ZPan Cloud is the source of truth for subscriptions and bound instances.
- ZPan stores the active binding in `license_bindings`.
- ZPan verifies signed entitlement certificates locally.
- `shared/feature-registry.ts` is the feature-comparison and gate-key source of
truth.
- `server/middleware/require-feature.ts` gates API routes that require a paid
feature.
The quota store uses Cloud for checkout/payment and ZPan for local catalog,
entitlement delivery, quota calculation, and usage enforcement.
## Testing And Quality Gates
**ShareX Compatibility**
Configured test projects in [`vitest.config.ts`](../vitest.config.ts):
- `unit` — pure utilities, schemas, domain helpers, and frontend components in
jsdom
- `integration` — Hono route/usecase integration against SQLite
- `cloudflare` — Workers runtime tests with D1 migrations applied through
`@cloudflare/vitest-pool-workers`
- `libsql` — platform smoke tests for the libSQL/Turso path
Other gates:
ShareX Custom Uploader is a JSON spec (`.sxcu` file). ZPan generates this config file dynamically via `GET /api/integrations/sharex` with the user's API token embedded.
- `pnpm lint` — Biome lint and format check
- `pnpm typecheck` — server and frontend TypeScript projects
- `pnpm test` — unit + integration
- `pnpm test:cf` — Workers runtime tests
- `pnpm test:libsql` — libSQL platform tests
- `pnpm e2e` — Playwright browser flows
Every PR that changes API or UI behavior also needs preview-environment
verification per [CONTRIBUTING.md](../CONTRIBUTING.md).
**PicGo Compatibility**
## Architectural Rules
PicGo uses a simple REST API: `POST` with `multipart/form-data`, response includes `url` field. The same `/api/upload` endpoint handles this.
---
### v2.5 — Branding & Polish
**Site Branding**
Stored in `system_options` table under key `core.branding`: `{ logo, title, favicon, description }`. Frontend fetches on load via `GET /api/system/options/core.branding` (public endpoint).
**Custom File Domain**
`custom_host` field on storage backends. When set, presigned URLs and public URLs use this host. No server-side proxy — DNS points directly to S3/R2.
**Dark Mode**
Frontend-only, CSS variables + user preference stored in localStorage.
---
### v2.6 — Backup (zpan-cli)
**Location**: `native/crates/zpan-cli/` (same monorepo)
**Language: Rust**
Rust produces small (~5MB) static binaries for every platform (macOS, Linux, Windows, ARM). No runtime dependencies. Ideal for NAS environments.
Core logic lives in `zpan-core` crate, shared with the desktop app (v2.7).
**Rust crate dependencies:**
- `aws-sdk-s3` — S3 operations (upload, download, head, delete)
- `notify` — filesystem watching for real-time backup
- `tokio-cron-scheduler` — scheduled backup runs
- `clap` — CLI argument parsing
- `blake3` — fast file hashing for change detection
- `reqwest` — HTTP client for ZPan API calls
- `serde` / `serde_json` — serialization
- `tokio` — async runtime
**Sync Protocol**
CLI authenticates with ZPan via API token. Workflow:
1. Scan local directory, compute file hashes
2. `POST /api/sync/check` — send hashes, get back list of files needing upload
3. `POST /api/sync/batch-presign` — get presigned URLs for new/changed files
4. Upload directly to S3
5. `POST /api/sync/complete` — report uploaded files, ZPan updates matter records
**ZPan API additions:**
- `POST /api/sync/check` — file fingerprint comparison
- `POST /api/sync/batch-presign` — batch presigned URL generation
- `GET /api/sync/status` — backup job status for web UI
---
### v2.7 — Sync & Desktop App
**Change Log**
New table `changes` (`id`, `matter_id`, `action`, `path`, `hash`, `device_id`, `timestamp`). Every file create/update/delete writes a change record.
**Sync API:**
- `GET /api/sync/changes?since={id}&device={deviceId}` — pull changes from other devices
- `POST /api/sync/changes` — push local changes
**Conflict Strategy**
Default: keep both files. Remote version wins the original filename, local version renamed to `{name}.conflict-{device}-{date}.{ext}`. Optional `--conflict last-write-wins` flag for advanced users.
**Desktop App: Tauri 2**
Location: `native/crates/zpan-desktop/`. Ships alongside v2.7 as the graphical interface for backup + sync.
- Built with Tauri 2, frontend reuses React components from `src/`
- Core sync/backup logic imported from `zpan-core` crate (same code as CLI)
- System tray with status menu (backup status, sync status, pause, settings)
- Settings UI for configuring backup directories, sync folders, server connection
- Auto-start on login (OS-native)
- macOS, Windows, Linux builds via Tauri's cross-platform tooling
Tauri dependencies:
- `tauri` — app framework, system tray, window management
- `tauri-plugin-autostart` — launch on login
- `tauri-plugin-notification` — native notifications (sync conflict, backup complete)
---
### v2.8 — Managed Service
**Stripe: `stripe` npm package**
Webhook endpoint at `POST /api/webhooks/stripe` for subscription lifecycle events. Plan limits enforced in middleware (check user's plan before allowing operations).
**Analytics**
Managed version runs a separate analytics pipeline. Options:
- CF Analytics Engine (native, no extra infra)
- ClickHouse (Docker deployment)
- Simple approach: aggregate counters in D1/PostgreSQL with daily rollup cron
**Webhooks**
New tables: `webhook_endpoints` (`url`, `events`, `secret`) and `webhook_deliveries` (`endpoint_id`, `event`, `payload`, `status`, `attempts`). Delivery via CF Queue (managed) or Bull/BullMQ (Docker).
**Audit Log**
New table `audit_logs` (`user_id`, `action`, `resource_type`, `resource_id`, `metadata`, `timestamp`). Middleware writes audit entries on every mutating API call. Retention enforced by cron (7 days for Pro, 90 days for Team).
---
### v2.9 — Managed Advanced
**Content Moderation**
Upload hook triggers image classification. Options:
- CF Workers AI (managed deployment)
- External API (AWS Rekognition, Google Cloud Vision)
Flagged files stored in `moderation_queue` table for admin review.
**Server-side Processing**
Background worker (CF Queue consumer or separate container) handles:
- Archive extraction: stream zip from S3, extract entries, write back to S3
- Thumbnails: sharp / CF Image Resizing
- Format conversion: sharp for images
**Managed Custom Domains**
CF for SaaS (managed deployment) — programmatic custom domain provisioning via CF API. Creates custom hostname with automatic SSL.
- Keep the server S3-native: no provider-specific storage SDKs unless the
abstraction genuinely requires it.
- Keep platform-specific code in `server/platform/`, Worker entry files, or
deployment adapters.
- Keep business workflows in usecases; HTTP routes should stay thin.
- Use ports/adapters for persistence and external services.
- Put shared contracts in `shared/`; do not duplicate types in frontend/server
folders.
- Generate migrations with drizzle-kit; do not hand-author migration journal
entries.
- Preserve direct-to-object-storage upload/download paths unless a feature has a
concrete reason to proxy bytes.
- Treat personal spaces and team spaces as organization-owned data containers;
quota, sharing, WebDAV, and future clients must preserve that ownership model.
+3 -3
View File
@@ -180,14 +180,14 @@ Current state (verified, complete):
| Send to a specific registered user | Directed share (`shareRecipients`) → in-app notification (`share_received`, deep-links to `/s/:token`) + email when configured | ✅ closed loop (`server/services/share-notification.ts`, `notification-item.tsx`) |
| Keep a copy of something shared to me | Save-to-drive (physical copy, target space pays) | ✅ shipped |
| Ongoing sharing with fixed people | Shared space + cross-space copy/move | space ✅ / transfer = §3 |
| Folder-level permissions inside a team | Advanced RBAC, **team spaces only** | 📋 v2.8 |
| Folder-level permissions inside a team | Advanced RBAC, **team spaces only** | Future / demand-driven |
| Grant others access inside a personal space | **Will not do** — guide to creating a shared space | decision |
Decisions:
1. **No per-item ACL, ever, in personal spaces.** A personal space has exactly one human.
The moment a second person needs standing access, that's a shared space.
2. **v2.8 RBAC stays scoped to team spaces** (folder-level restrictions among members).
2. **Future RBAC stays scoped to team spaces** (folder-level restrictions among members).
It adds a directory dimension to the existing space-role system; it does not introduce
a second permission system.
3. Traffic for share downloads continues to be charged to the share creator's org
@@ -210,4 +210,4 @@ Not built / deferred:
- **Quota allocation** (§2.2) — built then removed; no valid input (only subscriptions are
sold) and redundant with team purchase + admin grant. Revisit only with a one-time pack SKU.
- (Later) same-storage copy dedup via refcounting (§3); v2.8 team RBAC per roadmap.
- (Later) same-storage copy dedup via refcounting (§3); team RBAC remains future / demand-driven.
+148 -31
View File
@@ -1,45 +1,162 @@
# v2.10 — Sync & Desktop App
# v2.10 — Desktop Sync Foundation
Upgrade zpan-cli from one-way backup to bidirectional sync. Ship the desktop tray app.
Prepare ZPan for real desktop sync clients without putting those clients in this
repository. The main `zpan` repo owns the server-side sync model, API contract,
and protocol tests. OS-specific clients should live in separate projects.
> Originally planned as v2.7. Deferred behind Pro Launch so the commercial foundation ships first.
This version is not a CLI version. The CLI from v2.9 is an automation client for
agents and scripts; it should not become the internal engine for desktop sync.
## Product Boundary
- **Community** gets the basic sync protocol and device model needed for
personal desktop sync.
- **Pro / Business** may raise device limits or add operator controls later.
- Native desktop/mobile clients are separate projects, such as `zpan-desktop` or
`zpan-client`.
## Why Separate Client Projects
Desktop sync is platform-specific:
- Apple platforms likely need File Provider-style integration.
- Windows has its own shell / cloud-files integration model.
- Linux packaging and filesystem behavior are different again.
- A Flutter app may be a good UI shell, but it has a different toolchain,
release process, and platform configuration from this TypeScript full-stack
repository.
Keeping native clients outside this repo prevents the server/web project from
accumulating unrelated build systems and CI burden.
## Major Features
### Bidirectional Sync
- **Two-way sync** — changes on any device propagate to all others via S3
- **Change log** — server tracks every file create / update / delete with device attribution
- **Conflict resolution** — when two devices edit the same file, keep both copies (remote version wins, local saved as `.conflict` variant). Optional: last-write-wins mode
- **Selective sync** — choose which folders to sync on each device
- **Multi-device** — sync between laptop, desktop, NAS, any number of machines
### Sync Device Model
### Upgrade Path
- zpan-cli `backup` command unchanged (one-way, safe)
- New `sync` command enables bidirectional mode
- Same tool, same config format, new capability
Add server-side concepts for sync-capable clients:
### Desktop Tray App (Tauri)
- registered devices
- device tokens
- device name, platform, app version, and last seen timestamp
- per-device revocation
- per-space sync authorization
- audit events for device registration and revocation
Graphical interface for backup and sync, built with Tauri 2. Shares core logic with zpan-cli via the `zpan-core` Rust crate.
This is distinct from downloader agents and API keys. A sync device represents a
user's file-sync client.
- **System tray** with status menu:
- Backup status (last run, next scheduled)
- Sync status (synced / syncing / conflict)
- Pause / resume
- Open settings
- Open ZPan web UI in browser
- **Settings window** — configure backup directories, sync folders, server connection, schedule
- **Native notifications** — backup complete, sync conflict detected, storage quota warning
- **Auto-start on login**
- **macOS, Windows, Linux** builds
### Change Log
Add a durable change stream for object mutations:
- create
- update / replace
- move
- rename
- trash / restore
- delete
- folder operations
Each change should include:
- monotonically increasing sequence or cursor
- org / space id
- object id
- parent id or path context
- actor device id where known
- timestamp
- enough metadata for a client to decide what to fetch next
The change log is the foundation desktop clients need. Do not infer sync state
only from current folder listings.
### Sync API Contract
Initial API surface:
- Register / revoke device
- Pull changes since cursor
- List remote tree snapshot for bootstrap
- Create upload draft for a sync client
- Complete sync upload
- Apply remote file operations from a device
- Report client sync status and conflicts
The contract should be documented through OpenAPI and covered by integration
tests. Client projects should not depend on private frontend RPC helpers.
### Conflict Model
Define the server-side conflict contract before client implementation begins.
Default behavior:
- Keep both copies when concurrent edits cannot be reconciled safely.
- Preserve the remote canonical object.
- Store the losing local version as a conflict copy.
- Record conflict metadata for the client UI.
Last-write-wins can be a later opt-in policy, not the default.
### Server-Side Status Surface
Add lightweight visibility inside ZPan:
- connected sync devices
- last sync time
- current cursor / status where reported
- recent conflicts
- revoke device
This is not a desktop UI. It is the server-side management surface needed to
support external clients.
### Protocol Tests And Client Fixtures
Add reusable tests for client implementers:
- initial bootstrap from an empty local folder
- upload local file -> remote object appears
- remote update -> client receives change
- rename / move propagation
- delete / trash propagation
- conflict creation
- revoked device denied
These tests are part of the server contract. They should be usable by separate
client projects as conformance references.
## Non-goals for v2.10
- Implementing macOS File Provider
- Implementing Windows or Linux desktop sync client
- Implementing Flutter / Tauri / Swift / Kotlin UI
- Using `zpan-cli` as the desktop sync runtime
- LAN peer-to-peer sync
- Real-time collaborative editing
- LDAP, SCIM, SAML, or enterprise directory synchronization
- Folder-level ACLs or custom roles
## User Scenarios
**Freelancer with multiple machines:**
> I work on my desktop at home and laptop at the cafe. I sync `~/Documents` on both. Files stay in sync without thinking about it.
**Client developer:**
> I can build a macOS File Provider client against a documented sync protocol
> without reading private frontend code.
**Non-technical user:**
> I install the ZPan desktop app, log in, pick a folder to back up, and forget about it. The tray icon shows me it's working. I never touch the command line.
**Self-hosted user:**
> I can see which desktop devices are connected to my account and revoke an old
> laptop.
**Small team sharing a NAS:**
> We sync a shared folder between our NAS and each team member's laptop. Everyone has the latest files, even when working remotely.
**Future desktop app:**
> The app pulls a change cursor, applies local changes through the sync API, and
> reports conflicts in a format the server understands.
## Future Work
- Separate `zpan-desktop` / `zpan-client` project
- macOS File Provider implementation
- Windows cloud-files integration
- Linux desktop integration
- Mobile clients
- Flutter or other cross-platform UI experiments
- Advanced enterprise identity and team governance if demand proves real
+4 -2
View File
@@ -2,7 +2,9 @@
Break the "Cloudflare or Docker" duality. ZPan runs on AWS Lambda, Vercel, Netlify, Azure Functions, and Google Cloud Run via a thin adapter layer, with **Turso (libSQL)** as the default database for all non-CF targets.
Carries over the unfinished **avatar upload** item from the original v2.5 scope. Site-level branding (custom logo, favicon) moves to v2.8 as a paid-tier feature.
Carries over the unfinished **avatar upload** item from the original v2.5 scope.
Site-level branding (custom logo, favicon, wordmark) moved into the v2.6 paid-tier
launch instead of staying in the old v2.8 managed-service plan.
## Design Principles
@@ -310,7 +312,7 @@ The one user-level polish item still in v2.5 scope.
-**Dark mode** — already shipped (`next-themes` integrated, `/settings/appearance` page exists)
-**Site name / description** — already shipped (`use-site-options`)
-**Custom file access domain** — subsumed by v2.4 Image Hosting custom domain
- 💰 **Custom logo / favicon / site branding** — moved to [v2.8 Managed Service](v2.8.md) as a paid-tier feature. Self-host stays on the default ZPan brand.
- 💰 **Custom logo / favicon / site branding** — moved to [v2.6 Pro / Business Launch](v2.6.md) as a paid-tier feature.
## User Scenarios
+17 -17
View File
@@ -1,14 +1,14 @@
# v2.6 — Pro Launch
# v2.6 — Pro / Business Launch
Introduce ZPan Pro. Ship the cloud-account binding flow, the entitlement verification layer, the feature-gating framework, a self-service quota store, and visible Pro features so there is something concrete to sell on day one.
Introduce ZPan paid tiers. Ship the cloud-account binding flow, the entitlement verification layer, the feature-gating framework, a self-service quota store, and visible paid features so there is something concrete to sell on day one.
**Generic Pro activation remains Cloud-owned.** Subscription purchase, subscription management, generic Pro activation, membership redemption codes, and entitlement source-of-truth all live on `cloud.zpan.space`. ZPan instances only _bind_ to a cloud account for Pro status; their Pro status mirrors the bound account's entitlement automatically. There is no generic Pro token to paste into ZPan.
**Generic paid-tier activation remains Cloud-owned.** Subscription purchase, subscription management, generic Pro / Business activation, membership redemption codes, and entitlement source-of-truth all live on `cloud.zpan.space`. ZPan instances only _bind_ to a cloud account for paid-tier status; their status mirrors the bound account's entitlement automatically. There is no generic token to paste into ZPan.
**Product direction update:** v2.6 also adds a scoped storage-quota store inside ZPan. This does not move generic Pro activation into ZPan. It lets a Pro instance operator define a per-instance storage package catalog, while Cloud remains the merchant of record and Stripe integration owner. Terminal storage buyers do not need ZPan Cloud accounts.
**Product direction update:** v2.6 also adds a scoped storage-quota store inside ZPan. This does not move generic paid-tier activation into ZPan. It lets a Business instance operator define a per-instance storage package catalog, while Cloud remains the merchant of record and Stripe integration owner. Terminal storage buyers do not need ZPan Cloud accounts.
**Scope update:** audit log and site announcements have moved forward from the original v2.7 operator plan into v2.6. v2.7 is now reserved for file-processing workflows: small-file archive handling in Community and remote-download orchestration through user-owned download engines such as Aria2 and qBittorrent.
Pro features can be added continuously in later versions. This version's job is to make the **machinery** for paid features exist.
Paid-tier features can be added continuously in later versions. This version's job is to make the **machinery** for paid features exist.
## Depends on
@@ -67,7 +67,7 @@ Frontend components:
**If unbound (Free)**:
- Comparison table: Free vs Pro feature list
- Comparison table: Free vs Pro vs Business feature list
- Primary CTA: "Connect to ZPan Cloud" → launches the device-code binding flow
- Short explainer: "Create or sign in to your ZPan Cloud account, then activate Pro there. This instance will mirror your account's Pro status automatically."
@@ -80,25 +80,25 @@ Frontend components:
- Secondary CTA: "Disconnect from Cloud"
- Last-refresh timestamp + manual "Refresh now" button (for support scenarios where a just-completed purchase on cloud should reflect immediately)
### Pro Quota Store
### Business Quota Store
The quota store is a scoped addition to the original v2.6 Pro plan. It gives Pro operators a storage upsell path for their own instance users without making those users create Cloud accounts.
The quota store is a scoped addition to the v2.6 paid-tier plan. It gives Business operators a storage upsell path for their own instance users without making those users create Cloud accounts.
Ownership:
- **Cloud is merchant of record in v2.6.** Cloud owns Stripe Checkout, Stripe subscriptions, Stripe paid webhooks, credit grants, gift-card redemption, usage debit, order state, and webhook retries.
- **The ZPan operator defines the package catalog per instance.** Package name, description, bytes, amount, currency, active state, and sort order are configured in ZPan admin.
- **Terminal buyers do not need Cloud accounts.** ZPan creates checkout requests through the existing Pro Cloud binding and sends the user to Cloud only for checkout handling.
- **Terminal buyers do not need Cloud accounts.** ZPan creates checkout requests through the existing paid-tier Cloud binding and sends the user to Cloud only for checkout handling.
- **Purchased storage and traffic are delivered as Cloud entitlements in v2.6.** ZPan keeps admin base quota separate from Cloud-delivered storage and traffic entitlements, then exposes effective quota as `base + entitlement`. Editing the admin base quota changes only the local base quota. The existing `0` base quota sentinel means unlimited only when no active Cloud entitlement exists for that resource.
- **Monthly storage package subscriptions stay Cloud-owned.** Cloud owns Stripe subscriptions and sends authenticated `order.quota_changed` deliveries only for entitlement-changing subscription states: initial entitlement, positive create/update deltas, renewals that replace the current entitlement period, and terminal reversals. ZPan records deliveries idempotently under a stable subscription entitlement id such as `stripe_subscription:<subscriptionId>:<orgId>` and recomputes effective quota from active entitlements.
- **Metered traffic billing is credit-backed and usage-event based.** Credits are unitless internal ZPan Cloud units, not USD and not Stripe Customer Balance. ZPan increments local monthly traffic usage before issuing a presigned download URL, reports `traffic_egress` usage to Cloud with an idempotency key, and denies the request if Cloud returns an insufficient-credit decision. ZPan does not price or debit credits.
- **Subscriptions and credits are separate.** Stripe subscriptions remain normal USD subscriptions and may include credits per billing period. Free or fully couponed subscriptions still grant included credits when the Cloud subscription or invoice state qualifies. Credits can also come from top-up purchases, gift-card redemption, and admin grants, but credits only pay usage-based charges. They do not pay subscriptions or fixed package purchases.
- **Store Cloud communication is automatic through the Pro binding.** ZPan resolves Cloud from `ZPAN_CLOUD_URL` or the built-in default, authenticates catalog sync, checkout with the bound instance refresh token, and sends normal JSON request bodies that include `boundLicenseId`.
- **Store Cloud communication is automatic through the paid-tier binding.** ZPan resolves Cloud from `ZPAN_CLOUD_URL` or the built-in default, authenticates catalog sync, checkout with the bound instance refresh token, and sends normal JSON request bodies that include `boundLicenseId`.
- **Webhook auth uses Cloud-signed event tokens.** Cloud order quota-change webhook attempts include `x-zpan-cloud-event-token`, a PASETO token signed by Cloud. ZPan verifies it with the built-in Cloud public keys and requires the current issuer, an audience equal to the target instance/license id, the bound license id claim, order quota-change event id, raw-body payload hash, and short expiry.
Admin flow:
1. Pro admin opens `Settings -> Quota Store`
1. Business admin opens `Settings -> Quota Store`
2. Admin enables the store from the operator settings panel
3. Admin creates active storage packages
4. ZPan proxies catalog writes to Cloud through the admin package endpoints using the active Pro binding credentials
@@ -149,7 +149,7 @@ Deliberately excluded from v2.6 (deferred to later Pro versions):
### Site Announcements
Pro operators can publish instance-wide announcements without relying on external channels.
Business operators can publish instance-wide announcements without relying on external channels.
- **Admin page** — create, edit, publish, archive, and delete announcements
- **User surface** — authenticated users can view active announcements and announcement history
@@ -169,9 +169,9 @@ Audit log moved forward into v2.6 as an operator-grade Pro feature.
The v2.6 audit log is the local review surface. External event delivery remains a later webhook feature.
### Retroactive Pro Gates
### Retroactive Paid-Tier Gates
Features shipped in earlier versions that are **operator-grade** (help you run ZPan as a service for others) become Pro-only starting in v2.6. The framework is the same: if you are the administrator of your own instance using ZPan yourself, nothing changes. If you are running ZPan as a platform for unknown third parties, you'll hit these gates.
Features shipped in earlier versions that are **operator-grade** (help you run ZPan as a service for others) become paid-tier gated starting in v2.6. The framework is the same: if you are the administrator of your own instance using ZPan yourself, nothing changes. If you are running ZPan as a platform for unknown third parties, you'll hit these gates.
Three gates in v2.6:
@@ -208,9 +208,9 @@ Everything else from v2.0v2.5 stays Free:
- **In-ZPan subscription billing engine** — storage package subscriptions, metered usage pricing, credit debit, invoices, and payment operations stay on Cloud; ZPan only stores entitlement deliveries, proxies credit reads/redemptions, and reports traffic usage
- **Webhook notifications** — external event delivery is deferred past v2.6
- **Remote download and archive processing** — moved to v2.7; remote download is Community orchestration through user-owned engines, not a Pro Cloud downloader
- **Team / Business tiers** — single Pro SKU only
- **Multi-seat** — one Pro subscription binds one ZPan instance
- **Multi-bind per account** — a single Pro subscription can only bind one active instance. Binding a second instance on the same Pro account requires unbinding the first, or a future Team plan
- **Complex tier packaging** — v2.6 introduces Pro / Business entitlement mechanics, but detailed enterprise packaging continues in later versions
- **Multi-seat** — one paid subscription binds one ZPan instance
- **Multi-bind per account** — a single paid subscription can only bind one active instance. Binding a second instance on the same paid account requires unbinding the first, or a future Team plan
- **Refund automation** — manual via Stripe dashboard on cloud side
- **Chinese local payment (Alipay / WeChat Pay)** — Stripe-only at launch, evaluated post-launch
+129 -46
View File
@@ -1,69 +1,152 @@
# v2.8 — Pro Analytics & Identity
# v2.8 — Admin Analytics & Dashboard
The "enterprise-ready" version. Analytics gives operators the numbers to run a service; SSO and LDAP/SCIM make ZPan buyable by companies that require them.
Build the first real operator dashboard for ZPan admins. This version should
help an instance owner understand usage, growth, cost pressure, and reliability
without querying the database by hand.
All features local to ZPan — no cloud dependency.
v2.8 intentionally stays focused. It does not bundle enterprise identity,
desktop sync, webhooks, or native clients.
## Product Boundary
- **Business** gets the full admin analytics surface and report export.
- **Pro** keeps the existing operator features already shipped in v2.6, such as
audit log, white-label, open registration, and higher free limits.
- **Community** keeps normal file workflows and can continue to see local,
user-facing counters such as share views/downloads where already exposed.
No v2.8 feature requires a managed cloud runtime. ZPan Cloud only supplies the
Business entitlement certificate.
## Current Baseline
ZPan already records enough local data to build a useful first dashboard:
- `activity_events` for file, team, auth, admin, quota, share, and download-task
actions
- `shares.views` and `shares.downloads`
- `download_tasks`, `downloaders`, and `remote_download_usage_reports`
- `org_quotas`, `org_quota_entitlements`, and `cloud_traffic_reports`
- `background_jobs`
v2.8 should first turn these into a coherent admin view. Add new analytics
tables only where a metric cannot be reconstructed cheaply or safely from
existing records.
## Major Features
### Analytics Dashboard
### Admin Overview
Admin-facing dashboard under `Settings → Analytics`.
Add a Business-gated analytics page under Admin.
- **Storage trends** — total used / delta by day / week / month, grouped by file type
- **Bandwidth consumption** — uploads + downloads, day / week / month
- **Popular files** — top N by downloads and by views, filterable by time window
- **Share link analytics** — per-link views, downloads, unique IPs, referrer breakdown
- **User activity** — active users / signups / retention cohorts (for open-registration deployments)
- **API usage** — call counts, error rates, by endpoint
- **Data aggregation** — hourly rollups in a dedicated `analytics_hourly` table, daily/monthly computed from hourly
- **Retention** — raw events 7 days, hourly rollups 90 days, daily rollups forever
- **Export** — any chart as CSV
Top-level cards:
### SSO Enterprise
- Total storage used, quota allocated, and quota utilization
- Download traffic issued by ZPan
- Active users, new signups, active spaces, and team growth
- Share views/downloads and top shared files
- Remote-download task volume, success rate, failure reasons, and downloader
health
- Background job volume and failure rate
Community has basic single-IdP OIDC (v2.1). Pro adds the "sell to companies" tier.
The page should be useful immediately on a small self-hosted instance, even when
there is only one user and one storage backend.
- **Multiple OIDC providers simultaneously** — users see a list of "Sign in with X" options (Azure AD, Google Workspace, Okta, Auth0, etc.)
- **SAML 2.0** — for enterprise IdPs that don't speak OIDC
- **Pre-configured IdP templates** — one-click setup for common IdPs (paste tenant ID, done) instead of 20-field forms
- **Force SSO** — admin can disable username/password login, require SSO for all users
- **Just-in-time provisioning** — user exists in IdP → account auto-created on first login
- **Group → role mapping** — IdP groups mapped to ZPan roles (e.g. `zpan-admin` group → admin role)
- **SSO session management** — single logout, session timeout tied to IdP policy
### Analytics Rollups
### LDAP / SCIM Provisioning
Use additive rollups for metrics that would otherwise become expensive.
- **LDAP sync** (one-way, IdP → ZPan):
- Configurable search base + filter
- Periodic sync (every N minutes) + on-demand "Sync now"
- User create / update / disable based on LDAP state
- Attribute mapping (email, name, avatar)
- **SCIM 2.0 endpoint** (IdP pushes to ZPan):
- Expose `/scim/v2/Users` and `/scim/v2/Groups`
- Bearer-token auth per IdP
- Compatible with Okta, Azure AD SCIM, OneLogin
- **Deprovisioning** — LDAP user disabled → ZPan user disabled (not deleted); files preserved, access revoked
Suggested storage:
- **Hourly rollups** by metric, org, source, target type, and time bucket
- **Daily rollups** derived from hourly rows for long-range charts
Retention defaults:
- Hourly rollups: 180 days
- Daily rollups: kept until explicitly pruned by an admin policy
Do not create a broad raw event pipeline in v2.8 unless a specific chart needs
it. The audit log remains the record-level source for operational events.
### Usage And Cost Views
Operators need to understand where resource pressure comes from:
- Storage used by space and file type
- Quota allocation by base quota vs entitlements
- Traffic usage by storage backend and period
- Credit-backed traffic usage where enabled
- Remote-download usage by downloader and task category
- Over-quota or near-quota spaces
This is a dashboard, not a billing engine. Billing still belongs to Cloud and
the quota-store flow already designed in v2.6.
### Share And Public Access Analytics
The current share detail panel exposes simple counters. v2.8 turns them into an
admin-operable view:
- Top shares by views and downloads
- Share conversion: landing views -> file downloads
- Expired, revoked, and download-limit-hit counts
- Anonymous vs signed-in access where known
- Top public user-homepage items where data is available
Avoid per-recipient behavioral analytics in this version.
### Reliability Views
Give admins a place to diagnose operational health:
- API error rate by route family where locally available
- Background job failure list
- Remote-download task failure reasons
- Downloader heartbeat and offline history
- Cloud entitlement / quota-store webhook failures
- License refresh status
The goal is to answer "what is broken?" before adding deeper observability.
### Report Export
Business operators need portable reports:
- CSV export for visible dashboard tables
- Audit-event export using the existing audit filters
- Download-task and downloader export for troubleshooting
- Quota and entitlement export for support
Large exports should be streamed or job-backed; do not hold large exports in
memory on Workers.
## Non-goals for v2.8
- **Custom RBAC / role builder** — future Pro increment
- **Retention policies / automatic deletion** — future Pro increment
- **Multi-tenant isolation** (many workspaces per instance as hard tenants) — future Pro increment, likely Team tier
- **Analytics API** — read via admin UI only for v2.8
- **MFA for local accounts** — deferred, may ship as separate Community feature
- Outbound event webhooks — future integrations work
- SAML, LDAP, SCIM, or other enterprise / legacy identity protocols
- Folder-level ACLs, custom roles, or complex team governance
- CLI, desktop sync, Flutter, or native client implementation
- Hosted analytics pipeline in ZPan Cloud
- Content moderation, media transcoding, or managed large-file processing
- Replacing audit log; analytics summarizes behavior, audit log remains the
record-level review surface
## User Scenarios
**Series-B startup:**
> We already use Okta for everything. Setting up ZPan took 10 minutes: paste the Okta metadata URL, map the "engineering" group to admin, turn off password login. New hires get ZPan access automatically via Okta group membership.
**Self-hosted operator:**
> I can see storage growth, active users, traffic, failed jobs, and near-quota
> spaces from the admin dashboard.
**Agency running ZPan for multiple projects:**
> The analytics dashboard is how we justify the spend to clients. "Here's traffic to your share links this month, here's the top files." Export to CSV, paste into the monthly report.
**Paid self-hosted instance owner:**
> I know which teams are using the most capacity and which shares are consuming
> the most traffic before users ask for help.
**Enterprise with Active Directory:**
> AD is the source of truth. LDAP sync runs every 5 minutes. When HR offboards someone in AD, their ZPan account is disabled within 5 minutes, files stay where they are for handover.
**Agency sharing files with clients:**
> I export this month's share-link traffic and top downloads as CSV, then attach
> the report to the client invoice.
## v1 Issues Resolved
- **#18** Purchasable storage — already addressed in v2.6 (ZPan Pro), analytics here surfaces usage clearly
- **#18** Purchasable storage — v2.6 ships quota purchases; v2.8 adds the
reporting surface operators need to understand usage and cost.
+147 -24
View File
@@ -1,36 +1,159 @@
# v2.9 — Backup
# v2.9 — Agent CLI
A lightweight CLI agent that backs up local files to S3 via ZPan.
Ship ZPan's command-line tool as an automation client for agents, scripts, and
CI/CD. This CLI is not the foundation for desktop sync. It is a separate client
surface optimized for reliable command execution and machine-readable output.
> Originally planned as v2.6. Deferred behind Pro Launch so the commercial foundation ships first.
## Product Boundary
- **Community** gets the core CLI for personal and self-hosted automation.
- **Pro / Business** may raise API limits or expose operator-only commands when
the underlying feature is paid-tier gated.
- Desktop sync clients do not call this CLI as their internal engine. They use
sync-specific APIs and OS integrations in their own client projects.
## Why CLI Here
Agents and automation need stable verbs:
- upload a file
- list folders
- create a folder
- move / copy / delete objects
- create a share link
- download an object
- inspect quota and task state
These map naturally to shell commands with JSON output. They do not require a
filesystem-provider integration, long-running sync daemon, or platform-specific
desktop packaging.
## Major Features
### zpan-cli (Rust, in monorepo under `native/`)
- **One-way backup** — push local files to S3, local deletions do NOT delete remote copies
- **Incremental** — only upload new or changed files (hash + mtime comparison)
- **Preserve directory structure** — local paths map to remote paths as-is
- **Scheduled runs** — built-in cron (e.g. every night at 2 AM)
- **Directory watching** — optional real-time mode, upload on file change
- **Resumable uploads** — large files use multipart upload, resume on failure
- **Config file** — YAML config for persistent backup jobs
- **Cross-platform** — macOS, Linux, Windows, ARM (NAS friendly)
- **Small binary** — ~5MB static binary, no runtime dependencies
- **Docker image** — mount volume, set env vars, done (for NAS users)
### Command Surface
### ZPan API Additions
- Batch presigned URL endpoint — request upload URLs for many files in one call
- File fingerprint check — client sends hashes, server returns which need uploading
- Backup status visible in web UI
Initial command groups:
```sh
zpan auth login
zpan auth status
zpan auth logout
zpan file ls
zpan file upload
zpan file download
zpan file mkdir
zpan file mv
zpan file cp
zpan file rm
zpan file stat
zpan share create
zpan share list
zpan share revoke
zpan space list
zpan space use
zpan quota get
zpan task list
zpan task watch
```
Command names should be predictable and script-friendly. Prefer explicit flags
over interactive prompts, with optional prompts only for humans running the tool
manually.
### Agent-Friendly Output
The CLI must be easy for coding agents and MCP-style tools to call.
- `--json` for every command that returns data
- Stable exit codes
- Errors printed as structured JSON when `--json` is active
- No progress bars in non-TTY mode
- `--quiet` and `--no-color`
- Idempotency keys for commands that create or mutate resources where useful
- Clear stderr/stdout separation
### Authentication
Supported auth paths:
- Personal API key for simple automation
- Device authorization flow for human bootstrap
- Environment variable token for CI/agent runtime use
- Config profiles for multiple ZPan instances
The CLI should never ask users to paste browser cookies.
### Upload And File Management
The CLI should use the same object upload flow as the browser:
1. Ask ZPan to create an upload draft and presigned instructions.
2. Upload bytes directly to object storage.
3. Complete the upload through ZPan.
It should support:
- single-file upload
- recursive directory upload as an explicit command mode
- conflict strategies: fail, keep both, replace
- target space and folder selection
- resumable multipart upload where the server already supports it
This is file management automation, not backup/sync semantics. Local deletions
must not imply remote deletions unless the user runs an explicit delete command.
### Packaging
The implementation language should be chosen during task planning. The product
contract is more important than the runtime.
Packaging requirements:
- macOS, Linux, Windows
- installable in CI
- easy for agents to locate and execute
- versioned independently enough that agent tooling can pin a known CLI version
If implemented in this repository, keep the CLI isolated from the web frontend
and server runtime so it does not complicate normal ZPan deployment.
### OpenAPI Contract
The CLI should use the public REST/OpenAPI surface, not private frontend helpers.
If an existing route is awkward for CLI use, improve the API contract rather than
special-casing the CLI.
Frontend code continues to use Hono RPC wrappers.
## Non-goals for v2.9
- Desktop sync engine
- Apple File Provider, Windows Cloud Files, Linux filesystem integration
- Flutter, Tauri, Swift, Kotlin, or other graphical clients
- Bidirectional sync or conflict replication
- Enterprise / legacy identity protocols such as LDAP, SCIM, or SAML
- Folder-level ACLs or custom roles
## User Scenarios
**Photographer with a NAS:**
> I have 500GB of photos on my Synology. I run zpan-cli in Docker, it backs up to R2 nightly. If my NAS dies, I browse and download everything from ZPan's web UI.
**Coding agent:**
> The agent uploads a generated artifact, creates a share link, and posts the
> link back to the user without needing browser automation.
**Developer with a laptop:**
> I run `zpan-cli backup --dir ~/Projects --cron "0 2 * * *"`. Every night my projects are backed up. If my laptop gets stolen, I pull everything from ZPan.
**CI release workflow:**
> A GitHub Actions job uploads build artifacts to ZPan and creates a stable share
> link for release notes.
## v1 Issues Resolved
**Power user:**
> I run `zpan file upload ./dist --target /releases/v2.9 --recursive --json` and
> get a machine-readable summary of uploaded files and share URLs.
- **#136** Desktop client — zpan-cli for command line, desktop tray app in v2.10
## Future Work
- MCP server wrapping the CLI or using the same OpenAPI client
- Admin/operator command groups for Business features
- Bulk migration helpers
- Shell completions and package-manager distribution polish