# Balinyaar Server — Claude Code Guidelines The backend API of **Balinyaar**, a trust-first home-nursing marketplace in Iran. - **Coding rules** (the full rule set you must follow) → [CONVENTIONS.md](CONVENTIONS.md). Read it before writing any server code. - Repo-wide context and the frontend → root [CLAUDE.md](../CLAUDE.md). - Product/domain rules (business logic, schema, payments, escrow, verification) → [`product/`](../product/). Read the relevant doc before designing an entity, feature, or endpoint — don't infer business rules from code. --- ## Role You are a **senior .NET software engineer** working on this codebase. That means: - You write production-quality code, not demo code. Every file you touch should look like it was written by someone who has shipped .NET APIs at scale. - You understand the architecture and work _with_ it, not around it. Clean Architecture boundaries are non-negotiable. - You think before you write. If a task is ambiguous, reason through the design first. If it touches a contract other layers depend on, think about downstream impact. - You prefer simplicity and clarity over cleverness. The next engineer (or agent) should read your code without a guide. - You never leave the codebase in a worse state than you found it. --- ## Stack - **ASP.NET Core / .NET 10** (`net10.0`), Web API - **Clean Architecture** (Domain → Application → Infrastructure → API) - **CQRS** with **Mediator** (`martinothamar/Mediator` — source-generator based, **not** MediatR) - **EF Core 10** + **SQL Server** (Repository + Unit of Work pattern) - **ASP.NET Core Identity** with **JWE** (signed + AES-128-encrypted JWT), OTP, and dynamic permission authorization - **Mapster** for mapping, **FluentValidation** for validation, **Serilog** for structured logging - **OpenTelemetry** + **prometheus-net** for observability, **NSwag** for OpenAPI, **Asp.Versioning** for versioning - **xUnit** + **NSubstitute** for tests - All NuGet versions are centrally pinned in `Directory.Packages.props` > Note: some prose elsewhere may say "MediatR" — the actual dispatcher is `martinothamar/Mediator`. > Use `ISender`/`ICommand`/`IQuery` from that package, not MediatR types. --- ## Commands (run from `server/`) | Task | Command | | ----------------- | ------- | | Restore | `dotnet restore Baya.sln` | | Build | `dotnet build Baya.sln` | | Run API | `dotnet run --project src/API/Baya.Web.Api/Baya.Web.Api.csproj` | | Test | `dotnet test Baya.sln` | | Add migration | `dotnet ef migrations add --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Api` | | Update DB | `dotnet ef database update --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Api` | **Default URL:** `https://localhost:5002` — Swagger at `/swagger`. On boot, `Program.cs` calls `ApplyMigrationsAsync()` and `SeedDefaultUsersAsync()` — a reachable SQL Server is required to start. --- ## Quality gates — run before declaring work done 1. `dotnet build Baya.sln` — zero new warnings introduced. Unused `using`s, locals, parameters, private fields, or members count as failures — delete them, don't suppress them ([CONVENTIONS.md](CONVENTIONS.md) §2 "No unused code"). 2. `dotnet test Baya.sln` — all tests pass. 3. Read your own diff as if reviewing a PR: would a senior engineer approve it without comment? 4. If the change alters the architecture, update the **Project map** below in the same change (see "Keeping the Project map current"). --- ## Project map This tree is the **canonical description of the server's architecture** — the authoritative list of projects/assemblies, Clean-Architecture layers, and cross-layer dependencies. ``` src/ ├── Core/ │ ├── Baya.Domain Entities (User, Order, Role…), BaseEntity, IEntity, ITimeModification │ └── Baya.Application Features/ (Commands & Queries), Contracts/, Models/, pipeline behaviors (Common/) ├── Infrastructure/ │ ├── Baya.Infrastructure.Persistence ApplicationDbContext, Repositories/, Configuration/, Migrations/ │ ├── Baya.Infrastructure.Identity Jwt/, Identity/ (Managers, Stores, PermissionManager, Seed) │ ├── Baya.Infrastructure.CrossCutting Serilog wiring │ └── Baya.Infrastructure.Monitoring HealthChecks, OpenTelemetry, prometheus-net ├── API/ │ ├── Baya.Web.Api Program.cs, Controllers/V1/, appsettings*.json │ ├── Baya.WebFramework BaseController, Filters/, Middlewares/, Swagger/, Routing/ │ └── Plugins/Baya.Web.Plugins.Grpc gRPC services + .proto models ├── Shared/Baya.SharedKernel Extensions + validation base └── Tests/ ├── Baya.Tests.Setup Shared test infrastructure (SQLite, NSubstitute setup) └── Baya.Test.Infrastructure.Identity xUnit identity tests ``` **Dependency direction points inward.** Domain has no dependencies. Application depends only on Domain. Infrastructure and API implement/consume Application contracts. Never make Domain or Application reference Infrastructure or the API — this is a hard rule. **Keeping the Project map current.** When a change touches the architecture — adds, removes, or renames a project/assembly, a Clean-Architecture layer, or a major folder, or changes a cross-layer dependency — you **must** update this Project map (and the dependency rule above, if affected) in the **same** change. This is the server-specific form of the root "Keep docs honest" rule: the map is only canonical if it stays accurate. --- ## Startup wiring Service registration is composed from per-layer extension methods (each project's `ServiceConfiguration/`): ``` ConfigureHealthChecks() · SetupOpenTelemetry() AddApplicationServices() // Mediator + validators + pipeline behaviors RegisterIdentityServices(...) // Identity, JWT/JWE, authorization policies AddPersistenceServices(...) // DbContext, UnitOfWork, repositories AddWebFrameworkServices() // API versioning + snake_case routing AddSwagger("v1", "v1.1") · RegisterValidatorsAsServices() · AddMapster() ConfigureGrpcPluginServices() ``` Pipeline order: exception handler → Swagger → routing → **authentication → authorization** → controllers → metrics → health checks → gRPC. When adding new infrastructure, expose it as an extension method and call it from `Program.cs` — never inline registrations there directly. --- ## CQRS — how a feature is shaped Features live under `Baya.Application/Features//{Commands|Queries}//`: ``` Features/Order/ ├── Commands/CreateOrderCommand/ │ ├── CreateOrderCommand.cs record : ICommand> │ ├── CreateOrderCommand.Handler.cs internal sealed class : ICommandHandler<...> │ └── CreateOrderCommand.Validator.cs └── Queries/GetUserOrdersQuery/ ├── GetUserOrdersQuery.cs ├── GetUserOrdersQuery.Handler.cs └── GetUserOrdersQuery.Result.cs ``` Handlers are `internal sealed`. Requests are `record` types. Validators use FluentValidation and are picked up automatically by the `ValidateCommandBehavior` pipeline behavior. Never throw for expected failures — use `OperationResult` factory methods. **To add a feature:** create the folder, implement request + handler + (optional) validator, add any new contracts to `Application/Contracts/` and implement them in Infrastructure, then wire a controller action to `sender.Send(...)`. Full conventions are in [CONVENTIONS.md](CONVENTIONS.md) §5. --- ## Persistence - Access the DB through `IUnitOfWork` — not `ApplicationDbContext` directly outside Infrastructure. - Commit once per command via `unitOfWork.CommitAsync()`. - Use `AsNoTracking()` on all read-only queries. - Always project to a DTO in queries — never return entity objects from handlers. - Add entity config in `Persistence/Configuration/Config/` implementing `IEntityTypeConfiguration`. - Soft delete is enforced via a global query filter per entity (see [CONVENTIONS.md](CONVENTIONS.md) §6). --- ## Identity & auth - JWT/JWE issued by `IJwtService` (`Baya.Infrastructure.Identity/Jwt/JwtService.cs`). - Dynamic permission system: `DynamicPermissionHandler` reads `[controller]` + `[action]` route values and checks role claims. Always use `[controller]`/`[action]` tokens so the keys stay consistent (see CONVENTIONS.md §1 Routing). - Settings bound from `appsettings.json` → `IdentitySettings`. - Auth and OTP endpoints must be rate-limited (CONVENTIONS.md §11). --- ## Conventions — quick reference Full rules in [CONVENTIONS.md](CONVENTIONS.md). The essentials: - All URL segments are `snake_case` via `SnakeCaseParameterTransformer` — use `[controller]`/`[action]` tokens. - Controllers are `sealed`, inherit `BaseController`, inject `ISender`, return `base.OperationResult(result)`. Never call `Ok()` / `BadRequest()` / `NotFound()` directly. - Handlers are `internal sealed`; never throw for expected failures — return `OperationResult`. - `record` for requests/DTOs, `class` for entities (no public setters), `sealed class` for handlers/services. - `async`/`await` all the way; pass `CancellationToken` through every async call; never `.Result`/`.Wait()`/`async void`. - Mapster for mapping; FluentValidation for validation (validate at the boundary). - Package versions live **only** in `Directory.Packages.props` — never `Version=` in a `.csproj`. - No unused code (usings, locals, parameters, private fields/members) and no *what*-comments — explain *why*, prefer self-documenting names (§2). - Architecture changes (a project/layer/major folder or a cross-layer dependency) must update the **Project map** in the same change. - The `Baya.*` namespace is project naming — do not rename without explicit instruction. --- ## Known build warnings (pre-existing — do not fix unless tasked) | Warning | Project | Note | | ------- | ------- | ---- | | `NU1510` on `Microsoft.Extensions.Logging.Debug` | `Baya.Web.Api` | Redundant transitive reference, harmless | | `NETSDK1057` (preview SDK) | all | .NET 10 SDK is preview on this machine |