Appearance
Content Versions & Activity
Overview
The versions system allows creating named snapshots of content items for branching and review workflows. A version stores a delta (JSON diff) from the main item and can be promoted to become the live content.
The activity log tracks all create/update/delete operations across all collections, providing a full audit trail.
Versions
Data Model
A version is tied to a specific collection + item pair and has a human-readable key (slug). Multiple versions can exist for the same item simultaneously.
odp_versions
| Column | Type | Description |
|---|---|---|
id | UUID PK | |
key | string(64) | Human-readable slug/name |
name | string(255) | Display name |
collection | string(255) FK → odp_collections | Collection name |
item | string(255) | Item primary key (as string) |
hash | string(255) | Hash of the delta content |
date_created | timestamp | |
date_updated | timestamp | |
user_created | UUID FK → odp_users | |
user_updated | UUID FK → odp_users | |
delta | text (JSON) | The content diff from the main item |
Endpoints
POST /versions
Create a new version.
Auth required: Yes (permissions checked by service layer)
Request Body:
json
{
"key": "draft-v2",
"name": "Draft Version 2",
"collection": "articles",
"item": "item-uuid",
"delta": {
"title": "Updated Title",
"body": "New body content"
}
}| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Unique slug for this version within (collection, item) |
name | string | No | Display label |
collection | string | Yes | Collection name |
item | string | Yes | Item primary key |
delta | object | No | JSON object with field overrides |
Response:
json
{
"data": "new-version-uuid"
}GET /versions
List versions with optional filtering.
Auth required: Yes
Query Parameters: Standard ODP query params (filter, sort, fields, limit, offset, meta).
Example Query:
GET /versions?filter[collection][_eq]=articles&filter[item][_eq]=article-uuidResponse:
json
{
"data": [
{
"id": "version-uuid",
"key": "draft-v2",
"name": "Draft Version 2",
"collection": "articles",
"item": "article-uuid",
"hash": "abc123...",
"date_created": "2024-01-01T00:00:00.000Z",
"date_updated": null,
"user_created": "user-uuid",
"user_updated": null,
"delta": { "title": "Updated Title" }
}
],
"meta": { "total_count": 1 }
}GET /versions/:id
Read a single version.
Response: Single version object.
PATCH /versions/:id
Update a version's metadata or delta.
Request Body:
json
{
"name": "Renamed Draft",
"delta": {
"title": "Even More Updated Title"
}
}Response:
json
{
"data": "version-uuid"
}DELETE /versions/:id
Delete a version.
Response: 204 No Content
POST /versions/:id/promote
Promote a version to become the live content of the parent item.
Response: 204 No Content
Business Logic (implemented in VersionsService.promote):
- Reads the version record including
delta - Fetches the current live item from the collection
- Merges
deltaonto the current item data - Updates the live item with the merged data
- Optionally deletes the version after promotion (implementation-dependent)
Activity Log
The activity log (odp_activity) records all data mutations across the system. It is append-only and can be queried to build an audit trail.
odp_activity
| Column | Type | Description |
|---|---|---|
id | integer PK (auto-increment) | |
action | string(45) | Action type (see below) |
user | UUID FK → odp_users | User who performed the action |
timestamp | timestamp | When the action occurred |
ip | string(50) | Client IP |
user_agent | text | Browser/client |
collection | string(255) | Affected collection |
item | string(255) | Affected item PK |
origin | string(255) | Request origin header |
impersonated_by | UUID | If impersonating, the real admin user |
Common Action Types
| Action | Description |
|---|---|
create | Item created |
update | Item updated |
delete | Item deleted |
login | User logged in (via auth.login event) |
logout | User logged out |
impersonation.started | Impersonation session started |
impersonation.stopped | Impersonation session ended |
Revisions
Each activity record can have associated revision data in odp_revisions:
odp_revisions
| Column | Type | Description |
|---|---|---|
id | integer PK | |
activity | integer FK → odp_activity | Parent activity (CASCADE delete) |
collection | string(255) | Collection name |
item | string(255) | Item primary key |
data | text (JSON) | Full snapshot after the change |
delta | text (JSON) | Only the changed fields |
parent | integer FK → odp_revisions | Parent revision |
version | UUID FK → odp_versions | Linked version (if via promote) |
Activity Endpoints
Activity endpoints are read-only. See the system server docs for the full CRUD route list. Activity is accessible at GET /activity and GET /activity/:id.
Example: Fetch recent login activity
GET /activity?filter[action][_eq]=login&sort=-timestamp&limit=20