Initial commit
This commit is contained in:
commit
afed023152
8152 files changed
+2086461
No files matched your search
@@ -0,0 +1,89 @@
|
||||
// Package items is the reference vertical slice: model, repository
|
||||
// interface, Bun implementation, and service. To add a new resource, copy
|
||||
// this package's shape and register its routes in internal/api.
|
||||
package items
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
// Status is the lifecycle state of an item, stored as text.
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusActive Status = "active"
|
||||
StatusArchived Status = "archived"
|
||||
)
|
||||
|
||||
// Valid reports whether s is a known status value.
|
||||
func (s Status) Valid() bool {
|
||||
return s == StatusActive || s == StatusArchived
|
||||
}
|
||||
|
||||
// Item is the persisted model. Struct tags are Bun's mapping; json tags
|
||||
// define the wire format (camelCase, matching the frontend).
|
||||
type Item struct {
|
||||
bun.BaseModel `bun:"table:items,alias:i"`
|
||||
|
||||
ID int64 `bun:"id,pk,autoincrement" json:"id"`
|
||||
Title string `bun:"title,notnull" json:"title"`
|
||||
Description string `bun:"description,nullzero" json:"description"`
|
||||
Status Status `bun:"status,notnull,default:'active'" json:"status"`
|
||||
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"createdAt"`
|
||||
UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp" json:"updatedAt"`
|
||||
}
|
||||
|
||||
// BeforeAppendModel stamps timestamps on INSERT and UPDATE queries.
|
||||
// Postgres stores microsecond UTC; truncate + UTC so values set in Go
|
||||
// (PATCH responses) match what round-trips through the database.
|
||||
func (i *Item) BeforeAppendModel(_ context.Context, query bun.Query) error {
|
||||
now := time.Now().UTC().Truncate(time.Microsecond)
|
||||
switch query.(type) {
|
||||
case *bun.InsertQuery:
|
||||
i.CreatedAt, i.UpdatedAt = now, now
|
||||
case *bun.UpdateQuery:
|
||||
i.UpdatedAt = now
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filter narrows and pages List queries. Apply normalizeFilter (via the
|
||||
// service) before handing one to a repository.
|
||||
type Filter struct {
|
||||
Status Status
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
// List result paging defaults, applied by normalizeFilter.
|
||||
const (
|
||||
DefaultLimit = 50
|
||||
MaxLimit = 100
|
||||
)
|
||||
|
||||
// Repository is the persistence contract for items. The service depends on
|
||||
// this interface — not on Bun — so tests use in-memory fakes and other
|
||||
// backends remain possible.
|
||||
type Repository interface {
|
||||
List(ctx context.Context, f Filter) ([]Item, error)
|
||||
Get(ctx context.Context, id int64) (Item, error)
|
||||
Create(ctx context.Context, item *Item) error
|
||||
Update(ctx context.Context, item *Item) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
// ErrNotFound is returned by repositories and services when an item does
|
||||
// not exist. Handlers map it to 404.
|
||||
var ErrNotFound = errors.New("item not found")
|
||||
|
||||
// ValidationError reports an invalid field value. Handlers map it to 400.
|
||||
type ValidationError struct {
|
||||
Field string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string { return e.Field + ": " + e.Reason }
|
||||
Reference in new issue
Block a user