- Import task: persist object_key, author, cover_url, genres, summary, book_status in PocketBase so the runner can fetch the file and write book metadata on completion - Runner poll mode: pass task.ObjectKey instead of empty string - Runner: write BookMeta + UpsertBook in Meilisearch after chapter ingest so imported books appear in catalogue and search - Import UI: add author, cover URL, genres, summary, status fields; add AI tasks panel (chapter names, description, image gen, tagline) after import completes; add AI tasks button on each done task in the list - Admin nav: add Notifications entry to sidebar (all 5 locales) - Logout: delete user_sessions row on sign-out so sessions don't accumulate as phantoms after each login/logout cycle
229 lines
9.6 KiB
Go
229 lines
9.6 KiB
Go
// Package domain contains the core value types shared across all packages
|
|
// in this module. It has zero internal imports — only the standard library.
|
|
// Every other package imports domain; domain imports nothing from this module.
|
|
package domain
|
|
|
|
import "time"
|
|
|
|
// ── Book types ────────────────────────────────────────────────────────────────
|
|
|
|
// BookMeta carries all bibliographic information about a novel.
|
|
type BookMeta struct {
|
|
Slug string `json:"slug"`
|
|
Title string `json:"title"`
|
|
Author string `json:"author"`
|
|
Cover string `json:"cover,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
Genres []string `json:"genres,omitempty"`
|
|
Summary string `json:"summary,omitempty"`
|
|
TotalChapters int `json:"total_chapters,omitempty"`
|
|
SourceURL string `json:"source_url"`
|
|
Ranking int `json:"ranking,omitempty"`
|
|
Rating float64 `json:"rating,omitempty"`
|
|
// MetaUpdated is the Unix timestamp (seconds) when the book record was last
|
|
// updated in PocketBase. Populated on read; not sent on write (PocketBase
|
|
// manages its own updated field).
|
|
MetaUpdated int64 `json:"meta_updated,omitempty"`
|
|
}
|
|
|
|
// CatalogueEntry is a lightweight book reference returned by catalogue pages.
|
|
type CatalogueEntry struct {
|
|
Slug string `json:"slug"`
|
|
Title string `json:"title"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// ChapterRef is a reference to a single chapter returned by chapter-list pages.
|
|
type ChapterRef struct {
|
|
Number int `json:"number"`
|
|
Title string `json:"title"`
|
|
URL string `json:"url"`
|
|
Volume int `json:"volume,omitempty"`
|
|
}
|
|
|
|
// Chapter contains the fully-extracted text of a single chapter.
|
|
type Chapter struct {
|
|
Ref ChapterRef `json:"ref"`
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
// RankingItem represents a single entry in the novel ranking list.
|
|
type RankingItem struct {
|
|
Rank int `json:"rank"`
|
|
Slug string `json:"slug"`
|
|
Title string `json:"title"`
|
|
Author string `json:"author,omitempty"`
|
|
Cover string `json:"cover,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
Genres []string `json:"genres,omitempty"`
|
|
SourceURL string `json:"source_url,omitempty"`
|
|
Updated time.Time `json:"updated,omitempty"`
|
|
}
|
|
|
|
// ── Voice types ───────────────────────────────────────────────────────────────
|
|
|
|
// Voice describes a single text-to-speech voice available in the system.
|
|
type Voice struct {
|
|
// ID is the voice identifier passed to TTS clients (e.g. "af_bella", "alba").
|
|
ID string `json:"id"`
|
|
// Engine is "kokoro" or "pocket-tts".
|
|
Engine string `json:"engine"`
|
|
// Lang is the primary language tag (e.g. "en-us", "en-gb", "en", "es", "fr").
|
|
Lang string `json:"lang"`
|
|
// Gender is "f" or "m".
|
|
Gender string `json:"gender"`
|
|
}
|
|
|
|
// ── Storage record types ──────────────────────────────────────────────────────
|
|
|
|
// ChapterInfo is a lightweight chapter descriptor stored in the index.
|
|
type ChapterInfo struct {
|
|
Number int `json:"number"`
|
|
Title string `json:"title"`
|
|
Date string `json:"date,omitempty"`
|
|
}
|
|
|
|
// ReadingProgress holds a single user's reading position for one book.
|
|
type ReadingProgress struct {
|
|
Slug string `json:"slug"`
|
|
Chapter int `json:"chapter"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// ── Task record types ─────────────────────────────────────────────────────────
|
|
|
|
// TaskStatus enumerates the lifecycle states of any task.
|
|
type TaskStatus string
|
|
|
|
const (
|
|
TaskStatusPending TaskStatus = "pending"
|
|
TaskStatusRunning TaskStatus = "running"
|
|
TaskStatusDone TaskStatus = "done"
|
|
TaskStatusFailed TaskStatus = "failed"
|
|
TaskStatusCancelled TaskStatus = "cancelled"
|
|
)
|
|
|
|
// ScrapeTask represents a book-scraping job stored in PocketBase.
|
|
type ScrapeTask struct {
|
|
ID string `json:"id"`
|
|
Kind string `json:"kind"` // "catalogue" | "book" | "book_range"
|
|
TargetURL string `json:"target_url"` // non-empty for single-book tasks
|
|
FromChapter int `json:"from_chapter,omitempty"`
|
|
ToChapter int `json:"to_chapter,omitempty"`
|
|
WorkerID string `json:"worker_id,omitempty"`
|
|
Status TaskStatus `json:"status"`
|
|
BooksFound int `json:"books_found"`
|
|
ChaptersScraped int `json:"chapters_scraped"`
|
|
ChaptersSkipped int `json:"chapters_skipped"`
|
|
Errors int `json:"errors"`
|
|
Started time.Time `json:"started"`
|
|
Finished time.Time `json:"finished,omitempty"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
}
|
|
|
|
// ScrapeResult is the outcome reported by the runner after finishing a ScrapeTask.
|
|
type ScrapeResult struct {
|
|
BooksFound int `json:"books_found"`
|
|
ChaptersScraped int `json:"chapters_scraped"`
|
|
ChaptersSkipped int `json:"chapters_skipped"`
|
|
Errors int `json:"errors"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
}
|
|
|
|
// AudioTask represents an audio-generation job stored in PocketBase.
|
|
type AudioTask struct {
|
|
ID string `json:"id"`
|
|
CacheKey string `json:"cache_key"` // "slug/chapter/voice"
|
|
Slug string `json:"slug"`
|
|
Chapter int `json:"chapter"`
|
|
Voice string `json:"voice"`
|
|
WorkerID string `json:"worker_id,omitempty"`
|
|
Status TaskStatus `json:"status"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
Started time.Time `json:"started"`
|
|
Finished time.Time `json:"finished,omitempty"`
|
|
}
|
|
|
|
// AudioResult is the outcome reported by the runner after finishing an AudioTask.
|
|
type AudioResult struct {
|
|
ObjectKey string `json:"object_key,omitempty"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
}
|
|
|
|
// TranslationTask represents a machine-translation job stored in PocketBase.
|
|
type TranslationTask struct {
|
|
ID string `json:"id"`
|
|
CacheKey string `json:"cache_key"` // "{slug}/{chapter}/{lang}"
|
|
Slug string `json:"slug"`
|
|
Chapter int `json:"chapter"`
|
|
Lang string `json:"lang"`
|
|
WorkerID string `json:"worker_id,omitempty"`
|
|
Status TaskStatus `json:"status"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
Started time.Time `json:"started"`
|
|
Finished time.Time `json:"finished,omitempty"`
|
|
}
|
|
|
|
// TranslationResult is the outcome reported by the runner after finishing a TranslationTask.
|
|
type TranslationResult struct {
|
|
ObjectKey string `json:"object_key,omitempty"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
}
|
|
|
|
// ImportTask represents a PDF/EPUB import job stored in PocketBase.
|
|
type ImportTask struct {
|
|
ID string `json:"id"`
|
|
Slug string `json:"slug"` // derived from filename
|
|
Title string `json:"title"`
|
|
FileName string `json:"file_name"`
|
|
FileType string `json:"file_type"` // "pdf" or "epub"
|
|
ObjectKey string `json:"object_key,omitempty"` // MinIO path to uploaded file
|
|
Author string `json:"author,omitempty"`
|
|
CoverURL string `json:"cover_url,omitempty"`
|
|
Genres []string `json:"genres,omitempty"`
|
|
Summary string `json:"summary,omitempty"`
|
|
BookStatus string `json:"book_status,omitempty"` // "ongoing" | "completed" | "hiatus"
|
|
WorkerID string `json:"worker_id,omitempty"`
|
|
InitiatorUserID string `json:"initiator_user_id,omitempty"` // PocketBase user ID who submitted the import
|
|
Status TaskStatus `json:"status"`
|
|
ChaptersDone int `json:"chapters_done"`
|
|
ChaptersTotal int `json:"chapters_total"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
Started time.Time `json:"started"`
|
|
Finished time.Time `json:"finished,omitempty"`
|
|
}
|
|
|
|
// ImportResult is the outcome reported by the runner after finishing an ImportTask.
|
|
type ImportResult struct {
|
|
Slug string `json:"slug,omitempty"`
|
|
ChaptersImported int `json:"chapters_imported"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
}
|
|
|
|
// AIJob represents an AI generation task tracked in PocketBase (ai_jobs collection).
|
|
type AIJob struct {
|
|
ID string `json:"id"`
|
|
// Kind is one of: "chapter-names", "batch-covers", "chapter-covers", "refresh-metadata".
|
|
Kind string `json:"kind"`
|
|
// Slug is the book slug for per-book jobs; empty for catalogue-wide jobs.
|
|
Slug string `json:"slug"`
|
|
Status TaskStatus `json:"status"`
|
|
// FromItem is the first item to process (chapter number, or 0-based book index).
|
|
// 0 = start from the beginning.
|
|
FromItem int `json:"from_item"`
|
|
// ToItem is the last item to process (inclusive). 0 = process all.
|
|
ToItem int `json:"to_item"`
|
|
// ItemsDone is the cumulative count of successfully processed items.
|
|
ItemsDone int `json:"items_done"`
|
|
// ItemsTotal is the total number of items in this job.
|
|
ItemsTotal int `json:"items_total"`
|
|
Model string `json:"model"`
|
|
// Payload is a JSON-encoded string with job-specific parameters
|
|
// (e.g. naming pattern for chapter-names, num_steps for batch-covers).
|
|
Payload string `json:"payload"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
Started time.Time `json:"started,omitempty"`
|
|
Finished time.Time `json:"finished,omitempty"`
|
|
HeartbeatAt time.Time `json:"heartbeat_at,omitempty"`
|
|
}
|