refactor: 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:
Masahiko AMANO 2026-04-04 00:06:44 +03:00
parent a9209ae3a3
commit 8dd2d631e5
6 changed files with 70 additions and 25 deletions

View File

@ -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
}

View File

@ -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"}
)

View File

@ -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()
}

View File

@ -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
}

View File

@ -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
}

View File

@ -4,12 +4,12 @@ import "time"
// User is an application user.
type User struct {
ID int16
Name string
Password string // bcrypt hash; only populated when needed for auth
IsAdmin bool
CanCreate bool
IsBlocked bool
ID int16
Name string
Password string // bcrypt hash; only populated when needed for auth
IsAdmin bool
CanCreate bool
IsBlocked bool
}
// Session is an active user session.
@ -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