Parse DISABLE_SIGNUPS and related env booleans correctly for Compose string values, and derive auth trustedOrigins from BETTER_AUTH_URL. Rewrite README and architecture docs with the git.soconnor.dev remote and accurate deployment guidance. Allow zero-line-item draft invoices with validation when sending email. Co-authored-by: Cursor <cursoragent@cursor.com>
beenvoice-web
Web application and API for beenvoice — invoicing for freelancers and small businesses. Includes the Next.js dashboard, tRPC API, better-auth, PostgreSQL persistence, PDF/email delivery, time tracking, and an MCP automation endpoint.
Repository: git.soconnor.dev/soconnor/beenvoice-web
Architecture: docs/ARCHITECTURE.md
Mobile companion: beenvoice-app (separate repo; often checked out beside this one in a workspace)
Stack
| Layer | Technology |
|---|---|
| App | Next.js 16 App Router, React 19 |
| API | tRPC 11 + SuperJSON |
| Database | PostgreSQL 17, Drizzle ORM |
| Auth | better-auth (email/password, optional Authentik OIDC, Expo mobile) |
| UI | shadcn/ui, Tailwind CSS v4 |
| Email / PDF | Resend, @react-pdf/renderer |
| Runtime | Bun |
Features
- Clients, businesses, invoices (line items, tax, status workflow)
- Time clock with one running timer per user; clock-out can append invoice lines
- Expenses, payments, recurring invoices, invoice templates
- PDF export and email delivery (Resend)
- Public invoice links (
/i/[token]) - CSV import, reports, platform branding / admin settings
- MCP API (
/api/mcp) for automation via API keys (bv_…) - Optional Authentik OIDC SSO
Prerequisites
- Bun 1.x
- Docker & Docker Compose (for PostgreSQL locally or full-stack deploy)
- Git
Local development
1. Clone and install
git clone https://git.soconnor.dev/soconnor/beenvoice-web.git
cd beenvoice-web
bun install
2. Environment
cp .env.example .env.local
Edit .env.local for local dev. Minimum:
DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
DB_DISABLE_SSL=true
AUTH_SECRET=your-dev-secret # openssl rand -base64 32
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000
Email and SSO are optional for local work — leave RESEND_* and AUTHENTIK_* blank unless you need them.
3. Database
Start Postgres (dev compose exposes port 5432):
docker compose -f docker-compose.dev.yml up -d
Apply schema (pick one):
bun run db:push # fast iteration during development
# bun run db:migrate # same migrations the Docker image runs in production
4. Run
bun run dev
Open http://localhost:3000, register at /auth/register, then sign in.
Docker deployment (app + database)
The production compose file runs the Next.js app and PostgreSQL. Migrations run automatically on container start (bun migrate.ts in the image CMD).
1. Configure
cp .env.example .env
Set at least:
AUTH_SECRET=<openssl rand -base64 32>
BETTER_AUTH_URL=https://your-public-hostname
NEXT_PUBLIC_APP_URL=https://your-public-hostname
BETTER_AUTH_URL and NEXT_PUBLIC_APP_URL must match the URL users actually use in the browser. If they point at localhost but you access the app via another hostname, auth (sign-in / sign-up) will fail.
NEXT_PUBLIC_* values are embedded at image build time. Rebuild after changing white-label or Authentik client flags:
docker compose build --no-cache app
2. Start
docker compose up -d --build
App listens on ${WEB_PORT:-3000}. Postgres stays on the internal compose network.
3. Sign-ups
Registration is enabled by default. To block new email/password accounts:
DISABLE_SIGNUPS=true
Use the literal strings true or false (or omit the variable). Do not rely on bare boolean coercion from shell/compose — the app parses these explicitly.
4. Optional services
| Variable | Purpose |
|---|---|
RESEND_API_KEY, RESEND_DOMAIN |
Invoice and password-reset email |
AUTHENTIK_ISSUER, AUTHENTIK_CLIENT_ID, AUTHENTIK_CLIENT_SECRET |
OIDC SSO (also set NEXT_PUBLIC_AUTHENTIK_ENABLED=true and rebuild) |
CRON_SECRET |
Protects /api/cron/generate-recurring |
DISABLE_SIGNUPS=true |
Block new registrations |
Project structure
beenvoice-web/
├── src/app/ # Routes (dashboard, auth, /api/*)
├── src/server/api/ # tRPC routers
├── src/server/db/ # Drizzle schema, pool, migrate.ts
├── src/components/ # UI (ui/, forms/, layout/, branding/)
├── src/lib/ # auth, PDF, email, branding helpers
├── drizzle/ # SQL migrations
├── Dockerfile # Production image (migrate + next start)
├── docker-compose.yml # App + Postgres (deploy)
├── docker-compose.dev.yml # Postgres only (local dev)
└── docs/ # Architecture and UI guides
See docs/ARCHITECTURE.md for routers, schema, auth flows, and MCP.
Scripts
# App
bun run dev # next dev --turbo
bun run build # production build
bun run start # next start
bun run check # eslint + tsc
# Database
bun run db:push # push schema (local dev)
bun run db:migrate # run drizzle migrations
bun run db:generate # generate new migration SQL
bun run db:studio # Drizzle Studio
# Formatting
bun run lint
bun run lint:fix
bun run format:write
bun run typecheck
# Docker helpers (Postgres only — uses Colima on macOS)
bun run docker:up # colima start + docker-compose.dev.yml up -d
bun run docker:down # stop dev Postgres + colima
Full-stack deploy uses docker compose up (see Docker deployment), not bun run docker:up.
API surface
| Endpoint | Auth | Purpose |
|---|---|---|
/api/trpc |
Session cookie or API key | Primary API (web + mobile) |
/api/auth/* |
Varies | better-auth + custom register/reset REST |
/api/mcp |
API key only | JSON-RPC automation tools |
/i/[token] |
Public token | Client invoice view |
/api/i/[token]/pdf |
Public token | Invoice PDF download |
Business logic lives in src/server/api/routers/ with Zod validation.
Customization
- Runtime branding: Dashboard → Administration (platform settings)
- Build-time defaults:
NEXT_PUBLIC_BRAND_*in.env(rebuild Docker image to apply) - Theme / fonts:
src/styles/globals.css, appearance settings in the app - Logo component:
src/components/branding/logo.tsx
Documentation
| Doc | Contents |
|---|---|
| docs/ARCHITECTURE.md | Stack, routers, schema, auth, Docker, MCP |
| docs/README.md | Index of UI and product guides |
| AGENTS.md | Conventions for AI-assisted development |
License
MIT — see LICENSE.
