Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
fe8fad8
restructure: relocate layered variant into layered/ subfolder
cstacklab Jun 17, 2026
c045def
feat: add modular variant (single Api project, NsDepCop-enforced)
cstacklab Jun 17, 2026
852d7c5
chore: shared database, two-variant CI, and docs refresh
cstacklab Jun 17, 2026
a2c5c84
docs: add architecture decision records (ADR-001..003)
cstacklab Jun 20, 2026
46f1885
refactor(modular): rename AspNetCore->AspNetCoreDefaults, fold Config…
cstacklab Jun 20, 2026
dda4221
refactor: retire layered variant, promote modular to repo root (ADR-003)
cstacklab Jun 20, 2026
eb5872b
docs: remove source transcript, add architecture summary to README
cstacklab Jun 20, 2026
fa640ce
chore: nest solution folders in .slnx to mirror the directory tree
cstacklab Jun 21, 2026
b566f70
refactor(features): split Tasks into Projects/Tasks sub-feature
cstacklab Jun 21, 2026
d3241c3
refactor(features): co-locate endpoint groups with features, drop End…
cstacklab Jun 21, 2026
04c5e55
refactor(features): full vertical slices (one file per operation)
cstacklab Jun 21, 2026
5482b93
docs: update README layout for full vertical slices
cstacklab Jun 21, 2026
0928014
docs: describe single-solution NsDepCop enforcement (drop stale two-v…
cstacklab Jun 21, 2026
f089ebc
docs: add SKILLS.md how-to playbook (replaces AGENTS.md)
cstacklab Jun 21, 2026
342c91d
feat: add project Agent Skills (.claude/skills), replace SKILLS.md doc
cstacklab Jun 21, 2026
f0df231
refactor: inline validator registration, rename infrastructure DI class
cstacklab Jun 21, 2026
8d7632b
refactor: standardize extension class names on <Area>Extensions
cstacklab Jun 21, 2026
c5f8522
refactor(tests): rename test projects by type, not layer
cstacklab Jun 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions .claude/skills/add-endpoint/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
name: add-endpoint
description: Add or change an API endpoint in this codebase as a vertical slice — one file per operation co-locating its request, validator, endpoint mapping, and handler. Use when adding an endpoint, operation, or whole feature to the CleanApiStarter API.
paths: src/CleanApiStarter.Api/Features/**
---

# Add an API endpoint (vertical slice)

This API is a single `CleanApiStarter.Api` project organised as **vertical slices**:
one file per operation. Boundaries are enforced at build time by NsDepCop
(`src/CleanApiStarter.Api/config.nsdepcop`) — a handler must not reference an
`Infrastructure` type or the build fails.

## Add an operation to an existing feature

1. Create one file per operation in the feature folder, e.g.
`src/CleanApiStarter.Api/Features/Projects/ArchiveProject.cs`:

```csharp
namespace CleanApiStarter.Api.Features.Projects;

public static class ArchiveProject
{
public sealed record Request(string Reason); // omit if no body

public sealed class Validator : AbstractValidator<Request>
{
public Validator() => RuleFor(request => request.Reason).NotEmpty();
}

public static void Map(RouteGroupBuilder group) =>
group.MapPost("/{id:guid}/archive", Handle).WithName("ArchiveProjectV1");

private static async Task<IResult> Handle(
Guid id,
Request request,
IProjectRepository projectRepository, // an interface — never an Infrastructure type
IUser currentUser,
CancellationToken cancellationToken)
{
string userId = currentUser.RequireId();
// orchestrate via the repository, then return a typed result
return TypedResults.NoContent();
}
}
```

2. Register the slice in the feature's endpoint group `Map`
(`Features/Projects/Projects.cs`): add `ArchiveProject.Map(groupBuilder);`.

3. If the handler needs new data access, add a method to `IProjectRepository`
(`Features/Projects/IProjectRepository.cs`) and implement it in
`Infrastructure/Repositories/ProjectRepository.cs`.

## Add a new feature (capability)

1. Create `Features/<Capability>/`.
2. Add a thin endpoint group implementing `IEndpointGroup` — it sets `RoutePrefix`,
`MajorVersion`, and a `Map` that calls each slice's `Map`. It is discovered by
reflection; no manual registration needed.
3. Add slices as above. Put a nested resource in a sub-folder (e.g.
`Projects/Tasks/`) and a new API version in a `V2/` sub-folder.

## Conventions

- **Handlers depend only on interfaces** (`IProjectRepository`, `IAuthService`),
never on `Infrastructure` types. NsDepCop (`Features ✗→ Infrastructure`) breaks
the build otherwise.
- Validators are auto-registered (`AddValidatorsFromAssembly`) and run by the global
`ValidationFilter`, returning `422` on failure. Just add a nested
`Validator : AbstractValidator<Request>`.
- Return `TypedResults.*` (`Ok` / `Created` / `NoContent` / `NotFound` /
`Conflict` / `Forbid`) directly — there is no result-enum indirection.
- Map entities to DTOs with a static `From(...)` factory on the DTO
(e.g. `ProjectDto.From(project)`); for pages use `result.Map(ProjectDto.From)`.
- Group-level auth: the `Projects` group calls `RequireAuthorization()`; set
`.AllowAnonymous()` / `.RequireAuthorization()` per route when it differs.

## Verify

```bash
dotnet build CleanApiStarter.slnx
dotnet format CleanApiStarter.slnx --verify-no-changes
dotnet test CleanApiStarter.slnx # Docker required for the integration tests
```
39 changes: 39 additions & 0 deletions .claude/skills/add-migration/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: add-migration
description: Add or change the PostgreSQL schema in this codebase. Use when adding or modifying a database table, column, index, or migration in CleanApiStarter, including keeping the EF Core entity configuration in sync.
paths: database/migrations/**, src/CleanApiStarter.Api/Infrastructure/Persistence/**
---

# Add a database change

`database/migrations` is the schema's single source of truth. There are no EF
migrations; the EF Core entity configurations must be kept in sync with the SQL
by hand.

## Steps

1. Add `database/migrations/V<NNN>__short_description.sql`, numbered after the
latest file. Migrations are applied in file-name order by both the Aspire
AppHost and the integration test factory, so they must be **idempotent**
(`CREATE TABLE IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS`, …).

2. Update the matching EF config in
`src/CleanApiStarter.Api/Infrastructure/Persistence/Configuration/*Configuration.cs`
(an `IEntityTypeConfiguration<T>`), and the entity in
`src/CleanApiStarter.Api/Domain/Entities/` if columns changed.

## Conventions

- Timestamp columns are `TIMESTAMP WITH TIME ZONE`; all values are stored in UTC
(`DateTime.UtcNow` in code). Do not use plain `TIMESTAMP`.
- Identity tables live in `V002__create_identity_tables.sql`; application tables in
`V001`. Keep new application tables in their own `V<NNN>` file.
- Entities are persistence-agnostic POCOs; mapping details (column names, lengths,
relationships, delete behaviour) belong in the EF configuration, not the entity.

## Verify

```bash
dotnet build CleanApiStarter.slnx
dotnet test CleanApiStarter.slnx # integration tests apply the scripts to a real container
```
Loading