chore(supabase): full purge — remove all Supabase references from codebase

- Delete supabase/ directory (config.toml, push-migrations.js,
  ADMIN_CREATE_STOP_FIX.sql, 137 archived migration files)
- Remove @supabase/ssr and @supabase/supabase-js from package.json
- Strip *.supabase.co from next.config.ts image hostnames
- Strip https://*.supabase.co from vercel.json CSP connect-src
- Remove 'supabase/**' ignore from eslint.config.mjs
- Clean supabase references from src/lib/db.ts, vitest.config.ts,
  db/seeds/2026-tuxedo-tour-stops.sql, src/actions/storefront.ts,
  scripts/import-tuxedo-stops.ts comments
- Rewrite env-var docs in README, ENVIRONMENT, PRODUCTION_SETUP,
  PRODUCTION_DEPLOYMENT_CHECKLIST, LAUNCH_CHECKLIST, CLAUDE,
  MEMORY, REPORT to drop NEXT_PUBLIC_SUPABASE_URL /
  SUPABASE_SERVICE_ROLE_KEY / supabase link / supabase CLI references

The canonical migration runner is now scripts/migrate.js (uses pg
directly via DATABASE_URL). Migrations live in db/migrations/. The
Supabase CLI is no longer in the codebase. The only remaining
'@supabase/*' in the dep tree is @supabase/auth-js as a transitive
of @neondatabase/auth (Neon Auth / Better Auth).

