diff --git a/server/supabase/migrations/20260927000036_builtin_instruction_prompt_sync.sql b/server/supabase/migrations/20260927000036_builtin_instruction_prompt_sync.sql new file mode 100644 index 0000000..20c6740 --- /dev/null +++ b/server/supabase/migrations/20260927000036_builtin_instruction_prompt_sync.sql @@ -0,0 +1,101 @@ +-- Preset command prompts edited on the desktop reach the phone. +-- +-- Built-in custom_instructions rows are not writable by clients (RLS allows +-- only builtin_key IS NULL), and the phone executes every command with the +-- row's prompt. These RPCs let the owner change only the prompt of their own +-- preset row, or restore its default; name, key and order stay server-owned. +-- Defaults live in one function that bootstrap_custom_instructions' values +-- mirror, so "is this the default?" has a single answer. + +BEGIN; + +CREATE OR REPLACE FUNCTION public.builtin_instruction_default_prompt_v1(p_builtin_key text) +RETURNS text +LANGUAGE sql +IMMUTABLE +SET search_path = '' +AS $$ + SELECT CASE p_builtin_key + WHEN 'translate_en' THEN 'Translate the following text into natural English. Return only the translation.' + WHEN 'summarize' THEN 'Summarize the following text in no more than three concise lines.' + WHEN 'formal' THEN 'Rewrite the following text in a formal business style while preserving its meaning. Return only the rewritten text.' + WHEN 'explain_code' THEN 'Explain the following code in Korean, including its responsibilities and important caveats.' + END +$$; + +REVOKE ALL ON FUNCTION public.builtin_instruction_default_prompt_v1(text) FROM PUBLIC, anon; +GRANT EXECUTE ON FUNCTION public.builtin_instruction_default_prompt_v1(text) TO authenticated; + +-- p_prompt NULL (or blank) restores the default prompt. +CREATE OR REPLACE FUNCTION public.sync_set_builtin_instruction_prompt_v1( + p_builtin_key text, + p_prompt text DEFAULT NULL +) +RETURNS public.custom_instructions +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = pg_catalog, public, auth +AS $$ +DECLARE + actor_id uuid := auth.uid(); + default_prompt text := public.builtin_instruction_default_prompt_v1(p_builtin_key); + next_prompt text; + saved public.custom_instructions; +BEGIN + IF actor_id IS NULL THEN + RAISE EXCEPTION 'authentication_required' USING ERRCODE = '42501'; + END IF; + IF default_prompt IS NULL THEN + RAISE EXCEPTION 'invalid_builtin_key' USING ERRCODE = '22023'; + END IF; + + next_prompt := coalesce(nullif(btrim(p_prompt), ''), default_prompt); + IF char_length(next_prompt) > 4000 THEN + RAISE EXCEPTION 'invalid_instruction_prompt' USING ERRCODE = '22023'; + END IF; + + -- The preset rows are created lazily; make sure this user has them. + PERFORM public.bootstrap_custom_instructions(); + + UPDATE public.custom_instructions + SET prompt = next_prompt + WHERE user_id = actor_id + AND builtin_key = p_builtin_key + AND prompt IS DISTINCT FROM next_prompt + RETURNING * INTO saved; + + IF saved.id IS NULL THEN + SELECT * INTO saved + FROM public.custom_instructions + WHERE user_id = actor_id AND builtin_key = p_builtin_key; + END IF; + RETURN saved; +END; +$$; + +REVOKE ALL ON FUNCTION public.sync_set_builtin_instruction_prompt_v1(text, text) FROM PUBLIC, anon; +GRANT EXECUTE ON FUNCTION public.sync_set_builtin_instruction_prompt_v1(text, text) TO authenticated; + +-- Preset rows with a flag telling whether the prompt is still the default. +CREATE OR REPLACE FUNCTION public.sync_list_builtin_instructions_v1() +RETURNS TABLE (builtin_key text, prompt text, is_default boolean, updated_at timestamptz) +LANGUAGE sql +STABLE +SECURITY INVOKER +SET search_path = '' +AS $$ + SELECT + instruction.builtin_key, + instruction.prompt, + instruction.prompt = public.builtin_instruction_default_prompt_v1(instruction.builtin_key), + instruction.updated_at + FROM public.custom_instructions AS instruction + WHERE instruction.user_id = auth.uid() + AND instruction.builtin_key IS NOT NULL + ORDER BY instruction.sort_order +$$; + +REVOKE ALL ON FUNCTION public.sync_list_builtin_instructions_v1() FROM PUBLIC, anon; +GRANT EXECUTE ON FUNCTION public.sync_list_builtin_instructions_v1() TO authenticated; + +COMMIT;