# 프로젝트 우선 구조 리팩터 계획 (Project-First)

> 작성 2026-06-20. 현재 **저장소(Repo)=프로젝트 1:1** 구조를 **프로젝트 우선 + 프로젝트 1:N 저장소** 로 전환한다.
> 이 문서가 리팩터의 단일 진실 공급원(SSOT). 단계별로 갱신한다.

## 1. 목표 구조

```
Project (이름/설명/공개범위)                ← 작업의 기본 단위(우선)
  ├─ ProjectMember (admin/member, isOwner)  ← 멤버십을 프로젝트로 이동(RepoMember 폐지)
  ├─ Task (project, seq[프로젝트 내 순번], …) ← 업무는 프로젝트 소속
  │     └─ checklist / comment / attachment (기존 그대로, Task 종속)
  ├─ Activity (project, taskSeq?, …)         ← 활동도 프로젝트 단위
  └─ Repo[] (project FK, gitea 필드…)         ← 0~N개 저장소 연동(선택, 코드 연동 전용)
```

- **일반 프로젝트** = 연동 저장소 0개. 업무·멤버·활동 정상 사용.
- **저장소 연동 프로젝트** = 저장소 1~N개 링크. 추가로 Gitea/코드참조.

## 2. 확정된 설계 결정 (사용자 합의 2026-06-20)

1. **업무·멤버·활동 = 프로젝트 단위.** `Task→Repo` 종속을 `Task→Project` 로 이동. `RepoMember` 폐지 → `ProjectMember`.
2. **Gitea 자동 가져오기(역방향 동기화)** → import 저장소는 **"미분류" 기본 프로젝트**에 연결. 관리자가 나중에 적절한 프로젝트로 이동.
3. **dev DB 초기화**(volume 재생성). 마이그레이션 스크립트 불필요.

## 3. 추가 설계 결정 (구현자 판단 — 변경 가능)

- **라우팅**: 업무는 프로젝트 직속, 저장소는 형제 부속.
  - `/projects` · `/projects/:projectId` (상세=업무목록) · `/projects/:projectId/{members,activity,settings}`
  - `/projects/:projectId/tasks/:seq` (업무 상세) · `/projects/:projectId/tasks/new`
  - `/projects/:projectId/repos` (연동 저장소 목록·링크) · `/projects/:projectId/repos/:repoSlug` (저장소 상세/설정)
  - `/tasks` (내 업무, 프로젝트 횡단 — 경로 유지)
