22 KiB
Database Design - Ultimate History Map
Tài liệu này mô tả database hiện tại của project theo code đang chạy trong
BackEnd/db/polygons.js và schema thực tế đã kiểm tra bằng PRAGMA.
- Engine: SQLite
- Driver:
better-sqlite3 - DB file runtime:
BackEnd/data/polygons.db - Schema init/migration runtime:
BackEnd/db/polygons.js - Domain read API:
BackEnd/routes/entities.js,BackEnd/routes/geometries.js - Section workflow API:
BackEnd/routes/sections.js - Cập nhật:
2026-04-19
1. Tổng Quan
Database hiện chia thành 2 lớp dữ liệu:
-
Published data
- Dữ liệu đã được duyệt.
- Map, search và entity browser đọc trực tiếp từ các bảng này.
- Gồm
entities,geometries,entity_geometries.
-
Section review workflow
- Dữ liệu editor/version control/review.
- Commit và submission lưu full JSON snapshot, không lưu diff.
- Gồm
sections,section_states,section_commits,section_submissions.
Quy tắc hiện tại:
entitiesvàgeometrieschỉ được ghi qua workflow section review.- API domain hiện chỉ expose read cho entity/geometry.
- Editor tạo snapshot, commit snapshot vào
section_commits, submit sangsection_submissions. - Chỉ khi reviewer approve thì snapshot mới được apply vào published tables.
- Reject không đổi published data.
- Conflict protection hiện dựa trên
base_updated_at,base_hash,base_links_hashnếu snapshot có gửi.
2. ERD
erDiagram
entities {
TEXT id PK
TEXT name
TEXT slug UK
TEXT description
TEXT type_id
INTEGER status
INTEGER is_deleted
TEXT created_at
TEXT updated_at
}
geometries {
TEXT id PK
TEXT type
INTEGER is_deleted
TEXT draw_geometry
TEXT binding
INTEGER time_start
INTEGER time_end
REAL bbox_min_lng
REAL bbox_min_lat
REAL bbox_max_lng
REAL bbox_max_lat
TEXT created_at
TEXT updated_at
}
entity_geometries {
TEXT entity_id PK, FK
TEXT geometry_id PK, FK
TEXT created_at
}
sections {
TEXT id PK
TEXT title
TEXT description
TEXT user_id
TEXT created_by
TEXT created_at
TEXT updated_at
}
section_states {
TEXT section_id PK, FK
TEXT status
TEXT head_commit_id
INTEGER version
TEXT locked_by
TEXT locked_at
TEXT lock_expires_at
TEXT updated_at
}
section_commits {
TEXT id PK
TEXT section_id FK
TEXT parent_commit_id FK
INTEGER commit_no
TEXT kind
TEXT restored_from_commit_id FK
TEXT created_by
TEXT created_at
TEXT title
TEXT note
TEXT snapshot_json
TEXT snapshot_hash
}
section_submissions {
TEXT id PK
TEXT section_id FK
TEXT commit_id FK
TEXT submitted_by
TEXT submitted_at
TEXT status
TEXT reviewed_by
TEXT reviewed_at
TEXT review_note
TEXT snapshot_json
TEXT snapshot_hash
}
entities ||--o{ entity_geometries : links
geometries ||--o{ entity_geometries : links
sections ||--|| section_states : state
sections ||--o{ section_commits : commits
section_commits ||--o{ section_commits : parent
section_commits ||--o{ section_submissions : submitted
sections ||--o{ section_submissions : submissions
Ghi chú:
section_states.head_commit_idlà liên kết logic tớisection_commits.id, nhưng DB hiện không đặt foreign key cho cột này.section_commits.parent_commit_idvàrestored_from_commit_idcó foreign key tới chínhsection_commits.id.section_submissions.commit_idcó foreign key tớisection_commits.id.
3. Published Data
3.1 entities
Lưu entity đã được duyệt. API đọc từ bảng này:
GET /entitiesGET /entities/searchGET /entities/:id
| Cột | Kiểu | Ràng buộc/default | Ý nghĩa |
|---|---|---|---|
id |
TEXT |
PRIMARY KEY |
ID entity |
name |
TEXT |
NOT NULL |
Tên entity |
slug |
TEXT |
UNIQUE, nullable |
Slug tìm kiếm/URL |
description |
TEXT |
nullable | Mô tả |
type_id |
TEXT |
NOT NULL DEFAULT 'country' |
Semantic type của entity |
status |
INTEGER |
DEFAULT 1 |
Trạng thái nghiệp vụ |
is_deleted |
INTEGER |
NOT NULL DEFAULT 0 |
Soft delete |
created_at |
TEXT |
nullable | ISO datetime |
updated_at |
TEXT |
nullable | ISO datetime |
Index:
| Index | Cột | Loại |
|---|---|---|
idx_entities_slug |
slug |
unique |
idx_entities_name |
name |
normal |
Read behavior:
- Entity read API luôn lọc
is_deleted = 0. GET /entitiessearch optional quaq, matchnamehoặcslugbằngLIKE.GET /entities/searchsearch quaname, cólimit, max 100.- Response có
geometry_counttừentity_geometries.
Apply snapshot behavior:
operation = reference: không ghi DB.operation = create: insert entity mới.operation = updatehoặcreplace: update entity hiện có.operation = delete: setis_deleted = 1, đồng thời xóa link trongentity_geometries.- Nếu snapshot có
base_updated_at, backend so vớientities.updated_at. - Nếu snapshot có
base_hash, backend tính hash row hiện tại để bắt conflict.
3.2 geometries
Lưu geometry đã được duyệt. API đọc chính:
GET /geometries?minLng=&minLat=&maxLng=&maxLat=&time=&entity_id=
| Cột | Kiểu | Ràng buộc/default | Ý nghĩa |
|---|---|---|---|
id |
TEXT |
PRIMARY KEY |
ID geometry |
type |
TEXT |
nullable | Semantic type dùng render FE |
is_deleted |
INTEGER |
NOT NULL DEFAULT 0 |
Soft delete |
draw_geometry |
TEXT |
NOT NULL |
GeoJSON geometry serialize JSON |
binding |
TEXT |
nullable | JSON array geometry id được bind |
time_start |
INTEGER |
nullable | Năm bắt đầu |
time_end |
INTEGER |
nullable | Năm kết thúc |
bbox_min_lng |
REAL |
nullable | BBox min longitude |
bbox_min_lat |
REAL |
nullable | BBox min latitude |
bbox_max_lng |
REAL |
nullable | BBox max longitude |
bbox_max_lat |
REAL |
nullable | BBox max latitude |
created_at |
TEXT |
nullable | ISO datetime |
updated_at |
TEXT |
nullable | ISO datetime |
Read behavior:
- BBox query bắt buộc có đủ
minLng,minLat,maxLng,maxLat. - Backend lọc
g.is_deleted = 0. - Time filter optional:
- Pass nếu
time_start IS NULL OR time_start <= time. - Pass nếu
time_end IS NULL OR time_end >= time.
- Pass nếu
- Entity filter optional qua
entity_id, check link active trongentity_geometriesvàentities. - Response là GeoJSON
FeatureCollection. properties.typeưu tiêngeometries.type.- Nếu
geometries.typelà legacy tokenlinehoặcpath, backend fallback sangentity.type_idcủa primary entity. properties.bindingparse từ JSON string tronggeometries.binding.
Apply snapshot behavior:
operation = reference: không ghi DB.operation = create: insert geometry mới.operation = updatehoặcreplace: update geometry hiện có.operation = delete: setis_deleted = 1, xóa link trongentity_geometries, và gỡ geometry id đó khỏibindingcủa các geometry khác.draw_geometrynhận từsnapshot.draw_geometryhoặc fallbacksnapshot.geometry.- Nếu snapshot không gửi
bbox, backend tự tính bằnggetBBox. bindingđược normalize thành array string, bỏ rỗng, dedupe, không cho chứa chính id của geometry.- Nếu snapshot có
base_updated_at, backend so vớigeometries.updated_at. - Nếu snapshot có
base_hash, backend tính hash row hiện tại để bắt conflict.
3.3 entity_geometries
Bảng many-to-many giữa entity và geometry.
| Cột | Kiểu | Ràng buộc/default | Ý nghĩa |
|---|---|---|---|
entity_id |
TEXT |
NOT NULL, PRIMARY KEY(entity_id, geometry_id), FK -> entities(id) ON DELETE CASCADE |
Entity được gắn |
geometry_id |
TEXT |
NOT NULL, PRIMARY KEY(entity_id, geometry_id), FK -> geometries(id) ON DELETE CASCADE |
Geometry được gắn |
created_at |
TEXT |
nullable | ISO datetime |
Index:
| Index | Cột | Loại |
|---|---|---|
idx_entity_geometries_geometry_id |
geometry_id |
normal |
idx_entity_geometries_entity_id |
entity_id |
normal |
Apply link_scopes behavior:
- Mỗi scope cần
geometry_id. - Geometry phải tồn tại và
is_deleted = 0. entity_idskhông được rỗng.- Tất cả entity trong
entity_idsphải tồn tại vàis_deleted = 0. - Nếu scope có
base_links_hash, backend hash links hiện tại để bắt conflict. - Apply bằng cách xóa toàn bộ links cũ theo
geometry_id, sau đó insert lại danh sáchentity_ids.
4. Section Review Workflow
4.1 sections
Đại diện cho một workspace/section editor.
| Cột | Kiểu | Ràng buộc/default | Ý nghĩa |
|---|---|---|---|
id |
TEXT |
PRIMARY KEY |
ID section |
title |
TEXT |
NOT NULL |
Tên section |
description |
TEXT |
nullable | Mô tả |
user_id |
TEXT |
nullable | Owner/user liên quan |
created_by |
TEXT |
nullable | Actor tạo section |
created_at |
TEXT |
NOT NULL |
ISO datetime |
updated_at |
TEXT |
NOT NULL |
ISO datetime |
Behavior:
POST /sectionstạo section.- Sau khi tạo section, backend tạo kèm row trong
section_statesvới statusediting. GET /sectionstrả section kèmstate.
4.2 section_states
Lưu trạng thái hiện tại của section.
| Cột | Kiểu | Ràng buộc/default | Ý nghĩa |
|---|---|---|---|
section_id |
TEXT |
PRIMARY KEY, FK -> sections(id) ON DELETE CASCADE |
Section |
status |
TEXT |
NOT NULL DEFAULT 'editing' |
Trạng thái section |
head_commit_id |
TEXT |
nullable | Commit head hiện tại, link logic tới section_commits.id |
version |
INTEGER |
NOT NULL DEFAULT 0 |
Optimistic version |
locked_by |
TEXT |
nullable | Actor đang giữ lock |
locked_at |
TEXT |
nullable | ISO datetime lock |
lock_expires_at |
TEXT |
nullable | ISO datetime hết hạn lock |
updated_at |
TEXT |
NOT NULL |
ISO datetime |
Index:
| Index | Cột | Loại |
|---|---|---|
idx_section_states_status |
status, updated_at |
normal |
Status hiện dùng:
editingsubmittedapprovedrejected
Ý nghĩa status:
| Status | Ý nghĩa |
|---|---|
editing |
Section đang ở trạng thái chỉnh sửa. Editor có thể mở section, acquire lock, tạo commit mới, restore commit và submit commit hiện tại để review. Đây là trạng thái mặc định khi tạo section mới. |
submitted |
Section đã được submit để review từ head_commit_id hiện tại. Trong trạng thái này không tạo commit/restore mới cho section cho tới khi submission được xử lý. Submit cũng release lock editor. |
approved |
Submission mới nhất đã được reviewer approve và snapshot đã được apply vào published tables (entities, geometries, entity_geometries). Published data đã thay đổi theo snapshot được duyệt. |
rejected |
Submission mới nhất bị reviewer reject. Published data không đổi. Section có thể quay lại chỉnh sửa bằng commit/restore mới; commit mới sẽ set status về editing. |
Lock behavior:
- Lock TTL: 15 phút.
GET /sections/:sectionId/editor?user_id=...sẽ acquire lock nếu gửi actor.- Lock hợp lệ nếu trống, cùng actor, hoặc đã hết hạn.
POST /sections/:sectionId/lockacquire lock explicit.POST /sections/:sectionId/unlockrelease lock nếu không bị user khác giữ.
State transition:
| Action | Điều kiện chính | State sau action |
|---|---|---|
| create section | title hợp lệ | editing |
| commit | state editing hoặc rejected, lock hợp lệ |
editing |
| restore | state editing hoặc rejected, lock hợp lệ |
editing |
| submit | state editing, có commit |
submitted |
| approve | submission pending, apply snapshot thành công |
approved |
| reject | submission pending |
rejected |
4.3 section_commits
Lưu version history của section. Mỗi commit là một full snapshot.
| Cột | Kiểu | Ràng buộc/default | Ý nghĩa |
|---|---|---|---|
id |
TEXT |
PRIMARY KEY |
ID commit |
section_id |
TEXT |
NOT NULL, FK -> sections(id) ON DELETE CASCADE |
Section |
parent_commit_id |
TEXT |
nullable, FK -> section_commits(id) |
Commit head trước đó |
commit_no |
INTEGER |
NOT NULL |
Số thứ tự commit trong section |
kind |
TEXT |
NOT NULL DEFAULT 'manual' |
manual hoặc restore |
restored_from_commit_id |
TEXT |
nullable, FK -> section_commits(id) |
Commit nguồn khi restore |
created_by |
TEXT |
NOT NULL |
Actor tạo commit |
created_at |
TEXT |
NOT NULL |
ISO datetime |
title |
TEXT |
nullable | Title commit |
note |
TEXT |
nullable | Ghi chú |
snapshot_json |
TEXT |
NOT NULL |
Full snapshot JSON string |
snapshot_hash |
TEXT |
nullable | sha256:<hex> |
Index:
| Index | Cột | Loại |
|---|---|---|
idx_section_commits_no |
section_id, commit_no |
unique |
idx_section_commits_section_time |
section_id, created_at |
normal |
Behavior:
POST /sections/:sectionId/commitstạo commitmanual.POST /sections/:sectionId/restoretạo commitrestore, copy snapshot từ commit nguồn.- Commit không apply vào published data.
commit_notăng theo từng section.- Commit mới set
section_states.head_commit_id, tăngsection_states.version, set status vềediting. expected_versionvàexpected_head_commit_idlà optional optimistic checks.
4.4 section_submissions
Lưu submission được gửi đi review. Submission copy snapshot từ commit tại thời điểm submit.
| Cột | Kiểu | Ràng buộc/default | Ý nghĩa |
|---|---|---|---|
id |
TEXT |
PRIMARY KEY |
ID submission |
section_id |
TEXT |
NOT NULL, FK -> sections(id) ON DELETE CASCADE |
Section |
commit_id |
TEXT |
NOT NULL, FK -> section_commits(id) |
Commit được submit |
submitted_by |
TEXT |
NOT NULL |
Actor submit |
submitted_at |
TEXT |
NOT NULL |
ISO datetime |
status |
TEXT |
NOT NULL DEFAULT 'pending' |
Trạng thái review |
reviewed_by |
TEXT |
nullable | Actor review |
reviewed_at |
TEXT |
nullable | ISO datetime review |
review_note |
TEXT |
nullable | Ghi chú review hoặc conflict message |
snapshot_json |
TEXT |
NOT NULL |
Snapshot copy từ commit |
snapshot_hash |
TEXT |
nullable | sha256:<hex> |
Index:
| Index | Cột | Loại |
|---|---|---|
idx_section_submissions_section_status |
section_id, status, submitted_at |
normal |
Status hiện dùng:
pendingapprovedrejectedconflicted
Behavior:
POST /sections/:sectionId/submittạo submissionpending.- Submit yêu cầu section đang
editingvà có commit. - Submit set section state sang
submittedvà release lock. GET /sections/:sectionId/submissionstrả submission history của section.POST /submissions/:submissionId/approvechỉ nhận submissionpending.POST /submissions/:submissionId/rejectchỉ nhận submissionpending.- Approve verify
snapshot_hash, parse snapshot, apply vào published data trong transaction. - Reject chỉ cập nhật submission và section state.
- Nếu approve gặp conflict trong quá trình apply, backend set submission thành
conflicted.
5. Snapshot Format
Backend lưu snapshot trong section_commits.snapshot_json và
section_submissions.snapshot_json.
FE hiện gửi thêm editor_feature_collection để editor/reviewer render lại draft đúng trạng thái.
Backend không apply trực tiếp trường này vào published tables, nhưng reviewer UI cần nó để preview.
Shape đang dùng:
{
"schema_version": 1,
"section": {
"id": "section-id",
"title": "Section title"
},
"editor_feature_collection": {
"type": "FeatureCollection",
"features": []
},
"entities": [
{
"operation": "create",
"id": "entity-id",
"name": "Entity name",
"slug": "entity-slug",
"description": null,
"type_id": "country",
"status": 1,
"is_deleted": 0,
"base_updated_at": "optional ISO datetime",
"base_hash": "optional sha256 hash"
}
],
"geometries": [
{
"operation": "create",
"id": "geometry-id",
"type": "country",
"draw_geometry": {
"type": "Polygon",
"coordinates": []
},
"binding": ["other-geometry-id"],
"time_start": null,
"time_end": null,
"bbox": {
"min_lng": 0,
"min_lat": 0,
"max_lng": 1,
"max_lat": 1
},
"is_deleted": 0,
"base_updated_at": "optional ISO datetime",
"base_hash": "optional sha256 hash"
}
],
"link_scopes": [
{
"operation": "replace",
"geometry_id": "geometry-id",
"entity_ids": ["entity-id"],
"base_links_hash": "optional sha256 hash"
}
]
}
Operation hợp lệ:
createupdatedeletereferencereplace, backend normalize thànhupdatetrong entity/geometry apply, còn link scope dùng như replace semantics.
Validation hiện tại:
- Snapshot phải là object.
- Entity operation phải hợp lệ.
- Entity
create/update/replacecầnname. - Slug trong cùng snapshot không được trùng theo lowercase.
- Geometry operation phải hợp lệ.
- Geometry
delete/referencekhông cầndraw_geometry. - Geometry
create/update/replacecầndraw_geometryhoặcgeometry. time_start <= time_endnếu cả hai cùng có giá trị.bboxnếu có thì phải gồm number hợp lệ và min <= max.bindingnếu có phải là mảng id, không được chứa chính geometry id.- Mỗi
link_scopecầnentity_idskhông rỗng.
6. Luồng Dữ Liệu Chính
6.1 Đọc map
- FE gọi
GET /geometriesvới bbox bắt buộc. - Backend query
geometriesactive theo bbox, optional time và optional entity. - Backend load links từ
entity_geometriesvàentities. - Backend trả GeoJSON
FeatureCollection.
6.2 Tạo hoặc sửa dữ liệu trong editor
- FE mở section bằng
GET /sections/:sectionId/editor. - Backend trả section, state, head commit và snapshot.
- FE chỉnh draft local.
- FE build full snapshot.
- FE gọi
POST /sections/:sectionId/commits. - Backend validate snapshot và insert
section_commits. - Published data chưa thay đổi.
6.3 Submit section
- FE gọi
POST /sections/:sectionId/submit. - Backend lấy commit id từ body hoặc
section_states.head_commit_id. - Backend copy snapshot từ commit sang
section_submissions. - Backend set section state thành
submitted. - Backend release lock.
6.4 Duyệt section
- Reviewer UI
/submitedloadGET /sections, rồi load submissions theo từng section. - Reviewer xem snapshot preview từ
editor_feature_collection. - Reviewer approve hoặc reject.
- Approve apply snapshot vào
entities,geometries,entity_geometries. - Reject không apply published data.
7. Runtime Migration
BackEnd/db/polygons.js vừa init schema mới, vừa tự xử lý một số database cũ.
Runtime migration hiện có:
ensureColumn("entities", "status", "INTEGER DEFAULT 1")ensureColumn("entities", "is_deleted", "INTEGER NOT NULL DEFAULT 0")ensureColumn("entities", "type_id", "TEXT NOT NULL DEFAULT 'country'")ensureColumn("geometries", "is_deleted", "INTEGER NOT NULL DEFAULT 0")ensureColumn("geometries", "binding", "TEXT")ensureColumn("sections", "user_id", "TEXT")- Drop/rebuild
entitiesnếu còn cột deprecatedkind. - Drop/rebuild
geometriesnếu còn cột deprecatedkindhoặcline_mode. - Migrate legacy
geometries.typenếu rỗng hoặc làline/path, bằng cách lấytype_idtừ active entity đầu tiên đang link.
Lưu ý:
- Project chưa có bảng migrations riêng.
- Migration chạy khi backend require
BackEnd/db/polygons.js. - Rebuild table tạm tắt foreign keys rồi bật lại sau transaction.
8. Index Tổng Hợp
| Table | Index | Columns | Unique |
|---|---|---|---|
entities |
idx_entities_slug |
slug |
yes |
entities |
idx_entities_name |
name |
no |
entity_geometries |
idx_entity_geometries_geometry_id |
geometry_id |
no |
entity_geometries |
idx_entity_geometries_entity_id |
entity_id |
no |
section_states |
idx_section_states_status |
status, updated_at |
no |
section_commits |
idx_section_commits_no |
section_id, commit_no |
yes |
section_commits |
idx_section_commits_section_time |
section_id, created_at |
no |
section_submissions |
idx_section_submissions_section_status |
section_id, status, submitted_at |
no |
9. Không Có Trong DB Hiện Tại
Các phần sau chưa tồn tại trong schema hiện tại:
userstable.- Auth/JWT/session table.
- Role/permission table.
- Migration version table.
- Audit log riêng ngoài
section_commitsvàsection_submissions. - Direct draft table. Draft editor nằm trong snapshot JSON.
10. Checklist Khi Sửa Schema
Khi thêm hoặc đổi field published:
- Update table init trong
BackEnd/db/polygons.js. - Thêm migration runtime nếu DB cũ cần nâng cấp.
- Update apply logic trong
BackEnd/routes/sections.js. - Update hash conflict logic nếu field ảnh hưởng conflict.
- Update read mapper trong
BackEnd/routes/entities.jshoặcBackEnd/routes/geometries.js. - Update FE snapshot builder nếu field đến từ editor.
- Update reviewer preview nếu field cần hiển thị ở
/submited. - Update
DB.md,api.md,api_use.mdnếu API/schema thay đổi.
Khi đổi workflow section:
- Update
section_statesstatus transition. - Update create/commit/restore/submit/review routes.
- Update FE editor và reviewer UI.
- Giữ invariant: commit không apply published data, approve mới apply.