Framework: itty-router (v5+) on Cloudflare Workers
Runtime: WinterCG (Node.js compatibility disabled)
Database: Cloudflare D1 (SQLite)
Language: JavaScript (ES Modules) + JSDoc
The backend uses a localized "Fat-Free" architecture. It is a single Cloudflare Worker handling all API requests. The router delegates request handling to domain-specific modules. Modules leverage Dependency Injection (DI) principles, accepting valid D1Database bindings and Env configurations at runtime.
src/
├── index.js // Entry point, Router definition
├── operations.js // Route handlers (entries, auth, FreshBooks)
├── middleware/ // Custom middleware (JWT auth)
│ └── auth.js // JWT verification middleware
└── modules/
├── journal/ // Journal Entry logic (DAL + Service)
│ ├── dal.js // Data Access Layer
│ └── service.js // Business Logic
└── freshbooks/ // FreshBooks API client
├── FreshBooksClient.js // API wrapper
├── service.js // Service layer
└── index.js // Module exports
- Communication: JSON-only (
Content-Type: application/json). - Errors: Standardized error envelope.
/* Error Response Format */
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Duration cannot be negative."
}
}- Dates: ISO 8601 strings (
YYYY-MM-DDfor logical dates, ISO timestamps forcreated_at).
Implemented via FreshBooks OAuth 2.0 with JWT tokens (see ADR-006 for detailed design).
- OAuth Login: User authenticates via FreshBooks OAuth 2.0 (
/auth/login) - JWT Token: Backend signs a short-lived JWT (15 min expiry) using
@tsndr/cloudflare-worker-jwt - Request Header: All protected API requests must include the token in the
Authorizationheader- Standard:
Authorization: Bearer <token>
- Standard:
- Token Storage: Access token kept in memory; HttpOnly refresh cookie (1 year) for token refresh
- Middleware:
requireAuthmiddleware intercepts requests, verifies the JWT signature, checks expiry, and attaches theuserIdto theRequestcontext
The API mounts OAuth handlers at:
/auth/login- Initiates FreshBooks OAuth flow/auth/callback- Handles OAuth callback and issues JWT/auth/refresh- Refreshes expired JWT using refresh cookie/auth/logout- Clears authentication state
Handles the core "Write" and "Commit" loops.
D1 Schema (entries)
id(TEXT, PK): Nano ID or UUID.user_id(TEXT, FK): Author.client_id(TEXT): FreshBooks Client ID.project_id(TEXT): FreshBooks Project ID (e.g., "CPC General").service_id(TEXT): FreshBooks Service/Task ID (e.g., "DevOps").billable(INTEGER): Boolean flag (0/1).client_notes(TEXT): Public work log (Matches "Note" field).private_notes(TEXT): Private/Internal context.duration_minutes(INTEGER): Time spent.date(TEXT): YYYY-MM-DD (Logical date of entry).status(TEXT): 'DRAFT' | 'COMMITTED'.synced_at(INTEGER): Timestamp of successful FreshBooks sync.
API Endpoints
GET /api/entries: List entries (query params:date,status,limit)POST /api/entries: Create new entryGET /api/entries/:id: Get single entryPUT /api/entries/:id: Update entry (only ifstatusis 'DRAFT')PATCH /api/entries/:id: Update entry (alias for PUT)DELETE /api/entries/:id: Delete entry (only ifstatusis 'DRAFT')POST /api/entries/sync: Bulk sync entries to FreshBooksPOST /api/entries/:id/retry-sync: Retry failed sync for single entry
Abstracts the FreshBooks Accounting API.
Responsibilities
- Reference Data: Retrieve Clients, Projects, and Services to populate frontend dropdowns
- Time Entry Sync: POST time entries to FreshBooks for synced journal logs
- User Info: Fetch authenticated user details (avatar, email, business)
- Error Handling: Manage API rate limits and retry logic
API Endpoints
GET /api/freshbooks/user: Returns authenticated user infoGET /api/freshbooks/clients: Returns list of clients with projectsGET /api/freshbooks/projects: Returns list of projectsGET /api/freshbooks/services: Returns list of services/tasksPOST /api/entries/sync: Sync selected entries to FreshBooks
User preferences for UI state persistence.
Responsibilities
- Store user-specific preferences (favorite clients, last-used services, etc.)
- Persist settings across sessions
API Endpoints
GET /api/preferences: Get user preferencesPUT /api/preferences: Update user preferences
Client-side markdown generation for daily standup reports.
Responsibilities
- Format synced entries as markdown grouped by client > project > service
- Copy formatted markdown to clipboard for pasting into standup channels (Slack, Teams, etc.)
- Separate private notes from client-facing content
Implementation
- Frontend-only (no backend API)
- Per-day copy button in entries UI
- Markdown format:
- Service: ticket (Client > Project)\n - Note line 1\n - Note line 2 - Only includes SYNCED entries (excludes private_notes)
- CORS: Handle preflight and standard headers (implicit via Cloudflare Workers)
- withAuth: JWT validation middleware - verifies Authorization header and extracts userId
- Error Handler: Catch global exceptions and format as JSON (via itty-router error handling)
- Dependency Injection: The
Envobject (containingDB, OAuth credentials, and JWT secret) is passed through the application - Validation: Manual validation with clear error messages for user input
- OAuth Implementation: FreshBooks OAuth 2.0 with JWT token management (see ADR-006)
- Token Management:
@tsndr/cloudflare-worker-jwtfor JWT signing/verification (WinterCG compatible)