Final source scan: grep -rln '@supabase\|rest/v1\|supabase\.co'
src/ tests/ db/ scripts/ next.config.ts vercel.json eslint.config.mjs
package.json returns zero. Verification: npm install clean
(11 added, 21 removed, 12 changed), tsc shows same 17 pre-existing
errors (Stripe dahlia API version + fetch preconnect mocks),
vitest 172/175 (3 pre-existing failures in getAdminUser.test.ts),
lint shows same 14 pre-existing errors. Live-tested: dev server
boots in 248ms, public storefronts (/) (/login) (/pricing) (/tuxedo)
(/indian-river-direct) all return 200; /admin/v2?demo=1 reaches 200
after dev_session redirect; storefront renders real brand content.
This commit is contained in:
Nora
2026-06-25 17:48:32 -06:00
parent 68a749f7af
commit 49b8e27219
171 changed files with 136 additions and 28594 deletions
@@ -1,226 +0,0 @@
-- Abandoned cart RPCs
CREATE OR REPLACE FUNCTION get_abandoned_carts(p_brand_id UUID)
RETURNS TABLE(carts JSONB)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
RETURN QUERY
SELECT jsonb_agg(jsonb_build_object(
'id', acr.id,
'brand_id', acr.brand_id,
'customer_id', acr.customer_id,
'contact_email', acr.contact_email,
'contact_name', acr.contact_name,
'cart_snapshot', acr.cart_snapshot,
'brand_name', acr.brand_name,
'locale', acr.locale,
'sequence_step', acr.sequence_step,
'last_email_sent_at', acr.last_email_sent_at,
'next_email_at', acr.next_email_at,
'status', acr.status,
'recovered_order_id', acr.recovered_order_id,
'recovered_at', acr.recovered_at,
'created_at', acr.created_at
) ORDER BY acr.created_at DESC)
FROM abandoned_cart_recovery acr
WHERE acr.brand_id = p_brand_id;
END;
$$;
CREATE OR REPLACE FUNCTION enroll_abandoned_cart(
p_brand_id UUID,
p_customer_id UUID,
p_contact_email TEXT,
p_contact_name TEXT,
p_cart_snapshot JSONB,
p_brand_name TEXT,
p_locale TEXT DEFAULT 'en',
p_next_email_at TIMESTAMPTZ
)
RETURNS UUID
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_id UUID;
BEGIN
INSERT INTO abandoned_cart_recovery (brand_id, customer_id, contact_email, contact_name, cart_snapshot, brand_name, locale, next_email_at, status)
VALUES (p_brand_id, p_customer_id, p_contact_email, p_contact_name, p_cart_snapshot, p_brand_name, p_locale, p_next_email_at, 'active')
ON CONFLICT DO NOTHING
RETURNING id INTO v_id;
RETURN v_id;
END;
$$;
CREATE OR REPLACE FUNCTION get_active_abandoned_carts(p_brand_id UUID)
RETURNS TABLE(carts JSONB)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
RETURN QUERY
SELECT jsonb_agg(jsonb_build_object(
'id', acr.id,
'brand_id', acr.brand_id,
'customer_id', acr.customer_id,
'contact_email', acr.contact_email,
'contact_name', acr.contact_name,
'cart_snapshot', acr.cart_snapshot,
'brand_name', acr.brand_name,
'locale', acr.locale,
'sequence_step', acr.sequence_step,
'last_email_sent_at', acr.last_email_sent_at,
'next_email_at', acr.next_email_at,
'status', acr.status,
'created_at', acr.created_at
) ORDER BY acr.next_email_at ASC)
FROM abandoned_cart_recovery acr
WHERE acr.brand_id = p_brand_id
AND acr.status = 'active'
AND acr.next_email_at IS NOT NULL
AND acr.next_email_at <= now();
END;
$$;
CREATE OR REPLACE FUNCTION update_abandoned_cart(
p_id UUID,
p_sequence_step INTEGER DEFAULT NULL,
p_last_email_sent_at TIMESTAMPTZ DEFAULT NULL,
p_next_email_at TIMESTAMPTZ DEFAULT NULL,
p_status TEXT DEFAULT NULL,
p_expired_at TIMESTAMPTZ DEFAULT NULL,
p_manually_closed_by UUID DEFAULT NULL,
p_recovered_order_id UUID DEFAULT NULL,
p_recovered_at TIMESTAMPTZ DEFAULT NULL
)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
UPDATE abandoned_cart_recovery SET
sequence_step = COALESCE(p_sequence_step, sequence_step),
last_email_sent_at = COALESCE(p_last_email_sent_at, last_email_sent_at),
next_email_at = COALESCE(p_next_email_at, next_email_at),
status = COALESCE(p_status, status),
expired_at = COALESCE(p_expired_at, expired_at),
manually_closed_at = CASE WHEN p_manually_closed_by IS NOT NULL THEN now() ELSE manually_closed_at END,
manually_closed_by = COALESCE(p_manually_closed_by, manually_closed_by),
recovered_order_id = COALESCE(p_recovered_order_id, recovered_order_id),
recovered_at = COALESCE(p_recovered_at, recovered_at),
updated_at = now()
WHERE id = p_id;
END;
$$;
-- Welcome sequence RPCs
CREATE OR REPLACE FUNCTION get_welcome_sequence(p_brand_id UUID)
RETURNS TABLE(entries JSONB)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
RETURN QUERY
SELECT jsonb_agg(jsonb_build_object(
'id', ws.id,
'brand_id', ws.brand_id,
'contact_id', ws.contact_id,
'contact_email', ws.contact_email,
'contact_name', ws.contact_name,
'brand_name', ws.brand_name,
'locale', ws.locale,
'sequence_step', ws.sequence_step,
'last_email_sent_at', ws.last_email_sent_at,
'next_email_at', ws.next_email_at,
'status', ws.status,
'created_at', ws.created_at
) ORDER BY ws.created_at DESC)
FROM welcome_email_sequence ws
WHERE ws.brand_id = p_brand_id;
END;
$$;
CREATE OR REPLACE FUNCTION get_active_welcome_sequence(p_brand_id UUID)
RETURNS TABLE(entries JSONB)
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
RETURN QUERY
SELECT jsonb_agg(jsonb_build_object(
'id', ws.id,
'brand_id', ws.brand_id,
'contact_id', ws.contact_id,
'contact_email', ws.contact_email,
'contact_name', ws.contact_name,
'brand_name', ws.brand_name,
'locale', ws.locale,
'sequence_step', ws.sequence_step,
'last_email_sent_at', ws.last_email_sent_at,
'next_email_at', ws.next_email_at,
'status', ws.status,
'created_at', ws.created_at
) ORDER BY ws.next_email_at ASC)
FROM welcome_email_sequence ws
WHERE ws.brand_id = p_brand_id
AND ws.status = 'active'
AND ws.next_email_at IS NOT NULL
AND ws.next_email_at <= now();
END;
$$;
CREATE OR REPLACE FUNCTION update_welcome_sequence(
p_id UUID,
p_sequence_step INTEGER DEFAULT NULL,
p_last_email_sent_at TIMESTAMPTZ DEFAULT NULL,
p_next_email_at TIMESTAMPTZ DEFAULT NULL,
p_status TEXT DEFAULT NULL,
p_completed_at TIMESTAMPTZ DEFAULT NULL,
p_unsubscribed_at TIMESTAMPTZ DEFAULT NULL,
p_bounced_at TIMESTAMPTZ DEFAULT NULL
)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
UPDATE welcome_email_sequence SET
sequence_step = COALESCE(p_sequence_step, sequence_step),
last_email_sent_at = COALESCE(p_last_email_sent_at, last_email_sent_at),
next_email_at = COALESCE(p_next_email_at, next_email_at),
status = COALESCE(p_status, status),
completed_at = COALESCE(p_completed_at, completed_at),
unsubscribed_at = COALESCE(p_unsubscribed_at, unsubscribed_at),
bounced_at = COALESCE(p_bounced_at, bounced_at),
updated_at = now()
WHERE id = p_id;
END;
$$;
-- Trigger: auto-enroll new wholesale customer contacts into welcome sequence
CREATE OR REPLACE FUNCTION on_new_wholesale_customer_contact()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- Check if contact has email and opted in
IF NEW.email IS NOT NULL AND NEW.email_opt_in = TRUE THEN
PERFORM enroll_welcome_sequence(
NEW.brand_id,
NEW.id,
NEW.email,
COALESCE(NEW.first_name, NEW.full_name, NEW.contact_name),
'en'
);
END IF;
RETURN NEW;
END;
$$;
-- Note: This trigger would be added to communication_contacts but requires
-- source tracking. For now, enrollment is handled manually or via the
-- communications subscription UI.