diff --git a/backend/src/modules/repo/repo.controller.ts b/backend/src/modules/repo/repo.controller.ts index 5d81a4d..fc088d8 100644 --- a/backend/src/modules/repo/repo.controller.ts +++ b/backend/src/modules/repo/repo.controller.ts @@ -65,6 +65,14 @@ export class RepoController { return this.repoService.getCreateMeta(); } + // ':repoId' 보다 먼저 선언 — 생성 폼의 이름(slug) 실시간 가용성 체크 + @Get('name-available') + @ApiOperation({ summary: '저장소 이름(slug) 사용 가능 여부' }) + @ApiResponse({ status: 200, description: '확인 성공' }) + checkName(@Query('slug') slug: string): Promise<{ available: boolean }> { + return this.repoService.isNameAvailable(slug ?? ''); + } + @Get(':repoId') @ApiOperation({ summary: '저장소 단건 조회' }) @ApiResponse({ status: 200, description: '조회 성공' }) diff --git a/backend/src/modules/repo/repo.service.ts b/backend/src/modules/repo/repo.service.ts index a255430..5edae9d 100644 --- a/backend/src/modules/repo/repo.service.ts +++ b/backend/src/modules/repo/repo.service.ts @@ -116,6 +116,17 @@ export class RepoService { }); } + // 저장소 이름(slug) 사용 가능 여부 — 생성 폼의 실시간 체크용(Relay 중복만 검사). + // 빈 값/형식은 프론트가 거르고, Gitea 이름 충돌은 생성 시 최종 확인한다. + async isNameAvailable(slug: string): Promise<{ available: boolean }> { + const trimmed = (slug ?? '').trim(); + if (!trimmed) return { available: false }; + const existing = await this.repoRepo.findOne({ + where: { slugName: trimmed }, + }); + return { available: !existing }; + } + // 저장소 단건 — 조직 모델상 인증 사용자면 열람 가능(쓰기 권한은 별도 가드). // 응답에 현재 사용자의 권한(canManage/isOwner)을 함께 내려, 프론트가 설정 탭 노출·진입을 결정한다. async findOne(slugName: string, userId: string): Promise { diff --git a/frontend/src/composables/useRepo.ts b/frontend/src/composables/useRepo.ts index ef76073..0d866d0 100644 --- a/frontend/src/composables/useRepo.ts +++ b/frontend/src/composables/useRepo.ts @@ -22,6 +22,14 @@ export function useRepo() { return (await api.get('/repos/meta')) as unknown as RepoCreateMeta } + // 이름(slug) 사용 가능 여부 — 생성 폼 실시간 체크 (실패해도 전역 알림 X) + async function checkName(slug: string): Promise<{ available: boolean }> { + return (await api.get('/repos/name-available', { + params: { slug }, + silent: true, + })) as unknown as { available: boolean } + } + async function get(id: string): Promise { return (await api.get(`/repos/${id}`)) as unknown as ApiRepo } @@ -38,5 +46,5 @@ export function useRepo() { await api.delete(`/repos/${id}`) } - return { list, meta, get, create, update, remove } + return { list, meta, checkName, get, create, update, remove } } diff --git a/frontend/src/pages/relay/RepoCreatePage.vue b/frontend/src/pages/relay/RepoCreatePage.vue index 56aedc3..3113011 100644 --- a/frontend/src/pages/relay/RepoCreatePage.vue +++ b/frontend/src/pages/relay/RepoCreatePage.vue @@ -1,18 +1,19 @@