-- Fix Discord username generation by reading from auth.identities CREATE OR REPLACE FUNCTION public.handle_new_user() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public', 'auth' AS $$ DECLARE v_username TEXT; v_display_name TEXT; v_email TEXT; v_provider TEXT; v_username_attempt INTEGER := 0; v_max_attempts INTEGER := 10; v_unique_username TEXT; v_identity_data JSONB; BEGIN -- Extract email and provider v_email := NEW.email; v_provider := NEW.raw_app_meta_data->>'provider'; -- Smart username generation based on provider IF v_provider = 'google' THEN -- For Google: try email prefix, then name-based, then UUID fallback v_username := COALESCE( -- Email prefix (before @), sanitized LOWER(REGEXP_REPLACE(split_part(v_email, '@', 1), '[^a-z0-9_]', '_', 'g')), -- Name-based, sanitized LOWER(REGEXP_REPLACE(NEW.raw_user_meta_data->>'name', '[^a-z0-9_]', '_', 'g')), -- UUID fallback 'user_' || substring(NEW.id::text, 1, 8) ); v_display_name := NEW.raw_user_meta_data->>'name'; ELSIF v_provider = 'discord' THEN -- For Discord: Extract data from auth.identities table SELECT identity_data INTO v_identity_data FROM auth.identities WHERE user_id = NEW.id AND provider = 'discord' LIMIT 1; IF v_identity_data IS NOT NULL THEN -- Extract username from full_name (preferred) or name without discriminator v_username := COALESCE( LOWER(REGEXP_REPLACE(v_identity_data->>'full_name', '[^a-z0-9_]', '_', 'g')), LOWER(REGEXP_REPLACE(split_part(v_identity_data->>'name', '#', 1), '[^a-z0-9_]', '_', 'g')), 'user_' || substring(NEW.id::text, 1, 8) ); -- Extract display name from custom_claims.global_name (preferred) or full_name v_display_name := COALESCE( v_identity_data->'custom_claims'->>'global_name', v_identity_data->>'full_name', v_identity_data->>'name' ); RAISE NOTICE 'Discord user data extracted: username=%, display_name=%', v_username, v_display_name; ELSE -- Fallback if identity data not found v_username := 'user_' || substring(NEW.id::text, 1, 8); v_display_name := NULL; RAISE WARNING 'Discord identity data not found for user %, using fallback', NEW.id; END IF; ELSE -- Manual signup or other providers v_username := COALESCE( NEW.raw_user_meta_data->>'username', 'user_' || substring(NEW.id::text, 1, 8) ); v_display_name := COALESCE( NEW.raw_user_meta_data->>'display_name', NEW.raw_user_meta_data->>'name' ); END IF; -- Ensure username uniqueness by appending counter if needed v_unique_username := v_username; WHILE v_username_attempt < v_max_attempts LOOP BEGIN -- Try to insert with current username INSERT INTO public.profiles ( user_id, username, display_name, oauth_provider ) VALUES ( NEW.id, v_unique_username, v_display_name, v_provider ); -- If successful, exit loop EXIT; EXCEPTION WHEN unique_violation THEN -- Username taken, try next variant v_username_attempt := v_username_attempt + 1; IF v_username_attempt < v_max_attempts THEN v_unique_username := v_username || '_' || v_username_attempt; ELSE -- Final fallback to UUID if all attempts fail v_unique_username := 'user_' || substring(NEW.id::text, 1, 8); END IF; END; END LOOP; -- Log the final username for debugging RAISE NOTICE 'Created profile for user % with username %', NEW.id, v_unique_username; RETURN NEW; END; $$; COMMENT ON FUNCTION public.handle_new_user IS 'Creates user profile on signup with smart username generation. For Discord, reads from auth.identities table.';