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:
@@ -1,334 +0,0 @@
|
||||
-- Migration: 037_update_admin_user_profile
|
||||
-- Extends update_admin_user to accept display_name and phone_number.
|
||||
-- Updates admin_users and syncs auth.users raw_user_meta_data.
|
||||
-- After update, writes to admin_action_logs.
|
||||
|
||||
DROP FUNCTION IF EXISTS update_admin_user(UUID, TEXT, UUID, JSONB, BOOLEAN, TEXT, TEXT);
|
||||
CREATE OR REPLACE FUNCTION update_admin_user(
|
||||
p_id UUID,
|
||||
p_role TEXT DEFAULT NULL,
|
||||
p_brand_id UUID DEFAULT NULL,
|
||||
p_flags JSONB DEFAULT NULL,
|
||||
p_active BOOLEAN DEFAULT NULL,
|
||||
p_display_name TEXT DEFAULT NULL,
|
||||
p_phone_number TEXT DEFAULT NULL
|
||||
)
|
||||
RETURNS TABLE (
|
||||
id UUID,
|
||||
user_id UUID,
|
||||
display_name TEXT,
|
||||
email TEXT,
|
||||
role TEXT,
|
||||
brand_id UUID,
|
||||
active BOOLEAN,
|
||||
phone_number TEXT,
|
||||
updated_at TIMESTAMPTZ
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_caller_role TEXT;
|
||||
v_caller_brand_id UUID;
|
||||
v_caller_can_manage_users BOOLEAN;
|
||||
v_target_role TEXT;
|
||||
v_target_brand_id UUID;
|
||||
v_target_user_id UUID;
|
||||
v_admin_email TEXT;
|
||||
BEGIN
|
||||
IF auth.uid() IS NULL THEN
|
||||
RAISE EXCEPTION 'Not authenticated';
|
||||
END IF;
|
||||
|
||||
SELECT role, brand_id, can_manage_users
|
||||
INTO v_caller_role, v_caller_brand_id, v_caller_can_manage_users
|
||||
FROM admin_users
|
||||
WHERE user_id = auth.uid();
|
||||
|
||||
IF v_caller_role IS NULL THEN
|
||||
RAISE EXCEPTION 'Not an admin';
|
||||
END IF;
|
||||
|
||||
-- Cannot update your own account's role
|
||||
IF EXISTS (SELECT 1 FROM admin_users WHERE id = p_id AND user_id = auth.uid()) THEN
|
||||
RAISE EXCEPTION 'Cannot update your own admin account';
|
||||
END IF;
|
||||
|
||||
-- Look up target
|
||||
SELECT role, brand_id, user_id INTO v_target_role, v_target_brand_id, v_target_user_id
|
||||
FROM admin_users WHERE id = p_id;
|
||||
|
||||
IF v_target_role IS NULL THEN
|
||||
RAISE EXCEPTION 'Admin user not found';
|
||||
END IF;
|
||||
|
||||
-- Security: brand_admin cannot demote/promote platform_admin
|
||||
IF v_caller_role = 'brand_admin' AND v_target_role = 'platform_admin' AND p_role IS NOT NULL THEN
|
||||
RAISE EXCEPTION 'Insufficient permissions to modify a platform_admin';
|
||||
END IF;
|
||||
|
||||
-- Security: brand_admin cannot give platform_admin role
|
||||
IF v_caller_role = 'brand_admin' AND p_role = 'platform_admin' THEN
|
||||
RAISE EXCEPTION 'Insufficient permissions to create a platform_admin';
|
||||
END IF;
|
||||
|
||||
-- Security: brand_admin can only affect users in their brand
|
||||
IF v_caller_role = 'brand_admin' THEN
|
||||
IF v_target_brand_id != v_caller_brand_id THEN
|
||||
RAISE EXCEPTION 'Insufficient permissions to modify a user from another brand';
|
||||
END IF;
|
||||
IF p_brand_id IS NOT NULL AND p_brand_id != v_caller_brand_id THEN
|
||||
RAISE EXCEPTION 'Insufficient permissions to assign a user to another brand';
|
||||
END IF;
|
||||
IF p_role = 'platform_admin' THEN
|
||||
RAISE EXCEPTION 'Insufficient permissions to create a platform_admin';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Get caller email for audit log
|
||||
SELECT email INTO v_admin_email FROM auth.users WHERE id = auth.uid();
|
||||
|
||||
-- Apply updates to admin_users
|
||||
UPDATE admin_users SET
|
||||
role = COALESCE(p_role, role),
|
||||
brand_id = COALESCE(p_brand_id, brand_id),
|
||||
can_manage_products = COALESCE((p_flags->>'can_manage_products')::BOOLEAN, can_manage_products),
|
||||
can_manage_stops = COALESCE((p_flags->>'can_manage_stops')::BOOLEAN, can_manage_stops),
|
||||
can_manage_orders = COALESCE((p_flags->>'can_manage_orders')::BOOLEAN, can_manage_orders),
|
||||
can_manage_pickup = COALESCE((p_flags->>'can_manage_pickup')::BOOLEAN, can_manage_pickup),
|
||||
can_manage_messages = COALESCE((p_flags->>'can_manage_messages')::BOOLEAN, can_manage_messages),
|
||||
can_manage_refunds = COALESCE((p_flags->>'can_manage_refunds')::BOOLEAN, can_manage_refunds),
|
||||
can_manage_users = COALESCE((p_flags->>'can_manage_users')::BOOLEAN, can_manage_users),
|
||||
can_manage_water_log = COALESCE((p_flags->>'can_manage_water_log')::BOOLEAN, can_manage_water_log),
|
||||
can_manage_reports = COALESCE((p_flags->>'can_manage_reports')::BOOLEAN, can_manage_reports),
|
||||
active = COALESCE(p_active, active),
|
||||
display_name = COALESCE(p_display_name, display_name),
|
||||
phone_number = COALESCE(p_phone_number, phone_number)
|
||||
WHERE id = p_id;
|
||||
|
||||
-- Sync display_name / phone_number to auth.users raw_user_meta_data
|
||||
IF p_display_name IS NOT NULL OR p_phone_number IS NOT NULL THEN
|
||||
UPDATE auth.users SET
|
||||
raw_user_meta_data = jsonb_strip_nulls(
|
||||
jsonb_set(
|
||||
COALESCE(raw_user_meta_data, '{}'::jsonb),
|
||||
'{display_name}',
|
||||
to_jsonb(p_display_name)
|
||||
) ||
|
||||
jsonb_set(
|
||||
COALESCE(raw_user_meta_data, '{}'::jsonb),
|
||||
'{phone_number}',
|
||||
to_jsonb(p_phone_number)
|
||||
)
|
||||
)
|
||||
WHERE id = v_target_user_id;
|
||||
END IF;
|
||||
|
||||
-- Audit log: record the action
|
||||
PERFORM log_admin_action(jsonb_build_object(
|
||||
'action_type', 'update',
|
||||
'admin_id', auth.uid(),
|
||||
'admin_email', v_admin_email,
|
||||
'affected_user_id', v_target_user_id,
|
||||
'brand_id', COALESCE(p_brand_id, v_target_brand_id),
|
||||
'details', jsonb_build_object(
|
||||
'changed_fields', ARRAY_REMOVE(ARRAY[
|
||||
CASE WHEN p_role IS NOT NULL THEN 'role' ELSE NULL END,
|
||||
CASE WHEN p_brand_id IS NOT NULL THEN 'brand_id' ELSE NULL END,
|
||||
CASE WHEN p_flags IS NOT NULL THEN 'flags' ELSE NULL END,
|
||||
CASE WHEN p_active IS NOT NULL THEN 'active' ELSE NULL END,
|
||||
CASE WHEN p_display_name IS NOT NULL THEN 'display_name' ELSE NULL END,
|
||||
CASE WHEN p_phone_number IS NOT NULL THEN 'phone_number' ELSE NULL END
|
||||
], NULL)
|
||||
)
|
||||
));
|
||||
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
au.id, au.user_id,
|
||||
COALESCE(
|
||||
(au.raw_user_meta_data->>'display_name')::TEXT,
|
||||
u.email
|
||||
) AS display_name,
|
||||
u.email, au.role::TEXT, au.brand_id, au.active,
|
||||
au.phone_number, NOW() AS updated_at
|
||||
FROM admin_users au
|
||||
JOIN auth.users u ON au.user_id = u.id
|
||||
WHERE au.id = p_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Update create_admin_user to also log
|
||||
CREATE OR REPLACE FUNCTION create_admin_user(
|
||||
p_email TEXT,
|
||||
p_role TEXT,
|
||||
p_brand_id UUID DEFAULT NULL,
|
||||
p_flags JSONB DEFAULT '{}'::JSONB
|
||||
)
|
||||
RETURNS TABLE (
|
||||
id UUID,
|
||||
user_id UUID,
|
||||
display_name TEXT,
|
||||
email TEXT,
|
||||
role TEXT,
|
||||
brand_id UUID,
|
||||
active BOOLEAN,
|
||||
created_at TIMESTAMPTZ
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_caller_role TEXT;
|
||||
v_caller_brand_id UUID;
|
||||
v_caller_can_manage_users BOOLEAN;
|
||||
v_target_user_id UUID;
|
||||
v_new_id UUID;
|
||||
v_admin_email TEXT;
|
||||
BEGIN
|
||||
IF auth.uid() IS NULL THEN
|
||||
RAISE EXCEPTION 'Not authenticated';
|
||||
END IF;
|
||||
|
||||
SELECT role, brand_id, can_manage_users
|
||||
INTO v_caller_role, v_caller_brand_id, v_caller_can_manage_users
|
||||
FROM admin_users
|
||||
WHERE user_id = auth.uid();
|
||||
|
||||
IF v_caller_role IS NULL THEN
|
||||
RAISE EXCEPTION 'Not an admin';
|
||||
END IF;
|
||||
|
||||
IF v_caller_role = 'brand_admin' AND p_role = 'platform_admin' THEN
|
||||
RAISE EXCEPTION 'Insufficient permissions to create a platform_admin';
|
||||
END IF;
|
||||
|
||||
IF v_caller_role = 'brand_admin' THEN
|
||||
IF p_brand_id IS DISTINCT FROM v_caller_brand_id THEN
|
||||
RAISE EXCEPTION 'Insufficient permissions to create a user for another brand';
|
||||
END IF;
|
||||
IF p_brand_id IS NULL AND p_role = 'platform_admin' THEN
|
||||
RAISE EXCEPTION 'brand_admin cannot create platform_admin';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
BEGIN
|
||||
SELECT id INTO v_target_user_id FROM auth.users WHERE email = p_email;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
RAISE EXCEPTION 'Auth user with email % not found', p_email;
|
||||
END;
|
||||
|
||||
IF v_target_user_id IS NULL THEN
|
||||
RAISE EXCEPTION 'Auth user with email % not found', p_email;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (SELECT 1 FROM admin_users WHERE user_id = v_target_user_id) THEN
|
||||
RAISE EXCEPTION 'Admin user with email % already exists', p_email;
|
||||
END IF;
|
||||
|
||||
-- Get caller email for audit log
|
||||
SELECT email INTO v_admin_email FROM auth.users WHERE id = auth.uid();
|
||||
|
||||
INSERT INTO admin_users (
|
||||
user_id, role, brand_id,
|
||||
can_manage_products, can_manage_stops, can_manage_orders, can_manage_pickup,
|
||||
can_manage_messages, can_manage_refunds, can_manage_users, can_manage_water_log,
|
||||
can_manage_reports, active
|
||||
) VALUES (
|
||||
v_target_user_id, p_role, p_brand_id,
|
||||
COALESCE((p_flags->>'can_manage_products')::BOOLEAN, false),
|
||||
COALESCE((p_flags->>'can_manage_stops')::BOOLEAN, false),
|
||||
COALESCE((p_flags->>'can_manage_orders')::BOOLEAN, false),
|
||||
COALESCE((p_flags->>'can_manage_pickup')::BOOLEAN, false),
|
||||
COALESCE((p_flags->>'can_manage_messages')::BOOLEAN, false),
|
||||
COALESCE((p_flags->>'can_manage_refunds')::BOOLEAN, false),
|
||||
COALESCE((p_flags->>'can_manage_users')::BOOLEAN, false),
|
||||
COALESCE((p_flags->>'can_manage_water_log')::BOOLEAN, false),
|
||||
COALESCE((p_flags->>'can_manage_reports')::BOOLEAN, false),
|
||||
true
|
||||
)
|
||||
RETURNING id INTO v_new_id;
|
||||
|
||||
-- Audit log: record the creation
|
||||
PERFORM log_admin_action(jsonb_build_object(
|
||||
'action_type', 'create',
|
||||
'admin_id', auth.uid(),
|
||||
'admin_email', v_admin_email,
|
||||
'affected_user_id', v_target_user_id,
|
||||
'brand_id', p_brand_id,
|
||||
'details', jsonb_build_object('new_role', p_role)
|
||||
));
|
||||
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
au.id, au.user_id,
|
||||
COALESCE(
|
||||
(au.raw_user_meta_data->>'display_name')::TEXT,
|
||||
(au.raw_user_meta_data->>'full_name')::TEXT,
|
||||
u.email
|
||||
) AS display_name,
|
||||
u.email, au.role::TEXT, au.brand_id, au.active, au.created_at
|
||||
FROM admin_users au
|
||||
JOIN auth.users u ON au.user_id = u.id
|
||||
WHERE au.id = v_new_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Update delete_admin_user to also log
|
||||
CREATE OR REPLACE FUNCTION delete_admin_user(p_id UUID)
|
||||
RETURNS BOOLEAN
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_caller_role TEXT;
|
||||
v_target_user_id UUID;
|
||||
v_admin_email TEXT;
|
||||
v_target_brand_id UUID;
|
||||
BEGIN
|
||||
IF auth.uid() IS NULL THEN
|
||||
RAISE EXCEPTION 'Not authenticated';
|
||||
END IF;
|
||||
|
||||
SELECT role INTO v_caller_role FROM admin_users WHERE user_id = auth.uid();
|
||||
IF v_caller_role IS NULL THEN
|
||||
RAISE EXCEPTION 'Not an admin';
|
||||
END IF;
|
||||
|
||||
SELECT user_id, brand_id INTO v_target_user_id, v_target_brand_id
|
||||
FROM admin_users WHERE id = p_id;
|
||||
|
||||
SELECT email INTO v_admin_email FROM auth.users WHERE id = auth.uid();
|
||||
|
||||
IF v_target_user_id = auth.uid() THEN
|
||||
RAISE EXCEPTION 'Cannot delete your own admin account';
|
||||
END IF;
|
||||
|
||||
IF v_caller_role = 'brand_admin' THEN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM admin_users
|
||||
WHERE id = p_id AND brand_id = (SELECT brand_id FROM admin_users WHERE user_id = auth.uid())
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Insufficient permissions to delete this user';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Audit log: record the deletion
|
||||
PERFORM log_admin_action(jsonb_build_object(
|
||||
'action_type', 'delete',
|
||||
'admin_id', auth.uid(),
|
||||
'admin_email', v_admin_email,
|
||||
'affected_user_id', v_target_user_id,
|
||||
'brand_id', v_target_brand_id,
|
||||
'details', jsonb_build_object('deleted_admin_id', p_id)
|
||||
));
|
||||
|
||||
DELETE FROM admin_users WHERE id = p_id;
|
||||
RETURN true;
|
||||
END;
|
||||
$$;
|
||||
|
||||
NOTIFY pgrst, 'reload schema';
|
||||
Reference in New Issue
Block a user