feat(ports): define consumer-side interfaces, sentinel errors, IsUniqueViolation
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
package ports
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"booklib/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Re-export sentinel errors so handlers can use errors.Is via ports.
|
||||||
|
var (
|
||||||
|
ErrLastAdmin = store.ErrLastAdmin
|
||||||
|
ErrUniqueViolation = store.ErrUniqueViolation
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsUniqueViolation consolidates the pg 23505 check into a single predicate.
|
||||||
|
func IsUniqueViolation(err error) bool { return store.IsUniqueViolation(err) }
|
||||||
|
|
||||||
|
// Upload sentinel errors.
|
||||||
|
var (
|
||||||
|
ErrTooLarge = errors.New("file too large")
|
||||||
|
ErrIncomplete = errors.New("upload incomplete")
|
||||||
|
ErrSizeMismatch = errors.New("size mismatch")
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
)
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// Package ports defines consumer-side interfaces for the backend services.
|
||||||
|
// Handlers depend on these interfaces, not on concrete implementations.
|
||||||
|
// Main.go wires concrete types (*store.Store, *redispkg.R, etc.) that satisfy them.
|
||||||
|
//
|
||||||
|
// Value types (User, Book, etc.) live in internal/store and are referenced here.
|
||||||
|
package ports
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"booklib/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------- Store interfaces ----------
|
||||||
|
|
||||||
|
type UserStore interface {
|
||||||
|
CountUsers(ctx context.Context) (int, error)
|
||||||
|
CreateUser(ctx context.Context, username, hash, role string) (int64, error)
|
||||||
|
GetUserByName(ctx context.Context, username string) (store.User, error)
|
||||||
|
GetUserByID(ctx context.Context, id int64) (store.User, error)
|
||||||
|
ListUsers(ctx context.Context) ([]store.User, error)
|
||||||
|
DeleteUser(ctx context.Context, id int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type LibraryStore interface {
|
||||||
|
CreateLibrary(ctx context.Context, name, root string) (int64, error)
|
||||||
|
ListLibraries(ctx context.Context) ([]store.Library, error)
|
||||||
|
GetLibrary(ctx context.Context, id int64) (store.Library, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BookStore interface {
|
||||||
|
InsertBook(ctx context.Context, libID int64, path, title, format string, size, modTS int64, pageCount int) (int64, error)
|
||||||
|
GetBook(ctx context.Context, id int64) (store.Book, error)
|
||||||
|
ListBookMeta(ctx context.Context, libID int64) (map[string]store.BookMeta, error)
|
||||||
|
UpdateBookFile(ctx context.Context, id, size, modTS int64, pageCount int) error
|
||||||
|
DeleteBookByPath(ctx context.Context, libID int64, path string) error
|
||||||
|
DeleteBook(ctx context.Context, id int64) error
|
||||||
|
SetBookState(ctx context.Context, id int64, state, msg string) error
|
||||||
|
ListBooks(ctx context.Context, libID int64, q, prefix string, userID int64) ([]store.BookView, error)
|
||||||
|
BookHashes(ctx context.Context) (map[int64][2]int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProgressStore interface {
|
||||||
|
UpsertProgress(ctx context.Context, userID, libID int64, bookPath string, locator []byte, percent float64) error
|
||||||
|
GetProgress(ctx context.Context, userID, libID int64, bookPath string) (store.Progress, error)
|
||||||
|
ListProgress(ctx context.Context, userID int64) ([]store.Progress, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BookmarkStore interface {
|
||||||
|
InsertBookmark(ctx context.Context, userID, libID int64, bookPath string, locator []byte, percent float64, note string) (int64, error)
|
||||||
|
ListBookmarks(ctx context.Context, userID, libID int64, bookPath string) ([]store.Bookmark, error)
|
||||||
|
UpdateBookmarkNote(ctx context.Context, userID, id int64, note string) (bool, error)
|
||||||
|
DeleteBookmark(ctx context.Context, userID, id int64) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Redis interfaces ----------
|
||||||
|
|
||||||
|
type PageCache interface {
|
||||||
|
Get(ctx context.Context, key string) (string, bool)
|
||||||
|
Set(ctx context.Context, key, val string, ttl time.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RateLimiter interface {
|
||||||
|
IncrWindow(ctx context.Context, key string, ttl time.Duration) int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScanLocker interface {
|
||||||
|
ScanLock(ctx context.Context, key string, ttl time.Duration) (func(), bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Service interfaces ----------
|
||||||
|
|
||||||
|
type Scanner interface {
|
||||||
|
ScanLibraryByID(ctx context.Context, id int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadSessions interface {
|
||||||
|
Init(ctx context.Context, libID int64, name string, size, chunkSize int64) (string, error)
|
||||||
|
Status(ctx context.Context, uploadID string) ([]int64, error)
|
||||||
|
PutPart(ctx context.Context, uploadID string, index int64, body io.Reader, maxSize int64) error
|
||||||
|
Complete(ctx context.Context, uploadID string, root string) (string, error)
|
||||||
|
Sweep(ctx context.Context) error
|
||||||
|
UniquePath(root, name string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Media interface {
|
||||||
|
EnsureCover(ctx context.Context, bookID int64, format string, size, modTS int64, root, rel string) error
|
||||||
|
ChaptersOf(idx []string) []Chapter
|
||||||
|
PageIndex(ctx context.Context, bookID int64, size, modTS int64, root, rel string) ([]string, error)
|
||||||
|
CacheBuster(size, modTS int64) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapter represents a CBZ chapter derived from the archive's folder structure.
|
||||||
|
type Chapter struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Start int `json:"start"`
|
||||||
|
}
|
||||||
@@ -13,6 +13,9 @@ import (
|
|||||||
// ErrLastAdmin is returned by DeleteUser when attempting to delete the last admin.
|
// ErrLastAdmin is returned by DeleteUser when attempting to delete the last admin.
|
||||||
var ErrLastAdmin = errors.New("cannot delete the last admin")
|
var ErrLastAdmin = errors.New("cannot delete the last admin")
|
||||||
|
|
||||||
|
// ErrUniqueViolation is a sentinel for PostgreSQL unique constraint violations.
|
||||||
|
var ErrUniqueViolation = errors.New("unique violation")
|
||||||
|
|
||||||
// IsUniqueViolation reports whether err is a PostgreSQL unique constraint violation (23505).
|
// IsUniqueViolation reports whether err is a PostgreSQL unique constraint violation (23505).
|
||||||
func IsUniqueViolation(err error) bool {
|
func IsUniqueViolation(err error) bool {
|
||||||
var pgErr *pgconn.PgError
|
var pgErr *pgconn.PgError
|
||||||
|
|||||||
Reference in New Issue
Block a user