content

package
v1.1.17 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2026 License: MPL-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package content stores the CMS's pages and their editable content (blocks) in Postgres. Pages carry per-locale metadata; blocks carry the text and HTML that fill the editable regions declared in the host application's templates.

Index

Constants

View Source
const (
	ModeProduction  = "production"
	ModeDevelopment = "development"
)

The two site modes. A site under construction sits in development, where the CMS asks search engines to keep it out of their indexes; the switch to production is what makes it findable.

The empty value reads as production, so a site that predates this setting — or one whose settings were never saved — keeps behaving exactly as it did. New installs are seeded into development instead, which is the safe end to start from.

View Source
const (
	EditorThemeDark  = "dark"
	EditorThemeLight = "light"
)

The in-place editor's two colour schemes. The empty value reads as dark, so a site that predates this setting keeps the chrome it has always had.

View Source
const SiteSlug = "__site"

SiteSlug is the reserved slug of the site page — the system row that owns shared-region content ({{cmsShared "footer"}}). It holds an underscore, so ValidSlug rejects it and no editor-created page can ever take it.

Variables

View Source
var (
	// ErrNotFound is returned when no page matches the query.
	ErrNotFound = errors.New("content: page not found")
	// ErrDuplicateSlug is returned by Insert/Update when the slug is taken.
	ErrDuplicateSlug = errors.New("content: slug already in use")
)

Functions

func NormalizeSlug

func NormalizeSlug(s string) string

NormalizeSlug lowercases s and trims whitespace and surrounding slashes, so "/About-Us/" becomes "about-us".

func Slugify

func Slugify(title string) string

Slugify derives a URL slug from a human title: lowercased, diacritics stripped, everything else hyphenated — "Café & Bar!" becomes "cafe-bar". Returns "" when nothing usable remains.

func ValidEditorTheme added in v1.1.16

func ValidEditorTheme(t string) bool

ValidEditorTheme reports whether t is a theme that may be stored: one of the two named schemes, or "" for the dark default.

func ValidFeed

func ValidFeed(s string) bool

ValidFeed reports whether s names a known feed.

func ValidMode added in v0.9.2

func ValidMode(m string) bool

ValidMode reports whether m is a mode that may be stored: one of the two named modes, or "" for the production default.

func ValidSlug

func ValidSlug(s string) bool

ValidSlug reports whether s is empty (the homepage) or made of slash-separated segments of lowercase letters, digits, and hyphens.

func ValidVisibility

func ValidVisibility(s string) bool

ValidVisibility reports whether s names a known visibility.

Types

type Block

type Block struct {
	ID         int64
	PageID     int64
	Region     string
	Locale     string
	Status     Status
	Sort       int
	Kind       Kind
	SnippetKey *string
	Content    string
	Settings   map[string]string // section presentation settings (e.g. bg, width)
}

Block is one unit of editable content inside a page region. Simple regions (cmsText/cmsRegion/cmsImage) use a single block at sort 0; sections regions (cmsSections) hold an ordered list of blocks, one per section, with presentation settings.

type Feed

type Feed string

Feed is which of the two post feeds a post belongs to. Blog and news are the same engine — a post's feed decides which listings and RSS feed it appears in, and the slug prefix its page lives under.

const (
	FeedBlog Feed = "blog"
	FeedNews Feed = "news"
)

type Kind

type Kind string

Kind distinguishes short plain text ({{cmsText}}) from rich HTML ({{cmsRegion}}) block content.

const (
	KindText  Kind = "text"
	KindHTML  Kind = "html"
	KindImage Kind = "image" // content holds the image's public URL
)
type MenuItem struct {
	ID       int64
	Menu     string
	Sort     int
	Label    string            // default-locale label
	Labels   map[string]string // per-locale overrides, e.g. {"fr": "À propos"}
	PageID   *int64
	URL      string
	NewTab   bool
	ParentID *int64

	PageSlug       *string // nil when the item is a literal URL
	PageStatus     *Status
	PageVisibility *Visibility
}

MenuItem is one entry in a navigation menu, with the linked page's slug and status joined in for URL resolution and visibility filtering.

