Primary backend for Cynara: a configurable clinical platform for hospitals.
Built with ASP.NET Core. Implements the technology-neutral clinical form and
workflow schema contracts, whose meta-schemas live in
src/Cynara.Infrastructure/Schemas/v1/
and are served by the API at /schemas/v1/{contract}.schema.json so every
client validates against the same documents the API uses at runtime.
| Repository | Role |
|---|---|
| cynara-web | React frontend (primary) |
Validation must pass both layers defined in the contract:
- Structural — JSON Schema Draft 2020-12 against
schemas/v1/*.schema.jsonfromsrc/Cynara.Infrastructure/Schemas/v1/ - Semantic — the semantic rules (unique ids, cross-schema references, graph connectivity for workflows) enforced by the reference validator and integration tests
Recommended libraries: JsonSchema.Net or NJsonSchema with System.Text.Json.
The contract documents are served over HTTP for external tooling:
GET /schemas/v1/clinical-schema.schema.json, ui-schema.schema.json,
rules-schema.schema.json, and workflow-schema.schema.json.
Prerequisites: .NET SDK 9
dotnet restore # also installs git hooks via Husky.Net
dotnet run --project src/Cynara.ApiThe API listens on http://localhost:5000 by default.
In Development, interactive OpenAPI docs are at
http://localhost:5000/scalar/v1
(JSON document: /swagger/v1/swagger.json).
The HTTP contract is JSON:API (application/vnd.api+json) via
JsonApiDotNetCore, with resource routes under /api (for example
/api/formDefinitions, /api/formVersions). Workflow actions such as
publish/review/complete remain custom routes on those resources. Sample
requests live in http/cynara.http.
The OpenAPI 3.0 document is generated deterministically from source and
committed at contracts/openapi.json. Client
generators consume the committed artifact, so the contract is reviewable in
pull requests and CI fails on unintended drift.
Regenerate it after any endpoint or schema change:
dotnet cake --target=OpenApiExport # writes contracts/openapi.json
dotnet cake --target=OpenApiCheck # fails if a fresh export differs from the committed fileOpenApiCheck runs as part of dotnet cake --target=Check. The snapshot
suite (tests/Cynara.Api.Tests/OpenApiSnapshotTests.cs) regenerates the
document in-process and compares it byte-for-byte against the committed file,
verifies output is deterministic across runs, and validates the document under
the OpenAPI 3.0 rule set. OpenApiContractTests keeps an inventory of the
Stage 2 route map (every path/method, documented status codes, and supported
media types: application/vnd.api+json, application/json,
text/event-stream).
cynara-web (CYN-56) generates its client from contracts/openapi.json.
| Library | Role |
|---|---|
JsonApiDotNetCore |
JSON:API resources, query, pagination |
JsonApiDotNetCore.OpenApi.Swashbuckle |
OpenAPI document for JSON:API (experimental) |
Scalar.AspNetCore |
OpenAPI UI in Development (points at Swagger JSON) |
Microsoft.Extensions.AI + OpenAI |
Chat completions for Form AI |
Polly |
Retries/timeouts on non-streaming AI calls |
FluentValidation |
Request DTO validation for retained command models |
Stateless |
Form/component/response status transitions |
Verify.Xunit |
Snapshot tests for stable contracts |
Testcontainers.PostgreSql |
Optional PostgreSQL integration tests |
JsonSchema.Net |
Structural schema validation (Draft 2020-12) |
Run everything inside the WSL terminal, not PowerShell or CMD.
cd ~/ailuracode/cynara/cynara-api
dotnet run --project src/Cynara.Api- From WSL:
http://localhost:5000/health - From Windows browser:
http://localhost:5000/health(WSL2 forwards localhost by default) - If Windows cannot reach localhost: use the WSL IP instead, e.g.
http://172.22.252.7:5000/health(hostname -Ishows the current IP)
If you see address already in use, another instance is already bound to port 5000:
fuser -k 5000/tcp
dotnet run --project src/Cynara.ApiFor interactive shells, ensure the .NET SDK is on your PATH (add to ~/.bashrc if needed):
export DOTNET_ROOT="$HOME/.dotnet"
export PATH="$DOTNET_ROOT:$PATH"Git hooks load .husky/env.sh automatically so commits from the IDE work even when your shell profile is not sourced.
Linting and formatting follow .NET conventions via NetAnalyzers, .editorconfig, and the SDK dotnet format command.
Day-to-day tasks live in Cake (build.cake).
dotnet tool restore # one-time: pulls dotnet-cake, husky, sonarscanner
dotnet cake --target=Format # write formatting changes
dotnet cake --target=FormatCheck # verify only (--verify-no-changes)
dotnet cake --target=Lint # dotnet build --no-restore -warnaserror
dotnet cake --target=Test # PostgreSQL suite via Testcontainers (needs Docker; excludes Category=E2E)
dotnet cake --target=Check # restore + format-check + lint + openapi-check + test
dotnet cake --target=Fix # format + apply safe analyzer fixes
dotnet cake --target=Sonar # local SonarQube Community Build analysis
dotnet cake --target=Seed # seed demo showcase into configured DB (no HTTP)
dotnet cake --target=OpenApiExport # regenerate contracts/openapi.json from source
dotnet cake --target=OpenApiCheck # fail if the committed contract is stalePara correr la API localmente usa dotnet run --project src/Cynara.Api (ver "Getting started").
dotnet cake --target=Test starts a disposable PostgreSQL container
(Testcontainers) and runs the full API suite against it. Set
ConnectionStrings__Default (env var or appsettings) to point at a running
PostgreSQL instance instead of the ephemeral container.
Seed resolves ConnectionStrings:Default from appsettings, env vars, or
--connection. Examples:
dotnet cake --target=Seed
dotnet cake --target=Seed --seed-args='--connection "Host=...;Database=cynara;Username=...;Password=..."'
ConnectionStrings__Default='Host=...;Database=cynara;Username=...;Password=...' dotnet cake --target=SeedRules live in .editorconfig, .globalconfig, and Directory.Build.props.
Local smell / quality-gate analysis uses SonarQube Community Build via Docker and the SonarScanner for .NET.
Prerequisites: Docker Desktop (or Docker Engine) with Compose.
dotnet cake --target=Up # full dev stack: app Postgres + pgAdmin + SonarQube
dotnet cake --target=Sonar # change admin password, bootstrap token, run scanner
# or step by step:
dotnet cake --target=SonarUp # SonarQube container only (joins the shared Postgres)
dotnet cake --target=SonarBootstrap # change default admin password + write .sonar/token
dotnet cake --target=SonarScan # begin → build → end; opens project cynara-api
dotnet cake --target=Down # stop both stacks (preserves volumes)
dotnet cake --target=Status # ps of every Cynara-related container- UIs:
- SonarQube: http://localhost:9000
- pgAdmin: http://localhost:5050 (login
admin@example.com/admin)
- Default bootstrap password:
CynaraSonarAdmin1!(override withSONAR_ADMIN_PASSWORD) - Token path:
.sonar/token(gitignored), or exportSONAR_TOKEN - Compose files:
- App stack (Postgres + pgAdmin):
docker/stack.yml - SonarQube:
docker/sonarqube/docker-compose.yml
- App stack (Postgres + pgAdmin):
- API Dockerfile:
docker/Dockerfile - Local quality gate:
Cynara Local(new_violations=0, no coverage requirement) - Local C# profile:
Cynara C#— Sonar S104 file size warning at 400 LOC (maximumFileLocThreshold=400, severity MINOR)
The app stack and SonarQube share a single Postgres instance, joined by the
external bridge network cynara-net. The instance hosts two databases:
| Database | Owner | Used by |
|---|---|---|
postgres |
postgres |
Cynara API (EF Core, dev seed) |
sonar |
sonar |
SonarQube |
The sonar database and role are created on first boot from
docker/postgres/init-sonar.sql. SonarQube connects via
jdbc:postgresql://postgresql:5432/sonar — never with superuser.
Reset everything (destroy both databases):
docker compose -f docker/stack.yml down -v
docker compose -f docker/sonarqube/docker-compose.yml down -v
dotnet cake --target=UpReset only the app data, keep SonarQube history:
docker compose -f docker/stack.yml down
docker volume rm cynara_postgresql-data
dotnet cake --target=UpFirst boot can take 1–2 minutes while Elasticsearch starts. Re-run dotnet cake --target=SonarScan after code changes to refresh findings.
Cynara API is a modular monolith. The HTTP contract remains stable while each feature owns its endpoints, application workflows, persistence ports, and EF adapters.
| Module | Responsibility |
|---|---|
Forms |
Form definitions, drafts, versioning, review, publication, compilation |
Components |
Reusable component definitions and versions |
FormResponses |
Response drafts, completion, validation, and revisions |
Audit |
Audit event writing and filtered audit queries |
Schemas |
Serves the versioned clinical schema contract over HTTP |
Health |
Service health endpoint |
src/Cynara.Api/Modules/<Feature>/
src/Cynara.Application/Modules/<Feature>/
src/Cynara.Infrastructure/Modules/<Feature>/
src/Cynara.Domain/<Feature>/
Application modules expose ports and workflows. Infrastructure modules implement those ports with EF Core repositories and entity configurations. API modules only translate HTTP requests into application calls.
Repositories stage changes but do not call SaveChangesAsync. Each mutating
workflow injects IUnitOfWork and commits once. IAuditWriter stages the audit
event in the same unit of work, so a business mutation and its audit record cannot
commit independently.
Features with separate read and state-transition concerns use distinct services:
- Forms:
FormServiceandFormReviewService - Components:
ComponentQueriesServiceandComponentLifecycleService - Form responses:
FormResponseQueriesServiceandFormResponseLifecycleService
Avoid adding a generic repository or moving business rules into endpoints. Keep state transitions and validation in Application, domain entities in Domain, and database concerns in Infrastructure.
On dotnet restore, Husky.Net installs a pre-commit hook that:
- Formats staged
.csfiles (dotnet format) - Lints the solution (
dotnet build -warnaserror)
Tests run separately via dotnet cake --target=Test or CI (dotnet cake --target=Check). Disable hooks with HUSKY=0 git commit.
NuGet vulnerability audit is disabled locally (NuGetAudit=false in Directory.Build.props) so pre-commit does not require nuget.org. Enable it in CI with -p:NuGetAudit=true if needed.
If a previous failed restore cached NU1900 under obj/, run once:
dotnet clean && dotnet restoreIf formatting changes files, re-stage and commit again:
git add -u && git commitManual hook setup:
dotnet tool restore
dotnet husky installsrc/Cynara.Api/ ASP.NET Core host and HTTP modules
src/Cynara.Application/ Workflows, ports, DTOs, validators, compilers
src/Cynara.Domain/ Entities and domain status models
src/Cynara.Infrastructure/ EF Core, repositories, schemas, SeedData
tests/Cynara.Api.Tests/ Integration and workflow tests
tools/Cynara.Seed/ In-process demo showcase seeder CLI
tools/Cynara.OpenApiExport/ In-process OpenAPI exporter CLI (writes contracts/openapi.json)
contracts/openapi.json Committed OpenAPI 3.0 contract (client-generation source)
docker/ Compose stacks, API Dockerfile, Postgres init SQL
├─ Dockerfile Cynara API image
├─ stack.yml Shared Postgres + pgAdmin
├─ postgres/init-sonar.sql Creates `sonar` DB on first boot
├─ pgadmin/servers.json pgAdmin seed (Cynara Postgres + SonarQube QA)
└─ sonarqube/docker-compose.yml SonarQube (joins `cynara-net`)
scripts/ Local SonarQube bootstrap/scan helpers
.cursor/ Cursor MCP config + Postgres/SonarQube wrappers
MIT — see LICENSE.