-- =====================================================================
-- project_db 003 — 세션(단계) 삭제자 기록 · 하위 업무 · 프로젝트 간 멀티호밍
-- =====================================================================
-- 멱등(idempotent): 여러 번 실행해도 안전하다 — 단, 아래 UPDATE(설명 이스케이프)는
-- 예외다. 그 문장은 반드시 1회만 실행할 것(주석 참고). DROP/TRUNCATE 없음.
--
-- 실행:
-- docker exec -i postgres-db psql -U postgres -d project_db \
-- < scripts/sql/project_db_003_sections_subtasks_links.sql
--
-- 배경:
-- 프로젝트 → 세션(기존 "단계"를 그대로 재사용) → 업무 3단계 구조로 바뀌면서
-- 1) 세션도 "만든 사람만 삭제" 규칙을 적용하려면 만든 사람을 기록해야 한다.
-- 2) 업무는 하위 업무(subtask)를 가질 수 있어야 한다 — tasks 테이블 자체를
-- 재사용한다(parent_task_id 만 다르다. 부모 삭제 시 CASCADE).
-- 3) 업무 하나가 여러 프로젝트/세션에 동시에 속하는 멀티호밍(아사나 기능)을
-- 지원해야 한다 — 원래 소속(tasks.project_id/stage_id)은 "기본 홈"으로 두고,
-- 추가 소속만 담는 새 표를 둔다.
-- =====================================================================
\set ON_ERROR_STOP on
ALTER TABLE project_stages ADD COLUMN IF NOT EXISTS created_by TEXT NOT NULL DEFAULT '';
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS parent_task_id BIGINT REFERENCES tasks(id) ON DELETE CASCADE;
CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks (parent_task_id);
-- ⚠️ 1회성 데이터 변환 — 반드시 한 번만 실행할 것(두 번 실행하면 이중 이스케이프됨).
-- 지금까지 tasks.description 은 일반 텍스트였다. 앞으로 화면은 이 컬럼을 항상
-- HTML로 취급한다(붙여넣기 이미지 삽입을 지원하기 위해 — app/modules/project/
-- store.py 의 sanitize_description_html 참고). 런타임에 "이게 HTML이냐 텍스트냐"
-- 판단하는 방식은 오탐이 나므로 쓰지 않고, 기존 값을 여기서 한 번에 안전한 HTML로
-- 바꿔둔다(개행→
, &/> 이스케이프 — 순서가 중요하다: &부터 바꿔야 한다).
UPDATE tasks
SET description = replace(replace(replace(replace(
description, '&', '&'), '<', '<'), '>', '>'), E'\n', '
')
WHERE description IS NOT NULL AND description <> '';
-- ════════════════════════════════════════════════════════════
-- 멀티호밍 — 업무 1개가 여러 프로젝트(각각 세션 지정)에 동시에 속함.
-- 원래 소속(tasks.project_id/stage_id)은 그대로 "기본 홈"이고, 이 표는 "추가
-- 홈"만 담는다. 기본 홈은 이 표로 지우지 못한다(업무 삭제로만 없어진다).
-- ════════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS task_project_links (
id BIGSERIAL PRIMARY KEY,
task_id BIGINT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
project_id BIGINT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
stage_id BIGINT REFERENCES project_stages(id) ON DELETE SET NULL,
created_by TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (task_id, project_id)
);
CREATE INDEX IF NOT EXISTS idx_task_links_task ON task_project_links (task_id);
CREATE INDEX IF NOT EXISTS idx_task_links_project ON task_project_links (project_id, stage_id);
GRANT SELECT, INSERT, UPDATE, DELETE ON task_project_links TO project_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO project_app;
SELECT 'project_db 003 applied' AS status;