begin; alter table public.consultation_requests add column if not exists session_id uuid references public.chat_sessions(id) on delete set null, add column if not exists response_message jsonb; alter table public.consultation_requests drop constraint if exists consultation_requests_response_message_check; alter table public.consultation_requests add constraint consultation_requests_response_message_check check (response_message is null or jsonb_typeof(response_message) = 'object'); create index if not exists consultation_requests_user_session_created_idx on public.consultation_requests (user_id, session_id, created_at desc); create index if not exists consultation_requests_user_status_created_idx on public.consultation_requests (user_id, status, created_at desc); drop function if exists public.reserve_consultation_usage(uuid, text, text, integer); create or replace function public.reserve_consultation_usage( p_user_id uuid, p_request_id text, p_session_id uuid, p_requested_model_id text, p_credit_cost integer ) returns table( success boolean, reservation_id uuid, source text, credits integer, subscription_id uuid, reason text, retry_after_seconds integer ) language plpgsql security definer set search_path = '' as $$ declare v_authorization record; begin if p_user_id is null or p_session_id is null or btrim(coalesce(p_request_id, '')) = '' then return query select false, null::uuid, null::text, null::integer, null::uuid, 'invalid_request'::text, null::integer; return; end if; perform pg_advisory_xact_lock(hashtextextended(p_user_id::text || ':' || btrim(p_request_id), 0)); if not exists ( select 1 from public.chat_sessions as session where session.id = p_session_id and session.user_id = p_user_id and session.session_type = 'consultation' ) then return query select false, null::uuid, null::text, (select profile.credits from public.profiles as profile where profile.id = p_user_id), null::uuid, 'session_missing'::text, null::integer; return; end if; if exists ( select 1 from public.consultation_requests as request where request.user_id = p_user_id and request.request_id = btrim(p_request_id) ) then return query select false, null::uuid, null::text, (select profile.credits from public.profiles as profile where profile.id = p_user_id), null::uuid, 'request_conflict'::text, null::integer; return; end if; select * into v_authorization from public.authorize_usage( p_user_id, 'chat.standard', p_requested_model_id, btrim(p_request_id), p_credit_cost ); if coalesce(v_authorization.success, false) then insert into public.consultation_requests(user_id, request_id, session_id, status) values (p_user_id, btrim(p_request_id), p_session_id, 'reserved'); end if; return query select v_authorization.success, v_authorization.reservation_id, v_authorization.source, v_authorization.credits, v_authorization.subscription_id, v_authorization.reason, v_authorization.retry_after_seconds; end; $$; revoke all on function public.reserve_consultation_usage(uuid, text, uuid, text, integer) from public, anon, authenticated; grant execute on function public.reserve_consultation_usage(uuid, text, uuid, text, integer) to service_role; do $$ begin if exists(select 1 from pg_roles where rolname = 'admin_runtime') then grant execute on function public.reserve_consultation_usage(uuid, text, uuid, text, integer) to admin_runtime; end if; end $$; create or replace function public.cancel_consultation_credit(p_user_id uuid, p_request_id text) returns table(success boolean, credits integer, error_code text) language plpgsql security definer set search_path = '' as $$ declare v_request public.consultation_requests%rowtype; v_release record; v_balance integer; begin if p_user_id is null or btrim(coalesce(p_request_id, '')) = '' then return query select false, null::integer, 'invalid_request'::text; return; end if; perform pg_advisory_xact_lock(hashtextextended(p_user_id::text || ':' || btrim(p_request_id), 0)); select request.* into v_request from public.consultation_requests as request where request.user_id = p_user_id and request.request_id = btrim(p_request_id) for update; if not found then return query select false, null::integer, 'request_missing'::text; return; end if; select profile.credits into v_balance from public.profiles as profile where profile.id = p_user_id; if v_request.status = 'completed' then return query select false, v_balance, 'request_completed'::text; return; end if; if v_request.status = 'cancelled' then return query select true, v_balance, null::text; return; end if; if v_request.status <> 'reserved' then return query select false, v_balance, 'invalid_request_status'::text; return; end if; select * into v_release from public.release_usage(p_user_id, btrim(p_request_id), 'consultation_cancelled'); if not coalesce(v_release.success, false) then return query select false, v_release.credits, v_release.error_code; return; end if; update public.consultation_requests set status = 'cancelled', updated_at = clock_timestamp() where user_id = p_user_id and request_id = btrim(p_request_id) and status = 'reserved'; return query select true, v_release.credits, null::text; end; $$; revoke all on function public.cancel_consultation_credit(uuid, text) from public, anon, authenticated; grant execute on function public.cancel_consultation_credit(uuid, text) to service_role; do $$ begin if exists(select 1 from pg_roles where rolname = 'admin_runtime') then grant execute on function public.cancel_consultation_credit(uuid, text) to admin_runtime; end if; end $$; create or replace function public.complete_consultation_response( p_user_id uuid, p_request_id text, p_session_id uuid, p_response_message jsonb, p_actual_usage jsonb ) returns table(success boolean, credits integer, error_code text) language plpgsql security definer set search_path = '' as $$ declare v_request public.consultation_requests%rowtype; v_settlement record; v_balance integer; begin if p_user_id is null or p_session_id is null or btrim(coalesce(p_request_id, '')) = '' then return query select false, null::integer, 'invalid_request'::text; return; end if; if jsonb_typeof(p_response_message) <> 'object' or p_response_message->>'role' <> 'assistant' or btrim(coalesce(p_response_message->>'text', '')) = '' then return query select false, null::integer, 'invalid_response_message'::text; return; end if; perform pg_advisory_xact_lock(hashtextextended(p_user_id::text || ':' || btrim(p_request_id), 0)); select request.* into v_request from public.consultation_requests as request where request.user_id = p_user_id and request.request_id = btrim(p_request_id) and request.session_id = p_session_id for update; if not found then return query select false, null::integer, 'request_missing'::text; return; end if; if v_request.status = 'cancelled' then return query select false, null::integer, 'request_cancelled'::text; return; end if; if v_request.status = 'completed' then select profile.credits into v_balance from public.profiles as profile where profile.id = p_user_id; return query select v_request.response_message = p_response_message, v_balance, case when v_request.response_message = p_response_message then null::text else 'response_conflict'::text end; return; end if; update public.chat_sessions as session set messages = session.messages || jsonb_build_array(p_response_message), updated_at = clock_timestamp() where session.id = p_session_id and session.user_id = p_user_id and session.session_type = 'consultation'; if not found then return query select false, null::integer, 'session_missing'::text; return; end if; update public.consultation_requests set response_message = p_response_message, updated_at = clock_timestamp() where user_id = p_user_id and request_id = btrim(p_request_id); select * into v_settlement from public.complete_usage(p_user_id, btrim(p_request_id), coalesce(p_actual_usage, '{}'::jsonb)); if not coalesce(v_settlement.success, false) then raise exception 'consultation_usage_settlement_failed:%', coalesce(v_settlement.error_code, 'unknown'); end if; update public.consultation_requests set status = 'completed', updated_at = clock_timestamp() where user_id = p_user_id and request_id = btrim(p_request_id); return query select true, v_settlement.credits, null::text; end; $$; revoke all on function public.complete_consultation_response(uuid, text, uuid, jsonb, jsonb) from public, anon, authenticated; grant execute on function public.complete_consultation_response(uuid, text, uuid, jsonb, jsonb) to service_role; do $$ begin if exists(select 1 from pg_roles where rolname = 'admin_runtime') then grant execute on function public.complete_consultation_response(uuid, text, uuid, jsonb, jsonb) to admin_runtime; end if; end $$; commit;