func (m MenuItem) LabelFor(locale string) string

LabelFor returns the item's label for locale, falling back to the default-locale label when no override exists.

type MenuItemInput struct {
	Label    string            // default-locale label
	Labels   map[string]string // per-locale overrides; nil stores {}
	PageID   *int64
	URL      string
	NewTab   bool
	Children []MenuItemInput
}

MenuItemInput is one entry supplied to ReplaceMenu. A label-only entry (no page, no URL) is a dropdown parent; Children go one level deep and may not have children of their own.

type Page

type Page struct {
	ID           int64
	Slug         string // "" is the homepage; otherwise e.g. "about" or "about/team"
	TemplateName string // template file within the host's TemplateFS
	Status       Status
	Visibility   Visibility
	HeadCSS      string // extra per-page CSS (or raw head markup), injected by cmsHead
	BodyJS       string // extra per-page JS (or raw markup), injected by cmsScripts
	Title        string
	Description  string
	// MetaDescription is what search engines are told the page is about,
	// when that should differ from Description. It exists for posts,
	// whose Description is the summary shown in listings and feeds — a
	// blurb written for readers browsing a list, which is not always the
	// line worth showing under a search result. Empty means "use
	// Description", so an ordinary page, whose Description is already its
	// meta description, never sets it. Read MetaTag rather than this
	// field to get the words a page actually publishes.
	MetaDescription string
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

Page is a site page. Title and Description are the metadata for the locale the page was loaded with.

func (*Page) MetaTag

func (p *Page) MetaTag() string

MetaTag is the description the page publishes to search engines: its own meta description, or the summary/description it falls back to.

type PageMeta

type PageMeta struct {
	Title           string
	Description     string
	MetaDescription string
	// The default locale's stored values. All are empty when the
	// metadata read is the default locale's own.
	InheritedTitle           string
	InheritedDescription     string
	InheritedMetaDescription string
}

PageMeta is one locale's page metadata exactly as stored, without the default-locale fallback Page.Title and Page.Description carry. Inherited holds what that fallback would supply, so a caller editing a translation can offer the inherited words as a placeholder rather than prefilling the field with them — prefilling would copy the default language into the translation's own row on the next save, and the page would stop tracking the original for good.

It is also what UpdateMeta writes, so a save states every stored field rather than a list of strings whose order is the only thing keeping the title out of the description.

type PathViews added in v0.9.0

type PathViews struct {
	Path  string
	Views int
}

PathViews is one path's view total over a queried range.

type Post

type Post struct {
	Page
	PostID      int64
	Feed        Feed
	PublishedAt time.Time // display and ordering date, not a schedule
	AuthorID    *int64
	AuthorName  string // resolved from cms_users; "" when the author is gone
	// HideAuthor drops the byline from the post: a site that publishes as
	// itself — a notice, a release, a listing — wants the date without
	// the name of whoever typed it. It hides the byline only; the author
	// stays recorded, so clearing it brings the same name back. Stated
	// as "hide" so the zero value is the ordinary bylined post.
	HideAuthor bool

	// A post's thumbnail is either a library image or a bare URL. The
	// library is the normal case: the id is stored and ThumbnailMediaID
	// and Thumbnail are set, which lets the renderer pick the rendition
	// that fits the slot and build a srcset from the rest. The URL field
	// carries an image the library does not hold — an absolute URL
	// elsewhere, or a path the host site serves itself — and is empty
	// whenever an id is set.
	//
	// The banner at the top of a post is not here: it is a section in the
	// post template's header region, so it comes with the settings every
	// section has and follows the draft/publish flow.
	ThumbnailMediaID *int64
	ThumbnailURL     string       // optional listing thumbnail, when not from the library
	Thumbnail        *media.Media // joined library record, nil when there is none
}

Post is one blog or news entry. The embedded Page is its backing page — the post's body lives in that page's blocks and is edited exactly like any page (in place, with sections and snippets); the page's Title and Description double as the post's title and summary. Post slugs are always prefixed with the feed name, e.g. "blog/launch-day".

func (*Post) ThumbnailMediaIDValue

func (p *Post) ThumbnailMediaIDValue() int64

ThumbnailMediaIDValue returns the thumbnail's media id, or 0 when the post has no library thumbnail — convenient in templates, where pointers are awkward to compare.

type SectionInput

type SectionInput struct {
	Settings map[string]string
	Content  string
}

SectionInput is one section supplied to ReplaceDraftSections.

type SiteSettings

type SiteSettings struct {
	MenuAlign string // "left", "center", "right", or "" (host default)
	SiteName  string
	LogoURL   string // "" = no logo
	// FaviconURL is the site's browser-tab icon, emitted by cmsHead as
	// <link rel="icon">. "" leaves the host template's own icon (or the
	// browser's /favicon.ico guess) alone.
	FaviconURL string
	// LoginInNav adds a "Log in" link to {{cmsNav}} for visitors who
	// aren't logged in, pointing at the admin login page.
	LoginInNav bool
	// SiteCSS and SiteJS are injected raw into every public page (via
	// cmsHead/cmsScripts). Each holds plain code or full markup — <style>,
	// <link>, and <script> tags pass through as-is. Editing them is
	// admin-only, like per-page code.
	SiteCSS string
	SiteJS  string
	// Mode is ModeDevelopment or ModeProduction (or "", read as
	// production). Development asks search engines to leave the site
	// alone; see Development. Changing it is superadmin-only.
	Mode string
	// RobotsTxt is the site's own /robots.txt, served verbatim once the
	// site is in production. "" — the default — leaves the path to the
	// host, which is what a site that predates this setting keeps
	// getting. Development ignores it and serves its own Disallow; see
	// Development. Editing it is superadmin-only, like Mode.
	RobotsTxt string
	// Sitemap makes the CMS serve a sitemap of every published, public
	// page at /sitemap.xml. Off — the default — leaves that address to
	// the host app, so an upgrade never shadows a sitemap it already
	// serves; SeedAdmin turns it on for brand-new sites. A site in
	// development serves none regardless: it has nothing it wants found.
	// Switching it is superadmin-only, like Mode.
	Sitemap bool
	// NoticeBar shows the site-wide notice bar — a thin strip above
	// everything else on every page, for the message the whole site has
	// to carry at once: a holiday closure, a delivery delay, a service
	// interruption. Its words are not here: they live in the shared
	// region render.NoticeRegion, so they translate, sanitize, and
	// publish exactly like a footer does. These three settings are the
	// bar itself.
	NoticeBar bool
	// NoticeStyle names the bar's colour scheme, one of the curated keys
	// in render.NoticeStyles. "" is the first of them.
	NoticeStyle string
	// NoticeDismissible gives the bar a close button, and remembers the
	// dismissal in the visitor's browser until the notice's words
	// change. Off, the bar stays until it is switched off here.
	NoticeDismissible bool
	// EditorTheme is the colour scheme of the in-place editor's own
	// chrome — the edit bar, the tool rail, the floating block and
	// section toolbars, and TinyMCE's formatting toolbar. "" and
	// EditorThemeDark are the dark chrome the editor has always worn;
	// EditorThemeLight swaps it for a pale one, which is what a site
	// with a dark design of its own needs: dark chrome on a dark page
	// stops reading as chrome at all.
	EditorTheme string
}

SiteSettings are the site-wide presentation settings the in-place editor's "Site settings" dialog manages. Zero values mean "not set" — templates fall back to their own defaults.

func (SiteSettings) Development added in v0.9.2

func (s SiteSettings) Development() bool

Development reports whether the site is in development mode, and so should be kept out of search results.

This is a request to well-behaved crawlers, not access control: the site is still served to anyone who asks for it. Keeping an unfinished site genuinely private is the host's job — HTTP auth, an IP allowlist, or simply not pointing a public name at it.

type SitemapEntry added in v1.1.3

type SitemapEntry struct {
	Slug      string
	UpdatedAt time.Time
}

SitemapEntry is one page as a sitemap sees it: where it lives and when it last changed. No metadata — a sitemap lists addresses, and the titles and descriptions belong to the pages themselves.

type Status

type Status string

Status is a page's or block set's publication state.

const (
	StatusDraft     Status = "draft"
	StatusPublished Status = "published"
)

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store reads and writes pages and blocks in Postgres. Reads for a non-default locale fall back to the default locale's values where the requested locale has none.

func NewStore

func NewStore(db *sqldb.DB, defaultLocale string) *Store

NewStore returns a Store backed by db. defaultLocale is the fallback for per-locale reads (page metadata, blocks); pass the site's first configured locale. Empty defaults to "en".

func (*Store) All

func (s *Store) All(ctx context.Context, locale string) ([]Page, error)

All returns every page with metadata for locale, ordered by slug, as the working copy — it backs admin listings.

func (*Store) AllNonPost

func (s *Store) AllNonPost(ctx context.Context, locale string) ([]Page, error)

AllNonPost returns every page that is not a post's backing page, ordered by slug — the admin Pages list, where posts appear under Blog & News instead.

func (*Store) AllNonPostPage

func (s *Store) AllNonPostPage(ctx context.Context, locale string, limit, offset int) ([]Page, error)

AllNonPostPage is AllNonPost windowed: the limit pages starting offset in from the first. A non-positive limit returns everything, offset and all — there is no window to slide without one.

Slugs are unique, so ordering by slug is a total order: no page can straddle two pages of the list or be skipped between them.

func (*Store) BlocksFor

func (s *Store) BlocksFor(ctx context.Context, pageID int64, locale string, status Status) ([]Block, error)

BlocksFor returns a page's blocks for one locale and publication state, ordered by region and sort.

func (*Store) CountNonPost

func (s *Store) CountNonPost(ctx context.Context) (int, error)

CountNonPost is how many pages AllNonPost would return — what the admin's paginated Pages list needs to size its page links. It takes no locale: the metadata joins only decide which title a page is listed under, never whether it is listed.

func (*Store) CountPosts

func (s *Store) CountPosts(ctx context.Context, feed Feed, publishedOnly bool) (int, error)

CountPosts is how many posts Posts would return for the same feed and publishedOnly with no limit — what a paginated listing needs to know how many pages it has. It takes no locale: the locale joins only decide which title a post is listed under, never whether it is listed.

func (*Store) Counts

func (s *Store) Counts(ctx context.Context) (pages, posts int, err error)

Counts returns how many non-post pages and how many posts exist — the numbers the admin shows beside its Pages and Blog & News nav entries.

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, id int64) error

