Skip to content

Latest commit

 

History

History
100 lines (85 loc) · 8.09 KB

File metadata and controls

100 lines (85 loc) · 8.09 KB

TPI Planning Manager

Project Overview

Full-stack TypeScript monorepo for managing a Swiss TPI (Travail Pratique Individuel) project "AD Account Manager". Features Gantt timeline and Kanban board views. Available as a hosted web app (PostgreSQL + better-auth) and as an offline desktop app (Tauri + SQLite).

Tech Stack

  • Runtime: Bun
  • Frontend: React 19 + Vite + Tailwind CSS 4
  • Backend: Express 5 (running on Bun)
  • Database (web): PostgreSQL 18 (Docker) + Drizzle ORM
  • Database (desktop): SQLite via bun:sqlite + Drizzle ORM
  • Desktop shell: Tauri v2 (Rust + WebView2/WebKitGTK)
  • API Contract: OpenAPI 3.1 + Orval codegen

Workspace Structure

apps/web          - React frontend (Vite)
apps/api          - Express REST API (Bun)
apps/desktop      - Tauri v2 shell (Rust, src-tauri/)
packages/db       - Drizzle ORM schema & connection (PostgreSQL)
packages/desktop-db    - Drizzle ORM schema (SQLite mirror)
packages/desktop-api   - Bun sidecar wrapper for desktop mode
packages/api-spec - OpenAPI spec + Orval config
packages/api-client-react - Generated React Query hooks (DO NOT EDIT generated/)
packages/api-zod  - Generated Zod schemas (DO NOT EDIT generated/)

Quick Start (Web)

docker compose up -d          # Start PostgreSQL
bun install                   # Install dependencies
bun run db:push               # Push schema to DB
bun run db:seed               # Seed initial data
bun run dev:api               # Start API on :3001
bun run dev:web               # Start frontend on :5173

Quick Start (Desktop)

bun install                   # Install dependencies (includes @tauri-apps/cli)
bun run build:desktop         # Build frontend + sidecar (auto-detects OS)
bun run tauri:dev             # Launch the Tauri desktop app in dev mode

Production build (produces .deb/.rpm on Linux, NSIS/.msi on Windows):

bun run tauri:build

Key Commands

  • bun run dev - Start all web services
  • bun run dev:web - Frontend only
  • bun run dev:api - API only
  • bun run build:desktop - Build frontend + sidecar (platform auto-detect)
  • bun run tauri:dev - Launch desktop app in dev mode
  • bun run tauri:build - Build desktop installers for the current platform
  • bun run tauri:build:appimage - Build AppImage with sidecar workaround (Linux only)
  • bun run db:push - Apply Drizzle schema
  • bun run db:seed - Seed database
  • bun run codegen - Regenerate API client from OpenAPI spec
  • bun run typecheck - TypeScript check (all 7 packages)
  • bun run lint - Lint code

