Files
PersonalBlog/docs/domain-model.md
T
2026-08-27 08:16:40 +00:00

4.3 KiB

Domain model

Site (§12.1)

Site: id UUID, name TEXT, description TEXT NULL, canonical_url TEXT,
      default_theme_id TEXT, created_at/updated_at TIMESTAMPTZ

Even though v1 proposes one site per installation, site_id scopes persisted data, leaving a migration path to multi-site without designing tenancy now.

Content entry (§12.2)

ContentEntry: id UUID, site_id UUID, content_type TEXT, slug TEXT, title TEXT,
              status TEXT, excerpt TEXT NULL, published_at TIMESTAMPTZ NULL,
              current_revision_id UUID NULL, created_at/updated_at, deleted_at NULL

Initial statuses: draft, published. Initial registered content type: core.post. Future content types are registry contributions, not if type === ... branches.

Content revision (§12.3)

ContentRevision: id UUID, content_id UUID, revision_number BIGINT,
                 document_version INTEGER, document JSONB, created_by UUID, created_at

Unique (content_id, revision_number).

Identifier policy (§12.4)

PostgreSQL 18 provides UUIDv7 generation — use UUIDv7 for major aggregates (globally unique opaque IDs with better temporal/index locality than UUIDv4).

Canonical content document (§13)

A post body is a versioned semantic block document:

{ "version": 1, "blocks": [ { "id": "019d...", "type": "core.paragraph", "version": 1, "props": { "text": "Hello world." } } ] }

Every block persists block instance ID, globally unique block type ID, block schema version, and validated properties. Theme CSS classes / concrete theme markup must not be stored as semantic content.

core.markdown block (§13.1, v1.1)

Markdown authoring was a locked product decision omitted from v1.0; it is restored as a block inside the block model. props.source is CommonMark + GFM (tables, strikethrough, task lists, autolinks) + footnotes; raw HTML disabled. Rules:

  1. Exactly one server-side Markdown renderer ending in one sanitiser with an explicit allowlist; the admin preview calls it (no second client-side renderer).
  2. A v0.1 post is a single core.markdown block by default — editor is a Markdown text editor with server-rendered preview.
  3. When the block editor arrives, new content splits into finer blocks; existing core.markdown blocks are never bulk-converted.
  4. Export emits props.source as .md files with frontmatter — the portability guarantee.

Initial blocks (§14)

core.heading, core.paragraph, core.quote, core.code, core.divider, core.image (once media ships). v1.1: core.markdown joins the set as the primary authoring block.

Missing block behaviour (§14.1): public page still renders with a safe placeholder (no raw props, error logs include IDs); admin shows the missing dependency and preserves the original payload (no silent deletion).

Page composition (§15)

Home is an ordered list of versioned section instances:

{ "version": 1, "sections": [ { "id": "home-intro", "type": "core.site-intro", "version": 1, "enabled": true, "settings": {} }, { "id": "home-posts", "type": "core.post-list", "version": 1, "enabled": true, "settings": { "limit": 20 } } ] }

Initial composition UI supports only enable / disable / reorder / configure. Explicitly excluded: arbitrary pixel positioning, nested no-code canvas, user-authored raw HTML layouts, raw CSS editor.

Navigation (§28)

Persisted model: id, site_id, label, destination_type, destination, position, enabled, open_in_new_context, created_at, updated_at. Initial destination types: internal_route, external_url. Semantic <nav>, editable labels/order/visibility, external URL validation, disabled-extension destination yields an admin warning (never a silently broken link).

Column vs JSONB (§23.3)

Normal columns for data routinely constrained/joined/sorted/indexed/referenced/filtered. JSONB for versioned block props, settings, page section configuration, and sparse namespaced metadata.

Core table namespace (§23.1)

eppp_sites, eppp_site_settings, eppp_users, eppp_admin_sessions, eppp_content, eppp_content_revisions, eppp_page_compositions, eppp_navigation, eppp_extensions, eppp_extension_settings, eppp_extension_migrations, eppp_visitor_identities, eppp_visitor_preferences, eppp_media, eppp_jobs, eppp_job_runs, eppp_core_migrations. Extension tables use the ext_<name>_<table> prefix.