Delete removes a page and (via cascade) its metadata and blocks. The site page is not deletable — losing it would take every shared region with it — so it reads as not found.

func (*Store) DeleteLocaleContent

func (s *Store) DeleteLocaleContent(ctx context.Context, pageID int64, locale string) error

DeleteLocaleContent removes a page's draft blocks and draft metadata row for one (non-default) locale, so the page reverts to default-locale fallback. Draft-side only: like any edit it goes live on the next Publish.

func (*Store) DiscardDraft

func (s *Store) DiscardDraft(ctx context.Context, pageID int64) error

DiscardDraft throws away a page's unpublished edits: the draft blocks and metadata are replaced by copies of the currently published ones and the staged page-level fields revert to the page row, so the editor returns to exactly what is live. The page's publication status is left unchanged. It is the inverse of Publish.

func (*Store) Duplicate

func (s *Store) Duplicate(ctx context.Context, srcID int64, slug, title, locale string) (int64, error)

Duplicate copies the page srcID under a new slug: the page row itself (template, per-page CSS/JS), its metadata for every locale, and its draft blocks. title becomes the copy's title for locale; other locales keep the source's titles. The copy always starts as a draft, so the source's published blocks are not copied — the copy's first Publish snapshots the duplicated draft. Returns the new page's id, ErrDuplicateSlug when slug is taken, or ErrNotFound when the source page doesn't exist.

