-- mockshot-seed.sql — reversible sample data at deployment scale, for -- screenshots and demos (first used for the README operator-overview shot). -- Every row carries a marker (oidc_subject/slug prefix 'mockshot', accounts -- metadata {"mockshot": true}, grants.description = 'mockshot', org_types/ -- products carrying a mockshot marker) so mockshot-nuke.sql can reverse it -- exactly. -- -- Run against a test stack (port/creds from bootstrap-stack.sh output): -- psql -h localhost -p $POSTGRES_PORT -U member_console -d member_console \ -- -v ON_ERROR_STOP=1 -f test/mockshot-seed.sql -- Reverse with mockshot-nuke.sql the same way. Nuke BEFORE restarting the -- app: the boot-time entitlement materializer must never reconcile the mock -- provisions into real pool entitlements. -- -- Shape follows the app's own mechanics: signup mints a personal org per -- member (backdated over ~19 months of joins) and issues a default-plan -- grant to it; team orgs are created by long-standing members; upgrading to -- a paid subscription ends the free default's delivery (ladder exclusion); -- subscriptions carry priced items so monthly recurring money is real. -- -- Targets (existing → after): People 5→2,847 (~148 joined in 30 days), -- Team orgs 0→38, Subscriptions 0→214 (205 active + 9 trialing, MRR -- $2,160.00), Open invoices 0→9 ($372.00 outstanding), Delivering operator -- grants →19 with ≈2,669 signup defaults in the caption, Catalog -- products 10→15. BEGIN; -- 2,842 users + persons, joined over ~19 months (i*5h + jitter) WITH new_users AS ( INSERT INTO core.users (oidc_subject, created_at) SELECT 'mockshot-sub-' || i, now() - (i * 5 || ' hours')::interval - (random() * interval '55 minutes') FROM generate_series(1, 2842) AS i RETURNING user_id, created_at, split_part(oidc_subject, '-', 3)::int AS i ) INSERT INTO core.persons (user_id, display_name, primary_email, primary_email_verified, created_at) SELECT user_id, -- Stride 7 is coprime to 20, so consecutive members vary in both names -- and (first, last) pairs stay unique across lcm(21,20)=420 indices. (ARRAY['Ada','Basil','Corin','Dara','Emil','Freya','Gus','Hana','Iris','Jonas','Kira', 'Lior','Mara','Nils','Odile','Piotr','Quinn','Rosa','Sana','Theo','Uma'])[1 + (i % 21)] || ' ' || (ARRAY['Aldana','Brook','Calder','Dove','Ellis','Fen','Grange','Hale','Ingram','Jarvis', 'Keel','Lark','Marsh','North','Orr','Penn','Quill','Reyes','Sorrel','Vale'])[1 + ((i * 7) % 20)], 'mock-p' || i || '@mockshot.test', true, created_at + interval '1 minute' FROM new_users; -- One personal org per mock person, named and timed the way signup mints -- them (two minutes after the person row). WITH owners AS ( SELECT person_id, display_name, created_at, row_number() OVER (ORDER BY person_id) AS rn FROM core.persons WHERE primary_email LIKE '%@mockshot.test' ) INSERT INTO core.organizations (name, slug, org_type, owner_person_id, created_at) SELECT display_name || '''s Organization', 'mockshot-' || rn, 'personal', person_id, created_at + interval '2 minutes' FROM owners; -- The team org type (design taxonomy: personal / team / enterprise — -- design/organization/architecture.md §2). Marker in the description. INSERT INTO core.org_types (org_type, display_name, description, is_active) VALUES ('team', 'Team', 'mockshot', true); -- 38 team orgs created by long-standing members (the oldest signups), each -- 10-70 days after its owner joined. WITH owners AS ( SELECT person_id, created_at, row_number() OVER (ORDER BY person_id DESC) AS rn FROM core.persons WHERE primary_email LIKE '%@mockshot.test' ORDER BY person_id DESC LIMIT 38 ) INSERT INTO core.organizations (name, slug, org_type, owner_person_id, created_at) SELECT CASE WHEN rn <= 4 THEN (ARRAY['Cedar Print Collective','Prairie Signal Cooperative', 'Harbor Works Guild','Juniper Commons'])[rn] ELSE (ARRAY['Cedar','Prairie','Harbor','Juniper','Granite','Meadow','Larkspur','Alder'])[1 + ((rn - 5) % 8)] || ' ' || (ARRAY['Collective','Cooperative','Commons','Works','Guild','Studio','Assembly','Exchange'])[1 + ((rn - 5) / 8)] END, 'mockshot-t' || rn, 'team', person_id, created_at + interval '10 days' + (random() * interval '60 days') FROM owners; -- Billing accounts: every team + the 176 most recent personal orgs INSERT INTO core.accounts (org_id, name, metadata) SELECT org_id, name || ' billing', '{"mockshot": true}'::jsonb FROM core.organizations WHERE slug LIKE 'mockshot-%' ORDER BY (slug LIKE 'mockshot-t%') DESC, created_at DESC LIMIT 214; -- 214 live subscriptions: teams first (all active), then individuals; -- the last 9 are trialing. WITH accts AS ( SELECT a.billing_account_id, row_number() OVER (ORDER BY (o.slug LIKE 'mockshot-t%') DESC, a.billing_account_id) AS rn FROM core.accounts a JOIN core.organizations o ON o.org_id = a.org_id WHERE a.metadata @> '{"mockshot": true}'::jsonb LIMIT 214 ) INSERT INTO core.subscriptions (billing_account_id, status, current_period_start, current_period_end) SELECT billing_account_id, CASE WHEN rn <= 205 THEN 'active' ELSE 'trialing' END, date_trunc('month', now()), date_trunc('month', now()) + interval '1 month' FROM accts; -- Five mock products with deployment-plausible names (before the prices, -- items, and grants that reference them). Published so grant events print -- them; metadata carries the nuke marker. INSERT INTO core.products (name, description, display_category, metadata, lifecycle_status) VALUES ('Starter Hosting', 'Default plan for new members', NULL, '{"mockshot": true}'::jsonb, 'published'), ('Standard Hosting', 'Paid hosting tier', NULL, '{"mockshot": true}'::jsonb, 'published'), ('Sustainer Hosting','Supporter hosting tier', NULL, '{"mockshot": true}'::jsonb, 'published'), ('Custom Domain', 'Serve a site on your own domain', 'addon','{"mockshot": true}'::jsonb, 'published'), ('Storage Boost', 'Additional media storage', 'addon', '{"mockshot": true}'::jsonb, 'published'); -- Prices for the paid tiers: Standard $9/mo (default), Sustainer $18/mo -- (default) with an $180/yr alternative — the yearly price exercises the -- one-twelfth normalization on the overview's money headline. WITH plans AS ( SELECT product_id, name FROM core.products WHERE metadata @> '{"mockshot": true}'::jsonb AND name IN ('Standard Hosting', 'Sustainer Hosting') ) INSERT INTO core.prices (product_id, currency, unit_amount, recurring_interval, is_default) SELECT product_id, 'usd', CASE name WHEN 'Standard Hosting' THEN 900 ELSE 1800 END, 'month', true FROM plans UNION ALL SELECT product_id, 'usd', 18000, 'year', false FROM plans WHERE name = 'Sustainer Hosting'; -- Subscription items: teams ride Sustainer (every 4th on the yearly -- price), individuals ride Standard monthly. INSERT INTO core.subscription_items (subscription_id, product_id, price_id, quantity) SELECT s.subscription_id, pr.product_id, pr.price_id, 1 FROM core.subscriptions s JOIN core.accounts a ON a.billing_account_id = s.billing_account_id AND a.metadata @> '{"mockshot": true}'::jsonb JOIN core.organizations o ON o.org_id = a.org_id JOIN LATERAL ( SELECT p.price_id, p.product_id FROM core.prices p JOIN core.products pd ON pd.product_id = p.product_id WHERE pd.metadata @> '{"mockshot": true}'::jsonb AND CASE WHEN o.slug LIKE 'mockshot-t%' THEN pd.name = 'Sustainer Hosting' AND p.recurring_interval = CASE WHEN split_part(o.slug, '-t', 2)::int % 4 = 0 THEN 'year' ELSE 'month' END ELSE pd.name = 'Standard Hosting' AND p.recurring_interval = 'month' END LIMIT 1 ) pr ON true; -- Auto-provisioning: every live mock subscription delivers its plan, so a -- paying org is never provision-less after its free default ends below. INSERT INTO core.pool_provisions (pool_id, subscription_id, status, entitlement_set_id, product_id) SELECT t.pool_id, s.subscription_id, 'active', t.entitlement_set_id, si.product_id FROM core.subscriptions s JOIN core.accounts a ON a.billing_account_id = s.billing_account_id AND a.metadata @> '{"mockshot": true}'::jsonb JOIN core.subscription_items si ON si.subscription_id = s.subscription_id CROSS JOIN (SELECT pool_id, entitlement_set_id FROM core.pool_provisions WHERE status = 'active' AND grant_id IS NOT NULL LIMIT 1) t; -- 9 open invoices, staggered over the last two days for the timeline WITH accts AS ( SELECT billing_account_id, row_number() OVER (ORDER BY billing_account_id) AS rn FROM core.accounts WHERE metadata @> '{"mockshot": true}'::jsonb LIMIT 9 ) INSERT INTO core.invoices (billing_account_id, status, amount_due, amount_paid, currency, period_start, period_end, due_date, created_at) SELECT billing_account_id, 'open', (ARRAY[2400, 4800, 1200, 9600, 2400, 7200, 4800, 1200, 3600])[rn], 0, 'usd', now() - interval '1 month', now(), now() + interval '14 days', now() - (random() * interval '48 hours') FROM accts; -- 6 recently paid invoices, each settled by a succeeded payment two -- minutes after issue — the timeline's payment_received rows. WITH accts AS ( SELECT billing_account_id, row_number() OVER (ORDER BY billing_account_id DESC) AS rn FROM core.accounts WHERE metadata @> '{"mockshot": true}'::jsonb LIMIT 6 ), timed AS ( SELECT billing_account_id, rn, (ARRAY[2400, 4800, 2400, 9600, 4800, 2400])[rn] AS cents, now() - (random() * interval '68 hours') AS ts FROM accts ), paid AS ( INSERT INTO core.invoices (billing_account_id, status, amount_due, amount_paid, currency, period_start, period_end, due_date, paid_at, created_at) SELECT billing_account_id, 'paid', cents, cents, 'usd', now() - interval '1 month', now(), now() + interval '14 days', ts + interval '2 minutes', ts FROM timed RETURNING invoice_id, billing_account_id, amount_paid, created_at ) INSERT INTO core.payments (invoice_id, billing_account_id, amount, currency, status, created_at) SELECT invoice_id, billing_account_id, amount_paid, 'usd', 'succeeded', created_at + interval '2 minutes' FROM paid; -- The signup default-plan grant for every mock org, stamped minutes after -- the org itself (system-authored: reason 'default' ⟺ no granting person, -- per chk_grants_default_iff_system_authored). Each gets its provision. WITH tmpl AS ( SELECT pp.pool_id, pp.entitlement_set_id FROM core.pool_provisions pp WHERE pp.status = 'active' AND pp.grant_id IS NOT NULL LIMIT 1 ), default_plan AS ( SELECT product_id FROM core.products WHERE metadata @> '{"mockshot": true}'::jsonb AND name = 'Starter Hosting' LIMIT 1 ), new_grants AS ( INSERT INTO core.grants (product_id, granted_to_org_id, granted_by_person_id, grant_reason, description, valid_from, created_at) SELECT dp.product_id, o.org_id, NULL, 'default', 'mockshot', o.created_at + interval '2 minutes', o.created_at + interval '2 minutes' FROM core.organizations o CROSS JOIN default_plan dp WHERE o.slug LIKE 'mockshot-%' RETURNING grant_id, product_id, created_at ) INSERT INTO core.pool_provisions (pool_id, grant_id, status, entitlement_set_id, product_id, activated_at) SELECT t.pool_id, g.grant_id, 'active', t.entitlement_set_id, g.product_id, g.created_at FROM new_grants g CROSS JOIN tmpl t; -- Eighteen operator-issued extras (upgrades, promos) over the last ~3 days. WITH tmpl AS ( SELECT pp.pool_id, pp.entitlement_set_id FROM core.pool_provisions pp WHERE pp.status = 'active' AND pp.grant_id IS NOT NULL LIMIT 1 ), operator_person AS ( SELECT person_id FROM core.persons WHERE primary_email NOT LIKE '%@mockshot.test' ORDER BY (display_name ILIKE 'alice%') DESC, created_at LIMIT 1 ), products AS ( SELECT product_id, row_number() OVER (ORDER BY name) AS rn FROM core.products WHERE metadata @> '{"mockshot": true}'::jsonb AND name <> 'Starter Hosting' ), targets AS ( SELECT org_id, row_number() OVER (ORDER BY created_at DESC) AS rn, now() - (random() * interval '70 hours') - interval '20 minutes' AS ts FROM core.organizations WHERE slug LIKE 'mockshot-%' LIMIT 18 ), extra_grants AS ( INSERT INTO core.grants (product_id, granted_to_org_id, granted_by_person_id, grant_reason, description, valid_from, created_at) SELECT p.product_id, tg.org_id, op.person_id, CASE WHEN tg.rn % 2 = 0 THEN 'manual' ELSE 'promotional' END, 'mockshot', tg.ts, tg.ts FROM targets tg JOIN products p ON p.rn = 1 + (tg.rn % (SELECT count(*) FROM products))::int CROSS JOIN operator_person op RETURNING grant_id, product_id, created_at ) INSERT INTO core.pool_provisions (pool_id, grant_id, status, entitlement_set_id, product_id, activated_at) SELECT t.pool_id, g.grant_id, 'active', t.entitlement_set_id, g.product_id, g.created_at FROM extra_grants g CROSS JOIN tmpl t; -- Upgrading ends the free default's delivery: the ladder table's GiST -- exclusion admits one active provision per pool per ladder, so each -- subscribed org's default-grant provision ended when its paid subscription -- provisioned. The grant itself stays 'active' in the ledger — exactly the -- ledger-vs-delivery distinction the overview tile is built around. UPDATE core.pool_provisions pp SET status = 'ended', ended_at = pp.activated_at + interval '40 minutes' FROM core.grants g JOIN core.accounts a ON a.org_id = g.granted_to_org_id JOIN core.subscriptions s ON s.billing_account_id = a.billing_account_id WHERE pp.grant_id = g.grant_id AND g.description = 'mockshot' AND g.grant_reason = 'default' AND a.metadata @> '{"mockshot": true}'::jsonb; COMMIT; -- Post-seed tallies SELECT (SELECT count(*) FROM core.persons WHERE status = 'active') AS people, (SELECT count(*) FROM core.persons WHERE status = 'active' AND created_at > now() - interval '30 days') AS joined_30d, (SELECT count(*) FROM core.organizations o JOIN core.org_types t ON t.org_type = o.org_type WHERE o.status = 'active' AND o.org_type <> 'personal' AND NOT t.is_reserved) AS team_orgs, (SELECT count(*) FROM core.subscriptions WHERE status = 'active') AS active_subs, (SELECT count(*) FROM core.subscriptions WHERE status = 'trialing') AS trialing_subs, (SELECT sum(CASE p.recurring_interval WHEN 'month' THEN p.unit_amount * si.quantity WHEN 'year' THEN (p.unit_amount * si.quantity) / 12 ELSE 0 END) FROM core.subscriptions s JOIN core.subscription_items si ON si.subscription_id = s.subscription_id JOIN core.prices p ON p.price_id = si.price_id WHERE s.status = 'active') AS mrr_cents, (SELECT count(*) FROM core.invoices WHERE status = 'open') AS open_invoices, (SELECT count(DISTINCT g.grant_id) FROM core.grants g JOIN core.pool_provisions pp ON pp.grant_id = g.grant_id WHERE pp.status = 'active' AND g.grant_reason <> 'default') AS operator_grants, (SELECT count(DISTINCT g.grant_id) FROM core.grants g JOIN core.pool_provisions pp ON pp.grant_id = g.grant_id WHERE pp.status = 'active' AND g.grant_reason = 'default') AS default_grants;