Rework avatars.

Now we have a concept of an avatar at the server, realm, and scene level
and we have the groundwork for a realm store. New uesrs no longer props,
they get a default avatar. New system supports gender
{male,female,neutral} and {child,adult}.
This commit is contained in:
Evan Carroll 2026-01-22 21:04:27 -06:00
parent e4abdb183f
commit 6fb90e42c3
55 changed files with 7392 additions and 512 deletions

View file

@ -1,25 +0,0 @@
-- Reinitialize all users with current server props
-- Use: psql -d chattyness -f reinitialize_all_users.sql
DO $$
DECLARE
v_user RECORD;
v_count INT := 0;
BEGIN
FOR v_user IN SELECT id, username FROM auth.users
LOOP
-- Clear existing data
DELETE FROM auth.active_avatars WHERE user_id = v_user.id;
DELETE FROM auth.avatars WHERE user_id = v_user.id;
DELETE FROM auth.inventory WHERE user_id = v_user.id;
-- Reinitialize with current server props
PERFORM auth.initialize_new_user(v_user.id);
v_count := v_count + 1;
RAISE NOTICE 'Reinitialized user: % (%)', v_user.username, v_user.id;
END LOOP;
RAISE NOTICE 'Total users reinitialized: %', v_count;
END;
$$;

View file

@ -1,59 +0,0 @@
# Reinitialize User with Default Props
When stock props or avatars are updated in the database, existing users may need to be reinitialized to receive the new defaults.
## Steps
1. Find the user's ID:
```sql
SELECT id, username FROM auth.users WHERE username = 'TARGET_USERNAME';
```
2. Clear existing data and reinitialize:
```sql
BEGIN;
-- Clear existing props and avatars for the user
DELETE FROM auth.active_avatars WHERE user_id = 'USER_UUID';
DELETE FROM auth.avatars WHERE user_id = 'USER_UUID';
DELETE FROM auth.inventory WHERE user_id = 'USER_UUID';
-- Reinitialize with current server props
SELECT auth.initialize_new_user('USER_UUID');
COMMIT;
```
3. Verify the results:
```sql
SELECT COUNT(*) as inventory_count FROM auth.inventory WHERE user_id = 'USER_UUID';
SELECT id, name, slot_number FROM auth.avatars WHERE user_id = 'USER_UUID';
```
## Example: Reinitialize ranosh
```bash
psql -d chattyness <<'EOF'
BEGIN;
DELETE FROM auth.active_avatars WHERE user_id = '57a12201-ea0f-4545-9ccc-c4e67ea7e2c4';
DELETE FROM auth.avatars WHERE user_id = '57a12201-ea0f-4545-9ccc-c4e67ea7e2c4';
DELETE FROM auth.inventory WHERE user_id = '57a12201-ea0f-4545-9ccc-c4e67ea7e2c4';
SELECT auth.initialize_new_user('57a12201-ea0f-4545-9ccc-c4e67ea7e2c4');
COMMIT;
EOF
```
## What `initialize_new_user` Does
The `auth.initialize_new_user()` function:
1. Inserts all face-tagged server props into the user's inventory
2. Creates a default avatar (slot 0) with:
- Face prop in the skin layer (position 4, center)
- All emotion props mapped to their respective emotion slots

View file

