package article import "math" const ( StatusOnline = "online" StatusDraft = "draft" ) type SortField string type SortDirection string const ( SortByTitle SortField = "title" SortByStatus SortField = "status" SortByCreatedAt SortField = "created_at" SortByUpdatedAt SortField = "updated_at" SortByID SortField = "id" SortAsc SortDirection = "asc" SortDesc SortDirection = "desc" ) type Sort struct { Field SortField Direction SortDirection } func DefaultSort() Sort { return Sort{Field: SortByUpdatedAt, Direction: SortDesc} } type Filters struct { IsOnline *bool SiteID *uint32 VisibleTo *uint32 Page int Size int Q string Status string SEOStatus string Lang string Langs *[]string AuthorID *uint32 Sort Sort Unpaged bool } func (f Filters) Window() (int, int) { size := max(f.Size, 0) skipped := max(f.Page, 1) - 1 if size > 0 && skipped > math.MaxInt/size { return math.MaxInt, size } return skipped * size, size } type Nullable[T any] struct { Set bool Value *T } type Update struct { Title *string IfRevision *uint32 Body *string SiteID *uint32 AuthorIDs *[]uint32 Slug *string Excerpt *string SEOTitle *string SEODescription *string CoverImageID Nullable[uint32] CoverImageAlt *string SocialTitle *string SocialDescription *string SocialImageID Nullable[uint32] CanonicalURL *string Noindex *bool SEODismissed *[]string } type ImageReferences struct { Cover []uint32 Social []uint32 } func (r ImageReferences) Empty() bool { return len(r.Cover) == 0 && len(r.Social) == 0 } type Counts struct { All int `json:"all"` Online int `json:"online"` Draft int `json:"draft"` Suggestions int `json:"suggestions"` } type PageResult struct { Items []Article Total int } type Repository interface { FindByFilters(filters Filters) ([]Article, error) FindPage(filters Filters) (PageResult, error) CountByVisibility(onlineOnly bool) (Counts, error) CountVisibleTo(userID uint32) (Counts, error) CountVersions(filters Filters) (Counts, error) CountSuggestions(filters Filters) (int, error) Get(id uint32) (Article, error) GetByName(name string) (Article, error) GetByLangSlug(siteID uint32, lang string, slug string) (Article, error) GetAll() ([]Article, error) ListForAccount(creatorID uint32, authorID *uint32) ([]Article, error) Create(post Article) (Article, error) UpdateBody(id uint32, body string) (Article, error) Update(id uint32, update Update) (Article, error) UpdateIsOnline(id uint32, isOnline bool) (Article, error) Delete(id uint32) error FindByBodyContaining(filename string) ([]Article, error) FindByImage(imageID uint32) ([]Article, error) SlugTaken(siteID uint32, lang string, slug string, exceptID uint32) (bool, error) ClearImage(imageID uint32) (ImageReferences, error) RestoreImage(references ImageReferences, imageID uint32) error RepointImage(fromImageIDs []uint32, toImageID uint32) ([]uint32, error) AssignUnassignedLanguage(siteID uint32, lang string) (int, error) ListByStory(storyID uint32) ([]Article, error) MoveToSite(ids []uint32, siteID uint32) error DeleteStory(storyID uint32) error } type StoryReader interface { FindStoryPage(filters StoryFilters) (StoryPage, error) CountStories(filters StoryFilters) (StoryCounts, error) CountSuggestedStories(readable StoryFilters) (int, error) ListByStories(storyIDs []uint32, readable StoryFilters) ([]Article, error) ListVersions(ids []uint32) ([]Article, error) FindTitleMatches(q string, readable StoryFilters) ([]uint32, error) } type SEOIndex interface { StaleSEO(afterID uint32, limit int) ([]Article, error) StoreSEO(records []SEORecord) error MarkSiteSEOStale(siteID uint32) error } type ListingRepository interface { Repository StoryReader SEOIndex }