another step for constructing base project
This commit is contained in:
+114
-62
@@ -1,109 +1,161 @@
|
||||
# AGENTS.md — Balinyaar Server
|
||||
|
||||
Agent-oriented guide to the backend. For human setup/run instructions see [README.md](README.md).
|
||||
> **Coding rules** are in [CONVENTIONS.md](CONVENTIONS.md) — read it before writing any server 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 requires touching a contract that other layers depend on, think about the downstream impact.
|
||||
- You prefer simplicity and clarity over cleverness. The next engineer (or agent) should be able to read your code without a guide.
|
||||
- You never leave the codebase in a worse state than you found it. If you touch a file, leave it at least as clean as it was.
|
||||
|
||||
---
|
||||
|
||||
## 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`
|
||||
- **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 centrally pinned 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` |
|
||||
| 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` |
|
||||
| Update DB | `dotnet ef database update --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.cs` → `ApplyMigrationsAsync()` / `SeedDefaultUsersAsync()`), so a reachable DB is required.
|
||||
**Default URL:** `https://localhost:5002` — Swagger at `/swagger`.
|
||||
On boot, `Program.cs` calls `ApplyMigrationsAsync()` and `SeedDefaultUsersAsync()` — a reachable SQL Server is required to start.
|
||||
|
||||
## Projects by layer
|
||||
---
|
||||
|
||||
## Quality gates — run these before declaring work done
|
||||
|
||||
1. `dotnet build Baya.sln` — zero new warnings introduced.
|
||||
2. `dotnet test Baya.sln` — all tests pass.
|
||||
3. Read your own diff as if reviewing a PR. Ask: would a senior engineer approve this without comment?
|
||||
|
||||
---
|
||||
|
||||
## Project map
|
||||
|
||||
```
|
||||
src/
|
||||
├── Core/
|
||||
│ ├── Baya.Domain Entities (User, Order, Role...), BaseEntity, IEntity, ITimeModification
|
||||
│ └── Baya.Application CQRS Features/, Contracts/ (interfaces), Models/, MediatR pipeline (Common/)
|
||||
│ ├── Baya.Domain Entities (User, Order, Role…), BaseEntity, IEntity, ITimeModification
|
||||
│ └── Baya.Application Features/ (Commands & Queries), Contracts/, Models/, pipeline behaviors (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
|
||||
│ ├── 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/, 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
|
||||
│ ├── 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 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.
|
||||
**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.
|
||||
|
||||
## Startup wiring — `src/API/Baya.Web.Api/Program.cs`
|
||||
---
|
||||
|
||||
Service registration is composed from per-layer extension methods (in each project's `ServiceConfiguration`):
|
||||
## Startup wiring
|
||||
|
||||
Service registration is composed from per-layer extension methods (each project's `ServiceConfiguration/`):
|
||||
|
||||
```
|
||||
ConfigureHealthChecks() · SetupOpenTelemetry()
|
||||
AddApplicationServices() // MediatR + validators + pipeline behaviors
|
||||
AddApplicationServices() // Mediator + 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
|
||||
AddWebFrameworkServices() // API versioning + snake_case routing
|
||||
AddSwagger("v1", "v1.1") · RegisterValidatorsAsServices() · AddMapster()
|
||||
ConfigureGrpcPluginServices()
|
||||
```
|
||||
|
||||
Pipeline order: exception handling → Swagger → routing → **authentication → authorization** → controllers → metrics → health checks → `ConfigureGrpcPipeline()`.
|
||||
Pipeline order: exception handler → Swagger → routing → **authentication → authorization** → controllers → metrics → health checks → gRPC.
|
||||
|
||||
When adding infrastructure, expose it as an extension method and call it here rather than inlining into `Program.cs`.
|
||||
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/<Area>/{Commands|Queries}/<Name>/`. A query example (`Features/Order/Queries/GetAllOrders/`):
|
||||
Features live under `Baya.Application/Features/<Area>/{Commands|Queries}/<Name>/`:
|
||||
|
||||
- `GetAllOrdersQuery.cs` — `record ... : IRequest<OperationResult<...>>`
|
||||
- `GetAllOrdersQueryHandler.cs` — `internal` handler; depends on `IUnitOfWork`, `IMapper`; returns `OperationResult<T>`
|
||||
- `GetAllOrdersQueryResult.cs` — the DTO returned
|
||||
```
|
||||
Features/Order/
|
||||
├── Commands/CreateOrderCommand/
|
||||
│ ├── CreateOrderCommand.cs record : ICommand<OperationResult<T>>
|
||||
│ ├── CreateOrderCommand.Handler.cs internal sealed class : ICommandHandler<...>
|
||||
│ └── CreateOrderCommand.Validator.cs
|
||||
└── Queries/GetUserOrdersQuery/
|
||||
├── GetUserOrdersQuery.cs
|
||||
├── GetUserOrdersQuery.Handler.cs
|
||||
└── GetUserOrdersQuery.Result.cs
|
||||
```
|
||||
|
||||
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`.
|
||||
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 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.
|
||||
**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(...)`.
|
||||
|
||||
## 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.
|
||||
- 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/<Area>Config/` implementing `IEntityTypeConfiguration<T>`.
|
||||
|
||||
---
|
||||
|
||||
## 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.json` → `IdentitySettings` (`SecretKey`, `Encryptkey` = 16 chars, `Issuer`, `Audience`, lifetimes).
|
||||
- JWT/JWE issued by `IJwtService` (`Baya.Infrastructure.Identity/Jwt/JwtService.cs`).
|
||||
- Dynamic permission system: `DynamicPermissionHandler` reads `[controller]` + `[action]` route values and checks role claims. The key format is set at runtime — always use `[controller]`/`[action]` tokens (see CONVENTIONS.md Routing rule) so the keys are consistent.
|
||||
- Settings bound from `appsettings.json` → `IdentitySettings`.
|
||||
|
||||
## 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 (see [CONVENTIONS.md](CONVENTIONS.md) for the full rule set)
|
||||
|
||||
## Conventions
|
||||
Quick reference:
|
||||
- All URL segments are `snake_case` via `SnakeCaseParameterTransformer` — use `[controller]`/`[action]` tokens.
|
||||
- Controllers inherit `BaseController`, inject `ISender`, return `base.OperationResult(result)`.
|
||||
- Never call `Ok()` / `BadRequest()` directly in controllers.
|
||||
- Handlers are `internal sealed`; never throw for expected failures.
|
||||
- Mapster for mapping; FluentValidation for validation.
|
||||
- Package versions only in `Directory.Packages.props`.
|
||||
- The `Baya.*` namespace is project naming — do not rename without explicit instruction.
|
||||
|
||||
- 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).
|
||||
---
|
||||
|
||||
## 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 |
|
||||
|
||||
Reference in New Issue
Block a user