models

package
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Index

Constants

View Source
const (
	//tygo:emit export type DataSource = typeof DataSourceManual | typeof DataSourceSidecar | typeof DataSourcePlugin | typeof DataSourceFileMetadata | typeof DataSourceExistingCover | typeof DataSourceEPUBMetadata | typeof DataSourceCBZMetadata | typeof DataSourceM4BMetadata | typeof DataSourcePDFMetadata | typeof DataSourceMOBIMetadata | typeof DataSourceFilepath | `plugin:${string}`;
	DataSourceManual        = "manual"
	DataSourceSidecar       = "sidecar"
	DataSourcePlugin        = "plugin"
	DataSourceFileMetadata  = "file_metadata"
	DataSourceExistingCover = "existing_cover"
	DataSourceEPUBMetadata  = "epub_metadata"
	DataSourceCBZMetadata   = "cbz_metadata"
	DataSourceM4BMetadata   = "m4b_metadata"
	DataSourcePDFMetadata   = "pdf_metadata"
	DataSourceMOBIMetadata  = "mobi_metadata" // MOBI and AZW3 files
	DataSourceFilepath      = "filepath"

	// DataSourcePluginPrefix is the prefix for plugin-specific data sources.
	// Actual values are "plugin:scope/id" (e.g., "plugin:shisho/goodreads-metadata").
	DataSourcePluginPrefix = "plugin:"
)
View Source
const (
	DataSourceManualPriority       = 0
	DataSourceSidecarPriority      = 1 // Sidecar has higher priority than file metadata
	DataSourcePluginPriority       = 2 // Plugin enricher/parser results
	DataSourceFileMetadataPriority = 3 // All file-derived sources share this
	DataSourceFilepathPriority     = 4
)

Lower priority means that we respect it more than higher priority.

View Source
const (
	// FingerprintAlgorithmSHA256 is the exact-content sha256 hash over the
	// file's raw bytes. Used for move/rename detection.
	FingerprintAlgorithmSHA256 = "sha256"
	// FingerprintAlgorithmKOReaderPartialMD5 is KOReader's partial MD5 of the
	// file's current bytes. Its row says those bytes are in the document hash
	// map (DocumentHash); a change to the file deletes the row, not the map's.
	FingerprintAlgorithmKOReaderPartialMD5 = "koreader_partial_md5"
)

Fingerprint algorithm identifiers stored in FileFingerprint.Algorithm.

View Source
const (
	//tygo:emit export type IdentifierType = typeof IdentifierTypeISBN10 | typeof IdentifierTypeISBN13 | typeof IdentifierTypeASIN | typeof IdentifierTypeUUID | typeof IdentifierTypeGoodreads | typeof IdentifierTypeGoogle | typeof IdentifierTypeOther | (string & {});
	IdentifierTypeISBN10    = "isbn_10"
	IdentifierTypeISBN13    = "isbn_13"
	IdentifierTypeASIN      = "asin"
	IdentifierTypeUUID      = "uuid"
	IdentifierTypeGoodreads = "goodreads"
	IdentifierTypeGoogle    = "google"
	IdentifierTypeOther     = "other"
)

FileIdentifier type constants.

View Source
const (
	//tygo:emit export type FileType = typeof FileTypeAZW3 | typeof FileTypeCBZ | typeof FileTypeEPUB | typeof FileTypeKePub | typeof FileTypeM4B | typeof FileTypeMOBI | typeof FileTypePDF;
	//tygo:emit export type DeliveredFileType = FileType;
	FileTypeAZW3 = "azw3"
	FileTypeCBZ  = "cbz"
	FileTypeEPUB = "epub"
	// FileTypeKePub is Kobo's EPUB: an EPUB with Kobo markup, named
	// .kepub.epub. Kobo readers and KOReader tell it from an EPUB by that
	// name alone, and so does Shisho. A Calibre library stores one as a
	// bare .kepub, which is a KePub too.
	FileTypeKePub = "kepub"
	FileTypeM4B   = "m4b"
	FileTypeMOBI  = "mobi"
	FileTypePDF   = "pdf"
)
View Source
const (
	//tygo:emit export type FileRole = typeof FileRoleMain | typeof FileRoleSupplement;
	FileRoleMain       = "main"
	FileRoleSupplement = "supplement"
)
View Source
const (
	//tygo:emit export type ReviewOverride = typeof ReviewOverrideReviewed | typeof ReviewOverrideUnreviewed;
	ReviewOverrideReviewed   = "reviewed"
	ReviewOverrideUnreviewed = "unreviewed"
)
View Source
const (
	//tygo:emit export type ReviewedFilter = typeof ReviewedFilterAll | typeof ReviewedFilterNeedsReview | typeof ReviewedFilterReviewed;
	ReviewedFilterAll         = "all"
	ReviewedFilterNeedsReview = "needs_review"
	ReviewedFilterReviewed    = "reviewed"
)

ReviewedFilter values for the book list endpoint's reviewed_filter query param. "" and "all" both mean "all books"; the other two scope to needs-review or reviewed books respectively.

View Source
const (
	//tygo:emit export type JobStatus = typeof JobStatusPending | typeof JobStatusInProgress | typeof JobStatusCompleted | typeof JobStatusFailed;
	JobStatusPending    = "pending"
	JobStatusInProgress = "in_progress"
	JobStatusCompleted  = "completed"
	JobStatusFailed     = "failed"
)
View Source
const (
	//tygo:emit export type JobType = typeof JobTypeExport | typeof JobTypeScan | typeof JobTypeBulkDownload | typeof JobTypeHashGeneration | typeof JobTypeRecomputeReview;
	JobTypeExport          = "export"
	JobTypeScan            = "scan"
	JobTypeBulkDownload    = "bulk_download"
	JobTypeHashGeneration  = "hash_generation"
	JobTypeRecomputeReview = "recompute_review"
)
View Source
const (
	//tygo:emit export type CoverAspectRatio = typeof CoverAspectRatioBook | typeof CoverAspectRatioAudiobook | typeof CoverAspectRatioBookFallbackAudiobook | typeof CoverAspectRatioAudiobookFallbackBook;
	CoverAspectRatioBook                  = "book"
	CoverAspectRatioAudiobook             = "audiobook"
	CoverAspectRatioBookFallbackAudiobook = "book_fallback_audiobook"
	CoverAspectRatioAudiobookFallbackBook = "audiobook_fallback_book"
)

Cover aspect ratio preference constants. Determine which file's cover a hybrid (e.g. EPUB + M4B) book serves.

View Source
const (
	//tygo:emit export type ListPermission = typeof ListPermissionViewer | typeof ListPermissionEditor | typeof ListPermissionManager;
	ListPermissionViewer  = "viewer"
	ListPermissionEditor  = "editor"
	ListPermissionManager = "manager"
)

List permission levels.

View Source
const (
	//tygo:emit export type ListSort = typeof ListSortManual | typeof ListSortAddedAtDesc | typeof ListSortAddedAtAsc | typeof ListSortTitleAsc | typeof ListSortTitleDesc | typeof ListSortAuthorAsc | typeof ListSortAuthorDesc | typeof ListSortRatingDesc | typeof ListSortRatingAsc | typeof ListSortDateFinishedDesc | typeof ListSortDateFinishedAsc | typeof ListSortReadingStatusAsc | typeof ListSortReadingStatusDesc;
	ListSortAddedAtDesc = "added_at_desc"
	ListSortAddedAtAsc  = "added_at_asc"
	ListSortTitleAsc    = "title_asc"
	ListSortTitleDesc   = "title_desc"
	ListSortAuthorAsc   = "author_asc"
	ListSortAuthorDesc  = "author_desc"
	ListSortManual      = "manual"
	// The reading status, rating, and finish date sorts order by each
	// viewer's own Reading Status, Rating, and latest finished Read-through.
	ListSortReadingStatusAsc  = "reading_status_asc"
	ListSortReadingStatusDesc = "reading_status_desc"
	ListSortRatingDesc        = "rating_desc"
	ListSortRatingAsc         = "rating_asc"
	ListSortDateFinishedDesc  = "date_finished_desc"
	ListSortDateFinishedAsc   = "date_finished_asc"
)

List default sort options.

View Source
const (
	//tygo:emit export type LogLevel = typeof LogLevelDebug | typeof LogLevelInfo | typeof LogLevelWarn | typeof LogLevelError | typeof LogLevelFatal;
	LogLevelDebug = "debug"
	LogLevelInfo  = "info"
	LogLevelWarn  = "warn"
	LogLevelError = "error"
	LogLevelFatal = "fatal"
)
View Source
const (
	//tygo:emit export type AuthorRole = typeof AuthorRoleWriter | typeof AuthorRolePenciller | typeof AuthorRoleInker | typeof AuthorRoleColorist | typeof AuthorRoleLetterer | typeof AuthorRoleCoverArtist | typeof AuthorRoleEditor | typeof AuthorRoleTranslator;
	AuthorRoleWriter      = "writer"
	AuthorRolePenciller   = "penciller"
	AuthorRoleInker       = "inker"
	AuthorRoleColorist    = "colorist"
	AuthorRoleLetterer    = "letterer"
	AuthorRoleCoverArtist = "cover_artist"
	AuthorRoleEditor      = "editor"
	AuthorRoleTranslator  = "translator"
)

