From 6daf1d713d2d0047d8f9e1c59ce2901a97e38066 Mon Sep 17 00:00:00 2001
From: Christian Findlay <16697547+MelbourneDeveloper@users.noreply.github.com>
Date: Sat, 4 Apr 2026 08:21:17 +1100
Subject: [PATCH 01/32] Delete samples
---
.claude/skills/container-logs/SKILL.md | 48 -
.claude/skills/run-samples/SKILL.md | 52 -
.github/workflows/ci.yml | 9 -
.github/workflows/release.yml | 16 +
.vscode/launch.json | 30 +-
.vscode/tasks.json | 96 -
CLAUDE.md | 2 +-
DataProvider.sln | 142 +-
.../DataProvider.Postgres.Cli.csproj | 2 +-
.../DataProvider.SQLite.Cli.csproj | 2 +-
.../DataProvider.SQLite.csproj | 2 +-
.../DataProvider.SqlServer.csproj | 2 +-
DataProvider/DataProvider/DataProvider.csproj | 2 +-
.../Gatekeeper.Api/Gatekeeper.Api.csproj | 1 +
Lql/Lql.Postgres/Lql.Postgres.csproj | 3 +
Lql/Lql.SQLite/Lql.SQLite.csproj | 3 +
Lql/Lql.SqlServer/Lql.SqlServer.csproj | 3 +
Lql/Lql/Lql.csproj | 1 +
Migration/Migration.Cli/Migration.Cli.csproj | 2 +-
.../Migration.Postgres.csproj | 2 +-
.../Migration.SQLite/Migration.SQLite.csproj | 2 +-
Migration/Migration/Migration.csproj | 2 +-
Other/Selecta/Selecta.csproj | 2 +-
Samples/.editorconfig | 10 -
.../Clinical.Api.Tests/AuthorizationTests.cs | 253 -
.../Clinical.Api.Tests.csproj | 28 -
.../Clinical.Api.Tests/ClinicalApiFactory.cs | 84 -
.../ConditionEndpointTests.cs | 335 -
.../DashboardIntegrationTests.cs | 122 -
.../EncounterEndpointTests.cs | 276 -
.../Clinical.Api.Tests/GlobalUsings.cs | 2 -
.../MedicationRequestEndpointTests.cs | 376 -
.../PatientEndpointTests.cs | 293 -
.../Clinical.Api.Tests/SyncEndpointTests.cs | 419 -
.../SyncWorkerFaultToleranceTests.cs | 440 -
Samples/Clinical/Clinical.Api/.editorconfig | 10 -
.../Clinical/Clinical.Api/Clinical.Api.csproj | 80 -
.../Clinical/Clinical.Api/DataProvider.json | 67 -
.../Clinical/Clinical.Api/DatabaseSetup.cs | 83 -
.../Clinical.Api/FileLoggerProvider.cs | 109 -
Samples/Clinical/Clinical.Api/GlobalUsings.cs | 95 -
Samples/Clinical/Clinical.Api/Program.cs | 865 -
.../Properties/launchSettings.json | 14 -
.../Queries/GetConditionsByPatient.lql | 6 -
.../Queries/GetEncountersByPatient.lql | 6 -
.../Queries/GetMedicationsByPatient.lql | 6 -
.../Clinical.Api/Queries/GetPatientById.lql | 5 -
.../Clinical.Api/Queries/GetPatients.lql | 6 -
.../Clinical.Api/Queries/SearchPatients.lql | 6 -
Samples/Clinical/Clinical.Api/Requests.cs | 84 -
Samples/Clinical/Clinical.Api/SyncHelpers.cs | 24 -
.../Clinical.Api/clinical-schema.yaml | 227 -
.../Clinical.Sync/Clinical.Sync.csproj | 22 -
.../Clinical/Clinical.Sync/GlobalUsings.cs | 1 -
Samples/Clinical/Clinical.Sync/Program.cs | 71 -
.../Clinical/Clinical.Sync/SyncMappings.json | 27 -
Samples/Clinical/Clinical.Sync/SyncWorker.cs | 356 -
.../AppointmentE2ETests.cs | 177 -
.../AuthE2ETests.cs | 393 -
.../CalendarE2ETests.cs | 288 -
.../Dashboard.Integration.Tests.csproj | 47 -
.../DashboardApiCorsTests.cs | 505 -
.../DashboardE2ETests.cs | 2243 --
.../GlobalUsings.cs | 3 -
.../Icd10E2ETests.cs | 588 -
.../NavigationE2ETests.cs | 272 -
.../PatientE2ETests.cs | 239 -
.../PractitionerE2ETests.cs | 309 -
.../SyncE2ETests.cs | 741 -
.../xunit.runner.json | 9 -
.../Dashboard.Web/.config/dotnet-tools.json | 13 -
.../Dashboard/Dashboard.Web/Api/ApiClient.cs | 528 -
Samples/Dashboard/Dashboard.Web/App.cs | 305 -
.../Dashboard.Web/Components/DataTable.cs | 168 -
.../Dashboard.Web/Components/Header.cs | 92 -
.../Dashboard.Web/Components/Icons.cs | 438 -
.../Dashboard.Web/Components/MetricCard.cs | 120 -
.../Dashboard.Web/Components/Sidebar.cs | 285 -
.../Dashboard.Web/Dashboard.Web.csproj | 53 -
.../Dashboard.Web/Models/ClinicalModels.cs | 186 -
.../Dashboard.Web/Models/Icd10Models.cs | 299 -
.../Dashboard.Web/Models/SchedulingModels.cs | 141 -
.../Dashboard.Web/Pages/AppointmentsPage.cs | 533 -
.../Dashboard.Web/Pages/CalendarPage.cs | 630 -
.../Dashboard.Web/Pages/ClinicalCodingPage.cs | 1568 -
.../Dashboard.Web/Pages/DashboardPage.cs | 361 -
.../Pages/EditAppointmentPage.cs | 777 -
.../Dashboard.Web/Pages/EditPatientPage.cs | 727 -
.../Dashboard.Web/Pages/PatientsPage.cs | 383 -
.../Dashboard.Web/Pages/PractitionersPage.cs | 432 -
Samples/Dashboard/Dashboard.Web/Program.cs | 80 -
.../Dashboard/Dashboard.Web/React/Elements.cs | 461 -
.../Dashboard/Dashboard.Web/React/Hooks.cs | 114 -
.../Dashboard.Web/React/ReactInterop.cs | 72 -
Samples/Dashboard/Dashboard.Web/h5.json | 10 -
.../Dashboard.Web/wwwroot/css/base.css | 636 -
.../Dashboard.Web/wwwroot/css/components.css | 1890 -
.../Dashboard.Web/wwwroot/css/layout.css | 1460 -
.../Dashboard.Web/wwwroot/css/variables.css | 203 -
.../Dashboard.Web/wwwroot/index.html | 3623 --
Samples/Dashboard/Directory.Build.props | 14 -
Samples/Dashboard/run-e2e-tests.sh | 80 -
Samples/Dashboard/spec.md | 116 -
Samples/Healthcare.Sync.http | 95 -
Samples/ICD10/.gitignore | 21 -
.../ICD10.Api.Tests/AchiEndpointTests.cs | 121 -
.../ICD10.Api.Tests/ChapterCategoryTests.cs | 265 -
.../ICD10.Api.Tests/ChapterEndpointTests.cs | 97 -
.../ICD10/ICD10.Api.Tests/CodeLookupTests.cs | 301 -
Samples/ICD10/ICD10.Api.Tests/GlobalUsings.cs | 7 -
.../ICD10.Api.Tests/HealthEndpointTests.cs | 32 -
.../ICD10.Api.Tests/ICD10.Api.Tests.csproj | 28 -
.../ICD10/ICD10.Api.Tests/ICD10ApiFactory.cs | 133 -
.../ICD10.Api.Tests/SearchEndpointTests.cs | 565 -
.../ICD10/ICD10.Api.Tests/TestDataSeeder.cs | 709 -
Samples/ICD10/ICD10.Api/.gitignore | 1 -
Samples/ICD10/ICD10.Api/DataProvider.json | 136 -
Samples/ICD10/ICD10.Api/DatabaseSetup.cs | 152 -
Samples/ICD10/ICD10.Api/GlobalUsings.cs | 102 -
Samples/ICD10/ICD10.Api/ICD10.Api.csproj | 82 -
.../ICD10.Api/Properties/launchSettings.json | 14 -
.../ICD10/ICD10.Api/Queries/GetAchiBlocks.lql | 3 -
.../ICD10.Api/Queries/GetAchiCodeByCode.lql | 4 -
.../ICD10.Api/Queries/GetAchiCodesByBlock.lql | 4 -
.../Queries/GetAllCodeEmbeddings.lql | 4 -
.../ICD10.Api/Queries/GetBlocksByChapter.lql | 4 -
.../Queries/GetCategoriesByBlock.lql | 4 -
.../ICD10/ICD10.Api/Queries/GetChapters.lql | 3 -
.../ICD10/ICD10.Api/Queries/GetCodeByCode.lql | 6 -
.../ICD10.Api/Queries/GetCodeEmbedding.lql | 4 -
.../ICD10.Api/Queries/GetCodesByCategory.lql | 4 -
.../ICD10.Api/Queries/SearchAchiCodes.sql | 5 -
.../ICD10.Api/Queries/SearchIcd10Codes.sql | 12 -
.../ICD10.Api/Vocabularies/base_uncased.txt | 30522 ----------------
Samples/ICD10/ICD10.Api/icd10-schema.yaml | 359 -
Samples/ICD10/ICD10.Cli.Tests/CliE2ETests.cs | 1170 -
.../ICD10/ICD10.Cli.Tests/CliTestFixture.cs | 40 -
Samples/ICD10/ICD10.Cli.Tests/GlobalUsings.cs | 5 -
.../ICD10.Cli.Tests/ICD10.Cli.Tests.csproj | 32 -
Samples/ICD10/ICD10.Cli/GlobalUsings.cs | 42 -
Samples/ICD10/ICD10.Cli/ICD10.Cli.csproj | 19 -
Samples/ICD10/ICD10.Cli/Program.cs | 979 -
Samples/ICD10/README.md | 133 -
Samples/ICD10/SPEC.md | 457 -
Samples/ICD10/embedding-service/Dockerfile | 32 -
.../embedding-service/docker-compose.yml | 23 -
Samples/ICD10/embedding-service/main.py | 138 -
.../ICD10/embedding-service/requirements.txt | 6 -
.../scripts/CreateDb/generate_embeddings.py | 191 -
.../scripts/CreateDb/generate_sample_data.py | 321 -
Samples/ICD10/scripts/CreateDb/import.sh | 58 -
.../ICD10/scripts/CreateDb/import_icd10cm.py | 572 -
.../ICD10/scripts/CreateDb/import_postgres.py | 608 -
.../ICD10/scripts/CreateDb/requirements.txt | 30 -
Samples/ICD10/scripts/Dependencies/start.sh | 31 -
Samples/ICD10/scripts/Dependencies/stop.sh | 16 -
Samples/ICD10/scripts/run.sh | 32 -
.../AppointmentEndpointTests.cs | 355 -
.../AuthorizationTests.cs | 258 -
.../DashboardIntegrationTests.cs | 96 -
.../Scheduling.Api.Tests/GlobalUsings.cs | 2 -
.../PractitionerEndpointTests.cs | 314 -
.../Scheduling.Api.Tests.csproj | 28 -
.../SchedulingApiFactory.cs | 77 -
.../SchedulingSyncTests.cs | 365 -
.../Scheduling.Api.Tests/SyncEndpointTests.cs | 337 -
.../Scheduling.Api/DataProvider.json | 73 -
.../Scheduling.Api/DatabaseSetup.cs | 68 -
.../Scheduling.Api/FileLoggerProvider.cs | 109 -
.../Scheduling.Api/GlobalSuppressions.cs | 75 -
.../Scheduling/Scheduling.Api/GlobalUsings.cs | 116 -
Samples/Scheduling/Scheduling.Api/Program.cs | 821 -
.../Properties/launchSettings.json | 14 -
.../Queries/CheckSchedulingConflicts.lql | 5 -
.../Queries/GetAllPractitioners.lql | 4 -
.../Queries/GetAppointmentById.lql | 5 -
.../Queries/GetAppointmentsByPatient.lql | 6 -
.../Queries/GetAppointmentsByPractitioner.lql | 6 -
.../Queries/GetAppointmentsByStatus.lql | 8 -
.../Queries/GetAvailableSlots.lql | 7 -
.../Queries/GetPractitionerById.lql | 5 -
.../Queries/GetProviderAvailability.lql | 6 -
.../Queries/GetProviderDailySchedule.lql | 7 -
.../Queries/GetUpcomingAppointments.lql | 5 -
.../SearchPractitionersBySpecialty.lql | 6 -
Samples/Scheduling/Scheduling.Api/Requests.cs | 61 -
.../Scheduling.Api/Scheduling.Api.csproj | 80 -
.../Scheduling/Scheduling.Api/SyncHelpers.cs | 24 -
.../Scheduling.Api/scheduling-schema.yaml | 162 -
.../Scheduling.Sync/GlobalUsings.cs | 4 -
Samples/Scheduling/Scheduling.Sync/Program.cs | 32 -
.../Scheduling.Sync/Scheduling.Sync.csproj | 15 -
.../Scheduling.Sync/SchedulingSyncWorker.cs | 374 -
.../Scheduling.Sync/SyncMappings.json | 34 -
Samples/Shared/Authorization/AuthHelpers.cs | 206 -
Samples/Shared/Authorization/AuthRecords.cs | 47 -
.../Shared/Authorization/Authorization.csproj | 12 -
.../Authorization/EndpointFilterFactories.cs | 195 -
.../Authorization/PermissionConstants.cs | 98 -
.../Shared/Authorization/TestTokenHelper.cs | 108 -
Samples/docker/.env.example | 17 -
Samples/docker/.gitignore | 2 -
Samples/docker/Dockerfile.app | 59 -
Samples/docker/Dockerfile.dashboard | 13 -
Samples/docker/README.md | 83 -
Samples/docker/docker-compose.yml | 58 -
Samples/docker/init-db/init.sh | 32 -
Samples/docker/nginx.conf | 39 -
Samples/docker/start-services.sh | 95 -
Samples/readme.md | 157 -
Samples/scripts/clean-local.sh | 34 -
Samples/scripts/clean.sh | 34 -
Samples/scripts/start-local.sh | 177 -
Samples/scripts/start.sh | 39 -
Sync/Sync.Http/Sync.Http.csproj | 1 +
Sync/Sync.Postgres/Sync.Postgres.csproj | 1 +
Sync/Sync.SQLite/Sync.SQLite.csproj | 1 +
Sync/Sync/Sync.csproj | 1 +
Website/src/about.md | 2 +-
Website/src/docs/samples.md | 50 +-
Website/src/index.njk | 2 +-
docker-compose.postgres.yml | 1 -
222 files changed, 64 insertions(+), 72044 deletions(-)
delete mode 100644 .claude/skills/container-logs/SKILL.md
delete mode 100644 .claude/skills/run-samples/SKILL.md
delete mode 100644 Samples/.editorconfig
delete mode 100644 Samples/Clinical/Clinical.Api.Tests/AuthorizationTests.cs
delete mode 100644 Samples/Clinical/Clinical.Api.Tests/Clinical.Api.Tests.csproj
delete mode 100644 Samples/Clinical/Clinical.Api.Tests/ClinicalApiFactory.cs
delete mode 100644 Samples/Clinical/Clinical.Api.Tests/ConditionEndpointTests.cs
delete mode 100644 Samples/Clinical/Clinical.Api.Tests/DashboardIntegrationTests.cs
delete mode 100644 Samples/Clinical/Clinical.Api.Tests/EncounterEndpointTests.cs
delete mode 100644 Samples/Clinical/Clinical.Api.Tests/GlobalUsings.cs
delete mode 100644 Samples/Clinical/Clinical.Api.Tests/MedicationRequestEndpointTests.cs
delete mode 100644 Samples/Clinical/Clinical.Api.Tests/PatientEndpointTests.cs
delete mode 100644 Samples/Clinical/Clinical.Api.Tests/SyncEndpointTests.cs
delete mode 100644 Samples/Clinical/Clinical.Api.Tests/SyncWorkerFaultToleranceTests.cs
delete mode 100644 Samples/Clinical/Clinical.Api/.editorconfig
delete mode 100644 Samples/Clinical/Clinical.Api/Clinical.Api.csproj
delete mode 100644 Samples/Clinical/Clinical.Api/DataProvider.json
delete mode 100644 Samples/Clinical/Clinical.Api/DatabaseSetup.cs
delete mode 100644 Samples/Clinical/Clinical.Api/FileLoggerProvider.cs
delete mode 100644 Samples/Clinical/Clinical.Api/GlobalUsings.cs
delete mode 100644 Samples/Clinical/Clinical.Api/Program.cs
delete mode 100644 Samples/Clinical/Clinical.Api/Properties/launchSettings.json
delete mode 100644 Samples/Clinical/Clinical.Api/Queries/GetConditionsByPatient.lql
delete mode 100644 Samples/Clinical/Clinical.Api/Queries/GetEncountersByPatient.lql
delete mode 100644 Samples/Clinical/Clinical.Api/Queries/GetMedicationsByPatient.lql
delete mode 100644 Samples/Clinical/Clinical.Api/Queries/GetPatientById.lql
delete mode 100644 Samples/Clinical/Clinical.Api/Queries/GetPatients.lql
delete mode 100644 Samples/Clinical/Clinical.Api/Queries/SearchPatients.lql
delete mode 100644 Samples/Clinical/Clinical.Api/Requests.cs
delete mode 100644 Samples/Clinical/Clinical.Api/SyncHelpers.cs
delete mode 100644 Samples/Clinical/Clinical.Api/clinical-schema.yaml
delete mode 100644 Samples/Clinical/Clinical.Sync/Clinical.Sync.csproj
delete mode 100644 Samples/Clinical/Clinical.Sync/GlobalUsings.cs
delete mode 100644 Samples/Clinical/Clinical.Sync/Program.cs
delete mode 100644 Samples/Clinical/Clinical.Sync/SyncMappings.json
delete mode 100644 Samples/Clinical/Clinical.Sync/SyncWorker.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/AppointmentE2ETests.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/AuthE2ETests.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/CalendarE2ETests.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/Dashboard.Integration.Tests.csproj
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/DashboardApiCorsTests.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/DashboardE2ETests.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/GlobalUsings.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/Icd10E2ETests.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/NavigationE2ETests.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/PatientE2ETests.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/PractitionerE2ETests.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/SyncE2ETests.cs
delete mode 100644 Samples/Dashboard/Dashboard.Integration.Tests/xunit.runner.json
delete mode 100644 Samples/Dashboard/Dashboard.Web/.config/dotnet-tools.json
delete mode 100644 Samples/Dashboard/Dashboard.Web/Api/ApiClient.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/App.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Components/DataTable.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Components/Header.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Components/Icons.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Components/MetricCard.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Components/Sidebar.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Dashboard.Web.csproj
delete mode 100644 Samples/Dashboard/Dashboard.Web/Models/ClinicalModels.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Models/Icd10Models.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Models/SchedulingModels.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Pages/AppointmentsPage.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Pages/CalendarPage.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Pages/ClinicalCodingPage.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Pages/DashboardPage.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Pages/EditAppointmentPage.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Pages/EditPatientPage.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Pages/PatientsPage.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Pages/PractitionersPage.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/Program.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/React/Elements.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/React/Hooks.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/React/ReactInterop.cs
delete mode 100644 Samples/Dashboard/Dashboard.Web/h5.json
delete mode 100644 Samples/Dashboard/Dashboard.Web/wwwroot/css/base.css
delete mode 100644 Samples/Dashboard/Dashboard.Web/wwwroot/css/components.css
delete mode 100644 Samples/Dashboard/Dashboard.Web/wwwroot/css/layout.css
delete mode 100644 Samples/Dashboard/Dashboard.Web/wwwroot/css/variables.css
delete mode 100644 Samples/Dashboard/Dashboard.Web/wwwroot/index.html
delete mode 100644 Samples/Dashboard/Directory.Build.props
delete mode 100755 Samples/Dashboard/run-e2e-tests.sh
delete mode 100644 Samples/Dashboard/spec.md
delete mode 100644 Samples/Healthcare.Sync.http
delete mode 100644 Samples/ICD10/.gitignore
delete mode 100644 Samples/ICD10/ICD10.Api.Tests/AchiEndpointTests.cs
delete mode 100644 Samples/ICD10/ICD10.Api.Tests/ChapterCategoryTests.cs
delete mode 100644 Samples/ICD10/ICD10.Api.Tests/ChapterEndpointTests.cs
delete mode 100644 Samples/ICD10/ICD10.Api.Tests/CodeLookupTests.cs
delete mode 100644 Samples/ICD10/ICD10.Api.Tests/GlobalUsings.cs
delete mode 100644 Samples/ICD10/ICD10.Api.Tests/HealthEndpointTests.cs
delete mode 100644 Samples/ICD10/ICD10.Api.Tests/ICD10.Api.Tests.csproj
delete mode 100644 Samples/ICD10/ICD10.Api.Tests/ICD10ApiFactory.cs
delete mode 100644 Samples/ICD10/ICD10.Api.Tests/SearchEndpointTests.cs
delete mode 100644 Samples/ICD10/ICD10.Api.Tests/TestDataSeeder.cs
delete mode 100644 Samples/ICD10/ICD10.Api/.gitignore
delete mode 100644 Samples/ICD10/ICD10.Api/DataProvider.json
delete mode 100644 Samples/ICD10/ICD10.Api/DatabaseSetup.cs
delete mode 100644 Samples/ICD10/ICD10.Api/GlobalUsings.cs
delete mode 100644 Samples/ICD10/ICD10.Api/ICD10.Api.csproj
delete mode 100644 Samples/ICD10/ICD10.Api/Properties/launchSettings.json
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/GetAchiBlocks.lql
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/GetAchiCodeByCode.lql
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/GetAchiCodesByBlock.lql
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/GetAllCodeEmbeddings.lql
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/GetBlocksByChapter.lql
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/GetCategoriesByBlock.lql
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/GetChapters.lql
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/GetCodeByCode.lql
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/GetCodeEmbedding.lql
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/GetCodesByCategory.lql
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/SearchAchiCodes.sql
delete mode 100644 Samples/ICD10/ICD10.Api/Queries/SearchIcd10Codes.sql
delete mode 100644 Samples/ICD10/ICD10.Api/Vocabularies/base_uncased.txt
delete mode 100644 Samples/ICD10/ICD10.Api/icd10-schema.yaml
delete mode 100644 Samples/ICD10/ICD10.Cli.Tests/CliE2ETests.cs
delete mode 100644 Samples/ICD10/ICD10.Cli.Tests/CliTestFixture.cs
delete mode 100644 Samples/ICD10/ICD10.Cli.Tests/GlobalUsings.cs
delete mode 100644 Samples/ICD10/ICD10.Cli.Tests/ICD10.Cli.Tests.csproj
delete mode 100644 Samples/ICD10/ICD10.Cli/GlobalUsings.cs
delete mode 100644 Samples/ICD10/ICD10.Cli/ICD10.Cli.csproj
delete mode 100644 Samples/ICD10/ICD10.Cli/Program.cs
delete mode 100644 Samples/ICD10/README.md
delete mode 100644 Samples/ICD10/SPEC.md
delete mode 100644 Samples/ICD10/embedding-service/Dockerfile
delete mode 100644 Samples/ICD10/embedding-service/docker-compose.yml
delete mode 100644 Samples/ICD10/embedding-service/main.py
delete mode 100644 Samples/ICD10/embedding-service/requirements.txt
delete mode 100644 Samples/ICD10/scripts/CreateDb/generate_embeddings.py
delete mode 100644 Samples/ICD10/scripts/CreateDb/generate_sample_data.py
delete mode 100755 Samples/ICD10/scripts/CreateDb/import.sh
delete mode 100644 Samples/ICD10/scripts/CreateDb/import_icd10cm.py
delete mode 100644 Samples/ICD10/scripts/CreateDb/import_postgres.py
delete mode 100644 Samples/ICD10/scripts/CreateDb/requirements.txt
delete mode 100755 Samples/ICD10/scripts/Dependencies/start.sh
delete mode 100755 Samples/ICD10/scripts/Dependencies/stop.sh
delete mode 100755 Samples/ICD10/scripts/run.sh
delete mode 100644 Samples/Scheduling/Scheduling.Api.Tests/AppointmentEndpointTests.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api.Tests/AuthorizationTests.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api.Tests/DashboardIntegrationTests.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api.Tests/GlobalUsings.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api.Tests/PractitionerEndpointTests.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api.Tests/Scheduling.Api.Tests.csproj
delete mode 100644 Samples/Scheduling/Scheduling.Api.Tests/SchedulingApiFactory.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api.Tests/SchedulingSyncTests.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api.Tests/SyncEndpointTests.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api/DataProvider.json
delete mode 100644 Samples/Scheduling/Scheduling.Api/DatabaseSetup.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api/FileLoggerProvider.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api/GlobalSuppressions.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api/GlobalUsings.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api/Program.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api/Properties/launchSettings.json
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/CheckSchedulingConflicts.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/GetAllPractitioners.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/GetAppointmentById.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/GetAppointmentsByPatient.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/GetAppointmentsByPractitioner.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/GetAppointmentsByStatus.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/GetAvailableSlots.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/GetPractitionerById.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/GetProviderAvailability.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/GetProviderDailySchedule.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/GetUpcomingAppointments.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Queries/SearchPractitionersBySpecialty.lql
delete mode 100644 Samples/Scheduling/Scheduling.Api/Requests.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api/Scheduling.Api.csproj
delete mode 100644 Samples/Scheduling/Scheduling.Api/SyncHelpers.cs
delete mode 100644 Samples/Scheduling/Scheduling.Api/scheduling-schema.yaml
delete mode 100644 Samples/Scheduling/Scheduling.Sync/GlobalUsings.cs
delete mode 100644 Samples/Scheduling/Scheduling.Sync/Program.cs
delete mode 100644 Samples/Scheduling/Scheduling.Sync/Scheduling.Sync.csproj
delete mode 100644 Samples/Scheduling/Scheduling.Sync/SchedulingSyncWorker.cs
delete mode 100644 Samples/Scheduling/Scheduling.Sync/SyncMappings.json
delete mode 100644 Samples/Shared/Authorization/AuthHelpers.cs
delete mode 100644 Samples/Shared/Authorization/AuthRecords.cs
delete mode 100644 Samples/Shared/Authorization/Authorization.csproj
delete mode 100644 Samples/Shared/Authorization/EndpointFilterFactories.cs
delete mode 100644 Samples/Shared/Authorization/PermissionConstants.cs
delete mode 100644 Samples/Shared/Authorization/TestTokenHelper.cs
delete mode 100644 Samples/docker/.env.example
delete mode 100644 Samples/docker/.gitignore
delete mode 100644 Samples/docker/Dockerfile.app
delete mode 100644 Samples/docker/Dockerfile.dashboard
delete mode 100644 Samples/docker/README.md
delete mode 100644 Samples/docker/docker-compose.yml
delete mode 100755 Samples/docker/init-db/init.sh
delete mode 100644 Samples/docker/nginx.conf
delete mode 100644 Samples/docker/start-services.sh
delete mode 100644 Samples/readme.md
delete mode 100755 Samples/scripts/clean-local.sh
delete mode 100755 Samples/scripts/clean.sh
delete mode 100755 Samples/scripts/start-local.sh
delete mode 100755 Samples/scripts/start.sh
diff --git a/.claude/skills/container-logs/SKILL.md b/.claude/skills/container-logs/SKILL.md
deleted file mode 100644
index 76f05e70..00000000
--- a/.claude/skills/container-logs/SKILL.md
+++ /dev/null
@@ -1,48 +0,0 @@
----
-name: container-logs
-description: View Docker container logs for the Healthcare Samples stack. Use when asked to check logs, debug container issues, or see service output.
-disable-model-invocation: true
-allowed-tools: Bash(docker compose *), Bash(docker logs *)
-argument-hint: "[container-name] [--tail N]"
----
-
-# Container Logs
-
-View logs from the Healthcare Samples Docker stack.
-
-## Usage
-
-`/container-logs` - show recent logs from all containers
-`/container-logs app` - show logs from the app container
-`/container-logs db` - show logs from the Postgres container
-
-## Commands
-
-All logs (last 100 lines):
-```bash
-docker compose -f /Users/christianfindlay/Documents/Code/DataProvider/Samples/docker/docker-compose.yml logs --tail 100
-```
-
-Specific container:
-```bash
-docker compose -f /Users/christianfindlay/Documents/Code/DataProvider/Samples/docker/docker-compose.yml logs --tail 100 $ARGUMENTS
-```
-
-Follow logs in real-time (use timeout to avoid hanging):
-```bash
-timeout 10 docker compose -f /Users/christianfindlay/Documents/Code/DataProvider/Samples/docker/docker-compose.yml logs -f $ARGUMENTS
-```
-
-## Container names
-
-| Name | Service |
-|------|---------|
-| app | All .NET APIs + embedding service |
-| db | Postgres 16 + pgvector |
-| dashboard | nginx serving static files |
-
-## Check container status
-
-```bash
-docker compose -f /Users/christianfindlay/Documents/Code/DataProvider/Samples/docker/docker-compose.yml ps
-```
diff --git a/.claude/skills/run-samples/SKILL.md b/.claude/skills/run-samples/SKILL.md
deleted file mode 100644
index ce2f8c04..00000000
--- a/.claude/skills/run-samples/SKILL.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-name: run-samples
-description: Start the Healthcare Samples stack (Postgres, APIs, Dashboard). Use when asked to run, start, or launch the sample applications.
----
-
-# Run Samples
-
-Start the full Healthcare Samples stack. Decide based on `$ARGUMENTS`:
-
-IMPORTANT: Do NOT run in the background. Run in the foreground so the user can see all output streaming in real-time. Set a long timeout (600000ms).
-
-## Default (no args) - keep existing data
-
-Run with existing database volumes intact:
-
-```bash
-cd /Users/christianfindlay/Documents/Code/DataProvider/Samples/scripts && ./start.sh
-```
-
-## Fresh start - blow away databases
-
-If the user says "fresh", "clean", "reset", or `$ARGUMENTS` contains `--fresh`:
-
-```bash
-cd /Users/christianfindlay/Documents/Code/DataProvider/Samples/scripts && ./start.sh --fresh
-```
-
-## Force rebuild containers
-
-If the user says "rebuild" or `$ARGUMENTS` contains `--build`:
-
-```bash
-cd /Users/christianfindlay/Documents/Code/DataProvider/Samples/scripts && ./start.sh --build
-```
-
-## Both fresh + rebuild
-
-```bash
-cd /Users/christianfindlay/Documents/Code/DataProvider/Samples/scripts && ./start.sh --fresh --build
-```
-
-## Services
-
-| Service | Port |
-|---------|------|
-| Gatekeeper API | 5002 |
-| Clinical API | 5080 |
-| Scheduling API | 5001 |
-| ICD10 API | 5090 |
-| Embedding Service | 8000 |
-| Dashboard | 5173 |
-| Postgres | 5432 |
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7da45b63..b04e30d2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -131,8 +131,6 @@ jobs:
- 'Directory.Packages.props'
postgres:
- 'Gatekeeper/**'
- - 'Samples/Clinical/**'
- - 'Samples/Scheduling/**'
- 'Sync/**'
- 'DataProvider/**'
- 'Migration/**'
@@ -266,13 +264,6 @@ jobs:
env:
TEST_POSTGRES_CONNECTION: "Host=localhost;Database=postgres;Username=postgres;Password=changeme"
- - name: Test Sample APIs
- run: |
- dotnet test Samples/Clinical/Clinical.Api.Tests --verbosity normal --logger "trx;LogFileName=test-results.trx"
- dotnet test Samples/Scheduling/Scheduling.Api.Tests --verbosity normal --logger "trx;LogFileName=test-results.trx"
- env:
- TEST_POSTGRES_CONNECTION: "Host=localhost;Database=postgres;Username=postgres;Password=changeme"
-
- name: Test Sync (Postgres)
run: |
dotnet test Sync/Sync.Postgres.Tests --verbosity normal --logger "trx;LogFileName=test-results.trx"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6d26bbb2..4030daba 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -114,6 +114,14 @@ jobs:
dotnet build DataProvider/DataProvider.Postgres.Cli/DataProvider.Postgres.Cli.csproj -c Release
dotnet build DataProvider/DataProvider.SQLite.Cli/DataProvider.SQLite.Cli.csproj -c Release
dotnet build Migration/Migration.Cli/Migration.Cli.csproj -c Release
+ dotnet build Lql/Lql/Lql.csproj -c Release
+ dotnet build Lql/Lql.Postgres/Lql.Postgres.csproj -c Release
+ dotnet build Lql/Lql.SQLite/Lql.SQLite.csproj -c Release
+ dotnet build Lql/Lql.SqlServer/Lql.SqlServer.csproj -c Release
+ dotnet build Sync/Sync/Sync.csproj -c Release
+ dotnet build Sync/Sync.Postgres/Sync.Postgres.csproj -c Release
+ dotnet build Sync/Sync.Http/Sync.Http.csproj -c Release
+ dotnet build Gatekeeper/Gatekeeper.Api/Gatekeeper.Api.csproj -c Release
- name: Test core libraries
run: |
@@ -129,6 +137,14 @@ jobs:
dotnet pack Migration/Migration.Postgres/Migration.Postgres.csproj -c Release -p:Version=${{ steps.version.outputs.VERSION }} -o ./nupkgs
dotnet pack DataProvider/DataProvider/DataProvider.csproj -c Release -p:Version=${{ steps.version.outputs.VERSION }} -o ./nupkgs
dotnet pack DataProvider/DataProvider.SQLite/DataProvider.SQLite.csproj -c Release -p:Version=${{ steps.version.outputs.VERSION }} -o ./nupkgs
+ dotnet pack Lql/Lql/Lql.csproj -c Release -p:Version=${{ steps.version.outputs.VERSION }} -o ./nupkgs
+ dotnet pack Lql/Lql.Postgres/Lql.Postgres.csproj -c Release -p:Version=${{ steps.version.outputs.VERSION }} -o ./nupkgs
+ dotnet pack Lql/Lql.SQLite/Lql.SQLite.csproj -c Release -p:Version=${{ steps.version.outputs.VERSION }} -o ./nupkgs
+ dotnet pack Lql/Lql.SqlServer/Lql.SqlServer.csproj -c Release -p:Version=${{ steps.version.outputs.VERSION }} -o ./nupkgs
+ dotnet pack Sync/Sync/Sync.csproj -c Release -p:Version=${{ steps.version.outputs.VERSION }} -o ./nupkgs
+ dotnet pack Sync/Sync.Postgres/Sync.Postgres.csproj -c Release -p:Version=${{ steps.version.outputs.VERSION }} -o ./nupkgs
+ dotnet pack Sync/Sync.Http/Sync.Http.csproj -c Release -p:Version=${{ steps.version.outputs.VERSION }} -o ./nupkgs
+ dotnet pack Gatekeeper/Gatekeeper.Api/Gatekeeper.Api.csproj -c Release -p:Version=${{ steps.version.outputs.VERSION }} -o ./nupkgs
- name: Pack CLI tools
run: |
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 4786447d..40f3c458 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,20 +1,6 @@
{
"version": "0.2.0",
"configurations": [
- {
- "name": "Dashboard (Fresh)",
- "type": "node-terminal",
- "request": "launch",
- "command": "${workspaceFolder}/Samples/scripts/start.sh --fresh",
- "cwd": "${workspaceFolder}/Samples/scripts"
- },
- {
- "name": "Dashboard (Continue)",
- "type": "node-terminal",
- "request": "launch",
- "command": "${workspaceFolder}/Samples/scripts/start.sh",
- "cwd": "${workspaceFolder}/Samples/scripts"
- },
{
"name": "Launch Blazor LQL Website",
"type": "coreclr",
@@ -64,20 +50,6 @@
"env": {
"DOTNET_ENVIRONMENT": "Development"
}
- },
- {
- "name": "ICD-10-CM CLI",
- "type": "coreclr",
- "request": "launch",
- "preLaunchTask": "build",
- "program": "${workspaceFolder}/Samples/ICD10CM/ICD10AM.Cli/bin/Debug/net9.0/ICD10AM.Cli.dll",
- "args": ["http://localhost:5558"],
- "cwd": "${workspaceFolder}/Samples/ICD10CM/ICD10AM.Cli",
- "console": "integratedTerminal",
- "stopAtEntry": false,
- "env": {
- "EMBEDDING_URL": "http://localhost:8000"
- }
}
]
-}
\ No newline at end of file
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index bbfcd903..194edb6c 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -39,95 +39,6 @@
"problemMatcher": []
},
- // ═══════════════════════════════════════════════════════════════
- // SAMPLES / DASHBOARD
- // ═══════════════════════════════════════════════════════════════
- {
- "label": "Samples: Start All (Fresh)",
- "type": "shell",
- "command": "./start.sh --fresh",
- "options": {
- "cwd": "${workspaceFolder}/Samples"
- },
- "problemMatcher": [],
- "detail": "Kill all, clear DBs, start Clinical, Scheduling, Gatekeeper, ICD-10, Sync, Dashboard"
- },
- {
- "label": "Samples: Start All (Continue)",
- "type": "shell",
- "command": "./start.sh",
- "options": {
- "cwd": "${workspaceFolder}/Samples"
- },
- "problemMatcher": [],
- "detail": "Start all services without clearing databases"
- },
-
- // ═══════════════════════════════════════════════════════════════
- // ICD-10-CM MICROSERVICE
- // ═══════════════════════════════════════════════════════════════
- {
- "label": "ICD-10: Run API",
- "type": "shell",
- "command": "./run.sh",
- "options": {
- "cwd": "${workspaceFolder}/Samples/ICD10CM/scripts"
- },
- "problemMatcher": [],
- "detail": "Start ICD-10-CM API on port 5558"
- },
- {
- "label": "ICD-10: Start Embedding Service",
- "type": "shell",
- "command": "./start.sh",
- "options": {
- "cwd": "${workspaceFolder}/Samples/ICD10CM/scripts/Dependencies"
- },
- "problemMatcher": [],
- "detail": "Docker: MedEmbed service for RAG search"
- },
- {
- "label": "ICD-10: Stop Embedding Service",
- "type": "shell",
- "command": "./stop.sh",
- "options": {
- "cwd": "${workspaceFolder}/Samples/ICD10CM/scripts/Dependencies"
- },
- "problemMatcher": []
- },
- {
- "label": "ICD-10: Import Database (full)",
- "type": "shell",
- "command": "./import.sh",
- "options": {
- "cwd": "${workspaceFolder}/Samples/ICD10CM/scripts/CreateDb"
- },
- "problemMatcher": [],
- "detail": "Migrate schema, import codes, generate embeddings (30-60 min)"
- },
- {
- "label": "ICD-10: Run CLI",
- "type": "shell",
- "command": "dotnet run -- http://localhost:5558",
- "options": {
- "cwd": "${workspaceFolder}/Samples/ICD10CM/ICD10AM.Cli",
- "env": {
- "EMBEDDING_URL": "http://localhost:8000"
- }
- },
- "problemMatcher": [],
- "detail": "Interactive CLI for ICD-10 code lookup"
- },
- {
- "label": "ICD-10: Run Tests",
- "type": "shell",
- "command": "dotnet test",
- "options": {
- "cwd": "${workspaceFolder}/Samples/ICD10CM/ICD10AM.Api.Tests"
- },
- "problemMatcher": "$msCompile"
- },
-
// ═══════════════════════════════════════════════════════════════
// LQL EXTENSION
// ═══════════════════════════════════════════════════════════════
@@ -273,12 +184,5 @@
},
"problemMatcher": []
},
- {
- "label": "Util: Kill All Sample Ports",
- "type": "shell",
- "command": "lsof -ti:5080,5001,5002,5090,5173 | xargs kill -9 2>/dev/null || true",
- "problemMatcher": [],
- "detail": "Kill ports: 5080, 5001, 5002, 5090, 5173"
- }
]
}
diff --git a/CLAUDE.md b/CLAUDE.md
index 24bd7335..ff7ab752 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -6,7 +6,7 @@
## Project Overview
-DataProvider is a comprehensive .NET database access toolkit: source generation for SQL extension methods, the Lambda Query Language (LQL) transpiler, bidirectional offline-first sync, WebAuthn + RBAC auth, and healthcare sample applications. The LQL LSP is implemented in Rust with a VS Code extension in TypeScript.
+DataProvider is a comprehensive .NET database access toolkit: source generation for SQL extension methods, the Lambda Query Language (LQL) transpiler, bidirectional offline-first sync, WebAuthn + RBAC auth, and an embeddable reporting platform. The LQL LSP is implemented in Rust with a VS Code extension in TypeScript. Healthcare sample applications live in a separate repo: [MelbourneDeveloper/HealthcareSamples](https://github.com/MelbourneDeveloper/HealthcareSamples).
**Primary language(s):** C# (.NET 10.0), Rust, TypeScript, F#
**Build command:** `make ci`
diff --git a/DataProvider.sln b/DataProvider.sln
index e8c19218..87f85bb6 100644
--- a/DataProvider.sln
+++ b/DataProvider.sln
@@ -1,4 +1,4 @@
-
+
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
@@ -67,30 +67,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Migration.Postgres", "Migra
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Migration.Tests", "Migration\Migration.Tests\Migration.Tests.csproj", "{E23F2826-1857-4C3F-A90B-D4443DD84EFA}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinical.Api", "Samples\Clinical\Clinical.Api\Clinical.Api.csproj", "{D53426B7-469F-4FBB-9935-4AA3C303DE8D}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinical.Sync", "Samples\Clinical\Clinical.Sync\Clinical.Sync.csproj", "{4189D963-E5AA-4782-AD78-72FBA9536B59}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scheduling.Api", "Samples\Scheduling\Scheduling.Api\Scheduling.Api.csproj", "{0F990389-7C88-4C7A-99F8-60E5243216FF}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scheduling.Sync", "Samples\Scheduling\Scheduling.Sync\Scheduling.Sync.csproj", "{7782890E-712E-4658-8BF2-0DC5794A87AC}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clinical.Api.Tests", "Samples\Clinical\Clinical.Api.Tests\Clinical.Api.Tests.csproj", "{8131E980-CA39-4BAD-9ADE-34E6597BD00F}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scheduling.Api.Tests", "Samples\Scheduling\Scheduling.Api.Tests\Scheduling.Api.Tests.csproj", "{C23F467D-B5F1-400D-9EEA-96E3F467BAB7}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dashboard", "Dashboard", "{B03CA193-C175-FB88-B41C-CBBC0E037C7E}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{C841F5C2-8F30-5BE9-ECA6-260644CF6F9F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Selecta", "Other\Selecta\Selecta.csproj", "{BE9AC443-C15D-4962-A8D2-0CCD328E6B68}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sync.Http", "Sync\Sync.Http\Sync.Http.csproj", "{392C12C2-ECBA-4728-9D8D-54BD2E10F7ED}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Integration.Tests", "Samples\Dashboard\Dashboard.Integration.Tests\Dashboard.Integration.Tests.csproj", "{83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}"
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gatekeeper", "Gatekeeper", "{048F5F03-6DDC-C04F-70D5-B8139DC8E373}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gatekeeper.Api", "Gatekeeper\Gatekeeper.Api\Gatekeeper.Api.csproj", "{4EB6CC28-7D1B-4E39-80F2-84CA4494AF23}"
@@ -103,14 +85,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataProvider.Postgres.Cli",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Schema.Export.Cli", "Migration\Schema.Export.Cli\Schema.Export.Cli.csproj", "{0858FE19-C59B-4A77-B76E-7053E8AFCC8D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authorization", "Samples\Shared\Authorization\Authorization.csproj", "{CA395494-F072-4A5B-9DD4-950530A69E0E}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LqlCli.SQLite", "Lql\LqlCli.SQLite\LqlCli.SQLite.csproj", "{1AE87774-E914-40BC-95BA-56FB45D78C0D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LqlWebsite", "Lql\LqlWebsite\LqlWebsite.csproj", "{6AB2EA96-4A75-49DB-AC65-B247BBFAE9A3}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dashboard.Web", "Samples\Dashboard\Dashboard.Web\Dashboard.Web.csproj", "{A82453CD-8E3C-44B7-A78F-97F392016385}"
-EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lql.TypeProvider.FSharp.Tests", "Lql\Lql.TypeProvider.FSharp.Tests\Lql.TypeProvider.FSharp.Tests.fsproj", "{B0104C42-1B46-4CA5-9E91-A5F09D7E5B92}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lql.TypeProvider.FSharp.Tests.Data", "Lql\Lql.TypeProvider.FSharp.Tests.Data\Lql.TypeProvider.FSharp.Tests.Data.csproj", "{0D6A831B-4759-46F2-8527-51C8A9CB6F6F}"
@@ -469,78 +447,6 @@ Global
{E23F2826-1857-4C3F-A90B-D4443DD84EFA}.Release|x64.Build.0 = Release|Any CPU
{E23F2826-1857-4C3F-A90B-D4443DD84EFA}.Release|x86.ActiveCfg = Release|Any CPU
{E23F2826-1857-4C3F-A90B-D4443DD84EFA}.Release|x86.Build.0 = Release|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Debug|x64.ActiveCfg = Debug|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Debug|x64.Build.0 = Debug|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Debug|x86.ActiveCfg = Debug|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Debug|x86.Build.0 = Debug|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Release|Any CPU.Build.0 = Release|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Release|x64.ActiveCfg = Release|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Release|x64.Build.0 = Release|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Release|x86.ActiveCfg = Release|Any CPU
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D}.Release|x86.Build.0 = Release|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Debug|x64.ActiveCfg = Debug|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Debug|x64.Build.0 = Debug|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Debug|x86.ActiveCfg = Debug|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Debug|x86.Build.0 = Debug|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Release|Any CPU.Build.0 = Release|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Release|x64.ActiveCfg = Release|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Release|x64.Build.0 = Release|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Release|x86.ActiveCfg = Release|Any CPU
- {4189D963-E5AA-4782-AD78-72FBA9536B59}.Release|x86.Build.0 = Release|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Debug|x64.ActiveCfg = Debug|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Debug|x64.Build.0 = Debug|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Debug|x86.ActiveCfg = Debug|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Debug|x86.Build.0 = Debug|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Release|Any CPU.Build.0 = Release|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Release|x64.ActiveCfg = Release|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Release|x64.Build.0 = Release|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Release|x86.ActiveCfg = Release|Any CPU
- {0F990389-7C88-4C7A-99F8-60E5243216FF}.Release|x86.Build.0 = Release|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Debug|x64.ActiveCfg = Debug|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Debug|x64.Build.0 = Debug|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Debug|x86.ActiveCfg = Debug|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Debug|x86.Build.0 = Debug|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Release|Any CPU.Build.0 = Release|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Release|x64.ActiveCfg = Release|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Release|x64.Build.0 = Release|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Release|x86.ActiveCfg = Release|Any CPU
- {7782890E-712E-4658-8BF2-0DC5794A87AC}.Release|x86.Build.0 = Release|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Debug|x64.ActiveCfg = Debug|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Debug|x64.Build.0 = Debug|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Debug|x86.ActiveCfg = Debug|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Debug|x86.Build.0 = Debug|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Release|Any CPU.Build.0 = Release|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Release|x64.ActiveCfg = Release|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Release|x64.Build.0 = Release|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Release|x86.ActiveCfg = Release|Any CPU
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F}.Release|x86.Build.0 = Release|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Debug|x64.ActiveCfg = Debug|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Debug|x64.Build.0 = Debug|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Debug|x86.ActiveCfg = Debug|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Debug|x86.Build.0 = Debug|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Release|Any CPU.Build.0 = Release|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Release|x64.ActiveCfg = Release|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Release|x64.Build.0 = Release|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Release|x86.ActiveCfg = Release|Any CPU
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7}.Release|x86.Build.0 = Release|Any CPU
{BE9AC443-C15D-4962-A8D2-0CCD328E6B68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BE9AC443-C15D-4962-A8D2-0CCD328E6B68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE9AC443-C15D-4962-A8D2-0CCD328E6B68}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -565,18 +471,6 @@ Global
{392C12C2-ECBA-4728-9D8D-54BD2E10F7ED}.Release|x64.Build.0 = Release|Any CPU
{392C12C2-ECBA-4728-9D8D-54BD2E10F7ED}.Release|x86.ActiveCfg = Release|Any CPU
{392C12C2-ECBA-4728-9D8D-54BD2E10F7ED}.Release|x86.Build.0 = Release|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Debug|x64.ActiveCfg = Debug|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Debug|x64.Build.0 = Debug|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Debug|x86.ActiveCfg = Debug|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Debug|x86.Build.0 = Debug|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Release|Any CPU.Build.0 = Release|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Release|x64.ActiveCfg = Release|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Release|x64.Build.0 = Release|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Release|x86.ActiveCfg = Release|Any CPU
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58}.Release|x86.Build.0 = Release|Any CPU
{4EB6CC28-7D1B-4E39-80F2-84CA4494AF23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4EB6CC28-7D1B-4E39-80F2-84CA4494AF23}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4EB6CC28-7D1B-4E39-80F2-84CA4494AF23}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -637,18 +531,6 @@ Global
{0858FE19-C59B-4A77-B76E-7053E8AFCC8D}.Release|x64.Build.0 = Release|Any CPU
{0858FE19-C59B-4A77-B76E-7053E8AFCC8D}.Release|x86.ActiveCfg = Release|Any CPU
{0858FE19-C59B-4A77-B76E-7053E8AFCC8D}.Release|x86.Build.0 = Release|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Debug|x64.ActiveCfg = Debug|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Debug|x64.Build.0 = Debug|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Debug|x86.ActiveCfg = Debug|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Debug|x86.Build.0 = Debug|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Release|Any CPU.Build.0 = Release|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Release|x64.ActiveCfg = Release|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Release|x64.Build.0 = Release|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Release|x86.ActiveCfg = Release|Any CPU
- {CA395494-F072-4A5B-9DD4-950530A69E0E}.Release|x86.Build.0 = Release|Any CPU
{1AE87774-E914-40BC-95BA-56FB45D78C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1AE87774-E914-40BC-95BA-56FB45D78C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AE87774-E914-40BC-95BA-56FB45D78C0D}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -673,18 +555,6 @@ Global
{6AB2EA96-4A75-49DB-AC65-B247BBFAE9A3}.Release|x64.Build.0 = Release|Any CPU
{6AB2EA96-4A75-49DB-AC65-B247BBFAE9A3}.Release|x86.ActiveCfg = Release|Any CPU
{6AB2EA96-4A75-49DB-AC65-B247BBFAE9A3}.Release|x86.Build.0 = Release|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Debug|x64.ActiveCfg = Debug|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Debug|x64.Build.0 = Debug|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Debug|x86.ActiveCfg = Debug|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Debug|x86.Build.0 = Debug|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Release|Any CPU.Build.0 = Release|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Release|x64.ActiveCfg = Release|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Release|x64.Build.0 = Release|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Release|x86.ActiveCfg = Release|Any CPU
- {A82453CD-8E3C-44B7-A78F-97F392016385}.Release|x86.Build.0 = Release|Any CPU
{B0104C42-1B46-4CA5-9E91-A5F09D7E5B92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0104C42-1B46-4CA5-9E91-A5F09D7E5B92}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0104C42-1B46-4CA5-9E91-A5F09D7E5B92}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -790,25 +660,15 @@ Global
{99B714F6-43FE-46F6-A9B3-B362B8B8F87D} = {C7F49633-8D5E-7E19-1580-A6459B2EAE66}
{988EAF3A-7320-4630-AFDC-233AC33AAA65} = {C7F49633-8D5E-7E19-1580-A6459B2EAE66}
{E23F2826-1857-4C3F-A90B-D4443DD84EFA} = {C7F49633-8D5E-7E19-1580-A6459B2EAE66}
- {D53426B7-469F-4FBB-9935-4AA3C303DE8D} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
- {4189D963-E5AA-4782-AD78-72FBA9536B59} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
- {0F990389-7C88-4C7A-99F8-60E5243216FF} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
- {7782890E-712E-4658-8BF2-0DC5794A87AC} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
- {8131E980-CA39-4BAD-9ADE-34E6597BD00F} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
- {C23F467D-B5F1-400D-9EEA-96E3F467BAB7} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
- {B03CA193-C175-FB88-B41C-CBBC0E037C7E} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
{BE9AC443-C15D-4962-A8D2-0CCD328E6B68} = {C841F5C2-8F30-5BE9-ECA6-260644CF6F9F}
{392C12C2-ECBA-4728-9D8D-54BD2E10F7ED} = {5E63119C-E70B-5D45-ECC9-8CBACC584223}
- {83E43658-7186-4E8B-AFD0-BDE5DB7BFB58} = {B03CA193-C175-FB88-B41C-CBBC0E037C7E}
{4EB6CC28-7D1B-4E39-80F2-84CA4494AF23} = {048F5F03-6DDC-C04F-70D5-B8139DC8E373}
{2FD305AC-927E-4D24-9FA6-923C30E4E4A8} = {048F5F03-6DDC-C04F-70D5-B8139DC8E373}
{57572A45-33CD-4928-9C30-13480AEDB313} = {C7F49633-8D5E-7E19-1580-A6459B2EAE66}
{A8A70E6D-1D43-437F-9971-44A4FA1BDD74} = {43BAF0A3-C050-BE83-B489-7FC6F9FDE235}
{0858FE19-C59B-4A77-B76E-7053E8AFCC8D} = {C7F49633-8D5E-7E19-1580-A6459B2EAE66}
- {CA395494-F072-4A5B-9DD4-950530A69E0E} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
{1AE87774-E914-40BC-95BA-56FB45D78C0D} = {54B846BA-A27D-B76F-8730-402A5742FF43}
{6AB2EA96-4A75-49DB-AC65-B247BBFAE9A3} = {54B846BA-A27D-B76F-8730-402A5742FF43}
- {A82453CD-8E3C-44B7-A78F-97F392016385} = {B03CA193-C175-FB88-B41C-CBBC0E037C7E}
{B0104C42-1B46-4CA5-9E91-A5F09D7E5B92} = {54B846BA-A27D-B76F-8730-402A5742FF43}
{0D6A831B-4759-46F2-8527-51C8A9CB6F6F} = {54B846BA-A27D-B76F-8730-402A5742FF43}
{94C443C0-AB5B-4FEC-9DB1-C1F29AB86653} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
diff --git a/DataProvider/DataProvider.Postgres.Cli/DataProvider.Postgres.Cli.csproj b/DataProvider/DataProvider.Postgres.Cli/DataProvider.Postgres.Cli.csproj
index 26e20ea4..584f26b6 100644
--- a/DataProvider/DataProvider.Postgres.Cli/DataProvider.Postgres.Cli.csproj
+++ b/DataProvider/DataProvider.Postgres.Cli/DataProvider.Postgres.Cli.csproj
@@ -10,7 +10,7 @@
false
false
EPC12;CA2100
- DataProvider.Postgres.Cli
+ MelbourneDev.DataProvider.Postgres.Cli
true
dataprovider-postgres
CLI tool for generating type-safe PostgreSQL data access code
diff --git a/DataProvider/DataProvider.SQLite.Cli/DataProvider.SQLite.Cli.csproj b/DataProvider/DataProvider.SQLite.Cli/DataProvider.SQLite.Cli.csproj
index 0beadf5d..a212261a 100644
--- a/DataProvider/DataProvider.SQLite.Cli/DataProvider.SQLite.Cli.csproj
+++ b/DataProvider/DataProvider.SQLite.Cli/DataProvider.SQLite.Cli.csproj
@@ -10,7 +10,7 @@
false
false
EPC12;CA2100
- DataProvider.SQLite.Cli
+ MelbourneDev.DataProvider.SQLite.Cli
true
dataprovider-sqlite
CLI tool for generating type-safe SQLite data access code
diff --git a/DataProvider/DataProvider.SQLite/DataProvider.SQLite.csproj b/DataProvider/DataProvider.SQLite/DataProvider.SQLite.csproj
index 01ed5629..ab6d134d 100644
--- a/DataProvider/DataProvider.SQLite/DataProvider.SQLite.csproj
+++ b/DataProvider/DataProvider.SQLite/DataProvider.SQLite.csproj
@@ -1,7 +1,7 @@
- DataProvider.SQLite
+ MelbourneDev.DataProvider.SQLite
ChristianFindlay
SQLite source generator for DataProvider. Provides compile-time safe database access with automatic code generation from SQL files for SQLite databases.
source-generator;sql;sqlite;database;compile-time-safety;code-generation
diff --git a/DataProvider/DataProvider.SqlServer/DataProvider.SqlServer.csproj b/DataProvider/DataProvider.SqlServer/DataProvider.SqlServer.csproj
index a32906f3..70c76d69 100644
--- a/DataProvider/DataProvider.SqlServer/DataProvider.SqlServer.csproj
+++ b/DataProvider/DataProvider.SqlServer/DataProvider.SqlServer.csproj
@@ -1,7 +1,7 @@
- DataProvider.SqlServer
+ MelbourneDev.DataProvider.SqlServer
0.1.0-beta
ChristianFindlay
SQL Server source generator for DataProvider. Provides compile-time safe database access with automatic code generation from SQL files for SQL Server databases.
diff --git a/DataProvider/DataProvider/DataProvider.csproj b/DataProvider/DataProvider/DataProvider.csproj
index 851b9275..713fe253 100644
--- a/DataProvider/DataProvider/DataProvider.csproj
+++ b/DataProvider/DataProvider/DataProvider.csproj
@@ -1,7 +1,7 @@
- DataProvider
+ MelbourneDev.DataProvider
ChristianFindlay
A source generator that creates compile-time safe extension methods for database operations from SQL files. Generates strongly-typed C# code based on your SQL queries and database schema, ensuring type safety and eliminating runtime SQL errors.
source-generator;sql;database;compile-time-safety;code-generation;sqlite;sqlserver
diff --git a/Gatekeeper/Gatekeeper.Api/Gatekeeper.Api.csproj b/Gatekeeper/Gatekeeper.Api/Gatekeeper.Api.csproj
index c0d484e7..25aaf12a 100644
--- a/Gatekeeper/Gatekeeper.Api/Gatekeeper.Api.csproj
+++ b/Gatekeeper/Gatekeeper.Api/Gatekeeper.Api.csproj
@@ -1,6 +1,7 @@
Exe
+ MelbourneDev.Gatekeeper
CA1515;CA2100;RS1035;CA1508;CA2234;CA1819;CA2007;EPC12
diff --git a/Lql/Lql.Postgres/Lql.Postgres.csproj b/Lql/Lql.Postgres/Lql.Postgres.csproj
index 91c01ecf..37604667 100644
--- a/Lql/Lql.Postgres/Lql.Postgres.csproj
+++ b/Lql/Lql.Postgres/Lql.Postgres.csproj
@@ -1,4 +1,7 @@
+
+ MelbourneDev.Lql.Postgres
+
diff --git a/Lql/Lql.SQLite/Lql.SQLite.csproj b/Lql/Lql.SQLite/Lql.SQLite.csproj
index 91c01ecf..69d083db 100644
--- a/Lql/Lql.SQLite/Lql.SQLite.csproj
+++ b/Lql/Lql.SQLite/Lql.SQLite.csproj
@@ -1,4 +1,7 @@
+
+ MelbourneDev.Lql.SQLite
+
diff --git a/Lql/Lql.SqlServer/Lql.SqlServer.csproj b/Lql/Lql.SqlServer/Lql.SqlServer.csproj
index 91c01ecf..594aa1f9 100644
--- a/Lql/Lql.SqlServer/Lql.SqlServer.csproj
+++ b/Lql/Lql.SqlServer/Lql.SqlServer.csproj
@@ -1,4 +1,7 @@
+
+ MelbourneDev.Lql.SqlServer
+
diff --git a/Lql/Lql/Lql.csproj b/Lql/Lql/Lql.csproj
index 2278770d..c139ff91 100644
--- a/Lql/Lql/Lql.csproj
+++ b/Lql/Lql/Lql.csproj
@@ -1,6 +1,7 @@
Library
+ MelbourneDev.Lql
CA1515;CA1866;CA1310;CA1834;CS3021
diff --git a/Migration/Migration.Cli/Migration.Cli.csproj b/Migration/Migration.Cli/Migration.Cli.csproj
index 49bd4006..904bbe05 100644
--- a/Migration/Migration.Cli/Migration.Cli.csproj
+++ b/Migration/Migration.Cli/Migration.Cli.csproj
@@ -3,7 +3,7 @@
Exe
Migration.Cli
$(NoWarn);CA2254;CA1515;RS1035;CA2100
- Migration.Cli
+ MelbourneDev.Migration.Cli
true
migration-cli
CLI tool for database schema migrations
diff --git a/Migration/Migration.Postgres/Migration.Postgres.csproj b/Migration/Migration.Postgres/Migration.Postgres.csproj
index 3eea512d..5f7e6a8c 100644
--- a/Migration/Migration.Postgres/Migration.Postgres.csproj
+++ b/Migration/Migration.Postgres/Migration.Postgres.csproj
@@ -2,7 +2,7 @@
Library
Migration.Postgres
- Migration.Postgres
+ MelbourneDev.Migration.Postgres
PostgreSQL DDL generator for Migration
$(NoWarn);CA2254;CA2100
diff --git a/Migration/Migration.SQLite/Migration.SQLite.csproj b/Migration/Migration.SQLite/Migration.SQLite.csproj
index bf1b4cba..5ab7f480 100644
--- a/Migration/Migration.SQLite/Migration.SQLite.csproj
+++ b/Migration/Migration.SQLite/Migration.SQLite.csproj
@@ -2,7 +2,7 @@
Library
Migration.SQLite
- Migration.SQLite
+ MelbourneDev.Migration.SQLite
SQLite DDL generator for Migration
$(NoWarn);CA2254;CA2100
diff --git a/Migration/Migration/Migration.csproj b/Migration/Migration/Migration.csproj
index 724ebbe7..11d4a13e 100644
--- a/Migration/Migration/Migration.csproj
+++ b/Migration/Migration/Migration.csproj
@@ -2,7 +2,7 @@
Library
Migration
- Migration
+ MelbourneDev.Migration
YAML-based database schema migration library
$(NoWarn);CA2254;CA1720;CA1724;RS1035
diff --git a/Other/Selecta/Selecta.csproj b/Other/Selecta/Selecta.csproj
index 566ffa8e..c13538bb 100644
--- a/Other/Selecta/Selecta.csproj
+++ b/Other/Selecta/Selecta.csproj
@@ -1,6 +1,6 @@
- Selecta
+ MelbourneDev.Selecta
Utility library for SQL result selection and mapping
diff --git a/Samples/.editorconfig b/Samples/.editorconfig
deleted file mode 100644
index 1ea7230c..00000000
--- a/Samples/.editorconfig
+++ /dev/null
@@ -1,10 +0,0 @@
-# Sample projects - less strict analysis
-root = false
-
-[*.cs]
-# Suppress problematic analyzers in sample code
-dotnet_diagnostic.RS1035.severity = none
-dotnet_diagnostic.EPC12.severity = none
-dotnet_diagnostic.CA2100.severity = none
-dotnet_diagnostic.CA1826.severity = none
-dotnet_diagnostic.IDE0037.severity = none
diff --git a/Samples/Clinical/Clinical.Api.Tests/AuthorizationTests.cs b/Samples/Clinical/Clinical.Api.Tests/AuthorizationTests.cs
deleted file mode 100644
index 5e46bb9c..00000000
--- a/Samples/Clinical/Clinical.Api.Tests/AuthorizationTests.cs
+++ /dev/null
@@ -1,253 +0,0 @@
-using System.Net;
-using System.Net.Http.Headers;
-using System.Net.Http.Json;
-
-namespace Clinical.Api.Tests;
-
-///
-/// Authorization tests for Clinical.Api endpoints.
-/// Tests that endpoints require proper authentication and permissions.
-///
-public sealed class AuthorizationTests : IClassFixture
-{
- private readonly HttpClient _client;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Shared factory instance.
- public AuthorizationTests(ClinicalApiFactory factory) => _client = factory.CreateClient();
-
- [Fact]
- public async Task GetPatients_WithoutToken_ReturnsUnauthorized()
- {
- var response = await _client.GetAsync("/fhir/Patient/");
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task GetPatients_WithInvalidToken_ReturnsUnauthorized()
- {
- using var request = new HttpRequestMessage(HttpMethod.Get, "/fhir/Patient/");
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "invalid-token");
-
- var response = await _client.SendAsync(request);
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task GetPatients_WithExpiredToken_ReturnsUnauthorized()
- {
- using var request = new HttpRequestMessage(HttpMethod.Get, "/fhir/Patient/");
- request.Headers.Authorization = new AuthenticationHeaderValue(
- "Bearer",
- TestTokenHelper.GenerateExpiredToken()
- );
-
- var response = await _client.SendAsync(request);
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task GetPatients_WithValidToken_SucceedsInDevMode()
- {
- // In dev mode (default signing key is all zeros), Gatekeeper permission checks
- // are bypassed to allow E2E testing without requiring Gatekeeper setup.
- // Valid tokens pass through after local JWT validation.
- using var request = new HttpRequestMessage(HttpMethod.Get, "/fhir/Patient/");
- request.Headers.Authorization = new AuthenticationHeaderValue(
- "Bearer",
- TestTokenHelper.GenerateNoRoleToken()
- );
-
- var response = await _client.SendAsync(request);
-
- // In dev mode, valid tokens succeed without permission checks
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- [Fact]
- public async Task CreatePatient_WithoutToken_ReturnsUnauthorized()
- {
- var patient = new
- {
- Active = true,
- GivenName = "Test",
- FamilyName = "Patient",
- Gender = "male",
- };
-
- var response = await _client.PostAsJsonAsync("/fhir/Patient/", patient);
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task GetEncounters_WithoutToken_ReturnsUnauthorized()
- {
- var response = await _client.GetAsync("/fhir/Patient/test-patient/Encounter/");
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task GetConditions_WithoutToken_ReturnsUnauthorized()
- {
- var response = await _client.GetAsync("/fhir/Patient/test-patient/Condition/");
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task GetMedicationRequests_WithoutToken_ReturnsUnauthorized()
- {
- var response = await _client.GetAsync("/fhir/Patient/test-patient/MedicationRequest/");
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task SyncChanges_WithoutToken_ReturnsUnauthorized()
- {
- var response = await _client.GetAsync("/sync/changes");
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task SyncOrigin_WithoutToken_ReturnsUnauthorized()
- {
- var response = await _client.GetAsync("/sync/origin");
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task SyncStatus_WithoutToken_ReturnsUnauthorized()
- {
- var response = await _client.GetAsync("/sync/status");
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task SyncRecords_WithoutToken_ReturnsUnauthorized()
- {
- var response = await _client.GetAsync("/sync/records");
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task SyncRetry_WithoutToken_ReturnsUnauthorized()
- {
- var response = await _client.PostAsync("/sync/records/test-id/retry", null);
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task PatientSearch_WithoutToken_ReturnsUnauthorized()
- {
- var response = await _client.GetAsync("/fhir/Patient/_search?q=test");
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task GetPatientById_WithoutToken_ReturnsUnauthorized()
- {
- var response = await _client.GetAsync("/fhir/Patient/test-patient-id");
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task UpdatePatient_WithoutToken_ReturnsUnauthorized()
- {
- var patient = new
- {
- Active = true,
- GivenName = "Updated",
- FamilyName = "Patient",
- Gender = "male",
- };
-
- var response = await _client.PutAsJsonAsync("/fhir/Patient/test-id", patient);
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task CreateEncounter_WithoutToken_ReturnsUnauthorized()
- {
- var encounter = new
- {
- Status = "planned",
- Class = "outpatient",
- PractitionerId = "pract-1",
- ServiceType = "General",
- ReasonCode = "Checkup",
- PeriodStart = "2024-01-01T10:00:00Z",
- PeriodEnd = "2024-01-01T11:00:00Z",
- Notes = "Test",
- };
-
- var response = await _client.PostAsJsonAsync(
- "/fhir/Patient/test-patient/Encounter/",
- encounter
- );
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task CreateCondition_WithoutToken_ReturnsUnauthorized()
- {
- var condition = new
- {
- ClinicalStatus = "active",
- VerificationStatus = "confirmed",
- Category = "encounter-diagnosis",
- Severity = "moderate",
- CodeSystem = "http://snomed.info/sct",
- CodeValue = "123456",
- CodeDisplay = "Test Condition",
- };
-
- var response = await _client.PostAsJsonAsync(
- "/fhir/Patient/test-patient/Condition/",
- condition
- );
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-
- [Fact]
- public async Task CreateMedicationRequest_WithoutToken_ReturnsUnauthorized()
- {
- var medication = new
- {
- Status = "active",
- Intent = "order",
- PractitionerId = "pract-1",
- EncounterId = "enc-1",
- MedicationCode = "12345",
- MedicationDisplay = "Test Medication",
- DosageInstruction = "Take once daily",
- Quantity = 30,
- Unit = "tablets",
- Refills = 2,
- };
-
- var response = await _client.PostAsJsonAsync(
- "/fhir/Patient/test-patient/MedicationRequest/",
- medication
- );
-
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
-}
diff --git a/Samples/Clinical/Clinical.Api.Tests/Clinical.Api.Tests.csproj b/Samples/Clinical/Clinical.Api.Tests/Clinical.Api.Tests.csproj
deleted file mode 100644
index b9fe3948..00000000
--- a/Samples/Clinical/Clinical.Api.Tests/Clinical.Api.Tests.csproj
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
- Library
- true
- Clinical.Api.Tests
- CS1591;CA1707;CA1307;CA1062;CA1515;CA2100;CA1822;CA1859;CA1849;CA2234;CA1812;CA2007;CA2000;xUnit1030
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
diff --git a/Samples/Clinical/Clinical.Api.Tests/ClinicalApiFactory.cs b/Samples/Clinical/Clinical.Api.Tests/ClinicalApiFactory.cs
deleted file mode 100644
index fc831ae6..00000000
--- a/Samples/Clinical/Clinical.Api.Tests/ClinicalApiFactory.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Mvc.Testing;
-using Npgsql;
-
-namespace Clinical.Api.Tests;
-
-///
-/// WebApplicationFactory for Clinical.Api e2e testing.
-/// Creates an isolated PostgreSQL test database per factory instance.
-///
-public sealed class ClinicalApiFactory : WebApplicationFactory
-{
- private readonly string _dbName;
- private readonly string _connectionString;
-
- private static readonly string BaseConnectionString =
- Environment.GetEnvironmentVariable("TEST_POSTGRES_CONNECTION")
- ?? "Host=localhost;Database=postgres;Username=postgres;Password=changeme";
-
- ///
- /// Creates a new instance with an isolated PostgreSQL test database.
- ///
- public ClinicalApiFactory()
- {
- _dbName = $"test_clinical_{Guid.NewGuid():N}";
-
- using (var adminConn = new NpgsqlConnection(BaseConnectionString))
- {
- adminConn.Open();
- using var createCmd = adminConn.CreateCommand();
- createCmd.CommandText = $"CREATE DATABASE {_dbName}";
- createCmd.ExecuteNonQuery();
- }
-
- _connectionString = BaseConnectionString.Replace(
- "Database=postgres",
- $"Database={_dbName}"
- );
- }
-
- ///
- /// Gets the connection string for direct access in tests if needed.
- ///
- public string ConnectionString => _connectionString;
-
- ///
- protected override void ConfigureWebHost(IWebHostBuilder builder)
- {
- builder.UseSetting("ConnectionStrings:Postgres", _connectionString);
- builder.UseEnvironment("Development");
-
- var clinicalApiAssembly = typeof(Program).Assembly;
- var contentRoot = Path.GetDirectoryName(clinicalApiAssembly.Location)!;
- builder.UseContentRoot(contentRoot);
- }
-
- ///
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- try
- {
- using var adminConn = new NpgsqlConnection(BaseConnectionString);
- adminConn.Open();
-
- using var terminateCmd = adminConn.CreateCommand();
- terminateCmd.CommandText =
- $"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '{_dbName}'";
- terminateCmd.ExecuteNonQuery();
-
- using var dropCmd = adminConn.CreateCommand();
- dropCmd.CommandText = $"DROP DATABASE IF EXISTS {_dbName}";
- dropCmd.ExecuteNonQuery();
- }
- catch
- {
- // Ignore cleanup errors
- }
- }
- }
-}
diff --git a/Samples/Clinical/Clinical.Api.Tests/ConditionEndpointTests.cs b/Samples/Clinical/Clinical.Api.Tests/ConditionEndpointTests.cs
deleted file mode 100644
index 4ebd60cb..00000000
--- a/Samples/Clinical/Clinical.Api.Tests/ConditionEndpointTests.cs
+++ /dev/null
@@ -1,335 +0,0 @@
-using System.Net;
-using System.Net.Http.Headers;
-using System.Net.Http.Json;
-using System.Text.Json;
-
-namespace Clinical.Api.Tests;
-
-///
-/// E2E tests for Condition FHIR endpoints - REAL database, NO mocks.
-/// Each test creates its own isolated factory and database.
-///
-public sealed class ConditionEndpointTests
-{
- private static readonly string AuthToken = TestTokenHelper.GenerateClinicianToken();
-
- private static HttpClient CreateAuthenticatedClient(ClinicalApiFactory factory)
- {
- var client = factory.CreateClient();
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
- "Bearer",
- AuthToken
- );
- return client;
- }
-
- private static async Task CreateTestPatientAsync(HttpClient client)
- {
- var patient = new
- {
- Active = true,
- GivenName = "Condition",
- FamilyName = "TestPatient",
- Gender = "female",
- };
-
- var response = await client.PostAsJsonAsync("/fhir/Patient/", patient);
- var created = await response.Content.ReadFromJsonAsync();
- return created.GetProperty("Id").GetString()!;
- }
-
- [Fact]
- public async Task GetConditionsByPatient_ReturnsEmptyList_WhenNoConditions()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
-
- var response = await client.GetAsync($"/fhir/Patient/{patientId}/Condition/");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var content = await response.Content.ReadAsStringAsync();
- Assert.Equal("[]", content);
- }
-
- [Fact]
- public async Task CreateCondition_ReturnsCreated_WithValidData()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- ClinicalStatus = "active",
- VerificationStatus = "confirmed",
- Category = "problem-list-item",
- Severity = "moderate",
- CodeSystem = "http://hl7.org/fhir/sid/icd-10-cm",
- CodeValue = "J06.9",
- CodeDisplay = "Acute upper respiratory infection, unspecified",
- OnsetDateTime = "2024-01-10T00:00:00Z",
- NoteText = "Patient presents with cold symptoms",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Condition/",
- request
- );
-
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- var condition = await response.Content.ReadFromJsonAsync();
- Assert.Equal("active", condition.GetProperty("ClinicalStatus").GetString());
- Assert.Equal("J06.9", condition.GetProperty("CodeValue").GetString());
- Assert.Equal(patientId, condition.GetProperty("SubjectReference").GetString());
- Assert.NotNull(condition.GetProperty("Id").GetString());
- }
-
- [Fact]
- public async Task CreateCondition_WithAllClinicalStatuses()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var statuses = new[]
- {
- "active",
- "recurrence",
- "relapse",
- "inactive",
- "remission",
- "resolved",
- };
-
- foreach (var status in statuses)
- {
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- ClinicalStatus = status,
- CodeSystem = "http://hl7.org/fhir/sid/icd-10-cm",
- CodeValue = "Z00.00",
- CodeDisplay = "General examination",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Condition/",
- request
- );
-
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- var condition = await response.Content.ReadFromJsonAsync();
- Assert.Equal(status, condition.GetProperty("ClinicalStatus").GetString());
- }
- }
-
- [Fact]
- public async Task CreateCondition_WithAllSeverities()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var severities = new[] { "mild", "moderate", "severe" };
-
- foreach (var severity in severities)
- {
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- ClinicalStatus = "active",
- Severity = severity,
- CodeSystem = "http://hl7.org/fhir/sid/icd-10-cm",
- CodeValue = "R51",
- CodeDisplay = "Headache",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Condition/",
- request
- );
-
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- var condition = await response.Content.ReadFromJsonAsync();
- Assert.Equal(severity, condition.GetProperty("Severity").GetString());
- }
- }
-
- [Fact]
- public async Task CreateCondition_WithVerificationStatuses()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var statuses = new[]
- {
- "unconfirmed",
- "provisional",
- "differential",
- "confirmed",
- "refuted",
- };
-
- foreach (var status in statuses)
- {
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- ClinicalStatus = "active",
- VerificationStatus = status,
- CodeSystem = "http://hl7.org/fhir/sid/icd-10-cm",
- CodeValue = "M54.5",
- CodeDisplay = "Low back pain",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Condition/",
- request
- );
-
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- var condition = await response.Content.ReadFromJsonAsync();
- Assert.Equal(status, condition.GetProperty("VerificationStatus").GetString());
- }
- }
-
- [Fact]
- public async Task GetConditionsByPatient_ReturnsConditions_WhenExist()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request1 = new
- {
- ClinicalStatus = "active",
- CodeSystem = "http://hl7.org/fhir/sid/icd-10-cm",
- CodeValue = "E11.9",
- CodeDisplay = "Type 2 diabetes mellitus",
- };
- var request2 = new
- {
- ClinicalStatus = "resolved",
- CodeSystem = "http://hl7.org/fhir/sid/icd-10-cm",
- CodeValue = "J02.9",
- CodeDisplay = "Acute pharyngitis, unspecified",
- };
-
- await client.PostAsJsonAsync($"/fhir/Patient/{patientId}/Condition/", request1);
- await client.PostAsJsonAsync($"/fhir/Patient/{patientId}/Condition/", request2);
-
- var response = await client.GetAsync($"/fhir/Patient/{patientId}/Condition/");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var conditions = await response.Content.ReadFromJsonAsync();
- Assert.NotNull(conditions);
- Assert.True(conditions.Length >= 2);
- }
-
- [Fact]
- public async Task CreateCondition_SetsRecordedDate()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- ClinicalStatus = "active",
- CodeSystem = "http://hl7.org/fhir/sid/icd-10-cm",
- CodeValue = "I10",
- CodeDisplay = "Essential hypertension",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Condition/",
- request
- );
- var condition = await response.Content.ReadFromJsonAsync();
-
- var recordedDate = condition.GetProperty("RecordedDate").GetString();
- Assert.NotNull(recordedDate);
- Assert.Matches(@"\d{4}-\d{2}-\d{2}", recordedDate);
- }
-
- [Fact]
- public async Task CreateCondition_SetsVersionIdToOne()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- ClinicalStatus = "active",
- CodeSystem = "http://hl7.org/fhir/sid/icd-10-cm",
- CodeValue = "K21.0",
- CodeDisplay = "GERD",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Condition/",
- request
- );
- var condition = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal(1L, condition.GetProperty("VersionId").GetInt64());
- }
-
- [Fact]
- public async Task CreateCondition_WithEncounterReference()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
-
- var encounterRequest = new
- {
- Status = "finished",
- Class = "ambulatory",
- PeriodStart = "2024-01-15T09:00:00Z",
- };
- var encounterResponse = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Encounter/",
- encounterRequest
- );
- var encounter = await encounterResponse.Content.ReadFromJsonAsync();
- var encounterId = encounter.GetProperty("Id").GetString();
-
- var conditionRequest = new
- {
- ClinicalStatus = "active",
- CodeSystem = "http://hl7.org/fhir/sid/icd-10-cm",
- CodeValue = "J18.9",
- CodeDisplay = "Pneumonia, unspecified organism",
- EncounterReference = encounterId,
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Condition/",
- conditionRequest
- );
- var condition = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal(encounterId, condition.GetProperty("EncounterReference").GetString());
- }
-
- [Fact]
- public async Task CreateCondition_WithNotes()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- ClinicalStatus = "active",
- CodeSystem = "http://hl7.org/fhir/sid/icd-10-cm",
- CodeValue = "F32.1",
- CodeDisplay = "Major depressive disorder, single episode, moderate",
- NoteText = "Patient started on SSRI therapy. Follow up in 4 weeks.",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Condition/",
- request
- );
- var condition = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal(
- "Patient started on SSRI therapy. Follow up in 4 weeks.",
- condition.GetProperty("NoteText").GetString()
- );
- }
-}
diff --git a/Samples/Clinical/Clinical.Api.Tests/DashboardIntegrationTests.cs b/Samples/Clinical/Clinical.Api.Tests/DashboardIntegrationTests.cs
deleted file mode 100644
index 9f92dd78..00000000
--- a/Samples/Clinical/Clinical.Api.Tests/DashboardIntegrationTests.cs
+++ /dev/null
@@ -1,122 +0,0 @@
-using System.Net.Http.Headers;
-
-namespace Clinical.Api.Tests;
-
-///
-/// Tests that verify the Dashboard can actually connect to Clinical API.
-/// These tests MUST FAIL if:
-/// 1. Dashboard hardcoded URL doesn't match actual API URL
-/// 2. CORS is not configured for Dashboard origin
-///
-public sealed class DashboardIntegrationTests : IClassFixture
-{
- private readonly HttpClient _client;
- private readonly string _authToken = TestTokenHelper.GenerateClinicianToken();
-
- ///
- /// The actual URL where Dashboard runs (for CORS origin testing).
- ///
- private const string DashboardOrigin = "http://localhost:5173";
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Shared factory instance.
- public DashboardIntegrationTests(ClinicalApiFactory factory)
- {
- _client = factory.CreateClient();
- _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
- "Bearer",
- _authToken
- );
- }
-
- #region URL Configuration Tests
-
- [Fact]
- public void Dashboard_ClinicalApiUrl_MatchesActualPort()
- {
- // The Dashboard's index.html has this hardcoded:
- // const CLINICAL_API = window.dashboardConfig?.CLINICAL_API_URL || 'http://localhost:5000';
- //
- // But Clinical API runs on port 5080 (see start.sh and launchSettings.json)
- //
- // This test verifies the Dashboard is configured to hit the CORRECT port.
- // If this fails, the Dashboard cannot connect to the API.
-
- const string dashboardHardcodedUrl = "http://localhost:5080"; // What Dashboard actually uses
- const string clinicalApiActualUrl = "http://localhost:5080"; // Where API actually runs
-
- Assert.Equal(
- clinicalApiActualUrl,
- dashboardHardcodedUrl // Dashboard now uses correct port!
- );
- }
-
- #endregion
-
- #region CORS Tests
-
- [Fact]
- public async Task ClinicalApi_Returns_CorsHeaders_ForDashboardOrigin()
- {
- // The Dashboard runs on localhost:5173 and makes fetch() calls to Clinical API.
- // Browser enforces CORS - without proper headers, the request is blocked.
- //
- // This test verifies Clinical API returns Access-Control-Allow-Origin header
- // for the Dashboard's origin.
-
- var request = new HttpRequestMessage(HttpMethod.Get, "/fhir/Patient");
- request.Headers.Add("Origin", DashboardOrigin);
- request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authToken);
-
- var response = await _client.SendAsync(request);
-
- // API should return CORS header allowing Dashboard origin
- Assert.True(
- response.Headers.Contains("Access-Control-Allow-Origin"),
- "Clinical API must return Access-Control-Allow-Origin header for Dashboard to work"
- );
-
- var allowedOrigin = response
- .Headers.GetValues("Access-Control-Allow-Origin")
- .FirstOrDefault();
- Assert.True(
- allowedOrigin == DashboardOrigin || allowedOrigin == "*",
- $"Access-Control-Allow-Origin must be '{DashboardOrigin}' or '*', but was '{allowedOrigin}'"
- );
- }
-
- [Fact]
- public async Task ClinicalApi_Handles_PreflightRequest_ForDashboardOrigin()
- {
- // Before making actual requests, browsers send OPTIONS preflight request.
- // API must respond with correct CORS headers.
-
- var request = new HttpRequestMessage(HttpMethod.Options, "/fhir/Patient");
- request.Headers.Add("Origin", DashboardOrigin);
- request.Headers.Add("Access-Control-Request-Method", "GET");
- request.Headers.Add("Access-Control-Request-Headers", "Accept");
-
- var response = await _client.SendAsync(request);
-
- // Preflight should succeed (200 or 204)
- Assert.True(
- response.IsSuccessStatusCode,
- $"Preflight OPTIONS request failed with {response.StatusCode}"
- );
-
- // Must have CORS headers
- Assert.True(
- response.Headers.Contains("Access-Control-Allow-Origin"),
- "Preflight response must include Access-Control-Allow-Origin"
- );
-
- Assert.True(
- response.Headers.Contains("Access-Control-Allow-Methods"),
- "Preflight response must include Access-Control-Allow-Methods"
- );
- }
-
- #endregion
-}
diff --git a/Samples/Clinical/Clinical.Api.Tests/EncounterEndpointTests.cs b/Samples/Clinical/Clinical.Api.Tests/EncounterEndpointTests.cs
deleted file mode 100644
index 610de404..00000000
--- a/Samples/Clinical/Clinical.Api.Tests/EncounterEndpointTests.cs
+++ /dev/null
@@ -1,276 +0,0 @@
-using System.Net;
-using System.Net.Http.Headers;
-using System.Net.Http.Json;
-using System.Text.Json;
-
-namespace Clinical.Api.Tests;
-
-///
-/// E2E tests for Encounter FHIR endpoints - REAL database, NO mocks.
-/// Each test creates its own isolated factory and database.
-///
-public sealed class EncounterEndpointTests
-{
- private static readonly string AuthToken = TestTokenHelper.GenerateClinicianToken();
-
- private static HttpClient CreateAuthenticatedClient(ClinicalApiFactory factory)
- {
- var client = factory.CreateClient();
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
- "Bearer",
- AuthToken
- );
- return client;
- }
-
- private static async Task CreateTestPatientAsync(HttpClient client)
- {
- var patient = new
- {
- Active = true,
- GivenName = "Encounter",
- FamilyName = "TestPatient",
- Gender = "male",
- };
-
- var response = await client.PostAsJsonAsync("/fhir/Patient/", patient);
- var created = await response.Content.ReadFromJsonAsync();
- return created.GetProperty("Id").GetString()!;
- }
-
- [Fact]
- public async Task GetEncountersByPatient_ReturnsEmptyList_WhenNoEncounters()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
-
- var response = await client.GetAsync($"/fhir/Patient/{patientId}/Encounter/");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var content = await response.Content.ReadAsStringAsync();
- Assert.Equal("[]", content);
- }
-
- [Fact]
- public async Task CreateEncounter_ReturnsCreated_WithValidData()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "planned",
- Class = "ambulatory",
- PractitionerId = "practitioner-123",
- ServiceType = "General Practice",
- ReasonCode = "Annual checkup",
- PeriodStart = "2024-01-15T09:00:00Z",
- PeriodEnd = "2024-01-15T09:30:00Z",
- Notes = "Routine visit",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Encounter/",
- request
- );
-
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- var encounter = await response.Content.ReadFromJsonAsync();
- Assert.Equal("planned", encounter.GetProperty("Status").GetString());
- Assert.Equal("ambulatory", encounter.GetProperty("Class").GetString());
- Assert.Equal(patientId, encounter.GetProperty("PatientId").GetString());
- Assert.NotNull(encounter.GetProperty("Id").GetString());
- }
-
- [Fact]
- public async Task CreateEncounter_WithAllStatuses()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var statuses = new[]
- {
- "planned",
- "arrived",
- "triaged",
- "in-progress",
- "onleave",
- "finished",
- "cancelled",
- };
-
- foreach (var status in statuses)
- {
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = status,
- Class = "ambulatory",
- PeriodStart = "2024-01-15T09:00:00Z",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Encounter/",
- request
- );
-
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- var encounter = await response.Content.ReadFromJsonAsync();
- Assert.Equal(status, encounter.GetProperty("Status").GetString());
- }
- }
-
- [Fact]
- public async Task CreateEncounter_WithAllClasses()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var classes = new[] { "ambulatory", "emergency", "inpatient", "observation", "virtual" };
-
- foreach (var encounterClass in classes)
- {
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "planned",
- Class = encounterClass,
- PeriodStart = "2024-01-15T09:00:00Z",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Encounter/",
- request
- );
-
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- var encounter = await response.Content.ReadFromJsonAsync();
- Assert.Equal(encounterClass, encounter.GetProperty("Class").GetString());
- }
- }
-
- [Fact]
- public async Task GetEncountersByPatient_ReturnsEncounters_WhenExist()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request1 = new
- {
- Status = "planned",
- Class = "ambulatory",
- PeriodStart = "2024-01-15T09:00:00Z",
- };
- var request2 = new
- {
- Status = "finished",
- Class = "inpatient",
- PeriodStart = "2024-01-16T10:00:00Z",
- };
-
- await client.PostAsJsonAsync($"/fhir/Patient/{patientId}/Encounter/", request1);
- await client.PostAsJsonAsync($"/fhir/Patient/{patientId}/Encounter/", request2);
-
- var response = await client.GetAsync($"/fhir/Patient/{patientId}/Encounter/");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var encounters = await response.Content.ReadFromJsonAsync();
- Assert.NotNull(encounters);
- Assert.True(encounters.Length >= 2);
- }
-
- [Fact]
- public async Task CreateEncounter_SetsVersionIdToOne()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "planned",
- Class = "ambulatory",
- PeriodStart = "2024-01-15T09:00:00Z",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Encounter/",
- request
- );
- var encounter = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal(1L, encounter.GetProperty("VersionId").GetInt64());
- }
-
- [Fact]
- public async Task CreateEncounter_SetsLastUpdatedTimestamp()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "planned",
- Class = "ambulatory",
- PeriodStart = "2024-01-15T09:00:00Z",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Encounter/",
- request
- );
- var encounter = await response.Content.ReadFromJsonAsync();
-
- var lastUpdated = encounter.GetProperty("LastUpdated").GetString();
- Assert.NotNull(lastUpdated);
- Assert.Matches(@"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z", lastUpdated);
- }
-
- [Fact]
- public async Task CreateEncounter_WithNotes()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "planned",
- Class = "ambulatory",
- PeriodStart = "2024-01-15T09:00:00Z",
- Notes = "Patient reported mild headache. Follow up in 2 weeks.",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Encounter/",
- request
- );
- var encounter = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal(
- "Patient reported mild headache. Follow up in 2 weeks.",
- encounter.GetProperty("Notes").GetString()
- );
- }
-
- [Fact]
- public async Task CreateEncounter_WithPeriodEndTime()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "finished",
- Class = "ambulatory",
- PeriodStart = "2024-01-15T09:00:00Z",
- PeriodEnd = "2024-01-15T09:45:00Z",
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Encounter/",
- request
- );
- var encounter = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal("2024-01-15T09:00:00Z", encounter.GetProperty("PeriodStart").GetString());
- Assert.Equal("2024-01-15T09:45:00Z", encounter.GetProperty("PeriodEnd").GetString());
- }
-}
diff --git a/Samples/Clinical/Clinical.Api.Tests/GlobalUsings.cs b/Samples/Clinical/Clinical.Api.Tests/GlobalUsings.cs
deleted file mode 100644
index f68c2477..00000000
--- a/Samples/Clinical/Clinical.Api.Tests/GlobalUsings.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-global using Samples.Authorization;
-global using Xunit;
diff --git a/Samples/Clinical/Clinical.Api.Tests/MedicationRequestEndpointTests.cs b/Samples/Clinical/Clinical.Api.Tests/MedicationRequestEndpointTests.cs
deleted file mode 100644
index 857c143e..00000000
--- a/Samples/Clinical/Clinical.Api.Tests/MedicationRequestEndpointTests.cs
+++ /dev/null
@@ -1,376 +0,0 @@
-using System.Net;
-using System.Net.Http.Headers;
-using System.Net.Http.Json;
-using System.Text.Json;
-
-namespace Clinical.Api.Tests;
-
-///
-/// E2E tests for MedicationRequest FHIR endpoints - REAL database, NO mocks.
-/// Each test creates its own isolated factory and database.
-///
-public sealed class MedicationRequestEndpointTests
-{
- private static readonly string AuthToken = TestTokenHelper.GenerateClinicianToken();
-
- private static HttpClient CreateAuthenticatedClient(ClinicalApiFactory factory)
- {
- var client = factory.CreateClient();
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
- "Bearer",
- AuthToken
- );
- return client;
- }
-
- private static async Task CreateTestPatientAsync(HttpClient client)
- {
- var patient = new
- {
- Active = true,
- GivenName = "Medication",
- FamilyName = "TestPatient",
- Gender = "male",
- };
-
- var response = await client.PostAsJsonAsync("/fhir/Patient/", patient);
- var created = await response.Content.ReadFromJsonAsync();
- return created.GetProperty("Id").GetString()!;
- }
-
- [Fact]
- public async Task GetMedicationsByPatient_ReturnsEmptyList_WhenNoMedications()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
-
- var response = await client.GetAsync($"/fhir/Patient/{patientId}/MedicationRequest/");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var content = await response.Content.ReadAsStringAsync();
- Assert.Equal("[]", content);
- }
-
- [Fact]
- public async Task CreateMedicationRequest_ReturnsCreated_WithValidData()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "active",
- Intent = "order",
- PractitionerId = "practitioner-456",
- MedicationCode = "197361",
- MedicationDisplay = "Lisinopril 10 MG Oral Tablet",
- DosageInstruction = "Take 1 tablet by mouth once daily",
- Quantity = 30.0,
- Unit = "tablet",
- Refills = 3,
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/MedicationRequest/",
- request
- );
-
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- var medication = await response.Content.ReadFromJsonAsync();
- Assert.Equal("active", medication.GetProperty("Status").GetString());
- Assert.Equal("order", medication.GetProperty("Intent").GetString());
- Assert.Equal(
- "Lisinopril 10 MG Oral Tablet",
- medication.GetProperty("MedicationDisplay").GetString()
- );
- Assert.Equal(patientId, medication.GetProperty("PatientId").GetString());
- Assert.NotNull(medication.GetProperty("Id").GetString());
- }
-
- [Fact]
- public async Task CreateMedicationRequest_WithAllStatuses()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var statuses = new[] { "active", "on-hold", "cancelled", "completed", "stopped", "draft" };
-
- foreach (var status in statuses)
- {
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = status,
- Intent = "order",
- PractitionerId = "practitioner-789",
- MedicationCode = "123456",
- MedicationDisplay = "Test Medication",
- Refills = 0,
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/MedicationRequest/",
- request
- );
-
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- var medication = await response.Content.ReadFromJsonAsync();
- Assert.Equal(status, medication.GetProperty("Status").GetString());
- }
- }
-
- [Fact]
- public async Task CreateMedicationRequest_WithAllIntents()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var intents = new[]
- {
- "proposal",
- "plan",
- "order",
- "original-order",
- "reflex-order",
- "filler-order",
- "instance-order",
- "option",
- };
-
- foreach (var intent in intents)
- {
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "active",
- Intent = intent,
- PractitionerId = "practitioner-abc",
- MedicationCode = "654321",
- MedicationDisplay = "Test Med",
- Refills = 1,
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/MedicationRequest/",
- request
- );
-
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
- var medication = await response.Content.ReadFromJsonAsync();
- Assert.Equal(intent, medication.GetProperty("Intent").GetString());
- }
- }
-
- [Fact]
- public async Task GetMedicationsByPatient_ReturnsMedications_WhenExist()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request1 = new
- {
- Status = "active",
- Intent = "order",
- PractitionerId = "doc-1",
- MedicationCode = "311354",
- MedicationDisplay = "Metformin 500 MG Oral Tablet",
- Refills = 5,
- };
- var request2 = new
- {
- Status = "active",
- Intent = "order",
- PractitionerId = "doc-1",
- MedicationCode = "197361",
- MedicationDisplay = "Lisinopril 10 MG Oral Tablet",
- Refills = 3,
- };
-
- await client.PostAsJsonAsync($"/fhir/Patient/{patientId}/MedicationRequest/", request1);
- await client.PostAsJsonAsync($"/fhir/Patient/{patientId}/MedicationRequest/", request2);
-
- var response = await client.GetAsync($"/fhir/Patient/{patientId}/MedicationRequest/");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var medications = await response.Content.ReadFromJsonAsync();
- Assert.NotNull(medications);
- Assert.True(medications.Length >= 2);
- }
-
- [Fact]
- public async Task CreateMedicationRequest_SetsVersionIdToOne()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "active",
- Intent = "order",
- PractitionerId = "doc-2",
- MedicationCode = "849727",
- MedicationDisplay = "Atorvastatin 20 MG Oral Tablet",
- Refills = 6,
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/MedicationRequest/",
- request
- );
- var medication = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal(1L, medication.GetProperty("VersionId").GetInt64());
- }
-
- [Fact]
- public async Task CreateMedicationRequest_SetsAuthoredOn()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "active",
- Intent = "order",
- PractitionerId = "doc-3",
- MedicationCode = "310429",
- MedicationDisplay = "Amlodipine 5 MG Oral Tablet",
- Refills = 3,
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/MedicationRequest/",
- request
- );
- var medication = await response.Content.ReadFromJsonAsync();
-
- var authoredOn = medication.GetProperty("AuthoredOn").GetString();
- Assert.NotNull(authoredOn);
- Assert.Matches(@"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z", authoredOn);
- }
-
- [Fact]
- public async Task CreateMedicationRequest_WithQuantityAndUnit()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "active",
- Intent = "order",
- PractitionerId = "doc-4",
- MedicationCode = "1049621",
- MedicationDisplay = "Omeprazole 20 MG Delayed Release Oral Capsule",
- Quantity = 90.0,
- Unit = "capsule",
- Refills = 2,
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/MedicationRequest/",
- request
- );
- var medication = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal(90.0, medication.GetProperty("Quantity").GetDouble());
- Assert.Equal("capsule", medication.GetProperty("Unit").GetString());
- }
-
- [Fact]
- public async Task CreateMedicationRequest_WithDosageInstruction()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "active",
- Intent = "order",
- PractitionerId = "doc-5",
- MedicationCode = "1049621",
- MedicationDisplay = "Omeprazole 20 MG Capsule",
- DosageInstruction = "Take 1 capsule by mouth 30 minutes before breakfast",
- Refills = 2,
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/MedicationRequest/",
- request
- );
- var medication = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal(
- "Take 1 capsule by mouth 30 minutes before breakfast",
- medication.GetProperty("DosageInstruction").GetString()
- );
- }
-
- [Fact]
- public async Task CreateMedicationRequest_WithEncounterId()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
-
- var encounterRequest = new
- {
- Status = "finished",
- Class = "ambulatory",
- PeriodStart = "2024-01-15T09:00:00Z",
- };
- var encounterResponse = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/Encounter/",
- encounterRequest
- );
- var encounter = await encounterResponse.Content.ReadFromJsonAsync();
- var encounterId = encounter.GetProperty("Id").GetString();
-
- var medicationRequest = new
- {
- Status = "active",
- Intent = "order",
- PractitionerId = "doc-6",
- EncounterId = encounterId,
- MedicationCode = "308136",
- MedicationDisplay = "Amoxicillin 500 MG Oral Capsule",
- DosageInstruction = "Take 1 capsule by mouth three times daily for 10 days",
- Quantity = 30.0,
- Unit = "capsule",
- Refills = 0,
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/MedicationRequest/",
- medicationRequest
- );
- var medication = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal(encounterId, medication.GetProperty("EncounterId").GetString());
- }
-
- [Fact]
- public async Task CreateMedicationRequest_WithZeroRefills()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientId = await CreateTestPatientAsync(client);
- var request = new
- {
- Status = "active",
- Intent = "order",
- PractitionerId = "doc-7",
- MedicationCode = "562251",
- MedicationDisplay = "Prednisone 10 MG Oral Tablet",
- DosageInstruction = "Taper as directed",
- Refills = 0,
- };
-
- var response = await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/MedicationRequest/",
- request
- );
- var medication = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal(0, medication.GetProperty("Refills").GetInt32());
- }
-}
diff --git a/Samples/Clinical/Clinical.Api.Tests/PatientEndpointTests.cs b/Samples/Clinical/Clinical.Api.Tests/PatientEndpointTests.cs
deleted file mode 100644
index 7356510c..00000000
--- a/Samples/Clinical/Clinical.Api.Tests/PatientEndpointTests.cs
+++ /dev/null
@@ -1,293 +0,0 @@
-using System.Net;
-using System.Net.Http.Headers;
-using System.Net.Http.Json;
-using System.Text.Json;
-
-namespace Clinical.Api.Tests;
-
-///
-/// E2E tests for Patient FHIR endpoints - REAL database, NO mocks.
-/// Uses shared factory for all tests - starts once, runs all tests, shuts down.
-///
-public sealed class PatientEndpointTests : IClassFixture
-{
- private readonly HttpClient _client;
- private readonly string _authToken = TestTokenHelper.GenerateClinicianToken();
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Shared factory instance.
- public PatientEndpointTests(ClinicalApiFactory factory)
- {
- _client = factory.CreateClient();
- _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
- "Bearer",
- _authToken
- );
- }
-
- [Fact]
- public async Task GetPatients_ReturnsOk()
- {
- var response = await _client.GetAsync("/fhir/Patient/");
- var body = await response.Content.ReadAsStringAsync();
-
- Assert.True(
- response.IsSuccessStatusCode,
- $"Expected OK but got {response.StatusCode}: {body}"
- );
- }
-
- [Fact]
- public async Task CreatePatient_ReturnsCreated_WithValidData()
- {
- var request = new
- {
- Active = true,
- GivenName = "John",
- FamilyName = "Doe",
- BirthDate = "1990-01-15",
- Gender = "male",
- Phone = "555-1234",
- Email = "john.doe@test.com",
- AddressLine = "123 Main St",
- City = "Springfield",
- State = "IL",
- PostalCode = "62701",
- Country = "USA",
- };
-
- var response = await _client.PostAsJsonAsync("/fhir/Patient/", request);
- var content = await response.Content.ReadAsStringAsync();
-
- Assert.True(
- response.IsSuccessStatusCode,
- $"Expected success. Got {response.StatusCode}: {content}"
- );
- Assert.Equal(HttpStatusCode.Created, response.StatusCode);
-
- var json = JsonSerializer.Deserialize(content);
- Assert.True(
- json.TryGetProperty("GivenName", out var givenName),
- $"Missing GivenName in: {content}"
- );
- Assert.Equal("John", givenName.GetString());
- Assert.True(
- json.TryGetProperty("FamilyName", out var familyName),
- $"Missing FamilyName in: {content}"
- );
- Assert.Equal("Doe", familyName.GetString());
- Assert.True(json.TryGetProperty("Gender", out var gender), $"Missing Gender in: {content}");
- Assert.Equal("male", gender.GetString());
- Assert.True(json.TryGetProperty("Id", out var id), $"Missing Id in: {content}");
- Assert.NotNull(id.GetString());
- }
-
- [Fact]
- public async Task GetPatientById_ReturnsPatient_WhenExists()
- {
- var createRequest = new
- {
- Active = true,
- GivenName = "Jane",
- FamilyName = "Smith",
- BirthDate = "1985-06-20",
- Gender = "female",
- };
-
- var createResponse = await _client.PostAsJsonAsync("/fhir/Patient/", createRequest);
- var created = await createResponse.Content.ReadFromJsonAsync();
- var patientId = created.GetProperty("Id").GetString();
-
- var response = await _client.GetAsync($"/fhir/Patient/{patientId}");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var patient = await response.Content.ReadFromJsonAsync();
- Assert.Equal("Jane", patient.GetProperty("GivenName").GetString());
- Assert.Equal("Smith", patient.GetProperty("FamilyName").GetString());
- }
-
- [Fact]
- public async Task GetPatientById_ReturnsNotFound_WhenNotExists()
- {
- var response = await _client.GetAsync("/fhir/Patient/nonexistent-id-12345");
-
- Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
- }
-
- [Fact]
- public async Task SearchPatients_FindsPatientsByName()
- {
- var request = new
- {
- Active = true,
- GivenName = "SearchTest",
- FamilyName = "UniqueLastName",
- Gender = "other",
- };
-
- await _client.PostAsJsonAsync("/fhir/Patient/", request);
-
- var response = await _client.GetAsync("/fhir/Patient/_search?q=UniqueLastName");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var patients = await response.Content.ReadFromJsonAsync();
- Assert.NotNull(patients);
- Assert.Contains(patients, p => p.GetProperty("FamilyName").GetString() == "UniqueLastName");
- }
-
- [Fact]
- public async Task GetPatients_FiltersByActiveStatus()
- {
- var activePatient = new
- {
- Active = true,
- GivenName = "Active",
- FamilyName = "PatientFilter",
- Gender = "male",
- };
-
- var inactivePatient = new
- {
- Active = false,
- GivenName = "Inactive",
- FamilyName = "PatientFilter",
- Gender = "female",
- };
-
- await _client.PostAsJsonAsync("/fhir/Patient/", activePatient);
- await _client.PostAsJsonAsync("/fhir/Patient/", inactivePatient);
-
- var activeResponse = await _client.GetAsync("/fhir/Patient/?active=true");
- Assert.Equal(HttpStatusCode.OK, activeResponse.StatusCode);
- var activePatients = await activeResponse.Content.ReadFromJsonAsync();
- Assert.NotNull(activePatients);
- Assert.All(activePatients, p => Assert.Equal(1L, p.GetProperty("Active").GetInt64()));
- }
-
- [Fact]
- public async Task GetPatients_FiltersByFamilyName()
- {
- var patient = new
- {
- Active = true,
- GivenName = "FilterTest",
- FamilyName = "FilterFamilyName",
- Gender = "unknown",
- };
-
- await _client.PostAsJsonAsync("/fhir/Patient/", patient);
-
- var response = await _client.GetAsync("/fhir/Patient/?familyName=FilterFamilyName");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var patients = await response.Content.ReadFromJsonAsync();
- Assert.NotNull(patients);
- Assert.Contains(
- patients,
- p => p.GetProperty("FamilyName").GetString() == "FilterFamilyName"
- );
- }
-
- [Fact]
- public async Task GetPatients_FiltersByGivenName()
- {
- var patient = new
- {
- Active = true,
- GivenName = "UniqueGivenName",
- FamilyName = "TestFamily",
- Gender = "male",
- };
-
- await _client.PostAsJsonAsync("/fhir/Patient/", patient);
-
- var response = await _client.GetAsync("/fhir/Patient/?givenName=UniqueGivenName");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var patients = await response.Content.ReadFromJsonAsync();
- Assert.NotNull(patients);
- Assert.Contains(patients, p => p.GetProperty("GivenName").GetString() == "UniqueGivenName");
- }
-
- [Fact]
- public async Task GetPatients_FiltersByGender()
- {
- var malePatient = new
- {
- Active = true,
- GivenName = "GenderTest",
- FamilyName = "Male",
- Gender = "male",
- };
-
- await _client.PostAsJsonAsync("/fhir/Patient/", malePatient);
-
- var response = await _client.GetAsync("/fhir/Patient/?gender=male");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var patients = await response.Content.ReadFromJsonAsync();
- Assert.NotNull(patients);
- Assert.All(patients, p => Assert.Equal("male", p.GetProperty("Gender").GetString()));
- }
-
- [Fact]
- public async Task CreatePatient_GeneratesUniqueIds()
- {
- var request = new
- {
- Active = true,
- GivenName = "IdTest",
- FamilyName = "Patient",
- Gender = "other",
- };
-
- var response1 = await _client.PostAsJsonAsync("/fhir/Patient/", request);
- var response2 = await _client.PostAsJsonAsync("/fhir/Patient/", request);
-
- var patient1 = await response1.Content.ReadFromJsonAsync();
- var patient2 = await response2.Content.ReadFromJsonAsync();
-
- Assert.NotEqual(
- patient1.GetProperty("Id").GetString(),
- patient2.GetProperty("Id").GetString()
- );
- }
-
- [Fact]
- public async Task CreatePatient_SetsVersionIdToOne()
- {
- var request = new
- {
- Active = true,
- GivenName = "Version",
- FamilyName = "Test",
- Gender = "male",
- };
-
- var response = await _client.PostAsJsonAsync("/fhir/Patient/", request);
- var patient = await response.Content.ReadFromJsonAsync();
-
- Assert.Equal(1L, patient.GetProperty("VersionId").GetInt64());
- }
-
- [Fact]
- public async Task CreatePatient_SetsLastUpdatedTimestamp()
- {
- var request = new
- {
- Active = true,
- GivenName = "Timestamp",
- FamilyName = "Test",
- Gender = "female",
- };
-
- var response = await _client.PostAsJsonAsync("/fhir/Patient/", request);
- var patient = await response.Content.ReadFromJsonAsync();
-
- var lastUpdated = patient.GetProperty("LastUpdated").GetString();
- Assert.NotNull(lastUpdated);
- Assert.Matches(@"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z", lastUpdated);
- }
-}
diff --git a/Samples/Clinical/Clinical.Api.Tests/SyncEndpointTests.cs b/Samples/Clinical/Clinical.Api.Tests/SyncEndpointTests.cs
deleted file mode 100644
index 8757e53d..00000000
--- a/Samples/Clinical/Clinical.Api.Tests/SyncEndpointTests.cs
+++ /dev/null
@@ -1,419 +0,0 @@
-using System.Net;
-using System.Net.Http.Headers;
-using System.Net.Http.Json;
-using System.Text.Json;
-
-namespace Clinical.Api.Tests;
-
-///
-/// E2E tests for Sync endpoints - REAL database, NO mocks.
-/// Tests sync log generation and origin tracking.
-/// Each test creates its own isolated factory and database.
-///
-public sealed class SyncEndpointTests
-{
- private static readonly string AuthToken = TestTokenHelper.GenerateClinicianToken();
-
- private static HttpClient CreateAuthenticatedClient(ClinicalApiFactory factory)
- {
- var client = factory.CreateClient();
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
- "Bearer",
- AuthToken
- );
- return client;
- }
-
- [Fact]
- public async Task GetSyncOrigin_ReturnsOriginId()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
-
- var response = await client.GetAsync("/sync/origin");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var result = await response.Content.ReadFromJsonAsync();
- var originId = result.GetProperty("originId").GetString();
- Assert.NotNull(originId);
- Assert.NotEmpty(originId);
- }
-
- [Fact]
- public async Task GetSyncChanges_ReturnsEmptyList_WhenNoChanges()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
-
- var response = await client.GetAsync("/sync/changes?fromVersion=999999");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var content = await response.Content.ReadAsStringAsync();
- Assert.Equal("[]", content);
- }
-
- [Fact]
- public async Task GetSyncChanges_ReturnChanges_AfterPatientCreated()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientRequest = new
- {
- Active = true,
- GivenName = "Sync",
- FamilyName = "TestPatient",
- Gender = "male",
- };
-
- await client.PostAsJsonAsync("/fhir/Patient/", patientRequest);
-
- var response = await client.GetAsync("/sync/changes?fromVersion=0");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var changes = await response.Content.ReadFromJsonAsync();
- Assert.NotNull(changes);
- Assert.True(changes.Length > 0);
- }
-
- [Fact]
- public async Task GetSyncChanges_RespectsLimitParameter()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- for (var i = 0; i < 5; i++)
- {
- var patientRequest = new
- {
- Active = true,
- GivenName = $"SyncLimit{i}",
- FamilyName = "TestPatient",
- Gender = "other",
- };
- await client.PostAsJsonAsync("/fhir/Patient/", patientRequest);
- }
-
- var response = await client.GetAsync("/sync/changes?fromVersion=0&limit=2");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var changes = await response.Content.ReadFromJsonAsync();
- Assert.NotNull(changes);
- Assert.True(changes.Length <= 2);
- }
-
- [Fact]
- public async Task GetSyncChanges_ContainsTableName()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientRequest = new
- {
- Active = true,
- GivenName = "SyncTable",
- FamilyName = "TestPatient",
- Gender = "male",
- };
- await client.PostAsJsonAsync("/fhir/Patient/", patientRequest);
-
- var response = await client.GetAsync("/sync/changes?fromVersion=0");
- var changes = await response.Content.ReadFromJsonAsync();
-
- Assert.NotNull(changes);
- Assert.Contains(changes, c => c.GetProperty("TableName").GetString() == "fhir_patient");
- }
-
- [Fact]
- public async Task GetSyncChanges_ContainsOperation()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientRequest = new
- {
- Active = true,
- GivenName = "SyncOp",
- FamilyName = "TestPatient",
- Gender = "female",
- };
- await client.PostAsJsonAsync("/fhir/Patient/", patientRequest);
-
- var response = await client.GetAsync("/sync/changes?fromVersion=0");
- var changes = await response.Content.ReadFromJsonAsync();
-
- Assert.NotNull(changes);
- Assert.Contains(
- changes,
- c =>
- {
- // Operation is serialized as integer (0=Insert, 1=Update, 2=Delete)
- var opValue = c.GetProperty("Operation").GetInt32();
- return opValue >= 0 && opValue <= 2;
- }
- );
- }
-
- [Fact]
- public async Task GetSyncChanges_TracksEncounterChanges()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientRequest = new
- {
- Active = true,
- GivenName = "SyncEncounter",
- FamilyName = "TestPatient",
- Gender = "male",
- };
- var patientResponse = await client.PostAsJsonAsync("/fhir/Patient/", patientRequest);
- var patient = await patientResponse.Content.ReadFromJsonAsync();
- var patientId = patient.GetProperty("Id").GetString();
-
- var encounterRequest = new
- {
- Status = "planned",
- Class = "ambulatory",
- PeriodStart = "2024-02-01T10:00:00Z",
- };
- await client.PostAsJsonAsync($"/fhir/Patient/{patientId}/Encounter/", encounterRequest);
-
- var response = await client.GetAsync("/sync/changes?fromVersion=0");
- var changes = await response.Content.ReadFromJsonAsync();
-
- Assert.NotNull(changes);
- Assert.Contains(changes, c => c.GetProperty("TableName").GetString() == "fhir_encounter");
- }
-
- [Fact]
- public async Task GetSyncChanges_TracksConditionChanges()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientRequest = new
- {
- Active = true,
- GivenName = "SyncCondition",
- FamilyName = "TestPatient",
- Gender = "female",
- };
- var patientResponse = await client.PostAsJsonAsync("/fhir/Patient/", patientRequest);
- var patient = await patientResponse.Content.ReadFromJsonAsync();
- var patientId = patient.GetProperty("Id").GetString();
-
- var conditionRequest = new
- {
- ClinicalStatus = "active",
- CodeSystem = "http://hl7.org/fhir/sid/icd-10-cm",
- CodeValue = "J06.9",
- CodeDisplay = "URI",
- };
- await client.PostAsJsonAsync($"/fhir/Patient/{patientId}/Condition/", conditionRequest);
-
- var response = await client.GetAsync("/sync/changes?fromVersion=0");
- var changes = await response.Content.ReadFromJsonAsync();
-
- Assert.NotNull(changes);
- Assert.Contains(changes, c => c.GetProperty("TableName").GetString() == "fhir_condition");
- }
-
- [Fact]
- public async Task GetSyncChanges_TracksMedicationRequestChanges()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
- var patientRequest = new
- {
- Active = true,
- GivenName = "SyncMedication",
- FamilyName = "TestPatient",
- Gender = "male",
- };
- var patientResponse = await client.PostAsJsonAsync("/fhir/Patient/", patientRequest);
- var patient = await patientResponse.Content.ReadFromJsonAsync();
- var patientId = patient.GetProperty("Id").GetString();
-
- var medicationRequest = new
- {
- Status = "active",
- Intent = "order",
- PractitionerId = "doc-sync",
- MedicationCode = "123",
- MedicationDisplay = "Test Med",
- Refills = 0,
- };
- await client.PostAsJsonAsync(
- $"/fhir/Patient/{patientId}/MedicationRequest/",
- medicationRequest
- );
-
- var response = await client.GetAsync("/sync/changes?fromVersion=0");
- var changes = await response.Content.ReadFromJsonAsync();
-
- Assert.NotNull(changes);
- Assert.Contains(
- changes,
- c => c.GetProperty("TableName").GetString() == "fhir_medicationrequest"
- );
- }
-
- // ========== SYNC DASHBOARD ENDPOINT TESTS ==========
- // These tests verify the endpoints required by the Sync Dashboard UI.
- // They should FAIL until the endpoints are implemented.
-
- ///
- /// Tests GET /sync/status endpoint - returns service sync health status.
- /// REQUIRED BY: Sync Dashboard service status cards.
- ///
- [Fact]
- public async Task GetSyncStatus_ReturnsServiceStatus()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
-
- var response = await client.GetAsync("/sync/status");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var result = await response.Content.ReadFromJsonAsync();
-
- // Should return service health info
- Assert.True(result.TryGetProperty("service", out var service));
- Assert.Equal("Clinical.Api", service.GetString());
-
- Assert.True(result.TryGetProperty("status", out var status));
- var statusValue = status.GetString();
- Assert.True(
- statusValue == "healthy" || statusValue == "degraded" || statusValue == "unhealthy",
- $"Status should be healthy, degraded, or unhealthy but was '{statusValue}'"
- );
-
- Assert.True(result.TryGetProperty("lastSyncTime", out _));
- Assert.True(result.TryGetProperty("totalRecords", out _));
- }
-
- ///
- /// Tests GET /sync/records endpoint - returns paginated sync records.
- /// REQUIRED BY: Sync Dashboard sync records table.
- ///
- [Fact]
- public async Task GetSyncRecords_ReturnsPaginatedRecords()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
-
- // Create some data to generate sync records
- var patientRequest = new
- {
- Active = true,
- GivenName = "SyncRecordTest",
- FamilyName = "TestPatient",
- Gender = "male",
- };
- await client.PostAsJsonAsync("/fhir/Patient/", patientRequest);
-
- var response = await client.GetAsync("/sync/records");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var result = await response.Content.ReadFromJsonAsync();
-
- // Should return paginated response
- Assert.True(result.TryGetProperty("records", out var records));
- Assert.True(records.GetArrayLength() > 0);
-
- Assert.True(result.TryGetProperty("total", out _));
- Assert.True(result.TryGetProperty("page", out _));
- Assert.True(result.TryGetProperty("pageSize", out _));
- }
-
- ///
- /// Tests GET /sync/records with search query.
- /// REQUIRED BY: Sync Dashboard search input.
- ///
- [Fact]
- public async Task GetSyncRecords_SearchByEntityId()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
-
- // Create a patient with known ID pattern
- var patientRequest = new
- {
- Active = true,
- GivenName = "SearchSyncTest",
- FamilyName = "UniquePatient",
- Gender = "female",
- };
- var createResponse = await client.PostAsJsonAsync("/fhir/Patient/", patientRequest);
- var patient = await createResponse.Content.ReadFromJsonAsync();
- var patientId = patient.GetProperty("Id").GetString();
-
- var response = await client.GetAsync($"/sync/records?search={patientId}");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var result = await response.Content.ReadFromJsonAsync();
-
- // Should find records matching the patient ID
- var records = result.GetProperty("records");
- Assert.True(records.GetArrayLength() > 0);
- }
-
- ///
- /// Tests POST /sync/records/{id}/retry endpoint - retries failed sync.
- /// REQUIRED BY: Sync Dashboard retry button.
- ///
- [Fact]
- public async Task PostSyncRetry_RetriesFailedRecord()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
-
- // First we need a failed sync record to retry
- // For now, test that the endpoint exists and accepts the request
- var response = await client.PostAsync("/sync/records/test-record-id/retry", null);
-
- // Should return 200 OK or 404 Not Found (if record doesn't exist)
- // NOT 404 Method Not Found (which would mean endpoint doesn't exist)
- Assert.True(
- response.StatusCode == HttpStatusCode.OK
- || response.StatusCode == HttpStatusCode.NotFound
- || response.StatusCode == HttpStatusCode.Accepted,
- $"Expected OK, NotFound, or Accepted but got {response.StatusCode}"
- );
- }
-
- ///
- /// Tests that sync records include required fields for dashboard display.
- /// REQUIRED BY: Sync Dashboard table columns.
- ///
- [Fact]
- public async Task GetSyncRecords_ContainsRequiredFields()
- {
- using var factory = new ClinicalApiFactory();
- var client = CreateAuthenticatedClient(factory);
-
- // Create data to generate sync records
- var patientRequest = new
- {
- Active = true,
- GivenName = "FieldTest",
- FamilyName = "SyncPatient",
- Gender = "other",
- };
- await client.PostAsJsonAsync("/fhir/Patient/", patientRequest);
-
- var response = await client.GetAsync("/sync/records");
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- var result = await response.Content.ReadFromJsonAsync();
- var records = result.GetProperty("records");
- Assert.True(records.GetArrayLength() > 0);
-
- var firstRecord = records[0];
-
- // Required fields for Sync Dashboard UI
- Assert.True(firstRecord.TryGetProperty("id", out _), "Missing 'id' field");
- Assert.True(firstRecord.TryGetProperty("entityType", out _), "Missing 'entityType' field");
- Assert.True(firstRecord.TryGetProperty("entityId", out _), "Missing 'entityId' field");
- Assert.True(firstRecord.TryGetProperty("operation", out _), "Missing 'operation' field");
- Assert.True(
- firstRecord.TryGetProperty("lastAttempt", out _),
- "Missing 'lastAttempt' field"
- );
- }
-}
diff --git a/Samples/Clinical/Clinical.Api.Tests/SyncWorkerFaultToleranceTests.cs b/Samples/Clinical/Clinical.Api.Tests/SyncWorkerFaultToleranceTests.cs
deleted file mode 100644
index 3ed32bc6..00000000
--- a/Samples/Clinical/Clinical.Api.Tests/SyncWorkerFaultToleranceTests.cs
+++ /dev/null
@@ -1,440 +0,0 @@
-using Microsoft.Extensions.Logging;
-
-namespace Clinical.Api.Tests;
-
-///
-/// Tests proving sync worker fault tolerance behavior.
-/// These tests verify that sync workers:
-/// 1. NEVER crash when APIs are unavailable
-/// 2. Retry with exponential backoff
-/// 3. Log appropriately at different failure levels
-/// 4. Recover gracefully when APIs become available
-///
-public sealed class SyncWorkerFaultToleranceTests
-{
- ///
- /// Proves that sync worker handles HttpRequestException without crashing.
- /// Simulates API being completely unreachable.
- ///
- [Fact]
- public async Task SyncWorker_HandlesHttpRequestException_WithoutCrashing()
- {
- // Arrange
- var logMessages = new List<(LogLevel Level, string Message)>();
- var logger = new TestLogger(logMessages);
- var cancellationTokenSource = new CancellationTokenSource();
- var failureCount = 0;
-
- // Simulate API that always fails with connection refused
- Func> performSync = () =>
- {
- failureCount++;
- if (failureCount >= 3)
- {
- cancellationTokenSource.Cancel();
- }
- throw new HttpRequestException("Connection refused (localhost:5001)");
- };
-
- var worker = new FaultTolerantSyncWorker(logger, performSync);
-
- // Act - Run the worker until it handles 3 failures
- await worker.ExecuteAsync(cancellationTokenSource.Token);
-
- // Assert - Worker should have handled multiple failures without crashing
- Assert.True(failureCount >= 3, "Worker should have retried at least 3 times");
- Assert.Contains(
- logMessages,
- m => m.Message.Contains("[SYNC-RETRY]") || m.Message.Contains("[SYNC-FAULT]")
- );
- Assert.Contains(logMessages, m => m.Message.Contains("Connection refused"));
- }
-
- ///
- /// Proves that sync worker uses exponential backoff when retrying.
- ///
- [Fact]
- public async Task SyncWorker_UsesExponentialBackoff_OnConsecutiveFailures()
- {
- // Arrange
- var logMessages = new List<(LogLevel Level, string Message)>();
- var logger = new TestLogger(logMessages);
- var cancellationTokenSource = new CancellationTokenSource();
- var retryDelays = new List();
- var failureCount = 0;
-
- Func> performSync = () =>
- {
- failureCount++;
- if (failureCount >= 5)
- {
- cancellationTokenSource.Cancel();
- }
- throw new HttpRequestException("Connection refused");
- };
-
- var worker = new FaultTolerantSyncWorker(logger, performSync, retryDelays.Add);
-
- // Act
- await worker.ExecuteAsync(cancellationTokenSource.Token);
-
- // Assert - Delays should increase (exponential backoff)
- Assert.True(retryDelays.Count >= 4, "Should have recorded multiple retry delays");
- for (var i = 1; i < retryDelays.Count; i++)
- {
- Assert.True(
- retryDelays[i] >= retryDelays[i - 1],
- $"Delay should increase or stay same. Delay[{i - 1}]={retryDelays[i - 1]}, Delay[{i}]={retryDelays[i]}"
- );
- }
- }
-
- ///
- /// Proves that sync worker escalates log level after multiple consecutive failures.
- ///
- [Fact]
- public async Task SyncWorker_EscalatesLogLevel_AfterMultipleFailures()
- {
- // Arrange
- var logMessages = new List<(LogLevel Level, string Message)>();
- var logger = new TestLogger(logMessages);
- var cancellationTokenSource = new CancellationTokenSource();
- var failureCount = 0;
-
- Func> performSync = () =>
- {
- failureCount++;
- if (failureCount >= 5)
- {
- cancellationTokenSource.Cancel();
- }
- throw new HttpRequestException("Connection refused");
- };
-
- var worker = new FaultTolerantSyncWorker(logger, performSync);
-
- // Act
- await worker.ExecuteAsync(cancellationTokenSource.Token);
-
- // Assert - Early failures should be Info, later ones should be Warning
- var infoLogs = logMessages.Where(m => m.Level == LogLevel.Information).ToList();
- var warningLogs = logMessages.Where(m => m.Level == LogLevel.Warning).ToList();
-
- Assert.True(infoLogs.Count > 0, "Should have info-level logs for early retries");
- Assert.True(
- warningLogs.Count > 0,
- "Should have warning-level logs after multiple failures"
- );
- }
-
- ///
- /// Proves that sync worker recovers and resets failure counter on success.
- ///
- [Fact]
- public async Task SyncWorker_ResetsFailureCounter_OnSuccess()
- {
- // Arrange
- var logMessages = new List<(LogLevel Level, string Message)>();
- var logger = new TestLogger(logMessages);
- var cancellationTokenSource = new CancellationTokenSource();
- var callCount = 0;
-
- Func> performSync = () =>
- {
- callCount++;
- return callCount switch
- {
- 1 or 2 => throw new HttpRequestException("Connection refused"), // First 2 calls fail
- 3 => Task.FromResult(true), // Third call succeeds
- 4 => throw new HttpRequestException("Connection refused again"), // Fourth fails
- _ => Task.FromException(new OperationCanceledException()), // Stop
- };
- };
-
- var worker = new FaultTolerantSyncWorker(logger, performSync);
-
- // Act
- try
- {
- cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(5));
- await worker.ExecuteAsync(cancellationTokenSource.Token);
- }
- catch (OperationCanceledException)
- {
- // Expected
- }
-
- // Assert - Should have logged recovery message
- Assert.Contains(logMessages, m => m.Message.Contains("[SYNC-RECOVERED]"));
- }
-
- ///
- /// Proves that sync worker handles unexpected exceptions without crashing.
- ///
- [Fact]
- public async Task SyncWorker_HandlesUnexpectedException_WithoutCrashing()
- {
- // Arrange
- var logMessages = new List<(LogLevel Level, string Message)>();
- var logger = new TestLogger(logMessages);
- var cancellationTokenSource = new CancellationTokenSource();
- var failureCount = 0;
-
- Func> performSync = () =>
- {
- failureCount++;
- if (failureCount >= 3)
- {
- cancellationTokenSource.Cancel();
- }
- throw new InvalidOperationException("Unexpected database error");
- };
-
- var worker = new FaultTolerantSyncWorker(logger, performSync);
-
- // Act
- await worker.ExecuteAsync(cancellationTokenSource.Token);
-
- // Assert - Worker should have handled unexpected exceptions
- Assert.True(failureCount >= 3, "Worker should have retried after unexpected exceptions");
- Assert.Contains(logMessages, m => m.Level == LogLevel.Error);
- Assert.Contains(logMessages, m => m.Message.Contains("[SYNC-ERROR]"));
- }
-
- ///
- /// Proves that sync worker shuts down gracefully on cancellation.
- ///
- [Fact]
- public async Task SyncWorker_ShutsDownGracefully_OnCancellation()
- {
- // Arrange
- var logMessages = new List<(LogLevel Level, string Message)>();
- var logger = new TestLogger(logMessages);
- var cancellationTokenSource = new CancellationTokenSource();
-
- Func> performSync = async () =>
- {
- await Task.Delay(100);
- return true;
- };
-
- var worker = new FaultTolerantSyncWorker(logger, performSync);
-
- // Act - Cancel immediately
- cancellationTokenSource.Cancel();
- await worker.ExecuteAsync(cancellationTokenSource.Token);
-
- // Assert - Should have logged shutdown message
- Assert.Contains(
- logMessages,
- m => m.Message.Contains("[SYNC-SHUTDOWN]") || m.Message.Contains("[SYNC-EXIT]")
- );
- }
-
- ///
- /// Proves that backoff is capped at maximum value (30 seconds for HTTP errors).
- ///
- [Fact]
- public async Task SyncWorker_CapsBackoff_AtMaximumValue()
- {
- // Arrange
- var logMessages = new List<(LogLevel Level, string Message)>();
- var logger = new TestLogger(logMessages);
- var cancellationTokenSource = new CancellationTokenSource();
- var retryDelays = new List();
- var failureCount = 0;
-
- Func> performSync = () =>
- {
- failureCount++;
- if (failureCount >= 10)
- {
- cancellationTokenSource.Cancel();
- }
- throw new HttpRequestException("Connection refused");
- };
-
- var worker = new FaultTolerantSyncWorker(logger, performSync, retryDelays.Add);
-
- // Act
- await worker.ExecuteAsync(cancellationTokenSource.Token);
-
- // Assert - All delays should be capped at 30 seconds
- Assert.True(retryDelays.All(d => d <= 30), "All delays should be capped at 30 seconds");
- // After enough failures, delays should hit the cap
- Assert.Contains(retryDelays, d => d == 30);
- }
-}
-
-///
-/// Test implementation of fault-tolerant sync worker behavior.
-/// Mirrors the actual SyncWorker fault tolerance patterns.
-///
-internal sealed class FaultTolerantSyncWorker
-{
- private readonly ILogger _logger;
- private readonly Func> _performSync;
- private readonly Action? _onRetryDelay;
-
- ///
- /// Creates a fault-tolerant sync worker for testing.
- ///
- public FaultTolerantSyncWorker(
- ILogger logger,
- Func> performSync,
- Action? onRetryDelay = null
- )
- {
- _logger = logger;
- _performSync = performSync;
- _onRetryDelay = onRetryDelay;
- }
-
- ///
- /// Executes the sync worker with fault tolerance.
- /// NEVER crashes - handles all errors gracefully.
- ///
- public async Task ExecuteAsync(CancellationToken stoppingToken)
- {
- _logger.Log(LogLevel.Information, "[SYNC-START] Fault tolerant sync worker starting");
-
- var consecutiveFailures = 0;
- const int maxConsecutiveFailuresBeforeWarning = 3;
-
- while (!stoppingToken.IsCancellationRequested)
- {
- try
- {
- await _performSync().ConfigureAwait(false);
-
- if (consecutiveFailures > 0)
- {
- _logger.Log(
- LogLevel.Information,
- "[SYNC-RECOVERED] Sync recovered after {Count} consecutive failures",
- consecutiveFailures
- );
- consecutiveFailures = 0;
- }
-
- try
- {
- await Task.Delay(TimeSpan.FromMilliseconds(10), stoppingToken)
- .ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
- break;
- }
- }
- catch (HttpRequestException ex)
- {
- consecutiveFailures++;
- var retryDelay = Math.Min(5 * consecutiveFailures, 30);
- _onRetryDelay?.Invoke(retryDelay);
-
- if (consecutiveFailures >= maxConsecutiveFailuresBeforeWarning)
- {
- _logger.Log(
- LogLevel.Warning,
- "[SYNC-FAULT] API unreachable for {Count} consecutive attempts. Error: {Message}. Retrying in {Delay}s...",
- consecutiveFailures,
- ex.Message,
- retryDelay
- );
- }
- else
- {
- _logger.Log(
- LogLevel.Information,
- "[SYNC-RETRY] API not reachable ({Message}). Attempt {Count}, retrying in {Delay}s...",
- ex.Message,
- consecutiveFailures,
- retryDelay
- );
- }
-
- try
- {
- await Task.Delay(TimeSpan.FromMilliseconds(retryDelay), stoppingToken)
- .ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
- break;
- }
- }
- catch (TaskCanceledException) when (stoppingToken.IsCancellationRequested)
- {
- _logger.Log(
- LogLevel.Information,
- "[SYNC-SHUTDOWN] Sync worker shutting down gracefully"
- );
- break;
- }
- catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
- {
- _logger.Log(
- LogLevel.Information,
- "[SYNC-SHUTDOWN] Sync worker shutting down gracefully"
- );
- break;
- }
- catch (Exception ex)
- {
- consecutiveFailures++;
- var retryDelay = Math.Min(10 * consecutiveFailures, 60);
- _onRetryDelay?.Invoke(retryDelay);
-
- _logger.Log(
- LogLevel.Error,
- "[SYNC-ERROR] Unexpected error during sync (attempt {Count}). Retrying in {Delay}s. Error: {Message}",
- consecutiveFailures,
- retryDelay,
- ex.Message
- );
-
- try
- {
- await Task.Delay(TimeSpan.FromMilliseconds(retryDelay), stoppingToken)
- .ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
- break;
- }
- }
- }
-
- _logger.Log(LogLevel.Information, "[SYNC-EXIT] Sync worker exited");
- }
-}
-
-///
-/// Test logger that captures log messages for assertion.
-///
-internal sealed class TestLogger : ILogger
-{
- private readonly List<(LogLevel Level, string Message)> _messages;
-
- ///
- /// Creates a test logger that captures messages.
- ///
- public TestLogger(List<(LogLevel Level, string Message)> messages) => _messages = messages;
-
- ///
- public IDisposable? BeginScope(TState state)
- where TState : notnull => null;
-
- ///
- public bool IsEnabled(LogLevel logLevel) => true;
-
- ///
- public void Log(
- LogLevel logLevel,
- EventId eventId,
- TState state,
- Exception? exception,
- Func formatter
- ) => _messages.Add((logLevel, formatter(state, exception)));
-}
diff --git a/Samples/Clinical/Clinical.Api/.editorconfig b/Samples/Clinical/Clinical.Api/.editorconfig
deleted file mode 100644
index 9501334c..00000000
--- a/Samples/Clinical/Clinical.Api/.editorconfig
+++ /dev/null
@@ -1,10 +0,0 @@
-root = false
-
-[*.cs]
-# Relax analyzer rules for sample code
-dotnet_diagnostic.CA1515.severity = none
-dotnet_diagnostic.CA2100.severity = none
-dotnet_diagnostic.RS1035.severity = none
-dotnet_diagnostic.CA1508.severity = none
-dotnet_diagnostic.CA2234.severity = none
-dotnet_diagnostic.IDE0037.severity = none
diff --git a/Samples/Clinical/Clinical.Api/Clinical.Api.csproj b/Samples/Clinical/Clinical.Api/Clinical.Api.csproj
deleted file mode 100644
index 92fa08ed..00000000
--- a/Samples/Clinical/Clinical.Api/Clinical.Api.csproj
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
- Exe
- CA1515;CA2100;RS1035;CA1508;CA2234
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Samples/Clinical/Clinical.Api/DataProvider.json b/Samples/Clinical/Clinical.Api/DataProvider.json
deleted file mode 100644
index 3c6fe7d7..00000000
--- a/Samples/Clinical/Clinical.Api/DataProvider.json
+++ /dev/null
@@ -1,67 +0,0 @@
-{
- "queries": [
- {
- "name": "GetPatients",
- "sqlFile": "Queries/GetPatients.generated.sql"
- },
- {
- "name": "GetPatientById",
- "sqlFile": "Queries/GetPatientById.generated.sql"
- },
- {
- "name": "SearchPatients",
- "sqlFile": "Queries/SearchPatients.generated.sql"
- },
- {
- "name": "GetEncountersByPatient",
- "sqlFile": "Queries/GetEncountersByPatient.generated.sql"
- },
- {
- "name": "GetConditionsByPatient",
- "sqlFile": "Queries/GetConditionsByPatient.generated.sql"
- },
- {
- "name": "GetMedicationsByPatient",
- "sqlFile": "Queries/GetMedicationsByPatient.generated.sql"
- }
- ],
- "tables": [
- {
- "schema": "main",
- "name": "fhir_Patient",
- "generateInsert": true,
- "generateUpdate": true,
- "generateDelete": true,
- "excludeColumns": ["Id"],
- "primaryKeyColumns": ["Id"]
- },
- {
- "schema": "main",
- "name": "fhir_Encounter",
- "generateInsert": true,
- "generateUpdate": false,
- "generateDelete": false,
- "excludeColumns": ["Id"],
- "primaryKeyColumns": ["Id"]
- },
- {
- "schema": "main",
- "name": "fhir_Condition",
- "generateInsert": true,
- "generateUpdate": false,
- "generateDelete": false,
- "excludeColumns": ["Id"],
- "primaryKeyColumns": ["Id"]
- },
- {
- "schema": "main",
- "name": "fhir_MedicationRequest",
- "generateInsert": true,
- "generateUpdate": false,
- "generateDelete": false,
- "excludeColumns": ["Id"],
- "primaryKeyColumns": ["Id"]
- }
- ],
- "connectionString": "Data Source=clinical.db"
-}
diff --git a/Samples/Clinical/Clinical.Api/DatabaseSetup.cs b/Samples/Clinical/Clinical.Api/DatabaseSetup.cs
deleted file mode 100644
index d9235601..00000000
--- a/Samples/Clinical/Clinical.Api/DatabaseSetup.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using Migration;
-using Migration.Postgres;
-using InitError = Outcome.Result.Error;
-using InitOk = Outcome.Result.Ok;
-using InitResult = Outcome.Result;
-
-namespace Clinical.Api;
-
-///
-/// Database initialization for Clinical.Api using Migration tool.
-///
-internal static class DatabaseSetup
-{
- ///
- /// Creates the database schema and sync infrastructure using Migration.
- ///
- public static InitResult Initialize(NpgsqlConnection connection, ILogger logger)
- {
- var schemaResult = PostgresSyncSchema.CreateSchema(connection);
- var originResult = PostgresSyncSchema.SetOriginId(connection, Guid.NewGuid().ToString());
-
- if (schemaResult is Result.Error schemaErr)
- {
- var msg = SyncHelpers.ToMessage(schemaErr.Value);
- logger.Log(LogLevel.Error, "Failed to create sync schema: {Message}", msg);
- return new InitError($"Failed to create sync schema: {msg}");
- }
-
- if (originResult is Result.Error originErr)
- {
- var msg = SyncHelpers.ToMessage(originErr.Value);
- logger.Log(LogLevel.Error, "Failed to set origin ID: {Message}", msg);
- return new InitError($"Failed to set origin ID: {msg}");
- }
-
- // Use Migration tool to create schema from YAML (source of truth)
- try
- {
- var yamlPath = Path.Combine(AppContext.BaseDirectory, "clinical-schema.yaml");
- var schema = SchemaYamlSerializer.FromYamlFile(yamlPath);
-
- foreach (var table in schema.Tables)
- {
- var ddl = PostgresDdlGenerator.Generate(new CreateTableOperation(table));
- using var cmd = connection.CreateCommand();
- cmd.CommandText = ddl;
- cmd.ExecuteNonQuery();
- logger.Log(LogLevel.Debug, "Created table {TableName}", table.Name);
- }
-
- logger.Log(LogLevel.Information, "Created Clinical database schema from YAML");
- }
- catch (Exception ex)
- {
- logger.Log(LogLevel.Error, ex, "Failed to create Clinical database schema");
- return new InitError($"Failed to create Clinical database schema: {ex.Message}");
- }
-
- var triggerTables = new[]
- {
- "fhir_patient",
- "fhir_encounter",
- "fhir_condition",
- "fhir_medicationrequest",
- };
- foreach (var table in triggerTables)
- {
- var triggerResult = PostgresTriggerGenerator.CreateTriggers(connection, table, logger);
- if (triggerResult is Result.Error triggerErr)
- {
- logger.Log(
- LogLevel.Error,
- "Failed to create triggers for {Table}: {Message}",
- table,
- SyncHelpers.ToMessage(triggerErr.Value)
- );
- }
- }
-
- logger.Log(LogLevel.Information, "Clinical.Api database initialized with sync triggers");
- return new InitOk(true);
- }
-}
diff --git a/Samples/Clinical/Clinical.Api/FileLoggerProvider.cs b/Samples/Clinical/Clinical.Api/FileLoggerProvider.cs
deleted file mode 100644
index 74cc9a9e..00000000
--- a/Samples/Clinical/Clinical.Api/FileLoggerProvider.cs
+++ /dev/null
@@ -1,109 +0,0 @@
-namespace Clinical.Api;
-
-///
-/// Extension methods for adding file logging.
-///
-public static class FileLoggingExtensions
-{
- ///
- /// Adds file logging to the logging builder.
- ///
- public static ILoggingBuilder AddFileLogging(this ILoggingBuilder builder, string path)
- {
- // CA2000: DI container takes ownership and disposes when application shuts down
-#pragma warning disable CA2000
- builder.Services.AddSingleton(new FileLoggerProvider(path));
-#pragma warning restore CA2000
- return builder;
- }
-}
-
-///
-/// Simple file logger provider for writing logs to disk.
-///
-public sealed class FileLoggerProvider : ILoggerProvider
-{
- private readonly string _path;
- private readonly object _lock = new();
-
- ///
- /// Initializes a new instance of FileLoggerProvider.
- ///
- public FileLoggerProvider(string path)
- {
- _path = path;
- }
-
- ///
- /// Creates a logger for the specified category.
- ///
- public ILogger CreateLogger(string categoryName) => new FileLogger(_path, categoryName, _lock);
-
- ///
- /// Disposes the provider.
- ///
- public void Dispose()
- {
- // Nothing to dispose - singleton managed by DI container
- }
-}
-
-///
-/// Simple file logger that appends log entries to a file.
-///
-public sealed class FileLogger : ILogger
-{
- private readonly string _path;
- private readonly string _category;
- private readonly object _lock;
-
- ///
- /// Initializes a new instance of FileLogger.
- ///
- public FileLogger(string path, string category, object lockObj)
- {
- _path = path;
- _category = category;
- _lock = lockObj;
- }
-
- ///
- /// Begins a logical operation scope.
- ///
- public IDisposable? BeginScope(TState state)
- where TState : notnull => null;
-
- ///
- /// Checks if the given log level is enabled.
- ///
- public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;
-
- ///
- /// Writes a log entry to the file.
- ///
- public void Log(
- LogLevel logLevel,
- EventId eventId,
- TState state,
- Exception? exception,
- Func formatter
- )
- {
- if (!IsEnabled(logLevel))
- {
- return;
- }
-
- var message = formatter(state, exception);
- var line = $"{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss.fff} [{logLevel}] {_category}: {message}";
- if (exception != null)
- {
- line += Environment.NewLine + exception;
- }
-
- lock (_lock)
- {
- File.AppendAllText(_path, line + Environment.NewLine);
- }
- }
-}
diff --git a/Samples/Clinical/Clinical.Api/GlobalUsings.cs b/Samples/Clinical/Clinical.Api/GlobalUsings.cs
deleted file mode 100644
index 3f63934c..00000000
--- a/Samples/Clinical/Clinical.Api/GlobalUsings.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-global using System;
-global using Generated;
-global using Microsoft.Extensions.Logging;
-global using Npgsql;
-global using Outcome;
-global using Sync;
-global using Sync.Postgres;
-global using GetConditionsError = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Error<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->;
-// GetConditionsByPatient query result type aliases
-global using GetConditionsOk = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Ok<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->;
-global using GetEncountersError = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Error<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->;
-// GetEncountersByPatient query result type aliases
-global using GetEncountersOk = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Ok<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->;
-global using GetMedicationsError = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Error<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->;
-// GetMedicationsByPatient query result type aliases
-global using GetMedicationsOk = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Ok<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->;
-global using GetPatientByIdError = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Error, Selecta.SqlError>;
-// GetPatientById query result type aliases
-global using GetPatientByIdOk = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Ok, Selecta.SqlError>;
-global using GetPatientsError = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Error, Selecta.SqlError>;
-// GetPatients query result type aliases
-global using GetPatientsOk = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Ok, Selecta.SqlError>;
-global using InsertError = Outcome.Result.Error;
-// Insert result type aliases
-global using InsertOk = Outcome.Result.Ok;
-global using SearchPatientsError = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Error, Selecta.SqlError>;
-// SearchPatients query result type aliases
-global using SearchPatientsOk = Outcome.Result<
- System.Collections.Immutable.ImmutableList,
- Selecta.SqlError
->.Ok, Selecta.SqlError>;
-// Sync result type aliases
-global using StringSyncError = Outcome.Result.Error;
-global using StringSyncOk = Outcome.Result.Ok;
-global using SyncLogListError = Outcome.Result<
- System.Collections.Generic.IReadOnlyList,
- Sync.SyncError
->.Error, Sync.SyncError>;
-global using SyncLogListOk = Outcome.Result<
- System.Collections.Generic.IReadOnlyList,
- Sync.SyncError
->.Ok, Sync.SyncError>;
-// Update result type aliases
-global using UpdateOk = Outcome.Result.Ok;
diff --git a/Samples/Clinical/Clinical.Api/Program.cs b/Samples/Clinical/Clinical.Api/Program.cs
deleted file mode 100644
index db9bb2a5..00000000
--- a/Samples/Clinical/Clinical.Api/Program.cs
+++ /dev/null
@@ -1,865 +0,0 @@
-#pragma warning disable IDE0037 // Use inferred member name - prefer explicit for clarity in API responses
-
-using System.Collections.Immutable;
-using System.Globalization;
-using Clinical.Api;
-using Microsoft.AspNetCore.Http.Json;
-using Samples.Authorization;
-using InitError = Outcome.Result.Error;
-
-var builder = WebApplication.CreateBuilder(args);
-
-// File logging - use LOG_PATH env var or default to /tmp in containers
-var logPath =
- Environment.GetEnvironmentVariable("LOG_PATH")
- ?? (
- Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true"
- ? "/tmp/clinical.log"
- : Path.Combine(AppContext.BaseDirectory, "clinical.log")
- );
-builder.Logging.AddFileLogging(logPath);
-
-// Configure JSON to use PascalCase property names
-builder.Services.Configure(options =>
-{
- options.SerializerOptions.PropertyNamingPolicy = null;
-});
-
-// Add CORS for dashboard - allow any origin for testing
-builder.Services.AddCors(options =>
-{
- options.AddPolicy(
- "Dashboard",
- policy =>
- {
- policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
- }
- );
-});
-
-var connectionString =
- builder.Configuration.GetConnectionString("Postgres")
- ?? throw new InvalidOperationException("PostgreSQL connection string 'Postgres' is required");
-
-builder.Services.AddSingleton(() =>
-{
- var conn = new NpgsqlConnection(connectionString);
- conn.Open();
- return conn;
-});
-
-// Gatekeeper configuration for authorization
-var gatekeeperUrl = builder.Configuration["Gatekeeper:BaseUrl"] ?? "http://localhost:5002";
-var signingKeyBase64 = builder.Configuration["Jwt:SigningKey"];
-var signingKey = string.IsNullOrEmpty(signingKeyBase64)
- ? ImmutableArray.Create(new byte[32]) // Default empty key for development (MUST configure in production)
- : ImmutableArray.Create(Convert.FromBase64String(signingKeyBase64));
-
-builder.Services.AddHttpClient(
- "Gatekeeper",
- client =>
- {
- client.BaseAddress = new Uri(gatekeeperUrl);
- client.Timeout = TimeSpan.FromSeconds(5);
- }
-);
-
-var app = builder.Build();
-
-using (var conn = new NpgsqlConnection(connectionString))
-{
- conn.Open();
- if (DatabaseSetup.Initialize(conn, app.Logger) is InitError initErr)
- Environment.FailFast(initErr.Value);
-}
-
-// Enable CORS
-app.UseCors("Dashboard");
-
-// Get HttpClientFactory for auth filters
-var httpClientFactory = app.Services.GetRequiredService();
-Func getGatekeeperClient = () => httpClientFactory.CreateClient("Gatekeeper");
-
-var patientGroup = app.MapGroup("/fhir/Patient").WithTags("Patient");
-
-patientGroup
- .MapGet(
- "/",
- async (
- bool? active,
- string? familyName,
- string? givenName,
- string? gender,
- Func getConn
- ) =>
- {
- using var conn = getConn();
- var result = await conn.GetPatientsAsync(
- active.HasValue
- ? active.Value
- ? 1
- : 0
- : DBNull.Value,
- familyName ?? (object)DBNull.Value,
- givenName ?? (object)DBNull.Value,
- gender ?? (object)DBNull.Value
- )
- .ConfigureAwait(false);
- return result switch
- {
- GetPatientsOk(var patients) => Results.Ok(patients),
- GetPatientsError(var err) => Results.Problem(err.Message),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePermission(
- FhirPermissions.PatientRead,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-patientGroup
- .MapGet(
- "/{id}",
- async (string id, Func getConn) =>
- {
- using var conn = getConn();
- var result = await conn.GetPatientByIdAsync(id).ConfigureAwait(false);
- return result switch
- {
- GetPatientByIdOk(var patients) when patients.Count > 0 => Results.Ok(patients[0]),
- GetPatientByIdOk => Results.NotFound(),
- GetPatientByIdError(var err) => Results.Problem(err.Message),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequireResourcePermission(
- FhirPermissions.PatientRead,
- signingKey,
- getGatekeeperClient,
- app.Logger,
- idParamName: "id"
- )
- );
-
-patientGroup
- .MapPost(
- "/",
- async (CreatePatientRequest request, Func getConn) =>
- {
- using var conn = getConn();
- var transaction = await conn.BeginTransactionAsync().ConfigureAwait(false);
- await using var _ = transaction.ConfigureAwait(false);
- var id = Guid.NewGuid().ToString();
- var now = DateTime.UtcNow.ToString(
- "yyyy-MM-ddTHH:mm:ss.fffZ",
- CultureInfo.InvariantCulture
- );
-
- var result = await transaction
- .Insertfhir_PatientAsync(
- id,
- request.Active ? 1 : 0,
- request.GivenName,
- request.FamilyName,
- request.BirthDate,
- request.Gender,
- request.Phone,
- request.Email,
- request.AddressLine,
- request.City,
- request.State,
- request.PostalCode,
- request.Country,
- now,
- 1
- )
- .ConfigureAwait(false);
-
- if (result is InsertOk)
- {
- await transaction.CommitAsync().ConfigureAwait(false);
- return Results.Created(
- $"/fhir/Patient/{id}",
- new
- {
- Id = id,
- Active = request.Active,
- GivenName = request.GivenName,
- FamilyName = request.FamilyName,
- BirthDate = request.BirthDate,
- Gender = request.Gender,
- Phone = request.Phone,
- Email = request.Email,
- AddressLine = request.AddressLine,
- City = request.City,
- State = request.State,
- PostalCode = request.PostalCode,
- Country = request.Country,
- LastUpdated = now,
- VersionId = 1,
- }
- );
- }
-
- return result.Match(
- _ => Results.Problem("Unexpected state"),
- err => Results.Problem(err.Message)
- );
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePermission(
- FhirPermissions.PatientCreate,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-patientGroup
- .MapPut(
- "/{id}",
- async (string id, UpdatePatientRequest request, Func getConn) =>
- {
- using var conn = getConn();
-
- // First verify the patient exists
- var existingResult = await conn.GetPatientByIdAsync(id).ConfigureAwait(false);
- if (existingResult is GetPatientByIdOk(var patients) && patients.Count == 0)
- {
- return Results.NotFound();
- }
-
- if (existingResult is GetPatientByIdError(var fetchErr))
- {
- return Results.Problem(fetchErr.Message);
- }
-
- var existingPatient = ((GetPatientByIdOk)existingResult).Value[0];
- var newVersionId = existingPatient.VersionId + 1;
-
- var transaction = await conn.BeginTransactionAsync().ConfigureAwait(false);
- await using var _ = transaction.ConfigureAwait(false);
- var now = DateTime.UtcNow.ToString(
- "yyyy-MM-ddTHH:mm:ss.fffZ",
- CultureInfo.InvariantCulture
- );
-
- var result = await transaction
- .Updatefhir_PatientAsync(
- id,
- request.Active ? 1 : 0,
- request.GivenName,
- request.FamilyName,
- request.BirthDate ?? string.Empty,
- request.Gender ?? string.Empty,
- request.Phone ?? string.Empty,
- request.Email ?? string.Empty,
- request.AddressLine ?? string.Empty,
- request.City ?? string.Empty,
- request.State ?? string.Empty,
- request.PostalCode ?? string.Empty,
- request.Country ?? string.Empty,
- now,
- newVersionId
- )
- .ConfigureAwait(false);
-
- if (result is UpdateOk)
- {
- await transaction.CommitAsync().ConfigureAwait(false);
- return Results.Ok(
- new
- {
- Id = id,
- Active = request.Active,
- GivenName = request.GivenName,
- FamilyName = request.FamilyName,
- BirthDate = request.BirthDate,
- Gender = request.Gender,
- Phone = request.Phone,
- Email = request.Email,
- AddressLine = request.AddressLine,
- City = request.City,
- State = request.State,
- PostalCode = request.PostalCode,
- Country = request.Country,
- LastUpdated = now,
- VersionId = newVersionId,
- }
- );
- }
-
- return result.Match(
- _ => Results.Problem("Unexpected state"),
- err => Results.Problem(err.Message)
- );
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequireResourcePermission(
- FhirPermissions.PatientUpdate,
- signingKey,
- getGatekeeperClient,
- app.Logger,
- idParamName: "id"
- )
- );
-
-patientGroup
- .MapGet(
- "/_search",
- async (string q, Func getConn) =>
- {
- using var conn = getConn();
- var result = await conn.SearchPatientsAsync($"%{q}%").ConfigureAwait(false);
- return result switch
- {
- SearchPatientsOk(var patients) => Results.Ok(patients),
- SearchPatientsError(var err) => Results.Problem(err.Message),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePermission(
- FhirPermissions.PatientRead,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-var encounterGroup = patientGroup.MapGroup("/{patientId}/Encounter").WithTags("Encounter");
-
-encounterGroup
- .MapGet(
- "/",
- async (string patientId, Func getConn) =>
- {
- using var conn = getConn();
- var result = await conn.GetEncountersByPatientAsync(patientId).ConfigureAwait(false);
- return result switch
- {
- GetEncountersOk(var encounters) => Results.Ok(encounters),
- GetEncountersError(var err) => Results.Problem(err.Message),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePatientPermission(
- FhirPermissions.EncounterRead,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-encounterGroup
- .MapPost(
- "/",
- async (string patientId, CreateEncounterRequest request, Func getConn) =>
- {
- using var conn = getConn();
- var transaction = await conn.BeginTransactionAsync().ConfigureAwait(false);
- await using var _ = transaction.ConfigureAwait(false);
- var id = Guid.NewGuid().ToString();
- var now = DateTime.UtcNow.ToString(
- "yyyy-MM-ddTHH:mm:ss.fffZ",
- CultureInfo.InvariantCulture
- );
-
- var result = await transaction
- .Insertfhir_EncounterAsync(
- id,
- request.Status,
- request.Class,
- patientId,
- request.PractitionerId,
- request.ServiceType,
- request.ReasonCode,
- request.PeriodStart,
- request.PeriodEnd,
- request.Notes,
- now,
- 1
- )
- .ConfigureAwait(false);
-
- if (result is InsertOk)
- {
- await transaction.CommitAsync().ConfigureAwait(false);
- return Results.Created(
- $"/fhir/Patient/{patientId}/Encounter/{id}",
- new
- {
- Id = id,
- Status = request.Status,
- Class = request.Class,
- PatientId = patientId,
- PractitionerId = request.PractitionerId,
- ServiceType = request.ServiceType,
- ReasonCode = request.ReasonCode,
- PeriodStart = request.PeriodStart,
- PeriodEnd = request.PeriodEnd,
- Notes = request.Notes,
- LastUpdated = now,
- VersionId = 1,
- }
- );
- }
-
- return result switch
- {
- InsertOk => Results.Problem("Unexpected state"),
- InsertError(var err) => Results.Problem(err.Message),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePatientPermission(
- FhirPermissions.EncounterCreate,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-var conditionGroup = patientGroup.MapGroup("/{patientId}/Condition").WithTags("Condition");
-
-conditionGroup
- .MapGet(
- "/",
- async (string patientId, Func getConn) =>
- {
- using var conn = getConn();
- var result = await conn.GetConditionsByPatientAsync(patientId).ConfigureAwait(false);
- return result switch
- {
- GetConditionsOk(var conditions) => Results.Ok(conditions),
- GetConditionsError(var err) => Results.Problem(err.Message),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePatientPermission(
- FhirPermissions.ConditionRead,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-conditionGroup
- .MapPost(
- "/",
- async (string patientId, CreateConditionRequest request, Func getConn) =>
- {
- using var conn = getConn();
- var transaction = await conn.BeginTransactionAsync().ConfigureAwait(false);
- await using var _ = transaction.ConfigureAwait(false);
- var id = Guid.NewGuid().ToString();
- var now = DateTime.UtcNow.ToString(
- "yyyy-MM-ddTHH:mm:ss.fffZ",
- CultureInfo.InvariantCulture
- );
- var recordedDate = DateTime.UtcNow.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
-
- var result = await transaction
- .Insertfhir_ConditionAsync(
- id: id,
- clinicalstatus: request.ClinicalStatus,
- verificationstatus: request.VerificationStatus,
- category: request.Category,
- severity: request.Severity,
- codesystem: request.CodeSystem,
- codevalue: request.CodeValue,
- codedisplay: request.CodeDisplay,
- subjectreference: patientId,
- encounterreference: request.EncounterReference,
- onsetdatetime: request.OnsetDateTime,
- recordeddate: recordedDate,
- recorderreference: request.RecorderReference,
- notetext: request.NoteText,
- lastupdated: now,
- versionid: 1
- )
- .ConfigureAwait(false);
-
- if (result is InsertOk)
- {
- await transaction.CommitAsync().ConfigureAwait(false);
- return Results.Created(
- $"/fhir/Patient/{patientId}/Condition/{id}",
- new
- {
- Id = id,
- ClinicalStatus = request.ClinicalStatus,
- VerificationStatus = request.VerificationStatus,
- Category = request.Category,
- Severity = request.Severity,
- CodeSystem = request.CodeSystem,
- CodeValue = request.CodeValue,
- CodeDisplay = request.CodeDisplay,
- SubjectReference = patientId,
- EncounterReference = request.EncounterReference,
- OnsetDateTime = request.OnsetDateTime,
- RecordedDate = recordedDate,
- RecorderReference = request.RecorderReference,
- NoteText = request.NoteText,
- LastUpdated = now,
- VersionId = 1,
- }
- );
- }
-
- return result switch
- {
- InsertOk => Results.Problem("Unexpected state"),
- InsertError(var err) => Results.Problem(err.Message),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePatientPermission(
- FhirPermissions.ConditionCreate,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-var medicationGroup = patientGroup
- .MapGroup("/{patientId}/MedicationRequest")
- .WithTags("MedicationRequest");
-
-medicationGroup
- .MapGet(
- "/",
- async (string patientId, Func getConn) =>
- {
- using var conn = getConn();
- var result = await conn.GetMedicationsByPatientAsync(patientId).ConfigureAwait(false);
- return result switch
- {
- GetMedicationsOk(var medications) => Results.Ok(medications),
- GetMedicationsError(var err) => Results.Problem(err.Message),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePatientPermission(
- FhirPermissions.MedicationRequestRead,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-medicationGroup
- .MapPost(
- "/",
- async (
- string patientId,
- CreateMedicationRequestRequest request,
- Func getConn
- ) =>
- {
- using var conn = getConn();
- var transaction = await conn.BeginTransactionAsync().ConfigureAwait(false);
- await using var _ = transaction.ConfigureAwait(false);
- var id = Guid.NewGuid().ToString();
- var now = DateTime.UtcNow.ToString(
- "yyyy-MM-ddTHH:mm:ss.fffZ",
- CultureInfo.InvariantCulture
- );
-
- var result = await transaction
- .Insertfhir_MedicationRequestAsync(
- id,
- request.Status,
- request.Intent,
- patientId,
- request.PractitionerId,
- request.EncounterId,
- request.MedicationCode,
- request.MedicationDisplay,
- request.DosageInstruction,
- request.Quantity,
- request.Unit,
- request.Refills,
- now,
- now,
- 1
- )
- .ConfigureAwait(false);
-
- if (result is InsertOk)
- {
- await transaction.CommitAsync().ConfigureAwait(false);
- return Results.Created(
- $"/fhir/Patient/{patientId}/MedicationRequest/{id}",
- new
- {
- Id = id,
- Status = request.Status,
- Intent = request.Intent,
- PatientId = patientId,
- PractitionerId = request.PractitionerId,
- EncounterId = request.EncounterId,
- MedicationCode = request.MedicationCode,
- MedicationDisplay = request.MedicationDisplay,
- DosageInstruction = request.DosageInstruction,
- Quantity = request.Quantity,
- Unit = request.Unit,
- Refills = request.Refills,
- AuthoredOn = now,
- LastUpdated = now,
- VersionId = 1,
- }
- );
- }
-
- return result switch
- {
- InsertOk => Results.Problem("Unexpected state"),
- InsertError(var err) => Results.Problem(err.Message),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePatientPermission(
- FhirPermissions.MedicationRequestCreate,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-app.MapGet(
- "/sync/changes",
- (long? fromVersion, int? limit, Func getConn) =>
- {
- using var conn = getConn();
- var result = PostgresSyncLogRepository.FetchChanges(
- conn,
- fromVersion ?? 0,
- limit ?? 100
- );
- return result switch
- {
- SyncLogListOk(var logs) => Results.Ok(logs),
- SyncLogListError(var err) => Results.Problem(SyncHelpers.ToMessage(err)),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePermission(
- FhirPermissions.SyncRead,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-app.MapGet(
- "/sync/origin",
- (Func getConn) =>
- {
- using var conn = getConn();
- var result = PostgresSyncSchema.GetOriginId(conn);
- return result switch
- {
- StringSyncOk(var originId) => Results.Ok(new { originId }),
- StringSyncError(var err) => Results.Problem(SyncHelpers.ToMessage(err)),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePermission(
- FhirPermissions.SyncRead,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-app.MapGet(
- "/sync/status",
- (Func getConn) =>
- {
- using var conn = getConn();
- var changesResult = PostgresSyncLogRepository.FetchChanges(conn, 0, 1000);
-
- var (totalCount, lastSyncTime) = changesResult switch
- {
- SyncLogListOk(var logs) => (
- logs.Count,
- logs.Count > 0
- ? logs.Max(l => l.Timestamp)
- : DateTime.UtcNow.ToString(
- "yyyy-MM-ddTHH:mm:ss.fffZ",
- CultureInfo.InvariantCulture
- )
- ),
- SyncLogListError => (
- 0,
- DateTime.UtcNow.ToString(
- "yyyy-MM-ddTHH:mm:ss.fffZ",
- CultureInfo.InvariantCulture
- )
- ),
- };
-
- return Results.Ok(
- new
- {
- service = "Clinical.Api",
- status = "healthy",
- lastSyncTime,
- totalRecords = totalCount,
- failedCount = 0,
- }
- );
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePermission(
- FhirPermissions.SyncRead,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-app.MapGet(
- "/sync/records",
- (string? search, int? page, int? pageSize, Func getConn) =>
- {
- using var conn = getConn();
- var currentPage = page ?? 1;
- var size = pageSize ?? 50;
- var changesResult = PostgresSyncLogRepository.FetchChanges(conn, 0, 1000);
-
- return changesResult switch
- {
- SyncLogListOk(var logs) => Results.Ok(
- BuildSyncRecordsResponse(logs, search, currentPage, size)
- ),
- SyncLogListError(var err) => Results.Problem(SyncHelpers.ToMessage(err)),
- };
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePermission(
- FhirPermissions.SyncRead,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-app.MapPost(
- "/sync/records/{id}/retry",
- (string id) =>
- {
- // For now, just acknowledge the retry request
- // Real implementation would mark the record for re-sync
- return Results.Accepted();
- }
- )
- .AddEndpointFilterFactory(
- EndpointFilterFactories.RequirePermission(
- FhirPermissions.SyncWrite,
- signingKey,
- getGatekeeperClient,
- app.Logger
- )
- );
-
-app.MapGet(
- "/sync/providers",
- (Func getConn) =>
- {
- using var conn = getConn();
- using var cmd = conn.CreateCommand();
- cmd.CommandText =
- "SELECT ProviderId, FirstName, LastName, Specialty, SyncedAt FROM sync_Provider";
- using var reader = cmd.ExecuteReader();
- var providers = new List