func (*Store) EffectiveBlocks

func (s *Store) EffectiveBlocks(ctx context.Context, pageID int64, locale string, status Status) ([]Block, error)

EffectiveBlocks returns a page's blocks for locale with region-level fallback to the store's default locale: regions with no rows in the requested locale use the default locale's rows wholesale. Region-level (not per-block) because a sections region is one ordered document — interleaving two locales' section lists would be nonsense. Callers can tell fallback content apart by the blocks' Locale field.

func (*Store) EffectiveBlocksWithShared

func (s *Store) EffectiveBlocksWithShared(ctx context.Context, pageID int64, locale string, status Status) (page, shared []Block, err error)

EffectiveBlocksWithShared returns a page's blocks and the site's shared blocks for one locale and publication state, in a single query. Both sets get the same region-level locale fallback EffectiveBlocks applies.

The two travel together because every page render needs both: shared regions are the site's chrome, so a separate round trip would be one more query on every request, for content that is the same on all of them.

func (*Store) GetByID

func (s *Store) GetByID(ctx context.Context, id int64, locale string) (*Page, error)

GetByID returns the page with the given id, with metadata for locale. It reads the draft working copy: every caller is an admin screen.

func (*Store) GetBySlug

func (s *Store) GetBySlug(ctx context.Context, slug, locale string, publishedOnly bool) (*Page, error)

