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>
220 lines
6.7 KiB
Markdown
220 lines
6.7 KiB
Markdown

|
|
|
|
# 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](https://git.soconnor.dev/soconnor/beenvoice-web)
|
|
**Architecture:** [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)
|
|
**Mobile companion:** [beenvoice-app](https://git.soconnor.dev/soconnor/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](https://bun.sh) 1.x
|
|
- Docker & Docker Compose (for PostgreSQL locally or full-stack deploy)
|
|
- Git
|
|
|
|
## Local development
|
|
|
|
### 1. Clone and install
|
|
|
|
```bash
|
|
git clone https://git.soconnor.dev/soconnor/beenvoice-web.git
|
|
cd beenvoice-web
|
|
bun install
|
|
```
|
|
|
|
### 2. Environment
|
|
|
|
```bash
|
|
cp .env.example .env.local
|
|
```
|
|
|
|
Edit `.env.local` for local dev. Minimum:
|
|
|
|
```env
|
|
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):
|
|
|
|
```bash
|
|
docker compose -f docker-compose.dev.yml up -d
|
|
```
|
|
|
|
Apply schema (pick one):
|
|
|
|
```bash
|
|
bun run db:push # fast iteration during development
|
|
# bun run db:migrate # same migrations the Docker image runs in production
|
|
```
|
|
|
|
### 4. Run
|
|
|
|
```bash
|
|
bun run dev
|
|
```
|
|
|
|
Open [http://localhost:3000](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
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
Set at least:
|
|
|
|
```env
|
|
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:
|
|
|
|
```bash
|
|
docker compose build --no-cache app
|
|
```
|
|
|
|
### 2. Start
|
|
|
|
```bash
|
|
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:
|
|
|
|
```env
|
|
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](./docs/ARCHITECTURE.md) for routers, schema, auth flows, and MCP.
|
|
|
|
## Scripts
|
|
|
|
```bash
|
|
# 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](#docker-deployment-app--database)), 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](./docs/ARCHITECTURE.md) | Stack, routers, schema, auth, Docker, MCP |
|
|
| [docs/README.md](./docs/README.md) | Index of UI and product guides |
|
|
| [AGENTS.md](./AGENTS.md) | Conventions for AI-assisted development |
|
|
|
|
## License
|
|
|
|
MIT — see [LICENSE](LICENSE).
|