- Make data.files.exif column nullable (was NOT NULL but service passes nil
for files without EXIF data, causing a constraint violation on upload)
- FileRepo.Create: include id in INSERT so disk storage path and DB record
share the same UUID (previously DB generated its own UUID, causing a mismatch)
- Integration test: use correct filter DSL format {t=<uuid>} instead of tag:<uuid>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add UserService (GetMe, UpdateMe, admin CRUD with block/unblock),
UserHandler (/users, /users/me), ACLHandler (GET/PUT /acl/:type/:id),
AuditHandler (GET /audit with all filters). Fix UserRepo.Update to
include is_blocked. Wire all remaining routes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add pool repo (gap-based position ordering, cursor pagination, add/remove/reorder
files), service, handler, and wire all /pools endpoints including
/pools/:id/files, /pools/:id/files/remove, and /pools/:id/files/reorder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add category repo, service, handler, and wire all /categories endpoints
including list, create, get, update, delete, and list-tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
filter_parser.go — recursive-descent parser for the {token,...} DSL.
Tokens: t=UUID (tag), m=INT (MIME exact), m~PATTERN (MIME LIKE),
operators & | ! ( ) with standard NOT>AND>OR precedence.
All values go through pgx parameters ($N) — SQL injection impossible.
file_repo.go — full FileRepo:
- Create/GetByID/Update via CTE RETURNING with JOIN for one round-trip
- SoftDelete/Restore/DeletePermanent with RowsAffected guards
- SetTags: full replace (DELETE + INSERT per tag)
- ListTags: delegates to loadTagsBatch (single query for N files)
- List: keyset cursor pagination (bidirectional), anchor mode,
filter DSL, search ILIKE, trash flag, 4 sort columns.
Cursor is base64url(JSON) encoding sort position; backward
pagination fetches in reversed ORDER BY then reverses the slice.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AuditRepo.Log resolves action_type_id/object_type_id via SQL subqueries.
AuditRepo.List supports dynamic filtering by user, action, object type/ID,
and date range with COUNT(*) OVER() for total count.
AuditService.Log reads user from context, marshals details to JSON,
and delegates to the repo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add postgres ACLRepo (List/Get/Set) and ACLService with CanView/CanEdit
checks (admin bypass, public flag, creator shortcut, explicit grants)
and GetPermissions/SetPermissions for the /acl endpoints.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
db/db.go: TxFromContext/ContextWithTx for transaction propagation,
Querier interface (QueryRow/Query/Exec), ScanRow generic helper,
ClampLimit/ClampOffset pagination guards.
db/postgres/postgres.go: NewPool with ping validation, Transactor
backed by pgxpool (BeginTx → fn → commit/rollback), connOrTx helper
that returns the active transaction from context or falls back to pool.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>