Core Relationship Map
This page maps the core Capell model relationships without listing every schema column. Use it when you need the shape of the system before reading migrations or model classes.
blueprints is the registry for pages, sites, themes, and other blueprint-backed records. There is no separate core types table.
Page Delivery
Section titled “Page Delivery”The small Page-centred map above is generated from the canonical Mermaid source. It keeps the README vocabulary focused on the identity records; the detailed tables and operational maps below retain the wider relationship notes.
Start at Site. A site has one default language and one active theme. It owns domains, pages, site-specific layouts, and localized URLs.
Page is the main content record. It belongs to a site, layout, and blueprint, and it sits inside a nested tree. Its public path lives in PageUrl; its translated title, content, and metadata live in Translation.
Language-scoped URLs and content
Section titled “Language-scoped URLs and content”How does one article have two localised URLs? The English title and /en/coast path, and French title and /fr/cote path, are illustrative records for the same Page, not automatically generated defaults. Translations and Page URLs carry language scope. PageUrl::siteDomain() joins on site_id and language_id; it does not use a standalone domain foreign key. Both language contexts belong to the same Site, and separate hosts per language are not required.
Canonical Mermaid source · Public page URL lookup contract
Assets, Access, And Impact
Section titled “Assets, Access, And Impact”flowchart LR Page["Page"] Site["Site"] Layout["Layout"] Blueprint["Blueprint"] Translation["Translation"] Media["Media"] AssetAttachment["Asset attachment"] RoleRestriction["Role restriction"] Role["Role"] GraphEdge["Content graph edge"] Audit["Audit"]
Page -.->|"media owner"| Media Site -.->|"media owner"| Media Layout -.->|"media owner"| Media Blueprint -.->|"media owner"| Media Translation -.->|"media owner"| Media Page -.->|"related record"| AssetAttachment Media -.->|"attached asset"| AssetAttachment Blueprint -.->|"restrictable page behaviour"| RoleRestriction Role -->|"allowed role"| RoleRestriction Page -.->|"source or target"| GraphEdge Media -.->|"source or target"| GraphEdge Site -->|"optional graph scope"| GraphEdge Translation -.->|"auditable record"| Audit Page -.->|"auditable record"| Audit Site -.->|"auditable record"| AuditDashed lines are polymorphic or logical relationships. They are real application relationships, but they are not always hard foreign keys because packages can add more content models later.
media is owned by Spatie Media Library and extended by Capell’s Media model. asset_attachments lets content records point at reusable assets without hard-coding one asset table. content_graph_edges stores derived references for impact analysis.
Operations And Lifecycle
Section titled “Operations And Lifecycle”flowchart LR User["User"] UpgradeRun["Upgrade run"] UpgradeEvent["Upgrade run event"] UpgradeLog["Upgrade log"] Extension["Extension state"] MarketplaceInstall["Marketplace install"] HealthAlert["Extension health alert"] DeletionBatch["Deletion batch"] DeletionRecord["Deleted model record"] ContentLock["Content lock"] Notification["Admin notification subscription"]
User -->|"starts"| UpgradeRun UpgradeRun -->|"records"| UpgradeEvent UpgradeLog -->|"tracks package step"| Extension Extension -.->|"composer name"| HealthAlert MarketplaceInstall -.->|"install id"| HealthAlert DeletionBatch -->|"contains"| DeletionRecord User -->|"holds"| ContentLock User -.->|"subscribes to"| NotificationThe extension and marketplace tables intentionally use stable string keys such as composer_name, install_id, extension_slug, and affected_install_id instead of foreign keys. This lets Capell record package health and marketplace state even when an extension is missing, disabled, or no longer installed.
External Tables Referenced By Core
Section titled “External Tables Referenced By Core”| Table | Source | How core uses it |
|---|---|---|
users | Host Laravel app | Userstamps, content locks, upgrade runs, admin notification subscriptions, and audit users. |
roles | Spatie Permission | Page-type access restrictions through page_role_restrictions.role_id. |
media | Spatie Media Library | Capell media collections and media metadata. |
audits | OwenIt Laravel Auditing | Activity/audit trail for auditable core models. |
Relationship Notes
Section titled “Relationship Notes”| Relationship | Type | Notes |
|---|---|---|
pages.parent_id -> pages.id | Self-reference | Backed by nested set columns for hierarchy. |
page_urls.pageable_* | Polymorphic | Routes page-like records; core primarily uses Page. |
translations.translatable_* | Polymorphic | Localizes records; core primarily uses Page, Site, Media, and related content models. |
media.model_* | Polymorphic | Spatie Media Library ownership for Capell media collections. |
asset_attachments.related_* / asset_* | Polymorphic | Joins an owning record to an attached asset record. |
content_graph_edges.source_* / target_* | Logical polymorphic | Derived graph edges for impact analysis, scoped optionally by site/language. |
content_locks.model_* | Logical polymorphic | One active lock per model record. |
deletion_batches.root_* / deletion_batch_records.model_* | Logical polymorphic | Tracks restore groups without coupling to package tables. |
Page soft deletion records the live subtree in these batch tables before deleting descendants. Restoration and its permission checks use the same recorded membership, so crossing a clock second cannot lose a child or include independently deleted trash. Deletion re-reads current page state and nested-set bounds under row locks; a stale instance of an already-trashed page cannot record a second cascade. Membership is consumed per restored or permanently purged member, and empty Page batches are pruned. A moved subtree retains its own membership until it is restored or purged; a later genuine Page deletion supersedes earlier Page-batch membership for that member only. It preserves Site-owned membership recorded before the Site cascade. Site restoration consumes Page-owned membership for the pages it restores while retaining its own Site history.
Restoration checks every member’s old URLs before writes using the router’s enabled, site-and-language scope and the database’s collation. It restores standard URL and translation relations in two statements. Members are written parent-first; ordinary Eloquent model events and database auditing run inside the transaction. Only Core’s restore notification, cache invalidation and content-graph work wait until the outermost transaction commits. Refusal or rollback discards that deferred work. The vendor timestamp hook performs no second restore. See page restoration for the authorisation boundary, extension restrictions and Site restoration.
Historical trash without a recorded page batch must be restored explicitly; its
trashed ancestors are restored too, while descendant membership is never guessed
from deleted_at. Admin recovery names only descendants allowed by the page view
policy and explains separate recovery. Inaccessible descendants are neither named
nor counted. Recently Deleted applies the same view policy before rendering names
or totals and displays the recovery guidance before any action.