음성 재생과 운영 배포 정리

This commit is contained in:
Yun Chan 2026-06-28 12:18:20 +09:00
parent 8ed185ce6c
commit ac7db95542
1020 changed files with 46863 additions and 2175 deletions

View file

@ -128,3 +128,162 @@ CREATE POLICY p_case_worksheet_delete
AND s.learner_id = app.current_uid()
)
);
-- Live coaching interventions delivered during a session.
CREATE TABLE IF NOT EXISTS app.live_coach_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES app.sessions(id) ON DELETE CASCADE,
turn_seq INT NOT NULL CHECK (turn_seq >= 1),
stage TEXT NOT NULL,
learner_text_excerpt TEXT,
client_reply_excerpt TEXT,
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_live_coach_events_session_turn
ON app.live_coach_events(session_id, turn_seq, created_at);
ALTER TABLE app.live_coach_events ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS p_live_coach_events_select ON app.live_coach_events;
DROP POLICY IF EXISTS p_live_coach_events_insert ON app.live_coach_events;
CREATE POLICY p_live_coach_events_select
ON app.live_coach_events FOR SELECT USING (
app.current_role_name() IN ('admin','instructor')
OR EXISTS (
SELECT 1 FROM app.sessions s
WHERE s.id = app.live_coach_events.session_id
AND s.learner_id = app.current_uid()
)
);
CREATE POLICY p_live_coach_events_insert
ON app.live_coach_events FOR INSERT WITH CHECK (
EXISTS (
SELECT 1 FROM app.sessions s
WHERE s.id = app.live_coach_events.session_id
AND s.learner_id = app.current_uid()
)
);
-- Public share cards for learner-owned session reviews. Payload is intentionally
-- a sanitized preview, not the transcript or worksheet raw source.
CREATE TABLE IF NOT EXISTS app.session_share_link (
session_id UUID PRIMARY KEY REFERENCES app.sessions(id) ON DELETE CASCADE,
created_by UUID NOT NULL REFERENCES app.app_user(user_id),
token_hash TEXT NOT NULL UNIQUE,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
revoked_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_session_share_token_active
ON app.session_share_link(token_hash)
WHERE revoked_at IS NULL;
ALTER TABLE app.session_share_link ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS p_session_share_select ON app.session_share_link;
DROP POLICY IF EXISTS p_session_share_insert ON app.session_share_link;
DROP POLICY IF EXISTS p_session_share_update ON app.session_share_link;
DROP POLICY IF EXISTS p_session_share_delete ON app.session_share_link;
CREATE POLICY p_session_share_select
ON app.session_share_link FOR SELECT USING (
app.is_ai_context()
OR app.current_role_name() IN ('admin','instructor')
OR EXISTS (
SELECT 1 FROM app.sessions s
WHERE s.id = app.session_share_link.session_id
AND s.learner_id = app.current_uid()
)
);
CREATE POLICY p_session_share_insert
ON app.session_share_link FOR INSERT WITH CHECK (
app.is_ai_context()
OR (
created_by = app.current_uid()
AND EXISTS (
SELECT 1 FROM app.sessions s
WHERE s.id = app.session_share_link.session_id
AND s.learner_id = app.current_uid()
)
)
);
CREATE POLICY p_session_share_update
ON app.session_share_link FOR UPDATE USING (
app.is_ai_context()
OR EXISTS (
SELECT 1 FROM app.sessions s
WHERE s.id = app.session_share_link.session_id
AND s.learner_id = app.current_uid()
)
) WITH CHECK (
app.is_ai_context()
OR (
created_by = app.current_uid()
AND EXISTS (
SELECT 1 FROM app.sessions s
WHERE s.id = app.session_share_link.session_id
AND s.learner_id = app.current_uid()
)
)
);
CREATE POLICY p_session_share_delete
ON app.session_share_link FOR DELETE USING (
app.is_ai_context()
OR EXISTS (
SELECT 1 FROM app.sessions s
WHERE s.id = app.session_share_link.session_id
AND s.learner_id = app.current_uid()
)
);
-- Learner-owned history archive state. This is display state only; session,
-- turn, review, share, research, and audit evidence remain intact.
CREATE TABLE IF NOT EXISTS app.session_archive_state (
session_id UUID PRIMARY KEY REFERENCES app.sessions(id) ON DELETE CASCADE,
learner_id UUID NOT NULL REFERENCES app.app_user(user_id),
archived_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_session_archive_learner
ON app.session_archive_state(learner_id, archived_at DESC);
ALTER TABLE app.session_archive_state ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS p_session_archive_select ON app.session_archive_state;
DROP POLICY IF EXISTS p_session_archive_insert ON app.session_archive_state;
DROP POLICY IF EXISTS p_session_archive_update ON app.session_archive_state;
DROP POLICY IF EXISTS p_session_archive_delete ON app.session_archive_state;
CREATE POLICY p_session_archive_select
ON app.session_archive_state FOR SELECT USING (
app.current_role_name() IN ('admin','instructor')
OR learner_id = app.current_uid()
);
CREATE POLICY p_session_archive_insert
ON app.session_archive_state FOR INSERT WITH CHECK (
learner_id = app.current_uid()
AND EXISTS (
SELECT 1 FROM app.sessions s
WHERE s.id = app.session_archive_state.session_id
AND s.learner_id = app.current_uid()
)
);
CREATE POLICY p_session_archive_update
ON app.session_archive_state FOR UPDATE USING (
learner_id = app.current_uid()
) WITH CHECK (
learner_id = app.current_uid()
AND EXISTS (
SELECT 1 FROM app.sessions s
WHERE s.id = app.session_archive_state.session_id
AND s.learner_id = app.current_uid()
)
);
CREATE POLICY p_session_archive_delete
ON app.session_archive_state FOR DELETE USING (
learner_id = app.current_uid()
);