GetBySlug returns the page with the given slug, with metadata for locale. With publishedOnly, draft pages are treated as not found and the page reads as the site serves it; without, it reads as the working copy, which is what the editor and preview want.

func (*Store) HasSharedUnpublishedChanges

func (s *Store) HasSharedUnpublishedChanges(ctx context.Context) (bool, error)

HasSharedUnpublishedChanges reports whether shared regions hold saved edits the site is not showing yet — the same probe HasUnpublishedChanges runs for a page, so the editor's status chip can count shared content as what it is: an unpublished edit visible on the page in front of you.

A site page that does not exist has no edits, rather than being an error: this runs on every editor render and must not take pages down.

func (*Store) HasUnpublishedChanges

func (s *Store) HasUnpublishedChanges(ctx context.Context, pageID int64) (bool, error)

HasUnpublishedChanges reports whether a page has edits that the site is not yet showing: draft blocks or metadata differing from the published ones in any way (content, order, settings, or rows added/removed) in any locale, or staged page-level fields differing from the page row. Locale- blind because Publish snapshots every locale at once. The set-difference probes rely on EXCEPT, which MySQL only gained in 8.0.31 and MariaDB in 10.3 — that is where the CMS's MySQL floor comes from. The JSON cast and the NULL-safe comparison have no shared spelling, so both come from the dialect.

func (*Store) Insert

func (s *Store) Insert(ctx context.Context, p *Page, locale string) (int64, error)

Insert stores a new page and its metadata for locale, returning its id. New pages always start as drafts.

func (*Store) InsertPost

func (s *Store) InsertPost(ctx context.Context, p *Post, locale string) (int64, error)

InsertPost stores a new post and its backing page (always a draft) in one transaction, returning the post's id. The caller sets the page fields (Slug already feed-prefixed, TemplateName, Title, Description) and the post fields; a zero PublishedAt becomes now.

func (*Store) MenuItems

func (s *Store) MenuItems(ctx context.Context, menu string) ([]MenuItem, error)

MenuItems returns menu items ordered by menu and sort. An empty menu returns items for every menu (for rendering, which may need several).

func (*Store) MetaFor

func (s *Store) MetaFor(ctx context.Context, pageID int64, locale string) (PageMeta, error)

MetaFor returns the page's draft metadata for locale as stored: no fallback applied, so an empty field means this locale has none of its own and reads as the default locale's. It is the read behind an editing form, where Page's already-resolved Title and Description cannot tell an inherited value from an authored one.

func (*Store) PageViewsByDay added in v0.9.0

func (s *Store) PageViewsByDay(ctx context.Context, from, to time.Time) (map[string]int, error)

PageViewsByDay sums the recorded views per day over [from, to], both taken as UTC dates. The result maps "2006-01-02" keys to totals; days with no traffic are simply absent, so callers fill their own zeroes. The summing happens here rather than in SQL because SUM's result type is engine-flavoured (numeric, DECIMAL) while the per-row counters scan as plain integers everywhere — and a week holds few rows.

func (*Store) PostByID

