System Architecture
Components
API Server (cmd/api/)
HTTP server built with Gin. Handles all client requests:
| Layer | Package | Responsibility |
|---|---|---|
| Handlers | httpapi/handlers/ | Request parsing, validation, response formatting |
| Middleware | httpapi/middleware/ | Auth, rate limiting, timeouts, logging, panic recovery |
| Auth | auth/ | JWT minting/validation, refresh token rotation, OAuth verification |
| Sync | syncstore/ | Delta sync with cursor-based pagination and tombstones |
| URL | urlcanon/ | URL normalization (strip tracking, lowercase domain) |
| Search | via handlers | Hybrid search using FTS + trigram + pgvector |
| Real-time | realtime/ | WebSocket hub, Postgres LISTEN integration |
Enrichment Worker (cmd/worker/)
Background process that picks up pending enrichment jobs and runs the AI pipeline:
- Poll for
pendingjobs in the database - Fetch page content (platform-aware: YouTube, TikTok, X, etc.)
- Run AI enrichment (Gemini) or heuristic fallback
- Write results back to the bookmark record
- Cache results globally by normalized URL
PostgreSQL
Single Postgres 16 instance with extensions:| Extension | Purpose |
|---|---|
| pgvector | Semantic search via vector embeddings |
| pg_trgm | Fuzzy text matching (trigram similarity) |
| Built-in FTS | Full-text search with stemming and ranking |
Migration Tool (cmd/migrate/)
Database migrations via Goose v3. SQL migration files in migrations/.
Data Model
Design Principles
Offline-First
Clients save locally first, sync when connected. Delta sync with cursors ensures no data is lost.
Server-Authoritative Enrichment
AI-generated fields (summary, tags, saveWhy) are always set by the server. Clients never write these fields.
Last-Write-Wins
Conflict resolution is simple: the server stamps
updated_at = now() on every write. Most recent write wins.Soft Deletes
All deletes are soft (tombstones). Required for delta sync — clients need to know what was deleted.
Request Lifecycle
Every API request flows through:- OpenTelemetry — trace propagation
- Request ID — unique ID for tracing (
X-Request-Idheader) - Access Log — structured JSON logging
- Panic Recovery — catches panics, returns
500JSON error - Timeout — 30-second deadline for all v1 endpoints
- Rate Limit — IP-based for auth endpoints (2 req/s burst 10)
- Auth — JWT validation, user context injection
- Handler — business logic