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
|
CreatorID int16
|
||||||
CreatorName string // denormalized
|
CreatorName string // denormalized
|
||||||
IsPublic bool
|
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
|
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 (
|
var (
|
||||||
ErrNotFound = errors.New("not found")
|
ErrNotFound = &DomainError{"not_found", "not found"}
|
||||||
ErrForbidden = errors.New("forbidden")
|
ErrForbidden = &DomainError{"forbidden", "forbidden"}
|
||||||
ErrUnauthorized = errors.New("unauthorized")
|
ErrUnauthorized = &DomainError{"unauthorized", "unauthorized"}
|
||||||
ErrConflict = errors.New("conflict")
|
ErrConflict = &DomainError{"conflict", "conflict"}
|
||||||
ErrValidation = errors.New("validation error")
|
ErrValidation = &DomainError{"validation_error", "validation error"}
|
||||||
ErrUnsupportedMIME = errors.New("unsupported MIME type")
|
ErrUnsupportedMIME = &DomainError{"unsupported_mime", "unsupported MIME type"}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -29,21 +29,26 @@ type File struct {
|
|||||||
CreatorName string // denormalized from core.users
|
CreatorName string // denormalized from core.users
|
||||||
IsPublic bool
|
IsPublic bool
|
||||||
IsDeleted 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
|
Tags []Tag // loaded with the file
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileListParams holds all parameters for listing/filtering files.
|
// FileListParams holds all parameters for listing/filtering files.
|
||||||
type FileListParams struct {
|
type FileListParams struct {
|
||||||
Filter string
|
// Pagination
|
||||||
Sort string
|
|
||||||
Order string
|
|
||||||
Cursor string
|
Cursor string
|
||||||
Anchor *uuid.UUID
|
|
||||||
Direction string // "forward" or "backward"
|
Direction string // "forward" or "backward"
|
||||||
|
Anchor *uuid.UUID
|
||||||
Limit int
|
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.
|
// FilePage is the result of a cursor-based file listing.
|
||||||
@ -52,3 +57,11 @@ type FilePage struct {
|
|||||||
NextCursor *string
|
NextCursor *string
|
||||||
PrevCursor *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
|
CreatorName string // denormalized
|
||||||
IsPublic bool
|
IsPublic bool
|
||||||
FileCount int
|
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.
|
// PoolFile is a File with its ordering position within a pool.
|
||||||
@ -31,3 +31,11 @@ type PoolFilePage struct {
|
|||||||
Items []PoolFile
|
Items []PoolFile
|
||||||
NextCursor *string
|
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
|
CreatorID int16
|
||||||
CreatorName string // denormalized
|
CreatorName string // denormalized
|
||||||
IsPublic bool
|
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,
|
// TagRule defines an auto-tagging rule: when WhenTagID is applied,
|
||||||
@ -31,3 +31,11 @@ type TagRule struct {
|
|||||||
ThenTagName string // denormalized
|
ThenTagName string // denormalized
|
||||||
IsActive bool
|
IsActive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagOffsetPage is an offset-based page of tags.
|
||||||
|
type TagOffsetPage struct {
|
||||||
|
Items []Tag
|
||||||
|
Total int
|
||||||
|
Offset int
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|||||||
@ -4,12 +4,12 @@ import "time"
|
|||||||
|
|
||||||
// User is an application user.
|
// User is an application user.
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int16
|
ID int16
|
||||||
Name string
|
Name string
|
||||||
Password string // bcrypt hash; only populated when needed for auth
|
Password string // bcrypt hash; only populated when needed for auth
|
||||||
IsAdmin bool
|
IsAdmin bool
|
||||||
CanCreate bool
|
CanCreate bool
|
||||||
IsBlocked bool
|
IsBlocked bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session is an active user session.
|
// Session is an active user session.
|
||||||
@ -24,7 +24,7 @@ type Session struct {
|
|||||||
IsCurrent bool // true when this session matches the caller's token
|
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 {
|
type UserPage struct {
|
||||||
Items []User
|
Items []User
|
||||||
Total int
|
Total int
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user