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"`
|
||||
}
|
||||
Reference in New Issue
Block a user