seedproject-web/api/db/migrations/004_create_cja_projects.sql

68 lines
3.2 KiB
MySQL
Raw Normal View History

-- Projects — the source of truth for /projects and /projects/{slug}.
--
-- Two shapes live in one row: the short form the index needs (title, period,
-- summary, categories) and the long form a case-study page needs (lede, body,
-- metrics, links). Keeping them together avoids a join on every page render
-- and keeps editing in one place.
--
-- JSON is used for the small repeating lists — categories, stack, metrics,
-- links. At this scale that is the right trade: a normalised category table
-- would add two tables and a join to support a filter over a handful of rows.
-- MariaDB can still query inside them via JSON_CONTAINS if that is ever needed.
-- If projects grow past a few dozen, or categories need their own descriptions
-- and pages, promote `categories` to cja_project_categories + a map table.
CREATE TABLE IF NOT EXISTS `cja_projects` (
`project_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
-- identity
`slug` varchar(120) NOT NULL,
`title` varchar(160) NOT NULL,
`kind` varchar(60) NOT NULL DEFAULT '', -- "SaaS", "SaaS / Media"
-- timeline. period_label is what renders; the dates are what sorts, so an
-- ongoing project ("2020 — present") still orders correctly against a dated one.
`period_label` varchar(60) NOT NULL DEFAULT '',
`started_on` date DEFAULT NULL,
`ended_on` date DEFAULT NULL, -- NULL = ongoing
`status` enum('live','building','archived','concept')
NOT NULL DEFAULT 'live',
-- copy
`summary` varchar(500) NOT NULL DEFAULT '', -- index row
`lede` text DEFAULT NULL, -- detail page opener
`body` mediumtext DEFAULT NULL, -- markdown
`role` varchar(200) NOT NULL DEFAULT '', -- "Solo — design, build, ops"
-- structured lists
`categories` longtext DEFAULT NULL CHECK (json_valid(`categories`)),
`stack` longtext DEFAULT NULL CHECK (json_valid(`stack`)),
`metrics` longtext DEFAULT NULL CHECK (json_valid(`metrics`)),
`links` longtext DEFAULT NULL CHECK (json_valid(`links`)),
-- media
`cover_image` varchar(255) NOT NULL DEFAULT '',
`cover_alt` varchar(255) NOT NULL DEFAULT '',
-- seo. Nullable so the page can fall back to title/summary rather than
-- forcing an editor to duplicate them.
`seo_title` varchar(180) DEFAULT NULL,
`seo_description` varchar(320) DEFAULT NULL,
-- publishing
`featured` tinyint(1) NOT NULL DEFAULT 0,
`sort_order` int(11) NOT NULL DEFAULT 0,
`published` tinyint(1) NOT NULL DEFAULT 0,
`published_at` datetime DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
`updated_at` datetime NOT NULL DEFAULT current_timestamp()
ON UPDATE current_timestamp(),
PRIMARY KEY (`project_id`),
UNIQUE KEY `uq_cja_projects_slug` (`slug`),
KEY `idx_cja_projects_listing` (`published`, `sort_order`, `started_on`),
KEY `idx_cja_projects_status` (`status`),
KEY `idx_cja_projects_featured` (`featured`, `sort_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;