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,111 @@
|
|||
-- 회의 생성 시 문서 템플릿은 선택 사항이다. 템플릿 없이도 회의를 시작할 수
|
||||
-- 있어야 하며(데스크톱 동작과 동일), 문서 생성 시점에 사용자 템플릿 라이브러리
|
||||
-- 에서 템플릿이 해결된다. meetings.template_id 컬럼과 소유권/생성 무결성
|
||||
-- 트리거(enforce_meeting_template_owner_v1, enforce_meeting_creation_identity_v1)
|
||||
-- 는 이미 NULL을 허용하므로 생성 RPC의 인자 검증만 완화한다.
|
||||
--
|
||||
-- 주의: idempotency 요청 해시에 template_id가 포함되므로, 같은 idempotency
|
||||
-- 키를 NULL/NOT NULL 템플릿으로 재시도하면 creation_request_hash 불일치로
|
||||
-- meeting_creation_idempotency_conflict가 발생한다 — 의도된 방어 동작이다.
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.mobile_create_meeting_workspace_v2(
|
||||
p_title text,
|
||||
p_attendees jsonb,
|
||||
p_language text,
|
||||
p_template_id uuid,
|
||||
p_idempotency_key uuid
|
||||
)
|
||||
RETURNS public.meetings
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = pg_catalog, public, auth, extensions
|
||||
AS $$
|
||||
DECLARE
|
||||
actor_id uuid := auth.uid();
|
||||
safe_title text := regexp_replace(trim(p_title), '[[:space:]]+', ' ', 'g');
|
||||
safe_language text := lower(trim(p_language));
|
||||
safe_attendees jsonb;
|
||||
request_digest text;
|
||||
created public.meetings;
|
||||
BEGIN
|
||||
IF actor_id IS NULL THEN
|
||||
RAISE EXCEPTION 'authentication_required' USING ERRCODE = '42501';
|
||||
END IF;
|
||||
IF p_idempotency_key IS NULL THEN
|
||||
RAISE EXCEPTION 'meeting_creation_identifiers_required' USING ERRCODE = '22023';
|
||||
END IF;
|
||||
IF p_title IS NULL OR char_length(safe_title) NOT BETWEEN 1 AND 160 THEN
|
||||
RAISE EXCEPTION 'invalid_meeting_title' USING ERRCODE = '22023';
|
||||
END IF;
|
||||
IF p_language IS NULL OR safe_language NOT IN ('ko', 'en', 'ja', 'zh-cn') THEN
|
||||
RAISE EXCEPTION 'invalid_meeting_language' USING ERRCODE = '22023';
|
||||
END IF;
|
||||
IF NOT public.is_valid_meeting_attendees_v1(p_attendees) THEN
|
||||
RAISE EXCEPTION 'invalid_meeting_attendees' USING ERRCODE = '22023';
|
||||
END IF;
|
||||
IF p_template_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM public.user_templates
|
||||
WHERE id = p_template_id
|
||||
AND user_id = actor_id
|
||||
AND template_kind = 'meeting_document'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'meeting_template_not_found' USING ERRCODE = 'P0002';
|
||||
END IF;
|
||||
|
||||
PERFORM set_config('d3ro.meeting_creation_actor', actor_id::text, true);
|
||||
|
||||
SELECT coalesce(
|
||||
jsonb_agg(
|
||||
to_jsonb(regexp_replace(trim(item.value #>> '{}'), '[[:space:]]+', ' ', 'g'))
|
||||
ORDER BY item.ordinality
|
||||
),
|
||||
'[]'::jsonb
|
||||
)
|
||||
INTO safe_attendees
|
||||
FROM jsonb_array_elements(p_attendees) WITH ORDINALITY AS item(value, ordinality);
|
||||
|
||||
request_digest := encode(extensions.digest(
|
||||
jsonb_build_object(
|
||||
'title', safe_title,
|
||||
'attendees', safe_attendees,
|
||||
'language', safe_language,
|
||||
'template_id', p_template_id
|
||||
)::text,
|
||||
'sha256'
|
||||
), 'hex');
|
||||
|
||||
INSERT INTO public.meetings(
|
||||
user_id, title, status, language, attendees, template_id,
|
||||
creation_idempotency_key, creation_request_hash
|
||||
)
|
||||
VALUES (
|
||||
actor_id, safe_title, 'recording', safe_language, safe_attendees,
|
||||
p_template_id, p_idempotency_key, request_digest
|
||||
)
|
||||
ON CONFLICT (user_id, creation_idempotency_key)
|
||||
WHERE creation_idempotency_key IS NOT NULL
|
||||
DO NOTHING
|
||||
RETURNING * INTO created;
|
||||
|
||||
IF created.id IS NULL THEN
|
||||
SELECT * INTO created
|
||||
FROM public.meetings
|
||||
WHERE user_id = actor_id
|
||||
AND creation_idempotency_key = p_idempotency_key;
|
||||
IF created.id IS NULL THEN
|
||||
RAISE EXCEPTION 'meeting_creation_retry_state_missing' USING ERRCODE = 'PT409';
|
||||
END IF;
|
||||
IF created.creation_request_hash <> request_digest THEN
|
||||
RAISE EXCEPTION 'meeting_creation_idempotency_conflict' USING ERRCODE = 'PT409';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN created;
|
||||
END;
|
||||
$$;
|
||||
|
||||
REVOKE ALL ON FUNCTION public.mobile_create_meeting_workspace_v2(text, jsonb, text, uuid, uuid)
|
||||
FROM PUBLIC, anon;
|
||||
GRANT EXECUTE ON FUNCTION public.mobile_create_meeting_workspace_v2(text, jsonb, text, uuid, uuid)
|
||||
TO authenticated;
|
||||
Loading…
Add table
Add a link
Reference in a new issue