grabowski f12c901a97 Show personal/address/emergency on employee detail and in edit modal
Detail page now has three new cards beneath the main employee block:
- Personal: DOB (with computed age), gender, nationality, marital status
- Address: combined one-line address plus a labelled grid for the
  Thai-specific subdistrict/district/province/postal code parts
- Emergency Contact: name, phone, relationship

Edit modal extends with matching sections so HR/admin can update
all 14 new fields. updateEmployee server action passes the new
fields through to the employees table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:01:09 +07:00

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

git clone https://git.b4l.co.th/B4L/buildfor_life_budget.git
cd buildfor_life_budget

2. Install dependencies

npm install

3. Create the database

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

cp .env.example .env

Edit .env and fill in the required values (see Environment Variables below).

5. Push the database schema

npm run db:push

Running the Dev Server

npm run dev

The app is available at 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:

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

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.

S
Description
No description provided
Readme 1.1 MiB
Languages
Svelte 49.9%
TypeScript 49.8%
Shell 0.1%