Files
comrelay/docs/integration-progress.md
T
ttipo 7f432a0013 feat: 저장소 생성 시 설명 필수화 + 템플릿 복제 생성
- 프로젝트 설명을 생성 필수 항목으로 변경(CreateRepoDto.description required + 프론트 검증)
- 템플릿 복제: useTemplate=true 면 Gitea generate API 로 GITEA_TEMPLATE_OWNER/REPO
  (기본 TtiPo/project-base) 저장소를 복제 생성(브랜치는 템플릿을 따름)
- GET /repos/owner → /repos/meta 로 확장({ owner, template }) — 생성폼 메타 일괄 제공
- 프론트 생성폼: 가짜 템플릿 목록 제거 → 실제 템플릿 옵션, 설명 필수 UI
- env.example/ api-contract/ 진행현황 문서 갱신

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 00:20:21 +09:00

13 KiB

Relay 백엔드 연동 진행 현황

프론트 UI(목업)를 NestJS 백엔드에 단계적으로 연동하는 작업의 진행 기록. 계약/스키마 상세는 api-contract.md 참조. 최종 갱신: 2026-06-16 · 현재 위치: 3단계(멤버) 백엔드 완료, 4단계(업무) 대기


1. 작업 전략

  • 도메인별 수직 슬라이스: 한 도메인을 엔티티 → DTO → service → controller → Swagger → 프론트 composable → store → 페이지 목업 교체까지 끝내고 다음 도메인으로 이동. (수평 분할 아님)
  • 프론트는 백엔드 원시 데이터(타임스탬프/카운트)를 받아 표시용 파생값(상대시간·진행률·아바타 색상 등)은 shared/utils/format.ts에서 계산.
  • 모든 응답은 표준 래퍼 { success, data } / { success, error }. 에러 코드 맵은 CLAUDE.md SSOT.
  • 인증은 이메일+JWT만 먼저(소셜 로그인·이메일 인증은 8단계로 보류). access/refresh 모두 HttpOnly·Secure 쿠키.

단계 순서

0. 계약 정렬 ✅ → 1. 인증 ✅ → 2. 저장소 ✅ (+ Gitea 연동 ✅) → 3. 멤버 ✅(백엔드)
→ 4. 업무 → 5. 업무 상세 부속 → 6. 활동 피드 → 7. 내 업무 → 8. 마무리

2. 완료된 단계

0단계 — 계약 정렬

  • docs/api-contract.md: 7개 도메인 전체 엔드포인트(요청/응답/권한), ID 전략, 파생 필드 규칙, 상태 머신, 비즈니스 에러 메커니즘을 단일 진실 공급원으로 정의.
  • 비즈니스 에러 메커니즘: backend/src/common/exceptions/business.exception.tsBusinessException(code, message, status).
    • 전역 필터(http-exception.filter.ts)가 { code, message } 를 status 매핑보다 우선 처리 → 정확한 HTTP 상태를 유지하면서 사용자에게 구체 문구 노출.
    • 프론트 useApi 인터셉터는 BIZ_001 코드일 때 서버 message 를 우선 표시.
    • 기존 status→코드 매핑 구조는 그대로 유지(덧댄 우선 분기). BusinessException 을 쓰지 않으면 기존과 동일 동작.

1단계 — 인증 (이메일+JWT)

백엔드 backend/src/modules/auth/, modules/user/

  • User 엔티티(UUID, email unique, password_hash select:false, name, role nullable).
  • UserService(Repository 기반): findById/findByEmail/findByEmailWithPassword/create/toPublic.
  • AuthService: 가입(중복 검사), 로그인 검증(bcryptjs), access/refresh 토큰 발급.
  • AuthController: POST /auth/signup·/login·/logout·/refresh(refresh 가드), GET /auth/me(access 가드). HttpOnly 쿠키 발급/제거.
  • JWT access/refresh 전략(쿠키에서 추출) + 가드 + @CurrentUser() 데코레이터.
  • main.tscookie-parser. JWT 시크릿은 backend/.env.{env}(커밋된 .example 포함).

