tanabata

Tanabata File Manager β€” Architecture

System-level overview of Tanabata File Manager (TFM). For the product-level requirements see REQUIREMENTS.md; for per-side detail see GO_PROJECT_STRUCTURE.md (backend) and FRONTEND_STRUCTURE.md (frontend). The full HTTP contract lives in openapi.yaml.

System Context

TFM is a multi-user, tag-based web file manager for images and video. It is a single deployable unit β€” one Docker image that serves both the REST API and the built single-page app on one port β€” plus a PostgreSQL database.

                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   Browser / installed  β”‚  Reverse proxy (nginx, TLS)               β”‚
   PWA (desktop/mobile) β”‚  host: 443 β†’ 127.0.0.1:${APP_PORT}        β”‚
        β”‚  HTTPS        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        └─────────────────────────────────────┼──────────────► 127.0.0.1:42776
                                              β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Tanabata container (single image)               β”‚
                    β”‚                                                  β”‚
                    β”‚   Go server (Gin)                                β”‚
                    β”‚    β”œβ”€ /api/v1/*   REST API                       β”‚
                    β”‚    β”œβ”€ /health     liveness                       β”‚
                    β”‚    └─ /*          static SPA + index.html        β”‚
                    β”‚                     fallback                     β”‚
                    β”‚                                                  β”‚
                    β”‚   Disk: /data/files   (originals, name = UUID)   β”‚
                    β”‚         /data/thumbs  (thumbnail/preview cache)  β”‚
                    β”‚         /data/import  (server-side import drop)  β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                              β”‚ pgx (private network)
                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                    β”‚  PostgreSQL 14+   β”‚
                                    β”‚  (bundled or host)β”‚
                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Optional companion process: a one-shot dedup CLI (same image, different entrypoint) that backfills perceptual hashes and rebuilds the duplicate-pairs table. It is not a daemon β€” it is run on demand.

Components

Component Tech Responsibility
Frontend (SPA) SvelteKit (adapter-static, ssr=false), Svelte 5, Tailwind v4, TypeScript UI, client routing, PWA/offline, calls the REST API
API server Go + Gin, Clean Architecture REST API, auth, ACL, business logic, thumbnailing, audit
Database PostgreSQL 14+ (pgx v5, goose) All structured data across 4 schemas / 19 tables
File storage Local disk, flat, keyed by UUID Originals + a regenerable thumbnail/preview cache
dedup CLI Go (same image) Offline perceptual-hash backfill + pairs rescan
Reverse proxy nginx (host, not shipped) TLS termination, large-body/streaming config

Backend Architecture (Clean Architecture)

Dependencies point inward; no layer imports a layer above it.

handler  β†’  service  β†’  port (interfaces)  ←  db/postgres, storage, imagehash
                ↓
              domain (entities, value objects, errors) β€” stdlib only

Wiring is manual in cmd/server/main.go (no DI framework). See GO_PROJECT_STRUCTURE.md for the file-by-file layout, the transaction/context patterns, and the DI sketch.

Request Flow (typical authenticated call)

  1. The SPA sends Authorization: Bearer <access token> to /api/v1/....
  2. Gin middleware runs: security headers β†’ (for /auth) per-IP rate limiter β†’ auth middleware validates the JWT and puts (userID, isAdmin, sessionID) into the request context.
  3. The handler parses/validates input and calls a service method (ctx first arg).
  4. The service enforces ACL via ACLService, performs the use case β€” composing repository calls inside a Transactor.WithTx when several writes must be atomic β€” and writes an audit entry.
  5. Repositories run SQL through pgx (pool or the tx carried in ctx).
  6. The handler serializes the result; domain errors are mapped to { code, message, details? } with the right HTTP status.

Cross-Cutting Concerns

Authentication & sessions

JWT bearer auth. A short-lived access token (15 min default) authorizes API calls; a long-lived refresh token (30 days default) rotates on use and is stored as a hash in activity.sessions. A separate content token (6 h default) is a single-file capability embedded in media URLs so long video keeps streaming past access-token expiry. The /auth endpoints are rate-limited per client IP.

Authorization (ACL)

Private-by-default. Admins see everything; otherwise access requires a public flag, creator ownership, or an explicit grant in acl.permissions (read / edit). All checks are centralized in ACLService and applied before reads and writes.

File storage

Originals are stored flat under FILES_PATH, each named by its file UUID (no directory tree, no original-name collisions). Thumbnails and previews are a regenerable cache under THUMBS_CACHE_PATH: still images via vipsthumbnail (shrink-on-load) with a pure-Go imaging fallback, video frames via ffmpeg; metadata/EXIF via exiftool with a pure-Go fallback. Uploads are rejected unless their sniffed MIME type is whitelisted in core.mime_types.

Near-duplicate detection

Images are dHash-ed (64-bit perceptual hash) inline on upload; video hashes are backfilled by the dedup CLI. A rescan rebuilds data.duplicate_pairs using a BK-tree over Hamming distance (within DUPLICATE_HASH_THRESHOLD), and the API groups pairs into connected-component clusters. Dismissed pairs are remembered so they stop resurfacing. See the duplicate sections in GO_PROJECT_STRUCTURE.md.

Audit logging

User-visible actions (file/tag/category/pool CRUD, relations, ACL changes, auth, session termination, admin user actions) are recorded in activity.audit_log against a seeded set of action types.

Frontend / PWA

Pure client-side SPA: static assets served by the Go binary, with index.html as the fallback for client routes. Installable PWA with a service worker for app-shell caching and optional offline viewing of pinned files. See FRONTEND_STRUCTURE.md.

Data Model

PostgreSQL, four schemas (see backend/migrations/):

Migrations are goose files embedded via go:embed and applied automatically on server startup, so a fresh database bootstraps itself.

Deployment

See DEPLOY.md for the production deploy (Gitea Actions β†’ host) and the reverse-proxy notes in README.md.

Design Constraints & Future Direction