A web-based management platform for a single residential apartment association (built for "Ceebros Gardens" — five blocks: Mayflower, Tulip, Primrose, Daffodil, Orchid). Two internal staff logins, Admin and Secretary, manage every other record in the system — residents, vendors, staff, and association members are data the app tracks, not accounts that log in themselves.
The project doubles as a data-structures showcase: every module that genuinely benefits from one is backed by a real C data structure (BST, heap, hash table, FIFO queue, growable array), compiled and invoked as a stateless per-request compute engine. Postgres is the single source of truth; the C programs never touch disk.
- Resident directory — owner/tenant records, block/floor/door number, parking, contact info
- Association members — committee roster with roles (President, Secretary, Treasurer, Committee Member, Resident)
- Vendor management — service history, cost tracking, 1–5 star ratings, and a "best value" (rating-per-rupee) recommendation
- Staff attendance — check-in/check-out tracking, hourly wage, computed earnings
- Facility booking — reserve gym/pool/clubhouse/playground slots with automatic overlap/double-booking rejection
- Meeting records — agenda, location, attendees, and outcomes for every committee meeting
- Maintenance dues — a per-resident monthly ledger; record, mark paid, view unpaid balances
- Complaints / helpdesk — a priority-aware queue (urgent issues jump ahead), assignable and resolvable
- Notices — a pinnable announcements feed
- Profile — real logged-in username/role, account age, last login, and self-service password change
Frontend (static HTML/CSS/JS) → Node/Express (server/app.js) → Postgres (source of truth)
│
└──(for DS-backed modules only)──▶ C compute engine
(spawned via execFile,
data piped via stdin,
no disk I/O of its own)
For every DS-backed module, Node reads the module's current rows from Postgres, serializes them
to CSV, pipes that to a compiled C program's stdin via execFile (a real argv array — never a
shell string, which is what eliminates command injection entirely), the C program performs one
operation in memory and prints {"result": ..., "state": [...]} to stdout, and Node reconciles
state back into Postgres (upsert-by-natural-key + prune, never delete-then-reinsert, so ids and
foreign-key-dependent rows survive).
| Module | Data structure | Why |
|---|---|---|
| Residents | Block → Floor (linked lists) → Resident (BST by door number) | Genuine BST-backed CRUD/search |
| Association members | Singly linked list | — |
| Vendors | BST by id + min-heap by cost + max-heap by rating | Trades off cost vs. rating for "best value" |
| Staff | Min-heap by check-in time + max-heap by check-out time | — |
| Facility bookings | Hash table (resident lookup) + per-facility sorted timeline | O(k)-with-early-exit overlap detection |
| Meetings | Growable array (realloc) | No fixed slot cap |
| Complaints | FIFO queue (linked list); high priority enqueues at the head |
Simplest structure for "urgent jumps the queue" |
| Notices, Maintenance dues | Plain Postgres CRUD | No natural DS-shaped operation — forcing one would be artificial |
| Auth | bcrypt + JWT in Node | Deliberately not shelled out to C |
See CLAUDE.md for the full design rationale, the C stdin/stdout parsing contract, and
notes for anyone (human or AI) editing Backend/*.c.
Every field that reaches the database is validated server-side before it's ever written — not
just client-side regex that a direct API call could bypass. Rules are centralized in
server/lib/validators.js: names (letters/spaces only), 10-digit
phone numbers, user@domain email shape, positive costs/wages/dues amounts, YYYY-MM due
periods, MM/YY vendor contract dates, and blocks/roles/facilities/priorities restricted to their
known sets. Vendor validation is also enforced a second time inside Backend/vendorsds.c itself
(defense in depth, and a restoration of validators the original CLI prototype had before this
codebase's C→stateless-engine refactor dropped them).
- Frontend: static HTML/CSS/JavaScript (no framework, no build step)
- Backend: Node.js + Express (controllers, routing, auth, the Postgres↔C bridge)
- Compute engines: C, compiled per platform (
npm run build:c) - Database: PostgreSQL — the single source of truth (Docker Compose locally, any managed Postgres in production)
- Auth: bcrypt password hashing + JWT in an httpOnly,
sameSite=strictcookie — no server-side session store - Tests: Jest + Supertest
Backend/ C compute-engine sources (compiled to server/c-executables/, not checked in)
Frontend/ Static HTML/CSS/JS pages
server/
app.js Express app entry point
controllers/ One per module — validate, call the DS engine or Postgres directly, respond
routes/ Express routers, one per module
lib/ validators.js, csvShape.js, dsEngine.js (the C-engine bridge)
middleware/ auth.js (JWT sign/verify, cookie handling)
db/ pool.js, migrate.js, migrations/*.sql, reset.js, seedSynthetic.js, createUser.js
scripts/build-c.js Compiles every Backend/*.c into server/c-executables/
tests/ Jest unit + full-stack tests
docker/Dockerfile Multi-stage build: gcc + compile, then a slim Node runtime image
docker-compose.yml App + local Postgres for development
- Node.js 18+ with npm
- Docker and Docker Compose (recommended path), or a reachable Postgres instance for the native path
- gcc — only needed for the native path; Docker's build stage installs it for you
cp .env.example .env # only JWT_SECRET matters here — docker-compose sets DATABASE_URL itself
docker compose up -d --buildThe app is now at http://localhost:3000. Migrations run automatically on startup, so the schema is ready as soon as it boots — just open the site and create an account on the signup page.
To load the synthetic demo data (residents, vendors, etc.), either set SEED_ON_START=true before
the first docker compose up, or run it once (each command separately — chaining with && on
the host shell only sends the first command into the container):
docker compose exec app npm run seedThe other maintenance scripts are still available the same way if you want them —
docker compose exec app npm run db:reset to wipe all data, or
docker compose exec app npm run create-user -- --username admin --password <pw> --role admin to
bootstrap an account from the CLI instead of the signup page.
npm install
cp .env.example .envEdit .env: set DATABASE_URL to a reachable Postgres connection string and JWT_SECRET to any
random value (node -e "console.log(require('crypto').randomBytes(48).toString('hex'))").
npm run build:c # compiles Backend/*.c into server/c-executables/ — gcc must be on PATH
npm run migrate
npm run db:reset # optional on a brand-new database, but harmless
npm run seed # optional — populates realistic synthetic data
npm run create-user -- --username admin --password <your-password> --role admin
node server/app.js| Variable | Purpose |
|---|---|
DATABASE_URL |
Postgres connection string |
DATABASE_SSL |
Set to false for a local/Docker Postgres without SSL; leave unset (or anything else) for a managed Postgres that requires SSL |
JWT_SECRET |
Signs the auth cookie — generate a real random value for anything beyond local dev |
PORT |
Defaults to 3000 |
NODE_ENV |
development locally, production in Docker/deployment |
Schema lives in server/db/migrations/*.sql, applied via npm run migrate (tracked in a
schema_migrations table — no ORM, just numbered SQL files). Single-tenant: no association/tenant
id anywhere, by design.
npm run db:reset— truncates every data table (residents, vendors, staff, meetings, bookings, complaints, notices, dues, and the login audit log) and restarts id sequences. Leavesusersuntouched, since account bootstrap is a separate, explicit step.npm run seed— populates the (now-empty) database with realistic synthetic data: 33 residents spread across all 5 blocks, a committee roster, 9 vendors with ratings, 7 staff members with check-in/out history, 6 meetings, 7 facility bookings, 6 complaints across every priority, 5 notices, and 3 months of dues for 20 residents. Every row satisfies the same validators the live API enforces.npm run create-user -- --username <name> --password <pw> --role <admin|secretary>— bootstraps a login from the command line (handy for the first account or scripted setup).
Accounts can also be created through the app itself: the Sign in page links to a Create
account page (signup.html → POST /api/signup) where anyone can register a username, password,
and role (Admin or Secretary) and is signed straight in. Signup is deliberately open — there's no
invite/approval gating — and both roles can use every feature.
npm testtests/*.test.js runs two kinds of tests:
- Pure unit tests (
csvShape,resync,authmiddleware) — no external dependencies, always run. - Full-stack tests (
api.auth,api.residents) and the DS-engine integration test — gated behindDATABASE_URLbeing set and the relevant compiled.exe/binary existing (describe.skipotherwise). Run them locally with a Postgres reachable andnpm run build:calready done:
DATABASE_URL=postgres://postgres:postgres@localhost:5433/association_management \
DATABASE_SSL=false \
JWT_SECRET=any-value-for-testing \
npm testCI (.github/workflows/ci.yml) spins up a postgres:16 service, runs migrations, and runs the
full suite on every push.
This app spawns compiled C binaries via child_process and expects a persistent, writable-enough
process — that's incompatible with a serverless/ephemeral platform like Vercel. Deploy to any
platform that runs long-lived Docker containers instead: Render, Railway, or Fly.io all work
well with the existing docker/Dockerfile as-is.
General steps (the same on any of the three):
- Provision a managed Postgres — Render and Railway both have a built-in managed Postgres offering; Supabase also works from any platform. Copy its connection string.
- Create the web service from
docker/Dockerfile— point the platform's "deploy from Dockerfile" flow at this repo; the Dockerfile's build context is the repo root (docker build -f docker/Dockerfile .), and it needs no changes to run on any of these platforms. - Set environment variables on the platform (never commit real values):
DATABASE_URL→ the managed Postgres connection string from step 1DATABASE_SSL→ leave unset (managed Postgres providers require SSL)JWT_SECRET→ a real random value (node -e "console.log(require('crypto').randomBytes(48).toString('hex'))")NODE_ENV→productionSEED_ON_START→ optional; set totrueto load the synthetic demo data once, on the first boot with an empty database (it skips itself once residents exist). Handy on hosts whose free tier has no shell.
- No manual migration step needed — the app runs its own pending migrations automatically on
startup, so the schema is created on the first boot with no shell required. The first account
is created through the app's own signup page (
/signup.html), socreate-useris only needed if you specifically want a CLI-scripted account. If your host does give you a shell and you'd rather run things by hand,npm run migrate,npm run seed, andnpm run create-user -- --username admin --password <pw> --role adminall still work. - Confirm it actually works — open the deployed URL, create an account on the signup page, and click through a few pages before considering the deploy done.
Platform-specific notes:
- Render (works on the free tier — no shell required): "New → Web Service", connect the repo,
set the Dockerfile path to
docker/Dockerfile, add a PostgreSQL instance from the Render dashboard, and set the env vars above (leaveDATABASE_SSLunset). Migrations run on boot; make your first account on the signup page. SetSEED_ON_START=trueif you want the demo data. Note: Render free web services sleep after ~15 min idle and cold-start on the next request (the first load after a nap is slow, then normal), and free Postgres instances are time-limited. - Railway: "New Project → Deploy from GitHub repo", add a Postgres plugin, set the Dockerfile
path in service settings, run release commands via
railway run <command>or the web shell. - Fly.io:
fly launch(it will detectdocker/Dockerfile),fly postgres createfor the database,fly secrets set DATABASE_URL=... JWT_SECRET=..., thenfly ssh consoleto run the release commands.
- Guided by faculty of Sri Sivasubramaniya Nadar College of Engineering
- Special thanks to our client from Ceebros Gardens for feedback during development
MIT — see LICENSE.