Files
temp-history-api/DB.md
2026-04-21 16:07:17 +07:00

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:

  1. 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.
  2. 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:

  • entitiesgeometries chỉ đượ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 sang section_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_hash nế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_id là liên kết logic tới section_commits.id, nhưng DB hiện không đặt foreign key cho cột này.
  • section_commits.parent_commit_idrestored_from_commit_id có foreign key tới chính section_commits.id.
  • section_submissions.commit_id có foreign key tới section_commits.id.

3. Published Data

3.1 entities

Lưu entity đã được duyệt. API đọc từ bảng này:

  • GET /entities
  • GET /entities/search
  • GET /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 /entities search optional qua q, match name hoặc slug bằng LIKE.
  • GET /entities/search search qua name, có limit, max 100.
  • Response có geometry_count từ entity_geometries.

Apply snapshot behavior:

  • operation = reference: không ghi DB.
  • operation = create: insert entity mới.
  • operation = update hoặc replace: update entity hiện có.
  • operation = delete: set is_deleted = 1, đồng thời xóa link trong entity_geometries.
  • Nếu snapshot có base_updated_at, backend so với entities.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.
  • Entity filter optional qua entity_id, check link active trong entity_geometriesentities.
  • Response là GeoJSON FeatureCollection.
  • properties.type ưu tiên geometries.type.
  • Nếu geometries.type là legacy token line hoặc path, backend fallback sang entity.type_id của primary entity.
  • properties.binding parse từ JSON string trong geometries.binding.

Apply snapshot behavior:

  • operation = reference: không ghi DB.
  • operation = create: insert geometry mới.
  • operation = update hoặc replace: update geometry hiện có.
  • operation = delete: set is_deleted = 1, xóa link trong entity_geometries, và gỡ geometry id đó khỏi binding của các geometry khác.
  • draw_geometry nhận từ snapshot.draw_geometry hoặc fallback snapshot.geometry.
  • Nếu snapshot không gửi bbox, backend tự tính bằng getBBox.
  • 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ới geometries.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_ids không được rỗng.
  • Tất cả entity trong entity_ids phả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ách entity_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 /sections tạo section.
  • Sau khi tạo section, backend tạo kèm row trong section_states với status editing.
  • GET /sections trả section kèm state.

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:

  • editing
  • submitted
  • approved
  • rejected

Ý 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/lock acquire lock explicit.
  • POST /sections/:sectionId/unlock release 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/commits tạo commit manual.
  • POST /sections/:sectionId/restore tạo commit restore, copy snapshot từ commit nguồn.
  • Commit không apply vào published data.
  • commit_no tăng theo từng section.
  • Commit mới set section_states.head_commit_id, tăng section_states.version, set status về editing.
  • expected_versionexpected_head_commit_id là 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:

  • pending
  • approved
  • rejected
  • conflicted

Behavior:

  • POST /sections/:sectionId/submit tạo submission pending.
  • Submit yêu cầu section đang editing và có commit.
  • Submit set section state sang submitted và release lock.
  • GET /sections/:sectionId/submissions trả submission history của section.
  • POST /submissions/:submissionId/approve chỉ nhận submission pending.
  • POST /submissions/:submissionId/reject chỉ nhận submission pending.
  • 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_jsonsection_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ệ:

  • create
  • update
  • delete
  • reference
  • replace, backend normalize thành update trong 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/replace cần name.
  • Slug trong cùng snapshot không được trùng theo lowercase.
  • Geometry operation phải hợp lệ.
  • Geometry delete/reference không cần draw_geometry.
  • Geometry create/update/replace cần draw_geometry hoặc geometry.
  • time_start <= time_end nếu cả hai cùng có giá trị.
  • bbox nếu có thì phải gồm number hợp lệ và min <= max.
  • binding nếu có phải là mảng id, không được chứa chính geometry id.
  • Mỗi link_scope cần entity_ids không rỗng.

6. Luồng Dữ Liệu Chính

6.1 Đọc map

  1. FE gọi GET /geometries với bbox bắt buộc.
  2. Backend query geometries active theo bbox, optional time và optional entity.
  3. Backend load links từ entity_geometriesentities.
  4. Backend trả GeoJSON FeatureCollection.

6.2 Tạo hoặc sửa dữ liệu trong editor

  1. FE mở section bằng GET /sections/:sectionId/editor.
  2. Backend trả section, state, head commit và snapshot.
  3. FE chỉnh draft local.
  4. FE build full snapshot.
  5. FE gọi POST /sections/:sectionId/commits.
  6. Backend validate snapshot và insert section_commits.
  7. Published data chưa thay đổi.

6.3 Submit section

  1. FE gọi POST /sections/:sectionId/submit.
  2. Backend lấy commit id từ body hoặc section_states.head_commit_id.
  3. Backend copy snapshot từ commit sang section_submissions.
  4. Backend set section state thành submitted.
  5. Backend release lock.

6.4 Duyệt section

  1. Reviewer UI /submited load GET /sections, rồi load submissions theo từng section.
  2. Reviewer xem snapshot preview từ editor_feature_collection.
  3. Reviewer approve hoặc reject.
  4. Approve apply snapshot vào entities, geometries, entity_geometries.
  5. 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 entities nếu còn cột deprecated kind.
  • Drop/rebuild geometries nếu còn cột deprecated kind hoặc line_mode.
  • Migrate legacy geometries.type nếu rỗng hoặc là line/path, bằng cách lấy type_id từ 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:

  • users table.
  • Auth/JWT/session table.
  • Role/permission table.
  • Migration version table.
  • Audit log riêng ngoài section_commitssection_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:

  1. Update table init trong BackEnd/db/polygons.js.
  2. Thêm migration runtime nếu DB cũ cần nâng cấp.
  3. Update apply logic trong BackEnd/routes/sections.js.
  4. Update hash conflict logic nếu field ảnh hưởng conflict.
  5. Update read mapper trong BackEnd/routes/entities.js hoặc BackEnd/routes/geometries.js.
  6. Update FE snapshot builder nếu field đến từ editor.
  7. Update reviewer preview nếu field cần hiển thị ở /submited.
  8. Update DB.md, api.md, api_use.md nếu API/schema thay đổi.

Khi đổi workflow section:

  1. Update section_states status transition.
  2. Update create/commit/restore/submit/review routes.
  3. Update FE editor và reviewer UI.
  4. Giữ invariant: commit không apply published data, approve mới apply.