Skip to content

CRUD stack: ColumnPermission enforcement, ETag concurrency, audit fields, cursor pagination + Aop-Result integration#15

Open
MistyKuu wants to merge 7 commits into
masterfrom
feat/dto-column-permission-enforcement
Open

CRUD stack: ColumnPermission enforcement, ETag concurrency, audit fields, cursor pagination + Aop-Result integration#15
MistyKuu wants to merge 7 commits into
masterfrom
feat/dto-column-permission-enforcement

Conversation

@MistyKuu

Copy link
Copy Markdown
Owner

Summary

Feature sprint across Dto and Aop, worked through the backlog in order:

  • [ColumnPermission] auto-enforcement — generated CRUD endpoints (minimal API + controller) mask restricted response columns via a generated {Entity}ColumnPermissions class: GET/POST/PATCH/bulk responses, paginated list items, and select= projections. Anonymous callers are treated as having no permissions (safe by default).
  • [CrudApi(Concurrency = true)] — optimistic concurrency: generated RowVersion partial, weak ETag on GET/POST/PATCH, If-Match required on PATCH (428 missing / 412 stale), honored on DELETE, If-Match: * bypass. Generated CRUD smoke tests send If-Match: * automatically.
  • [CrudApi(Audit = true)] — endpoints stamp CreatedAt/UpdatedAt (UTC) and CreatedBy/UpdatedBy (identity name) on POST/bulk-create, refresh on PATCH and soft DELETE. Missing properties are generated on the entity partial. Store-agnostic (EF/Dapper/custom).
  • Cursor (keyset) pagination?cursor= on generated GET list endpoints returns CursorPage<T> with opaque nextCursor (null on last page). filter= composes; sort= ignored in cursor mode; int/long/Guid/string keys.
  • Aop ↔ Result — methods returning Result/Result<T> receive [Authorize] failures as Error.Unauthorized and [Validate] failures as Error.Validation instead of exceptions. New AspectAuthorizationException/AspectValidationException derive from the previous types for compatibility. Method-own exceptions still throw.
  • Cleanup: dropped stale "Phase 1B/1C/1D" fluent-config comments (feature already shipped), fixed soft-delete GetById doc/code mismatch, removed the abandoned kill-switch test file.

Deliberately not done: soft-delete filtering inside EF/Dapper stores — generated endpoints already filter and a store-level filter would break ?includeDeleted=true.

Tests

  • New integration suites in SampleApi.Tests: ColumnPermission (6), ETag concurrency (8), audit fields (3), cursor pagination (4) — 58/58 green.
  • New ResultIntegrationTests in Aop (7) — 125/125 green incl. updated exception-type assertions.
  • All CI suites pass: Log 46, Aop 125, Aop.Analyzers 133, Dto 248, Result 46, Validation 70, UI 62, Query 69, Log.Analyzers 14. Full solution builds clean.

Docs

Updated crud-api.md (column permissions, concurrency, audit, cursor), paginated.md (CursorPage), aop/built-in.md + result.md (Result integration), ui.md note, root README.

🤖 Generated with Claude Code

MistyKuu added 7 commits June 11, 2026 13:22
…points

Endpoints (minimal API + controller) now mask restricted response columns
via a generated {Entity}ColumnPermissions class: GET/POST/PATCH/bulk
responses, paginated list items, and select= projections. Anonymous users
are treated as having no permissions (safe by default). Columns ignored
from the response DTO are skipped; list masking respects [DtoIgnore(List)].
…y = true)]

Generator adds a RowVersion (long) partial to the entity, GET/POST/PATCH
respond with a weak ETag, PATCH requires If-Match (428 missing / 412 stale),
DELETE validates If-Match when provided. Generated CRUD smoke tests send
If-Match: * on concurrency-enabled entities. New Document sample entity +
ETag integration test suite.
Generated endpoints stamp CreatedAt/UpdatedAt (UTC) and CreatedBy/UpdatedBy
(caller identity name) on POST/bulk-create, refresh UpdatedAt/UpdatedBy on
PATCH and soft DELETE. Missing audit properties are emitted on the entity
partial; user-declared ones are reused. Works with EF, Dapper and custom
stores since stamping happens in the endpoint layer.
…methods

Methods returning ZibStack.NET.Result.Result / Result<T> now receive
[Authorize] failures as Error.Unauthorized and [Validate] failures as
Error.Validation instead of exceptions. Handlers throw new dedicated
AspectAuthorizationException / AspectValidationException types (derived
from the previous base types for compatibility); the generated interceptor
wraps its body in a conversion try/catch only when the (unwrapped) return
type is the fully-qualified Result type. Method-own exceptions still throw.
cursor= (empty) starts keyset pagination ordered by the entity key; the
CursorPage<T> response carries an opaque nextCursor (null on last page).
filter= composes, sort= is ignored in cursor mode, [ColumnPermission]
masking applies. Supported key types: int/long/Guid/string — others simply
don't get the cursor parameter. Offset PaginatedResponse behavior unchanged.
Also fix stale soft-delete GetById doc (generated code returns 404).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant