There are no test projects in this solution currently.
StyleCop analyzers run during build (`EnforceCodeStyleInBuild=true` in `Directory.Build.props`). Fix analyzer warnings before committing.
## Architecture
Clean Architecture with 5 projects:
-**`src/api`** — ASP.NET Core 8 Web API. Controllers, authorization handlers/policies, middleware, error handling, and `Program.cs`.
-**`src/application`** — Application layer. Repository interfaces, service interfaces, configuration models, Dapper mappers, and `ServiceRegistration.cs` for DI setup.
-**`src/infrastructure`** — Data access. EF Core `ApplicationDbContext`, entity configurations, repository implementations, `EmailService`, Quartz jobs, and `ServiceRegistration.cs`.
-**`src/models`** — Domain models/entities only. No logic here.
-**`src/migrations`** — Raw SQL migration scripts (not EF migrations). Files are timestamped SQL scripts applied manually.
-**`src/nexus`** — Internal CQRS mediator library (`Cals.Nexus`). Defines `ICommand`, `IQuery`, `ICommandHandler`, `IQueryHandler` interfaces and the mediator implementation.
## Key Patterns
**Authentication:** University of Wisconsin login.wisc.edu (Azure AD OIDC). JWT Bearer tokens. `UserInfoClaimsTransformation` maps OIDC claims to app claims. `VisorUser` wraps `ClaimsPrincipal`.
**Authorization:** Two policies defined in `src/api/Authorization/Policies.cs`:
-`"CanEdit"` — requires membership in the Admins app group
-`"OneAppGroup"` — requires membership in at least one app group
All write endpoints (POST/PUT) require `[Authorize(Policy = Policies.CanEdit)]`.
**Data Access:** Hybrid approach — EF Core (Pomelo MySQL provider) for standard CRUD, Dapper via `IDbConnectionFactoryVisor` for complex queries. DB uses snake_case naming via `EFCore.NamingConventions`.
**Audit Logging:**`AuditSaveChangesInterceptor` auto-tracks changes to entities implementing `IAuditableEntity`. Timestamps (`ICreatedAt`, `IUpdatedAt`) are set automatically.
**Dependency Injection:** Scrutor for assembly scanning. Each layer has a `ServiceRegistration.cs` with an extension method called from `Program.cs`.
**CQRS:** The `Cals.Nexus` mediator is available but not extensively used yet. New feature work can use the command/query pattern via this library.
## Configuration
- Local dev: `appsettings.Development.json` with a local MySQL connection string.
- Production/Staging: Secrets pulled from Azure Key Vault (certificate-based auth). Non-dev environments require `AzureKeyVault__*` env vars and a PFX certificate.
-`AllowedOrigins` in `appsettings.json` controls CORS.
- App group membership (SysAdmin, Admins, Users) configured in `AppGroups.cs` and `appsettings.json`.
## Database Migrations
Migrations are plain SQL files in `src/migrations/scripts/`. To add a migration, create a new timestamped `.sql` file (format: `YYYYMMDDHHMMSS_Description.sql`) and apply it manually to the target database.
## NuGet Sources
Requires access to two internal feeds (configured in `nuget.config`):