docs
/
AppEngine API

Lifecycle, history and approval

State transitions, versioning, restore from history and trash, and per-record permissions.

Versioning and approval are properties of BaseModel, not of any one feature — they work identically on a page, a product, a payroll run and a community post. Publishing applies to pages and posts: for any other datatype publish and unpublish do nothing.

States

ModelState values, in the order they normally occur:

draft → new → pending → inprogress → reviewed → approved → published → completed

Off-ramps: hold, rejected, cancelled, archived, deleted.

Filter by state on any list or search call using options.modelState, which accepts one state or an array — this is how a public site fetches only published records.

Approval

GET/repository/request-approval/:datatype/:idJWT
POST/repository/approve/:datatype/:idJWT
POST/repository/reject/:idJWT

Who may approve is governed by the review and approve content permissions — held through roles such as Reviewer and Publisher, or granted per record through requiredRole.

Publishing

POST/repository/publish/:datatype/:idJWT
POST/repository/unpublish/:datatype/:idJWT

Publishing copies the page or post into the published collection — same sk, with sourceDatatype naming the original — and marks the source published with a publishedDate. Sites read that copy (for posts always; for pages when the site sets publishedPagesOnly).

Any later save marks the source draft again — unpublished changes — while the live copy stays as it was until you publish again. Unpublishing deletes the published copy and resets the source to draft with no publishedDate; the source keeps its content. Deleting the source deletes its published copy too.

History

GET/repository/history/:datatype/:idJWT
POST/repository/history/:restoreJWT

version increments on each write and prior revisions are retained in the history datatype. HistoryModule (/history/*) exposes the same data as its own surface.

Trash

Deletes are soft. Records move to the trash datatype and can be brought back:

POST/repository/trash-restoreJWT

DELETE /repository/truncate/:datatype is the exception — it empties the collection, with nothing to restore. It is a root-admin API call only; the Studio does not offer it.

Per-record permissions

requiredRole stores content permissions per operation on the record itself:

{
  "requiredRole": {
    "read":   ["read"],
    "update": ["update", "approve"],
    "delete": ["delete"]
  }
}

Enforced in two places:

  • JwtAuthGuard reads requiredRole.update and requiredRole.delete before the handler runs.
  • ContentPermissionInterceptor reads requiredRole.read — and falls back to requiredRole.review — on the response, throwing 403 after the handler has already executed.

A user-datatype caller is allowed through when requiredRole.read includes User, without any permission comparison.

Read checks sample the first record only

For a list, the interceptor inspects data[0] and applies its requiredRole to the entire response. A list mixing records with different read requirements is not filtered element by element — either all of it returns or the whole call is 403.

Workflow and notes

workflow on a record holds a TaskModel, connecting it to WorkflowModule (/workflow) and the task datatype.

notes is an array of { author, comment, date } on any record, with its own surface:

POST/notes/:datatype/:idJWT
GET/notes/:datatype/:idJWT

CommentsModule (/comments) is the heavier sibling — threaded discussion with moderation, mentions and edits — where notes is deliberately just inline commentary.

Activity

Significant events are written as activity records via recordActivity(orgId, datatype, id, actor, { type, status, comment, meta }). Sign-in writes one on both success and failure, which is what makes the login audit trail work without a separate audit system.