bookwiz.io / supabase / migrations / 044_cleanup_duplicate_usage.sql
044_cleanup_duplicate_usage.sql
Raw
-- Clean up existing duplicate user_usage records
-- This migration finds users who have both free tier and subscription usage records
-- and consolidates them properly

-- Function to clean up duplicate usage records for a specific user
CREATE OR REPLACE FUNCTION cleanup_duplicate_usage_for_user(user_uuid UUID)
RETURNS TEXT
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public, pg_catalog, pg_temp
AS $$
DECLARE
    subscription_record RECORD;
    current_time TIMESTAMPTZ := NOW();
    free_tier_start TIMESTAMPTZ;
    free_tier_end TIMESTAMPTZ;
    free_tier_record user_usage;
    subscription_record_usage user_usage;
    cleanup_result TEXT := 'No action needed';
BEGIN
    -- Get the user's active subscription
    SELECT 
        id,
        user_id, 
        status,
        current_period_start::TIMESTAMPTZ as period_start,
        current_period_end::TIMESTAMPTZ as period_end,
        created_at
    INTO subscription_record
    FROM subscriptions s
    WHERE s.user_id = user_uuid 
    AND s.status IN ('active', 'trialing', 'past_due')
    AND s.current_period_end > current_time
    ORDER BY s.created_at DESC
    LIMIT 1;
    
    -- Only proceed if user has an active subscription
    IF subscription_record IS NULL THEN
        RETURN 'No active subscription';
    END IF;
    
    -- Calculate free tier period for current month
    free_tier_start := DATE_TRUNC('month', current_time::TIMESTAMP)::TIMESTAMPTZ;
    free_tier_end := (free_tier_start + INTERVAL '1 month')::TIMESTAMPTZ;
    
    -- Look for free tier usage record
    SELECT * INTO free_tier_record
    FROM user_usage 
    WHERE user_id = user_uuid 
    AND period_start = free_tier_start 
    AND period_end = free_tier_end;
    
    -- Look for subscription period usage record
    SELECT * INTO subscription_record_usage
    FROM user_usage 
    WHERE user_id = user_uuid 
    AND period_start = subscription_record.period_start
    AND period_end = subscription_record.period_end;
    
    -- Check if we have both records (the duplicate scenario)
    IF free_tier_record IS NOT NULL AND subscription_record_usage IS NOT NULL THEN
        -- Merge the free tier usage into the subscription record
        UPDATE user_usage 
        SET 
            advanced_prompts_used = advanced_prompts_used + free_tier_record.advanced_prompts_used,
            auto_prompts_used = auto_prompts_used + free_tier_record.auto_prompts_used,
            storage_used_gb = storage_used_gb + free_tier_record.storage_used_gb,
            custom_templates_created = custom_templates_created + free_tier_record.custom_templates_created,
            books_created = books_created + free_tier_record.books_created,
            updated_at = NOW()
        WHERE id = subscription_record_usage.id;
        
        -- Delete the free tier record
        DELETE FROM user_usage WHERE id = free_tier_record.id;
        
        -- Log the cleanup
        INSERT INTO usage_logs (user_id, feature_type, action, amount, metadata)
        VALUES (user_uuid, 'advanced_prompts', 'cleanup', 0, 
                jsonb_build_object(
                    'cleanup_type', 'merge_duplicate_records',
                    'merged_advanced_prompts', free_tier_record.advanced_prompts_used,
                    'merged_auto_prompts', free_tier_record.auto_prompts_used,
                    'merged_books', free_tier_record.books_created,
                    'deleted_record_id', free_tier_record.id::text,
                    'kept_record_id', subscription_record_usage.id::text
                ));
        
        cleanup_result := 'Merged ' || free_tier_record.advanced_prompts_used || ' advanced + ' || 
                         free_tier_record.auto_prompts_used || ' auto prompts, ' ||
                         free_tier_record.books_created || ' books';
                         
    ELSIF free_tier_record IS NOT NULL AND subscription_record_usage IS NULL THEN
        -- Only free tier record exists, convert it to subscription period
        UPDATE user_usage 
        SET 
            period_start = subscription_record.period_start,
            period_end = subscription_record.period_end,
            updated_at = NOW()
        WHERE id = free_tier_record.id;
        
        -- Log the conversion
        INSERT INTO usage_logs (user_id, feature_type, action, amount, metadata)
        VALUES (user_uuid, 'advanced_prompts', 'convert', 0, 
                jsonb_build_object(
                    'convert_type', 'free_to_subscription_period',
                    'old_start', free_tier_start,
                    'old_end', free_tier_end,
                    'new_start', subscription_record.period_start,
                    'new_end', subscription_record.period_end
                ));
        
        cleanup_result := 'Converted free tier record to subscription period';
        
    ELSIF free_tier_record IS NULL AND subscription_record_usage IS NOT NULL THEN
        cleanup_result := 'Already has correct subscription record';
    ELSE
        cleanup_result := 'No usage records found';
    END IF;
    
    RETURN cleanup_result;