Author role constants for CBZ ComicInfo.xml creator types.

View Source
const (
	//tygo:emit export type PluginHookType = typeof PluginHookInputConverter | typeof PluginHookFileParser | typeof PluginHookMetadataEnricher;
	PluginHookInputConverter   = "inputConverter"
	PluginHookFileParser       = "fileParser"
	PluginHookMetadataEnricher = "metadataEnricher"
)

Plugin hook type constants.

View Source
const (
	//tygo:emit export type PluginMode = typeof PluginModeEnabled | typeof PluginModeManualOnly | typeof PluginModeDisabled;
	PluginModeEnabled    = "enabled"
	PluginModeManualOnly = "manual_only"
	PluginModeDisabled   = "disabled"
)

Plugin mode constants for hook config entries.

View Source
const (
	//tygo:emit export type ReadingStatus = typeof ReadingStatusCurrentlyReading | typeof ReadingStatusPaused | typeof ReadingStatusToRead | typeof ReadingStatusRead | typeof ReadingStatusDidNotFinish;
	ReadingStatusCurrentlyReading = "currently_reading"
	ReadingStatusPaused           = "paused"
	ReadingStatusToRead           = "to_read"
	ReadingStatusRead             = "read"
	ReadingStatusDidNotFinish     = "did_not_finish"
)

Reading Statuses. A status is derived from a user's Read-throughs and To Read membership on every read and never stored (ADR 0013); see reading.StatusExpr for the order.

View Source
const (
	//tygo:emit export type ReadThroughOutcome = typeof ReadThroughOutcomeUnderway | typeof ReadThroughOutcomeFinished | typeof ReadThroughOutcomeAbandoned;
	ReadThroughOutcomeUnderway  = "underway"
	ReadThroughOutcomeFinished  = "finished"
	ReadThroughOutcomeAbandoned = "abandoned"
)

Read-through outcomes.

View Source
const (
	//tygo:emit export type PageFlow = typeof PageFlowPaginated | typeof PageFlowScrolled;
	PageFlowPaginated = "paginated"
	PageFlowScrolled  = "scrolled"
)

Page flows: how the Page-based reader lays out a Book's pages, one at a time or as one continuous scroll.

View Source
const (
	//tygo:emit export type ReadingSource = typeof ReadingSourceWebReader | typeof ReadingSourceKOReader | typeof ReadingSourceKobo;
	ReadingSourceWebReader = "web_reader"
	ReadingSourceKOReader  = "koreader"
	ReadingSourceKobo      = "kobo"
)

Reading sources: what reported a Reading Position update or a Reading Session.

View Source
const (
	ResourceLibraries = "libraries"
	ResourceBooks     = "books"
	ResourcePeople    = "people"
	ResourceSeries    = "series"
	ResourceUsers     = "users"
	ResourceJobs      = "jobs"
	ResourceConfig    = "config"
	ResourceShares    = "shares"
)

Permission resources.

View Source
const (
	OperationRead  = "read"
	OperationWrite = "write"
)

Permission operations.

View Source
const (
	RoleAdmin  = "admin"
	RoleEditor = "editor"
	RoleViewer = "viewer"
)

Predefined role names.

View Source
const (
	//tygo:emit export type SeriesNumberUnit = typeof SeriesNumberUnitVolume | typeof SeriesNumberUnitChapter;
	SeriesNumberUnitVolume  = "volume"
	SeriesNumberUnitChapter = "chapter"
)
View Source
const (
	//tygo:emit export type ShareLinkState = typeof ShareLinkStateActive | typeof ShareLinkStateExpired | typeof ShareLinkStateRevoked;
	ShareLinkStateActive  = "active"
	ShareLinkStateExpired = "expired"
	ShareLinkStateRevoked = "revoked"
)

Share Link states. The state is derived from revoked_at and expires_at on every read and never stored.

View Source
const (
	//tygo:emit export type ShareLinkPausedReason = typeof ShareLinkPausedCreatorDeactivated | typeof ShareLinkPausedCreatorNoLibraryAccess;
	ShareLinkPausedCreatorDeactivated     = "creator_deactivated"
	ShareLinkPausedCreatorNoLibraryAccess = "creator_no_library_access"
)

Reasons an active Share Link is paused: it does not resolve because of its creator, and works again if the creator's access returns. Like the state, the reason is derived on every read and never stored.

View Source
const (
	//tygo:emit export type FitMode = typeof FitModeHeight | typeof FitModeWidth;
	FitModeHeight = "fit-height"
	FitModeWidth  = "fit-width"
)
View Source
const (
	//tygo:emit export type ReflowableTheme = typeof ReflowableThemeLight | typeof ReflowableThemeDark | typeof ReflowableThemeSepia;
	ReflowableThemeLight = "light"
	ReflowableThemeDark  = "dark"
	ReflowableThemeSepia = "sepia"
)
View Source
const (
	//tygo:emit export type ReflowableFlow = typeof ReflowableFlowPaginated | typeof ReflowableFlowScrolled;
	ReflowableFlowPaginated = "paginated"
	ReflowableFlowScrolled  = "scrolled"
)
View Source
const (
	//tygo:emit export type GallerySize = typeof GallerySizeSmall | typeof GallerySizeMedium | typeof GallerySizeLarge | typeof GallerySizeExtraLarge;
	GallerySizeSmall      = "s"
	GallerySizeMedium     = "m"
	GallerySizeLarge      = "l"
	GallerySizeExtraLarge = "xl"
)
View Source
const (
	// The tygo:emit lines mirror the Go PlaybackSpeeds var into the
	// generated TS (a const array plus a union type) so the player's speed
	// menu and the backend validation share the same set of values. tygo
	// only processes const/type declarations, so the directives live here
	// rather than on the var. Keep the emitted list in sync with
	// PlaybackSpeeds.
	//
	//tygo:emit export const PlaybackSpeeds = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3] as const;
	//tygo:emit export type PlaybackSpeed = (typeof PlaybackSpeeds)[number];
	PlaybackSpeedDefault = 1.0
)
View Source
const (
	//tygo:emit export type ReadingStatusFilter = ReadingStatus | typeof ReadingStatusFilterNone;
	ReadingStatusFilterNone = "none"
)

ReadingStatusFilterNone filters book listings to Books with no Reading Status. It's a filter value only; no Book has it as its status.

Variables

BuiltInFileTypes are the file types Shisho parses itself. Each is eligible to be a main file.

View Source
var ConversionSteps = conversionGraph{
	{From: FileTypeEPUB, To: FileTypeKePub},
	{From: FileTypeCBZ, To: FileTypeKePub},
	{From: FileTypeCBZ, To: FileTypeEPUB},
	{From: FileTypeKePub, To: FileTypeEPUB},
	{From: FileTypeAZW3, To: FileTypeEPUB},
	{From: FileTypeMOBI, To: FileTypeEPUB},
	{From: FileTypeEPUB, To: FileTypeAZW3},
}

ConversionSteps are the Conversions Shisho can run. Chains of them are found automatically, so a new step makes every File Type that reaches its From reach its To too. The order breaks ties between chains of the same length and orders ReachableFileTypes.

EbookFileTypes are the file types in the ebook cover category, as opposed to M4B audiobooks. Preferred Cover is exclusive within a category.

View Source
var PlaybackSpeeds = []float64{0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3}

PlaybackSpeeds lists the allowed audiobook playback speed steps, in ascending order.

Functions

func BuiltInFileExtensions added in v0.2.0

func BuiltInFileExtensions() []string

BuiltInFileExtensions returns every extension (lowercase, no dot) of a built-in file type, sorted.

func DeliveredFileTypeExtension added in v0.5.0

func DeliveredFileTypeExtension(fileType string) string

DeliveredFileTypeExtension returns the extension (no leading dot) of a File delivered as fileType: "kepub.epub" for a KePub, which Kobo and KOReader recognize by it, and the File Type itself for every built-in File Type.

func DeliveredFileTypeForExtension added in v0.5.0

func DeliveredFileTypeForExtension(ext string) (string, bool)

DeliveredFileTypeForExtension is the inverse of DeliveredFileTypeExtension. It returns false for an extension no delivered File Type has.

func EbookCoverRank added in v0.2.0

func EbookCoverRank(fileType string) int

EbookCoverRank orders ebook files when no file is the Preferred Cover: EPUB, then KePub, then AZW3, then MOBI, then the other ebook formats, which share a rank so a stable sort keeps their order. app/utils/fileTypes.ts mirrors it.

func FileExtensionForPath added in v0.5.0

func FileExtensionForPath(path string) string

FileExtensionForPath returns a path's extension with its leading dot, as filepath.Ext does, except that a KePub's is the whole .kepub.epub. Code that renames a file keeps this, so a KePub stays one.

func FileTypeForPath added in v0.2.0

func FileTypeForPath(path string) string

FileTypeForPath returns the file type for a path: KePub for a .kepub.epub, the built-in type its extension maps to (KePub for a bare .kepub too), or else the lowercase extension without the dot, which is how plugin-parsed files and supplements are typed.

