c192d61c71
- project_db 신규(projects/members/stages/tasks/comments/activity, project_app CRUD) - 프로젝트/서브프로젝트(self-FK), 업무·진행단계(칸반), 멤버 배정 - 메인 뷰 달력(FullCalendar)/타임라인(vis-timeline) 토글 + 보드/리스트 - 진입은 로그인 회사 직원 전원, 생성/배정은 관리자 전용 - 업무 배정·완료 시 관리자 메일 알림(app/mail.py, SMTP_* env, 미설정 시 skip) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
186 lines
10 KiB
PL/PgSQL
186 lines
10 KiB
PL/PgSQL
-- =====================================================================
|
|
-- project_db 초기화 스크립트 (PostgreSQL) — 프로젝트 관리(아사나식) 모듈
|
|
-- =====================================================================
|
|
-- 멱등(idempotent): 여러 번 실행해도 안전. 기존 데이터를 삭제하지 않는다.
|
|
--
|
|
-- ⚠️ 실행 전 사용자 승인 + 백업 확인 필수. DROP/TRUNCATE 없음.
|
|
--
|
|
-- 실행 방법 (운영 PostgreSQL 컨테이너명: postgres-db):
|
|
--
|
|
-- 1) DB / 역할 / 스키마 생성 (superuser 로 1회)
|
|
-- read -s -p "project_app password: " APP_PWD; echo
|
|
-- docker exec -i postgres-db psql -U postgres \
|
|
-- -v app_password="$APP_PWD" \
|
|
-- < scripts/sql/project_db_init.sql
|
|
--
|
|
-- 2) main-app .env 에 연결 정보 등록
|
|
-- PROJECT_DB_URL=postgresql://project_app:<APP_PWD>@postgres-db:5432/project_db
|
|
--
|
|
-- 3) main-app 재기동
|
|
-- cd /opt/www/main && docker compose up -d --build
|
|
--
|
|
-- 주의:
|
|
-- - 기존 DB 가 있으면 DROP 하지 않는다(CREATE DATABASE 는 미존재 시에만).
|
|
-- - 비밀번호는 절대 Git 에 커밋하지 않는다. psql -v 로만 전달.
|
|
-- =====================================================================
|
|
|
|
\set ON_ERROR_STOP on
|
|
|
|
-- DB 가 없을 때만 생성
|
|
SELECT 'CREATE DATABASE project_db ENCODING ''UTF8'' TEMPLATE template0'
|
|
WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'project_db')
|
|
\gexec
|
|
|
|
-- 앱 전용 로그인 역할 (vacation_app / cupang_app 패턴과 동일)
|
|
SELECT 'CREATE ROLE project_app LOGIN PASSWORD ' || quote_literal(:'app_password')
|
|
WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'project_app')
|
|
\gexec
|
|
|
|
-- 항상 최신 비밀번호로 동기화
|
|
SELECT 'ALTER ROLE project_app WITH LOGIN PASSWORD ' || quote_literal(:'app_password')
|
|
\gexec
|
|
|
|
GRANT CONNECT ON DATABASE project_db TO project_app;
|
|
|
|
-- project_db 컨텍스트로 전환
|
|
\connect project_db
|
|
|
|
-- ── updated_at 자동 갱신 트리거 함수 (멱등: CREATE OR REPLACE) ──
|
|
CREATE OR REPLACE FUNCTION project_set_updated_at() RETURNS trigger AS $$
|
|
BEGIN
|
|
NEW.updated_at := now();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 1) 프로젝트 / 서브프로젝트 (self-referential: parent_id)
|
|
-- parent_id NULL = 최상위 프로젝트, NOT NULL = 서브프로젝트.
|
|
-- 삭제는 CASCADE (서브프로젝트·단계·업무 모두 함께 제거).
|
|
-- ════════════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS projects (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
parent_id BIGINT REFERENCES projects(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
color TEXT NOT NULL DEFAULT '#4573d2',
|
|
owner_email TEXT NOT NULL DEFAULT '',
|
|
start_date DATE,
|
|
due_date DATE,
|
|
status TEXT NOT NULL DEFAULT 'active', -- active | archived
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
created_by TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_projects_parent ON projects (parent_id);
|
|
CREATE INDEX IF NOT EXISTS idx_projects_status ON projects (status);
|
|
|
|
DROP TRIGGER IF EXISTS trg_projects_updated ON projects;
|
|
CREATE TRIGGER trg_projects_updated
|
|
BEFORE UPDATE ON projects
|
|
FOR EACH ROW EXECUTE FUNCTION project_set_updated_at();
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 2) 프로젝트 멤버 배정 (관리자가 사용자를 프로젝트에 지정)
|
|
-- role: manager(서브프로젝트/업무 관리) | member
|
|
-- ════════════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS project_members (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
project_id BIGINT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
user_email TEXT NOT NULL,
|
|
user_name TEXT NOT NULL DEFAULT '',
|
|
role TEXT NOT NULL DEFAULT 'member', -- manager | member
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (project_id, user_email)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_project_members_project ON project_members (project_id);
|
|
CREATE INDEX IF NOT EXISTS idx_project_members_email ON project_members (user_email);
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 3) 진행 단계 (칸반 컬럼) — 프로젝트별. 생성 시 기본 4단계 seed.
|
|
-- is_done_stage: 이 단계로 옮기면 업무 '완료'로 간주(메일 트리거).
|
|
-- ════════════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS project_stages (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
project_id BIGINT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
is_done_stage BOOLEAN NOT NULL DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_project_stages_project ON project_stages (project_id, sort_order);
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 4) 업무 (task)
|
|
-- stage_id 로 진행단계 표현. assignee_email 로 담당자 지정.
|
|
-- ════════════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
project_id BIGINT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
stage_id BIGINT REFERENCES project_stages(id) ON DELETE SET NULL,
|
|
title TEXT NOT NULL,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
assignee_email TEXT NOT NULL DEFAULT '',
|
|
assignee_name TEXT NOT NULL DEFAULT '',
|
|
priority TEXT NOT NULL DEFAULT 'normal', -- low | normal | high
|
|
start_date DATE,
|
|
due_date DATE,
|
|
completed_at TIMESTAMPTZ,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
created_by TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks (project_id, sort_order);
|
|
CREATE INDEX IF NOT EXISTS idx_tasks_stage ON tasks (stage_id);
|
|
CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks (assignee_email);
|
|
CREATE INDEX IF NOT EXISTS idx_tasks_due ON tasks (due_date);
|
|
|
|
DROP TRIGGER IF EXISTS trg_tasks_updated ON tasks;
|
|
CREATE TRIGGER trg_tasks_updated
|
|
BEFORE UPDATE ON tasks
|
|
FOR EACH ROW EXECUTE FUNCTION project_set_updated_at();
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 5) 업무 댓글 (아사나 기본기능 — 스켈레톤 단계 테이블)
|
|
-- ════════════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS task_comments (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
task_id BIGINT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
author_email TEXT NOT NULL DEFAULT '',
|
|
author_name TEXT NOT NULL DEFAULT '',
|
|
body TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments (task_id, created_at);
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 6) 활동 이력 (배정/완료 등 — 메일 알림 근거 + 타임라인)
|
|
-- ════════════════════════════════════════════════════════════
|
|
CREATE TABLE IF NOT EXISTS project_activity (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
project_id BIGINT REFERENCES projects(id) ON DELETE CASCADE,
|
|
task_id BIGINT REFERENCES tasks(id) ON DELETE CASCADE,
|
|
actor_email TEXT NOT NULL DEFAULT '',
|
|
action TEXT NOT NULL, -- assigned | completed | created | stage_changed ...
|
|
detail TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_project_activity_project ON project_activity (project_id, created_at DESC);
|
|
|
|
-- ════════════════════════════════════════════════════════════
|
|
-- 7) 권한 (project_app: CRUD only)
|
|
-- ════════════════════════════════════════════════════════════
|
|
GRANT USAGE ON SCHEMA public TO project_app;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON
|
|
projects, project_members, project_stages, tasks, task_comments, project_activity
|
|
TO project_app;
|
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO project_app;
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO project_app;
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
GRANT USAGE, SELECT ON SEQUENCES TO project_app;
|
|
|
|
SELECT 'project_db ready' AS status;
|