프론트 frontend/src/

  • types/auth.ts, composables/useAuth.ts, stores/auth.store.ts(bootstrap/login/signup/logout).
  • composables/useApi.ts: 401→/auth/refresh 자동 1회 재시도, silent 플래그(부트스트랩 무알림), BIZ_001 서버 메시지 우선.
  • 라우터 가드: 최초 진입 시 /auth/me 세션 복원 + 미인증 리다이렉트(redirect 쿼리 보존).
  • LoginPage/SignupPage 실제 연동(소셜 버튼은 안내, 가입 즉시 자동 로그인), AppShell 아바타=실사용자 + 로그아웃 드롭다운.

로그인 실패 문구: BusinessException('BIZ_001', '이메일 또는 비밀번호가 올바르지 않습니다.', 401) / 중복 가입: 동일 패턴 409.

2단계 — 저장소(Repo) CRUD

백엔드 backend/src/modules/repo/

  • Repo 엔티티: slugName(영문, 라우팅 id, unique), owner('marketing-team' 고정 상수), name(한글 표시제목), description, visibility, branch.
  • RepoMember 엔티티: repo↔user 조인, roleType(admin/member), isOwner, subRole, @Unique([repo, user]).
  • RepoService: 내 멤버십 저장소 목록 / 단건(멤버·공개 접근 체크) / 생성(생성자를 owner·admin 등록) / 수정(admin) / 삭제(owner).
  • GET/POST/PATCH/DELETE /repos[/:repoId] — 전부 JwtAuthGuard. 응답 RepoResponse(원시값; doneCount/totalCount=0, 4단계에서 집계).

프론트

  • shared/utils/format.ts: avatarColor(id 해시)·initialOf·relativeTimeKo·progressOf 추가.
  • types/repo.ts, composables/useRepo.ts, stores/repo.store.ts(API→기존 mock Repo/User 뷰로 매핑: 아바타 3명+moreCount, 진행률, 상대시간).
  • 페이지 연동: RepoListPage(목록·로딩/빈상태), RepoCreatePage(생성→상세), RepoDetailPage(헤더·진행률만), RepoSettingsPage(저장·삭제·이름변경 안내).

2.5단계 — Gitea 조직 연동

백엔드 backend/src/modules/gitea/

  • GiteaService: 외부 Gitea(/api/v1) 얇은 클라이언트(Node 내장 fetch, 새 의존성 없음). createRepo/updateRepo/deleteRepo + enabled 게이트. GiteaApiError(상태코드 보존).
  • 환경변수 GITEA_BASE_URL/GITEA_TOKEN/GITEA_ORG 셋 다 있어야 활성화. 비어 있으면 저장소 CRUD 가 Gitea 호출 없이 기존대로 동작(로컬/미연동 대비).
  • Repo 엔티티에 연동 컬럼 추가: giteaRepoId, giteaFullName, cloneUrl, htmlUrl(전부 nullable). RepoResponsecloneUrl/htmlUrl 노출.
  • RepoService 와이어링:
    • 생성: Gitea 조직에 repo 생성(auto_init) → 성공 시 Relay 저장(연동정보 보관). 소유자=GITEA_ORG. 이름 충돌(409)→BIZ_001. Relay 저장 실패 시 Gitea repo 롤백 삭제.
    • 수정: 표시제목/설명→Gitea description, 공개범위→private 동기화(slug 불변). 베스트 에포트.
    • 삭제: Gitea repo 삭제 동기화. 베스트 에포트(실패해도 Relay 삭제 유지, 에러 로그).
  • 인프라 변경 없음(외부 Gitea). 토큰은 JWT 처럼 backend/.env.{env} 의 백엔드 고유 시크릿. 커밋용 backend/.env.{env}.example 에 키 문서화.