@ -1,170 +0,0 @@
-- Chattyness User Initialization Functions
-- PostgreSQL 18
--
-- Functions to initialize new users with default props and avatars.
-- Load via: psql -f schema/functions/002_user_init.sql
\set ON_ERROR_STOP on
BEGIN;
-- =============================================================================
-- Initialize New User with Default Props and Avatar
-- =============================================================================
-- Called when a new user is created to give them:
-- 1. All face-tagged server props in their inventory
-- 2. A default avatar (slot 0) with the Face prop and all emotions configured
--
-- Note: active_avatars entry is NOT created here - it's created when the user
-- joins a realm for the first time (per-realm avatar state).
-- =============================================================================
CREATE OR REPLACE FUNCTION auth.initialize_new_user(p_user_id UUID)
RETURNS VOID AS $$
DECLARE
v_avatar_id UUID;
v_face_inventory_id UUID;
v_neutral_inventory_id UUID;
v_happy_inventory_id UUID;
v_sad_inventory_id UUID;
v_angry_inventory_id UUID;
v_surprised_inventory_id UUID;
v_thinking_inventory_id UUID;
v_laughing_inventory_id UUID;
v_crying_inventory_id UUID;
v_love_inventory_id UUID;
v_confused_inventory_id UUID;
v_sleeping_inventory_id UUID;
v_wink_inventory_id UUID;
v_prop RECORD;
BEGIN
-- Insert all face-tagged server props into user's inventory
-- Note: inventory layer/position are only for content layer props (skin/clothes/accessories).
-- Emotion props have default_emotion instead of default_layer, so they get NULL layer/position.
FOR v_prop IN
SELECT id, name, asset_path, default_layer, default_emotion, default_position, slug,
is_transferable, is_portable, is_droppable
FROM server.props
WHERE tags @> ARRAY['face']
AND is_active = true
LOOP
-- Use a local variable for the inserted inventory ID
DECLARE
v_new_inventory_id UUID;
BEGIN
INSERT INTO auth.inventory (
user_id,
server_prop_id,
prop_name,
prop_asset_path,
layer,
position,
origin,
is_transferable,
is_portable,
is_droppable
)
VALUES (
p_user_id,
v_prop.id,
v_prop.name,
v_prop.asset_path,
v_prop.default_layer, -- NULL for emotion props
CASE WHEN v_prop.default_layer IS NOT NULL THEN v_prop.default_position ELSE NULL END,
'server_library',
v_prop.is_transferable,
v_prop.is_portable,
v_prop.is_droppable
)
RETURNING id INTO v_new_inventory_id;
-- Track inventory IDs for avatar assignment based on slug
CASE v_prop.slug
WHEN 'face' THEN v_face_inventory_id := v_new_inventory_id;
WHEN 'neutral' THEN v_neutral_inventory_id := v_new_inventory_id;
WHEN 'smile' THEN v_happy_inventory_id := v_new_inventory_id;
WHEN 'sad' THEN v_sad_inventory_id := v_new_inventory_id;
WHEN 'angry' THEN v_angry_inventory_id := v_new_inventory_id;
WHEN 'surprised' THEN v_surprised_inventory_id := v_new_inventory_id;
WHEN 'thinking' THEN v_thinking_inventory_id := v_new_inventory_id;
WHEN 'laughing' THEN v_laughing_inventory_id := v_new_inventory_id;
WHEN 'crying' THEN v_crying_inventory_id := v_new_inventory_id;
WHEN 'love' THEN v_love_inventory_id := v_new_inventory_id;
WHEN 'confused' THEN v_confused_inventory_id := v_new_inventory_id;
WHEN 'sleeping' THEN v_sleeping_inventory_id := v_new_inventory_id;
WHEN 'wink' THEN v_wink_inventory_id := v_new_inventory_id;
ELSE NULL;
END CASE;
END;
END LOOP;
-- Create default avatar (slot 0) with the Face prop in skin layer
-- and all emotion props in their respective emotion slots at position 4 (center)
INSERT INTO auth.avatars (
user_id,
name,
slot_number,
last_emotion,
-- Content layer: Face goes in skin layer, center position
l_skin_4,
-- Emotion layers: Each emotion prop goes to its matching emotion at center position
e_neutral_4,
e_happy_4,
e_sad_4,
e_angry_4,
e_surprised_4,
e_thinking_4,
e_laughing_4,
e_crying_4,
e_love_4,
e_confused_4,
e_sleeping_4,
e_wink_4
)
VALUES (
p_user_id,
'Default',
0,
0, -- Start with neutral emotion
v_face_inventory_id,
v_neutral_inventory_id,
v_happy_inventory_id,
v_sad_inventory_id,
v_angry_inventory_id,
v_surprised_inventory_id,
v_thinking_inventory_id,
v_laughing_inventory_id,
v_crying_inventory_id,
v_love_inventory_id,
v_confused_inventory_id,
v_sleeping_inventory_id,
v_wink_inventory_id
)
RETURNING id INTO v_avatar_id;
-- Note: We don't create an active_avatars entry here because that's per-realm.
-- The active_avatars entry will be created when the user first joins a realm.
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
COMMENT ON FUNCTION auth.initialize_new_user(UUID) IS
'Initialize a new user with default props in inventory and a default avatar configuration';
-- =============================================================================
-- Trigger Function for User Registration
-- =============================================================================
-- Wrapper trigger function that calls initialize_new_user.
-- =============================================================================
CREATE OR REPLACE FUNCTION auth.initialize_new_user_trigger()
RETURNS TRIGGER AS $$
BEGIN
PERFORM auth.initialize_new_user(NEW.id);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
COMMENT ON FUNCTION auth.initialize_new_user_trigger() IS
'Trigger function to initialize new users on registration';
COMMIT;

