Files
lunarfront-app/CLAUDE.md
Ryan Moon 7aa81c4e7c
All checks were successful
Build & Release / build (push) Successful in 15s
docs: add infrastructure, build pipeline, and dev box sections to CLAUDE.md
2026-04-04 10:30:40 -05:00

8.4 KiB

LunarFront — Project Conventions

App

  • Name: LunarFront
  • Purpose: Small business management platform (POS, inventory, rentals, scheduling, repairs, accounting)
  • Company: Lunarfront Tech LLC

Tech Stack

  • Runtime: Bun
  • Language: TypeScript (strict mode, end-to-end)
  • API: Fastify with Pino JSON logging
  • ORM: Drizzle ORM (PostgreSQL 16)
  • Validation: Zod (shared schemas between frontend and backend)
  • Queue: BullMQ (Valkey-backed)
  • Cache: Valkey 8 (Redis-compatible fork)
  • Monorepo: Turborepo with Bun workspaces
  • Testing: bun test (built-in, uses bun:test imports)
  • Linting: ESLint 9 flat config + Prettier

Package Namespace

  • @lunarfront/shared — types, Zod schemas, business logic, utils
  • @lunarfront/backend — Fastify API server

Database

  • Dev: lunarfront on localhost:5432
  • Test: lunarfront_test on localhost:5432
  • Each deployed instance has its own isolated database — no multi-tenancy, no company_id
  • Migrations via Drizzle Kit (bunx drizzle-kit generate, bunx drizzle-kit migrate)

Key Entity Names

  • account — billing entity (family, individual, or business)
  • member — individual person on an account (NOT "student" — renamed to support multiple adults)
  • member.is_minor — derived from date_of_birth, controls consent/portal rules

Commands

  • bun run dev — start all packages in dev mode
  • bun run test — run all tests
  • bun run lint — lint all packages
  • bun run format — format all files with Prettier

API Conventions

  • Every endpoint that returns a list must support pagination, search, and sorting — no exceptions unless the endpoint is explicitly a lightweight lookup (see below)
    • ?page=1&limit=25 — pagination (default: page 1, 25 per page, max 100)
    • ?q=search+term — full-text search across relevant columns
    • ?sort=name&order=asc — sorting by field name, asc or desc
  • List responses always return { data: [...], pagination: { page, limit, total, totalPages } }
  • Search and filtering is ALWAYS server-side, never client-side
  • Use PaginationSchema from @lunarfront/shared/schemas to parse query params
  • Use pagination helpers from packages/backend/src/utils/pagination.ts
  • Lookup endpoints (e.g., /roles/all, /statuses/all) are the exception — these return a flat unpaginated list for populating dropdowns/selects. Use a /all suffix to distinguish from the paginated list endpoint for the same resource.

Frontend Table Conventions

  • Every table that displays data must use the shared DataTable component (components/shared/data-table.tsx)
  • All tables must support: search (via ?q=), sortable columns, and server-side pagination
  • Use the usePagination() hook (hooks/use-pagination.ts) — it manages page, search, and sort state via URL params
  • All data columns that make sense to sort by should be sortable (e.g., name, email, date, status) — don't limit to just 1-2 columns
  • Sub-resource tables (e.g., members within an account, payment methods) follow the same rules — use DataTable with pagination, not raw <Table> with unbounded queries
  • Loading states should use skeleton loading (built into DataTable), not plain "Loading..." text

Conventions

  • Shared Zod schemas are the single source of truth for validation (used on both frontend and backend)
  • Business logic lives in @lunarfront/shared, not in individual app packages
  • API routes are thin — validate with Zod, call a service, return result
  • All financial events must be auditable (append-only audit records)
  • JSON structured logging with request IDs on every log line

Infrastructure

Overview

LunarFront runs on DigitalOcean Kubernetes (DOKS). Each customer gets an isolated namespace, database, and Helm release managed by ArgoCD.

