Skip to content

Running it locally

This page is for a developer setting up SBDMS on their own machine for the first time. Follow it top to bottom once; after that you only need the three “run” commands.

Tool Version Check
.NET SDK 10.0 or newer dotnet --version
Node.js 24 (the docs site image uses node:24; the app frontend builds on any current LTS) node --version
Git any git --version
sqlite3 CLI any, optional sqlite3 --version

Clone the repository (govtprojs/swarnabindu) and check out staging, which is what production runs.

The backend reads backend/Sbpdms.Backend/appsettings.json. Two settings matter locally:

  • ConnectionStrings:DefaultConnection points at a database path that is probably not valid on your machine. Do not edit the file; override it with the SBPDMS_DB_PATH environment variable, which takes a plain file path and wins over the setting.
  • Jwt:Key must be at least 32 characters. The checked-in development value works locally. The backend refuses to start when the key is missing. Never reuse the development key anywhere public.

Start it:

Terminal window
cd backend/Sbpdms.Backend
SBPDMS_DB_PATH=$PWD/dev.db ASPNETCORE_ENVIRONMENT=Development dotnet run --launch-profile http

What happens on first start, in order:

  1. The SQLite file is created if it does not exist.
  2. SchemaManager creates every table and runs the additive migrations.
  3. The seeder creates one SuperAdmin user with username admin. Its password comes from Admin:InitialPassword in appsettings.json (or the ADMIN_INITIAL_PASSWORD environment variable). Look it up there; it is not printed in this guide.
  4. Lookup data (regions, districts, local levels) is seeded when SeedLookupData:Enabled is true, which it is by default.
  5. The log shows the absolute path of the database it bound to. Check it once; a wrong path is the most common local mistake.

The API is now at http://localhost:5090. Useful checks:

  • curl http://localhost:5090/healthz and curl http://localhost:5090/readyz
  • Swagger UI at http://localhost:5090/swagger (Development only)

To start over, stop the backend and delete the .db file; the next start recreates the schema and the admin.

Terminal window
cd frontend
npm ci
PUBLIC_API_URL=http://localhost:5090/api npm run dev

Open http://localhost:4321 and log in as admin. The dev server listens on all interfaces, so a phone on the same Wi-Fi can open it using your computer’s IP.

PUBLIC_API_URL is read when the frontend starts or builds, not per request. Forget it and the default points at https://localhost:5091/api: pages render, data calls fail. There are no frontend unit tests; npm run build is the gate, so run it before you push.

The backend has an xUnit integration test project. Each test class starts the real application against a fresh SQLite file in your temp folder, with a fixed test JWT key and a known admin password, so tests are independent and can run in parallel.

Terminal window
cd backend
dotnet test Sbpdms.IntegrationTests/Sbpdms.IntegrationTests.csproj
# one class:
dotnet test Sbpdms.IntegrationTests/Sbpdms.IntegrationTests.csproj --filter RoleManagementTests

Pitfalls that cost time before: read JSON into typed DTOs, not dynamic; camp-day create returns its id in the Location header; a dose-prep create needs a preparer assigned to that site; registration needs client-generated UPCs.

Terminal window
cd docs-site
npm install
npm run dev # http://localhost:4321 (stop the app frontend first, or pass --port)
npm run check:words # the compliance gate; the Docker build runs it too
npm run build

Pages are plain Markdown under src/content/docs/. Read docs-site/WRITING.md before writing; it holds the style rules and the list of words that must never appear.

Follow the pattern of the file you are in. These are the ones that matter:

  • Comments explain why, not what. Many methods carry a paragraph naming the incident that caused a rule (a date, a site, a count). Keep that habit; the next person needs the reason, not a restatement of the code.
  • All SQL goes through IDbService (GetAsync, GetAllAsync, EditData) with parameters. The only values ever inlined into SQL are integer ids that the server itself read from the database.
  • Every endpoint that touches a child, guardian, camp day, batch or staff row calls IPermissionService before reading or writing, and returns NotFound() on failure. Never write your own scope check. Call _perm.LogDenial(...) so the refusal is in the logs.
  • Every write calls _audit.LogAsync(table, id, action, User, before, after), with the row read before and after the change.
  • Soft delete only. Set IsDeleted = 1; filter it in every read.
  • Schema changes are additive. Add a CREATE TABLE IF NOT EXISTS or an ALTER TABLE ... ADD COLUMN guarded by PRAGMA table_info in SchemaManager, then any index. Never drop or rename.
  • Errors are BadRequest(new { Error = "..." }) with a sentence a health worker can act on.
  • Bilingual strings. Where a page supports both languages (the registration form, the dose wizard, the reports pages), each label appears twice: once inside x-show="$store.i18n.showEn" and once inside x-show="$store.i18n.showNe". New user-facing pages should do the same. Do not call $store.i18n.t(...); that helper exists only on the QC branch.
  • Authenticated calls run in the browser. Use ApiService from src/services/api.js inside a <script> or Alpine component, never in Astro frontmatter. Mark polling calls { silent: true } or they trigger the global “Working” overlay.
  • Do not mix ?? and || without parentheses; it breaks the build.
  1. Put it in the controller for its area under Features/, or make a new folder there. Set [Authorize] on the class and [Authorize(Roles = ...)] on write actions.
  2. Validate the input and return 400 with { Error } for anything wrong.
  3. Call the right IPermissionService check; return NotFound() when it fails. For list endpoints, append the ...ScopeWhereAsync fragment to your WHERE.
  4. Write SQL with parameters through IDbService. Filter IsDeleted = 0.
  5. For writes: read the row before, write, read after, call _audit.LogAsync.
  6. If you touched a lookup table that the public /api/lookups serves, call ILookupCache.Invalidate(...).
  7. Add an integration test in Sbpdms.IntegrationTests that covers the happy path and one scoped-user denial.
  8. Run dotnet test, then try it from the frontend against your local backend.
  1. Create the .astro file under src/pages/ in the folder that matches the URL. Use Layout.astro.
  2. Load data in a client-side script through ApiService; show the Skeleton component or an empty state until it arrives.
  3. Labels in both languages with the showEn and showNe flags; dates through utils/dateFormat.js so AD and BS both appear.
  4. Hide the page or its buttons from roles that cannot use them, but remember the server is the real gate.
  5. Show API Error messages with window.toast.error. Use window.confirmDialog before anything destructive.
  6. Check the page at 414 px wide and with keyboard only.
  7. npm run build must pass. Check the page on QC as well as locally; the two differ in the i18n store.
  8. If the page changes what users see, update this guide.