barakoCMS

Site settings

Site settings

A site's identity, theme and chrome are one entry of a singleton content type in its tenant, not a file in the site's repository. barakoPress reads that entry per request, so one renderer can serve several sites and a change in barakoBrew reaches the site on the next publish. This is the shape decided for #793, as part of the configured sites plan (#722).

Creating it

POST /api/content-types/blueprints/site

Creates the site type in the caller's tenant: publicly deliverable, and a singleton, so the tenant holds exactly one entry. Then create that entry once and edit it from then on. Applying the blueprint in a tenant that already has a site type is a 409, like any blueprint.

A renderer reads it anonymously:

GET /api/public/site

The list holds the one published entry. A draft is not delivered, so publishing is what makes a theme change live.

Fields

Plain fields hold identity. JSON fields hold the parts a renderer reads as structures. The API checks that a JSON field holds valid JSON and nothing more; the barakoBrew Site and Theme screens check the shapes below, and barakoPress falls back to its defaults for anything missing or unreadable, so a half-filled theme renders rather than breaks.

Field Type What
Name string, required The site's name, used in the header, titles and feeds
Tagline string One line under the name
Url url The site's canonical origin, from which absolute links are built
Locale string For dates and lang, for example en-PH
Logo, FooterLogo, Favicon, ShareImage url Uploaded files or any absolute URL
LogoAlt string Alt text for the logo
Colors json Named colours
Fonts json Font families by role
Radii json Corner radii
Layout json Content widths
Collections json Content types rendered as lists and detail pages
OptionColors json A colour per option of a choice field
Variants json Themes a visitor can switch between
TopBar json The strip above the header
HeaderLinks json Links in the header beyond the page tree
FooterColumns json Footer link columns
SocialLinks json Social profiles
Copyright string The footer's copyright line
Mode string Live or Holding. Unset means Live. See Holding a site back
HoldingPath string The site path of the page shown while holding, such as /holding
HeaderPath, FooterPath string The site path of a page drawn as the header or footer region in place of the built-in one
HeaderTone, FooterTone choice The tone behind that region: page, surface, accent, inverse, gradient or wash
Tokens json Named colours, lengths and font stacks. See Tokens and Tones
Tones json Named tones built from tokens. See Tokens and Tones
StyleRecipes json Named looks a block wears with recipe. See StyleRecipes
MenuLinks json The phone menu's rows. See MenuLinks and HeaderActions
HeaderActions json Call to action links after the header links. See MenuLinks and HeaderActions
Plugins json The plugins this tenant renders. See Plugins

Colors

An object of colour name to hex. The names barakoPress reads today are its theme slots (pageBg, surface, ink, proseInk, secondaryInk, muted, hairline, accent, accentHover, accentInk, accentTint, accentTintBorder, accentTintBorderStrong, darkPanel, darkPanelChrome, darkPanelInk, darkPanelAccent, codeGreen, success). Any other name is a site colour blocks can refer to.

{ "accent": "#17458F", "ink": "#1C1C1C", "pageBg": "#FFFFFF", "royalBlue": "#17458F", "gold": "#F7A81B", "cranberry": "#D41367" }

Fonts, Radii, Layout

{ "heading": "Zilla Slab", "body": "Open Sans", "mono": "JetBrains Mono" }

Family names as Google Fonts spells them. The renderer loads them and adds a fallback stack.

{ "panel": "2px", "control": "2px", "pill": "999px" }
{ "prose": "680px", "wide": "1200px", "gutter": "32px" }

CSS lengths.

Collections