func (s *Store) PostByID(ctx context.Context, id int64, locale string) (*Post, error)

PostByID returns the post with the given post id, with page metadata for locale. It reads the working copy: every caller is an admin screen.

func (*Store) PostByPageID

func (s *Store) PostByPageID(ctx context.Context, pageID int64, locale string, draft bool) (*Post, error)

PostByPageID returns the post backed by the given page, or ErrNotFound when the page is not a post. With draft it reads the working copy, which is what the in-place editor shows; without, what the site serves.

func (*Store) Posts

func (s *Store) Posts(ctx context.Context, feed Feed, locale string, publishedOnly bool, limit int) ([]Post, error)

Posts returns a feed's posts newest first, with page metadata for locale. An empty feed returns both feeds (the admin's combined list). With publishedOnly, draft and private posts are omitted (the public view); without, editors see everything. A non-positive limit returns everything.

func (*Store) PostsPage

func (s *Store) PostsPage(ctx context.Context, feed Feed, locale string, publishedOnly bool, limit, offset int) ([]Post, error)

PostsPage is Posts with an offset: the window of limit posts starting offset in from the newest, which is what a paginated listing asks for. A non-positive limit still returns everything, offset and all — there is no window to slide without one.

The ordering is total (published_at, then id), so no post can straddle two pages or be skipped between them the way an ordering with ties can.

func (*Store) PrunePageViews added in v0.9.0

func (s *Store) PrunePageViews(ctx context.Context, before time.Time) error

PrunePageViews deletes counters for days before the given UTC date. The dashboard charts a week; keeping a season of history costs almost nothing and leaves room for a longer chart later, but the table should not grow forever, so Migrate calls this on every startup.

func (*Store) Publish

func (s *Store) Publish(ctx context.Context, pageID int64) error

Publish makes the page's draft content live: the published block set and metadata are replaced by copies of the draft ones, the staged page-level fields are copied onto the page row, and the page is marked published.

func (*Store) PublishShared

func (s *Store) PublishShared(ctx context.Context) error

PublishShared makes the shared regions' draft content live. Every page shows shared content, so there is no page to publish it "on": it goes live alongside whichever page the editor published from.

func (*Store) RecordPageView added in v0.9.0

func (s *Store) RecordPageView(ctx context.Context, day time.Time, path string) error

RecordPageView adds one to the counter for path on the given day (taken as a UTC date). Concurrent instances land on the same row; the upsert makes the increment atomic.

func (*Store) ReplaceDraftSections

func (s *Store) ReplaceDraftSections(ctx context.Context, pageID int64, region, locale string, sections []SectionInput) error

ReplaceDraftSections replaces a sections region's draft blocks with the given ordered list, atomically. An empty list clears the region.

func (*Store) ReplaceMenu

func (s *Store) ReplaceMenu(ctx context.Context, menu string, items []MenuItemInput) error

ReplaceMenu replaces a menu's items with the given ordered tree, atomically. Menus have no draft state — changes are live on commit.

func (*Store) SaveSiteSettings

func (s *Store) SaveSiteSettings(ctx context.Context, in SiteSettings) error

SaveSiteSettings stores the settings, atomically. Like menus they have no draft state — a save is live on commit.

func (*Store) SetSiteMode added in v0.9.2

func (s *Store) SetSiteMode(ctx context.Context, mode string) error

SetSiteMode stores the site mode on its own, leaving every other setting alone — what a fresh install's seeding wants, where writing a whole SiteSettings would mean inventing values for keys nobody has set yet.

func (*Store) SetSitemap added in v1.1.3

func (s *Store) SetSitemap(ctx context.Context, on bool) error

SetSitemap turns the generated sitemap on or off on its own, for the same reason SetSiteMode exists: seeding a new site sets this one key and has no opinion about the others.

func (*Store) SetVisibility

func (s *Store) SetVisibility(ctx context.Context, pageID int64, v Visibility) error

SetVisibility changes who may view the page on the public site. It does not touch publication status or content.

func (*Store) SharedBlocks

func (s *Store) SharedBlocks(ctx context.Context, locale string, status Status) ([]Block, error)

