refactor(backend): strengthen domain layer types and add missing page types
- DomainError struct with Code() string method replaces plain errors.New sentinels; errors.Is() still works via pointer equality - UUIDCreatedAt(uuid.UUID) time.Time helper extracts timestamp from UUID v7 - Add TagOffsetPage, CategoryOffsetPage, PoolOffsetPage - FileListParams fields grouped with comments matching openapi.yaml params - Fix mismatched comment on UserPage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
36d9488f21
commit
1e2a2a61de
@ -17,5 +17,13 @@ type Category struct {
|
||||
CreatorID int16
|
||||
CreatorName string // denormalized
|
||||
IsPublic bool
|
||||
CreatedAt time.Time // extracted from UUID v7
|
||||
CreatedAt time.Time // extracted from UUID v7 via UUIDCreatedAt
|
||||
}
|
||||
|
||||
// CategoryOffsetPage is an offset-based page of categories.
|
||||
type CategoryOffsetPage struct {
|
||||
Items []Category
|
||||
Total int
|
||||
Offset int
|
||||
Limit int
|
||||
}
|
||||
|
||||
@ -1,13 +1,21 @@
|
||||
package domain
|
||||
|
||||
import "errors"
|
||||
// DomainError is a typed domain error with a stable machine-readable code.
|
||||
// Handlers map these codes to HTTP status codes.
|
||||
type DomainError struct {
|
||||
code string
|
||||
message string
|
||||
}
|
||||
|
||||
// Sentinel domain errors. Handlers map these to HTTP status codes.
|
||||
func (e *DomainError) Error() string { return e.message }
|
||||
func (e *DomainError) Code() string { return e.code }
|
||||
|
||||
// Sentinel domain errors. Use errors.Is(err, domain.ErrNotFound) for matching.
|
||||
var (
|
||||
ErrNotFound = errors.New("not found")
|
||||
ErrForbidden = errors.New("forbidden")
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
ErrConflict = errors.New("conflict")
|
||||
ErrValidation = errors.New("validation error")
|
||||
ErrUnsupportedMIME = errors.New("unsupported MIME type")
|
||||
ErrNotFound = &DomainError{"not_found", "not found"}
|
||||
ErrForbidden = &DomainError{"forbidden", "forbidden"}
|
||||
ErrUnauthorized = &DomainError{"unauthorized", "unauthorized"}
|
||||
ErrConflict = &DomainError{"conflict", "conflict"}
|
||||
ErrValidation = &DomainError{"validation_error", "validation error"}
|
||||
ErrUnsupportedMIME = &DomainError{"unsupported_mime", "unsupported MIME type"}
|
||||
)
|
||||
|
||||
@ -29,21 +29,26 @@ type File struct {
|
||||
CreatorName string // denormalized from core.users
|
||||
IsPublic bool
|
||||
IsDeleted bool
|
||||
CreatedAt time.Time // extracted from UUID v7
|
||||
CreatedAt time.Time // extracted from UUID v7 via UUIDCreatedAt
|
||||
Tags []Tag // loaded with the file
|
||||
}
|
||||
|
||||
// FileListParams holds all parameters for listing/filtering files.
|
||||
type FileListParams struct {
|
||||
Filter string
|
||||
Sort string
|
||||
Order string
|
||||
// Pagination
|
||||
Cursor string
|
||||
Anchor *uuid.UUID
|
||||
Direction string // "forward" or "backward"
|
||||
Anchor *uuid.UUID
|
||||
Limit int
|
||||
Trash bool
|
||||
Search string
|
||||
|
||||
// Sorting
|
||||
Sort string // "content_datetime" | "created" | "original_name" | "mime"
|
||||
Order string // "asc" | "desc"
|
||||
|
||||
// Filtering
|
||||
Filter string // filter DSL expression
|
||||
Search string // substring match on original_name
|
||||
Trash bool // if true, return only soft-deleted files
|
||||
}
|
||||
|
||||
// FilePage is the result of a cursor-based file listing.
|
||||
@ -52,3 +57,11 @@ type FilePage struct {
|
||||
NextCursor *string
|
||||
PrevCursor *string
|
||||
}
|
||||
|
||||
// UUIDCreatedAt extracts the creation timestamp embedded in a UUID v7.
|
||||
// UUID v7 stores Unix milliseconds in the most-significant 48 bits.
|
||||
func UUIDCreatedAt(id uuid.UUID) time.Time {
|
||||
ms := int64(id[0])<<40 | int64(id[1])<<32 | int64(id[2])<<24 |
|
||||
int64(id[3])<<16 | int64(id[4])<<8 | int64(id[5])
|
||||
return time.Unix(ms/1000, (ms%1000)*int64(time.Millisecond)).UTC()
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ type Pool struct {
|
||||
CreatorName string // denormalized
|
||||
IsPublic bool
|
||||
FileCount int
|
||||
CreatedAt time.Time // extracted from UUID v7
|
||||
CreatedAt time.Time // extracted from UUID v7 via UUIDCreatedAt
|
||||
}
|
||||
|
||||
// PoolFile is a File with its ordering position within a pool.
|
||||
@ -31,3 +31,11 @@ type PoolFilePage struct {
|
||||
Items []PoolFile
|
||||
NextCursor *string
|
||||
}
|
||||
|
||||
// PoolOffsetPage is an offset-based page of pools.
|
||||
type PoolOffsetPage struct {
|
||||
Items []Pool
|
||||
Total int
|
||||
Offset int
|
||||
Limit int
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ type Tag struct {
|
||||
CreatorID int16
|
||||
CreatorName string // denormalized
|
||||
IsPublic bool
|
||||
CreatedAt time.Time // extracted from UUID v7
|
||||
CreatedAt time.Time // extracted from UUID v7 via UUIDCreatedAt
|
||||
}
|
||||
|
||||
// TagRule defines an auto-tagging rule: when WhenTagID is applied,
|
||||
@ -31,3 +31,11 @@ type TagRule struct {
|
||||
ThenTagName string // denormalized
|
||||
IsActive bool
|
||||
}
|
||||
|
||||
// TagOffsetPage is an offset-based page of tags.
|
||||
type TagOffsetPage struct {
|
||||
Items []Tag
|
||||
Total int
|
||||
Offset int
|
||||
Limit int
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ type Session struct {
|
||||
IsCurrent bool // true when this session matches the caller's token
|
||||
}
|
||||
|
||||
// OffsetPage is a generic offset-based page of users.
|
||||
// UserPage is an offset-based page of users.
|
||||
type UserPage struct {
|
||||
Items []User
|
||||
Total int
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user