feat: 저장소 생성 시 Gitea 조직 연동 추가
외부 Gitea 인스턴스의 조직에 저장소를 자동 동기화한다. - GiteaService: /api/v1 클라이언트(내장 fetch, 새 의존성 없음), enabled 게이트 - Repo 엔티티에 연동 컬럼(giteaRepoId/giteaFullName/cloneUrl/htmlUrl) 추가 - RepoService 생성/수정/삭제에 Gitea 동기화 와이어링(생성 우선·실패 시 롤백) - GITEA_* env 문서화(.env.example), api-contract/진행현황 문서 갱신 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+21
-3
@@ -85,15 +85,15 @@ status 매핑보다 우선해 그대로 표준 응답으로 내려보내고, 프
|
||||
| PATCH | `/repos/:repoId` | `UpdateRepoDto` | `Repo` |
|
||||
| DELETE | `/repos/:repoId` | — | `null` |
|
||||
|
||||
`CreateRepoDto`: `{ name, desc, visibility('private'|'public'), branch? }`
|
||||
`CreateRepoDto`: `{ slug, name, visibility('private'|'public'), description?, branch? }`
|
||||
`Repo` 응답(원시값 중심):
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"id": "q3-launch-campaign", // slug 마지막 segment = 라우팅 id
|
||||
"name": "3분기 신제품 런칭 캠페인",
|
||||
"owner": "marketing-team",
|
||||
"slug": "marketing-team/q3-launch-campaign",
|
||||
"owner": "marketing-team", // Gitea 연동 시 Gitea 조직명
|
||||
"slug": "marketing-team/q3-launch-campaign", // = Gitea full_name
|
||||
"desc": "...",
|
||||
"visibility": "private",
|
||||
"branch": "main",
|
||||
@@ -101,12 +101,30 @@ status 매핑보다 우선해 그대로 표준 응답으로 내려보내고, 프
|
||||
"memberCount": 5,
|
||||
"doneCount": 8,
|
||||
"totalCount": 12,
|
||||
"cloneUrl": "https://gitea.example.com/marketing-team/q3-launch-campaign.git", // 미연동 시 null
|
||||
"htmlUrl": "https://gitea.example.com/marketing-team/q3-launch-campaign", // 미연동 시 null
|
||||
"updatedAt": "2026-06-16T11:00:00Z"
|
||||
}
|
||||
```
|
||||
> `moreCount`, `progressPct/Level/Label`, `updatedAgo` 는 프론트 계산.
|
||||
> 매핑 페이지: RepoListPage, RepoCreatePage, RepoDetailPage(헤더), RepoSettingsPage.
|
||||
|
||||
#### Gitea 연동 (외부 인스턴스)
|
||||
|
||||
저장소 생명주기를 외부 Gitea 조직에 동기화한다. `GITEA_BASE_URL`/`GITEA_TOKEN`/`GITEA_ORG`
|
||||
(backend `.env`) 가 모두 설정되면 활성화되고, 비어 있으면 Gitea 호출 없이 기존대로 동작한다.
|
||||
|
||||
| Relay 동작 | Gitea API | 매핑 |
|
||||
|---|---|---|
|
||||
| `POST /repos` | `POST /api/v1/orgs/{org}/repos` (auto_init) | `slug`→repo name, `visibility`→private, `branch`→default_branch, `name`+`desc`→description |
|
||||
| `PATCH /repos/:id` | `PATCH /api/v1/repos/{org}/{slug}` | `name`/`desc` 변경→description, `visibility` 변경→private (slug 불변) |
|
||||
| `DELETE /repos/:id` | `DELETE /api/v1/repos/{org}/{slug}` | — |
|
||||
|
||||
- 인증: 조직 권한 토큰 단일 사용(`Authorization: token <TOKEN>`). 사용자별 Gitea 계정 매핑은 추후.
|
||||
- 생성은 Gitea 우선 → 성공 시 Relay 저장(연동 정보 `giteaRepoId`/`cloneUrl`/`htmlUrl` 보관). Relay 저장 실패 시 Gitea repo 롤백 삭제.
|
||||
- Gitea 이름 충돌(409) → `BIZ_001` "이미 사용 중인 저장소 이름입니다.", 그 외 실패 → `BIZ_001`(502).
|
||||
- 수정/삭제의 Gitea 동기화는 **베스트 에포트**(실패해도 Relay 상태는 반영, 에러 로그만 남김).
|
||||
|
||||
---
|
||||
|
||||
## 3. 멤버 (`/api/repos/:repoId/members`) — **3단계**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 프론트 UI(목업)를 NestJS 백엔드에 단계적으로 연동하는 작업의 진행 기록.
|
||||
> 계약/스키마 상세는 [`api-contract.md`](./api-contract.md) 참조.
|
||||
> 최종 갱신: 2026-06-16 · 현재 위치: **2단계(저장소) 완료, 3단계(멤버) 대기**
|
||||
> 최종 갱신: 2026-06-16 · 현재 위치: **2단계(저장소) 완료 + Gitea 연동, 3단계(멤버) 대기**
|
||||
|
||||
---
|
||||
|
||||
@@ -63,6 +63,20 @@
|
||||
- `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). `RepoResponse` 에 `cloneUrl`/`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. 남은 단계
|
||||
@@ -107,8 +121,10 @@
|
||||
- **엔티티 순환참조**: 서로 참조하는 관계(예: `Repo`↔`RepoMember`)의 **단일 클래스 관계 속성에는 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'` 상수. 조직 도입 시 교체.
|
||||
- **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 없이도 기존 흐름 유지.
|
||||
|
||||
---
|
||||
|
||||
@@ -124,5 +140,14 @@ docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.d
|
||||
|
||||
- 백엔드: `npm run build` / `npm run lint` (현재 0 error)
|
||||
- 프론트: `npm run type-check` / `npm run lint` / `npm run build-only` (현재 0 error)
|
||||
- dev `synchronize=true` 가 `users`·`repos`·`repo_members` 테이블 자동 생성. DB가 비어 있으므로 **회원가입 후 로그인**.
|
||||
- dev `synchronize=true` 가 `users`·`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 측 반영 확인.
|
||||
|
||||
Reference in New Issue
Block a user