View file

@ -45,7 +45,9 @@
\echo 'Phase 3: Creating tables...'
\ir tables/010_server.sql
\ir tables/020_auth.sql
\ir tables/025_server_avatars.sql
\ir tables/030_realm.sql
\ir tables/035_realm_avatars.sql
\ir tables/045_scene.sql
\ir tables/050_chat.sql
\ir tables/080_audit.sql
@ -56,7 +58,6 @@
-- =============================================================================
\echo 'Phase 4: Creating functions...'
\ir functions/001_helpers.sql
\ir functions/002_user_init.sql
\echo ''
-- =============================================================================
@ -64,7 +65,6 @@
-- =============================================================================
\echo 'Phase 5: Creating triggers...'
\ir triggers/001_updated_at.sql
\ir triggers/002_user_init.sql
\echo ''
-- =============================================================================

View file

@ -79,6 +79,29 @@ CREATE POLICY server_props_delete ON server.props
GRANT SELECT ON server.props TO chattyness_app;
GRANT INSERT, UPDATE, DELETE ON server.props TO chattyness_app;
-- server.avatars
ALTER TABLE server.avatars ENABLE ROW LEVEL SECURITY;
CREATE POLICY server_avatars_select ON server.avatars
FOR SELECT TO chattyness_app
USING (true);
CREATE POLICY server_avatars_insert ON server.avatars
FOR INSERT TO chattyness_app
WITH CHECK (public.is_server_admin());
CREATE POLICY server_avatars_update ON server.avatars
FOR UPDATE TO chattyness_app
USING (public.is_server_admin())
WITH CHECK (public.is_server_admin());
CREATE POLICY server_avatars_delete ON server.avatars
FOR DELETE TO chattyness_app
USING (public.is_server_admin());
GRANT SELECT ON server.avatars TO chattyness_app;
GRANT INSERT, UPDATE, DELETE ON server.avatars TO chattyness_app;
-- server.audio
ALTER TABLE server.audio ENABLE ROW LEVEL SECURITY;
@ -350,6 +373,12 @@ CREATE POLICY auth_active_avatars_view ON auth.active_avatars
FOR SELECT TO chattyness_app
USING (true);
-- Allow realm moderators to update forced avatar columns on any user in their realm
CREATE POLICY auth_active_avatars_mod ON auth.active_avatars
FOR UPDATE TO chattyness_app
USING (public.is_realm_moderator(realm_id))
WITH CHECK (public.is_realm_moderator(realm_id));
GRANT SELECT, INSERT, UPDATE, DELETE ON auth.active_avatars TO chattyness_app;
-- =============================================================================
@ -507,6 +536,30 @@ CREATE POLICY realm_props_modify ON realm.props
GRANT SELECT, INSERT, UPDATE, DELETE ON realm.props TO chattyness_app;
-- realm.avatars
ALTER TABLE realm.avatars ENABLE ROW LEVEL SECURITY;
CREATE POLICY realm_avatars_select ON realm.avatars
FOR SELECT TO chattyness_app
USING (
public.has_realm_membership(realm_id)
OR public.is_server_admin()
);
CREATE POLICY realm_avatars_modify ON realm.avatars
FOR ALL TO chattyness_app
USING (
EXISTS (
SELECT 1 FROM realm.memberships m
WHERE m.realm_id = realm.avatars.realm_id
AND m.user_id = public.current_user_id()
AND m.role IN ('owner', 'builder')
)
OR public.is_server_admin()
);
GRANT SELECT, INSERT, UPDATE, DELETE ON realm.avatars TO chattyness_app;
-- realm.reports
ALTER TABLE realm.reports ENABLE ROW LEVEL SECURITY;

