-- 카카오톡 채널 신원 연결 — 채널 발화자를 우리 user_id 에 묶는다. -- -- ★ 카카오 채널이 주는 발화자 식별자(channel_user_key)는 **채널 단위 익명 키**다. -- 우리 user_id 와 아무 관계가 없다. 이 표가 없으면 채널 진입점만 소유자 범위 -- 밖에 놓여, 채널에 말을 건 아무나가 남의 가게를 고친다 — 다른 모든 엔드포인트가 -- place_crud.get_place(s, owner_user_id, place_id) 로 지키는 경계다. -- -- ★ 코드는 평문으로 두지 않는다(code_sha). 사장님이 카톡에 손으로 치는 값이라 짧고, -- 짧은 값을 평문으로 들고 있으면 DB 를 읽을 수 있는 쪽이 곧 연결 권한을 갖는다. CREATE TABLE IF NOT EXISTS public.owner_kakao_links ( link_id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL, -- 연결이 끝나야 채워진다. PENDING 행은 아직 누구의 카톡인지 모른다. channel_user_key varchar(200), code_sha varchar(64), code_expires_at timestamptz, -- 소진된 코드 시도 횟수. 짧은 코드라 무차별 대입을 이 값으로 끊는다. code_attempts smallint NOT NULL DEFAULT 0, status varchar(16) NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING','LINKED','REVOKED')), linked_at timestamptz, last_seen_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), deleted boolean NOT NULL DEFAULT false ); -- 한 사장님은 활성 연결 하나. 다시 [연결하기] 를 눌러도 행이 늘지 않고 코드만 바뀐다. CREATE UNIQUE INDEX IF NOT EXISTS uq_kakao_link_user ON public.owner_kakao_links(user_id) WHERE deleted=false AND status IN ('PENDING','LINKED'); -- ★ 한 카카오 계정은 한 사장님에만 묶인다. 없으면 같은 카톡 계정이 여러 사장님에 -- 연결돼 "어느 가게 이야기냐" 가 대화가 아니라 DB 에서 갈라진다. CREATE UNIQUE INDEX IF NOT EXISTS uq_kakao_link_channel_key ON public.owner_kakao_links(channel_user_key) WHERE deleted=false AND status='LINKED'; -- 코드 소비는 이 인덱스로 한 행을 집는다(일회성은 UPDATE ... WHERE status='PENDING' CAS 가 보장). CREATE UNIQUE INDEX IF NOT EXISTS uq_kakao_link_code ON public.owner_kakao_links(code_sha) WHERE deleted=false AND status='PENDING';