TODO(프론트): RepoSettingsPage/RepoDetailPage 에 cloneUrl/htmlUrl 표시(클론 주소 복사, Gitea 열기 버튼). 백엔드 응답에는 이미 포함.

3단계 — 멤버 관리 (백엔드)

백엔드 backend/src/modules/repo/

  • MemberService + MemberController(repo 모듈에 흡수, RepoMember 엔티티 재사용). 엔드포인트: GET/POST/PATCH/DELETE /repos/:repoId/members[/:userId].
    • 목록: 멤버이거나 공개 저장소만 조회(생성순 정렬). 응답 RepoMemberResponse { user, email, subRole, taskCount, roleType, owner }.
    • 초대: admin 권한. 이미 가입된 사용자만 이메일로 초대(별도 초대 메일/계정 생성 없음). 미가입 이메일→BIZ_001(404), 이미 멤버→BIZ_001(409).
    • 역할/직무 변경·제거: admin 권한. 소유자(owner)는 역할 변경·제거 불가(BIZ_001 403).
  • InviteMemberDto(email/roleType/subRole?), UpdateMemberDto(roleType?/subRole?). 단위 테스트 member.service.spec.ts(소유자 보호·중복·권한 가드).
  • taskCount 는 4단계 업무 모듈 도입 전까지 0.
  • Gitea 미동기화: 인증 모델이 조직 토큰 단일 사용이라 멤버↔Gitea collaborator 동기화는 하지 않음(사용자별 Gitea 계정 매핑은 8단계 후보).

TODO(프론트): useMember/member.store, RepoMembersPage(초대 모달·역할 변경·제거) 연동.


3. 남은 단계

4단계 — 업무(Task) CRUD + 상태 전이

  • 백엔드: Task(+ChecklistItem) 엔티티, GET/POST/PATCH/DELETE /repos/:repoId/tasks[/:taskId], POST .../status. 상태 머신 todo→prog→review→done/changes.
  • 저장소 doneCount/totalCount 실제 집계로 교체(현재 0 하드코딩).
  • 프론트: RepoDetailPage 업무 목록, TaskCreatePage, TaskDetailPage(지시자), TaskAssigneePage(담당자) 연동.

5단계 — 업무 상세 부속

  • 체크리스트 토글, 댓글/답글, 파일 첨부, 승인 워크플로우(승인 요청/승인/수정 요청).
  • 결정 필요: 파일 저장소(로컬 볼륨 vs S3/MinIO).

6단계 — 활동 피드

  • 2~5단계 행위를 이벤트로 적재(쓰기 API 없음, 읽기만). GET /repos/:repoId/activity.
  • 프론트: RepoActivityPage, TaskDetail 활동 탭.
  • 결정 필요: 활동 표현 — 현재 mock은 HTML 통문자열 → 구조화(타입+행위자+대상) 권장.

7단계 — 내 업무 집계

  • GET /tasks/assigned·/tasks/issued(저장소 횡단 집계). 프론트: MyTasksPage(담당/지시 탭).

8단계 — 마무리(선택)

  • 소셜 로그인(Google/Kakao), 이메일 인증, 알림, AI 에이전트 패널, Redis 캐싱, 전체 페이지네이션 통일.

4. 전환기 한계 (현재 시점)

  • RepoMembersPage 는 백엔드 API(3단계)는 준비됐으나 프론트 연동 대기 → 연동 전까지 mock 사용. RepoActivityPage 는 아직 mock findRepo 사용(6단계에서 해소).
  • RepoDetail 업무 목록도 mock(4단계). 업무 모듈이 없어 모든 저장소 진행률은 0% / 시작 전.
  • src/mock/relay.mock.ts 는 아직 타입 정의(Repo/User/TaskStatus 등) + 미연동 화면 데이터 공급원으로 사용 중. 해당 도메인이 연동되면 데이터는 제거하고 타입만 src/types/ 로 이전.