Environment Variables

  • DATABASE_URL - PostgreSQL connection string (default: postgresql://tpi:tpi@localhost:5432/tpi_flow)
  • PORT - API server port (default: 3001)
  • DISABLE_ENCRYPTION + IS_DESKTOP - Both required to bypass field encryption in desktop mode
  • CSP_ENFORCE / CSP_REPORT_URI - Toggle CSP enforce mode and set the reporting endpoint.
  • POSTMARK_API_TOKEN - Postmark Server API token for transactional email (reset-password, email verification). When unset, sendEmail() no-ops with a console warning so local dev keeps working without Postmark.
  • EMAIL_FROM - Sender address for all transactional emails (default: TPI-Flow <noreply@tpiflow.ch>). Domain must be verified in Postmark (DKIM + Return-Path).
  • EMAIL_REPLY_TO / POSTMARK_MESSAGE_STREAM - Optional overrides for Reply-To header and Postmark stream (default stream: outbound).
  • INACTIVITY_WARN_DAYS / INACTIVITY_DELETE_DAYS - Inactive-account cleanup thresholds (defaults: 7 / 30 days). Final reminder fires at DELETE_DAYS - 1. Override only for tests; admins (role='admin') and ADMIN_PROTECTED_EMAILS are always exempt.
  • INACTIVITY_CRON - Cron expression for the inactivity job (default: 0 3 * * * Europe/Zurich).

Important Rules

  • Never edit files in */generated/ directories
  • Update packages/api-spec/openapi.yaml first, then run codegen
  • Use bun for everything (install, run, etc.)
  • Routes read db and tables from req.app.locals (not module-level imports)
  • packages/desktop-db schema must stay in sync with packages/db schema
  • packages/desktop-api/src/init-schema.ts DDL must stay in sync with packages/desktop-db/src/schema/
  • When changing the desktop schema: update init-schema.ts (for fresh installs) AND add a migration in packages/desktop-api/src/migrator.ts (for existing users)

Desktop Architecture Notes

  • AppImage build: Uses a dummy-swap workaround (scripts/build-appimage.sh) because Tauri's linuxdeploy corrupts Bun sidecar binaries. The script re-signs the repackaged AppImage for the auto-updater.
  • Auto-updater: tauri-plugin-updater (Rust) + @tauri-apps/plugin-updater (JS). Frontend hook useUpdater() checks on startup + hourly. UpdateBanner component in the sidebar. No-ops in web mode.
  • SQLite migrations: Embedded in TypeScript (migrator.ts MIGRATIONS array) — no filesystem SQL files (incompatible with bun build --compile). Boot sequence: initSchema()runMigrations()seedLocalUser().
  • Desktop auth stub: /api/auth/get-session returns a local session JSON in desktop mode so useSession() works without better-auth.

Web-only Subsystems (not in desktop)

  • WebSocket notifications (apps/api/src/services/socket-broadcaster.ts): Socket.io server attached to the HTTP server fans out MAINTENANCE, CLEAR_MAINTENANCE, NEW_FEATURE, FORCE_LOGOUT, DIRECT_MESSAGE events via WebSocket. Redis adapter (@socket.io/redis-adapter) propagates events across multiple instances (Railway). MAINTENANCE state is persisted in the singleton maintenance_state table and rehydrated at boot via initBroadcaster().
  • Admin panel (/admin): RBAC-gated by the session user's role column (user or admin). requireAuth + requireAdmin middleware chain on /api/admin/*, rate-limited via adminLimiter. Bootstrap the first admin via bun run db:promote-admin <email> (see packages/db/src/promote-admin.ts). The seed user (test@tpi-flow.local) is admin by default. Exposes broadcast + feedback triage + user management + weekly digest preview endpoints.
  • In-app feedback: POST /api/feedback (rate-limited 5/h per IP) writes to the feedback table; only title and description are encrypted at rest (see FEEDBACK_FIELDS in packages/db/src/field-encryption.ts). Admin triage via GET /api/admin/feedback and PATCH /api/admin/feedback/:id.
  • CSP: apps/api/src/middleware/security.ts hashes two inline scripts from apps/web/index.html (theme bootstrap + JSON-LD). Update both hashes if either script changes — see docs/DEVELOPMENT.md. connect-src derives WebSocket origins from CORS_ORIGIN so only same-host wss:// upgrades pass CSP2/CSP3 enforcement.
  • Transactional email (apps/api/src/services/email.ts): Postmark wrapper used by better-auth for password reset (sendResetPassword) and email verification (sendVerificationEmail). Templates live in apps/api/src/emails/ as React Email components (base-layout, reset-password, verify-email) rendered with @react-email/render. Failures are swallowed and logged so a Postmark outage never blocks the auth flow. User name is decrypted (see encryptUserName) before being passed to templates. New signups receive a verification email (emailVerification.sendOnSignUp: true) but login is not blocked on unverified email. DNS setup: on Cloudflare, the pm-bounces CNAME must be "DNS only" (not proxied) or Postmark bounces break.
  • Inactive account cleanup (apps/api/src/scheduler/inactivity-cron.ts + services/inactivity.ts): Daily Redis-locked cron (same advisory-lock pattern as the weekly digest) sends a J+7 warning, a J+29 final reminder, and at J+30 deletes the user via the same cascade as DELETE /api/admin/users/:id. Activity is tracked by apps/api/src/middleware/track-activity.ts (1h throttle, fire-and-forget UPDATE) mounted after requireAuth on /api. Reconnection clears the warning flags atomically in the same UPDATE. Admins and ADMIN_PROTECTED_EMAILS are exempt. Admin bypass: POST /api/admin/users/:id/trigger-inactivity-warning skips the 7d wait by backdating lastActivityAt. CGU section 11.3 documents the policy.