diff --git a/README.md b/README.md new file mode 100644 index 0000000..32fb8a3 --- /dev/null +++ b/README.md @@ -0,0 +1,223 @@ +# Buildfor Life Budget + +A self-hosted, multi-company budget and project tracking tool built for internal use at Buildfor Life (B4L). Designed as a replacement for Actual Budget, with support for multiple companies, role-based access, project-level budget allocation, and an expense approval workflow. + +--- + +## Features + +- **Multi-company** — manage multiple companies from a single instance; each with its own members, budget, and data +- **Role-based access control** — per-company roles (admin, manager, user, viewer) with fine-grained permissions +- **Project budget allocation** — allocate funds from the company pool to individual projects +- **Expense submission and approval** — users submit expenses; managers/admins approve or reject +- **Categories and tags** — organise expenses with categories and free-form tags +- **Reports** — spending by category, spending by project, monthly trend, and budget vs actual +- **CSV import** — migrate historical data from Actual Budget via CSV upload +- **Audit log** — full company-scoped changelog of all significant actions +- **Company and user management** — soft-delete companies, disable or permanently delete users (system admin only) +- **Local authentication** — email + password with Argon2 password hashing +- **OIDC SSO** — optional generic OIDC provider (Keycloak, Authentik, etc.) alongside local auth +- **Light / dark mode** — toggle persisted to `localStorage` + +--- + +## Tech Stack + +- **Framework**: SvelteKit 5 with `adapter-node` +- **Database**: PostgreSQL 14+ via `pg` driver +- **ORM / migrations**: Drizzle ORM + Drizzle Kit +- **Styling**: Tailwind CSS v4 +- **Auth**: `@node-rs/argon2` (password hashing), `@oslojs/crypto` (session tokens), generic OIDC +- **Charts**: Chart.js +- **CSV parsing**: PapaParse + +--- + +## Prerequisites + +- Node.js 20 or later +- PostgreSQL 14 or later +- A dedicated database user with full privileges on the `public` schema + +--- + +## Setup + +### 1. Clone the repository + +```bash +git clone https://git.b4l.co.th/B4L/buildfor_life_budget.git +cd buildfor_life_budget +``` + +### 2. Install dependencies + +```bash +npm install +``` + +### 3. Create the database + +```sql +CREATE USER budget_app WITH PASSWORD 'your_password'; +CREATE DATABASE buildfor_life_budget OWNER budget_app; +\c buildfor_life_budget +GRANT ALL ON SCHEMA public TO budget_app; +``` + +### 4. Configure environment variables + +```bash +cp .env.example .env +``` + +Edit `.env` and fill in the required values (see [Environment Variables](#environment-variables) below). + +### 5. Push the database schema + +```bash +npm run db:push +``` + +--- + +## Running the Dev Server + +```bash +npm run dev +``` + +The app is available at [http://localhost:5173](http://localhost:5173). + +--- + +## First-Time Admin Bootstrap + +The first user to register receives a regular user account with no company access. To grant system-admin privileges, run the following SQL against the database: + +```sql +UPDATE users +SET is_system_admin = true +WHERE email = 'you@example.com'; +``` + +Only system admins can create companies. All other users must be invited to a company by an admin or manager. + +--- + +## Available Scripts + +| Script | Description | +|---|---| +| `npm run dev` | Start the Vite development server (port 5173) | +| `npm run build` | Compile for production into `./build/` | +| `npm run preview` | Preview the production build locally | +| `npm run check` | Run `svelte-check` type checking | +| `npm run db:push` | Push schema changes directly to the database (dev) | +| `npm run db:generate` | Generate a new Drizzle migration file | +| `npm run db:migrate` | Apply pending migrations | +| `npm run db:studio` | Open Drizzle Studio in the browser | + +--- + +## Building for Production + +```bash +npm run build +node build +``` + +The build output lives in `./build/`. The app is started with `node build` and reads configuration from environment variables or `.env`. + +--- + +## Environment Variables + +| Variable | Required | Description | +|---|---|---| +| `PORT` | No | Port the Node server listens on (default: `3000`) | +| `HOST` | No | Bind address (default: `127.0.0.1`) | +| `ORIGIN` | Yes | Public-facing URL — used by SvelteKit for CSRF protection (e.g. `https://budget.b4l.co.th`) | +| `DATABASE_URL` | Yes | PostgreSQL connection string, e.g. `postgresql://user:pass@localhost:5432/dbname` | +| `OIDC_ISSUER_URL` | No | OIDC provider issuer URL. Leave blank to disable SSO. | +| `OIDC_CLIENT_ID` | No | OIDC client ID | +| `OIDC_CLIENT_SECRET` | No | OIDC client secret | +| `OIDC_REDIRECT_URI` | No | Callback URL registered with the OIDC provider (e.g. `https://budget.b4l.co.th/oidc/callback`) | + +--- + +## Deployment + +Ready-to-use deployment files are in the `deploy/` directory: + +| File | Purpose | +|---|---| +| `buildfor-life-budget.service` | systemd unit file — runs `node build` as a hardened service under the `budget-app` user | +| `nginx.conf` | nginx reverse proxy — TLS termination, HTTP→HTTPS redirect, static asset caching, WebSocket upgrade headers | +| `setup.sh` | Server provisioning helper script | + +The app is served at `https://budget.b4l.co.th` behind nginx, which proxies to the Node process on `127.0.0.1:3000`. + +--- + +## Project Structure + +``` +buildfor_life_budget/ +├── src/ +│ ├── app.html # HTML shell +│ ├── app.css # Global styles (Tailwind entry point) +│ ├── hooks.server.ts # Session validation, auth middleware +│ ├── lib/ +│ │ ├── components/ # Shared Svelte components +│ │ ├── server/ +│ │ │ ├── auth/ # Session management, OIDC helpers +│ │ │ ├── db/ +│ │ │ │ ├── schema.ts # Drizzle table definitions +│ │ │ │ └── index.ts # Database client +│ │ │ ├── audit.ts # Audit log helpers +│ │ │ └── authorization.ts # Role/permission checks +│ │ ├── stores/ # Svelte stores (e.g. theme) +│ │ ├── types/ # Shared TypeScript types +│ │ └── utils/ # Misc utility functions +│ └── routes/ +│ ├── (auth)/ # Login, register, OIDC callback +│ └── (app)/ # Authenticated area +│ ├── dashboard/ # Home dashboard +│ ├── companies/ +│ │ └── [companyId]/ +│ │ ├── projects/ # Project list and detail +│ │ ├── expenses/ # Expense submission and approval +│ │ ├── budget/ # Budget allocation +│ │ ├── categories/ # Category management +│ │ ├── reports/ # Spending reports and charts +│ │ ├── import/ # CSV import (Actual Budget migration) +│ │ └── settings/ # Company settings +│ └── admin/ # System admin panel (users, companies) +├── deploy/ # systemd unit, nginx config, setup script +├── drizzle.config.ts # Drizzle Kit configuration +├── svelte.config.js # SvelteKit / adapter-node config +├── vite.config.ts # Vite / Tailwind plugin config +└── .env.example # Environment variable template +``` + +--- + +## User Roles + +Roles are assigned per company. A user can have different roles in different companies. + +| Role | Description | +|---|---| +| **Admin** | Full company control — manage members, roles, categories, settings, and approve or reject any expense | +| **Manager** | Manage projects and budgets, approve or reject expenses submitted by users | +| **User** | Submit and manage their own expenses, view project budgets | +| **Viewer** | Read-only access to company budgets, projects, and reports | + +System admins (set via SQL) can create and archive companies and disable or permanently delete any user account, regardless of company membership. + +--- + +## License + +Private / internal — all rights reserved.