bookwiz.io / supabase / migrations / 042_fix_date_trunc_final.sql
042_fix_date_trunc_final.sql
Raw
-- Final fix for the date_trunc function error
-- This addresses the "function date_trunc(unknown, time with time zone) does not exist" error
-- by being very explicit about types and using a simpler approach

-- Drop and recreate the get_current_usage_period function with explicit types
DROP FUNCTION IF EXISTS get_current_usage_period(UUID);

CREATE OR REPLACE FUNCTION get_current_usage_period(user_uuid UUID)
RETURNS TABLE (
    period_start TIMESTAMPTZ,
    period_end TIMESTAMPTZ
)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public, pg_catalog, pg_temp
AS $$
DECLARE
    subscription_record RECORD;
    now_ts TIMESTAMPTZ;
    month_start TIMESTAMPTZ;
    month_end TIMESTAMPTZ;
BEGIN
    -- Use explicit TIMESTAMPTZ for all time operations
    now_ts := NOW()::TIMESTAMPTZ;
    
    -- Calculate month boundaries using explicit casting
    month_start := DATE_TRUNC('month', now_ts::TIMESTAMP)::TIMESTAMPTZ;
    month_end := (month_start + INTERVAL '1 month')::TIMESTAMPTZ;
    
    -- 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')
    ORDER BY s.created_at DESC
    LIMIT 1;
    
    -- Check if we have an active subscription with valid period
    IF subscription_record IS NOT NULL AND 
       subscription_record.period_start IS NOT NULL AND 
       subscription_record.period_end IS NOT NULL THEN
        
        -- Safely check if subscription is still active
        BEGIN
            IF subscription_record.period_end > now_ts THEN
                -- Return subscription period
                RETURN QUERY 
                SELECT 
                    subscription_record.period_start,
                    subscription_record.period_end;
                RETURN;
            END IF;
        EXCEPTION 
            WHEN OTHERS THEN
                -- If comparison fails, log and fall through to free tier
                RAISE LOG 'Failed to compare subscription periods for user %: %', user_uuid, SQLERRM;
        END;
    END IF;
    
    -- Return current month for free tier or when subscription is not active
    RETURN QUERY
    SELECT 
        month_start,
        month_end;
END;
$$;

-- Also fix the increment_usage function to use the same pattern
DROP FUNCTION IF EXISTS increment_usage(UUID, TEXT, DECIMAL, JSONB, BOOLEAN);

CREATE OR REPLACE FUNCTION increment_usage(
    user_uuid UUID,
    feature_type_param TEXT,
    amount_param DECIMAL DEFAULT 1,
    metadata_param JSONB DEFAULT '{}'::jsonb,
    skip_limit_check BOOLEAN DEFAULT FALSE
)
RETURNS user_usage
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public, pg_catalog, pg_temp
AS $$
DECLARE
    usage_record user_usage;
    current_value DECIMAL;
    new_value DECIMAL;
    now_ts TIMESTAMPTZ := NOW()::TIMESTAMPTZ;
BEGIN
    -- Get or create current usage record
    usage_record := get_or_create_current_usage(user_uuid);
    
    -- Get current value for the feature type
    current_value := CASE feature_type_param
        WHEN 'advanced_prompts' THEN usage_record.advanced_prompts_used
        WHEN 'auto_prompts' THEN usage_record.auto_prompts_used
        WHEN 'storage_gb' THEN usage_record.storage_used_gb
        WHEN 'custom_templates' THEN usage_record.custom_templates_created
        WHEN 'books' THEN usage_record.books_created
        ELSE 0
    END;
    
    -- Calculate new value
    new_value := current_value + amount_param;
    
    -- Update the specific counter
    CASE feature_type_param
        WHEN 'advanced_prompts' THEN
            UPDATE user_usage 
            SET advanced_prompts_used = new_value::INTEGER,
                updated_at = now_ts
            WHERE id = usage_record.id
            RETURNING * INTO usage_record;
            
        WHEN 'auto_prompts' THEN
            UPDATE user_usage 
            SET auto_prompts_used = new_value::INTEGER,
                updated_at = now_ts
            WHERE id = usage_record.id
            RETURNING * INTO usage_record;
            
        WHEN 'storage_gb' THEN
            UPDATE user_usage 
            SET storage_used_gb = new_value,
                updated_at = now_ts
            WHERE id = usage_record.id
            RETURNING * INTO usage_record;
            
        WHEN 'custom_templates' THEN
            UPDATE user_usage 
            SET custom_templates_created = new_value::INTEGER,
                updated_at = now_ts
            WHERE id = usage_record.id
            RETURNING * INTO usage_record;
            
        WHEN 'books' THEN
            UPDATE user_usage 
            SET books_created = new_value::INTEGER,
                updated_at = now_ts
            WHERE id = usage_record.id
            RETURNING * INTO usage_record;
            
        ELSE
            RAISE EXCEPTION 'Invalid feature_type: %', feature_type_param;
    END CASE;
    
    -- Log the usage increment
    INSERT INTO usage_logs (
        user_id,
        feature_type,
        action,
        amount,
        metadata
    ) VALUES (
        user_uuid,
        feature_type_param,
        'increment',
        amount_param,
        metadata_param
    );
    
    RETURN usage_record;
END;
$$;

-- Fix get_or_create_current_usage to use same pattern
DROP FUNCTION IF EXISTS get_or_create_current_usage(UUID);

CREATE OR REPLACE FUNCTION get_or_create_current_usage(user_uuid UUID)
RETURNS user_usage
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public, pg_catalog, pg_temp
AS $$
DECLARE
    usage_record user_usage;
    period_info RECORD;
BEGIN
    -- Get current period
    SELECT * INTO period_info FROM get_current_usage_period(user_uuid);
    
    -- Try to get existing usage record
    SELECT * INTO usage_record 
    FROM user_usage 
    WHERE user_id = user_uuid 
    AND period_start = period_info.period_start 
    AND period_end = period_info.period_end;
    
    -- Create if doesn't exist
    IF usage_record IS NULL THEN
        INSERT INTO user_usage (
            user_id, 
            period_start, 
            period_end
        ) VALUES (
            user_uuid,
            period_info.period_start,
            period_info.period_end
        )
        RETURNING * INTO usage_record;
    END IF;
    
    RETURN usage_record;
END;
$$;

-- Add helpful comments
COMMENT ON FUNCTION get_current_usage_period(UUID) IS 'Fixed date_trunc issues with explicit timestamp casting and simplified logic';
COMMENT ON FUNCTION increment_usage(UUID, TEXT, DECIMAL, JSONB, BOOLEAN) IS 'Fixed timestamp handling with explicit TIMESTAMPTZ casting';
COMMENT ON FUNCTION get_or_create_current_usage(UUID) IS 'Gets existing usage record for current period or creates new one';