feat(repository): refactor query/mutation ports with capability-safe

interfaces

- refactor: split UserRepository into AuthUserRepository and
  AdminUserRepository capabilities
- refactor: split SessionRepository into AuthSessionRepository and
  repository-owned CLI/admin methods
- refactor: replace generic Repository Update/Delete with
  operation-specific params and ownership predicates
- refactor: replace CredentialRepository generic CRUD with
  passkey-specific methods
- refactor: replace FileRepository generic Create/Update/Delete with
  UploadedFileParams, DirectoryParams, and owned soft-delete
- refactor: remove repository fields from WebApp struct; repositories
  are now composition-time wiring only
- feat: add domain error kinds ErrParentNotFound, ErrParentNotDir,
  ErrDirectoryNotEmpty, ErrInvalidMove
- feat: add CredentialTypeAppPasskey constant
- feat: add testutil.SetUserAdmin for test fixture setup that bypasses
  production service ports
- test: add architecture test banning GORM Save in repository package
- test: add capability interface contract tests ensuring each service
  receives the minimal interface
- test: add blockingStorage helper for concurrent promotion tests
- test: add preserved DSN parameter test for sqliteImmediateDSN
- docs: update architecture decisions with repository write rules and
  capability separation
- docs: update roadmap to clarify atomic single-use refresh sessions
- docs: add -race test target to development docs
This commit is contained in:
2026-07-16 12:24:36 +08:00
parent 6604ecb026
commit a18f96912d
30 changed files with 1259 additions and 394 deletions
+3
View File
@@ -4,6 +4,9 @@ import (
"time"
)
// CredentialTypeAppPasskey identifies an application passkey credential.
const CredentialTypeAppPasskey = "app_passkey"
// Credential represents an alternative authentication credential for a user.
// The primary password is stored on the User model; additional credentials
// (app passkeys, WebAuthn, OAuth) are stored here with a type discriminator.
+9 -5
View File
@@ -6,11 +6,15 @@ import (
)
var (
ErrNotFound = errors.New("resource not found")
ErrDuplicate = errors.New("resource already exists")
ErrUnauthorized = errors.New("unauthorized")
ErrForbidden = errors.New("forbidden")
ErrUploadTooLarge = errors.New("upload exceeds maximum size")
ErrNotFound = errors.New("resource not found")
ErrDuplicate = errors.New("resource already exists")
ErrUnauthorized = errors.New("unauthorized")
ErrForbidden = errors.New("forbidden")
ErrUploadTooLarge = errors.New("upload exceeds maximum size")
ErrParentNotFound = errors.New("parent resource not found")
ErrParentNotDir = errors.New("parent resource is not a directory")
ErrDirectoryNotEmpty = errors.New("directory is not empty")
ErrInvalidMove = errors.New("invalid file move")
)
// ErrorKind classifies domain errors without tying them to a transport.