Appearance
Content Versions & Activity
Overview
Hệ thống versions cho phép tạo các snapshot có tên của các content item phục vụ luồng branching và review. Một version lưu trữ một delta (JSON diff) so với item chính và có thể được promote để trở thành nội dung trực tiếp.
Activity log ghi lại toàn bộ các thao tác tạo/cập nhật/xóa trên mọi collection, cung cấp một audit trail đầy đủ.
Versions
Data Model
Một version được gắn với một cặp collection + item cụ thể và có một key dễ đọc (slug). Nhiều version có thể tồn tại đồng thời cho cùng một item.
odp_versions
| Column | Type | Description |
|---|---|---|
id | UUID PK | |
key | string(64) | Slug/tên dễ đọc |
name | string(255) | Tên hiển thị |
collection | string(255) FK → odp_collections | Tên collection |
item | string(255) | Khóa chính của item (dạng chuỗi) |
hash | string(255) | Hash của nội dung delta |
date_created | timestamp | |
date_updated | timestamp | |
user_created | UUID FK → odp_users | |
user_updated | UUID FK → odp_users | |
delta | text (JSON) | Nội dung diff so với item chính |
Endpoints
POST /versions
Tạo một version mới.
Yêu cầu xác thực: Có (quyền được kiểm tra ở 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 | Có | Slug duy nhất cho version này trong phạm vi (collection, item) |
name | string | Không | Nhãn hiển thị |
collection | string | Có | Tên collection |
item | string | Có | Khóa chính của item |
delta | object | Không | JSON object chứa các field được ghi đè |
Response:
json
{
"data": "new-version-uuid"
}GET /versions
Lấy danh sách version với bộ lọc tùy chọn.
Yêu cầu xác thực: Có
Query Parameters: Các query param chuẩn của ODP (filter, sort, fields, limit, offset, meta).
Ví dụ truy vấn:
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
Đọc một version cụ thể.
Response: Một object version duy nhất.
PATCH /versions/:id
Cập nhật metadata hoặc delta của một version.
Request Body:
json
{
"name": "Renamed Draft",
"delta": {
"title": "Even More Updated Title"
}
}Response:
json
{
"data": "version-uuid"
}DELETE /versions/:id
Xóa một version.
Response: 204 No Content
POST /versions/:id/promote
Promote một version để trở thành nội dung trực tiếp của item cha.
Response: 204 No Content
Business Logic (được triển khai trong VersionsService.promote):
- Đọc bản ghi version bao gồm
delta(toàn bộ tập field cho item mới, hoặc các field đã thay đổi cho lần chỉnh sửa). - Xác định item đích thông qua
version.item(khóa chính mà version liên kết đến). - Tạo mới hoặc cập nhật:
- Nếu item chưa tồn tại →
ItemsService.createOne({ [pk]: version.item, ...delta })— item được tạo khi approval. - Nếu item đã tồn tại →
ItemsService.updateOne(version.item, delta).
- Nếu item chưa tồn tại →
- Thao tác ghi đi qua
ItemsService(không phải cập nhật column trực tiếp), do đó các relational field (o2m / m2m / translations) và nested write được bảo toàn khi promote. - Nếu collection có field
statusvà delta không thiết lập nó, status sẽ được đặt là published. - Đánh dấu version là
approved.
**Không có placeholder item cho nội dung mới.** Việc tạo item mới theo luồng approval không còn chèn một row trống placeholder từ trước nữa. Frontend tự tạo id (uuid PKs) và liên kết draft version với nó; row thực sự chỉ được tạo **khi version được promote**. Các PK auto-increment vẫn fallback về cách tạo placeholder (vì client không thể tự tạo id). Một version bị rejected sẽ không tạo bất kỳ thứ gì.
**Promotion nằm trong transaction của approval.** Khi một workflow promote một version lúc approval cuối cùng, `promote` chạy **bên trong transaction database của approval**. Nếu promotion thất bại, toàn bộ approval sẽ rollback — instance **không** được đánh dấu là hoàn thành và version vẫn ở trạng thái chưa approved (không có trạng thái "hoàn thành nhưng chưa promote"). Xem [[workflow]] → *Approval Outcome & Version Promotion*.
Activity Log
Activity log (odp_activity) ghi lại toàn bộ các thao tác thay đổi dữ liệu trong hệ thống. Đây là log chỉ ghi thêm (append-only) và có thể được truy vấn để xây dựng audit trail.
odp_activity
| Column | Type | Description |
|---|---|---|
id | integer PK (auto-increment) | |
action | string(45) | Loại hành động (xem bên dưới) |
user | UUID FK → odp_users | Người dùng thực hiện hành động |
timestamp | timestamp | Thời điểm thực hiện hành động |
ip | string(50) | IP của client |
user_agent | text | Trình duyệt/client |
collection | string(255) | Collection bị ảnh hưởng |
item | string(255) | Khóa chính của item bị ảnh hưởng |
origin | string(255) | Request origin header |
impersonated_by | UUID | Nếu đang impersonate, đây là user admin thực sự |
Common Action Types
| Action | Description |
|---|---|
create | Item được tạo mới |
update | Item được cập nhật |
delete | Item bị xóa |
login | Người dùng đăng nhập (qua sự kiện auth.login) |
logout | Người dùng đăng xuất |
impersonation.started | Phiên impersonation bắt đầu |
impersonation.stopped | Phiên impersonation kết thúc |
Revisions
Mỗi bản ghi activity có thể có dữ liệu revision liên quan trong odp_revisions:
odp_revisions
| Column | Type | Description |
|---|---|---|
id | integer PK | |
activity | integer FK → odp_activity | Activity cha (CASCADE delete) |
collection | string(255) | Tên collection |
item | string(255) | Khóa chính của item |
data | text (JSON) | Snapshot đầy đủ sau khi thay đổi |
delta | text (JSON) | Chỉ các field đã thay đổi |
parent | integer FK → odp_revisions | Revision cha |
version | UUID FK → odp_versions | Version liên kết (nếu thông qua promote) |
Activity Endpoints
Các endpoint activity chỉ đọc. Xem tài liệu system server để biết danh sách đầy đủ các route CRUD. Activity có thể truy cập tại GET /activity và GET /activity/:id.
Ví dụ: Lấy activity đăng nhập gần đây
GET /activity?filter[action][_eq]=login&sort=-timestamp&limit=20