5. 알아둘 사항 / 결정 기록

  • 엔티티 순환참조: 서로 참조하는 관계(예: RepoRepoMember)의 단일 클래스 관계 속성에는 TypeORM Relation<> 래퍼를 사용한다. 안 그러면 SWC 환경에서 Cannot access 'X' before initialization 크래시. → 4·5단계(Task↔Repo, Comment↔Task 등)에도 동일 적용.
  • bcrypt → bcryptjs: Docker 베이스가 node:20-alpine(빌드 도구 없음)이라 네이티브 모듈 bcrypt 대신 순수 JS bcryptjs 사용.
  • dev 도커 익명 볼륨: /app/node_modules 가 익명 볼륨이라 의존성 변경 시 up -d --build -V(익명 볼륨 갱신) 필요. --build 만으로는 기존 node_modules 볼륨이 재사용되어 새 패키지가 반영되지 않음.
  • owner 고정: 조직 모델이 없어 owner='marketing-team' 상수. Gitea 연동 시 owner=GITEA_ORG 로 대체(미연동 시 상수 폴백). 조직 도입 시 교체.
  • ID 전략: User/Repo PK=UUID, Repo 라우팅 식별자=slugName, Task=저장소 내 순번(#9, 4단계).
  • Gitea 연동 방식: HTTP 클라이언트는 새 의존성(@nestjs/axios) 없이 Node 20 내장 fetch 사용(베이스 이미지 alpine 호환). 인증은 조직 토큰 1개(사용자별 Gitea 계정 매핑은 8단계 후보). slug↔Gitea repo name 1:1, 한글 표시제목은 Gitea description 에 합쳐 저장(Gitea 에 표시명 필드 없음).
  • 연동 비활성 폴백: GITEA_* 미설정 시 GiteaService.enabled=false → 저장소 CRUD 가 Gitea 없이 동작. 로컬 개발/테스트에서 Gitea 없이도 기존 흐름 유지.
  • 저장소 생성 폼 보강: ① 프로젝트 설명 필수화(CreateRepoDto.description required, 프론트 검증 추가). ② 템플릿 복제useTemplate=true 면 Gitea generate API 로 GITEA_TEMPLATE_OWNER/REPO(기본 TtiPo/project-base, template 설정 필요) 저장소를 복제 생성(브랜치는 템플릿을 따름). 프론트는 GET /repos/meta(owner+template) 로 owner 고정표시·템플릿 옵션을 받아 하드코딩 제거.

6. 실행 / 검증

# 개발 환경 기동 (의존성 변경 시 --build -V)
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d --build -V

# 백엔드 로그
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development logs -f backend
  • 백엔드: npm run build / npm run lint (현재 0 error)
  • 프론트: npm run type-check / npm run lint / npm run build-only (현재 0 error)
  • dev synchronize=trueusers·repos·repo_members 테이블 자동 생성(Gitea 연동 컬럼 포함). DB가 비어 있으므로 회원가입 후 로그인.
  • 포트: 프론트 5273, 백엔드 3100(/api), Swagger http://localhost:3100/api-docs.

Gitea 연동 설정/검증

  1. backend/.env.{env}GITEA_BASE_URL/GITEA_TOKEN/GITEA_ORG 채우기(예시: backend/.env.{env}.example).
    • 토큰: Gitea → Settings → Applications → Generate Token(조직 repo 생성 권한 필요).
    • docker-compose 는 backend/.env.{env}env_file 로 자동 주입하므로 compose 수정 불필요.
  2. 부팅 로그에서 Gitea 연동 활성화 — <url> (org: <org>) 확인(미설정 시 비활성 경고).
  3. Relay 에서 저장소 생성 → Gitea <org> 조직에 동일 slug repo 가 생기는지 확인. 응답 cloneUrl/htmlUrl 채워짐.
  4. 수정(설명/공개범위)·삭제 시 Gitea 측 반영 확인.