6ab52a2499
Backend & Auth: - Add @clerk/nextjs for production authentication - Create src/proxy.ts with clerkMiddleware() for route protection - Implement multi-tenant auth with role-based access control - Add Clerk components (Show, UserButton, SignInButton, SignUpButton) Billing & Payments: - Full Stripe integration (subscriptions, add-ons, customer portal) - Plan tiers: Starter 9/mo, Farm 49/mo, Enterprise 99/mo - Webhook handling for subscription events - createSubscription(), createAddonSubscription(), createCustomerPortalSession() API & Security: - Rate limiting with @upstash/ratelimit (100 req/min API, 20 req/min checkout) - Zod validation schemas for all endpoints (orders, products, campaigns, etc.) - Security headers (CSP, HSTS, X-Frame-Options) - API routes: /api/v1/ with validated, rate-limited endpoints Monitoring: - Sentry error tracking with performance monitoring - PostHog analytics for feature usage, funnels, cohorts - User activity logging and breadcrumb tracking Admin Features: - Analytics dashboard with revenue charts, customer growth, conversion funnel - Onboarding flow with 6-step interactive tour - Referral system with share tracking and reward redemption - Changelog feed with in-app notifications PWA & SEO: - Web app manifest with icons and shortcuts - Service worker for offline support and caching - Full SEO metadata, OpenGraph, Twitter cards - Structured data (JSON-LD) for organization and products Database: - Add referral_codes, changelogs, onboarding_progress tables - Add user_activity_logs, api_keys, notification_preferences - Comprehensive RLS policies for all new tables - Seed data for demo brands and products
250 lines
9.8 KiB
PL/PgSQL
250 lines
9.8 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()
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- 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; |