cfa41f0f6f
지금까지 같은 세션에 다시 놓으면 아무 일도 안 일어났다(다른 세션으로 옮길 때만 동작). 놓은 위치(마우스 Y좌표)를 카드들 사이 인덱스로 계산해 그 순서를 새 엔드포인트로 저장하고(tasks.sort_order, 세션 소속만 재배정 — 세션 재정렬의 reorder_stages 와 동일 패턴), 로컬 tasks 배열도 그 순서에 맞게 재배치(applyStageOrder)해 새로고침 없이 즉시 반영. 실제 3개 업무로 드래그→서버 저장→하드리로드까지 헤드리스 크롬으로 검증.
13 KiB
13 KiB
프로젝트 관리 모듈 (아사나식) — app/modules/project/
회사(dbxcorp.co.kr) 직원이 프로젝트→세션→업무 3단계 구조를 달력/타임라인/ 보드로 관리하는 아사나(Asana) 스타일 협업 모듈. DB는
project_db전용.
1. 개요
| 항목 | 값 |
|---|---|
| URL prefix | /project |
| DB | project_db (PostgreSQL, 역할 project_app CRUD only) |
| 연결 env | PROJECT_DB_URL (미설정 시 "설정 필요" 안내, 앱은 죽지 않음) |
| 진입 권한 | 권한키 project (관리자 페이지 토글로 직원별 부여, admin 자동) |
| 멤버 배정 후보 | project 권한 보유 등록 사용자 자동 목록 (GET /project/api/assignable-users) |
| 생성 권한 | 프로젝트·세션·업무(하위 업무 포함) 생성은 로그인해 이 모듈에 접근할 수 있는 사용자 누구나. 프로젝트를 만든 사람은 자동으로 owner 가 되어 그 프로젝트를 관리(_can_manage)할 수 있다. |
| 수정/이동 권한 | 서브프로젝트·업무·세션 CRUD(이름변경·완료토글·순서변경 등) = 그 프로젝트의 배정 멤버 또는 owner 또는 admin(_require_manage) |
| 삭제 권한 | 프로젝트·세션·업무 모두 "만든 사람만"(슈퍼관리자 예외, 생성자 정보 없는 과거 데이터는 admin 허용) — _can_delete 하나로 통일 |
| 멤버 배정 | 여전히 관리자(is_admin) 전용. 비관리자가 "새 프로젝트" 모달을 열면 멤버 선택란은 안내 문구만 뜨고 시도하지 않는다(불필요한 403 방지) |
| 메일 알림 | app/mail.py (stdlib smtplib). 업무 배정/완료 시 관리자에게 발송 |
1-1. 프로젝트 → 세션 → 업무 (하위 업무 포함) 3단계 구조
"세션"은 새 개념이 아니라 기존 "단계"(칸반 컬럼)를 그대로 재사용한다 —
아사나도 보드 뷰의 컬럼과 리스트 뷰의 구분선이 같은 "섹션" 하나다. DB
컬럼/테이블 이름(project_stages, stage_id)은 그대로 두고, 사용자에게
보이는 문구만 "세션"으로 바꿨다.
- 세션 관리 — 프로젝트 화면(
/project/p/{id})에서 "이 프로젝트" 스코프로 볼 때만 보드 컬럼에 관리 UI가 붙는다(이름변경·완료토글·삭제·"+ 세션 추가"). 컬럼 헤더를 드래그해서 순서를 바꿀 수 있다(카드 드래그와 같은 네이티브 HTML5 DnD,PUT /api/projects/{id}/stages/order재사용). "전체 프로젝트" 스코프(여러 프로젝트를 세션 '이름'으로 합쳐 보는 예전 방식)에서는 세션을 하나로 특정할 수 없어 관리 UI가 없다. - 하위 업무(subtask) — 새 테이블이 아니라
tasks.parent_task_id재사용 (부모 삭제 시 CASCADE). 업무 편집 팝업 안에 인라인으로 표시되며(체크박스+ 제목+담당자+마감일, 한 줄에서 바로 저장), 보드/캘린더/리스트에는 최상위 업무만 카드로 뜬다(하위 업무는 부모 카드에 "2/5" 진행 배지로만 나타난다). 세션을 갖지 않는다(칸반에 안 보이므로). - 멀티호밍(다른 프로젝트에 연결) —
task_project_links테이블. 업무 하나가 원래 소속(기본 홈,tasks.project_id/stage_id)과 무관하게 다른 프로젝트의 한 세션에도 동시에 나타날 수 있다(아사나의 "Add to project"). 업무 편집 팝업의 "연결된 프로젝트" 칩에서 추가/해제한다. 새 연결을 만들 때는 원 프로젝트·대상 프로젝트 양쪽 관리 권한이 필요하지만(무단으로 남의 업무를 끌어와 노출시키는 것 방지), 이미 연결된 업무를 그 프로젝트 보드 안에서 다른 세션으로 드래그하는 것은 대상 프로젝트 권한만 있으면 된다.list_tasks(project_id=X)(비재귀 단일 프로젝트 조회, 프로젝트 화면이 쓰는 바로 그 경로)가 자동으로 연결된 업무까지 포함해서 돌려준다 — 다른 호출 경로(홈/내 업무 등project_id없이 부르는 전체보기)는 원 소속만 세어 중복 노출을 막는다. - 사이드바 프로젝트 스코프 — 프로젝트 화면 툴바에 "이 프로젝트"/"전체
프로젝트" 토글이 있다. 기본은 "이 프로젝트"(아사나처럼 지금 선택한
프로젝트의 세션만 보드/캘린더/타임라인/리스트에 나온다), "전체 프로젝트"는
예전처럼 접근 가능한 모든 프로젝트를 합쳐서 보여준다. 서버가
tasks배열의 각 행에context_project_id(어느 프로젝트를 보다가 담겼는지)를 표시해두고, 클라이언트가 이 값으로 스코프를 걸러낸다(순수 프론트 필터 — 서버 재조회 없음). - 업무 설명 이미지 삽입 — 설명란이
contenteditable로 바뀌어 이미지를 붙여넣거나 파일로 올릴 수 있다. 새 첨부 endpoint 없이 기존task_attachments업로드를 재사용하고, 표시는GET /api/attachments/{id}/inline(다운로드용/download는Content-Disposition: attachment가 걸려<img>로 못 씀 — 그래서 인라인 전용 endpoint를 따로 뒀다)로 한다. 저장 전 서버가store.sanitize_description_html()로 허용 태그만 남기고 스크립트/이벤트속성/위험 URL 스킴을 제거한다(표준html.parser, 외부 라이브러리 없음). 새 업무 작성 중(저장 전, task_id 없음) 에는 이미지 삽입이 비활성화된다 — 첨부가 업무 id 를 필요로 하기 때문.
2. 데이터 모델 (scripts/sql/project_db_init.sql + ..._002_*/..._003_*)
projects—parent_id(self-FK, NULL=최상위 / NOT NULL=서브프로젝트,ON DELETE CASCADE), name, description, color, owner_email, start/due_date, status(active|archived).project_members— 프로젝트↔사용자 배정. role(manager|member),UNIQUE(project_id, user_email).project_stages(="세션") — 진행단계(칸반 컬럼). 프로젝트 생성 시 자동으로 만들지 않는다(아사나처럼 직접 "+ 세션 추가"로 만든다 —create_project의seed_stages기본값은False).is_done_stage=TRUE 단계로 옮기면 업무 완료 처리.created_by(003) — 삭제를 "만든 사람만"으로 제한하기 위함.tasks— 업무. stage_id, title, description(HTML — 이미지 삽입 지원), assignee_email/name, priority(low|normal|high), start/due_date, completed_at,parent_task_id(003, self-FKON DELETE CASCADE, NULL=최상위 업무).task_project_links(003) — 멀티호밍.(task_id, project_id)UNIQUE,stage_idnullable(그 프로젝트 안에서의 세션).task_comments— 댓글.task_attachments— 첨부(설명란 이미지도 이걸 재사용).project_activity— 활동 이력(created/assigned/completed/stage_changed). 메일 트리거 근거 + 타임라인.
3. 화면 / 뷰
- 홈 (
/project/): "진행중인 프로젝트" / "완료된 프로젝트" 두 섹션으로 구분된 카드 그리드 + "내 업무" 사이드. 관리자는 "새 프로젝트"(이름/설명/시작일/마감일/색상 지정 + 멤버 다중 선택, 생성 직후 일괄 배정). 완료 섹션은 접기/펼치기(localStorage 기억). 각 카드에 이름 아래 배정 멤버(아이콘+이름), 진행률 바(완료/전체 업무)와 업무 미리보기(단계·담당자·마감일, 최대 8건 + 더보기)를 표시 — 완료된 업무도 취소선으로 계속 노출해 전체 현황을 한눈에 파악. 카드 이름 옆에는 호버 시에만 보이는 "업무 추가" 아이콘 버튼(관리자 또는 해당 프로젝트 멤버/owner) — 같은 업무편집 팝업을 새 업무 작성 모드로 연다. 카드·내 업무의 업무를 클릭하면 페이지 이동 없이 그 자리에서 업무편집 팝업(담당자·단계·일정·첨부·댓글)이 뜬다 (#pj-modal-task+home_tasks/#pj-home-data, 담당자·단계 옵션은 해당 프로젝트를 API 로 즉시 조회). 업무에 댓글이 있으면 말풍선 아이콘+개수를 표시, 더블클릭하면 댓글만 보는 카톡 대화창 스타일 팝업(#pj-modal-comments)이 뜬다. 확인 안 한 새 댓글은 말풍선이 커지며 반짝이고(브라우저별localStorage: pj_seen_comments로 확인 여부 기억), 팝업을 열면(확인하면) 원래대로 돌아간다. 날짜는 전부mm/dd(요일)형식(_fmt_date_kr)으로 표시. 프로젝트/업무/댓글/멤버/단계가 추가·수정되면GET /project/api/live-version(최근 변경 시각) 폴링으로 감지해 해당 화면을 열어둔 모든 사용자의 페이지가 자동 새로고침(모달을 열어 입력 중이면 닫힐 때까지 대기). - 프로젝트 (
/project/p/{id}): 좌측 서브프로젝트/멤버, 우측 뷰 토글. 좌측 트리의 업무 클릭도 페이지 이동 없이 바로 팝업(이미 로드된 전체 업무 데이터 사용).- 달력 — FullCalendar. 업무를 시작~마감 기간으로 표시. 클릭 편집.
- 타임라인 — vis-timeline(간트형). 기간 있는 업무만.
- 보드 — 세션별 칸반. 카드 드래그로 세션 이동(
PUT /api/tasks/{id}stage_id, 멀티호밍된 업무는POST /api/tasks/{id}/links). 같은 세션 안에서 드래그하면 이동이 아니라 카드 순서만 바꾼다(PUT /api/projects/{id}/stages/{stage_id}/tasks/order, 세션 재정렬과 같은 패턴 —tasks.sort_order를 그 세션 소속만 0부터 재배정). 세션 컬럼 자체도 드래그로 재정렬(§1-1). - 리스트 — 표.
- 4개 뷰 모두 툴바의 "이 프로젝트"/"전체 프로젝트" 스코프 토글을 따른다(§1-1).
- 라이브러리는 현재 CDN 로드(스켈레톤). 추후
app/static/vendor/self-host 권장.
4. 메일 알림
app/mail.pysend_email(...)—SMTP_HOST있어야 발송, 없으면 skip. STARTTLS/SSL/평문 지원.- env:
SMTP_HOST/PORT/USER/PASSWORD/FROM/TLS, 수신자PROJECT_NOTIFY_EMAIL(쉼표, 비우면 admin 전원). - 트리거: 업무 신규 배정(생성/수정 시 담당자 변경) · 완료단계 진입.
BackgroundTasks비동기, 실패는 로그만.
5. 배포
# 1) DB 생성 (superuser 1회, 컨테이너 postgres-db)
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) /opt/www/main/.env 에 등록
# PROJECT_DB_URL=postgresql://project_app:<APP_PWD>@postgres-db:5432/project_db
# (메일 쓰려면 SMTP_* 추가)
# 3) 마이그레이션 002·003 적용 (첨부/알림, 세션 생성자·하위업무·멀티호밍)
docker exec -i postgres-db psql -U postgres -d project_db \
< scripts/sql/project_db_002_attachments_notifications.sql
docker exec -i postgres-db psql -U postgres -d project_db \
< scripts/sql/project_db_003_sections_subtasks_links.sql
# 4) 재배포 (git pull 후 반드시 --build)
cd /opt/www/main && docker compose up -d --build web
⚠️ 003 안에는 기존 tasks.description(일반 텍스트)을 안전한 HTML로 바꾸는
1회성 UPDATE 문이 있다(설명란이 contenteditable 로 바뀌어 이제
description 을 항상 HTML로 취급하기 때문). 두 번 실행하면 이중 이스케이프되니
꼭 한 번만 돌릴 것.
6. 댓글 · 첨부 · 알림센터 · 세션 편집 (구현됨)
- 댓글
task_comments— 업무 모달 하단. 등록/삭제(본인·관리자). 새 댓글 시 관련자 인앱 알림. - 첨부
task_attachments— 업무 모달. 파일 업로드(최대 20MB)/다운로드/삭제. 실제 파일은DATA_DIR/project/<task_id>/<uuid>.<ext>, DB엔 메타만. 첨부 디렉토리는 운영 볼륨(DATA_DIR)에 저장돼 재배포에도 보존. 설명란 이미지 삽입도 이 표를 재사용(§1-1). - 세션 편집 — 보드 칸반 헤더에서 이름변경/완료토글/삭제, 트레일링 "+ 세션 추가", 컬럼 드래그로 순서 변경.
PUT .../stages/order,PUT .../stages/{id}, 삭제는 만든 사람만. - 알림센터(인앱)
project_notifications— 배정/완료/댓글 시 수신자별 알림 생성. 우측 상단 벨(미읽음 배지) →/project/inbox. 항목 클릭=읽음, "모두 읽음". 본인 행동은 알림 제외. - 마이그레이션:
scripts/sql/project_db_002_attachments_notifications.sql(첨부·알림 테이블 + 권한),scripts/sql/project_db_003_sections_subtasks_links.sql(세션 생성자·하위업무·멀티호밍).
7. 추후 단계
태그 · 검색/필터 · 업무 정렬 영속화 · 멘션 · 하위 업무의 하위 업무(다단계 중첩, 아사나도 UI상 1단계만 허용해 현재 의도적으로 안 함).