END;
$$;

-- Run cleanup for all users with active subscriptions
DO $$
DECLARE
    user_record RECORD;
    cleanup_result TEXT;
    total_users INTEGER := 0;
    fixed_users INTEGER := 0;
BEGIN
    RAISE NOTICE 'Starting cleanup of duplicate usage records...';
    
    -- Process all users with active subscriptions
    FOR user_record IN 
        SELECT DISTINCT user_id 
        FROM subscriptions 
        WHERE status IN ('active', 'trialing', 'past_due')
        AND current_period_end > NOW()
    LOOP
        total_users := total_users + 1;
        
        -- Run cleanup for each user
        BEGIN
            SELECT cleanup_duplicate_usage_for_user(user_record.user_id) INTO cleanup_result;
            
            IF cleanup_result != 'No action needed' AND cleanup_result != 'No active subscription' THEN
                fixed_users := fixed_users + 1;
                RAISE NOTICE 'User %: %', user_record.user_id, cleanup_result;
            END IF;
            
        EXCEPTION 
            WHEN OTHERS THEN
                RAISE NOTICE 'ERROR cleaning up user %: %', user_record.user_id, SQLERRM;
        END;
    END LOOP;
    
    RAISE NOTICE 'Cleanup complete: % users processed, % users fixed', total_users, fixed_users;
END $$;

-- Function to check for remaining duplicate records (diagnostic)
CREATE OR REPLACE FUNCTION find_users_with_duplicate_usage()
RETURNS TABLE (
    user_id UUID,
    subscription_periods TEXT,
    free_tier_periods TEXT,
    total_records INTEGER
)
LANGUAGE SQL
AS $$
    SELECT 
        uu.user_id,
        STRING_AGG(DISTINCT uu.period_start::TEXT || ' to ' || uu.period_end::TEXT, '; ') as subscription_periods,
        STRING_AGG(DISTINCT 
            CASE 
                WHEN uu.period_start = DATE_TRUNC('month', NOW()) 
                THEN uu.period_start::TEXT || ' to ' || uu.period_end::TEXT 
                ELSE NULL 
            END, '; ') as free_tier_periods,
        COUNT(*) as total_records
    FROM user_usage uu
    WHERE EXISTS (
        SELECT 1 FROM subscriptions s 
        WHERE s.user_id = uu.user_id 
        AND s.status IN ('active', 'trialing', 'past_due')
        AND s.current_period_end > NOW()
    )
    GROUP BY uu.user_id
    HAVING COUNT(*) > 1;
$$;

-- Clean up the diagnostic function after use
COMMENT ON FUNCTION cleanup_duplicate_usage_for_user(UUID) IS 'Fixes duplicate usage records for a user by merging or converting periods';
COMMENT ON FUNCTION find_users_with_duplicate_usage() IS 'Diagnostic function to find users who still have duplicate usage records';