Initialize eng-pad-server with project documentation
- README.md: project overview, quick start, build commands - CLAUDE.md: AI dev context, source tree, key conventions - ARCHITECTURE.md: full system spec covering data model, auth (password + FIDO2/U2F), gRPC sync API, REST API, SVG/JPG/PDF rendering, web UI, configuration, deployment, security - PROJECT_PLAN.md: 11 phases with discrete checkboxable steps - PROGRESS.md: decision log and completion tracking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
313
ARCHITECTURE.md
Normal file
313
ARCHITECTURE.md
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
# ARCHITECTURE.md — eng-pad-server
|
||||||
|
|
||||||
|
## 1. System Overview
|
||||||
|
|
||||||
|
eng-pad-server is a read-only sync and viewing service for eng-pad
|
||||||
|
engineering notebooks. The Android app is the sole writer; the server
|
||||||
|
receives complete notebook data via gRPC and serves it through a web UI.
|
||||||
|
|
||||||
|
```
|
||||||
|
Android App eng-pad-server
|
||||||
|
+-----------+ gRPC/TLS +------------------+
|
||||||
|
| eng-pad | ───────────────> | Sync Service |
|
||||||
|
| (writer) | username/pass | (gRPC :9443) |
|
||||||
|
+-----------+ in metadata +------------------+
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
+──────────────+
|
||||||
|
│ SQLite DB │
|
||||||
|
+──────────────+
|
||||||
|
│
|
||||||
|
┌────────────┼────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
REST API Web UI Share Links
|
||||||
|
(:8443) (:8080) (/s/:token)
|
||||||
|
JSON htmx/SVG No auth
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Data Model
|
||||||
|
|
||||||
|
### Users
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| id | INTEGER PK | Auto-increment |
|
||||||
|
| username | TEXT UNIQUE | Login identifier |
|
||||||
|
| password_hash | TEXT | Argon2id hash |
|
||||||
|
| created_at | INTEGER | Epoch millis |
|
||||||
|
| updated_at | INTEGER | Epoch millis |
|
||||||
|
|
||||||
|
### WebAuthn Credentials
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| id | INTEGER PK | Auto-increment |
|
||||||
|
| user_id | INTEGER FK | References users(id) CASCADE |
|
||||||
|
| credential_id | BLOB UNIQUE | WebAuthn credential ID |
|
||||||
|
| public_key | BLOB | COSE public key |
|
||||||
|
| name | TEXT | User-assigned label |
|
||||||
|
| sign_count | INTEGER | Signature counter |
|
||||||
|
| created_at | INTEGER | Epoch millis |
|
||||||
|
|
||||||
|
### Notebooks
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| id | INTEGER PK | Auto-increment |
|
||||||
|
| user_id | INTEGER FK | References users(id) CASCADE |
|
||||||
|
| remote_id | INTEGER | App-side notebook ID |
|
||||||
|
| title | TEXT | Notebook title |
|
||||||
|
| page_size | TEXT | "REGULAR" or "LARGE" |
|
||||||
|
| synced_at | INTEGER | Last sync epoch millis |
|
||||||
|
|
||||||
|
UNIQUE(user_id, remote_id)
|
||||||
|
|
||||||
|
### Pages
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| id | INTEGER PK | Auto-increment |
|
||||||
|
| notebook_id | INTEGER FK | References notebooks(id) CASCADE |
|
||||||
|
| remote_id | INTEGER | App-side page ID |
|
||||||
|
| page_number | INTEGER | 1-based page number |
|
||||||
|
|
||||||
|
UNIQUE(notebook_id, remote_id)
|
||||||
|
|
||||||
|
### Strokes
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| id | INTEGER PK | Auto-increment |
|
||||||
|
| page_id | INTEGER FK | References pages(id) CASCADE |
|
||||||
|
| pen_size | REAL | Width in canonical points (300 DPI) |
|
||||||
|
| color | INTEGER | ARGB packed int |
|
||||||
|
| style | TEXT | "plain", "dashed", "arrow", "double_arrow" |
|
||||||
|
| point_data | BLOB | Packed LE floats: [x0,y0,x1,y1,...] |
|
||||||
|
| stroke_order | INTEGER | Z-order within page |
|
||||||
|
|
||||||
|
### Share Links
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| id | INTEGER PK | Auto-increment |
|
||||||
|
| notebook_id | INTEGER FK | References notebooks(id) CASCADE |
|
||||||
|
| token | TEXT UNIQUE | 32-byte random, URL-safe base64 |
|
||||||
|
| expires_at | INTEGER | Epoch millis, NULL = never |
|
||||||
|
| created_at | INTEGER | Epoch millis |
|
||||||
|
|
||||||
|
## 3. Authentication
|
||||||
|
|
||||||
|
### gRPC (Android App Sync)
|
||||||
|
|
||||||
|
- Username and password sent in gRPC metadata on every RPC
|
||||||
|
- Unary interceptor verifies against Argon2id hash
|
||||||
|
- TLS required — plaintext rejected
|
||||||
|
- No tokens, no login RPC
|
||||||
|
|
||||||
|
### Web UI (Browser)
|
||||||
|
|
||||||
|
- `POST /v1/auth/login` — password → bearer token
|
||||||
|
- Token in `HttpOnly; Secure; SameSite=Strict` cookie
|
||||||
|
- 24h TTL, SHA-256 keyed lookup with cache
|
||||||
|
|
||||||
|
### FIDO2/U2F (Web UI Only)
|
||||||
|
|
||||||
|
- Register keys after password login
|
||||||
|
- Login with key as password alternative
|
||||||
|
- Multiple keys per user, user-assigned labels
|
||||||
|
- `go-webauthn/webauthn` library
|
||||||
|
|
||||||
|
### Shareable Links
|
||||||
|
|
||||||
|
- Token in URL, no auth required
|
||||||
|
- 32-byte `crypto/rand`, URL-safe base64
|
||||||
|
- Optional expiry (default: never)
|
||||||
|
- Expired → 410 Gone
|
||||||
|
- Revocable via gRPC API or web UI
|
||||||
|
|
||||||
|
## 4. gRPC API
|
||||||
|
|
||||||
|
Service: `engpad.v1.EngPadSync`
|
||||||
|
|
||||||
|
| RPC | Description | Auth |
|
||||||
|
|-----|-------------|------|
|
||||||
|
| SyncNotebook | Push complete notebook (upsert) | user/pass |
|
||||||
|
| DeleteNotebook | Remove notebook from server | user/pass |
|
||||||
|
| ListNotebooks | List user's synced notebooks | user/pass |
|
||||||
|
| CreateShareLink | Generate shareable URL | user/pass |
|
||||||
|
| RevokeShareLink | Invalidate a share link | user/pass |
|
||||||
|
| ListShareLinks | List links for a notebook | user/pass |
|
||||||
|
|
||||||
|
### Sync Semantics
|
||||||
|
|
||||||
|
`SyncNotebook` is a full replacement: all pages and strokes for the
|
||||||
|
notebook are deleted and re-inserted. The server mirrors exactly what
|
||||||
|
the tablet has. No incremental sync, no conflict resolution.
|
||||||
|
|
||||||
|
Keyed by (user_id, remote_id) where remote_id is the app-side
|
||||||
|
notebook ID.
|
||||||
|
|
||||||
|
## 5. REST API
|
||||||
|
|
||||||
|
### Auth Endpoints
|
||||||
|
|
||||||
|
| Method | Path | Auth | Description |
|
||||||
|
|--------|------|------|-------------|
|
||||||
|
| POST | /v1/auth/login | None | Password login → token |
|
||||||
|
| POST | /v1/auth/webauthn/register/begin | Bearer | Start key registration |
|
||||||
|
| POST | /v1/auth/webauthn/register/finish | Bearer | Complete registration |
|
||||||
|
| POST | /v1/auth/webauthn/login/begin | None | Start key login |
|
||||||
|
| POST | /v1/auth/webauthn/login/finish | None | Complete key login |
|
||||||
|
|
||||||
|
### Notebook Endpoints
|
||||||
|
|
||||||
|
| Method | Path | Auth | Description |
|
||||||
|
|--------|------|------|-------------|
|
||||||
|
| GET | /v1/notebooks | Bearer | List notebooks |
|
||||||
|
| GET | /v1/notebooks/:id | Bearer | Notebook + page list |
|
||||||
|
| GET | /v1/notebooks/:id/pages/:num/svg | Bearer | Page as SVG |
|
||||||
|
| GET | /v1/notebooks/:id/pages/:num/jpg | Bearer | Page as JPG (300 DPI) |
|
||||||
|
| GET | /v1/notebooks/:id/pdf | Bearer | Full notebook PDF |
|
||||||
|
|
||||||
|
### Share Endpoints
|
||||||
|
|
||||||
|
| Method | Path | Auth | Description |
|
||||||
|
|--------|------|------|-------------|
|
||||||
|
| GET | /s/:token | None | Notebook view |
|
||||||
|
| GET | /s/:token/pages/:num/svg | None | Page SVG |
|
||||||
|
| GET | /s/:token/pages/:num/jpg | None | Page JPG |
|
||||||
|
| GET | /s/:token/pdf | None | Notebook PDF |
|
||||||
|
|
||||||
|
## 6. Rendering
|
||||||
|
|
||||||
|
### SVG
|
||||||
|
|
||||||
|
Strokes rendered as SVG `<path>` elements. Coordinates scaled from
|
||||||
|
300 DPI to 72 DPI (×0.24) for standard SVG/PDF point units.
|
||||||
|
|
||||||
|
- `stroke-linecap="round"`, `stroke-linejoin="round"`
|
||||||
|
- Dashed strokes: `stroke-dasharray="7.2 4.8"`
|
||||||
|
- Arrow heads: separate `<line>` elements
|
||||||
|
- No grid — grid is a tablet writing aid only
|
||||||
|
- `viewBox` matches physical page dimensions in points
|
||||||
|
|
||||||
|
### JPG
|
||||||
|
|
||||||
|
Server-side rasterization at 300 DPI using Go's `image` package.
|
||||||
|
White background, strokes rendered with the same coordinate system.
|
||||||
|
|
||||||
|
### PDF
|
||||||
|
|
||||||
|
Generated with a Go PDF library. Coordinates in 72 DPI (native PDF
|
||||||
|
points). One page per notebook page.
|
||||||
|
|
||||||
|
## 7. Web Interface
|
||||||
|
|
||||||
|
Built with Go `html/template` + htmx. Embedded via `//go:embed`.
|
||||||
|
|
||||||
|
### Pages
|
||||||
|
|
||||||
|
| Route | Template | Description |
|
||||||
|
|-------|----------|-------------|
|
||||||
|
| /login | login.html | Login form (password + WebAuthn) |
|
||||||
|
| /notebooks | notebooks.html | Notebook list |
|
||||||
|
| /notebooks/:id | notebook.html | Page grid with thumbnails |
|
||||||
|
| /notebooks/:id/pages/:num | page.html | Full page SVG viewer |
|
||||||
|
| /s/:token | notebook.html | Shared notebook (no auth) |
|
||||||
|
| /s/:token/pages/:num | page.html | Shared page viewer |
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- CSRF via signed double-submit cookies
|
||||||
|
- Session cookie: HttpOnly, Secure, SameSite=Strict
|
||||||
|
- html/template auto-escaping
|
||||||
|
|
||||||
|
## 8. Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[server]
|
||||||
|
listen_addr = ":8443"
|
||||||
|
grpc_addr = ":9443"
|
||||||
|
tls_cert = "/srv/eng-pad-server/certs/cert.pem"
|
||||||
|
tls_key = "/srv/eng-pad-server/certs/key.pem"
|
||||||
|
|
||||||
|
[web]
|
||||||
|
listen_addr = ":8080"
|
||||||
|
base_url = "https://pad.metacircular.net"
|
||||||
|
|
||||||
|
[database]
|
||||||
|
path = "/srv/eng-pad-server/eng-pad-server.db"
|
||||||
|
|
||||||
|
[auth]
|
||||||
|
token_ttl = "24h"
|
||||||
|
argon2_memory = 65536
|
||||||
|
argon2_time = 3
|
||||||
|
argon2_threads = 4
|
||||||
|
|
||||||
|
[webauthn]
|
||||||
|
rp_display_name = "Engineering Pad"
|
||||||
|
rp_id = "pad.metacircular.net"
|
||||||
|
rp_origins = ["https://pad.metacircular.net"]
|
||||||
|
|
||||||
|
[log]
|
||||||
|
level = "info"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Deployment
|
||||||
|
|
||||||
|
### Container
|
||||||
|
|
||||||
|
Multi-stage Docker build:
|
||||||
|
1. Builder: `golang:1.25-alpine`, `CGO_ENABLED=0`, stripped binary
|
||||||
|
2. Runtime: `alpine:latest`, non-root user
|
||||||
|
|
||||||
|
### systemd
|
||||||
|
|
||||||
|
| Unit | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| eng-pad-server.service | Main service |
|
||||||
|
| eng-pad-server-backup.service | Oneshot backup |
|
||||||
|
| eng-pad-server-backup.timer | Daily 02:00 UTC |
|
||||||
|
|
||||||
|
Security hardening: NoNewPrivileges, ProtectSystem=strict,
|
||||||
|
ReadWritePaths=/srv/eng-pad-server.
|
||||||
|
|
||||||
|
### Data Directory
|
||||||
|
|
||||||
|
```
|
||||||
|
/srv/eng-pad-server/
|
||||||
|
├── eng-pad-server.toml
|
||||||
|
├── eng-pad-server.db
|
||||||
|
├── certs/
|
||||||
|
│ ├── cert.pem
|
||||||
|
│ └── key.pem
|
||||||
|
└── backups/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Security
|
||||||
|
|
||||||
|
- TLS 1.3 minimum, no fallback
|
||||||
|
- Argon2id for password hashing
|
||||||
|
- `crypto/rand` for all tokens and nonces
|
||||||
|
- `crypto/subtle` for constant-time comparisons
|
||||||
|
- No secrets in logs
|
||||||
|
- Default deny: unauthenticated requests rejected
|
||||||
|
- Share links: scoped to single notebook, optional expiry, revocable
|
||||||
|
- Graceful shutdown: SIGINT/SIGTERM → drain → close DB → exit
|
||||||
|
|
||||||
|
## 11. CLI Commands
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| server | Start the service |
|
||||||
|
| init | Create database, first user |
|
||||||
|
| snapshot | Database backup (VACUUM INTO) |
|
||||||
|
| status | Health check |
|
||||||
|
|
||||||
|
## 12. Future Work
|
||||||
|
|
||||||
|
- MCIAS integration for auth delegation
|
||||||
|
- Per-page share links (URL structure already supports it)
|
||||||
|
- Notebook version history (store previous syncs)
|
||||||
|
- WebSocket notifications for real-time sync status
|
||||||
|
- Thumbnail generation for notebook list
|
||||||
131
CLAUDE.md
Normal file
131
CLAUDE.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
eng-pad-server is a Go service that receives engineering notebook data from the eng-pad Android app via gRPC, stores it in SQLite, and serves read-only views through a web UI. It supports password + FIDO2/U2F authentication and shareable links.
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make all # vet → lint → test → build
|
||||||
|
make eng-pad-server # build binary with version injection
|
||||||
|
make build # compile all packages
|
||||||
|
make test # run tests
|
||||||
|
make vet # go vet
|
||||||
|
make lint # golangci-lint
|
||||||
|
make proto # regenerate gRPC code
|
||||||
|
make proto-lint # buf lint + breaking changes
|
||||||
|
make clean # remove binary
|
||||||
|
|
||||||
|
# Run a single test:
|
||||||
|
go test -run TestFunctionName ./internal/...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- **Go 1.25+**, pure-Go dependencies, `CGO_ENABLED=0`
|
||||||
|
- **gRPC API**: receives notebook sync from the Android app (password auth per-request over TLS)
|
||||||
|
- **REST API**: JSON over HTTPS for web viewing, auth endpoints
|
||||||
|
- **Web UI**: Go `html/template` + htmx, SVG page rendering
|
||||||
|
- **SQLite**: via `modernc.org/sqlite`, WAL mode, foreign keys
|
||||||
|
- **Auth**: Argon2id passwords + FIDO2/U2F via `go-webauthn/webauthn`
|
||||||
|
- **Router**: chi (lightweight, stdlib-compatible)
|
||||||
|
|
||||||
|
## Project Documents
|
||||||
|
|
||||||
|
- **ARCHITECTURE.md** — full system specification
|
||||||
|
- **PROJECT_PLAN.md** — implementation steps with checkboxes
|
||||||
|
- **PROGRESS.md** — completion tracking and decisions
|
||||||
|
|
||||||
|
**Keep PROJECT_PLAN.md and PROGRESS.md in sync.**
|
||||||
|
|
||||||
|
## Source Tree
|
||||||
|
|
||||||
|
```
|
||||||
|
eng-pad-server/
|
||||||
|
├── cmd/
|
||||||
|
│ └── eng-pad-server/ CLI entry point (cobra)
|
||||||
|
│ ├── main.go
|
||||||
|
│ ├── server.go server subcommand
|
||||||
|
│ └── init.go init subcommand
|
||||||
|
├── internal/
|
||||||
|
│ ├── auth/
|
||||||
|
│ │ ├── argon2.go Password hashing
|
||||||
|
│ │ ├── tokens.go Bearer token generation/validation
|
||||||
|
│ │ └── webauthn.go FIDO2/U2F integration
|
||||||
|
│ ├── config/
|
||||||
|
│ │ └── config.go TOML configuration
|
||||||
|
│ ├── db/
|
||||||
|
│ │ ├── db.go Database setup, pragmas
|
||||||
|
│ │ └── migrations.go Schema migrations
|
||||||
|
│ ├── grpcserver/
|
||||||
|
│ │ ├── server.go gRPC server setup
|
||||||
|
│ │ ├── sync.go SyncNotebook handler
|
||||||
|
│ │ ├── share.go Share link RPCs
|
||||||
|
│ │ └── interceptors.go Auth interceptor
|
||||||
|
│ ├── server/
|
||||||
|
│ │ ├── server.go REST server setup
|
||||||
|
│ │ ├── routes.go Route registration
|
||||||
|
│ │ ├── auth.go Login/register endpoints
|
||||||
|
│ │ ├── notebooks.go Notebook/page endpoints
|
||||||
|
│ │ └── middleware.go Auth middleware
|
||||||
|
│ ├── render/
|
||||||
|
│ │ ├── svg.go Page → SVG rendering
|
||||||
|
│ │ ├── jpg.go Page → JPG rendering
|
||||||
|
│ │ └── pdf.go Notebook → PDF rendering
|
||||||
|
│ ├── share/
|
||||||
|
│ │ └── share.go Share link token management
|
||||||
|
│ └── webserver/
|
||||||
|
│ ├── server.go Web UI server setup
|
||||||
|
│ ├── routes.go Template routes
|
||||||
|
│ └── handlers.go htmx handlers
|
||||||
|
├── proto/engpad/
|
||||||
|
│ └── v1/
|
||||||
|
│ └── sync.proto gRPC service definition
|
||||||
|
├── gen/engpad/
|
||||||
|
│ └── v1/ Generated Go gRPC code
|
||||||
|
├── web/
|
||||||
|
│ ├── embed.go //go:embed directive
|
||||||
|
│ ├── templates/
|
||||||
|
│ │ ├── layout.html Shared HTML skeleton
|
||||||
|
│ │ ├── login.html Login page
|
||||||
|
│ │ ├── notebooks.html Notebook list
|
||||||
|
│ │ ├── notebook.html Single notebook view
|
||||||
|
│ │ └── page.html Page viewer (SVG embed)
|
||||||
|
│ └── static/
|
||||||
|
│ └── htmx.min.js
|
||||||
|
├── deploy/
|
||||||
|
│ ├── docker/
|
||||||
|
│ │ └── docker-compose.yml
|
||||||
|
│ ├── systemd/
|
||||||
|
│ │ ├── eng-pad-server.service
|
||||||
|
│ │ └── eng-pad-server-backup.timer
|
||||||
|
│ ├── scripts/
|
||||||
|
│ │ └── install.sh
|
||||||
|
│ └── examples/
|
||||||
|
│ └── eng-pad-server.toml
|
||||||
|
├── Dockerfile
|
||||||
|
├── Makefile
|
||||||
|
├── buf.yaml
|
||||||
|
├── .golangci.yaml
|
||||||
|
├── .gitignore
|
||||||
|
├── CLAUDE.md This file
|
||||||
|
├── README.md
|
||||||
|
├── ARCHITECTURE.md Full system spec
|
||||||
|
├── PROJECT_PLAN.md Implementation steps
|
||||||
|
└── PROGRESS.md Completion tracking
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Conventions
|
||||||
|
|
||||||
|
- Stroke point data: packed little-endian floats `[x0,y0,x1,y1,...]` — same binary format as the Android app
|
||||||
|
- Canonical coordinates: 300 DPI. Scaled to 72 DPI for SVG/PDF output (×0.24)
|
||||||
|
- Page sizes: REGULAR (2550×3300 pts), LARGE (3300×5100 pts)
|
||||||
|
- Stroke styles: "plain", "dashed", "arrow", "double_arrow"
|
||||||
|
- No grid rendering — grid is a tablet writing aid only
|
||||||
|
- Share link tokens: 32-byte `crypto/rand`, URL-safe base64
|
||||||
|
- gRPC auth: username+password in metadata, verified per-request
|
||||||
|
- Web auth: password login → bearer token in session cookie
|
||||||
|
- TLS 1.3 minimum, no exceptions
|
||||||
27
PROGRESS.md
Normal file
27
PROGRESS.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# PROGRESS.md — eng-pad-server Implementation Progress
|
||||||
|
|
||||||
|
This file tracks completed work and decisions. Updated after every step.
|
||||||
|
See PROJECT_PLAN.md for the full step list.
|
||||||
|
|
||||||
|
## Completed
|
||||||
|
|
||||||
|
_(none yet)_
|
||||||
|
|
||||||
|
## In Progress
|
||||||
|
|
||||||
|
Phase 0: Project Setup
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- **Language**: Go (Metacircular standard)
|
||||||
|
- **Database**: SQLite via `modernc.org/sqlite` (pure Go, no CGo)
|
||||||
|
- **Auth**: Argon2id passwords + FIDO2/U2F via `go-webauthn/webauthn`
|
||||||
|
- **gRPC auth**: username/password in metadata per-request (no tokens)
|
||||||
|
- **Web auth**: password → bearer token in session cookie
|
||||||
|
- **Rendering**: SVG for web viewing, JPG/PDF for export
|
||||||
|
- **Sync model**: full notebook replacement (upsert), no incremental sync
|
||||||
|
- **Share links**: 32-byte random token, optional expiry, scoped to notebook
|
||||||
|
- **Grid**: not rendered server-side (tablet writing aid only)
|
||||||
|
- **Coordinate system**: 300 DPI canonical, scaled to 72 DPI for SVG/PDF
|
||||||
|
- **FIDO2/U2F**: web UI login only, not gRPC sync
|
||||||
|
- **Server is read-only**: mirrors tablet exactly, no content modification
|
||||||
148
PROJECT_PLAN.md
Normal file
148
PROJECT_PLAN.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# PROJECT_PLAN.md — eng-pad-server Implementation Steps
|
||||||
|
|
||||||
|
This file tracks all implementation steps. Check off steps as they are
|
||||||
|
completed and log them in PROGRESS.md.
|
||||||
|
|
||||||
|
## Phase 0: Project Setup
|
||||||
|
|
||||||
|
- [ ] 0.1: Initialize Go module (`git.wntrmute.dev/kyle/eng-pad-server`)
|
||||||
|
- [ ] 0.2: Create Makefile with standard targets
|
||||||
|
- [ ] 0.3: Configure `.golangci.yaml`
|
||||||
|
- [ ] 0.4: Create `.gitignore`
|
||||||
|
- [ ] 0.5: Create example config `deploy/examples/eng-pad-server.toml`
|
||||||
|
- **Verify:** `make build`
|
||||||
|
|
||||||
|
## Phase 1: Database + Config
|
||||||
|
|
||||||
|
- [ ] 1.1: TOML config loading
|
||||||
|
- `internal/config/config.go`
|
||||||
|
- [ ] 1.2: SQLite database setup (WAL, foreign keys, busy timeout)
|
||||||
|
- `internal/db/db.go`
|
||||||
|
- [ ] 1.3: Schema migrations (users, notebooks, pages, strokes, share_links, webauthn_credentials)
|
||||||
|
- `internal/db/migrations.go`
|
||||||
|
- [ ] 1.4: Unit tests for migrations
|
||||||
|
- **Verify:** `make test`
|
||||||
|
|
||||||
|
## Phase 2: Auth — Password
|
||||||
|
|
||||||
|
- [ ] 2.1: Argon2id password hashing + verification
|
||||||
|
- `internal/auth/argon2.go`
|
||||||
|
- [ ] 2.2: Bearer token generation, storage, validation
|
||||||
|
- `internal/auth/tokens.go`
|
||||||
|
- [ ] 2.3: User creation (for `init` command)
|
||||||
|
- [ ] 2.4: Unit tests for auth
|
||||||
|
- **Verify:** `make test`
|
||||||
|
|
||||||
|
## Phase 3: CLI
|
||||||
|
|
||||||
|
- [ ] 3.1: Cobra CLI scaffold
|
||||||
|
- `cmd/eng-pad-server/main.go`
|
||||||
|
- [ ] 3.2: `init` command — create DB, prompt for admin user
|
||||||
|
- `cmd/eng-pad-server/init.go`
|
||||||
|
- [ ] 3.3: `server` command — start gRPC + REST + web servers
|
||||||
|
- `cmd/eng-pad-server/server.go`
|
||||||
|
- [ ] 3.4: `snapshot` command — VACUUM INTO backup
|
||||||
|
- [ ] 3.5: `status` command — health check
|
||||||
|
- **Verify:** `make all && ./eng-pad-server init`
|
||||||
|
|
||||||
|
## Phase 4: gRPC Sync Service
|
||||||
|
|
||||||
|
- [ ] 4.1: Proto definitions
|
||||||
|
- `proto/engpad/v1/sync.proto`
|
||||||
|
- [ ] 4.2: Generate Go code
|
||||||
|
- `make proto`
|
||||||
|
- [ ] 4.3: gRPC server setup with TLS
|
||||||
|
- `internal/grpcserver/server.go`
|
||||||
|
- [ ] 4.4: Auth interceptor (username/password from metadata)
|
||||||
|
- `internal/grpcserver/interceptors.go`
|
||||||
|
- [ ] 4.5: SyncNotebook handler (upsert: delete + re-insert)
|
||||||
|
- `internal/grpcserver/sync.go`
|
||||||
|
- [ ] 4.6: DeleteNotebook handler
|
||||||
|
- [ ] 4.7: ListNotebooks handler
|
||||||
|
- [ ] 4.8: Unit tests for sync
|
||||||
|
- **Verify:** `make test` + manual gRPC test with `grpcurl`
|
||||||
|
|
||||||
|
## Phase 5: Rendering
|
||||||
|
|
||||||
|
- [ ] 5.1: SVG rendering — strokes to SVG path elements
|
||||||
|
- `internal/render/svg.go`
|
||||||
|
- [ ] 5.2: JPG rendering — rasterize page at 300 DPI
|
||||||
|
- `internal/render/jpg.go`
|
||||||
|
- [ ] 5.3: PDF rendering — notebook to multi-page PDF
|
||||||
|
- `internal/render/pdf.go`
|
||||||
|
- [ ] 5.4: Unit tests — verify SVG output, JPG dimensions, PDF page count
|
||||||
|
- **Verify:** `make test`
|
||||||
|
|
||||||
|
## Phase 6: REST API
|
||||||
|
|
||||||
|
- [ ] 6.1: chi router setup with TLS
|
||||||
|
- `internal/server/server.go`, `routes.go`
|
||||||
|
- [ ] 6.2: Auth middleware (bearer token validation)
|
||||||
|
- `internal/server/middleware.go`
|
||||||
|
- [ ] 6.3: Login endpoint
|
||||||
|
- `internal/server/auth.go`
|
||||||
|
- [ ] 6.4: Notebook/page endpoints (JSON metadata)
|
||||||
|
- `internal/server/notebooks.go`
|
||||||
|
- [ ] 6.5: Rendering endpoints (SVG, JPG, PDF)
|
||||||
|
- [ ] 6.6: Unit tests for API
|
||||||
|
- **Verify:** `make test` + manual curl
|
||||||
|
|
||||||
|
## Phase 7: Share Links
|
||||||
|
|
||||||
|
- [ ] 7.1: Token generation + storage
|
||||||
|
- `internal/share/share.go`
|
||||||
|
- [ ] 7.2: gRPC RPCs — CreateShareLink, RevokeShareLink, ListShareLinks
|
||||||
|
- `internal/grpcserver/share.go`
|
||||||
|
- [ ] 7.3: REST endpoints — /s/:token routes
|
||||||
|
- [ ] 7.4: Expiry enforcement (check on access, periodic cleanup)
|
||||||
|
- [ ] 7.5: Unit tests
|
||||||
|
- **Verify:** `make test`
|
||||||
|
|
||||||
|
## Phase 8: Web UI
|
||||||
|
|
||||||
|
- [ ] 8.1: Template skeleton — layout.html, navigation
|
||||||
|
- `web/templates/layout.html`
|
||||||
|
- [ ] 8.2: Login page (password + WebAuthn)
|
||||||
|
- `web/templates/login.html`
|
||||||
|
- [ ] 8.3: Notebook list page
|
||||||
|
- `web/templates/notebooks.html`
|
||||||
|
- [ ] 8.4: Notebook view page (page grid with SVG thumbnails)
|
||||||
|
- `web/templates/notebook.html`
|
||||||
|
- [ ] 8.5: Page viewer (embedded SVG, export buttons)
|
||||||
|
- `web/templates/page.html`
|
||||||
|
- [ ] 8.6: Shared notebook/page views (same templates, no auth chrome)
|
||||||
|
- [ ] 8.7: Web server setup + embed
|
||||||
|
- `internal/webserver/`, `web/embed.go`
|
||||||
|
- **Verify:** manual browser test
|
||||||
|
|
||||||
|
## Phase 9: FIDO2/U2F (WebAuthn)
|
||||||
|
|
||||||
|
- [ ] 9.1: WebAuthn integration with `go-webauthn/webauthn`
|
||||||
|
- `internal/auth/webauthn.go`
|
||||||
|
- [ ] 9.2: Registration endpoints (begin/finish)
|
||||||
|
- [ ] 9.3: Login endpoints (begin/finish)
|
||||||
|
- [ ] 9.4: Key management UI (list keys, add key, remove key)
|
||||||
|
- [ ] 9.5: Unit tests
|
||||||
|
- **Verify:** manual test with security key
|
||||||
|
|
||||||
|
## Phase 10: Deployment
|
||||||
|
|
||||||
|
- [ ] 10.1: Dockerfile (multi-stage, non-root)
|
||||||
|
- [ ] 10.2: systemd units (service, backup timer)
|
||||||
|
- `deploy/systemd/`
|
||||||
|
- [ ] 10.3: Install script
|
||||||
|
- `deploy/scripts/install.sh`
|
||||||
|
- [ ] 10.4: Graceful shutdown (SIGINT/SIGTERM)
|
||||||
|
- **Verify:** `make docker && docker run`
|
||||||
|
|
||||||
|
## Phase 11: Android App Sync Integration
|
||||||
|
|
||||||
|
_(Implemented in the eng-pad repo, not here)_
|
||||||
|
|
||||||
|
- [ ] 11.1: gRPC client dependency (protobuf-lite)
|
||||||
|
- [ ] 11.2: SyncClient.kt — gRPC channel + stub
|
||||||
|
- [ ] 11.3: SyncManager.kt — serialize notebook to proto, call sync
|
||||||
|
- [ ] 11.4: Sync settings screen (server URL, username, password)
|
||||||
|
- [ ] 11.5: Notebook overflow menu — "Sync to server"
|
||||||
|
- [ ] 11.6: Library — "Sync all" button
|
||||||
|
- [ ] 11.7: Sync status indicator on notebook cards
|
||||||
53
README.md
Normal file
53
README.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
eng-pad-server
|
||||||
|
==============
|
||||||
|
|
||||||
|
Read-only sync and web viewer for [eng-pad](https://git.wntrmute.dev/kyle/eng-pad)
|
||||||
|
engineering notebooks.
|
||||||
|
|
||||||
|
The Android app pushes complete notebooks to this server via gRPC. The
|
||||||
|
server stores them and serves read-only views through a web UI with
|
||||||
|
SVG rendering. Shareable links allow unauthenticated access to specific
|
||||||
|
notebooks.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **gRPC sync**: receive notebook data from the Android app over TLS
|
||||||
|
- **Web viewer**: browse notebooks, view pages as SVG, export JPG/PDF
|
||||||
|
- **Authentication**: password (Argon2id) + FIDO2/U2F security keys
|
||||||
|
- **Shareable links**: token-based URLs with optional expiry
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build
|
||||||
|
make eng-pad-server
|
||||||
|
|
||||||
|
# Generate example config
|
||||||
|
cp eng-pad-server.toml.example /srv/eng-pad-server/eng-pad-server.toml
|
||||||
|
# Edit configuration (TLS certs, database path, etc.)
|
||||||
|
|
||||||
|
# Initialize (creates database, prompts for admin user)
|
||||||
|
./eng-pad-server init
|
||||||
|
|
||||||
|
# Run
|
||||||
|
./eng-pad-server server
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make all # vet → lint → test → build
|
||||||
|
make test # run tests
|
||||||
|
make lint # golangci-lint
|
||||||
|
make proto # regenerate gRPC code from .proto files
|
||||||
|
make proto-lint # buf lint + breaking change detection
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [ARCHITECTURE.md](ARCHITECTURE.md) — full system specification
|
||||||
|
- [CLAUDE.md](CLAUDE.md) — AI development context
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Private. All rights reserved.
|
||||||
Reference in New Issue
Block a user