@topy-ai/maggie 0.7.32 → 0.7.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-zh-TW.md +2 -2
- package/README.md +19 -4
- package/bundled-skills/maggie-blog-bootstrap/SKILL.md +8 -0
- package/bundled-skills/maggie-clone/SKILL.md +7 -1
- package/bundled-templates/astro-blog/README.md +217 -0
- package/bundled-templates/astro-blog/examples/blog.config.ts +30 -0
- package/bundled-templates/astro-blog/examples/content.config.ts +28 -0
- package/bundled-templates/astro-blog/examples/metadata.ts +37 -0
- package/bundled-templates/astro-blog/examples/ops-api.ts +31 -0
- package/bundled-templates/astro-blog/examples/route-manifest.ts +21 -0
- package/bundled-templates/astro-blog/starter/.env.example +36 -0
- package/bundled-templates/astro-blog/starter/README.md +168 -0
- package/bundled-templates/astro-blog/starter/astro.config.mjs +19 -0
- package/bundled-templates/astro-blog/starter/db/schema.sql +508 -0
- package/bundled-templates/astro-blog/starter/package-lock.json +8039 -0
- package/bundled-templates/astro-blog/starter/package.json +41 -0
- package/bundled-templates/astro-blog/starter/scripts/contract-check.mjs +16 -0
- package/bundled-templates/astro-blog/starter/scripts/fixture-seed.mjs +11 -0
- package/bundled-templates/astro-blog/starter/scripts/schema-check.mjs +13 -0
- package/bundled-templates/astro-blog/starter/scripts/seed-db.mjs +20 -0
- package/bundled-templates/astro-blog/starter/src/data/demo.ts +52 -0
- package/bundled-templates/astro-blog/starter/src/layouts/BaseLayout.astro +53 -0
- package/bundled-templates/astro-blog/starter/src/layouts/OpsLayout.astro +73 -0
- package/bundled-templates/astro-blog/starter/src/lib/ai-cmo.ts +14 -0
- package/bundled-templates/astro-blog/starter/src/lib/config.ts +15 -0
- package/bundled-templates/astro-blog/starter/src/lib/db.ts +95 -0
- package/bundled-templates/astro-blog/starter/src/lib/google.ts +28 -0
- package/bundled-templates/astro-blog/starter/src/lib/media.ts +12 -0
- package/bundled-templates/astro-blog/starter/src/lib/migration-validation.ts +11 -0
- package/bundled-templates/astro-blog/starter/src/lib/migration.ts +21 -0
- package/bundled-templates/astro-blog/starter/src/lib/ops-auth.ts +21 -0
- package/bundled-templates/astro-blog/starter/src/lib/providers/email.ts +45 -0
- package/bundled-templates/astro-blog/starter/src/lib/providers/media-storage.ts +37 -0
- package/bundled-templates/astro-blog/starter/src/lib/report-delivery.ts +26 -0
- package/bundled-templates/astro-blog/starter/src/lib/report-pdf.ts +49 -0
- package/bundled-templates/astro-blog/starter/src/lib/repository.ts +72 -0
- package/bundled-templates/astro-blog/starter/src/lib/seo-audit.ts +72 -0
- package/bundled-templates/astro-blog/starter/src/lib/seo-insights.ts +14 -0
- package/bundled-templates/astro-blog/starter/src/lib/seo.ts +33 -0
- package/bundled-templates/astro-blog/starter/src/lib/types.ts +58 -0
- package/bundled-templates/astro-blog/starter/src/lib/wordpress.ts +847 -0
- package/bundled-templates/astro-blog/starter/src/middleware.ts +7 -0
- package/bundled-templates/astro-blog/starter/src/pages/404.astro +11 -0
- package/bundled-templates/astro-blog/starter/src/pages/about.astro +15 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/approval/[token].ts +25 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/consent.ts +8 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/agency.ts +47 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/authors.ts +11 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/bulk.ts +58 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/calendar/publish-due.ts +12 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/config/validate.ts +4 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/content-tracking/report-state/batch.ts +19 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/content-tracking/report-state.ts +15 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/ctas.ts +5 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/entities.ts +23 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/media/optimize.ts +5 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/media/upload.ts +30 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/migrations/[id]/resume.ts +28 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/migrations/[id]/validate.ts +6 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/migrations/[id].ts +7 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/migrations/import.ts +458 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/migrations/media.ts +5 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/operations.ts +189 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/playbooks/[id]/run.ts +55 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/posts/[id]/approve.ts +13 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/posts/[id]/preview.ts +9 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/posts/[id]/publish.ts +13 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/posts/[id].ts +18 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/posts/index.ts +28 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/providers.ts +6 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/history.ts +3 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/latest.ts +3 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/posts.ts +3 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/project-context.ts +4 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/sync-updates.ts +43 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/sync.ts +172 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/pull/updates.ts +3 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/redirects/check.ts +5 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/redirects.ts +32 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/reports/deliver-due.ts +12 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/reports/deliver.ts +6 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/reports/export.ts +17 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/reports.ts +31 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/rewrite/history/[contentId].ts +3 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/rewrite/policy.ts +4 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/rewrite/queue.ts +4 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/seo/audit.ts +7 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/seo/insights.ts +5 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/settings/site.ts +7 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/settings.ts +19 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap/auto-detect.ts +13 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap/diff.ts +11 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap/match.ts +16 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap/matching-history.ts +4 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap/runs.ts +4 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/sitemap.ts +11 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/summary.ts +12 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/topics/[id]/faq.ts +12 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/topics.ts +11 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/capabilities.ts +5 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/migration-plan.ts +21 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/migrations/[id]/resume.ts +5 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/migrations/[id]/validate.ts +6 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/migrations/[id].ts +13 -0
- package/bundled-templates/astro-blog/starter/src/pages/api/ops/wordpress/migrations.ts +147 -0
- package/bundled-templates/astro-blog/starter/src/pages/authors/[slug].astro +12 -0
- package/bundled-templates/astro-blog/starter/src/pages/blog/[slug].astro +25 -0
- package/bundled-templates/astro-blog/starter/src/pages/blog/index.astro +32 -0
- package/bundled-templates/astro-blog/starter/src/pages/blog/page/[page].astro +21 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/index.astro +20 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/operations.astro +33 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/posts/[id]/edit.astro +17 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/posts/[id]/preview.astro +11 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/posts/index.astro +15 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/posts/new.astro +12 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/reports.astro +12 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/rewrite.astro +13 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/settings/integrations.astro +13 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/settings/site.astro +11 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/sitemap.astro +15 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/topics.astro +13 -0
- package/bundled-templates/astro-blog/starter/src/pages/ops/wordpress.astro +34 -0
- package/bundled-templates/astro-blog/starter/src/pages/review/[token].astro +13 -0
- package/bundled-templates/astro-blog/starter/src/pages/robots.txt.ts +4 -0
- package/bundled-templates/astro-blog/starter/src/pages/sitemap.xml.ts +12 -0
- package/bundled-templates/astro-blog/starter/src/pages/topics/[slug]/page/[page].astro +10 -0
- package/bundled-templates/astro-blog/starter/src/pages/topics/[slug].astro +19 -0
- package/bundled-templates/astro-blog/starter/src/pages/topics/index.astro +10 -0
- package/bundled-templates/astro-blog/starter/src/styles/global.css +4 -0
- package/bundled-templates/astro-blog/starter/tsconfig.json +9 -0
- package/bundled-templates/astro-blog/starter/wrangler.jsonc +52 -0
- package/bundled-tools/clis/maggie_clone.py +42 -15
- package/bundled-tools/clis/maggie_dash.py +41 -25
- package/bundled-tools/clis/maggie_design.py +2 -0
- package/bundled-tools/clis/maggie_design_system.py +7 -1
- package/bundled-tools/clis/site_audit.py +25 -2
- package/bundled-tools/runtime/maggie_memory.py +6 -1
- package/package.json +1 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Maggie Astro Blog Starter
|
|
2
|
+
|
|
3
|
+
This directory is a runnable reference scaffold for the [Astro Blog Template
|
|
4
|
+
Contract](../README.md). It provides public routes, a stable SQLite schema and
|
|
5
|
+
a server-only Maggie Ops API. The UI can be redesigned without changing the
|
|
6
|
+
content, publishing or SEO contracts.
|
|
7
|
+
|
|
8
|
+
## Run it
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
cp .env.example .env
|
|
12
|
+
# Set PUBLIC_SITE_URL to the real absolute origin before building.
|
|
13
|
+
npm install
|
|
14
|
+
npm run db:seed
|
|
15
|
+
npm run dev
|
|
16
|
+
npm run build
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The default example uses SQLite. The schema is created on first server request at
|
|
20
|
+
`BLOG_DB_PATH`. Demo posts are development-only; production with no configured
|
|
21
|
+
adapter renders an empty, non-indexable content surface rather than publishing
|
|
22
|
+
fixture posts. For local-only Ops testing set `OPS_AUTH_PROVIDER=local` and a
|
|
23
|
+
long random `OPS_LOCAL_TOKEN`. Production deployments must replace the local
|
|
24
|
+
token with a server-side OIDC/session adapter.
|
|
25
|
+
|
|
26
|
+
The blog index defaults to 9 posts per page in a 3-column (3×3) grid. Change
|
|
27
|
+
`PUBLIC_POSTS_PER_PAGE` or `PUBLIC_GRID_COLUMNS` only through reviewed site
|
|
28
|
+
configuration so pagination and SEO checks continue to use the same values.
|
|
29
|
+
|
|
30
|
+
`PUBLIC_SITE_URL` is required and must be an absolute URL; the starter does not
|
|
31
|
+
fall back to `example.com`. Provider reports use `GSC_SITE_URL` + `GSC_ACCESS_TOKEN` for visibility and
|
|
32
|
+
`GA4_PROPERTY_ID` + `GA4_ACCESS_TOKEN` for analytics. Tokens are never returned
|
|
33
|
+
by the settings API. Technical SEO and content-quality reports can run locally;
|
|
34
|
+
provider reports persist an explicit error when credentials are unavailable.
|
|
35
|
+
|
|
36
|
+
To load the deterministic example records into SQLite, run `npm run db:seed`.
|
|
37
|
+
For a browser session in local mode, set the `maggie_ops_token` cookie to the
|
|
38
|
+
same value as `OPS_LOCAL_TOKEN`; API clients may instead send a Bearer token.
|
|
39
|
+
|
|
40
|
+
## Routes
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
/blog
|
|
44
|
+
/blog/[slug]
|
|
45
|
+
/blog/page/[page]
|
|
46
|
+
/about
|
|
47
|
+
/topics
|
|
48
|
+
/topics/[slug]
|
|
49
|
+
/authors/[slug]
|
|
50
|
+
/sitemap.xml
|
|
51
|
+
/robots.txt
|
|
52
|
+
/ops/* authenticated Maggie Ops screens
|
|
53
|
+
/api/ops/* authenticated content, sitemap, report and settings API
|
|
54
|
+
/api/approval/[token] expiring client review link API
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The public pages use the same publication predicate, URL helper, metadata
|
|
58
|
+
builder, and JSON-LD shape that a production adapter must preserve. The
|
|
59
|
+
server-only Ops API currently exposes summary, author/post CRUD, guarded
|
|
60
|
+
publish, topics/FAQs/CTAs, sitemap sources/runs/matching/history, reports,
|
|
61
|
+
settings, API Pull project-context/posts/updates/history/latest sync, rewrite
|
|
62
|
+
queue/history/policy, content-tracking state reporting, and server-side AI
|
|
63
|
+
CMO/GSC/GA4 provider seams.
|
|
64
|
+
|
|
65
|
+
`/ops/wordpress` provides a capability, mapping-plan, preview/apply/resume migration flow for the
|
|
66
|
+
WordPress REST resources `/wp/v2/posts`, `/pages`, `/categories`, `/tags`, and
|
|
67
|
+
`/media`. Selected post types, category/topic mappings, author mappings, SEO
|
|
68
|
+
normalization, source modified timestamps, and conflict policy are persisted in
|
|
69
|
+
the migration options.
|
|
70
|
+
It preserves WordPress source IDs, stores categories as draft topics, stores
|
|
71
|
+
tags as migration terms, registers media URLs, and records redirects when a
|
|
72
|
+
source URL differs from the destination slug. Configure WordPress API access
|
|
73
|
+
with `WORDPRESS_API_TOKEN` or a server-only application-password pair.
|
|
74
|
+
|
|
75
|
+
## Replace points
|
|
76
|
+
|
|
77
|
+
| File | Replace with |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `src/data/demo.ts` | demo-only fallback content |
|
|
80
|
+
| `src/lib/repository.ts` | canonical publication queries and adapter mapping |
|
|
81
|
+
| `src/lib/seo.ts` | validated metadata/JSON-LD builder if fields expand |
|
|
82
|
+
| `db/schema.sql` | selected database migration, preserving constraints |
|
|
83
|
+
| `src/middleware.ts` | real session and role authorization |
|
|
84
|
+
| `src/lib/db.ts` | Postgres adapter or migration runner when SQLite is not selected |
|
|
85
|
+
| `src/pages/ops/*` | authenticated Maggie Ops screens calling the API |
|
|
86
|
+
| `src/pages/sitemap.xml.ts` | repository-backed published URL query |
|
|
87
|
+
|
|
88
|
+
The starter includes a Tailwind v4 Vite entry point for projects that choose
|
|
89
|
+
Tailwind during bootstrap. It remains compatible with the existing contract
|
|
90
|
+
surface and does not require replacing the host's design system.
|
|
91
|
+
|
|
92
|
+
`npm run maggie:*` uses the installed `maggie` binary. It is portable after the
|
|
93
|
+
starter is copied out of this monorepo; it no longer depends on repository-
|
|
94
|
+
relative Python paths.
|
|
95
|
+
|
|
96
|
+
Do not expose `AI_CMO_API_KEY`, GSC verification tokens, GA4 credentials, or
|
|
97
|
+
database credentials to browser code. API Pull sync, sitemap matching,
|
|
98
|
+
rewrite queueing and report-state remain server/CLI operations; connect them to
|
|
99
|
+
the authenticated Ops actions rather than accepting secrets from the browser.
|
|
100
|
+
|
|
101
|
+
## Operations contract
|
|
102
|
+
|
|
103
|
+
`/api/ops/operations` exposes durable calendar, revision, bulk preview/apply,
|
|
104
|
+
redirect, task, media and agency workspace operations. Approved posts can be
|
|
105
|
+
published by schedule with `POST /api/ops/calendar/publish-due`. Client review
|
|
106
|
+
links are created by the authenticated API and expose only a hashed token.
|
|
107
|
+
|
|
108
|
+
Agency clients should use `/api/ops/agency?workspaceId=...` for scoped reads and
|
|
109
|
+
`POST /api/ops/agency` for client, site, project, template, and member changes.
|
|
110
|
+
Cross-workspace relations are rejected and provider credentials are accepted only
|
|
111
|
+
as server-side references.
|
|
112
|
+
|
|
113
|
+
For bulk changes, use `POST /api/ops/bulk`: first send `filter`, `changes`,
|
|
114
|
+
and preview mode, then apply the returned `previewId`. Apply rejects stale
|
|
115
|
+
previews, records a revision for every affected post, and supports taxonomy,
|
|
116
|
+
metadata, publish/archive, regeneration, and rewrite-queue changes.
|
|
117
|
+
|
|
118
|
+
Generic migration is available at `POST /api/ops/migrations/import` with
|
|
119
|
+
`format` set to `json`, `csv`, `wxr`, `sitemap`, or `media-archive`, plus
|
|
120
|
+
`mode: preview|apply`. `syncMode` supports `all`, `new`, and `modified`, while
|
|
121
|
+
`detectDeleted` archives records missing from a full source snapshot. Applied
|
|
122
|
+
records use source identity and SHA-256 checksums for safe reruns; conflicts
|
|
123
|
+
support source-wins, target-wins, or manual review.
|
|
124
|
+
|
|
125
|
+
After explicit bootstrap confirmation, the CLI writes `.maggie/project.json`,
|
|
126
|
+
`decisions.json`, `schema.json`, `routes.json`, `integrations.json`, and
|
|
127
|
+
`migration.json`. Use `maggie doctor --strict --require-bootstrap`,
|
|
128
|
+
`maggie generate fixture`, `npm run schema:check`, and `npm run fixture:seed`
|
|
129
|
+
as local or CI gates. `npm run maggie:ci` is the strict CI entry point after
|
|
130
|
+
bootstrap has been completed.
|
|
131
|
+
|
|
132
|
+
API Pull lifecycle routes are server-only: use `/api/ops/pull/project-context`
|
|
133
|
+
for sanitized project and CTA context, `/api/ops/pull/sync` for idempotent
|
|
134
|
+
local storage, `/api/ops/pull/sync-updates` for rewrite/update delivery,
|
|
135
|
+
`/api/ops/sitemap/matching-history` for readback, and
|
|
136
|
+
`/api/ops/content-tracking/report-state` (or its bounded batch variant) only
|
|
137
|
+
after local publication state is known. API keys are never accepted in request
|
|
138
|
+
bodies.
|
|
139
|
+
|
|
140
|
+
Run `POST /api/ops/seo/audit` to persist findings for canonical conflicts,
|
|
141
|
+
redirect chains, orphan pages, pagination requirements, duplicate titles, thin
|
|
142
|
+
content, missing authors/alt text, invalid JSON-LD, image performance, hreflang,
|
|
143
|
+
FAQ quality, answer-first structure, entity completeness, metadata and
|
|
144
|
+
publication-date integrity. Playbooks and runs are stored in
|
|
145
|
+
`playbooks` and `playbook_runs` so agencies can attach repeatable migration,
|
|
146
|
+
launch, refresh and audit workflows to a site.
|
|
147
|
+
Run and advance a playbook through `/api/ops/playbooks/:id/run`; steps are
|
|
148
|
+
confirmation-gated and completion requires declared validation gates.
|
|
149
|
+
|
|
150
|
+
`/api/ops/seo/insights` exposes internal-link suggestions, topic coverage,
|
|
151
|
+
FAQ quality, answer-first checks, refresh recommendations, entity profiles,
|
|
152
|
+
entity graph relations, and crawler policy.
|
|
153
|
+
`/api/ops/sitemap/diff` stores URL snapshots and returns added/removed URLs;
|
|
154
|
+
`/api/ops/redirects?format=csv` exports redirect mappings; POSTing either JSON
|
|
155
|
+
`{"items": [...]}` or `text/csv` imports them. `/api/ops/redirects/check` checks
|
|
156
|
+
known targets.
|
|
157
|
+
|
|
158
|
+
Report deliveries accept `daily`, `weekly`, `monthly`, or an ISO timestamp.
|
|
159
|
+
Configure a platform cron to call `POST /api/ops/reports/deliver-due`; the
|
|
160
|
+
default server-only email adapter is Resend (`EMAIL_PROVIDER=resend`). A
|
|
161
|
+
Webhook adapter remains available for self-hosted mail gateways, and delivery
|
|
162
|
+
state is persisted regardless of provider.
|
|
163
|
+
|
|
164
|
+
Media uploads use the same provider boundary. The default is Cloudinary
|
|
165
|
+
(`MEDIA_STORAGE_PROVIDER=cloudinary`) with signed server-side uploads; set it
|
|
166
|
+
to `local` for development. Custom adapters can be registered in
|
|
167
|
+
`src/lib/providers/` against the stable provider interface. `GET /api/ops/providers`
|
|
168
|
+
returns capability and configuration status without returning credentials.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from "astro/config";
|
|
2
|
+
import { loadEnv } from "vite";
|
|
3
|
+
import node from "@astrojs/node";
|
|
4
|
+
import cloudflare from "@astrojs/cloudflare";
|
|
5
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
6
|
+
|
|
7
|
+
const env = loadEnv(process.env.NODE_ENV || "development", process.cwd(), "");
|
|
8
|
+
const site = env.PUBLIC_SITE_URL?.trim();
|
|
9
|
+
if (!site) throw new Error("PUBLIC_SITE_URL is required; copy .env.example and configure the canonical public site URL");
|
|
10
|
+
try { new URL(site); } catch { throw new Error("PUBLIC_SITE_URL must be an absolute URL"); }
|
|
11
|
+
const deploymentProvider = env.MAGGIE_DEPLOY_PROVIDER || "node";
|
|
12
|
+
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
output: "server",
|
|
15
|
+
adapter: deploymentProvider === "cloudflare" ? cloudflare() : node({ mode: "standalone" }),
|
|
16
|
+
site,
|
|
17
|
+
vite: { plugins: [tailwindcss()] },
|
|
18
|
+
security: { checkOrigin: true },
|
|
19
|
+
});
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
PRAGMA foreign_keys = ON;
|
|
2
|
+
|
|
3
|
+
CREATE TABLE IF NOT EXISTS authors (
|
|
4
|
+
id TEXT PRIMARY KEY,
|
|
5
|
+
slug TEXT NOT NULL UNIQUE,
|
|
6
|
+
name TEXT NOT NULL,
|
|
7
|
+
job_title TEXT,
|
|
8
|
+
organisation TEXT,
|
|
9
|
+
bio TEXT,
|
|
10
|
+
profile_url TEXT,
|
|
11
|
+
credentials_json TEXT NOT NULL DEFAULT '[]',
|
|
12
|
+
created_at TEXT NOT NULL,
|
|
13
|
+
updated_at TEXT NOT NULL
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE TABLE IF NOT EXISTS posts (
|
|
17
|
+
id TEXT PRIMARY KEY,
|
|
18
|
+
source TEXT NOT NULL CHECK (source IN ('local', 'api-pull', 'cms', 'manual')),
|
|
19
|
+
source_id TEXT,
|
|
20
|
+
slug TEXT NOT NULL UNIQUE,
|
|
21
|
+
title TEXT NOT NULL,
|
|
22
|
+
excerpt TEXT NOT NULL DEFAULT '',
|
|
23
|
+
content_markdown TEXT,
|
|
24
|
+
content_html TEXT,
|
|
25
|
+
canonical_url TEXT NOT NULL UNIQUE,
|
|
26
|
+
cover_image_url TEXT,
|
|
27
|
+
cover_image_alt TEXT NOT NULL DEFAULT '',
|
|
28
|
+
author_id TEXT NOT NULL REFERENCES authors(id),
|
|
29
|
+
language TEXT NOT NULL DEFAULT 'en',
|
|
30
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'review', 'approved', 'scheduled', 'published', 'archived')),
|
|
31
|
+
published_at TEXT,
|
|
32
|
+
updated_at TEXT,
|
|
33
|
+
created_at TEXT NOT NULL,
|
|
34
|
+
content_hash TEXT,
|
|
35
|
+
seo_title TEXT,
|
|
36
|
+
meta_description TEXT,
|
|
37
|
+
focus_keyword TEXT,
|
|
38
|
+
canonical_override TEXT,
|
|
39
|
+
robots_index TEXT NOT NULL DEFAULT 'index',
|
|
40
|
+
robots_follow TEXT NOT NULL DEFAULT 'follow',
|
|
41
|
+
og_image_url TEXT,
|
|
42
|
+
schema_type TEXT NOT NULL DEFAULT 'Article',
|
|
43
|
+
custom_metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
44
|
+
scheduled_at TEXT,
|
|
45
|
+
checksum TEXT,
|
|
46
|
+
source_modified_at TEXT,
|
|
47
|
+
CHECK (status <> 'published' OR published_at IS NOT NULL)
|
|
48
|
+
);
|
|
49
|
+
CREATE UNIQUE INDEX IF NOT EXISTS posts_source_identity ON posts (source, source_id) WHERE source_id IS NOT NULL;
|
|
50
|
+
CREATE INDEX IF NOT EXISTS posts_public_order ON posts (status, published_at DESC, updated_at DESC);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE IF NOT EXISTS topics (
|
|
53
|
+
id TEXT PRIMARY KEY,
|
|
54
|
+
slug TEXT NOT NULL UNIQUE,
|
|
55
|
+
title TEXT NOT NULL,
|
|
56
|
+
description TEXT NOT NULL,
|
|
57
|
+
seo_title TEXT,
|
|
58
|
+
seo_description TEXT,
|
|
59
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived')),
|
|
60
|
+
minimum_published_posts INTEGER NOT NULL DEFAULT 3,
|
|
61
|
+
created_at TEXT NOT NULL,
|
|
62
|
+
updated_at TEXT NOT NULL
|
|
63
|
+
);
|
|
64
|
+
CREATE TABLE IF NOT EXISTS post_topics (
|
|
65
|
+
post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
|
66
|
+
topic_id TEXT NOT NULL REFERENCES topics(id) ON DELETE CASCADE,
|
|
67
|
+
PRIMARY KEY (post_id, topic_id)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
CREATE TABLE IF NOT EXISTS faq_items (
|
|
71
|
+
id TEXT PRIMARY KEY,
|
|
72
|
+
question TEXT NOT NULL,
|
|
73
|
+
answer_markdown TEXT NOT NULL,
|
|
74
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
75
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published')),
|
|
76
|
+
created_at TEXT NOT NULL,
|
|
77
|
+
updated_at TEXT NOT NULL
|
|
78
|
+
);
|
|
79
|
+
CREATE TABLE IF NOT EXISTS post_faqs (
|
|
80
|
+
post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
|
81
|
+
faq_id TEXT NOT NULL REFERENCES faq_items(id) ON DELETE CASCADE,
|
|
82
|
+
PRIMARY KEY (post_id, faq_id)
|
|
83
|
+
);
|
|
84
|
+
CREATE TABLE IF NOT EXISTS topic_faqs (
|
|
85
|
+
topic_id TEXT NOT NULL REFERENCES topics(id) ON DELETE CASCADE,
|
|
86
|
+
faq_id TEXT NOT NULL REFERENCES faq_items(id) ON DELETE CASCADE,
|
|
87
|
+
PRIMARY KEY (topic_id, faq_id)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
CREATE TABLE IF NOT EXISTS ctas (
|
|
91
|
+
id TEXT PRIMARY KEY,
|
|
92
|
+
label TEXT NOT NULL,
|
|
93
|
+
url TEXT NOT NULL,
|
|
94
|
+
placement TEXT NOT NULL,
|
|
95
|
+
tracking_key TEXT,
|
|
96
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'active', 'archived')),
|
|
97
|
+
created_at TEXT NOT NULL,
|
|
98
|
+
updated_at TEXT NOT NULL
|
|
99
|
+
);
|
|
100
|
+
CREATE TABLE IF NOT EXISTS post_ctas (
|
|
101
|
+
post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
|
102
|
+
cta_id TEXT NOT NULL REFERENCES ctas(id) ON DELETE CASCADE,
|
|
103
|
+
PRIMARY KEY (post_id, cta_id)
|
|
104
|
+
);
|
|
105
|
+
CREATE TABLE IF NOT EXISTS topic_ctas (
|
|
106
|
+
topic_id TEXT NOT NULL REFERENCES topics(id) ON DELETE CASCADE,
|
|
107
|
+
cta_id TEXT NOT NULL REFERENCES ctas(id) ON DELETE CASCADE,
|
|
108
|
+
PRIMARY KEY (topic_id, cta_id)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
CREATE TABLE IF NOT EXISTS content_sync_state (
|
|
112
|
+
source TEXT PRIMARY KEY,
|
|
113
|
+
cursor TEXT,
|
|
114
|
+
etag TEXT,
|
|
115
|
+
last_success_at TEXT,
|
|
116
|
+
last_error TEXT,
|
|
117
|
+
updated_at TEXT NOT NULL
|
|
118
|
+
);
|
|
119
|
+
CREATE TABLE IF NOT EXISTS content_delivery_state (
|
|
120
|
+
post_id TEXT PRIMARY KEY REFERENCES posts(id) ON DELETE CASCADE,
|
|
121
|
+
remote_content_id TEXT,
|
|
122
|
+
remote_version TEXT,
|
|
123
|
+
delivery_status TEXT NOT NULL DEFAULT 'not_delivered',
|
|
124
|
+
last_reported_url TEXT,
|
|
125
|
+
last_reported_hash TEXT,
|
|
126
|
+
last_error TEXT,
|
|
127
|
+
updated_at TEXT NOT NULL
|
|
128
|
+
);
|
|
129
|
+
CREATE TABLE IF NOT EXISTS sitemap_sources (
|
|
130
|
+
id TEXT PRIMARY KEY,
|
|
131
|
+
url TEXT NOT NULL UNIQUE,
|
|
132
|
+
source_type TEXT NOT NULL CHECK (source_type IN ('manual', 'auto_detected')),
|
|
133
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled')),
|
|
134
|
+
created_at TEXT NOT NULL,
|
|
135
|
+
updated_at TEXT NOT NULL
|
|
136
|
+
);
|
|
137
|
+
CREATE TABLE IF NOT EXISTS sitemap_runs (
|
|
138
|
+
id TEXT PRIMARY KEY,
|
|
139
|
+
source_id TEXT REFERENCES sitemap_sources(id),
|
|
140
|
+
status TEXT NOT NULL CHECK (status IN ('queued', 'running', 'completed', 'failed')),
|
|
141
|
+
total_urls INTEGER NOT NULL DEFAULT 0,
|
|
142
|
+
matched_posts INTEGER NOT NULL DEFAULT 0,
|
|
143
|
+
eligible_posts INTEGER NOT NULL DEFAULT 0,
|
|
144
|
+
unmatched_urls INTEGER NOT NULL DEFAULT 0,
|
|
145
|
+
error TEXT,
|
|
146
|
+
started_at TEXT NOT NULL,
|
|
147
|
+
completed_at TEXT
|
|
148
|
+
);
|
|
149
|
+
CREATE TABLE IF NOT EXISTS blog_reports (
|
|
150
|
+
id TEXT PRIMARY KEY,
|
|
151
|
+
run_type TEXT NOT NULL,
|
|
152
|
+
status TEXT NOT NULL CHECK (status IN ('queued', 'running', 'completed', 'failed')),
|
|
153
|
+
summary_json TEXT NOT NULL DEFAULT '{}',
|
|
154
|
+
findings_json TEXT NOT NULL DEFAULT '[]',
|
|
155
|
+
started_at TEXT,
|
|
156
|
+
completed_at TEXT,
|
|
157
|
+
error TEXT
|
|
158
|
+
);
|
|
159
|
+
CREATE TABLE IF NOT EXISTS blog_settings (
|
|
160
|
+
key TEXT PRIMARY KEY,
|
|
161
|
+
value_json TEXT NOT NULL,
|
|
162
|
+
updated_at TEXT NOT NULL
|
|
163
|
+
);
|
|
164
|
+
CREATE TABLE IF NOT EXISTS audit_events (
|
|
165
|
+
id TEXT PRIMARY KEY,
|
|
166
|
+
actor_id TEXT NOT NULL,
|
|
167
|
+
action TEXT NOT NULL,
|
|
168
|
+
entity_type TEXT NOT NULL,
|
|
169
|
+
entity_id TEXT NOT NULL,
|
|
170
|
+
previous_state TEXT,
|
|
171
|
+
next_state TEXT,
|
|
172
|
+
reason TEXT,
|
|
173
|
+
correlation_id TEXT NOT NULL,
|
|
174
|
+
created_at TEXT NOT NULL
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
CREATE TABLE IF NOT EXISTS wp_migrations (
|
|
178
|
+
id TEXT PRIMARY KEY,
|
|
179
|
+
source_url TEXT NOT NULL,
|
|
180
|
+
mode TEXT NOT NULL CHECK (mode IN ('preview', 'apply')),
|
|
181
|
+
status TEXT NOT NULL CHECK (status IN ('queued', 'running', 'completed', 'failed', 'cancelled')),
|
|
182
|
+
options_json TEXT NOT NULL DEFAULT '{}',
|
|
183
|
+
counts_json TEXT NOT NULL DEFAULT '{}',
|
|
184
|
+
error TEXT,
|
|
185
|
+
started_at TEXT NOT NULL,
|
|
186
|
+
completed_at TEXT,
|
|
187
|
+
checkpoint_json TEXT NOT NULL DEFAULT '{}',
|
|
188
|
+
source_checksum TEXT,
|
|
189
|
+
deleted_count INTEGER NOT NULL DEFAULT 0,
|
|
190
|
+
conflict_count INTEGER NOT NULL DEFAULT 0
|
|
191
|
+
);
|
|
192
|
+
CREATE TABLE IF NOT EXISTS wp_import_items (
|
|
193
|
+
id TEXT PRIMARY KEY,
|
|
194
|
+
migration_id TEXT NOT NULL REFERENCES wp_migrations(id) ON DELETE CASCADE,
|
|
195
|
+
entity_type TEXT NOT NULL CHECK (entity_type IN ('post', 'category', 'tag', 'author', 'media')),
|
|
196
|
+
source_id TEXT NOT NULL,
|
|
197
|
+
target_id TEXT,
|
|
198
|
+
action TEXT NOT NULL CHECK (action IN ('created', 'updated', 'skipped', 'failed')),
|
|
199
|
+
error TEXT,
|
|
200
|
+
created_at TEXT NOT NULL,
|
|
201
|
+
UNIQUE (migration_id, entity_type, source_id)
|
|
202
|
+
);
|
|
203
|
+
CREATE TABLE IF NOT EXISTS wp_terms (
|
|
204
|
+
id TEXT PRIMARY KEY,
|
|
205
|
+
taxonomy TEXT NOT NULL CHECK (taxonomy IN ('category', 'tag')),
|
|
206
|
+
source_id TEXT NOT NULL,
|
|
207
|
+
name TEXT NOT NULL,
|
|
208
|
+
slug TEXT NOT NULL,
|
|
209
|
+
description TEXT NOT NULL DEFAULT '',
|
|
210
|
+
topic_id TEXT REFERENCES topics(id),
|
|
211
|
+
created_at TEXT NOT NULL,
|
|
212
|
+
updated_at TEXT NOT NULL,
|
|
213
|
+
UNIQUE (taxonomy, source_id)
|
|
214
|
+
);
|
|
215
|
+
CREATE TABLE IF NOT EXISTS post_wp_terms (
|
|
216
|
+
post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
|
217
|
+
term_id TEXT NOT NULL REFERENCES wp_terms(id) ON DELETE CASCADE,
|
|
218
|
+
PRIMARY KEY (post_id, term_id)
|
|
219
|
+
);
|
|
220
|
+
CREATE TABLE IF NOT EXISTS media_assets (
|
|
221
|
+
id TEXT PRIMARY KEY,
|
|
222
|
+
source TEXT NOT NULL,
|
|
223
|
+
source_id TEXT NOT NULL,
|
|
224
|
+
source_url TEXT NOT NULL,
|
|
225
|
+
local_url TEXT,
|
|
226
|
+
mime_type TEXT,
|
|
227
|
+
alt_text TEXT NOT NULL DEFAULT '',
|
|
228
|
+
caption TEXT NOT NULL DEFAULT '',
|
|
229
|
+
width INTEGER,
|
|
230
|
+
height INTEGER,
|
|
231
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
232
|
+
optimized_formats_json TEXT NOT NULL DEFAULT '[]',
|
|
233
|
+
optimization_status TEXT NOT NULL DEFAULT 'pending',
|
|
234
|
+
storage_provider TEXT NOT NULL DEFAULT 'local',
|
|
235
|
+
storage_key TEXT,
|
|
236
|
+
delivery_url TEXT,
|
|
237
|
+
upload_status TEXT NOT NULL DEFAULT 'pending',
|
|
238
|
+
upload_error TEXT,
|
|
239
|
+
created_at TEXT NOT NULL,
|
|
240
|
+
updated_at TEXT NOT NULL,
|
|
241
|
+
UNIQUE (source, source_id)
|
|
242
|
+
);
|
|
243
|
+
CREATE TABLE IF NOT EXISTS post_media (
|
|
244
|
+
post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
|
245
|
+
media_id TEXT NOT NULL REFERENCES media_assets(id) ON DELETE CASCADE,
|
|
246
|
+
role TEXT NOT NULL CHECK (role IN ('featured', 'inline', 'gallery')),
|
|
247
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
248
|
+
PRIMARY KEY (post_id, media_id, role)
|
|
249
|
+
);
|
|
250
|
+
CREATE TABLE IF NOT EXISTS redirects (
|
|
251
|
+
id TEXT PRIMARY KEY,
|
|
252
|
+
source_path TEXT NOT NULL UNIQUE,
|
|
253
|
+
target_path TEXT NOT NULL,
|
|
254
|
+
status_code INTEGER NOT NULL DEFAULT 301 CHECK (status_code IN (301, 308)),
|
|
255
|
+
reason TEXT NOT NULL,
|
|
256
|
+
created_at TEXT NOT NULL
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
CREATE TABLE IF NOT EXISTS post_revisions (
|
|
260
|
+
id TEXT PRIMARY KEY,
|
|
261
|
+
post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
|
262
|
+
version INTEGER NOT NULL,
|
|
263
|
+
snapshot_json TEXT NOT NULL,
|
|
264
|
+
actor_id TEXT NOT NULL,
|
|
265
|
+
reason TEXT,
|
|
266
|
+
created_at TEXT NOT NULL,
|
|
267
|
+
UNIQUE(post_id, version)
|
|
268
|
+
);
|
|
269
|
+
CREATE TABLE IF NOT EXISTS content_calendar (
|
|
270
|
+
id TEXT PRIMARY KEY,
|
|
271
|
+
post_id TEXT NOT NULL UNIQUE REFERENCES posts(id) ON DELETE CASCADE,
|
|
272
|
+
scheduled_at TEXT NOT NULL,
|
|
273
|
+
campaign TEXT,
|
|
274
|
+
notes TEXT,
|
|
275
|
+
reminder_at TEXT,
|
|
276
|
+
created_at TEXT NOT NULL,
|
|
277
|
+
updated_at TEXT NOT NULL
|
|
278
|
+
);
|
|
279
|
+
CREATE TABLE IF NOT EXISTS bulk_operations (
|
|
280
|
+
id TEXT PRIMARY KEY,
|
|
281
|
+
operation TEXT NOT NULL,
|
|
282
|
+
filter_json TEXT NOT NULL,
|
|
283
|
+
changes_json TEXT NOT NULL,
|
|
284
|
+
preview_json TEXT NOT NULL DEFAULT '{}',
|
|
285
|
+
status TEXT NOT NULL CHECK(status IN ('preview','queued','running','completed','failed','cancelled')),
|
|
286
|
+
actor_id TEXT NOT NULL,
|
|
287
|
+
created_at TEXT NOT NULL,
|
|
288
|
+
completed_at TEXT,
|
|
289
|
+
error TEXT
|
|
290
|
+
);
|
|
291
|
+
CREATE TABLE IF NOT EXISTS media_folders (
|
|
292
|
+
id TEXT PRIMARY KEY,
|
|
293
|
+
name TEXT NOT NULL UNIQUE,
|
|
294
|
+
created_at TEXT NOT NULL
|
|
295
|
+
);
|
|
296
|
+
CREATE TABLE IF NOT EXISTS media_folder_items (
|
|
297
|
+
media_id TEXT NOT NULL REFERENCES media_assets(id) ON DELETE CASCADE,
|
|
298
|
+
folder_id TEXT NOT NULL REFERENCES media_folders(id) ON DELETE CASCADE,
|
|
299
|
+
PRIMARY KEY(media_id, folder_id)
|
|
300
|
+
);
|
|
301
|
+
CREATE TABLE IF NOT EXISTS migration_sources (
|
|
302
|
+
id TEXT PRIMARY KEY,
|
|
303
|
+
kind TEXT NOT NULL CHECK(kind IN ('wordpress-rest','wxr','csv','json','sitemap','media-archive')),
|
|
304
|
+
location TEXT NOT NULL,
|
|
305
|
+
options_json TEXT NOT NULL DEFAULT '{}',
|
|
306
|
+
checksum TEXT,
|
|
307
|
+
created_at TEXT NOT NULL,
|
|
308
|
+
updated_at TEXT NOT NULL
|
|
309
|
+
);
|
|
310
|
+
CREATE TABLE IF NOT EXISTS migration_conflicts (
|
|
311
|
+
id TEXT PRIMARY KEY,
|
|
312
|
+
migration_id TEXT NOT NULL REFERENCES wp_migrations(id) ON DELETE CASCADE,
|
|
313
|
+
source_id TEXT NOT NULL,
|
|
314
|
+
target_id TEXT,
|
|
315
|
+
resolution TEXT NOT NULL DEFAULT 'manual' CHECK(resolution IN ('source-wins','target-wins','manual')),
|
|
316
|
+
details_json TEXT NOT NULL DEFAULT '{}',
|
|
317
|
+
created_at TEXT NOT NULL,
|
|
318
|
+
resolved_at TEXT
|
|
319
|
+
);
|
|
320
|
+
CREATE TABLE IF NOT EXISTS workspaces (
|
|
321
|
+
id TEXT PRIMARY KEY,
|
|
322
|
+
name TEXT NOT NULL,
|
|
323
|
+
slug TEXT NOT NULL UNIQUE,
|
|
324
|
+
logo_url TEXT,
|
|
325
|
+
brand_colors_json TEXT NOT NULL DEFAULT '{}',
|
|
326
|
+
custom_domain TEXT,
|
|
327
|
+
created_at TEXT NOT NULL,
|
|
328
|
+
updated_at TEXT NOT NULL
|
|
329
|
+
);
|
|
330
|
+
CREATE TABLE IF NOT EXISTS clients (
|
|
331
|
+
id TEXT PRIMARY KEY,
|
|
332
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
333
|
+
name TEXT NOT NULL,
|
|
334
|
+
email TEXT,
|
|
335
|
+
created_at TEXT NOT NULL,
|
|
336
|
+
updated_at TEXT NOT NULL
|
|
337
|
+
);
|
|
338
|
+
CREATE TABLE IF NOT EXISTS sites (
|
|
339
|
+
id TEXT PRIMARY KEY,
|
|
340
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
341
|
+
client_id TEXT REFERENCES clients(id) ON DELETE SET NULL,
|
|
342
|
+
name TEXT NOT NULL,
|
|
343
|
+
domain TEXT NOT NULL,
|
|
344
|
+
settings_json TEXT NOT NULL DEFAULT '{}',
|
|
345
|
+
credentials_json TEXT NOT NULL DEFAULT '{}',
|
|
346
|
+
created_at TEXT NOT NULL,
|
|
347
|
+
updated_at TEXT NOT NULL,
|
|
348
|
+
UNIQUE(workspace_id, domain)
|
|
349
|
+
);
|
|
350
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
351
|
+
id TEXT PRIMARY KEY,
|
|
352
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
353
|
+
site_id TEXT REFERENCES sites(id) ON DELETE SET NULL,
|
|
354
|
+
name TEXT NOT NULL,
|
|
355
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','paused','archived')),
|
|
356
|
+
created_at TEXT NOT NULL,
|
|
357
|
+
updated_at TEXT NOT NULL
|
|
358
|
+
);
|
|
359
|
+
CREATE TABLE IF NOT EXISTS workspace_templates (
|
|
360
|
+
id TEXT PRIMARY KEY,
|
|
361
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
362
|
+
name TEXT NOT NULL,
|
|
363
|
+
template_type TEXT NOT NULL,
|
|
364
|
+
content_json TEXT NOT NULL DEFAULT '{}',
|
|
365
|
+
created_at TEXT NOT NULL,
|
|
366
|
+
updated_at TEXT NOT NULL,
|
|
367
|
+
UNIQUE(workspace_id,name)
|
|
368
|
+
);
|
|
369
|
+
CREATE TABLE IF NOT EXISTS workspace_members (
|
|
370
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
371
|
+
user_id TEXT NOT NULL,
|
|
372
|
+
role TEXT NOT NULL CHECK(role IN ('agency-owner','account-manager','seo-strategist','writer','editor','developer','client-reviewer','read-only-analyst')),
|
|
373
|
+
created_at TEXT NOT NULL,
|
|
374
|
+
PRIMARY KEY(workspace_id, user_id)
|
|
375
|
+
);
|
|
376
|
+
CREATE TABLE IF NOT EXISTS approval_links (
|
|
377
|
+
id TEXT PRIMARY KEY,
|
|
378
|
+
post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
|
379
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
380
|
+
expires_at TEXT NOT NULL,
|
|
381
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','approved','changes_requested','expired')),
|
|
382
|
+
reviewer_name TEXT,
|
|
383
|
+
reviewer_email TEXT,
|
|
384
|
+
comment TEXT,
|
|
385
|
+
approved_at TEXT,
|
|
386
|
+
created_at TEXT NOT NULL
|
|
387
|
+
);
|
|
388
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
389
|
+
id TEXT PRIMARY KEY,
|
|
390
|
+
title TEXT NOT NULL,
|
|
391
|
+
description TEXT NOT NULL DEFAULT '',
|
|
392
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','in_progress','blocked','done','cancelled')),
|
|
393
|
+
assignee_id TEXT,
|
|
394
|
+
due_at TEXT,
|
|
395
|
+
linked_type TEXT,
|
|
396
|
+
linked_id TEXT,
|
|
397
|
+
approval_status TEXT,
|
|
398
|
+
created_at TEXT NOT NULL,
|
|
399
|
+
updated_at TEXT NOT NULL
|
|
400
|
+
);
|
|
401
|
+
CREATE TABLE IF NOT EXISTS task_comments (
|
|
402
|
+
id TEXT PRIMARY KEY,
|
|
403
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
404
|
+
author_id TEXT NOT NULL,
|
|
405
|
+
body TEXT NOT NULL,
|
|
406
|
+
created_at TEXT NOT NULL
|
|
407
|
+
);
|
|
408
|
+
CREATE TABLE IF NOT EXISTS playbooks (
|
|
409
|
+
id TEXT PRIMARY KEY,
|
|
410
|
+
name TEXT NOT NULL UNIQUE,
|
|
411
|
+
description TEXT NOT NULL DEFAULT '',
|
|
412
|
+
steps_json TEXT NOT NULL,
|
|
413
|
+
gates_json TEXT NOT NULL DEFAULT '[]',
|
|
414
|
+
report_format TEXT NOT NULL DEFAULT 'json',
|
|
415
|
+
created_at TEXT NOT NULL,
|
|
416
|
+
updated_at TEXT NOT NULL
|
|
417
|
+
);
|
|
418
|
+
CREATE TABLE IF NOT EXISTS playbook_runs (
|
|
419
|
+
id TEXT PRIMARY KEY,
|
|
420
|
+
playbook_id TEXT NOT NULL REFERENCES playbooks(id),
|
|
421
|
+
site_id TEXT REFERENCES sites(id),
|
|
422
|
+
status TEXT NOT NULL DEFAULT 'queued' CHECK(status IN ('queued','running','completed','failed')),
|
|
423
|
+
state_json TEXT NOT NULL DEFAULT '{}',
|
|
424
|
+
started_at TEXT,
|
|
425
|
+
completed_at TEXT,
|
|
426
|
+
error TEXT
|
|
427
|
+
);
|
|
428
|
+
CREATE TABLE IF NOT EXISTS seo_findings (
|
|
429
|
+
id TEXT PRIMARY KEY,
|
|
430
|
+
run_id TEXT NOT NULL REFERENCES blog_reports(id) ON DELETE CASCADE,
|
|
431
|
+
post_id TEXT REFERENCES posts(id) ON DELETE CASCADE,
|
|
432
|
+
category TEXT NOT NULL,
|
|
433
|
+
severity TEXT NOT NULL CHECK(severity IN ('info','warning','error')),
|
|
434
|
+
code TEXT NOT NULL,
|
|
435
|
+
message TEXT NOT NULL,
|
|
436
|
+
details_json TEXT NOT NULL DEFAULT '{}',
|
|
437
|
+
created_at TEXT NOT NULL
|
|
438
|
+
);
|
|
439
|
+
CREATE TABLE IF NOT EXISTS fact_review_reminders (
|
|
440
|
+
id TEXT PRIMARY KEY,
|
|
441
|
+
post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
|
442
|
+
due_at TEXT NOT NULL,
|
|
443
|
+
reason TEXT NOT NULL,
|
|
444
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','completed','snoozed')),
|
|
445
|
+
completed_at TEXT,
|
|
446
|
+
created_at TEXT NOT NULL,
|
|
447
|
+
updated_at TEXT NOT NULL,
|
|
448
|
+
UNIQUE(post_id,status)
|
|
449
|
+
);
|
|
450
|
+
CREATE TABLE IF NOT EXISTS rewrite_queue_items (
|
|
451
|
+
id TEXT PRIMARY KEY,
|
|
452
|
+
post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
|
453
|
+
reason TEXT NOT NULL DEFAULT 'manual',
|
|
454
|
+
status TEXT NOT NULL DEFAULT 'queued' CHECK(status IN ('queued','processing','completed','failed','cancelled')),
|
|
455
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
456
|
+
requested_by TEXT NOT NULL,
|
|
457
|
+
created_at TEXT NOT NULL,
|
|
458
|
+
updated_at TEXT NOT NULL,
|
|
459
|
+
UNIQUE(post_id, status)
|
|
460
|
+
);
|
|
461
|
+
CREATE TABLE IF NOT EXISTS not_found_logs (
|
|
462
|
+
path TEXT PRIMARY KEY,
|
|
463
|
+
hits INTEGER NOT NULL DEFAULT 1,
|
|
464
|
+
referrer TEXT,
|
|
465
|
+
first_seen_at TEXT NOT NULL,
|
|
466
|
+
last_seen_at TEXT NOT NULL
|
|
467
|
+
);
|
|
468
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
469
|
+
id TEXT PRIMARY KEY,
|
|
470
|
+
user_id TEXT,
|
|
471
|
+
type TEXT NOT NULL,
|
|
472
|
+
entity_type TEXT,
|
|
473
|
+
entity_id TEXT,
|
|
474
|
+
message TEXT NOT NULL,
|
|
475
|
+
read_at TEXT,
|
|
476
|
+
created_at TEXT NOT NULL
|
|
477
|
+
);
|
|
478
|
+
CREATE TABLE IF NOT EXISTS approval_comments (
|
|
479
|
+
id TEXT PRIMARY KEY,
|
|
480
|
+
approval_link_id TEXT NOT NULL REFERENCES approval_links(id) ON DELETE CASCADE,
|
|
481
|
+
paragraph_key TEXT,
|
|
482
|
+
body TEXT NOT NULL,
|
|
483
|
+
author_name TEXT,
|
|
484
|
+
created_at TEXT NOT NULL
|
|
485
|
+
);
|
|
486
|
+
CREATE TABLE IF NOT EXISTS report_deliveries (
|
|
487
|
+
id TEXT PRIMARY KEY,
|
|
488
|
+
workspace_id TEXT REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
489
|
+
report_type TEXT NOT NULL,
|
|
490
|
+
recipient TEXT NOT NULL,
|
|
491
|
+
format TEXT NOT NULL CHECK(format IN ('json','csv','html','pdf')),
|
|
492
|
+
schedule TEXT,
|
|
493
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','paused','failed')),
|
|
494
|
+
last_sent_at TEXT,
|
|
495
|
+
last_error TEXT,
|
|
496
|
+
created_at TEXT NOT NULL
|
|
497
|
+
);
|
|
498
|
+
CREATE TABLE IF NOT EXISTS content_citations (id TEXT PRIMARY KEY, post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE, source_url TEXT NOT NULL, source_title TEXT, verified_at TEXT, created_at TEXT NOT NULL);
|
|
499
|
+
CREATE TABLE IF NOT EXISTS query_page_map (id TEXT PRIMARY KEY, query TEXT NOT NULL, post_id TEXT REFERENCES posts(id) ON DELETE CASCADE, intent TEXT, priority INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, UNIQUE(query, post_id));
|
|
500
|
+
CREATE TABLE IF NOT EXISTS brand_mentions (id TEXT PRIMARY KEY, query TEXT NOT NULL, source_url TEXT NOT NULL, source_name TEXT, mentioned INTEGER NOT NULL DEFAULT 0, checked_at TEXT NOT NULL);
|
|
501
|
+
CREATE TABLE IF NOT EXISTS entity_profiles (id TEXT PRIMARY KEY, entity_type TEXT NOT NULL, entity_key TEXT NOT NULL UNIQUE, name TEXT NOT NULL, same_as_json TEXT NOT NULL DEFAULT '[]', schema_json TEXT NOT NULL DEFAULT '{}', updated_at TEXT NOT NULL);
|
|
502
|
+
CREATE TABLE IF NOT EXISTS entity_relations (subject_key TEXT NOT NULL REFERENCES entity_profiles(entity_key) ON DELETE CASCADE, predicate TEXT NOT NULL, object_key TEXT NOT NULL REFERENCES entity_profiles(entity_key) ON DELETE CASCADE, created_at TEXT NOT NULL, PRIMARY KEY(subject_key,predicate,object_key));
|
|
503
|
+
CREATE TABLE IF NOT EXISTS indexability_checks (id TEXT PRIMARY KEY, url TEXT NOT NULL, status TEXT NOT NULL, robots TEXT, canonical TEXT, checked_at TEXT NOT NULL, details_json TEXT NOT NULL DEFAULT '{}');
|
|
504
|
+
CREATE TABLE IF NOT EXISTS sitemap_snapshots (id TEXT PRIMARY KEY, source_url TEXT NOT NULL, urls_json TEXT NOT NULL, checksum TEXT NOT NULL, created_at TEXT NOT NULL);
|
|
505
|
+
CREATE TABLE IF NOT EXISTS hreflang_links (post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE, language TEXT NOT NULL, url TEXT NOT NULL, PRIMARY KEY(post_id, language));
|
|
506
|
+
CREATE TABLE IF NOT EXISTS core_web_vitals (id TEXT PRIMARY KEY, url TEXT NOT NULL, period_start TEXT NOT NULL, lcp REAL, inp REAL, cls REAL, source TEXT NOT NULL, created_at TEXT NOT NULL);
|
|
507
|
+
CREATE INDEX IF NOT EXISTS tasks_due_idx ON tasks(status, due_at);
|
|
508
|
+
CREATE INDEX IF NOT EXISTS post_revisions_idx ON post_revisions(post_id, version DESC);
|