CRUD stack: ColumnPermission enforcement, ETag concurrency, audit fields, cursor pagination + Aop-Result integration#15
Open
MistyKuu wants to merge 7 commits into
Open
Conversation
…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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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}ColumnPermissionsclass: GET/POST/PATCH/bulk responses, paginated list items, andselect=projections. Anonymous callers are treated as having no permissions (safe by default).[CrudApi(Concurrency = true)]— optimistic concurrency: generatedRowVersionpartial, weakETagon GET/POST/PATCH,If-Matchrequired on PATCH (428 missing / 412 stale), honored on DELETE,If-Match: *bypass. Generated CRUD smoke tests sendIf-Match: *automatically.[CrudApi(Audit = true)]— endpoints stampCreatedAt/UpdatedAt(UTC) andCreatedBy/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=on generated GET list endpoints returnsCursorPage<T>with opaquenextCursor(null on last page).filter=composes;sort=ignored in cursor mode; int/long/Guid/string keys.Result/Result<T>receive[Authorize]failures asError.Unauthorizedand[Validate]failures asError.Validationinstead of exceptions. NewAspectAuthorizationException/AspectValidationExceptionderive from the previous types for compatibility. Method-own exceptions still throw.Deliberately not done: soft-delete filtering inside EF/Dapper stores — generated endpoints already filter and a store-level filter would break
?includeDeleted=true.Tests
SampleApi.Tests: ColumnPermission (6), ETag concurrency (8), audit fields (3), cursor pagination (4) — 58/58 green.ResultIntegrationTestsin Aop (7) — 125/125 green incl. updated exception-type assertions.Docs
Updated
crud-api.md(column permissions, concurrency, audit, cursor),paginated.md(CursorPage),aop/built-in.md+result.md(Result integration),ui.mdnote, root README.🤖 Generated with Claude Code