Skip to content

About

WeFix – maintenance requests for residential buildings: residents report, company admins assign, technicians fix. .NET 10 Minimal API (vertical slices, DDD status machine), EF Core 10 + PostgreSQL 18, JWT, Angular 22 + Clarity.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

WeFix

Maintenance requests for residential buildings. Residents report problems in the building they live in, company admins assign technicians and review the work, technicians carry it out. The platform admin registers property management companies and their buildings.

Stack

  • .NET 10 / C# 14, ASP.NET Core Minimal APIs, built-in validation (AddValidation), ProblemDetails (RFC 9457)
  • EF Core 10 + PostgreSQL 18 (Npgsql), migrations in src/K.Fixer.Persistence/Migrations
  • JWT bearer authentication, one authorization policy per role
  • OpenAPI (Microsoft.AspNetCore.OpenApi) + Scalar
  • xUnit, Testcontainers (PostgreSQL), FakeTimeProvider
  • CSharpier, Husky.Net, JetBrains code inspection

Layout

src/
  K.Fixer.Domain/        aggregates and rules - no EF Core, no ASP.NET Core, no clock
    Maintenance/           MaintenanceRequest (aggregate root) + status history, the status machine
    Companies/ Buildings/ Users/
  K.Fixer.Persistence/   AppDbContext, table mapping, migrations, reference data and demo seed
  K.Fixer.Api/           host
    Features/<Area>/       one file per operation (vertical slices): request, response, endpoint, handler
    Common/                auth, errors, OpenAPI, paging, database start-up
    ApiEndpoints.cs        every route; ApiTags.cs - every tag with its description
tests/
  K.Fixer.Domain.Tests/  unit tests of the rules (every status move against every status)
  K.Fixer.Api.Tests/     HTTP tests on a real PostgreSQL, one container per run

Maintenance request life cycle

New ──assign──► Assigned ──start──► InProgress ──complete──► Completed ──close──► Closed
                    ▲                                            │
                    └──────────start──── Reopened ◄──reopen──────┘
New / Assigned / InProgress / Completed / Reopened ──cancel──► Cancelled

Every move goes through a method of MaintenanceRequest, which checks it and records it in the status history. A move the rules do not allow is 409 Conflict with a stable code (e.g. maintenance-request.invalid-status). Two people changing the same request at once get one success and one 409 (xmin concurrency token).

Requirements

  • .NET SDK 10 (global.json)
  • Docker (tests start PostgreSQL in a container; the showcase stack runs in compose)

First run

dotnet restore        # also restores local tools (CSharpier, Husky, dotnet-ef) and installs the git hooks

Run locally

The whole stack (PostgreSQL, migrations, demo data, API with the web client) - showcase/:

showcase/start.sh                 # http://localhost:25080/login; -stop, -clean

From the IDE / dotnet run (Development, http://localhost:25000): the database comes from the shared local Docker Swarm hr-local (PostgreSQL on localhost:5432, see src/K.Fixer.Api/appsettings.Development.json):

deploy/deploy.sh --db-only        # database kfixer, migrations, demo data
dotnet run --project src/K.Fixer.Api

Demo accounts (password fixer777): admin (platform admin), victoria@cme.com (company admin), anna@cme.com (technician), daniel@cme.com (resident) - or the one-click buttons on the sign-in page (DemoSignIn:Enabled).

Configuration

Setting (environment variable) Default
ConnectionStrings__Main - required
Jwt__Key - required secret, at least 32 characters
Jwt__Issuer, Jwt__Audience KFixer
Jwt__TokenLifetime 06:00:00 5 minutes to 1 day
DemoSignIn__Enabled false (true in Development) one-click sign-in as each role, no password; never in production
ScalarVisible false OpenAPI document and Scalar outside Development

K.Fixer.Seed (one-off, after Liquibase): Seed__Showcase (false) adds the demo data to an empty database - 160 companies (some deactivated), each with 1 to 10 buildings, company admins, technicians and residents (with past stays, some locked or deactivated), two platform admins; Seed__MaintenanceRequests (5000, 0 to 100 000) is the number of maintenance requests at every stage, a fifth of them in Acme (the demo accounts' company).

Wrong or missing settings stop the API at start-up with the reason.

Health: GET /health/live (process), GET /health/ready (database reachable).

Showcase and hr-local

  • showcase/: the whole stack in one command, no .env - cd showcase && docker compose up --build, then http://localhost:25080 (see showcase/README.md).
  • deploy/deploy.sh: on the shared local Docker Swarm hr-local (database kfixer, role kfixer_app, container kfixer on lnet), http://127.0.0.1:6400. --no-demo, --db-only; deploy/remove.sh [--purge].

Both seed the demo data (5000 requests) and enable the demo sign-in buttons.

Build, test, format

dotnet build
dotnet test                       # needs Docker
dotnet csharpier check .          # dotnet csharpier format . to fix
scripts/inspect-code.sh           # JetBrains inspection (jb), must report nothing

Git hooks (Husky.Net): pre-commit - CSharpier check and build; pre-push - build, code inspection and scripts/web-check.sh (the web client below). HUSKY=0 turns them off (CI, Docker).

Web client (Angular)

web/ - Angular 22 with Clarity, built into the API's wwwroot. Node.js from web/.nvmrc.

cd web
npm start                         # ng serve on :4200, /api proxied to the API on :25000
npm test                          # Vitest unit tests, incl. axe-core accessibility rules (jsdom)
npm run check                     # what pre-push runs: Prettier, production build, tests
npm run a11y                      # axe-core in Chrome against the running app (showcase/), every role's pages
npm run api:gen                   # regenerate the API client (orval) after an API change

How it is written, following angular.dev's best practices for v22:

  • Standalone components, zoneless and OnPush by default (v22) - no standalone, changeDetection or zone.js.
  • State in signals (signal, computed, linkedSignal); data loading with rxResource - core/paged-list.ts pages a Clarity datagrid on top of it, going back to page 1 when the filters change.
  • Forms are Signal Forms (@angular/forms/signals): a model signal, a schema() with the rules and their messages, [formRoot] + [formField] in the template. core/forms.ts sends a form to the API and turns a 400 validation problem into errors on the fields. shared/clarity-form.ts gives Clarity's form layout to a Signal Form (Clarity's own clrForm expects the classic forms API).
  • inject(), input()/output()/model(), @if/@for, protected for template-only members, @Service().
  • TypeScript strict, strictTemplates and strictStandalone; the API client is generated, never edited.
  • Feature folders (requests/, companies/, buildings/...), shared UI in shared/, cross-cutting code in core/.

Database migrations

scripts/add-migration.sh <Name>   # from the model, no running API or database needed

RichDomainModel (2026-11) moves data written by the previous version: "no technician" stored as 0 becomes NULL, the reporter moves from created_by_user_id to reported_by_id, and requests saved without a building (building_id = 0, a bug in the old resident endpoint) get the building the reporter lived in when they reported it. If a request still has no known reporter or building, the migration stops and names the count; fix those rows by hand and start the API again.

API changes against the previous version

The HTTP contract was redone. Clients have to be updated:

  • every route is under /api/v1 (/resident-requests → /api/v1/my/maintenance-requests, /api/company/requests → /api/v1/my-company/maintenance-requests, /api/company-manage → /api/v1/companies, /api/technician/requests → /api/v1/my/assigned-requests, /api/auth/session-info → /api/v1/auth/me);
  • a missing role is 403 (was 422), an unknown or foreign record is 404, a business rule refusal is 409, invalid input is 400 with errors per field - all as ProblemDetails;
  • creating returns 201 with Location and the created record; state changes return 204;
  • paging is page / pageSize; ids in responses are the public GUIDs only;
  • login: wrong user name and wrong password give the same 401.

New: residents can list, read and withdraw their requests; company admins can read a request with its history, close or reopen completed work and list their technicians.

License

MIT

About

WeFix – maintenance requests for residential buildings: residents report, company admins assign, technicians fix. .NET 10 Minimal API (vertical slices, DDD status machine), EF Core 10 + PostgreSQL 18, JWT, Angular 22 + Clarity.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages