Files
History-user/commit_snapshot.md
2026-05-07 13:38:52 +07:00

11 KiB

Commit Snapshot (commits.snapshot_json) - Chuẩn Hiện Tại (FrontEndAdmin)

Tài liệu này mô tả commit snapshot được FrontEndAdmin tạo ra khi bấm Commit trong /editor, và được lưu vào BackEndGo.commits.snapshot_json (JSONB).

Nguồn tham chiếu trong code:

  • Type snapshot: FrontEndAdmin/src/uhm/types/sections.ts (EditorSnapshot)
  • Build snapshot: FrontEndAdmin/src/uhm/lib/editor/snapshot/editorSnapshot.ts (buildEditorSnapshot)

1) Tổng Quan Schema

Snapshot hiện tại:

  • Không có schema_version.
  • Không lưu section (entity trong DB là projects; project được xác định bằng context record commits.project_id).
  • Không dùng ref:{id} nữa: id là canonical, source:"ref" nghĩa là tham chiếu theo id.
export type CommitSnapshot = {
  editor_feature_collection?: FeatureCollection;

  entities?: EntitySnapshot[];
  geometries?: GeometrySnapshot[];
  wikis?: WikiSnapshot[];

  geometry_entity?: GeometryEntitySnapshot[]; // geometry ↔ entity (many-to-many)
  entity_wiki?: EntityWikiLinkSnapshot[];     // entity ↔ wiki
};

1.1 Type đầy đủ (TypeScript)

Đây là bản type "đúng để BEGo implement chuyển đổi snapshot → DB". FE có thể gửi thêm field legacy (xem mục 6), nhưng BE nên normalize theo các type dưới đây.

// ---- GeoJSON ----

export type Geometry =
  | { type: "Point"; coordinates: [number, number] }
  | { type: "MultiPoint"; coordinates: [number, number][] }
  | { type: "LineString"; coordinates: [number, number][] }
  | { type: "MultiLineString"; coordinates: [number, number][][] }
  | { type: "Polygon"; coordinates: [number, number][][] }
  | { type: "MultiPolygon"; coordinates: [number, number][][][] };

export type FeatureId = string | number; // FE hiện dùng UUIDv7 string

export type FeatureProperties = {
  id: FeatureId;
  type?: string | null;
  geometry_preset?: string | null;
  time_start?: number | null;
  time_end?: number | null;
  binding?: string[]; // entity ids used as "binding filter"

  // Legacy UI fields. FE persist snapshot hiện tại KHONG gửi các field này,
  // nhưng BE nên ignore nếu gặp trong snapshot cũ:
  entity_id?: string | null;
  entity_ids?: string[];
  entity_name?: string | null;
  entity_names?: string[];
  entity_type_id?: string | null;
};

export type Feature = {
  type: "Feature";
  properties: FeatureProperties;
  geometry: Geometry;
};

export type FeatureCollection = {
  type: "FeatureCollection";
  features: Feature[];
};

// ---- Snapshot rows ----

export type SnapshotSource = "inline" | "ref";

export type EntitySnapshotOperation = "create" | "update" | "delete" | "reference";
export type GeometrySnapshotOperation = "create" | "update" | "delete" | "reference";
export type WikiSnapshotOperation = "create" | "update" | "delete" | "reference";

export type EntitySnapshot = {
  id: string; // UUIDv7 string (canonical)
  source: SnapshotSource;
  operation?: EntitySnapshotOperation;
  name?: string;
  slug?: string | null;
  description?: string | null;
  status?: number | null;
  base_updated_at?: string;
  base_hash?: string;
};

export type GeometrySnapshot = {
  id: string; // UUIDv7 string (canonical)
  source: SnapshotSource;
  operation?: GeometrySnapshotOperation;

  // Present when source:"inline" (draft features)
  type?: string | null;
  draw_geometry?: Geometry;
  binding?: string[];
  time_start?: number | null;
  time_end?: number | null;
  bbox?: {
    min_lng: number;
    min_lat: number;
    max_lng: number;
    max_lat: number;
  } | null;

  base_updated_at?: string;
  base_hash?: string;
};

export type WikiSnapshot = {
  id: string; // UUIDv7 string (canonical)
  source: SnapshotSource;
  operation?: WikiSnapshotOperation;
  title: string;
  doc: unknown; // tiptap JSON (inline) hoặc null (ref)
  updated_at?: string;
};

// ---- Join tables ----

export type GeometryEntitySnapshot = {
  geometry_id: string;
  entity_id: string;
  base_links_hash?: string;
};

export type EntityWikiLinkSnapshot = {
  entity_id: string;
  wiki_id: string;
  // If missing, BE should treat as "reference" (active link) for backwards-compat.
  operation?: "reference" | "delete";
};

// ---- Root ----

export type CommitSnapshot = {
  editor_feature_collection?: FeatureCollection;
  entities?: EntitySnapshot[];
  geometries?: GeometrySnapshot[];
  wikis?: WikiSnapshot[];
  geometry_entity?: GeometryEntitySnapshot[];
  entity_wiki?: EntityWikiLinkSnapshot[];
};

2) Quy Ước sourceoperation

2.1 source (bắt buộc)

source bắt buộc là một trong:

  • inline: dữ liệu được embed trong snapshot_json.
  • ref: dữ liệu là tham chiếu (theo id), cần fetch bên ngoài nếu muốn đầy đủ.

FE hiện tại luôn ghi source cho entities[], geometries[], wikis[].

2.2 operation (tùy chọn)

operation là tùy chọn. Khi không có operation thì hiểu là:

  • row được đưa vào snapshot như project context (hoặc không đổi trong commit này),
  • commit này không sửa record, và cũng không cần đánh dấu là "reference" để làm “đầu mối nối”.

operation có thể xuất hiện ở:

  • entities[].operation: create | update | delete | reference
  • geometries[].operation: create | update | delete | reference
  • wikis[].operation: create | update | delete | reference

geometry_entity[] không có operation (join table state).

entity_wiki[] dùng operation:"binding"|"delete" để biểu diễn link/unlink trong snapshot (không phải delete trong DB).

3) Ý Nghĩa Từng Phần

3.1 editor_feature_collection

GeoJSON FeatureCollection là nguồn để:

  • render map trong editor,
  • làm cơ sở build geometries[] và join table geometry_entity[].

Lưu ý quan trọng:

  • Snapshot persist không lưu các field entity denormalize trên feature.properties: entity_id/entity_ids/entity_name/entity_names/entity_type_id.
  • Quan hệ geometry ↔ entity nằm ở geometry_entity[].
  • Khi load commit vào editor, FE có thể rehydrate entity_ids/entity_id lên features từ geometry_entity[] để UI hoạt động, nhưng đó không phải dữ liệu persist.

3.2 entities[]

entities[] là danh sách entity liên quan tới project/commit. Mỗi row có source và có thể có/không có operation.

FE build entities[] từ:

  1. Pending entities tạo mới trong editor: source:"inline", operation:"create".

  2. Entity được user “pin” vào project từ search (không gắn geometry, không link wiki): source:"ref", không có operation.

  3. Entities xuất hiện trong geometry_entity[]: source:"ref", operation:"reference".

  4. Entities xuất hiện trong entity_wiki[]: source:"ref", operation:"reference".

3.3 geometries[]

Mỗi Feature trong editor_feature_collection.features[] sinh 1 GeometrySnapshot row:

  • id = String(feature.properties.id)
  • source:"inline"
  • draw_geometry = feature.geometry
  • kèm type, binding, time_start/time_end, bbox (nếu tính được)

operation cho geometry:

  • create: feature mới
  • update: feature thay đổi
  • (không có operation): feature không đổi (không delta trong commit)

Nếu feature bị xoá khỏi draft, FE thêm 1 delete row:

{ "id": "g_1", "source": "ref", "operation": "delete" }

Lưu ý: geometry operation:"delete" không xuất hiện trên map, vì map render theo editor_feature_collection.features[].

Gợi ý cho BE khi apply vào DB:

  • Có thể coi editor_feature_collection là state hiện tại để render/map.
  • geometries[] là "rows + deltas": sẽ có 1 row cho mỗi feature đang tồn tại trong draft (có/không có operation), và có thể có thêm các row operation:"delete" để xoá geometry khỏi project state.

3.4 geometry_entity[] (join table Geometry ↔ Entity)

Join table many-to-many giữa geometry và entity. Mỗi cặp geometry↔entity là một row:

{ geometry_id: string; entity_id: string }

3.5 wikis[]

Danh sách wiki của project tại thời điểm commit:

  • Wiki tạo mới: source:"inline", operation:"create", doc là HTML string (Quill).
  • Wiki sửa: source:"inline", operation:"update", doc là HTML string (Quill).
  • Wiki không đổi: thường không có operation.
  • Wiki add từ search (wiki đã có trong DB): source:"ref", operation:"reference", doc có thể là null.

3.6 entity_wiki[] (join table Entity ↔ Wiki)

export type EntityWikiLinkSnapshot = {
  entity_id: string;
  wiki_id: string;
  // New semantics:
  // - binding: link active
  // - delete: link removed in this snapshot
  // Backwards-compat: older snapshots may use "reference" meaning link active.
  operation?: "binding" | "delete" | "reference";
};

Toggle link trong UI:

  • Toggle ON (bind): { operation: "binding" } (or legacy "reference")
  • Untick checkbox: { operation: "delete" }

4) Ví Dụ JSON (rút gọn)

{
  "editor_feature_collection": {
    "type": "FeatureCollection",
    "features": [
      {
        "type": "Feature",
        "properties": {
          "id": "g_1",
          "type": "city",
          "time_start": 1200,
          "time_end": 1300,
          "binding": []
        },
        "geometry": { "type": "Point", "coordinates": [105.8, 21.0] }
      }
    ]
  },
  "entities": [
    { "id": "e_2", "source": "ref", "name": "Pinned Entity" },
    { "id": "e_1", "source": "ref", "operation": "reference", "name": "Ha Noi", "status": 1 }
  ],
  "geometries": [
    {
      "id": "g_1",
      "source": "inline",
      "operation": "update",
      "type": "city",
      "draw_geometry": { "type": "Point", "coordinates": [105.8, 21.0] },
      "binding": [],
      "time_start": 1200,
      "time_end": 1300,
      "bbox": { "min_lng": 105.8, "min_lat": 21.0, "max_lng": 105.8, "max_lat": 21.0 }
    }
  ],
  "geometry_entity": [
    { "geometry_id": "g_1", "entity_id": "e_1" }
  ],
	  "wikis": [
	    {
	      "id": "w_inline_1",
	      "source": "inline",
	      "operation": "create",
	      "title": "Overview",
	      "doc": "<p>Overview</p>"
	    },
    {
      "id": "019d...wiki_from_db",
      "source": "ref",
      "operation": "reference",
      "title": "Existing Wiki (DB)",
      "doc": null
    }
  ],
	  "entity_wiki": [
	    { "entity_id": "e_1", "wiki_id": "w_inline_1", "operation": "binding" }
	  ]
	}
	```

## 5) Notes Cho BackEnd (Normalize + Compat)

BE nên normalize trước khi convert snapshot  DB:

- Ignore toàn bộ field entity denormalize trên `feature.properties` (nếu có): `entity_id/entity_ids/entity_name/entity_names/entity_type_id`. Quan hệ geometry↔entity lấy từ `geometry_entity[]`.
- `entity_wiki[].operation`:
  - `"binding"` (or legacy `"reference"`): link active
  - `"delete"`: link removed trong snapshot
  - missing: treat as `"binding"` (compat)

## 6) Legacy Compatibility (nếu gặp snapshot cũ)

FE đã từng gửi các field legacy; BE  thể gặp nếu đang xử  commit cũ:

- `entity_wikis` (plural) thay  `entity_wiki` (singular): treat như nhau.
- `ref:{id}` trong `entities/geometries/wikis`: ignore (id canonical).
- `is_deleted` trong join table entity↔wiki: map sang `operation:"delete"` khi `is_deleted==1`, ngược lại `"binding"` (or legacy `"reference"`).