diff --git a/supabase/migrations/20251012004831_b7806af4-2194-47e7-8e19-c1fbfabbc920.sql b/supabase/migrations/20251012004831_b7806af4-2194-47e7-8e19-c1fbfabbc920.sql new file mode 100644 index 00000000..ede65e71 --- /dev/null +++ b/supabase/migrations/20251012004831_b7806af4-2194-47e7-8e19-c1fbfabbc920.sql @@ -0,0 +1,118 @@ +-- 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.'; \ No newline at end of file