Add AI-assisted world and character drafting flows
All checks were successful
CI / validate (push) Successful in 14m3s
All checks were successful
CI / validate (push) Successful in 14m3s
This commit is contained in:
@@ -2030,6 +2030,151 @@ grant execute on function public.dng_schema_version() to service_role;
|
||||
|
||||
notify pgrst, 'reload schema';
|
||||
|
||||
-- ============================================================================
|
||||
-- 0007_stage_three_four_completion.sql
|
||||
-- ============================================================================
|
||||
|
||||
-- Complete the universe/character alpha slice: resumable coauthor questions and
|
||||
-- an atomic, server-only character creation path.
|
||||
|
||||
alter table public.coauthor_sessions
|
||||
add column if not exists current_question jsonb;
|
||||
|
||||
alter table public.coauthor_sessions
|
||||
drop constraint if exists coauthor_sessions_current_question_object;
|
||||
alter table public.coauthor_sessions
|
||||
add constraint coauthor_sessions_current_question_object check (
|
||||
current_question is null or jsonb_typeof(current_question) = 'object'
|
||||
);
|
||||
|
||||
create or replace function public.stage_two_append_coauthor_message(
|
||||
p_session_id uuid,
|
||||
p_owner_id uuid,
|
||||
p_content text
|
||||
) returns jsonb language plpgsql security definer set search_path = '' as $$
|
||||
declare
|
||||
v_session public.coauthor_sessions%rowtype;
|
||||
v_result jsonb;
|
||||
begin
|
||||
if p_content is null or char_length(btrim(p_content)) not between 1 and 5000 then
|
||||
raise exception 'message must be between 1 and 5000 characters';
|
||||
end if;
|
||||
select * into v_session from public.coauthor_sessions
|
||||
where id = p_session_id and owner_id = p_owner_id for update;
|
||||
if not found then raise exception 'coauthor session not found'; end if;
|
||||
if v_session.status in ('generating', 'confirmed') then
|
||||
raise exception 'coauthor session cannot be edited in status %', v_session.status;
|
||||
end if;
|
||||
update public.coauthor_sessions
|
||||
set messages = messages || jsonb_build_array(jsonb_build_object('role', 'user', 'content', btrim(p_content))),
|
||||
generated_world = null,
|
||||
current_question = null,
|
||||
status = 'collecting',
|
||||
updated_at = now()
|
||||
where id = p_session_id
|
||||
returning jsonb_build_object('id', id, 'status', status, 'messages', messages, 'updatedAt', updated_at)
|
||||
into v_result;
|
||||
return v_result;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function public.stage_four_set_coauthor_question(
|
||||
p_session_id uuid,
|
||||
p_owner_id uuid,
|
||||
p_question jsonb
|
||||
) returns jsonb language plpgsql security definer set search_path = '' as $$
|
||||
declare
|
||||
v_result jsonb;
|
||||
begin
|
||||
if p_question is null
|
||||
or jsonb_typeof(p_question) <> 'object'
|
||||
or nullif(btrim(p_question->>'id'), '') is null
|
||||
or char_length(btrim(p_question->>'label')) not between 5 and 300
|
||||
or jsonb_typeof(p_question->'options') <> 'array'
|
||||
or jsonb_array_length(p_question->'options') <> 3 then
|
||||
raise exception 'invalid coauthor question';
|
||||
end if;
|
||||
|
||||
update public.coauthor_sessions
|
||||
set messages = messages || jsonb_build_array(jsonb_build_object(
|
||||
'role', 'assistant', 'content', btrim(p_question->>'label')
|
||||
)),
|
||||
current_question = p_question,
|
||||
updated_at = now()
|
||||
where id = p_session_id and owner_id = p_owner_id and status = 'collecting'
|
||||
returning current_question into v_result;
|
||||
if not found then raise exception 'collecting coauthor session not found'; end if;
|
||||
return v_result;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function public.stage_four_create_character(
|
||||
p_campaign_id uuid,
|
||||
p_actor_id uuid,
|
||||
p_controller text,
|
||||
p_name text,
|
||||
p_concept text,
|
||||
p_abilities jsonb,
|
||||
p_hp integer,
|
||||
p_max_hp integer,
|
||||
p_defense integer,
|
||||
p_proficiency integer,
|
||||
p_inventory jsonb,
|
||||
p_statuses jsonb,
|
||||
p_persona jsonb
|
||||
) returns uuid language plpgsql security definer set search_path = '' as $$
|
||||
declare
|
||||
v_character_id uuid;
|
||||
v_user_id uuid;
|
||||
begin
|
||||
if p_controller not in ('human', 'ai') then raise exception 'invalid character controller'; end if;
|
||||
if not exists (
|
||||
select 1 from public.campaign_members
|
||||
where campaign_id = p_campaign_id and user_id = p_actor_id and active
|
||||
) then raise exception 'active campaign membership is required'; end if;
|
||||
|
||||
if p_controller = 'ai' then
|
||||
if not exists (
|
||||
select 1 from public.campaigns where id = p_campaign_id and owner_id = p_actor_id
|
||||
) then raise exception 'only the campaign owner can add AI heroes'; end if;
|
||||
v_user_id := null;
|
||||
else
|
||||
v_user_id := p_actor_id;
|
||||
perform pg_catalog.pg_advisory_xact_lock(
|
||||
pg_catalog.hashtextextended(p_campaign_id::text || ':' || p_actor_id::text, 0)
|
||||
);
|
||||
if exists (
|
||||
select 1 from public.characters
|
||||
where campaign_id = p_campaign_id and user_id = p_actor_id
|
||||
) then raise exception 'this player already has a character'; end if;
|
||||
end if;
|
||||
|
||||
insert into public.characters(
|
||||
campaign_id, user_id, name, concept, controller, abilities, hp, max_hp,
|
||||
defense, proficiency, inventory, statuses, persona
|
||||
) values (
|
||||
p_campaign_id, v_user_id, btrim(p_name), btrim(p_concept),
|
||||
p_controller::public.character_controller, p_abilities, p_hp, p_max_hp,
|
||||
p_defense, p_proficiency, p_inventory, p_statuses, p_persona
|
||||
) returning id into v_character_id;
|
||||
return v_character_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function public.dng_schema_version()
|
||||
returns integer language sql stable security definer set search_path = '' as $$
|
||||
select 7;
|
||||
$$;
|
||||
|
||||
revoke all on function public.stage_four_set_coauthor_question(uuid, uuid, jsonb) from public, anon, authenticated;
|
||||
revoke all on function public.stage_four_create_character(uuid, uuid, text, text, text, jsonb, integer, integer, integer, integer, jsonb, jsonb, jsonb) from public, anon, authenticated;
|
||||
revoke all on function public.dng_schema_version() from public, anon, authenticated;
|
||||
grant execute on function public.stage_four_set_coauthor_question(uuid, uuid, jsonb) to service_role;
|
||||
grant execute on function public.stage_four_create_character(uuid, uuid, text, text, text, jsonb, integer, integer, integer, integer, jsonb, jsonb, jsonb) to service_role;
|
||||
grant execute on function public.dng_schema_version() to service_role;
|
||||
|
||||
notify pgrst, 'reload schema';
|
||||
|
||||
|
||||
do $$
|
||||
begin
|
||||
@@ -2051,6 +2196,12 @@ begin
|
||||
if to_regprocedure('public.dng_schema_version()') is null then
|
||||
raise exception 'D&G bootstrap verification failed: schema version RPC is missing';
|
||||
end if;
|
||||
if to_regprocedure('public.stage_four_create_character(uuid,uuid,text,text,text,jsonb,integer,integer,integer,integer,jsonb,jsonb,jsonb)') is null then
|
||||
raise exception 'D&G bootstrap verification failed: character creation RPC is missing';
|
||||
end if;
|
||||
if public.dng_schema_version() <> 7 then
|
||||
raise exception 'D&G bootstrap verification failed: unexpected schema version';
|
||||
end if;
|
||||
end;
|
||||
$$;
|
||||
|
||||
|
||||
140
supabase/migrations/0007_stage_three_four_completion.sql
Normal file
140
supabase/migrations/0007_stage_three_four_completion.sql
Normal file
@@ -0,0 +1,140 @@
|
||||
-- Complete the universe/character alpha slice: resumable coauthor questions and
|
||||
-- an atomic, server-only character creation path.
|
||||
|
||||
alter table public.coauthor_sessions
|
||||
add column if not exists current_question jsonb;
|
||||
|
||||
alter table public.coauthor_sessions
|
||||
drop constraint if exists coauthor_sessions_current_question_object;
|
||||
alter table public.coauthor_sessions
|
||||
add constraint coauthor_sessions_current_question_object check (
|
||||
current_question is null or jsonb_typeof(current_question) = 'object'
|
||||
);
|
||||
|
||||
create or replace function public.stage_two_append_coauthor_message(
|
||||
p_session_id uuid,
|
||||
p_owner_id uuid,
|
||||
p_content text
|
||||
) returns jsonb language plpgsql security definer set search_path = '' as $$
|
||||
declare
|
||||
v_session public.coauthor_sessions%rowtype;
|
||||
v_result jsonb;
|
||||
begin
|
||||
if p_content is null or char_length(btrim(p_content)) not between 1 and 5000 then
|
||||
raise exception 'message must be between 1 and 5000 characters';
|
||||
end if;
|
||||
select * into v_session from public.coauthor_sessions
|
||||
where id = p_session_id and owner_id = p_owner_id for update;
|
||||
if not found then raise exception 'coauthor session not found'; end if;
|
||||
if v_session.status in ('generating', 'confirmed') then
|
||||
raise exception 'coauthor session cannot be edited in status %', v_session.status;
|
||||
end if;
|
||||
update public.coauthor_sessions
|
||||
set messages = messages || jsonb_build_array(jsonb_build_object('role', 'user', 'content', btrim(p_content))),
|
||||
generated_world = null,
|
||||
current_question = null,
|
||||
status = 'collecting',
|
||||
updated_at = now()
|
||||
where id = p_session_id
|
||||
returning jsonb_build_object('id', id, 'status', status, 'messages', messages, 'updatedAt', updated_at)
|
||||
into v_result;
|
||||
return v_result;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function public.stage_four_set_coauthor_question(
|
||||
p_session_id uuid,
|
||||
p_owner_id uuid,
|
||||
p_question jsonb
|
||||
) returns jsonb language plpgsql security definer set search_path = '' as $$
|
||||
declare
|
||||
v_result jsonb;
|
||||
begin
|
||||
if p_question is null
|
||||
or jsonb_typeof(p_question) <> 'object'
|
||||
or nullif(btrim(p_question->>'id'), '') is null
|
||||
or char_length(btrim(p_question->>'label')) not between 5 and 300
|
||||
or jsonb_typeof(p_question->'options') <> 'array'
|
||||
or jsonb_array_length(p_question->'options') <> 3 then
|
||||
raise exception 'invalid coauthor question';
|
||||
end if;
|
||||
|
||||
update public.coauthor_sessions
|
||||
set messages = messages || jsonb_build_array(jsonb_build_object(
|
||||
'role', 'assistant', 'content', btrim(p_question->>'label')
|
||||
)),
|
||||
current_question = p_question,
|
||||
updated_at = now()
|
||||
where id = p_session_id and owner_id = p_owner_id and status = 'collecting'
|
||||
returning current_question into v_result;
|
||||
if not found then raise exception 'collecting coauthor session not found'; end if;
|
||||
return v_result;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function public.stage_four_create_character(
|
||||
p_campaign_id uuid,
|
||||
p_actor_id uuid,
|
||||
p_controller text,
|
||||
p_name text,
|
||||
p_concept text,
|
||||
p_abilities jsonb,
|
||||
p_hp integer,
|
||||
p_max_hp integer,
|
||||
p_defense integer,
|
||||
p_proficiency integer,
|
||||
p_inventory jsonb,
|
||||
p_statuses jsonb,
|
||||
p_persona jsonb
|
||||
) returns uuid language plpgsql security definer set search_path = '' as $$
|
||||
declare
|
||||
v_character_id uuid;
|
||||
v_user_id uuid;
|
||||
begin
|
||||
if p_controller not in ('human', 'ai') then raise exception 'invalid character controller'; end if;
|
||||
if not exists (
|
||||
select 1 from public.campaign_members
|
||||
where campaign_id = p_campaign_id and user_id = p_actor_id and active
|
||||
) then raise exception 'active campaign membership is required'; end if;
|
||||
|
||||
if p_controller = 'ai' then
|
||||
if not exists (
|
||||
select 1 from public.campaigns where id = p_campaign_id and owner_id = p_actor_id
|
||||
) then raise exception 'only the campaign owner can add AI heroes'; end if;
|
||||
v_user_id := null;
|
||||
else
|
||||
v_user_id := p_actor_id;
|
||||
perform pg_catalog.pg_advisory_xact_lock(
|
||||
pg_catalog.hashtextextended(p_campaign_id::text || ':' || p_actor_id::text, 0)
|
||||
);
|
||||
if exists (
|
||||
select 1 from public.characters
|
||||
where campaign_id = p_campaign_id and user_id = p_actor_id
|
||||
) then raise exception 'this player already has a character'; end if;
|
||||
end if;
|
||||
|
||||
insert into public.characters(
|
||||
campaign_id, user_id, name, concept, controller, abilities, hp, max_hp,
|
||||
defense, proficiency, inventory, statuses, persona
|
||||
) values (
|
||||
p_campaign_id, v_user_id, btrim(p_name), btrim(p_concept),
|
||||
p_controller::public.character_controller, p_abilities, p_hp, p_max_hp,
|
||||
p_defense, p_proficiency, p_inventory, p_statuses, p_persona
|
||||
) returning id into v_character_id;
|
||||
return v_character_id;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function public.dng_schema_version()
|
||||
returns integer language sql stable security definer set search_path = '' as $$
|
||||
select 7;
|
||||
$$;
|
||||
|
||||
revoke all on function public.stage_four_set_coauthor_question(uuid, uuid, jsonb) from public, anon, authenticated;
|
||||
revoke all on function public.stage_four_create_character(uuid, uuid, text, text, text, jsonb, integer, integer, integer, integer, jsonb, jsonb, jsonb) from public, anon, authenticated;
|
||||
revoke all on function public.dng_schema_version() from public, anon, authenticated;
|
||||
grant execute on function public.stage_four_set_coauthor_question(uuid, uuid, jsonb) to service_role;
|
||||
grant execute on function public.stage_four_create_character(uuid, uuid, text, text, text, jsonb, integer, integer, integer, integer, jsonb, jsonb, jsonb) to service_role;
|
||||
grant execute on function public.dng_schema_version() to service_role;
|
||||
|
||||
notify pgrst, 'reload schema';
|
||||
Reference in New Issue
Block a user