Skip to content

Content model

SeeAMess keeps Optimizely’s useful idea: a stable identity, immutable versions, and a published pointer per language. It does not keep the typed attribute-value storage behind it.

  • A content has a UUID, a type, a parent in the tree, a sort index, and a master locale. Imported content keeps its Optimizely GUID.
  • A version is one row with a JSON document of field values, keyed by field ID. Each locale of a content has its own versions, numbered seq 1, 2, 3…
  • A published pointer per content and locale says which version is live.
Status Meaning
draft Work in progress. The only status that can be edited.
ready Marked ready for review. Read-only; editing creates a new draft.
rejected Sent back by a reviewer, optionally with a comment.
scheduled Will be published at its startPublish time by a job.
published The live version of its locale.
superseded Was published, replaced by a newer one.

Editing published content creates a new draft. Saving sends the version you based your edit on (baseVersionId, baseRev); if someone saved in between, the save is refused with stale-version and the editor offers to compare or overwrite. Of two saves on the same base, exactly one succeeds.

A field is either localized (translated per locale) or shared. Shared values are stored on the master locale and merged once, at publish time, into each locale’s projected document. On a non-master locale, shared fields are read-only in the editor.

Content can also be language-neutral (invariant: true): it has one document in its master locale and is served for every locale. Optimizely’s system folders are imported this way.

parent_id is the truth. The projected path (/<locale>/<slug>/<slug>/…/) is derived below a site’s start page. Pages have a slug derived from the name unless you set one; blocks, media and folders have none.

Deleting content deletes its whole subtree in one transaction with one timestamp, which is how the recycle bin restores exactly what one delete removed. A subtree delete is refused when you lack Delete on any live descendant, so an access list deeper in the tree cannot be bypassed.

Reordering sends a position: the content is placed at that index among its new siblings, which are renumbered 0, 1, 2, … in one locked transaction, so concurrent moves cannot produce ties.

Every reference, contentArea and rich text link is recorded in a reference table in the same transaction as the version. So “used by” is always current, and Delivery can expand references without searching documents.

Concept Optimizely SeeAMess
Identity int pkID plus GUID UUID only. Imported content keeps its GUID; integer IDs are kept on the side.
Version tblWorkContent row plus one row per property One version row with a JSONB data document
Published state A copy of the property rows A pointer per locale, plus a projected document
Tree Adjacency, path string and closure table parent_id; the path is derived
Shared fields Stored on the master language, merged at every read Stored on the master language, merged once at publish
Content area HTML-like string JSON array of items
References Maintained separately Written with the version
URL resolution Walk segments on each request One indexed lookup of (site, path)
Schema Rows synced at startup Versioned manifest, applied explicitly