View file

@ -24,6 +24,11 @@ CREATE TABLE auth.users (
bio TEXT,
avatar_url public.url,
-- User preferences for default avatar selection
birthday DATE,
gender_preference auth.gender_preference NOT NULL DEFAULT 'gender_neutral',
age_category auth.age_category NOT NULL DEFAULT 'adult',
reputation_tier server.reputation_tier NOT NULL DEFAULT 'member',
reputation_promoted_at TIMESTAMPTZ,
@ -634,9 +639,13 @@ CREATE INDEX idx_auth_avatars_default ON auth.avatars (user_id, is_default) WHER
CREATE TABLE auth.active_avatars (
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
realm_id UUID NOT NULL, -- FK added in 030_realm.sql after realm.realms exists
avatar_id UUID NOT NULL REFERENCES auth.avatars(id) ON DELETE CASCADE,
avatar_id UUID REFERENCES auth.avatars(id) ON DELETE SET NULL,
current_emotion SMALLINT NOT NULL DEFAULT 0 CHECK (current_emotion >= 0 AND current_emotion <= 11),
-- User-selected avatars from avatar stores (lower priority than custom avatar)
selected_server_avatar_id UUID, -- FK added in 025_server_avatars.sql
selected_realm_avatar_id UUID, -- FK added in 035_realm_avatars.sql
current_emotion server.emotion_state NOT NULL DEFAULT 'happy',
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
@ -644,6 +653,12 @@ CREATE TABLE auth.active_avatars (
);
COMMENT ON TABLE auth.active_avatars IS 'Current avatar per user per realm';
COMMENT ON COLUMN auth.active_avatars.avatar_id IS
'User custom avatar (highest priority, nullable for users without custom avatars)';
COMMENT ON COLUMN auth.active_avatars.selected_server_avatar_id IS
'User-selected server avatar (from avatar store), lower priority than custom avatar';
COMMENT ON COLUMN auth.active_avatars.selected_realm_avatar_id IS
'User-selected realm avatar (from avatar store), higher priority than server selection';
-- =============================================================================
-- Server-Level Moderation: IP Bans

View file

@ -0,0 +1,222 @@
-- =============================================================================
-- Server Avatars
-- =============================================================================
-- Pre-configured avatar configurations available globally across all realms.
-- These reference server.props directly (not inventory items).
--
-- Loaded after 020_auth.sql (server.props must exist)
-- =============================================================================
CREATE TABLE server.avatars (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug public.slug NOT NULL,
name public.nonempty_text NOT NULL,
description TEXT,
is_public BOOLEAN NOT NULL DEFAULT false,
is_active BOOLEAN NOT NULL DEFAULT true,
thumbnail_path public.asset_path,
-- Content layers: 3 layers x 9 positions = 27 slots
-- All reference server.props(id) directly
l_skin_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_skin_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_skin_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_skin_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_skin_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_skin_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_skin_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_skin_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_skin_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_clothes_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_clothes_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_clothes_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_clothes_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_clothes_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_clothes_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_clothes_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_clothes_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_clothes_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_accessories_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_accessories_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_accessories_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_accessories_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_accessories_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_accessories_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_accessories_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_accessories_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
l_accessories_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
-- Emotion layers: 12 emotions x 9 positions = 108 slots
e_neutral_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_neutral_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_neutral_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_neutral_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_neutral_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_neutral_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_neutral_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_neutral_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_neutral_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_happy_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_happy_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_happy_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_happy_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_happy_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_happy_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_happy_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_happy_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_happy_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sad_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sad_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sad_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sad_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sad_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sad_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sad_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sad_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sad_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_angry_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_angry_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_angry_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_angry_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_angry_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_angry_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_angry_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_angry_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_angry_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_surprised_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_surprised_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_surprised_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_surprised_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_surprised_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_surprised_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_surprised_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_surprised_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_surprised_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_thinking_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_thinking_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_thinking_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_thinking_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_thinking_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_thinking_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_thinking_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_thinking_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_thinking_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_laughing_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_laughing_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_laughing_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_laughing_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_laughing_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_laughing_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_laughing_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_laughing_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_laughing_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_crying_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_crying_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_crying_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_crying_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_crying_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_crying_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_crying_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_crying_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_crying_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_love_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_love_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_love_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_love_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_love_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_love_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_love_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_love_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_love_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_confused_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_confused_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_confused_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_confused_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_confused_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_confused_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_confused_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_confused_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_confused_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sleeping_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sleeping_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sleeping_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sleeping_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sleeping_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sleeping_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sleeping_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sleeping_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_sleeping_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_wink_0 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_wink_1 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_wink_2 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_wink_3 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_wink_4 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_wink_5 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_wink_6 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_wink_7 UUID REFERENCES server.props(id) ON DELETE SET NULL,
e_wink_8 UUID REFERENCES server.props(id) ON DELETE SET NULL,
created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT uq_server_avatars_slug UNIQUE (slug)
);
COMMENT ON TABLE server.avatars IS 'Pre-configured avatar configurations available globally (references server.props)';
CREATE INDEX idx_server_avatars_active ON server.avatars (is_active) WHERE is_active = true;
CREATE INDEX idx_server_avatars_public ON server.avatars (is_public) WHERE is_public = true;
COMMENT ON COLUMN server.avatars.is_public IS
'When true, avatar appears in the public avatar selection. Uses filtered index.';
-- =============================================================================
-- Add Default Avatar Columns to server.config
-- =============================================================================
-- These columns reference server.avatars for gender/age-based default avatars.
-- =============================================================================
ALTER TABLE server.config
ADD COLUMN default_avatar_neutral_child UUID REFERENCES server.avatars(id) ON DELETE SET NULL,
ADD COLUMN default_avatar_neutral_adult UUID REFERENCES server.avatars(id) ON DELETE SET NULL,
ADD COLUMN default_avatar_male_child UUID REFERENCES server.avatars(id) ON DELETE SET NULL,
ADD COLUMN default_avatar_male_adult UUID REFERENCES server.avatars(id) ON DELETE SET NULL,
ADD COLUMN default_avatar_female_child UUID REFERENCES server.avatars(id) ON DELETE SET NULL,
ADD COLUMN default_avatar_female_adult UUID REFERENCES server.avatars(id) ON DELETE SET NULL;
COMMENT ON COLUMN server.config.default_avatar_neutral_child IS
'Default server avatar for gender-neutral child users';
COMMENT ON COLUMN server.config.default_avatar_neutral_adult IS
'Default server avatar for gender-neutral adult users';
COMMENT ON COLUMN server.config.default_avatar_male_child IS
'Default server avatar for male child users';
COMMENT ON COLUMN server.config.default_avatar_male_adult IS
'Default server avatar for male adult users';
COMMENT ON COLUMN server.config.default_avatar_female_child IS
'Default server avatar for female child users';
COMMENT ON COLUMN server.config.default_avatar_female_adult IS
'Default server avatar for female adult users';
-- =============================================================================
-- Add FK for auth.active_avatars.selected_server_avatar_id
-- =============================================================================
ALTER TABLE auth.active_avatars
ADD CONSTRAINT fk_auth_active_avatars_selected_server_avatar
FOREIGN KEY (selected_server_avatar_id) REFERENCES server.avatars(id) ON DELETE SET NULL;

View file

@ -35,6 +35,15 @@ CREATE TABLE realm.realms (
default_scene_id UUID,
-- Default avatars for this realm (optional, override server defaults)
-- FK constraints added in 035_realm_avatars.sql after realm.avatars exists
default_avatar_neutral_child UUID,
default_avatar_neutral_adult UUID,
default_avatar_male_child UUID,
default_avatar_male_adult UUID,
default_avatar_female_child UUID,
default_avatar_female_adult UUID,
member_count INTEGER NOT NULL DEFAULT 0 CHECK (member_count >= 0),
current_user_count INTEGER NOT NULL DEFAULT 0 CHECK (current_user_count >= 0),

View file

@ -0,0 +1,288 @@
-- =============================================================================
-- Realm Avatars and Forced Avatar Support
-- =============================================================================
-- Pre-configured avatar configurations specific to a realm.
-- These reference realm.props directly (not inventory items).
--
-- Also adds forced avatar support to auth.active_avatars and realm.scenes.
--
-- Loaded after 030_realm.sql (realm.props must exist)
-- =============================================================================
-- =============================================================================
-- Realm Avatars Table
-- =============================================================================
CREATE TABLE realm.avatars (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
realm_id UUID NOT NULL REFERENCES realm.realms(id) ON DELETE CASCADE,
slug public.slug NOT NULL,
name public.nonempty_text NOT NULL,
description TEXT,
is_public BOOLEAN NOT NULL DEFAULT false,
is_active BOOLEAN NOT NULL DEFAULT true,
thumbnail_path public.asset_path,
-- Content layers: 3 layers x 9 positions = 27 slots
-- All reference realm.props(id) directly (realm props only, no server props)
l_skin_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_skin_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_skin_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_skin_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_skin_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_skin_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_skin_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_skin_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_skin_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_clothes_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_clothes_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_clothes_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_clothes_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_clothes_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_clothes_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_clothes_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_clothes_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_clothes_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_accessories_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_accessories_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_accessories_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_accessories_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_accessories_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_accessories_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_accessories_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_accessories_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
l_accessories_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
-- Emotion layers: 12 emotions x 9 positions = 108 slots
e_neutral_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_neutral_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_neutral_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_neutral_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_neutral_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_neutral_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_neutral_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_neutral_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_neutral_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_happy_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_happy_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_happy_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_happy_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_happy_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_happy_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_happy_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_happy_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_happy_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sad_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sad_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sad_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sad_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sad_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sad_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sad_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sad_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sad_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_angry_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_angry_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_angry_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_angry_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_angry_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_angry_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_angry_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_angry_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_angry_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_surprised_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_surprised_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_surprised_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_surprised_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_surprised_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_surprised_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_surprised_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_surprised_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_surprised_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_thinking_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_thinking_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_thinking_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_thinking_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_thinking_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_thinking_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_thinking_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_thinking_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_thinking_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_laughing_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_laughing_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_laughing_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_laughing_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_laughing_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_laughing_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_laughing_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_laughing_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_laughing_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_crying_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_crying_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_crying_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_crying_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_crying_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_crying_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_crying_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_crying_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_crying_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_love_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_love_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_love_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_love_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_love_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_love_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_love_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_love_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_love_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_confused_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_confused_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_confused_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_confused_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_confused_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_confused_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_confused_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_confused_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_confused_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sleeping_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sleeping_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sleeping_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sleeping_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sleeping_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sleeping_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sleeping_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sleeping_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_sleeping_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_wink_0 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_wink_1 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_wink_2 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_wink_3 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_wink_4 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_wink_5 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_wink_6 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_wink_7 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
e_wink_8 UUID REFERENCES realm.props(id) ON DELETE SET NULL,
created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT uq_realm_avatars_slug UNIQUE (realm_id, slug)
);
COMMENT ON TABLE realm.avatars IS 'Pre-configured avatar configurations specific to a realm (references realm.props only)';
CREATE INDEX idx_realm_avatars_realm ON realm.avatars (realm_id);
CREATE INDEX idx_realm_avatars_active ON realm.avatars (realm_id, is_active) WHERE is_active = true;
CREATE INDEX idx_realm_avatars_public ON realm.avatars (realm_id, is_public) WHERE is_public = true;
COMMENT ON COLUMN realm.avatars.is_public IS
'When true, avatar appears in the public avatar selection for this realm. Uses filtered index.';
-- =============================================================================
-- Add Default Avatar FK Constraints to realm.realms
-- =============================================================================
-- Now that realm.avatars exists, add the FK constraints for default avatars.
-- =============================================================================
ALTER TABLE realm.realms
ADD CONSTRAINT fk_realm_default_avatar_neutral_child
FOREIGN KEY (default_avatar_neutral_child) REFERENCES realm.avatars(id) ON DELETE SET NULL,
ADD CONSTRAINT fk_realm_default_avatar_neutral_adult
FOREIGN KEY (default_avatar_neutral_adult) REFERENCES realm.avatars(id) ON DELETE SET NULL,
ADD CONSTRAINT fk_realm_default_avatar_male_child
FOREIGN KEY (default_avatar_male_child) REFERENCES realm.avatars(id) ON DELETE SET NULL,
ADD CONSTRAINT fk_realm_default_avatar_male_adult
FOREIGN KEY (default_avatar_male_adult) REFERENCES realm.avatars(id) ON DELETE SET NULL,
ADD CONSTRAINT fk_realm_default_avatar_female_child
FOREIGN KEY (default_avatar_female_child) REFERENCES realm.avatars(id) ON DELETE SET NULL,
ADD CONSTRAINT fk_realm_default_avatar_female_adult
FOREIGN KEY (default_avatar_female_adult) REFERENCES realm.avatars(id) ON DELETE SET NULL;
COMMENT ON COLUMN realm.realms.default_avatar_neutral_child IS
'Default realm avatar for gender-neutral child users (overrides server default)';
COMMENT ON COLUMN realm.realms.default_avatar_neutral_adult IS
'Default realm avatar for gender-neutral adult users (overrides server default)';
COMMENT ON COLUMN realm.realms.default_avatar_male_child IS
'Default realm avatar for male child users (overrides server default)';
COMMENT ON COLUMN realm.realms.default_avatar_male_adult IS
'Default realm avatar for male adult users (overrides server default)';
COMMENT ON COLUMN realm.realms.default_avatar_female_child IS
'Default realm avatar for female child users (overrides server default)';
COMMENT ON COLUMN realm.realms.default_avatar_female_adult IS
'Default realm avatar for female adult users (overrides server default)';
-- =============================================================================
-- Add FK for auth.active_avatars.selected_realm_avatar_id
-- =============================================================================
ALTER TABLE auth.active_avatars
ADD CONSTRAINT fk_auth_active_avatars_selected_realm_avatar
FOREIGN KEY (selected_realm_avatar_id) REFERENCES realm.avatars(id) ON DELETE SET NULL;
-- =============================================================================
-- Add Forced Avatar Columns to auth.active_avatars
-- =============================================================================
-- Tracks when a user has a forced avatar (from mod command or scene entry)
ALTER TABLE auth.active_avatars
ADD COLUMN forced_avatar_id UUID,
ADD COLUMN forced_avatar_source TEXT
CHECK (forced_avatar_source IS NULL OR forced_avatar_source IN ('server', 'realm', 'scene')),
ADD COLUMN forced_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
ADD COLUMN forced_until TIMESTAMPTZ;
COMMENT ON COLUMN auth.active_avatars.forced_avatar_id IS
'UUID of the forced avatar (from server.avatars or realm.avatars depending on source)';
COMMENT ON COLUMN auth.active_avatars.forced_avatar_source IS
'Source of forced avatar: server (server.avatars), realm (realm.avatars), or scene (from scene setting)';
COMMENT ON COLUMN auth.active_avatars.forced_by IS
'User who forced this avatar (moderator), NULL for scene-forced';
COMMENT ON COLUMN auth.active_avatars.forced_until IS
'When the forced avatar expires, NULL for permanent/until manually cleared';
-- Index for finding expired forced avatars
CREATE INDEX idx_auth_active_avatars_forced_expires
ON auth.active_avatars (forced_until)
WHERE forced_until IS NOT NULL;
-- =============================================================================
-- Add Forced Avatar Columns to realm.scenes
-- =============================================================================
-- Allows scenes to force all users to wear a specific avatar
ALTER TABLE realm.scenes
ADD COLUMN forced_avatar_id UUID,
ADD COLUMN forced_avatar_source TEXT
CHECK (forced_avatar_source IS NULL OR forced_avatar_source IN ('server', 'realm'));
COMMENT ON COLUMN realm.scenes.forced_avatar_id IS
'UUID of avatar all users must wear in this scene (from server.avatars or realm.avatars)';
COMMENT ON COLUMN realm.scenes.forced_avatar_source IS
'Source of forced avatar: server (server.avatars) or realm (realm.avatars)';
-- =============================================================================
-- Add New Moderation Action Types
-- =============================================================================
ALTER TYPE server.action_type ADD VALUE 'dress_user';
ALTER TYPE server.action_type ADD VALUE 'undress_user';
ALTER TYPE server.action_type ADD VALUE 'teleport';
COMMENT ON TYPE server.action_type IS 'Type of moderation action taken (includes dress_user, undress_user, teleport)';

View file

@ -1,26 +0,0 @@
-- Chattyness User Initialization Trigger
-- PostgreSQL 18
--
-- Trigger to initialize new users with default props and avatar.
-- Load via: psql -f schema/triggers/002_user_init.sql
\set ON_ERROR_STOP on
BEGIN;
-- =============================================================================
-- User Registration Trigger
-- =============================================================================
-- Automatically initializes new users with default props and avatar
-- when they are inserted into auth.users.
-- =============================================================================
CREATE TRIGGER trg_auth_users_initialize
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION auth.initialize_new_user_trigger();
COMMENT ON TRIGGER trg_auth_users_initialize ON auth.users IS
'Initialize new users with default props and avatar on registration';
COMMIT;

View file

@ -135,6 +135,21 @@ COMMENT ON TYPE server.filter_action IS 'Action to take when content filter matc
-- Authentication Enums
-- =============================================================================
-- Gender preference for default avatar selection
CREATE TYPE auth.gender_preference AS ENUM (
'gender_neutral',
'gender_male',
'gender_female'
);
COMMENT ON TYPE auth.gender_preference IS 'User gender preference for selecting default avatars';
-- Age category for default avatar selection
CREATE TYPE auth.age_category AS ENUM (
'child',
'adult'
);
COMMENT ON TYPE auth.age_category IS 'User age category for selecting default avatars';
-- User account tags for feature gating and access control
CREATE TYPE auth.user_tag AS ENUM (
'guest',