Key Services

  • Cluster: lunarfront DOKS cluster, region NYC
  • Registry: registry.digitalocean.com/lunarfront (DOCR) — stores Docker images and Helm charts
  • Git: git.lunarfront.tech — self-hosted Gitea, source of truth for code and charts
  • CI: Gitea Actions — builds Docker images and Helm charts on push to main
  • CD: ArgoCD at argocd.lunarfront.tech — deploys from lunarfront-charts repo
  • Database: DO Managed PostgreSQL — one database per customer, plus manager DB
  • Cache/Queue: DO Managed Valkey — shared across all customers (key-prefixed per customer)
  • Ingress: nginx ingress controller with Cloudflare proxy in front
  • TLS: cert-manager with Let's Encrypt (letsencrypt-prod cluster issuer)
  • DNS: Cloudflare — wildcard *.lunarfront.tech → cluster LB IP 167.99.21.170

Node Pools

  • system — 2x s-2vcpu-4gb, runs ingress, ArgoCD, manager, pgbouncer
  • customers — autoscales 0→N, s-4vcpu-8gb, runs customer app pods (tainted role=customer)
  • dev — autoscales 0→1, s-4vcpu-8gb, runs dev pod only (tainted dedicated=dev:NoSchedule)

Repos

  • lunarfront-app — main application code (this repo)
  • lunarfront-charts — Helm charts and ArgoCD app definitions
  • lunarfront-infra — Terraform for DO infrastructure (DOKS, managed DBs, registry, DNS)
  • lunarfront-manager — internal ops tool for provisioning/deprovisioning customers

Build & Deploy Pipeline

How it works

  1. Push code to main on lunarfront-app
  2. Gitea Actions runs .gitea/workflows/build.yml:
    • Builds lunarfront-app Docker image → pushes as 0.1.{run_number}, {sha}, latest
    • Builds lunarfront-frontend Docker image → same tags
    • Packages Helm chart → pushes as 0.1.{run_number} to DOCR OCI registry
  3. ArgoCD image updater detects new image digests → updates customer deployments
  4. New customer provisions always get the latest chart version (queried from DOCR at provision time)
  5. Existing customers upgraded via POST /customers/:slug/upgrade or POST /customers/upgrade-all in the manager

Versioning

  • Version format: 0.1.{gitea_run_number} — always incrementing, no git commit-back needed
  • No version stored in git — source of truth is DOCR tags
  • Chart version and app version are kept in sync

Key files

  • Dockerfile — backend image (bun runtime, runs packages/backend/src/main.ts directly)
  • Dockerfile.frontend — frontend nginx image
  • chart/ — Helm chart for customer app deployments
  • .gitea/workflows/build.yml — CI pipeline
  • .gitea/workflows/build-devpod.yml — builds dev box image on Dockerfile.devpod changes

Dev Box

What it is

A persistent development pod running in the dev namespace on the cluster. Provides a full remote dev environment accessible from anywhere.

  • VS Code in browser: dev.lunarfront.tech (Cloudflare Access protected, OTP to email)
  • SSH: ssh -p 2222 root@dev-ssh.lunarfront.tech
  • Storage: 100GB DO block storage PVC mounted at /root — everything in home dir persists
  • Image: registry.digitalocean.com/lunarfront/manager:devpod-latest
  • Tools: bun, Claude Code CLI, code-server, kubectl, helm, k9s, doctl, psql, redis-cli, git

Managing the dev pod

# Scale up (provisions node automatically)
kubectl scale deployment dev -n dev --replicas=1

# Scale down (node auto-terminates after ~15 min)
kubectl scale deployment dev -n dev --replicas=0

Running the app locally on the dev box (no containers)

The dev box runs the app as plain Bun processes, connecting to the same DO managed services as production.

Required env vars (create a .env file in the repo root or export in .bashrc):

DATABASE_URL=postgresql://...   # DO managed postgres, lunarfront database
REDIS_URL=rediss://...          # DO managed valkey
JWT_SECRET=...                  # any random hex string for local dev
PORT=8000

Start the app:

cd ~/lunarfront-app
bun run dev

Access the running backend at dev.lunarfront.tech/proxy/8000/ in the browser (code-server proxy), or via SSH port forward:

ssh -p 2222 -L 8000:localhost:8000 root@dev-ssh.lunarfront.tech

Run migrations against the dev database:

bunx drizzle-kit migrate

Workflow

  1. Edit code in VS Code at dev.lunarfront.tech or via SSH
  2. Run and test locally with bun run dev — app connects to DO managed postgres/valkey
  3. Push to main → Gitea Actions builds and pushes new Docker image + Helm chart
  4. ArgoCD deploys to the cluster automatically
  5. Use manager at manager.lunarfront.tech to upgrade customer instances if needed