SharedBlocks returns just the site's shared blocks, with the same locale fallback.

func (*Store) SitePageID

func (s *Store) SitePageID(ctx context.Context) (int64, error)

SitePageID returns the id of the site page, the row shared blocks hang off, creating it if it has gone missing. The migration writes it, so the insert is only reached by a database that has been emptied — a test harness truncating between cases, or a hand-cleaned install — and recreating it there is better than leaving shared content unsavable.

Only write paths need the id: reads reach the site page through a subquery, so rendering never pays for this.

func (*Store) SiteSettings

func (s *Store) SiteSettings(ctx context.Context) (SiteSettings, error)

SiteSettings returns the stored site settings. Keys never saved come back as zero values, so a fresh install reads as "all defaults".

func (*Store) SitemapPages added in v1.1.3

func (s *Store) SitemapPages(ctx context.Context, limit int) ([]SitemapEntry, error)

SitemapPages returns every page a search engine may be pointed at: published, publicly visible, and not a system page. Posts come back alongside ordinary pages — a post is a page, so one pass covers both — ordered by slug.

UpdatedAt is the page row's, which moves when a page is published, unpublished, renamed, or has its visibility changed, and stays put while a draft is edited (those writes land on cms_blocks). That makes it the date the live page last changed, which is what a sitemap's lastmod means.

A non-positive limit returns everything; callers that serve the result in one document pass the protocol's ceiling.

func (*Store) TopPages added in v0.9.0

func (s *Store) TopPages(ctx context.Context, from, to time.Time, limit int) ([]PathViews, error)

TopPages returns the most-viewed paths over [from, to] (UTC dates, inclusive), busiest first, at most limit of them. Ties break on path so the order is stable across renders. Summed in Go for the same reason PageViewsByDay is: the per-row counters scan as plain integers on every engine, and a season of a real site's counters is small.

func (*Store) Unpublish

func (s *Store) Unpublish(ctx context.Context, pageID int64) error

Unpublish takes a page off the public site. Draft and published content are left as they are.

func (*Store) Update

func (s *Store) Update(ctx context.Context, p *Page, locale string) error

Update saves a page's fields and its metadata for locale. It does not change publication status; use Publish and Unpublish for that.

Title, description, template and per-page code are staged: they land in the working copy and reach the site on the next Publish. Slug and visibility are not staged and take effect immediately.

func (*Store) UpdateMeta

func (s *Store) UpdateMeta(ctx context.Context, pageID int64, locale string, m PageMeta) error

UpdateMeta saves only a page's per-locale metadata — how non-default- locale admin tabs save, since every other page field is locale- independent, and how the in-place editor's settings dialogs save. Like Update it writes the working copy, so the change reaches the site on the next Publish.

func (*Store) UpdatePost

func (s *Store) UpdatePost(ctx context.Context, p *Post, locale string) error

UpdatePost saves a post's fields and its backing page's fields and metadata for locale, in one transaction. Like Page updates it does not change publication status, and the author is fixed at creation.

The backing page's staged fields (title, description, template, per-page code) go to the working copy and reach the site on the next Publish; the slug and the cms_posts fields — feed, date, images — apply immediately.

func (*Store) UpsertDraftBlock

func (s *Store) UpsertDraftBlock(ctx context.Context, pageID int64, region, locale string, kind Kind, content string) error

UpsertDraftBlock creates or updates the draft block at sort position 0 of a region — the single-block-per-region model used until snippets arrive.

func (*Store) UpsertSharedBlock

func (s *Store) UpsertSharedBlock(ctx context.Context, region, locale string, kind Kind, content string) error

UpsertSharedBlock stores one shared region's draft content. It is UpsertDraftBlock aimed at the site page, so shared edits ride the same draft/publish workflow as page content.

type Visibility

type Visibility string

Visibility is who may view a page on the public site, independent of its publication status: a private page goes through the same draft/publish workflow but is only served to logged-in users once published.

const (
	VisibilityPublic  Visibility = "public"
	VisibilityPrivate Visibility = "private"
)

Jump to

Keyboard shortcuts

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