mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
ready to test
Testing Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
# Landing Pages / Page Builder — Schema Documentation
|
||||
|
||||
**Migrations:** `41b`, `42`, `43`, `44`, `45`, `49`
|
||||
**Base URL (planned):** `/api/admin/pages`
|
||||
**Guards:** `authenticate → requireAdmin() → adminLimiter`
|
||||
|
||||
> **TODO:** Controller, service, and routes for this module have not been created yet.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
- [Overview](#overview)
|
||||
- [Schema](#schema)
|
||||
- [pages](#pages)
|
||||
- [landing_pages](#landing_pages)
|
||||
- [page_sections](#page_sections)
|
||||
- [page_blocks](#page_blocks)
|
||||
- [page_templates](#page_templates)
|
||||
- [page_user_groups](#page_user_groups)
|
||||
- [Relationships](#relationships)
|
||||
- [Design Decisions](#design-decisions)
|
||||
- [ENUM Types](#enum-types)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The page builder uses a generalized `pages` root table as the single owner of all `page_sections`. This means sections and blocks are not locked to `landing_pages` — any future page type (`lesson_page`, `course_page`, etc.) can reuse the same section/block system by linking to `pages`.
|
||||
|
||||
**Hierarchy:**
|
||||
```
|
||||
pages
|
||||
├── landing_pages (1:1 via page_id)
|
||||
└── page_sections (1:N via page_id)
|
||||
└── page_blocks (1:N via page_section_id)
|
||||
|
||||
page_templates — standalone reusable layout snapshots
|
||||
page_user_groups — controls which groups can see a page
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schema
|
||||
|
||||
### `pages`
|
||||
|
||||
Root identity table. Every page type gets a row here first.
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|------------|------------|----------------------|------------------------------------|
|
||||
| id | BIGSERIAL | PK | |
|
||||
| type | page_type | NOT NULL | Discriminator for the page type |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT now() | |
|
||||
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT now() | |
|
||||
|
||||
**Indexes:** `idx_pages_type` on `(type)`
|
||||
|
||||
---
|
||||
|
||||
### `landing_pages`
|
||||
|
||||
Landing-page-specific metadata. One-to-one with `pages`.
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|------------------|--------------------|------------------------------------|------------------------------|
|
||||
| id | BIGSERIAL | PK | |
|
||||
| page_id | BIGINT | NOT NULL, UNIQUE, FK → pages(id) | Link to root pages table |
|
||||
| title | VARCHAR(255) | NOT NULL | |
|
||||
| slug | VARCHAR(255) | NOT NULL, UNIQUE | URL path segment |
|
||||
| meta_title | VARCHAR(255) | | SEO title override |
|
||||
| meta_description | TEXT | | SEO description |
|
||||
| status | landing_page_status | NOT NULL DEFAULT 'draft' | |
|
||||
| published_at | TIMESTAMPTZ | | Set when first published |
|
||||
| created_by | BIGINT | FK → users(user_id) SET NULL | Admin who created the page |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT now() | |
|
||||
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT now() | |
|
||||
|
||||
**Indexes:** `idx_landing_pages_status` on `(status)`
|
||||
|
||||
---
|
||||
|
||||
### `page_sections`
|
||||
|
||||
Ordered sections within any page. References `pages`, not `landing_pages`.
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|------------|------------------|--------------------------------|------------------------------------|
|
||||
| id | BIGSERIAL | PK | |
|
||||
| page_id | BIGINT | NOT NULL, FK → pages(id) | Belongs to a page (any type) |
|
||||
| type | page_section_type | NOT NULL | Section layout type |
|
||||
| label | VARCHAR(255) | | Admin-facing label |
|
||||
| position | INT | NOT NULL DEFAULT 0 | Display order |
|
||||
| settings | JSONB | | Background, padding, layout, etc. |
|
||||
| is_visible | BOOLEAN | NOT NULL DEFAULT true | Toggle section visibility |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT now() | |
|
||||
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT now() | |
|
||||
|
||||
**Indexes:** `idx_page_sections_page_id` on `(page_id)`, `idx_page_sections_order` on `(page_id, position)`
|
||||
|
||||
---
|
||||
|
||||
### `page_blocks`
|
||||
|
||||
Content blocks within a section.
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|-----------------|----------------|------------------------------------|----------------------------------------|
|
||||
| id | BIGSERIAL | PK | |
|
||||
| page_section_id | BIGINT | NOT NULL, FK → page_sections(id) | |
|
||||
| type | page_block_type | NOT NULL | Block content type |
|
||||
| content | JSONB | | Payload — varies by type (see below) |
|
||||
| position | INT | NOT NULL DEFAULT 0 | Display order within section |
|
||||
| settings | JSONB | | Block-level style overrides |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT now() | |
|
||||
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT now() | |
|
||||
|
||||
**Indexes:** `idx_page_blocks_section_id` on `(page_section_id)`, `idx_page_blocks_order` on `(page_section_id, position)`
|
||||
|
||||
**`content` shape by block type:**
|
||||
| type | content fields |
|
||||
|--------|-----------------------------------------|
|
||||
| text | `{ body: string }` |
|
||||
| image | `{ src: string, alt: string }` |
|
||||
| button | `{ label: string, href: string, variant: string }` |
|
||||
| video | `{ src: string, autoplay: boolean }` |
|
||||
| form | `{ form_id: number }` |
|
||||
| spacer | `{ height: number }` |
|
||||
|
||||
---
|
||||
|
||||
### `page_templates`
|
||||
|
||||
Reusable layout snapshots. Stored as a full JSONB dump of sections + blocks — not live FK references.
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|---------------|-------------|------------------------|--------------------------------------|
|
||||
| id | BIGSERIAL | PK | |
|
||||
| name | VARCHAR(255) | NOT NULL | Template display name |
|
||||
| thumbnail_url | TEXT | | Preview image URL |
|
||||
| structure | JSONB | | Full snapshot of sections and blocks |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT now() | |
|
||||
|
||||
---
|
||||
|
||||
### `page_user_groups`
|
||||
|
||||
Junction table — controls which user groups can access a page.
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|------------|-------------|--------------------------------------|-------------|
|
||||
| page_id | BIGINT | PK, FK → pages(id) ON DELETE CASCADE | |
|
||||
| group_id | BIGINT | PK, FK → user_groups(group_id) | |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT now() | |
|
||||
|
||||
**Indexes:** `idx_page_user_groups_group_id` on `(group_id)`
|
||||
**Primary Key:** composite `(page_id, group_id)`
|
||||
|
||||
---
|
||||
|
||||
## Relationships
|
||||
|
||||
```
|
||||
pages 1 ──── 1 landing_pages
|
||||
pages 1 ──── N page_sections
|
||||
pages N ──── N user_groups (via page_user_groups)
|
||||
page_sections 1 ──── N page_blocks
|
||||
```
|
||||
|
||||
Cascade deletes flow top-down: deleting a `pages` row removes its `landing_pages` record, all its `page_sections`, and all nested `page_blocks` automatically.
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions
|
||||
|
||||
- **`pages` as root** — sections belong to `pages`, not directly to `landing_pages`. Adding a new page type (e.g. `course_page`) only requires a new detail table + adding its value to the `page_type` ENUM. No changes to `page_sections` or `page_blocks`.
|
||||
- **`page_templates.structure` is a snapshot** — templates store a JSONB copy of sections/blocks, not live FK references. This keeps templates stable when source pages are edited.
|
||||
- **`page_sections.page_id`** points to `pages(id)` directly, giving sections access to any page type without schema changes.
|
||||
|
||||
---
|
||||
|
||||
## ENUM Types
|
||||
|
||||
| Type | Values |
|
||||
|---------------------|------------------------------------------------------------------------|
|
||||
| `page_type` | `landing_page`, `lesson_page`, `course_page` |
|
||||
| `landing_page_status` | `draft`, `published`, `archived` |
|
||||
| `page_section_type` | `hero`, `features`, `cta`, `testimonials`, `faq`, `pricing`, `gallery`, `custom` |
|
||||
| `page_block_type` | `text`, `image`, `button`, `video`, `form`, `spacer` |
|
||||
Reference in New Issue
Block a user