Keyed by a name of the tenant's choosing, each entry a content type rendered as a list and a detail page: a hospital's doctors, a law firm's people, an association's events. This is the shape barakoPress reads (BaryoDev/barakoPress#5); a key that does not read as one below is left out whole rather than half applied.

{
  "events": {
    "type": "event",
    "route": "/events",
    "fields": {
      "title": "Title",
      "slug": "Slug",
      "summary": "Description",
      "date": "StartDate",
      "image": "CoverImage",
      "tags": "Tags"
    },
    "sort": "-StartDate",
    "colorBy": "EntryType",
    "label": "Upcoming events",
    "noun": ["event", "events"]
  }
}
Key What
type Required. The content type holding the items
route The index is served here, an item at {route}/{slug}. Absent, items are listed but never linked
fields.title Required. A field name, or several tried in order until one holds a value
fields.slug, summary, body, date, image, imageAlt, featured, tags, url, photo, progress Optional field roles, each the same shape as title
references Reference fields by field name, for example { "Speaker": { "collection": "person", "label": "with" } }
sort Sent to the API as is, for example -StartDate
feed, sitemap, index Booleans. feed is off, sitemap and index are on, unless said otherwise
pageSize Items on its index, 1 to 100
label, noun The index heading, and a singular and plural for a count, for example ["event", "events"]
colorBy A choice field whose option colours the item, resolved through OptionColors below
related "reference", "semantic", or false. "reference" unless set
readingTime Shows a read time worked out from the body
layout "list" or "article". "list" unless set
index Also takes an object of the index's own words (eyebrow, heading, lede, empty, unavailable), which turns the index on
indexPage A site path whose page's blocks are drawn above the list on the index
tree Turns the collection into a documentation manual: field names for section, order, parent and product, plus sections, products (each may carry a note), searchPath, searchIndex, variant, editBase, editPath and limit for the sidebar and product switcher

OptionColors

Keyed by type.field, then by option, naming a colour from Colors. The events collection above colours its EntryType choice field this way:

{ "project.AreaOfFocus": { "Providing clean water": "sky", "Supporting education": "gold" }, "event.EntryType": { "Fundraiser": "gold", "Outreach": "sky" } }

Variants

[ { "name": "pine", "label": "Pine", "colors": { "accent": "#1A6B41" } } ]

Each variant overrides colours only. A visitor's choice is remembered in their browser.

{ "text": "City of Koronadal, South Cotabato", "links": [ { "label": "Facebook", "href": "https://facebook.com/rckoronadal" } ] }
[ { "label": "Donate", "href": "/donate" } ]
[ { "heading": "Club", "links": [ { "label": "About", "href": "/about" } ] } ]
[ { "network": "facebook", "href": "https://facebook.com/rckoronadal" } ]

An href is either a path on the site or an absolute http or https URL. The renderer drops any other scheme.

A header link may also carry activeOn, space separated site paths it is current on, and children, links drawn as a dropdown one level deep:

[ { "label": "Docs", "href": "/docs", "activeOn": "/docs /guides", "children": [ { "label": "API", "href": "/docs/api" } ] } ]

MenuLinks has the shape of HeaderLinks and holds the phone menu's rows. Unset or empty, the phone menu shows HeaderLinks. HeaderActions is up to four links after the header links, each with a variant of primary, secondary or plain; unset or unknown is primary.

[ { "label": "Donate", "href": "/donate", "variant": "primary" }, { "label": "Contact", "href": "/contact", "variant": "plain" } ]

The barakoPress README has the rest, in The built-in header.

Tokens and Tones

{ "accent": "#E4572E", "cms-ink": "#1D3A8A", "cms-bg": "#E8EEFD", "gutter": "24px", "serif": "'Zilla Slab', Georgia, serif" }
{ "cms": { "ink": "cms-ink", "bg": "cms-bg", "edge": "#B9C8F5" } }

A token is a name and one value: a colour, a CSS length or a font stack. The renderer emits each one as --t-<name>. A tone is a name and three colours, ink, bg and edge, each a token name, a Colors slot or a colour written out. Block tone fields, HeaderTone and FooterTone included, offer the site's tones after the built-in six. The renderer drops a token or tone that fails its check and keeps the rest. See Tokens and tones.

StyleRecipes

Keyed by a recipe name, each with an optional class and an optional style of CSS property to value. {name} in a value stands for a token, and {colors.<slot>}, {space.<step>} and the like for the theme's own values.

{ "card": { "class": "lift", "style": { "padding": "22px 24px", "background": "{colors.surface}", "border-radius": "16px" } } }

The renderer keeps a fixed list of properties and a narrow value shape, and drops what falls outside them. See Style recipes.

Plugins

A list of plugin names, from the plugins the deployment's barakoPress image has installed.

[ "tally" ]

Unset, the renderer uses the plugins its own config names. Saved as an empty list, every plugin is off for this tenant. See Plugin packages.

Holding a site back

Mode set to Holding asks a frontend to show the page at HoldingPath on every route instead of the site. It covers a launch, maintenance and a seasonal break. The holding page is an ordinary page from the Pages module, and HoldingPath is a path string rather than a reference because a blueprint may only reference types it declares. Going live again is a publish, not a deploy.

Mode is a string today, documented as Live or Holding. Any other value should be read as Live. It becomes a choice field once the choice type (#820) is on master.

Mode and HoldingPath are presentation, not access control. Frontends honour them; the API hides nothing. While holding, published, publicly deliverable content is still served from /api/public/... to anyone who asks. To keep content hidden before launch, leave it unpublished and schedule the publish.

People who need to see a held site (a client, a board, a reviewer) get a share link. A tenant can have many, each with its own label and expiry, and each revoked on its own.

A link is:

{site Url}/_share#{key}

The key is in the fragment, so a browser never sends it to a server: it stays out of access logs and out of the Referer header. The frontend's /_share page reads the fragment and redeems it.

Route Who Answers
POST /api/site/share-links may update site 201 { id, label, expiresAt, createdAt, key }
GET /api/site/share-links may update site a page of { id, label, createdAt, createdBy, expiresAt, revokedAt, lastUsedAt }
DELETE /api/site/share-links/{id} may update site 204, or 404 for an unknown id
POST /api/public/site/share-links/redeem anyone 200 { expiresAt }, or 404

Managing links needs update permission on the site type (SuperAdmin always has it), for listing too, since the list names who shared the site with whom.

Creating. The body is { "label": "...", "expiresAt": "..." }. The label is required, at most 100 characters. expiresAt is optional: unset means 30 days from now, and more than 90 days away is a 400. The key is 32 random bytes, base64url encoded, and appears in this response and nowhere else. Only its SHA-256 is stored, on a tenant scoped document that is not part of site settings, public delivery or a portability export. Creating is audited as site.share_link.created with the label and expiry, never the key. A tenant holds at most 100 active links; revoke one to make another.

Redeeming. The frontend posts { "key": "..." } with the tenant resolved the same way as GET /api/public/site. A live link answers 200 with its expiresAt and records lastUsedAt. A wrong key, an expired or revoked link, and another tenant's key all answer the same 404. Both answers carry Cache-Control: no-store. Redeeming is rate limited per tenant and visitor (RateLimiting:SiteShare, 10 a minute by default). The key is never logged.

Sessions. After a 200 the frontend may keep its own session so the previewer does not redeem on every page. Keep it for at most 24 hours, and never past the link's expiresAt; after that, redeem again.

Revoking. DELETE sets revokedAt, is audited as site.share_link.revoked, and the key stops redeeming at once. A session a frontend already started runs out on its own, within 24 hours.

Why a content type

Decided on #793: a singleton reuses what content already has, publishing, history, per-field permissions and delivery, and barakoBrew already edits singletons as one screen. A dedicated endpoint could refuse an unreadable colour pair at the API, and that check lives in the barakoBrew Theme screen instead (BaryoDev/barakoBrew#134).