func FileTypeLabel added in v0.5.0

func FileTypeLabel(fileType string) string

FileTypeLabel is how a File Type is written for a reader: KePub, or the File Type in capitals.

func FileTypeMimeType added in v0.1.0

func FileTypeMimeType(fileType string) string

FileTypeMimeType returns the media type a file of this type is served as, or "" for a type Shisho does not know (a supplement's extension or a format only a plugin parses), which is typed by its extension instead. Downloads use it rather than the host's mime table, which in the Alpine image has no entry for .epub, .cbz, or .m4b.

func GetDataSourcePriority

func GetDataSourcePriority(source string) int

GetDataSourcePriority returns the priority for a given data source string. Handles "plugin:scope/id" format by matching the prefix.

func IsAudiobookFileType added in v0.5.0

func IsAudiobookFileType(fileType string) bool

IsAudiobookFileType reports whether a file type is an audiobook (M4B), the category opposite the ebook File Types.

func IsBuiltInFileExtension added in v0.2.0

func IsBuiltInFileExtension(ext string) bool

IsBuiltInFileExtension reports whether an extension (lowercase, no dot) belongs to a built-in file type. Plugin file parsers cannot claim these.

func IsBuiltInFileType added in v0.2.0

func IsBuiltInFileType(fileType string) bool

IsBuiltInFileType reports whether Shisho parses this file type itself. These are also the File Types a File can be delivered as, which is why the generated DeliveredFileType is FileType.

func IsEbookFileType added in v0.2.0

func IsEbookFileType(fileType string) bool

IsEbookFileType reports whether a file type is in the ebook cover category.

func IsPageBasedFileType added in v0.0.24

func IsPageBasedFileType(fileType string) bool

IsPageBasedFileType returns true for file types that derive covers from page content (CBZ, PDF). These formats should never have their covers replaced by external sources (plugins, uploads).

func PluginDataSource

func PluginDataSource(scope, id string) string

PluginDataSource returns a data source string for a specific plugin (e.g., "plugin:shisho/goodreads").

func ReachableFileTypes added in v0.5.0

func ReachableFileTypes(from string) []string

ReachableFileTypes returns the File Types a File of type from can be delivered as: from itself first, then every type a Conversion reaches.

func ResolveBookFileDisplayNames added in v0.1.0

func ResolveBookFileDisplayNames(books ...*Book)

ResolveBookFileDisplayNames sets DisplayName and ReachableFileTypes on every file of each book. Call it wherever books are loaded with their Files relation.

func ResolveFileDisplayNames added in v0.1.0

func ResolveFileDisplayNames(files []*File)

ResolveFileDisplayNames sets DisplayName and ReachableFileTypes on each file.

Types

type AppSetting added in v0.0.39

type AppSetting struct {
	bun.BaseModel `bun:"table:app_settings,alias:as" tstype:"-"`

	Key       string    `bun:",pk,nullzero" json:"key"`
	Value     string    `bun:",notnull" json:"value"`
	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp" json:"updated_at"`
}

type Author

type Author struct {
	bun.BaseModel `bun:"table:authors,alias:a" tstype:"-"`

	ID        int     `bun:",pk,nullzero" json:"id"`
	BookID    int     `bun:",nullzero" json:"book_id"`
	PersonID  int     `bun:",nullzero" json:"person_id"`
	Person    *Person `bun:"rel:belongs-to,join:person_id=id" json:"person,omitempty" tstype:"Person"`
	SortOrder int     `bun:",nullzero" json:"sort_order"`
	Role      *string `json:"role" tstype:"AuthorRole"` // CBZ creator role: writer, penciller, inker, etc. NULL for generic author
}

type Book

type Book struct {
	bun.BaseModel `bun:"table:books,alias:b" tstype:"-"`

	ID                int           `bun:",pk,nullzero" json:"id"`
	CreatedAt         time.Time     `json:"created_at"`
	UpdatedAt         time.Time     `json:"updated_at"`
	LibraryID         int           `bun:",nullzero" json:"library_id"`
	Library           *Library      `bun:"rel:belongs-to" json:"library" tstype:"Library"`
	Filepath          string        `bun:",nullzero" json:"filepath"`
	Title             string        `bun:",nullzero" json:"title"`
	TitleSource       string        `bun:",nullzero" json:"title_source" tstype:"DataSource"`
	SortTitle         string        `bun:",notnull" json:"sort_title"`
	SortTitleSource   string        `bun:",notnull" json:"sort_title_source" tstype:"DataSource"`
	Subtitle          *string       `json:"subtitle"`
	SubtitleSource    *string       `json:"subtitle_source" tstype:"DataSource"`
	Description       *string       `json:"description"`
	DescriptionSource *string       `json:"description_source" tstype:"DataSource"`
	Authors           []*Author     `bun:"rel:has-many,join:id=book_id" json:"authors,omitempty" tstype:"Author[]"`
	AuthorSource      string        `bun:",nullzero" json:"author_source" tstype:"DataSource"`
	BookSeries        []*BookSeries `bun:"rel:has-many,join:id=book_id" json:"book_series,omitempty" tstype:"BookSeries[]"`
	// SeriesSource is aggregate provenance for the ordered membership
	// collection and its Series Number groups. It never describes a Series
	// resource's name; see Series.NameSource for that.
	SeriesSource  *string      `json:"series_source" tstype:"DataSource"`
	BookGenres    []*BookGenre `bun:"rel:has-many,join:id=book_id" json:"book_genres,omitempty" tstype:"BookGenre[]"`
	GenreSource   *string      `json:"genre_source" tstype:"DataSource"`
	BookTags      []*BookTag   `bun:"rel:has-many,join:id=book_id" json:"book_tags,omitempty" tstype:"BookTag[]"`
	TagSource     *string      `json:"tag_source" tstype:"DataSource"`
	Files         []*File      `bun:"rel:has-many" json:"files" tstype:"File[]"`
	CoverCacheKey string       `bun:"-" json:"cover_cache_key"`
	// ReadingStatus and Rating are the signed-in user's Reading Status and
	// Rating (in stars), set only by listings that attach them
	// (reading.AttachReading). They are private to that user, so nothing
	// that serves another user may set them.
	ReadingStatus *string  `bun:"-" json:"reading_status,omitempty" tstype:"ReadingStatus"`
	Rating        *float64 `bun:"-" json:"rating,omitempty"`
	// ReadingProgress is how far through the Book the user is, from 0 to 1:
	// the furthest of its main Files' positions, while a Read-through is
	// underway.
	ReadingProgress *float64 `bun:"-" json:"reading_progress,omitempty"`
	// LastReadAt is the latest reading in the Read-through underway, set by the
	// same listings and just as private. See reading.LastReadExpr.
	LastReadAt *time.Time `bun:"-" json:"last_read_at,omitempty"`
}

type BookGenre

type BookGenre struct {
	bun.BaseModel `bun:"table:book_genres,alias:bg" tstype:"-"`

	ID      int    `bun:",pk,nullzero" json:"id"`
	BookID  int    `bun:",nullzero" json:"book_id"`
	GenreID int    `bun:",nullzero" json:"genre_id"`
	Genre   *Genre `bun:"rel:belongs-to,join:genre_id=id" json:"genre,omitempty" tstype:"Genre"`
}

type BookSeries

type BookSeries struct {
	bun.BaseModel `bun:"table:book_series,alias:bs" tstype:"-"`

	ID               int      `bun:",pk,nullzero" json:"id"`
	BookID           int      `bun:",nullzero" json:"book_id"`
	SeriesID         int      `bun:",nullzero" json:"series_id"`
	Series           *Series  `bun:"rel:belongs-to,join:series_id=id" json:"series,omitempty" tstype:"Series"`
	SeriesNumber     *float64 `json:"series_number,omitempty"`
	SeriesNumberEnd  *float64 `json:"series_number_end,omitempty"`
	SeriesNumberUnit *string  `json:"series_number_unit,omitempty" tstype:"SeriesNumberUnit"`
	SortOrder        int      `bun:",nullzero" json:"sort_order"`
}

type BookTag

type BookTag struct {
	bun.BaseModel `bun:"table:book_tags,alias:bt" tstype:"-"`

	ID     int  `bun:",pk,nullzero" json:"id"`
	BookID int  `bun:",nullzero" json:"book_id"`
	TagID  int  `bun:",nullzero" json:"tag_id"`
	Tag    *Tag `bun:"rel:belongs-to,join:tag_id=id" json:"tag,omitempty" tstype:"Tag"`
}

type Chapter

type Chapter struct {
	bun.BaseModel `bun:"table:chapters,alias:ch" tstype:"-"`

	ID        int       `bun:",pk,autoincrement" json:"id"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	FileID    int       `bun:",notnull" json:"file_id"`
	ParentID  *int      `json:"parent_id"`
	SortOrder int       `bun:",notnull" json:"sort_order"`
	Title     string    `bun:",notnull" json:"title"`

	// Position data (mutually exclusive based on file type)
	StartPage        *int    `json:"start_page"`         // CBZ/PDF: 0-indexed page number
	StartTimestampMs *int64  `json:"start_timestamp_ms"` // M4B: milliseconds from start
	Href             *string `json:"href"`               // EPUB: content document href; MOBI/AZW3: location, shown only

	// Relations
	File     *File      `bun:"rel:belongs-to,join:file_id=id" json:"-"`
	Parent   *Chapter   `bun:"rel:belongs-to,join:parent_id=id" json:"-"`
	Children []*Chapter `bun:"rel:has-many,join:id=parent_id" json:"children,omitempty"`
}

func ChapterTree added in v0.2.0

func ChapterTree(chapters []*Chapter) []*Chapter

ChapterTree returns the chapters as a tree of top-level chapters. A File loaded by RetrieveFileWithRelations lists every chapter in Chapters, nested ones included, with only copies of their direct children attached, so a list holding a chapter's parent is rebuilt from ParentID. A list of top-level chapters with their children attached is returned as it is.

type ConversionStep added in v0.5.0

type ConversionStep struct {
	From string
	To   string
}

ConversionStep is one Conversion: delivering a File of type From as type To. pkg/filegen holds the code that runs each step, and a test there checks that every step listed here has it.

func ConversionChain added in v0.5.0

func ConversionChain(from, to string) ([]ConversionStep, bool)

ConversionChain returns the steps that deliver a File of type from as type to, and false when no chain of Conversions reaches it.

type DocumentHash added in v0.7.0

type DocumentHash struct {
	bun.BaseModel `bun:"table:koreader_document_hashes,alias:kdh" tstype:"-"`

	Hash              string    `bun:",pk" json:"hash"`
	FileID            int       `bun:",pk" json:"file_id"`
	DeliveredFileType string    `bun:",pk" json:"delivered_file_type"`
	CreatedAt         time.Time `json:"created_at"`

	File *File `bun:"rel:belongs-to,join:file_id=id" json:"-"`
}

DocumentHash maps a KOReader document hash to a File and the File Type it was delivered as: the Original File, or a Generated File the download cache published (ADR 0014). One hash can map to several Files, when identical bytes exist more than once. A row lasts as long as its File, since a copy on a device keeps its hash after the File's bytes or metadata change.

type File

type File struct {
	bun.BaseModel `bun:"table:files,alias:f" tstype:"-"`

	ID                       int               `bun:",pk,nullzero" json:"id"`
	CreatedAt                time.Time         `json:"created_at"`
	UpdatedAt                time.Time         `json:"updated_at"`
	LibraryID                int               `bun:",nullzero" json:"library_id"`
	BookID                   int               `bun:",nullzero" json:"book_id"`
	Book                     *Book             `bun:"rel:belongs-to" json:"book" tstype:"Book"`
	Filepath                 string            `bun:",nullzero" json:"filepath"`
	FileType                 string            `bun:",nullzero" json:"file_type" tstype:"FileType"`
	FileRole                 string            `bun:",nullzero,default:'main'" json:"file_role" tstype:"FileRole"`
	FilesizeBytes            int64             `json:"filesize_bytes"`
	FileModifiedAt           *time.Time        `json:"file_modified_at"`
	ScanError                *string           `json:"scan_error"` // Why the last scan could not parse the file; nil when it parsed fine
	CoverImageFilename       *string           `json:"cover_image_filename"`
	CoverMimeType            *string           `json:"cover_mime_type"`
	CoverSource              *string           `json:"cover_source" tstype:"DataSource"`
	CoverPage                *int              `json:"cover_page"` // 0-indexed page number for CBZ/PDF cover, NULL for EPUB/M4B
	Name                     *string           `json:"name"`
	NameSource               *string           `json:"name_source" tstype:"DataSource"`
	PageCount                *int              `json:"page_count"` // Number of pages for CBZ/PDF files, NULL for EPUB/M4B
	AudiobookDurationSeconds *float64          `json:"audiobook_duration_seconds"`
	AudiobookBitrateBps      *int              `json:"audiobook_bitrate_bps"`
	AudiobookCodec           *string           `json:"audiobook_codec"`
	Narrators                []*Narrator       `bun:"rel:has-many,join:id=file_id" json:"narrators,omitempty" tstype:"Narrator[]"`
	NarratorSource           *string           `json:"narrator_source" tstype:"DataSource"`
	Identifiers              []*FileIdentifier `bun:"rel:has-many,join:id=file_id" json:"identifiers,omitempty" tstype:"FileIdentifier[]"`
	IdentifierSource         *string           `json:"identifier_source" tstype:"DataSource"`
	Chapters                 []*Chapter        `bun:"rel:has-many,join:id=file_id" json:"chapters,omitempty" tstype:"Chapter[]"`
	URL                      *string           `json:"url"`
	URLSource                *string           `json:"url_source" tstype:"DataSource"`
	ReleaseDate              *time.Time        `json:"release_date"`
	ReleaseDateSource        *string           `json:"release_date_source" tstype:"DataSource"`
	PublisherID              *int              `json:"publisher_id"`
	PublisherSource          *string           `json:"publisher_source" tstype:"DataSource"`
	Publisher                *Publisher        `bun:"rel:belongs-to,join:publisher_id=id" json:"publisher,omitempty" tstype:"Publisher"`
	ChapterSource            *string           `json:"chapter_source" tstype:"DataSource"`
	Language                 *string           `json:"language"`
	LanguageSource           *string           `json:"language_source" tstype:"DataSource"`
	Abridged                 *bool             `json:"abridged"`
	AbridgedSource           *string           `json:"abridged_source" tstype:"DataSource"`
	ReviewOverride           *string           `json:"review_override" tstype:"ReviewOverride"`
	ReviewOverriddenAt       *time.Time        `json:"review_overridden_at"`
	Reviewed                 *bool             `json:"reviewed"`
	IsPreferredCover         bool              `bun:",default:false" json:"is_preferred_cover"`
	// DisplayName is the label the UI shows for the file. It is not stored:
	// AfterScanRow fills it for files loaded directly, and the book loaders
	// call ResolveBookFileDisplayNames for Book.Files, which Bun loads as a
	// has-many relation without row hooks.
	DisplayName string `bun:"-" json:"display_name"`
	// ReachableFileTypes are the File Types the file can be downloaded as:
	// its own first, then every one reachable through Conversion. A
	// supplement downloads only as itself. Like DisplayName it is not
	// stored, and is resolved alongside it.
	ReachableFileTypes []string `bun:"-" json:"reachable_file_types" tstype:"DeliveredFileType[]"`
}

func (*File) AfterScanRow added in v0.1.0

func (f *File) AfterScanRow(_ context.Context) error

AfterScanRow is a Bun hook that resolves DisplayName and ReachableFileTypes for files loaded by a direct query (a single file, or a slice of files). It does not run for Book.Files: Bun scans has-many relations without row hooks.

func (*File) CoverExtension

func (f *File) CoverExtension() string

func (*File) HasName added in v0.4.0

func (f *File) HasName() bool

HasName reports whether the file has a name. A whitespace-only name does not count. Check it rather than comparing Name with "".

func (*File) ResolveDisplayName added in v0.1.0

func (f *File) ResolveDisplayName() string

ResolveDisplayName returns the label for the file. A main file shows its name (the edition title), falling back to the filename. A supplement shows its filename unless the user set a name (source manual): the scanner stores the filename stem as the name when it first sees a supplement, and that stored value goes stale when the book is renamed or reorganized. A sidecar source does not count, because book edits write every file's sidecar with its stored name, so a rescan can restore the stale stem as sidecar. As in ResolveTitle, a whitespace-only name does not count as set.

func (*File) ResolveTitle added in v0.4.0

func (f *File) ResolveTitle(bookTitle string) string

ResolveTitle returns the title a reader sees for this file: its name when set, otherwise the book's title, even when the book has only one file. Unlike ResolveDisplayName it never falls back to the filename. A whitespace-only name does not count as set. A nil file gives the book's title.

type FileFingerprint added in v0.0.29

type FileFingerprint struct {
	bun.BaseModel `bun:"table:file_fingerprints,alias:ffp" tstype:"-"`

	ID        int       `bun:",pk,nullzero" json:"id"`
	CreatedAt time.Time `json:"created_at"`
	FileID    int       `bun:",nullzero" json:"file_id"`
	Algorithm string    `bun:",nullzero" json:"algorithm"`
	Value     string    `bun:",nullzero" json:"value"`

	File *File `bun:"rel:belongs-to" json:"-"`
}

FileFingerprint is a content fingerprint for a file. A single file may have multiple fingerprints, one per algorithm (e.g. sha256 for exact matching, phash for cover similarity, simhash for text similarity).

type FileIdentifier

type FileIdentifier struct {
	bun.BaseModel `bun:"table:file_identifiers,alias:fi" tstype:"-"`

	ID        int       `bun:",pk,nullzero" json:"id"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	FileID    int       `bun:",nullzero" json:"file_id"`
	Type      string    `bun:",nullzero" json:"type" tstype:"IdentifierType"`
	Value     string    `bun:",nullzero" json:"value"`
	Source    string    `bun:",nullzero" json:"source" tstype:"DataSource"`
}

type Genre

type Genre struct {
	bun.BaseModel `bun:"table:genres,alias:g" tstype:"-"`

	ID        int           `bun:",pk,nullzero" json:"id"`
	CreatedAt time.Time     `json:"created_at"`
	UpdatedAt time.Time     `json:"updated_at"`
	LibraryID int           `bun:",nullzero" json:"library_id"`
	Name      string        `bun:",nullzero" json:"name"`
	Aliases   []*GenreAlias `bun:"rel:has-many,join:id=genre_id" json:"aliases" tstype:"-"`
	BookCount int           `bun:",scanonly" json:"book_count"`
}

type GenreAlias added in v0.0.43

type GenreAlias struct {
	bun.BaseModel `bun:"table:genre_aliases,alias:ga" tstype:"-"`

	ID        int       `bun:",pk,nullzero" json:"id"`
	CreatedAt time.Time `json:"created_at"`
	GenreID   int       `bun:",nullzero" json:"genre_id"`
	Name      string    `bun:",nullzero" json:"name"`
	LibraryID int       `bun:",nullzero" json:"library_id"`
}

type Job

type Job struct {
	bun.BaseModel `bun:"table:jobs,alias:j" tstype:"-"`

	ID         int         `bun:",pk,nullzero" json:"id"`
	CreatedAt  time.Time   `json:"created_at"`
	UpdatedAt  time.Time   `json:"updated_at"`
	Type       string      `bun:",nullzero" json:"type" tstype:"JobType"`
	Status     string      `bun:",nullzero" json:"status" tstype:"JobStatus"`
	Data       string      `bun:",nullzero" json:"-"`
	DataParsed interface{} `` /* 127-byte string literal not displayed */
	Progress   int         `json:"progress"`
	ProcessID  *string     `json:"process_id,omitempty"`
	LibraryID  *int        `json:"library_id,omitempty"`
	// CreatedByUserID is the user who requested the job through POST /api/jobs.
	// It is nil for jobs enqueued any other way, such as by library saves,
	// review settings, the scheduler, or the worker.
	CreatedByUserID *int `json:"created_by_user_id,omitempty"`
}

func (*Job) UnmarshalData

func (job *Job) UnmarshalData() error

type JobBulkDownloadData added in v0.0.21

type JobBulkDownloadData struct {
	// Input (set on creation)
	FileIDs            []int `json:"file_ids"`
	EstimatedSizeBytes int64 `json:"estimated_size_bytes"`

	// Result (set on completion)
	ZipFilename     string `json:"zip_filename,omitempty"`
	SizeBytes       int64  `json:"size_bytes,omitempty"`
	FileCount       int    `json:"file_count,omitempty"`
	FingerprintHash string `json:"fingerprint_hash,omitempty"`
}

type JobExportData

type JobExportData struct{}

type JobHashGenerationData added in v0.0.29

type JobHashGenerationData struct {
	LibraryID int `json:"library_id"`
}

JobHashGenerationData is the payload for a hash generation job. The job processes all files in the given library that do not yet have a sha256 fingerprint in file_fingerprints.

type JobLog

type JobLog struct {
	bun.BaseModel `bun:"table:job_logs,alias:jl" tstype:"-"`

	ID         int       `bun:",pk,nullzero" json:"id"`
	CreatedAt  time.Time `json:"created_at"`
	JobID      int       `bun:",nullzero" json:"job_id"`
	Level      string    `bun:",nullzero" json:"level" tstype:"LogLevel"`
	Message    string    `bun:",nullzero" json:"message"`
	Plugin     *string   `json:"plugin,omitempty"`
	Data       *string   `json:"data,omitempty"`
	StackTrace *string   `json:"stack_trace,omitempty"`
}

type JobRecomputeReviewData added in v0.0.39

type JobRecomputeReviewData struct {
	// ClearOverrides, when true, sets review_override and review_overridden_at to NULL
	// for every main file before recomputing reviewed.
	ClearOverrides bool `json:"clear_overrides"`
}

type JobScanData

type JobScanData struct{}

type KOReaderPull added in v0.7.0

type KOReaderPull struct {
	bun.BaseModel `bun:"table:koreader_pulls,alias:kp" tstype:"-"`

	DeviceID string    `bun:",pk" json:"device_id"`
	Document string    `bun:",pk" json:"document"`
	FileID   int       `bun:",nullzero" json:"file_id"`
	PulledAt time.Time `bun:",notnull" json:"pulled_at"`
	// Percentage is the position the pull found, which the session starts
	// from, or nil when there was none.
	Percentage *float64   `json:"percentage"`
	PairedAt   *time.Time `json:"paired_at"`
	SessionID  *int       `json:"session_id"`
	// EndedAt and EndPercentage are the session's end as the pushes last set
	// it. PreviousEndedAt and PreviousEndPercentage are its end before the
	// push at ExtendedAt moved it, which a replay puts back.
	EndedAt               *time.Time `json:"ended_at"`
	EndPercentage         *float64   `json:"end_percentage"`
	ExtendedAt            *time.Time `json:"extended_at"`
	PreviousEndedAt       *time.Time `json:"previous_ended_at"`
	PreviousEndPercentage *float64   `json:"previous_end_percentage"`
}

KOReaderPull is a KOReader Device's latest pull of one document. KOReader pulls when it opens or wakes with a book, and pushes when it closes or sleeps, and as you read, so the pushes of the same document that follow make an estimated Reading Session from the pull. PairedAt is when the first did, and SessionID the session they make.

type Library

type Library struct {
	bun.BaseModel `bun:"table:libraries,alias:l" tstype:"-"`

	ID                    int       `bun:",pk,nullzero" json:"id"`
	CreatedAt             time.Time `json:"created_at"`
	UpdatedAt             time.Time `json:"updated_at"`
	Name                  string    `bun:",nullzero" json:"name"`
	OrganizeFileStructure bool      `json:"organize_file_structure"`
	CoverAspectRatio      string    `bun:",nullzero" json:"cover_aspect_ratio" tstype:"CoverAspectRatio"`
	// TagNamespaces are the tag name prefixes the library groups tags by, in
	// the order the book page lists them. A tag named "Circle: Foo" or
	// "Circle:Foo" is the tag "Foo" in the "Circle" namespace; the matching
	// rule is in pkg/tags/namespaces.go.
	TagNamespaces StringList     `json:"tag_namespaces"`
	LibraryPaths  []*LibraryPath `bun:"rel:has-many" json:"library_paths,omitempty" tstype:"LibraryPath[]"`
}

type LibraryPath

type LibraryPath struct {
	bun.BaseModel `bun:"table:library_paths,alias:lp" tstype:"-"`

	ID        int       `bun:",pk,nullzero" json:"id"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	LibraryID int       `bun:",nullzero" json:"library_id"`
	Filepath  string    `bun:",nullzero" json:"filepath"`
}

type LibraryPluginCustomization

type LibraryPluginCustomization struct {
	bun.BaseModel `bun:"table:library_plugin_customizations,alias:lpc" tstype:"-"`

	LibraryID int    `bun:",pk" json:"library_id"`
	HookType  string `bun:",pk" json:"hook_type" tstype:"PluginHookType"`
}

type LibraryPluginFieldSetting added in v0.0.10

type LibraryPluginFieldSetting struct {
	bun.BaseModel `bun:"table:library_plugin_field_settings,alias:lpfs" tstype:"-"`

	LibraryID int    `bun:",pk" json:"library_id"`
	Scope     string `bun:",pk" json:"scope"`
	PluginID  string `bun:",pk" json:"plugin_id"`
	Field     string `bun:",pk" json:"field"`
	Enabled   bool   `bun:",notnull" json:"enabled"`
}

LibraryPluginFieldSetting stores per-library field overrides. Only rows with explicit overrides are stored; absence means use global default.

type LibraryPluginHookConfig added in v0.0.26

type LibraryPluginHookConfig struct {
	bun.BaseModel `bun:"table:library_plugin_hook_configs,alias:lphc" tstype:"-"`

	LibraryID int    `bun:",pk" json:"library_id"`
	HookType  string `bun:",pk" json:"hook_type" tstype:"PluginHookType"`
	Scope     string `bun:",pk" json:"scope"`
	PluginID  string `bun:",pk" json:"plugin_id"`
	Position  int    `bun:",notnull" json:"position"`
	Mode      string `bun:",notnull,default:'enabled'" json:"mode" tstype:"PluginMode"`
}

type List

type List struct {
	bun.BaseModel `bun:"table:lists,alias:l" tstype:"-"`

	ID          int       `bun:",pk,nullzero" json:"id"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
	UserID      int       `bun:",nullzero" json:"user_id"`
	User        *UserRef  `bun:"rel:belongs-to,join:user_id=id" json:"user,omitempty" tstype:"UserRef"`
	Name        string    `bun:",nullzero" json:"name"`
	Description *string   `json:"description"`
	IsOrdered   bool      `json:"is_ordered"`
	DefaultSort string    `bun:",nullzero" json:"default_sort" tstype:"ListSort"`

	// Relations
	ListBooks  []*ListBook  `bun:"rel:has-many,join:id=list_id" json:"list_books,omitempty" tstype:"ListBook[]"`
	ListShares []*ListShare `bun:"rel:has-many,join:id=list_id" json:"list_shares,omitempty" tstype:"ListShare[]"`
}

type ListBook

type ListBook struct {
	bun.BaseModel `bun:"table:list_books,alias:lb" tstype:"-"`

	ID            int       `bun:",pk,nullzero" json:"id"`
	ListID        int       `bun:",nullzero" json:"list_id"`
	List          *List     `bun:"rel:belongs-to,join:list_id=id" json:"list,omitempty" tstype:"List"`
	BookID        int       `bun:",nullzero" json:"book_id"`
	Book          *Book     `bun:"rel:belongs-to,join:book_id=id" json:"book,omitempty" tstype:"Book"`
	AddedAt       time.Time `json:"added_at"`
	AddedByUserID *int      `json:"added_by_user_id"`
	AddedByUser   *UserRef  `bun:"rel:belongs-to,join:added_by_user_id=id" json:"added_by_user,omitempty" tstype:"UserRef"`
	SortOrder     *int      `json:"sort_order"`
}

type ListShare

type ListShare struct {
	bun.BaseModel `bun:"table:list_shares,alias:ls" tstype:"-"`

	ID             int       `bun:",pk,nullzero" json:"id"`
	ListID         int       `bun:",nullzero" json:"list_id"`
	List           *List     `bun:"rel:belongs-to,join:list_id=id" json:"list,omitempty" tstype:"List"`
	UserID         int       `bun:",nullzero" json:"user_id"`
	User           *UserRef  `bun:"rel:belongs-to,join:user_id=id" json:"user,omitempty" tstype:"UserRef"`
	Permission     string    `bun:",nullzero" json:"permission" tstype:"ListPermission"`
	CreatedAt      time.Time `json:"created_at"`
	SharedByUserID *int      `json:"shared_by_user_id"`
	SharedByUser   *UserRef  `bun:"rel:belongs-to,join:shared_by_user_id=id" json:"shared_by_user,omitempty" tstype:"UserRef"`
}

type Narrator

type Narrator struct {
	bun.BaseModel `bun:"table:narrators,alias:n" tstype:"-"`

	ID        int     `bun:",pk,nullzero" json:"id"`
	FileID    int     `bun:",nullzero" json:"file_id"`
	PersonID  int     `bun:",nullzero" json:"person_id"`
	Person    *Person `bun:"rel:belongs-to,join:person_id=id" json:"person,omitempty" tstype:"Person"`
	SortOrder int     `bun:",nullzero" json:"sort_order"`
}

type Permission

type Permission struct {
	bun.BaseModel `bun:"table:permissions,alias:p" tstype:"-"`

	ID        int    `bun:",pk,nullzero" json:"id"`
	RoleID    int    `json:"role_id"`
	Resource  string `json:"resource"`
	Operation string `json:"operation"`
}

type Person

type Person struct {
	bun.BaseModel `bun:"table:persons,alias:p" tstype:"-"`

	ID             int            `bun:",pk,nullzero" json:"id"`
	CreatedAt      time.Time      `json:"created_at"`
	UpdatedAt      time.Time      `json:"updated_at"`
	LibraryID      int            `bun:",nullzero" json:"library_id"`
	Name           string         `bun:",nullzero" json:"name"`
	SortName       string         `bun:",notnull" json:"sort_name"`
	SortNameSource string         `bun:",notnull" json:"sort_name_source" tstype:"DataSource"`
	Aliases        []*PersonAlias `bun:"rel:has-many,join:id=person_id" json:"aliases" tstype:"-"`
}

type PersonAlias added in v0.0.43

type PersonAlias struct {
	bun.BaseModel `bun:"table:person_aliases,alias:pa" tstype:"-"`

	ID        int       `bun:",pk,nullzero" json:"id"`
	CreatedAt time.Time `json:"created_at"`
	PersonID  int       `bun:",nullzero" json:"person_id"`
	Name      string    `bun:",nullzero" json:"name"`
	LibraryID int       `bun:",nullzero" json:"library_id"`
}

type Plugin

type Plugin struct {
	bun.BaseModel `bun:"table:plugins,alias:p" tstype:"-"`

	Scope                  string       `bun:",pk" json:"scope"`
	ID                     string       `bun:",pk" json:"id"`
	Name                   string       `bun:",notnull" json:"name"`
	Version                string       `bun:",notnull" json:"version"`
	Description            *string      `json:"description"`
	Homepage               *string      `json:"homepage"`
	Status                 PluginStatus `bun:",notnull,default:0" json:"status"`
	AutoUpdate             bool         `bun:",notnull,default:true" json:"auto_update"`
	RepositoryScope        *string      `json:"repository_scope"`
	RepositoryURL          *string      `json:"repository_url"`
	InstalledAt            time.Time    `bun:",notnull" json:"installed_at"`
	UpdatedAt              *time.Time   `json:"updated_at"`
	LoadError              *string      `json:"load_error"`
	UpdateAvailableVersion *string      `json:"update_available_version"`
	ConfidenceThreshold    *float64     `json:"confidence_threshold"`
}

type PluginConfig

type PluginConfig struct {
	bun.BaseModel `bun:"table:plugin_configs,alias:pc" tstype:"-"`

	Scope    string  `bun:",pk" json:"scope"`
	PluginID string  `bun:",pk" json:"plugin_id"`
	Key      string  `bun:",pk" json:"key"`
	Value    *string `json:"value"`
}

type PluginFieldSetting added in v0.0.10

type PluginFieldSetting struct {
	bun.BaseModel `bun:"table:plugin_field_settings,alias:pfs" tstype:"-"`

	Scope    string `bun:",pk" json:"scope"`
	PluginID string `bun:",pk" json:"plugin_id"`
	Field    string `bun:",pk" json:"field"`
	Enabled  bool   `bun:",notnull" json:"enabled"`
}

PluginFieldSetting stores global field enable/disable settings for a plugin. Absence of a row means the field is enabled (default).

type PluginHookConfig added in v0.0.26

type PluginHookConfig struct {
	bun.BaseModel `bun:"table:plugin_hook_configs,alias:phc" tstype:"-"`

	HookType string `bun:",pk" json:"hook_type" tstype:"PluginHookType"`
	Scope    string `bun:",pk" json:"scope"`
	PluginID string `bun:",pk" json:"plugin_id"`
	Position int    `bun:",notnull" json:"position"`
	Mode     string `bun:",notnull,default:'enabled'" json:"mode" tstype:"PluginMode"`
}

type PluginIdentifierType

type PluginIdentifierType struct {
	bun.BaseModel `bun:"table:plugin_identifier_types,alias:pit" tstype:"-"`

	ID          string  `bun:",pk" json:"id"`
	Scope       string  `bun:",pk" json:"scope"`
	PluginID    string  `bun:",pk" json:"plugin_id"`
	Name        string  `bun:",notnull" json:"name"`
	URLTemplate *string `json:"url_template"`
	Pattern     *string `json:"pattern"`
}

type PluginRepository

type PluginRepository struct {
	bun.BaseModel `bun:"table:plugin_repositories,alias:pr" tstype:"-"`

	URL           string     `bun:",pk" json:"url"`
	Scope         string     `bun:",notnull,unique" json:"scope"`
	Name          *string    `json:"name"`
	IsOfficial    bool       `bun:",notnull" json:"is_official"`
	Enabled       bool       `bun:",notnull" json:"enabled"`
	LastFetchedAt *time.Time `json:"last_fetched_at"`
	FetchError    *string    `json:"fetch_error"`
}

type PluginStatus added in v0.0.18

type PluginStatus int

PluginStatus represents the lifecycle state of a plugin.

const (
	// PluginStatusActive means the plugin is running normally.
	PluginStatusActive PluginStatus = 0
	// PluginStatusDisabled means the user has disabled the plugin.
	PluginStatusDisabled PluginStatus = -1
	// PluginStatusMalfunctioned means the plugin failed to load (LoadError has details).
	PluginStatusMalfunctioned PluginStatus = -2
	// PluginStatusNotSupported means the plugin's minShishoVersion is incompatible.
	PluginStatusNotSupported PluginStatus = -3
)

type Publisher

type Publisher struct {
	bun.BaseModel `bun:"table:publishers,alias:pub" tstype:"-"`

	ID        int        `bun:",pk,nullzero" json:"id"`
	CreatedAt time.Time  `json:"created_at"`
	UpdatedAt time.Time  `json:"updated_at"`
	LibraryID int        `bun:",nullzero" json:"library_id"`
	Name      string     `bun:",nullzero" json:"name"`
	ParentID  *int       `json:"parent_id"`
	Parent    *Publisher `bun:"rel:belongs-to,join:parent_id=id" json:"parent,omitempty" tstype:"Publisher"`
	// Children and Aliases are reshaped by the publishers API responses
	// (Children flattens to ChildResponse, Aliases to []string), so they are
	// excluded from TS generation with tstype:"-". The json tags stay so the Go
	// wire format is unchanged. See pkg/publishers/types.go and ADR 0004.
	Children  []*Publisher      `bun:"rel:has-many,join:id=parent_id" json:"children,omitempty" tstype:"-"`
	Aliases   []*PublisherAlias `bun:"rel:has-many,join:id=publisher_id" json:"aliases" tstype:"-"`
	FileCount int               `bun:",scanonly" json:"file_count"`
}

type PublisherAlias added in v0.0.43

type PublisherAlias struct {
	bun.BaseModel `bun:"table:publisher_aliases,alias:puba" tstype:"-"`

	ID          int       `bun:",pk,nullzero" json:"id"`
	CreatedAt   time.Time `json:"created_at"`
	PublisherID int       `bun:",nullzero" json:"publisher_id"`
	Name        string    `bun:",nullzero" json:"name"`
	LibraryID   int       `bun:",nullzero" json:"library_id"`
}

type ReadThrough added in v0.6.0

type ReadThrough struct {
	bun.BaseModel `bun:"table:read_throughs,alias:rt" tstype:"-"`

	ID        int       `bun:",pk,nullzero" json:"id"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	UserID    int       `bun:",nullzero" json:"-"`
	BookID    int       `bun:",nullzero" json:"book_id"`
	BeginDay  *string   `json:"begin_day"`
	EndDay    *string   `json:"end_day"`
	Outcome   string    `bun:",nullzero" json:"outcome" tstype:"ReadThroughOutcome"`
	Paused    bool      `bun:",notnull" json:"paused"`
	// LeftToReadPosition is the To Read position the Book left when this
	// Read-through started, which deleting it while underway restores.
	LeftToReadPosition *int `json:"-"`
}

ReadThrough is one pass a user makes through a Book. See "Read-through" in CONTEXT.md. BeginDay and EndDay are calendar days (YYYY-MM-DD), each optional. Paused means something only while the outcome is underway.

type ReadingLocator added in v0.7.0

type ReadingLocator struct {
	CFI      *string  `bun:"cfi" json:"cfi"`
	XPointer *string  `bun:"xpointer" json:"xpointer"`
	Page     *int     `json:"page"`
	Seconds  *float64 `json:"seconds"`
	KoboSpan *string  `json:"kobo_span"`
}

ReadingLocator is where in a File a reader is, in exactly one form: an EPUB CFI from the web ebook reader, a KOReader XPointer, a 0-based page for comics and PDFs, seconds into an audiobook, or a Kobo span, stored as the chapter's path in the KePub and the span's id ("OEBPS/ch1.xhtml#kobo.4.1", reflowable.KoboSpan). Each source writes the form it reads.

func (ReadingLocator) Equal added in v0.7.0

func (l ReadingLocator) Equal(o ReadingLocator) bool

Equal reports whether l and o are the same position in the same form.

func (ReadingLocator) IsZero added in v0.7.0

func (l ReadingLocator) IsZero() bool

IsZero reports whether l holds no position at all.

type ReadingPosition added in v0.7.0

type ReadingPosition struct {
	bun.BaseModel `bun:"table:reading_positions,alias:rp" tstype:"-"`

	UserID         int       `bun:",pk" json:"-"`
	FileID         int       `bun:",pk" json:"file_id"`
	CreatedAt      time.Time `json:"created_at"`
	UpdatedAt      time.Time `json:"updated_at"`
	ReadingLocator `tstype:",extends"`
	// LocatorFileType is the File Type of the copy the locator points into,
	// when it isn't the File's own, such as the KePub a KOReader holds of an
	// EPUB: span markup gives a KePub's XPointers different paths.
	LocatorFileType *string `json:"-"`
	Percentage      float64 `bun:",notnull" json:"percentage"`
	// ChapterPercentage is how far through its chapter the position is, from
	// 0 to 1, when the source said: a Kobo does, which places its span when
	// the span itself can't be found.
	ChapterPercentage *float64 `json:"-"`
	Source            string   `bun:",nullzero" json:"source" tstype:"ReadingSource"`
	// SourceDeviceID is the Device that reported the position, when a Device
	// did and still exists.
	SourceDeviceID *string `json:"-"`
	// ReportedDevice and ReportedDeviceID are how the source named itself,
	// when it did. KOReader gets them back with its own position, and
	// ignores a position carrying them.
	ReportedDevice   *string   `json:"-"`
	ReportedDeviceID *string   `json:"-"`
	OccurredAt       time.Time `json:"occurred_at"`
}

ReadingPosition is one user's place in one File: the latest update by the instant it happened (OccurredAt), whichever source sent it. Percentage is how far through the File that is, from 0 to 1. See "Reading Position" in CONTEXT.md.

type ReadingPositionUpdate added in v0.7.0

type ReadingPositionUpdate struct {
	bun.BaseModel `bun:"table:reading_position_updates,alias:rpu" tstype:"-"`

	ID             int       `bun:",pk,nullzero" json:"id"`
	CreatedAt      time.Time `json:"created_at"`
	UserID         int       `bun:",nullzero" json:"-"`
	FileID         *int      `json:"file_id"`
	ReadThroughID  *int      `json:"read_through_id"`
	ReadingLocator `tstype:",extends"`
	Percentage     float64   `bun:",notnull" json:"percentage"`
	Source         string    `bun:",nullzero" json:"source" tstype:"ReadingSource"`
	SourceDeviceID *string   `json:"-"`
	OccurredAt     time.Time `json:"occurred_at"`
	AtEnd          bool      `bun:",notnull" json:"at_end"`
	AutoFinished   bool      `bun:",notnull" json:"auto_finished"`
	// Opened marks a place a link opened, which isn't reading.
	Opened bool `bun:",notnull" json:"opened"`
}

ReadingPositionUpdate is one accepted update in the append-only log. It belongs to the Read-through underway when it arrived, or the one it finished (AutoFinished), and outlives its File with FileID cleared.

type ReadingSession added in v0.7.0

type ReadingSession struct {
	bun.BaseModel `bun:"table:reading_sessions,alias:rs" tstype:"-"`

	ID              int       `bun:",pk,nullzero" json:"id"`
	CreatedAt       time.Time `json:"created_at"`
	UserID          int       `bun:",nullzero" json:"-"`
	FileID          *int      `json:"file_id"`
	ReadThroughID   *int      `json:"read_through_id"`
	Source          string    `bun:",nullzero" json:"source" tstype:"ReadingSource"`
	StartedAt       time.Time `json:"started_at"`
	EndedAt         time.Time `json:"ended_at"`
	StartPercentage float64   `bun:",notnull" json:"start_percentage"`
	EndPercentage   float64   `bun:",notnull" json:"end_percentage"`
	Measured        bool      `bun:",notnull" json:"measured"`
}

ReadingSession is one stretch of reading or listening to a File. See "Reading Session" in CONTEXT.md. Measured is false for a session a source estimated rather than timed.

type Role

type Role struct {
	bun.BaseModel `bun:"table:roles,alias:r" tstype:"-"`

	ID          int           `bun:",pk,nullzero" json:"id"`
	CreatedAt   time.Time     `json:"created_at"`
	UpdatedAt   time.Time     `json:"updated_at"`
	Name        string        `bun:",nullzero" json:"name"`
	IsSystem    bool          `json:"is_system"`
	Permissions []*Permission `bun:"rel:has-many,join:id=role_id" json:"permissions,omitempty" tstype:"Permission[]"`
}

func (*Role) HasPermission

func (r *Role) HasPermission(resource, operation string) bool

HasPermission checks if the role has a specific permission.

type Series

type Series struct {
	bun.BaseModel `bun:"table:series,alias:s" tstype:"-"`

	ID                 int            `bun:",pk,nullzero" json:"id"`
	CreatedAt          time.Time      `json:"created_at"`
	UpdatedAt          time.Time      `json:"updated_at"`
	LibraryID          int            `bun:",nullzero" json:"library_id"`
	Library            *Library       `bun:"rel:belongs-to" json:"library,omitempty" tstype:"Library"`
	Name               string         `bun:",nullzero" json:"name"`
	NameSource         string         `bun:",nullzero" json:"name_source" tstype:"DataSource"`
	SortName           string         `bun:",notnull" json:"sort_name"`
	SortNameSource     string         `bun:",notnull" json:"sort_name_source" tstype:"DataSource"`
	Description        *string        `json:"description,omitempty"`
	CoverImageFilename *string        `json:"cover_image_filename,omitempty"`
	Aliases            []*SeriesAlias `bun:"rel:has-many,join:id=series_id" json:"aliases" tstype:"-"`
	BookSeries         []*BookSeries  `bun:"rel:has-many" json:"book_series,omitempty" tstype:"BookSeries[]"`
	BookCount          int            `bun:",scanonly" json:"book_count"`
	CoverCacheKey      string         `bun:"-" json:"cover_cache_key"`
}

type SeriesAlias added in v0.0.43

type SeriesAlias struct {
	bun.BaseModel `bun:"table:series_aliases,alias:sa" tstype:"-"`

	ID        int       `bun:",pk,nullzero" json:"id"`
	CreatedAt time.Time `json:"created_at"`
	SeriesID  int       `bun:",nullzero" json:"series_id"`
	Name      string    `bun:",nullzero" json:"name"`
	LibraryID int       `bun:",nullzero" json:"library_id"`
}
type ShareLink struct {
	bun.BaseModel `bun:"table:share_links,alias:sl" tstype:"-"`

	ID              int        `bun:",pk,nullzero" json:"id"`
	CreatedAt       time.Time  `json:"created_at"`
	UpdatedAt       time.Time  `json:"updated_at"`
	Token           string     `bun:",nullzero" json:"token"`
	BookID          int        `bun:",nullzero" json:"book_id"`
	CreatedByUserID int        `bun:",nullzero" json:"created_by_user_id"`
	CreatedByUser   *User      `bun:"rel:belongs-to,join:created_by_user_id=id" json:"-"`
	Label           *string    `json:"label"`
	ExpiresAt       *time.Time `json:"expires_at"`
	RevokedAt       *time.Time `json:"revoked_at"`
	OpenCount       int        `json:"open_count"`
	DownloadCount   int        `json:"download_count"`
	LastAccessedAt  *time.Time `json:"last_accessed_at"`
}

ShareLink is an anonymous, optionally expiring link that grants access to one Book. See "Share Link" in CONTEXT.md.

func (*ShareLink) PausedReason added in v0.1.0

func (l *ShareLink) PausedReason(libraryID int) string

PausedReason reports why the link's creator stops it from resolving for a book in libraryID, or "" when the creator can still share it. It needs CreatedByUser loaded with its LibraryAccess; a missing creator counts as deactivated so a caller that forgot to load it fails closed.

func (*ShareLink) State added in v0.1.0

func (l *ShareLink) State(now time.Time) string

State derives the link's state at now: revoked if it was revoked, otherwise expired once expires_at is at or before now, otherwise active.

type StringList added in v0.5.0

type StringList []string

StringList is a list of strings stored in one TEXT column as a JSON array. A nil list is stored as an empty one and always scans back non-nil, so it serializes to [] rather than null.

func (*StringList) Scan added in v0.5.0

func (l *StringList) Scan(src any) error

func (StringList) Value added in v0.5.0

func (l StringList) Value() (driver.Value, error)

type Tag

type Tag struct {
	bun.BaseModel `bun:"table:tags,alias:t" tstype:"-"`

	ID        int         `bun:",pk,nullzero" json:"id"`
	CreatedAt time.Time   `json:"created_at"`
	UpdatedAt time.Time   `json:"updated_at"`
	LibraryID int         `bun:",nullzero" json:"library_id"`
	Name      string      `bun:",nullzero" json:"name"`
	Aliases   []*TagAlias `bun:"rel:has-many,join:id=tag_id" json:"aliases" tstype:"-"`
	BookCount int         `bun:",scanonly" json:"book_count"`
}

type TagAlias added in v0.0.43

type TagAlias struct {
	bun.BaseModel `bun:"table:tag_aliases,alias:ta" tstype:"-"`

	ID        int       `bun:",pk,nullzero" json:"id"`
	CreatedAt time.Time `json:"created_at"`
	TagID     int       `bun:",nullzero" json:"tag_id"`
	Name      string    `bun:",nullzero" json:"name"`
	LibraryID int       `bun:",nullzero" json:"library_id"`
}

type User

type User struct {
	bun.BaseModel `bun:"table:users,alias:u" tstype:"-"`

	ID                 int       `bun:",pk,nullzero" json:"id"`
	CreatedAt          time.Time `json:"created_at"`
	UpdatedAt          time.Time `json:"updated_at"`
	Username           string    `bun:",nullzero" json:"username"`
	Email              *string   `json:"email,omitempty"`
	PasswordHash       string    `json:"-"` // Never expose password hash
	RoleID             int       `json:"role_id"`
	IsActive           bool      `json:"is_active"`
	MustChangePassword bool      `json:"must_change_password"`

	// Relations
	Role          *Role                `bun:"rel:belongs-to,join:role_id=id" json:"role,omitempty" tstype:"Role"`
	LibraryAccess []*UserLibraryAccess `bun:"rel:has-many,join:id=user_id" json:"library_access,omitempty" tstype:"UserLibraryAccess[]"`
}

func (*User) GetAccessibleLibraryIDs

func (u *User) GetAccessibleLibraryIDs() []int

GetAccessibleLibraryIDs returns the list of library IDs the user can access. Returns nil if user has access to all libraries.

func (*User) HasAllLibraryAccess

func (u *User) HasAllLibraryAccess() bool

HasAllLibraryAccess checks if the user has access to all libraries.

func (*User) HasLibraryAccess

func (u *User) HasLibraryAccess(libraryID int) bool

HasLibraryAccess checks if the user can access a specific library. Returns true if user has access to all libraries (null library_id entry) or has explicit access to the specified library.

func (*User) HasPermission

func (u *User) HasPermission(resource, operation string) bool

HasPermission checks if the user has a specific permission.

type UserBook added in v0.6.0

type UserBook struct {
	bun.BaseModel `bun:"table:user_books,alias:ub" tstype:"-"`

	UserID         int       `bun:",pk" json:"-"`
	BookID         int       `bun:",pk" json:"book_id"`
	CreatedAt      time.Time `json:"created_at"`
	UpdatedAt      time.Time `json:"updated_at"`
	Rating         *int      `json:"rating_units"`
	ToReadPosition *int      `json:"to_read_position"`
	PageFlow       *string   `json:"page_flow" tstype:"PageFlow | null"`
}

UserBook is one user's record for one Book of what doesn't belong to a Read-through: the Rating, in quarter stars from 1 to 20, the position on To Read, and the Page flow the user chose for reading it. Nil means unrated, not on To Read, and no flow chosen.

type UserLibraryAccess

type UserLibraryAccess struct {
	bun.BaseModel `bun:"table:user_library_access,alias:ula" tstype:"-"`

	ID        int  `bun:",pk,nullzero" json:"id"`
	UserID    int  `json:"user_id"`
	LibraryID *int `json:"library_id"` // null means access to all libraries
}

type UserLibrarySettings added in v0.0.32

type UserLibrarySettings struct {
	bun.BaseModel `bun:"table:user_library_settings,alias:uls" tstype:"-"`

	ID        int       `bun:",pk,autoincrement"`
	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
	UserID    int       `bun:",notnull"`
	LibraryID int       `bun:",notnull"`
	SortSpec  *string   `bun:",nullzero"`
}

UserLibrarySettings is intentionally not exposed over JSON or generated as a TS type (`tstype:"-"`). Handlers transform it into LibrarySettingsResponse (in pkg/settings) so the wire shape can evolve independently of the storage row, and tygo skips it. The struct therefore carries no `json:"..."` tags.

type UserRef added in v0.1.0

type UserRef struct {
	bun.BaseModel `bun:"table:users,alias:u" tstype:"-"`

	ID       int    `bun:",pk,nullzero" json:"id"`
	Username string `json:"username"`
}

UserRef is a read-only view of a user carrying only the id and username. Payloads that any signed-in user can read, such as a list's owner, its shares, and who added each book, embed it instead of User so they never carry an email address, role, or account state.

type UserSettings

type UserSettings struct {
	bun.BaseModel `bun:"table:user_settings,alias:us" tstype:"-"`

	ID                 int       `bun:",pk,autoincrement" json:"id"`
	CreatedAt          time.Time `bun:",nullzero,notnull,default:current_timestamp" json:"created_at"`
	UpdatedAt          time.Time `bun:",nullzero,notnull,default:current_timestamp" json:"updated_at"`
	UserID             int       `bun:",notnull,unique" json:"user_id"`
	ViewerPreloadCount int       `bun:",notnull,default:3" json:"viewer_preload_count"`
	ViewerFitMode      string    `bun:",notnull,default:'fit-height'" json:"viewer_fit_mode" tstype:"FitMode"`
	ReflowableFontSize int       `bun:"viewer_reflowable_font_size,notnull,default:100" json:"viewer_reflowable_font_size"`
	ReflowableTheme    string    `bun:"viewer_reflowable_theme,notnull,default:'light'" json:"viewer_reflowable_theme" tstype:"ReflowableTheme"`
	ReflowableFlow     string    `bun:"viewer_reflowable_flow,notnull,default:'paginated'" json:"viewer_reflowable_flow" tstype:"ReflowableFlow"`
	GallerySize        string    `bun:",notnull,default:'m'" json:"gallery_size" tstype:"GallerySize"`
	ViewerHideChrome   bool      `bun:",notnull,default:false" json:"viewer_hide_chrome"`
	PlaybackSpeed      float64   `bun:"viewer_playback_speed,notnull,default:1.0" json:"viewer_playback_speed" tstype:"PlaybackSpeed"`
	// DefaultLibraryID is the library `/` opens. Nil opens the first library
	// the user can access.
	DefaultLibraryID *int `json:"default_library_id" tstype:"number | null"`
	// TimeZone is the IANA zone that dates the user's automatic Read-through
	// days. Nil until the browser fills it in; days are then dated in UTC.
	TimeZone *string `json:"time_zone" tstype:"string | null"`
}

func DefaultUserSettings

func DefaultUserSettings() *UserSettings

DefaultUserSettings returns a UserSettings with default values.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL