logicsrc/supabase/migrations/20260608020000_blog_posts.sql
Anthony Ettinger cf99e93c53 feat(web): blog-post ingestion webhook + /blog, dynamic RSS & sitemap
Add an autoblog webhook receiver and a Supabase-backed blog to logicsrc-web
(the app had no Supabase usage before).

- Migration: blog_posts table (RLS: public reads published, service-role
  writes). Applied to the linked project.
- POST /api/webhooks/blog: verifies the Standard Webhooks signature against
  BLOG_WEBHOOK_SECRET via @profullstack/autoblog verifyAndParse (no admin
  user — shared secret only) and upserts the post by slug.
- /blog index + /blog/[slug] render published posts from the table.
- /blog/rss.xml and /sitemap.xml are now dynamic, generated from the table;
  removed the static public/sitemap.xml and public/blog/rss.xml.
- BLOG_WEBHOOK_SECRET added to .env.example.

Verified end-to-end: a signed sample post delivered 200 and appeared in the
index, post page, RSS, and sitemap; build + typecheck pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:53:43 +00:00

37 lines
1.4 KiB
SQL

-- Blog posts ingested via the autoblog webhook (/api/webhooks/blog).
-- Source of truth for /blog, /blog/[slug], /blog/rss.xml, and sitemap.xml.
-- Writes happen only through the service-role key (the webhook); the public
-- (anon) key can read published posts.
create table if not exists public.blog_posts (
id uuid primary key default gen_random_uuid(),
external_id text unique, -- autoblog Post.id (idempotency)
slug text not null unique,
title text not null,
excerpt text,
html text not null,
markdown text,
url text,
canonical_url text,
author jsonb,
tags text[] not null default '{}',
categories text[] not null default '{}',
featured_image jsonb,
status text not null default 'published',
published_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
created_at timestamptz not null default now()
);
create index if not exists blog_posts_published_idx
on public.blog_posts (published_at desc)
where status = 'published';
alter table public.blog_posts enable row level security;
-- Public can read published posts; everything else is service-role only
-- (service_role bypasses RLS, so no insert/update policy is needed).
drop policy if exists "blog_posts public read" on public.blog_posts;
create policy "blog_posts public read"
on public.blog_posts for select
using (status = 'published');