ba94d755fa
- Update supabase/push-migrations.js: - Detect modern Supabase CLI link state (supabase/.temp/project-ref) - Prefer `supabase db query --linked --file` (works in this env where direct postgres connections fail with ENOTFOUND/network unreachable) - Updated docs/comments for `supabase login` + `supabase link --project-ref wnzkhezyhnfzhkhiflrp` workflow - Add MEMORY.md capturing the Supabase login/link session, tooling changes, migration patches applied, and gotchas - Patch migrations for current remote schema (column drift, syntax, idempotency, pre-existing tables): - 091_brand_plan_tier.sql: fix extra paren in get_brand_plan_info - 145_create_product_images_bucket.sql: allowed_mime_types + DROP POLICY IF EXISTS + safer bucket insert - 148_public_stops_rpc.sql: quote reserved "time" column in RETURNS TABLE + SELECT - 200_production_features.sql: add ALTER TABLE for user_activity_logs (originated in 036) so policies/indexes validate - 201_seed_data.sql: trim demo seeds with outdated column lists (products/stops/etc.); keep only compatible brands insert - Include 202_fix_admin_create_stop.sql and related stop creation updates (src/actions/stops/create-stop.ts, 147_admin_create_stop_rpcs.sql, ADMIN_CREATE_STOP_FIX.sql) - Update CLAUDE.md with pointer to MEMORY.md for recent migration work Applied via the new flow (after supabase login + link): 084, 091, 142–148, 200–202 etc. Refs: Supabase CLI now linked; use `node supabase/push-migrations.js <prefix>` or `npm run migrate:one NNN`
258 lines
10 KiB
PL/PgSQL
258 lines
10 KiB
PL/PgSQL
-- Production Database Migrations
|
|
-- Add comprehensive RLS policies, audit tables, referral system, and seed data
|
|
|
|
BEGIN;
|
|
|
|
-- =============================================================================
|
|
-- REFERRAL SYSTEM
|
|
-- =============================================================================
|
|
|
|
-- Referral codes table
|
|
CREATE TABLE IF NOT EXISTS referral_codes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
brand_id UUID REFERENCES brands(id) ON DELETE CASCADE,
|
|
referrer_user_id UUID NOT NULL,
|
|
referral_code VARCHAR(50) UNIQUE NOT NULL,
|
|
referrer_email VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
expires_at TIMESTAMPTZ DEFAULT (NOW() + INTERVAL '1 year'),
|
|
max_uses INTEGER DEFAULT 1,
|
|
current_uses INTEGER DEFAULT 0,
|
|
is_active BOOLEAN DEFAULT true,
|
|
reward_type VARCHAR(50) DEFAULT 'percentage',
|
|
reward_value DECIMAL(10,2) DEFAULT 20.00,
|
|
metadata JSONB DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
-- Referral redemptions table
|
|
CREATE TABLE IF NOT EXISTS referral_redemptions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
referral_code_id UUID REFERENCES referral_codes(id) ON DELETE CASCADE,
|
|
brand_id UUID REFERENCES brands(id) ON DELETE CASCADE,
|
|
referred_user_id UUID NOT NULL,
|
|
referred_email VARCHAR(255) NOT NULL,
|
|
redeemed_at TIMESTAMPTZ DEFAULT NOW(),
|
|
reward_type VARCHAR(50),
|
|
reward_value DECIMAL(10,2),
|
|
is_credit_applied BOOLEAN DEFAULT false,
|
|
signup_plan VARCHAR(50),
|
|
signup_value DECIMAL(10,2),
|
|
metadata JSONB DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- CHANGELOG SYSTEM
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS changelogs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
brand_id UUID REFERENCES brands(id) ON DELETE CASCADE,
|
|
version VARCHAR(50) NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
content JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
released_at TIMESTAMPTZ DEFAULT NOW(),
|
|
is_published BOOLEAN DEFAULT false,
|
|
feature_type VARCHAR(50) DEFAULT 'general',
|
|
metadata JSONB DEFAULT '{}'::jsonb,
|
|
UNIQUE(brand_id, version)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS changelog_reads (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL,
|
|
changelog_id UUID REFERENCES changelogs(id) ON DELETE CASCADE,
|
|
read_at TIMESTAMPTZ DEFAULT NOW(),
|
|
dismissed BOOLEAN DEFAULT false,
|
|
UNIQUE(user_id, changelog_id)
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- ONBOARDING PROGRESS
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS onboarding_progress (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
brand_id UUID REFERENCES brands(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL,
|
|
current_step VARCHAR(50) NOT NULL,
|
|
completed_steps JSONB DEFAULT '[]'::jsonb,
|
|
skipped_steps JSONB DEFAULT '[]'::jsonb,
|
|
data JSONB DEFAULT '{}'::jsonb,
|
|
started_at TIMESTAMPTZ DEFAULT NOW(),
|
|
completed_at TIMESTAMPTZ,
|
|
is_completed BOOLEAN DEFAULT false,
|
|
metadata JSONB DEFAULT '{}'::jsonb,
|
|
UNIQUE(brand_id, user_id)
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- USER ACTIVITY / ANALYTICS
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS user_activity_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL,
|
|
brand_id UUID REFERENCES brands(id) ON DELETE SET NULL,
|
|
action VARCHAR(100) NOT NULL,
|
|
resource_type VARCHAR(50),
|
|
resource_id UUID,
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
metadata JSONB DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- If table pre-existed from 036 (without brand_id etc), add the columns now so policies below succeed
|
|
ALTER TABLE user_activity_logs
|
|
ADD COLUMN IF NOT EXISTS brand_id UUID REFERENCES brands(id) ON DELETE SET NULL,
|
|
ADD COLUMN IF NOT EXISTS action VARCHAR(100),
|
|
ADD COLUMN IF NOT EXISTS resource_type VARCHAR(50),
|
|
ADD COLUMN IF NOT EXISTS resource_id UUID,
|
|
ADD COLUMN IF NOT EXISTS metadata JSONB DEFAULT '{}'::jsonb;
|
|
|
|
-- =============================================================================
|
|
-- API KEYS FOR EXTERNAL INTEGRATIONS
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS api_keys (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
brand_id UUID REFERENCES brands(id) ON DELETE CASCADE,
|
|
name VARCHAR(255) NOT NULL,
|
|
key_hash VARCHAR(255) NOT NULL,
|
|
key_prefix VARCHAR(20),
|
|
permissions JSONB DEFAULT '["read"]'::jsonb,
|
|
rate_limit INTEGER DEFAULT 1000,
|
|
last_used_at TIMESTAMPTZ,
|
|
expires_at TIMESTAMPTZ,
|
|
is_active BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
created_by UUID NOT NULL
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- NOTIFICATION PREFERENCES
|
|
-- =============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS notification_preferences (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL,
|
|
brand_id UUID REFERENCES brands(id) ON DELETE CASCADE,
|
|
email_orders BOOLEAN DEFAULT true,
|
|
email_marketing BOOLEAN DEFAULT false,
|
|
email_reports BOOLEAN DEFAULT true,
|
|
email_billing BOOLEAN DEFAULT true,
|
|
sms_orders BOOLEAN DEFAULT false,
|
|
sms_marketing BOOLEAN DEFAULT false,
|
|
push_orders BOOLEAN DEFAULT true,
|
|
push_marketing BOOLEAN DEFAULT false,
|
|
quiet_hours_start TIME,
|
|
quiet_hours_end TIME,
|
|
timezone VARCHAR(50) DEFAULT 'America/New_York',
|
|
metadata JSONB DEFAULT '{}'::jsonb,
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(user_id, brand_id)
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- REFINED RLS POLICIES
|
|
-- =============================================================================
|
|
|
|
-- Enable RLS on new tables
|
|
ALTER TABLE referral_codes ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE referral_redemptions ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE changelogs ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE changelog_reads ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE onboarding_progress ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE user_activity_logs ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE api_keys ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE notification_preferences ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Referral codes policies
|
|
CREATE POLICY "Users can view their own referral codes"
|
|
ON referral_codes FOR SELECT
|
|
USING (referrer_user_id = current_setting('app.current_user_id', true)::uuid);
|
|
|
|
CREATE POLICY "Platform admins can manage all referral codes"
|
|
ON referral_codes FOR ALL
|
|
USING (current_setting('app.current_user_role', true) = 'platform_admin');
|
|
|
|
CREATE POLICY "Brand admins can manage their brand's referral codes"
|
|
ON referral_codes FOR ALL
|
|
USING (brand_id = current_setting('app.current_brand_id', true)::uuid);
|
|
|
|
-- Changelogs policies - public read for all authenticated
|
|
CREATE POLICY "Authenticated users can view published changelogs"
|
|
ON changelogs FOR SELECT
|
|
USING (
|
|
is_published = true
|
|
AND (
|
|
current_setting('app.current_user_role', true) = 'platform_admin'
|
|
OR brand_id = current_setting('app.current_brand_id', true)::uuid
|
|
)
|
|
);
|
|
|
|
CREATE POLICY "Brand admins can manage their changelogs"
|
|
ON changelogs FOR ALL
|
|
USING (brand_id = current_setting('app.current_brand_id', true)::uuid);
|
|
|
|
-- Onboarding progress policies
|
|
CREATE POLICY "Users can view their own onboarding progress"
|
|
ON onboarding_progress FOR SELECT
|
|
USING (user_id = current_setting('app.current_user_id', true)::uuid);
|
|
|
|
CREATE POLICY "Users can update their own onboarding progress"
|
|
ON onboarding_progress FOR UPDATE
|
|
USING (user_id = current_setting('app.current_user_id', true)::uuid);
|
|
|
|
CREATE POLICY "Brand admins can view onboarding progress for their brand"
|
|
ON onboarding_progress FOR SELECT
|
|
USING (brand_id = current_setting('app.current_brand_id', true)::uuid);
|
|
|
|
-- User activity logs - admins only
|
|
CREATE POLICY "Brand admins can view activity logs for their brand"
|
|
ON user_activity_logs FOR SELECT
|
|
USING (
|
|
brand_id = current_setting('app.current_brand_id', true)::uuid
|
|
OR current_setting('app.current_user_role', true) = 'platform_admin'
|
|
);
|
|
|
|
-- API keys policies
|
|
CREATE POLICY "Brand admins can manage their API keys"
|
|
ON api_keys FOR ALL
|
|
USING (brand_id = current_setting('app.current_brand_id', true)::uuid);
|
|
|
|
CREATE POLICY "Anyone can read active API key prefix (for verification)"
|
|
ON api_keys FOR SELECT
|
|
USING (is_active = true);
|
|
|
|
-- Notification preferences policies
|
|
CREATE POLICY "Users can manage their own notification preferences"
|
|
ON notification_preferences FOR ALL
|
|
USING (user_id = current_setting('app.current_user_id', true)::uuid);
|
|
|
|
CREATE POLICY "Brand admins can view preferences for their brand users"
|
|
ON notification_preferences FOR SELECT
|
|
USING (brand_id = current_setting('app.current_brand_id', true)::uuid);
|
|
|
|
-- =============================================================================
|
|
-- COMPREHENSIVE INDEXES
|
|
-- =============================================================================
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_referral_codes_brand ON referral_codes(brand_id);
|
|
CREATE INDEX IF NOT EXISTS idx_referral_codes_referrer ON referral_codes(referrer_user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_referral_codes_code ON referral_codes(referral_code);
|
|
CREATE INDEX IF NOT EXISTS idx_referral_redemptions_referrer ON referral_redemptions(referral_code_id);
|
|
CREATE INDEX IF NOT EXISTS idx_referral_redemptions_referred ON referral_redemptions(referred_user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_changelogs_brand ON changelogs(brand_id);
|
|
CREATE INDEX IF NOT EXISTS idx_changelogs_published ON changelogs(is_published, released_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_changelog_reads_user ON changelog_reads(user_id, changelog_id);
|
|
CREATE INDEX IF NOT EXISTS idx_onboarding_brand ON onboarding_progress(brand_id);
|
|
CREATE INDEX IF NOT EXISTS idx_onboarding_user ON onboarding_progress(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_user_activity_user ON user_activity_logs(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_user_activity_brand ON user_activity_logs(brand_id);
|
|
CREATE INDEX IF NOT EXISTS idx_user_activity_created ON user_activity_logs(created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_api_keys_brand ON api_keys(brand_id);
|
|
CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(is_active);
|
|
|
|
COMMIT; |