Files
baya-monorepo/server/AGENTS.md
T
2026-06-16 01:46:53 +03:30

7.7 KiB

AGENTS.md — Balinyaar Server

Agent-oriented guide to the backend. For human setup/run instructions see README.md.

Stack

  • ASP.NET Core / .NET 10 (net10.0), Web API
  • Clean Architecture (Domain → Application → Infrastructure → API)
  • CQRS with MediatR (source-generated Mediator)
  • EF Core 10 + SQL Server (Repository + Unit of Work)
  • ASP.NET Core Identity with JWE (signed + AES-encrypted JWT), OTP, and dynamic permission authorization
  • Mapster (mapping), FluentValidation (validation), Serilog (logging), OpenTelemetry + prometheus-net (observability), NSwag/Swagger (OpenAPI), Asp.Versioning (API versioning)
  • xUnit + NSubstitute (tests)
  • Centralized NuGet versions in Directory.Packages.props

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 <Name> --project src/Infrastructure/Baya.Infrastructure.Persistence --startup-project src/API/Baya.Web.Api

Startup project: src/API/Baya.Web.Api. Default URL https://localhost:5002, Swagger at /swagger. On boot the app applies EF migrations and seeds default users (Program.csApplyMigrationsAsync() / SeedDefaultUsersAsync()), so a reachable DB is required.

Projects by layer

src/
├── Core/
│   ├── Baya.Domain                          Entities (User, Order, Role...), BaseEntity, IEntity, ITimeModification
│   └── Baya.Application                      CQRS Features/, Contracts/ (interfaces), Models/, MediatR pipeline (Common/)
├── Infrastructure/
│   ├── Baya.Infrastructure.Persistence      ApplicationDbContext, Configuration/, Repositories/, Migrations/
│   ├── Baya.Infrastructure.Identity         Jwt/, Identity/ (Managers, Stores, PermissionManager, Seed), ServiceConfiguration/
│   ├── Baya.Infrastructure.CrossCutting     Logging (Serilog)
│   └── Baya.Infrastructure.Monitoring       HealthCheck / OpenTelemetry / Prometheus configs
├── API/
│   ├── Baya.Web.Api                         Program.cs, Controllers/V1/, appsettings*.json
│   ├── Baya.WebFramework                    BaseController, Filters/, Middlewares/, Swagger/, Attributes/
│   └── Plugins/Baya.Web.Plugins.Grpc        GrpcPluginStartup, Services/, ProtoModels/
├── Shared/Baya.SharedKernel                 Extensions + validation base used by all layers
└── Tests/                                       Baya.Tests.Setup + Baya.Test.Infrastructure.Identity

Dependency direction points inward: Domain depends on nothing; Application depends on Domain; Infrastructure and API implement/consume Application's contracts. Never make Domain or Application reference Infrastructure or the API.

Startup wiring — src/API/Baya.Web.Api/Program.cs

Service registration is composed from per-layer extension methods (in each project's ServiceConfiguration):

ConfigureHealthChecks() · SetupOpenTelemetry()
AddApplicationServices()           // MediatR + validators + pipeline behaviors
RegisterIdentityServices(...)      // Identity, JWT/JWE, authorization policies
AddPersistenceServices(...)        // DbContext, UnitOfWork, repositories
AddWebFrameworkServices()          // API versioning
AddSwagger("v1","v1.1") · RegisterValidatorsAsServices() · AddMapster()
ConfigureGrpcPluginServices()      // gRPC plugin

Pipeline order: exception handling → Swagger → routing → authentication → authorization → controllers → metrics → health checks → ConfigureGrpcPipeline().

When adding infrastructure, expose it as an extension method and call it here rather than inlining into Program.cs.

CQRS — how a feature is shaped

Features live under Baya.Application/Features/<Area>/{Commands|Queries}/<Name>/. A query example (Features/Order/Queries/GetAllOrders/):

  • GetAllOrdersQuery.csrecord ... : IRequest<OperationResult<...>>
  • GetAllOrdersQueryHandler.csinternal handler; depends on IUnitOfWork, IMapper; returns OperationResult<T>
  • GetAllOrdersQueryResult.cs — the DTO returned

Commands additionally implement IValidatableModel<T> and declare FluentValidation rules; the ValidateCommandBehavior MediatR pipeline (Application/Common/) runs validators before the handler and surfaces errors in OperationResult.

To add a feature: create the folder with the request + handler (+ result/validator), then call it from a controller via _sender.Send(...). Contracts the handler needs go in Application/Contracts/ and are implemented in Infrastructure.

Controllers & results

  • Controllers live in Baya.Web.Api/Controllers/V1/ and inherit BaseController (Baya.WebFramework/BaseController/BaseController.cs), which exposes UserId/UserName/etc. from claims and maps OperationResult<T>IActionResult.
  • All responses are wrapped in OperationResult<T> (Application/Models/Common/): Result, IsSuccess, ErrorMessages, IsNotFound, IsException. Use the factory methods (SuccessResult, FailureResult, NotFoundResult).
  • Protected endpoints use [Authorize(ConstantPolicies.DynamicPermission)].

Persistence

  • ApplicationDbContext (Baya.Infrastructure.Persistence/ApplicationDbContext.cs) extends IdentityDbContext<...>; it auto-registers IEntity types and applies all IEntityTypeConfiguration from the assembly.
  • Per-entity config in Configuration/<Area>Config/. Repositories in Repositories/ derive from BaseAsyncRepository<T>; expose them through IUnitOfWork (interface in Application/Contracts/Persistence/). Commit via unitOfWork.CommitAsync().
  • Migrations in Migrations/. Add new ones with the dotnet ef command above.

Identity & auth

  • Token service: Baya.Infrastructure.Identity/Jwt/JwtService.cs (IJwtService) — issues JWE (HMAC-SHA256 signed, AES-128 encrypted), refresh tokens, and OTP/phone-based tokens.
  • Custom Identity managers/stores under Identity/Manager/ and Identity/Store/.
  • Dynamic permissions: Identity/PermissionManager/ (DynamicPermissionService, DynamicPermissionHandler, ConstantPolicies).
  • Settings from appsettings.jsonIdentitySettings (SecretKey, Encryptkey = 16 chars, Issuer, Audience, lifetimes).

gRPC plugin

Plugins/Baya.Web.Plugins.Grpc is a self-contained module mounted via Application Parts. GrpcPluginStartup.cs provides ConfigureGrpcPluginServices() / ConfigureGrpcPipeline() (called from Program.cs). Proto contracts in ProtoModels/*.proto, services in Services/. The host uses HTTP/2 (Kestrel config) for gRPC.

Conventions

  • Add cross-layer wiring as ServiceConfiguration extension methods, not inline in Program.cs.
  • Keep handlers internal; return OperationResult<T>; don't throw for expected failures (use FailureResult/NotFoundResult).
  • Use Mapster for entity↔DTO mapping; FluentValidation for input validation.
  • Centralize package versions in Directory.Packages.props (no inline Version= in .csproj).
  • The Baya* namespace/.sln naming is internal project naming, not template branding — don't rename it without an explicit request (it touches every file and the EF migrations).