- **저장소 식별자**: `slugName` 은 Gitea 조직 내 유일이므로 **전역 유일 유지**(프로젝트 내 유일로 바꾸지 않음). Repo 에 `project_id` FK만 추가.
- **Task.seq**: `@Unique(['repo','seq'])` → `@Unique(['project','seq'])` (프로젝트 내 순번 #N).
- **권한**: `assertMember/assertAdmin(repo.id, …)` → `assertProjectMember/assertProjectAdmin(project.id, …)`. 저장소 단위 세분 권한은 두지 않음(프로젝트 멤버면 연동 저장소 전체 접근). 향후 옵션.
- **저장소 자체엔 멤버 없음.** 저장소는 순수 Gitea 연동 메타. 멤버십/권한은 소유 프로젝트가 결정.
- **"미분류" 기본 프로젝트**: 시스템 예약 프로젝트 1개(부팅 시 보장 생성). Gitea import 대상.

## 4. 단계별 실행 계획

### Phase 1 — 백엔드 데이터 모델 + Project 모듈 (핵심)
- [ ] `Project`·`ProjectMember` 엔티티 + `ProjectModule`/`ProjectService`/`ProjectController`(CRUD + 멤버 초대/역할/제거 + 권한 가드)
- [ ] "미분류" 기본 프로젝트 부팅 보장(OnApplicationBootstrap)
- [ ] `Repo` 에 `project` FK 추가. `RepoMember` 폐지. Repo 생성/수정/삭제가 프로젝트 권한으로 게이트.
- [ ] `Task` 를 `project` 종속으로 변경(`@Unique(['project','seq'])`), 권한 체인 프로젝트로 교체, 담당자=프로젝트 멤버.
- [ ] `Activity` 를 `project` 종속으로. (taskSeq 유지)
- [ ] `RepoSyncService`: import → "미분류" 프로젝트 연결.
- [ ] `AiService.suggestChecklist`: 프로젝트/저장소 컨텍스트 정합.
- [ ] 컨트롤러 라우트 재배치(`/projects/...`). 기존 `/repos/...` 제거 또는 이전.
- [ ] 검증: build/lint/test.

### Phase 2 — 프론트 프로젝트 중심 재구성
- [ ] `project.store`·`useProject`·`types/project.ts`
- [ ] 신규 페이지: ProjectListPage(진입점)·ProjectDetailPage(업무목록)·ProjectSettingsPage·ProjectMembersPage·ProjectActivityPage
- [ ] 저장소: 프로젝트 내 "연동 저장소" 섹션 + RepoLinkPage(기존 RepoCreate 재구성)
- [ ] task/member/activity 스토어를 projectId 키로 재설계
- [ ] 라우팅 전면 교체, AppShell 네비(프로젝트/내 업무)
- [ ] 검증: type-check/lint/build.

### Phase 3 — 마무리
- [ ] 문서(api-contract.md)·메모리 갱신, 잔여 UI 정리, 런타임 점검.

## 5. 진행 로그
- Phase 0: 계획 수립 ✅
- Phase 1-a: Project/ProjectMember 모듈 기반(엔티티·서비스·컨트롤러·"미분류" 부팅) ✅ (커밋 2f66cdd)
- Phase 1-b: 백엔드 이전 ✅ — Activity(project FK·listForProject)·Repo(project FK, RepoMember 폐지)·Task(project 종속, seq 프로젝트 내 순번, 권한=ProjectMember) 전환. RepoService 프로젝트 종속(`/projects/:projectId/repos`, 멤버·업무카운트·캐시 제거). Task/Activity 컨트롤러 `/projects/...`. RepoSync→"미분류". AiService 프로젝트 컨텍스트. member.service/controller·repo-member·repo-cache 삭제. 검증 build/lint 0·14 tests.
- **Phase 1 잔여(소소, 후속)**: ProjectResponse 카운트(repoCount/doneCount/totalCount 현재 0 stub) 집계 배선, ProjectMemberService 활동·알림 배선(TODO), TaskService.assigneeCounts→ProjectMemberService taskCount 연결.
- Phase 2-a: 프론트 프로젝트 중심 재구성 ✅ — `types/project`·`useProject`·`project.store` 신설. `useTask`/`useMember`/`useActivity` 를 `/projects/:projectId/...` 로, task/my-task/activity 타입·스토어를 projectId/projectName 으로 전환. 알림 store 라우팅/문구 프로젝트화. 라우팅 `/projects/...` 전면 교체. 페이지 rename(Repo*→Project*): List/Create(간단 폼)/Detail/Settings/Members/Activity. `RepoHeader`→`ProjectHeader`, `RepoTabs`→`ProjectTabs`. AppShell 네비 "프로젝트". 죽은 파일(repo.store/useRepo/GiteaRepoBar) 삭제. 검증 type-check/lint/build 0.
- Phase 1 카운트 배선 ✅ — ProjectService 가 프로젝트별 연동 저장소 수·업무 완료/전체 수를 그룹 쿼리로 집계(Repo/Task forFeature 등록). ProjectMemberService 멤버 taskCount 집계 + 초대/역할변경/제거 시 활동 적재·대상자 알림 발송(Activity/Notification 모듈 주입). TaskService 미사용 집계 메서드 제거. 검증 build/lint 0·14 tests.
- Phase 2b 저장소 연동 UI ✅ — `useRepo`(프로젝트 종속) 복구, `types/repo` ApiRepo 를 새 RepoResponse(projectId 추가·members/counts 제거)로 정합. `ProjectTabs` 에 "저장소" 탭(repoCount) 추가, 신규 `ProjectReposPage`(`/projects/:id/repos`) — 연동 저장소 목록 + slug 가용성 체크 포함 연동(생성) 폼 + 연동 해제(admin). 라우트 추가. 화면 잔여 "저장소" 문구를 "프로젝트"로 정리(crumb·설정 문구·멤버 모달·주석). 검증 type-check/lint/build 0.
- Phase 3 마무리 ✅ — **런타임 점검 완료(2026-06-20)**. dev DB 초기화(데이터 디렉터리 비우고 `up -d --build`) 후 새 스키마 정상 생성, 부팅 시 "미분류" 시스템 프로젝트 생성 + Gitea 동기화가 원격 저장소 1개를 "미분류"에 import(repoCount=1) 확인. 스모크: signup→이메일 인증(로그 링크)→login→프로젝트 생성(소유자 owner/admin 등록)→프로젝트 종속 업무 생성(담당자=멤버)→목록/카운트→프로젝트 상세 totalCount·멤버 taskCount·repoCount 집계 반영→내 업무/활동 피드→연동 저장소 목록/meta/name-available 전부 정상. 프론트 200. **부수 발견·수정**: 백엔드/프론트 헬스체크 오탐(루트 프로브 404·localhost→IPv6) 교정 → 4개 컨테이너 모두 healthy(커밋 9aa22f0).
- **리팩터 완료.** 후속(별건): Phase 1 잔여 소소 항목은 카운트 배선으로 해소됨. 신규 저장소 연동의 실제 Gitea 생성 경로는 런타임 미검증(외부 부작용 회피 — 역방향 import 로 간접 검증됨).
