feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
|
|
@ -0,0 +1,267 @@
|
|||
BEGIN;
|
||||
|
||||
-- Dictionary identity is case-insensitive in every client. Consolidate any
|
||||
-- legacy case-only duplicates before enforcing that contract atomically.
|
||||
CREATE TEMP TABLE dictionary_duplicate_merge ON COMMIT DROP AS
|
||||
SELECT
|
||||
id,
|
||||
first_value(id) OVER identity_order AS keeper_id,
|
||||
row_number() OVER identity_order AS duplicate_rank
|
||||
FROM public.dictionary
|
||||
WINDOW identity_order AS (
|
||||
PARTITION BY user_id, lower(btrim(word)), category
|
||||
ORDER BY updated_at DESC, created_at DESC, id
|
||||
);
|
||||
|
||||
WITH aggregates AS (
|
||||
SELECT
|
||||
merge.keeper_id,
|
||||
sum(dictionary.usage_count)::integer AS usage_count,
|
||||
max(dictionary.last_used_at) AS last_used_at,
|
||||
(array_agg(dictionary.pronunciation ORDER BY dictionary.updated_at DESC)
|
||||
FILTER (WHERE dictionary.pronunciation IS NOT NULL AND btrim(dictionary.pronunciation) <> ''))[1]
|
||||
AS pronunciation
|
||||
FROM dictionary_duplicate_merge AS merge
|
||||
JOIN public.dictionary AS dictionary ON dictionary.id = merge.id
|
||||
GROUP BY merge.keeper_id
|
||||
)
|
||||
UPDATE public.dictionary AS dictionary
|
||||
SET
|
||||
word = btrim(dictionary.word),
|
||||
pronunciation = coalesce(aggregates.pronunciation, dictionary.pronunciation),
|
||||
usage_count = aggregates.usage_count,
|
||||
last_used_at = aggregates.last_used_at,
|
||||
updated_at = now()
|
||||
FROM aggregates
|
||||
WHERE dictionary.id = aggregates.keeper_id;
|
||||
|
||||
DELETE FROM public.dictionary AS dictionary
|
||||
USING dictionary_duplicate_merge AS merge
|
||||
WHERE dictionary.id = merge.id
|
||||
AND merge.duplicate_rank > 1;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_dictionary_user_word_category_normalized
|
||||
ON public.dictionary(user_id, lower(btrim(word)), category);
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.normalize_dictionary_word()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SET search_path = ''
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.word := btrim(NEW.word);
|
||||
IF NEW.word = '' OR char_length(NEW.word) > 120 THEN
|
||||
RAISE EXCEPTION 'invalid_dictionary_word' USING ERRCODE = '22023';
|
||||
END IF;
|
||||
IF NEW.pronunciation IS NOT NULL THEN
|
||||
NEW.pronunciation := nullif(btrim(NEW.pronunciation), '');
|
||||
IF char_length(NEW.pronunciation) > 200 THEN
|
||||
RAISE EXCEPTION 'invalid_dictionary_pronunciation' USING ERRCODE = '22023';
|
||||
END IF;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS normalize_dictionary_word_before_write ON public.dictionary;
|
||||
CREATE TRIGGER normalize_dictionary_word_before_write
|
||||
BEFORE INSERT OR UPDATE OF word, pronunciation ON public.dictionary
|
||||
FOR EACH ROW EXECUTE FUNCTION public.normalize_dictionary_word();
|
||||
|
||||
CREATE TABLE public.custom_instructions (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
builtin_key text,
|
||||
name text NOT NULL CHECK (char_length(btrim(name)) BETWEEN 1 AND 80),
|
||||
description text NOT NULL DEFAULT '' CHECK (char_length(description) <= 240),
|
||||
prompt text NOT NULL CHECK (char_length(btrim(prompt)) BETWEEN 1 AND 4000),
|
||||
icon text NOT NULL DEFAULT 'sparkles' CHECK (char_length(icon) BETWEEN 1 AND 32),
|
||||
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
|
||||
revision bigint NOT NULL DEFAULT 1 CHECK (revision > 0),
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
CHECK (builtin_key IS NULL OR builtin_key IN (
|
||||
'translate_en', 'summarize', 'formal', 'explain_code'
|
||||
))
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_custom_instructions_builtin
|
||||
ON public.custom_instructions(user_id, builtin_key)
|
||||
WHERE builtin_key IS NOT NULL;
|
||||
CREATE UNIQUE INDEX idx_custom_instructions_name_normalized
|
||||
ON public.custom_instructions(user_id, lower(btrim(name)));
|
||||
CREATE INDEX idx_custom_instructions_order
|
||||
ON public.custom_instructions(user_id, sort_order, created_at, id);
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.bump_custom_instruction_revision()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SET search_path = ''
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.name := btrim(NEW.name);
|
||||
NEW.description := btrim(NEW.description);
|
||||
NEW.prompt := btrim(NEW.prompt);
|
||||
NEW.revision := OLD.revision + 1;
|
||||
NEW.updated_at := now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER bump_custom_instruction_revision_before_update
|
||||
BEFORE UPDATE ON public.custom_instructions
|
||||
FOR EACH ROW EXECUTE FUNCTION public.bump_custom_instruction_revision();
|
||||
|
||||
ALTER TABLE public.custom_instructions ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "custom_instructions_read_own" ON public.custom_instructions
|
||||
FOR SELECT USING (user_id = auth.uid());
|
||||
CREATE POLICY "custom_instructions_insert_custom" ON public.custom_instructions
|
||||
FOR INSERT WITH CHECK (user_id = auth.uid() AND builtin_key IS NULL);
|
||||
CREATE POLICY "custom_instructions_update_custom" ON public.custom_instructions
|
||||
FOR UPDATE
|
||||
USING (user_id = auth.uid() AND builtin_key IS NULL)
|
||||
WITH CHECK (user_id = auth.uid() AND builtin_key IS NULL);
|
||||
CREATE POLICY "custom_instructions_delete_custom" ON public.custom_instructions
|
||||
FOR DELETE USING (user_id = auth.uid() AND builtin_key IS NULL);
|
||||
|
||||
REVOKE ALL ON public.custom_instructions FROM anon;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON public.custom_instructions TO authenticated;
|
||||
|
||||
ALTER TABLE public.user_settings
|
||||
ADD COLUMN active_instruction_id uuid
|
||||
REFERENCES public.custom_instructions(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.validate_active_instruction_owner()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = ''
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW.active_instruction_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM public.custom_instructions AS instruction
|
||||
WHERE instruction.id = NEW.active_instruction_id
|
||||
AND instruction.user_id = NEW.user_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'active_instruction_not_owned' USING ERRCODE = '42501';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER validate_active_instruction_owner_before_write
|
||||
BEFORE INSERT OR UPDATE OF active_instruction_id ON public.user_settings
|
||||
FOR EACH ROW EXECUTE FUNCTION public.validate_active_instruction_owner();
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.bootstrap_custom_instructions()
|
||||
RETURNS SETOF public.custom_instructions
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = ''
|
||||
AS $$
|
||||
DECLARE
|
||||
current_user_id uuid := auth.uid();
|
||||
BEGIN
|
||||
IF current_user_id IS NULL THEN
|
||||
RAISE EXCEPTION 'authentication_required' USING ERRCODE = '42501';
|
||||
END IF;
|
||||
|
||||
INSERT INTO public.custom_instructions (
|
||||
user_id, builtin_key, name, description, prompt, icon, sort_order
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
current_user_id,
|
||||
'translate_en',
|
||||
'Translate to English',
|
||||
'Translate the input into natural English.',
|
||||
'Translate the following text into natural English. Return only the translation.',
|
||||
'translate',
|
||||
10
|
||||
),
|
||||
(
|
||||
current_user_id,
|
||||
'summarize',
|
||||
'Key summary',
|
||||
'Summarize the key points in no more than three lines.',
|
||||
'Summarize the following text in no more than three concise lines.',
|
||||
'list',
|
||||
20
|
||||
),
|
||||
(
|
||||
current_user_id,
|
||||
'formal',
|
||||
'Professional rewrite',
|
||||
'Rewrite in a formal business style while preserving meaning.',
|
||||
'Rewrite the following text in a formal business style while preserving its meaning. Return only the rewritten text.',
|
||||
'briefcase',
|
||||
30
|
||||
),
|
||||
(
|
||||
current_user_id,
|
||||
'explain_code',
|
||||
'Explain code',
|
||||
'Explain responsibilities and caveats in Korean.',
|
||||
'Explain the following code in Korean, including its responsibilities and important caveats.',
|
||||
'code',
|
||||
40
|
||||
)
|
||||
ON CONFLICT (user_id, builtin_key) WHERE builtin_key IS NOT NULL DO NOTHING;
|
||||
|
||||
INSERT INTO public.user_settings (user_id)
|
||||
VALUES (current_user_id)
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
RETURN QUERY
|
||||
SELECT instruction.*
|
||||
FROM public.custom_instructions AS instruction
|
||||
WHERE instruction.user_id = current_user_id
|
||||
ORDER BY instruction.sort_order, instruction.created_at, instruction.id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.set_active_custom_instruction(
|
||||
instruction_id uuid
|
||||
)
|
||||
RETURNS public.user_settings
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = ''
|
||||
AS $$
|
||||
DECLARE
|
||||
current_user_id uuid := auth.uid();
|
||||
settings public.user_settings;
|
||||
BEGIN
|
||||
IF current_user_id IS NULL THEN
|
||||
RAISE EXCEPTION 'authentication_required' USING ERRCODE = '42501';
|
||||
END IF;
|
||||
IF instruction_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM public.custom_instructions AS instruction
|
||||
WHERE instruction.id = instruction_id
|
||||
AND instruction.user_id = current_user_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'instruction_not_found' USING ERRCODE = 'P0002';
|
||||
END IF;
|
||||
|
||||
INSERT INTO public.user_settings (user_id, active_instruction_id)
|
||||
VALUES (current_user_id, instruction_id)
|
||||
ON CONFLICT (user_id) DO UPDATE
|
||||
SET
|
||||
active_instruction_id = EXCLUDED.active_instruction_id,
|
||||
revision = public.user_settings.revision + 1
|
||||
RETURNING * INTO settings;
|
||||
RETURN settings;
|
||||
END;
|
||||
$$;
|
||||
|
||||
REVOKE ALL ON FUNCTION public.bootstrap_custom_instructions() FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION public.bootstrap_custom_instructions() TO authenticated;
|
||||
REVOKE ALL ON FUNCTION public.set_active_custom_instruction(uuid) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION public.set_active_custom_instruction(uuid) TO authenticated;
|
||||
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE public.custom_instructions;
|
||||
|
||||
COMMIT;
|
||||
Loading…
Add table
Add a link
Reference in a new issue