refactor: 저장소 공개 범위(visibility) UI 제거 + 기본 공개(public) 생성
사용자 입력/표시에서 저장소 공개 범위를 제거하고 새 저장소는 공개로 생성한다. (Gitea 역방향 동기화에 쓰이는 Repo 엔티티 visibility 컬럼은 유지) 백엔드: - CreateRepoDto/UpdateRepoDto 에서 visibility 제거 - RepoService.create 가 visibility 를 'public' 으로 고정 - update 의 visibility 변경·Gitea private 푸시 로직 제거(공개범위는 Gitea 기준) - RepoResponse/toResponse 에서 visibility 제거 프론트: - types/repo ApiRepo/Create/Update 페이로드에서 visibility 제거 - ProjectReposPage 연동 폼의 공개 범위 입력 + 목록 배지 + 관련 CSS 제거 - mock Visibility 타입·orphan Repo 뷰의 visibility 제거 런타임 검증: 저장소 응답에 visibility 없음, repos.visibility 컬럼 유지(Gitea 동기화). 백엔드 build/lint 0·14 tests, 프론트 type-check/lint/build 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
IsBoolean,
|
||||
IsIn,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
@@ -8,7 +7,6 @@ import {
|
||||
MaxLength,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import type { RepoVisibility } from '../entities/repo.entity';
|
||||
|
||||
// 저장소 생성 DTO
|
||||
export class CreateRepoDto {
|
||||
@@ -34,14 +32,6 @@ export class CreateRepoDto {
|
||||
@MaxLength(40)
|
||||
name!: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '공개 범위',
|
||||
enum: ['private', 'public'],
|
||||
example: 'private',
|
||||
})
|
||||
@IsIn(['private', 'public'], { message: '공개 범위를 선택해 주세요.' })
|
||||
visibility!: RepoVisibility;
|
||||
|
||||
@ApiProperty({
|
||||
description: '프로젝트 설명(필수)',
|
||||
example: '3분기 신제품 런칭을 위한 마케팅 캠페인 저장소',
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { IsIn, IsOptional, IsString, MaxLength } from 'class-validator';
|
||||
import { IsOptional, IsString, MaxLength } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import type { RepoVisibility } from '../entities/repo.entity';
|
||||
|
||||
// 저장소 수정 DTO — 표시 제목/설명/공개범위/브랜치 (slug·owner 는 변경 불가)
|
||||
// 저장소 수정 DTO — 표시 제목/설명/브랜치 (slug·owner 는 변경 불가, 공개범위는 Gitea 기준)
|
||||
export class UpdateRepoDto {
|
||||
@ApiProperty({ description: '표시 제목(한글)', required: false })
|
||||
@IsOptional()
|
||||
@@ -16,15 +15,6 @@ export class UpdateRepoDto {
|
||||
@MaxLength(300)
|
||||
description?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '공개 범위',
|
||||
enum: ['private', 'public'],
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsIn(['private', 'public'], { message: '공개 범위를 선택해 주세요.' })
|
||||
visibility?: RepoVisibility;
|
||||
|
||||
@ApiProperty({ description: '메인 브랜치', required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
@@ -37,7 +37,6 @@ export interface RepoResponse {
|
||||
owner: string;
|
||||
slug: string; // owner/slugName
|
||||
desc: string | null;
|
||||
visibility: RepoVisibility;
|
||||
branch: string;
|
||||
cloneUrl: string | null;
|
||||
htmlUrl: string | null;
|
||||
@@ -139,7 +138,8 @@ export class RepoService {
|
||||
}
|
||||
|
||||
const description = dto.description;
|
||||
const visibility = dto.visibility;
|
||||
// 공개 범위는 사용자 입력에서 제거됨 — 새 저장소는 공개(public)로 생성
|
||||
const visibility: RepoVisibility = 'public';
|
||||
const requestedBranch = dto.branch?.trim() || 'main';
|
||||
const owner = this.gitea.enabled ? this.gitea.org : DEFAULT_OWNER;
|
||||
|
||||
@@ -206,37 +206,31 @@ export class RepoService {
|
||||
|
||||
const prevName = repo.name;
|
||||
const prevDescription = repo.description ?? '';
|
||||
const prevVisibility = repo.visibility;
|
||||
const prevBranch = repo.branch;
|
||||
|
||||
if (dto.name !== undefined) repo.name = dto.name;
|
||||
if (dto.description !== undefined) repo.description = dto.description;
|
||||
if (dto.visibility !== undefined) repo.visibility = dto.visibility;
|
||||
if (dto.branch !== undefined && dto.branch.trim())
|
||||
repo.branch = dto.branch.trim();
|
||||
|
||||
const nameChanged = repo.name !== prevName;
|
||||
const descriptionChanged = (repo.description ?? '') !== prevDescription;
|
||||
const visibilityChanged = repo.visibility !== prevVisibility;
|
||||
const branchChanged = repo.branch !== prevBranch;
|
||||
|
||||
await this.repoRepo.save(repo);
|
||||
|
||||
// Gitea 메타데이터 동기화 (실제로 바뀐 경우에만)
|
||||
// Gitea 메타데이터 동기화 (실제로 바뀐 경우에만). 공개 범위는 Gitea 기준이라 푸시하지 않음.
|
||||
const giteaDescChanged = nameChanged || descriptionChanged;
|
||||
if (
|
||||
this.gitea.enabled &&
|
||||
repo.giteaRepoId !== null &&
|
||||
(giteaDescChanged || visibilityChanged || branchChanged)
|
||||
(giteaDescChanged || branchChanged)
|
||||
) {
|
||||
await this.gitea
|
||||
.updateRepo(repo.slugName, {
|
||||
description: giteaDescChanged
|
||||
? this.buildGiteaDescription(repo.name, repo.description)
|
||||
: undefined,
|
||||
private: visibilityChanged
|
||||
? repo.visibility === 'private'
|
||||
: undefined,
|
||||
defaultBranch: branchChanged ? repo.branch : undefined,
|
||||
})
|
||||
.catch((e: unknown) => {
|
||||
@@ -376,7 +370,6 @@ export class RepoService {
|
||||
owner: repo.owner,
|
||||
slug: `${repo.owner}/${repo.slugName}`,
|
||||
desc: repo.description,
|
||||
visibility: repo.visibility,
|
||||
branch: repo.branch,
|
||||
cloneUrl: repo.cloneUrl,
|
||||
htmlUrl: repo.htmlUrl,
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
/** 업무 상태 */
|
||||
export type TaskStatus = 'todo' | 'prog' | 'review' | 'done' | 'changes'
|
||||
|
||||
/** 저장소 공개 여부 */
|
||||
export type Visibility = 'private' | 'public'
|
||||
|
||||
/** 사용자(멤버) */
|
||||
export interface User {
|
||||
id: string
|
||||
@@ -46,7 +43,6 @@ export interface Repo {
|
||||
owner: string
|
||||
slug: string
|
||||
desc: string
|
||||
visibility: Visibility
|
||||
branch: string
|
||||
members: User[]
|
||||
/** 멤버 스택에서 추가로 더 있는 인원 수(+N) */
|
||||
|
||||
@@ -7,7 +7,7 @@ import ProjectHeader from '@/components/ProjectHeader.vue'
|
||||
import ProjectTabs from '@/components/ProjectTabs.vue'
|
||||
import { useProjectStore } from '@/stores/project.store'
|
||||
import { useRepo } from '@/composables/useRepo'
|
||||
import type { Project, Visibility } from '@/mock/relay.mock'
|
||||
import type { Project } from '@/mock/relay.mock'
|
||||
import type { ApiRepo, RepoCreateMeta } from '@/types/repo'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -60,7 +60,6 @@ const meta = ref<RepoCreateMeta | null>(null)
|
||||
const slug = ref('')
|
||||
const name = ref('')
|
||||
const description = ref('')
|
||||
const visibility = ref<Visibility>('private')
|
||||
const useTemplate = ref(false)
|
||||
const submitting = ref(false)
|
||||
|
||||
@@ -88,7 +87,6 @@ function closeForm() {
|
||||
slug.value = ''
|
||||
name.value = ''
|
||||
description.value = ''
|
||||
visibility.value = 'private'
|
||||
useTemplate.value = false
|
||||
slugAvailable.value = null
|
||||
}
|
||||
@@ -132,7 +130,6 @@ async function linkRepo() {
|
||||
slug: slug.value.trim(),
|
||||
name: name.value.trim(),
|
||||
description: description.value.trim(),
|
||||
visibility: visibility.value,
|
||||
...(meta.value?.template.available ? { useTemplate: useTemplate.value } : {}),
|
||||
})
|
||||
closeForm()
|
||||
@@ -301,30 +298,6 @@ async function unlinkRepo(repo: ApiRepo) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="fblock">
|
||||
<div class="flabel">
|
||||
<span class="req">*</span> 공개 범위
|
||||
</div>
|
||||
<div class="radio-row">
|
||||
<button
|
||||
class="radio-chip"
|
||||
:class="{ sel: visibility === 'private' }"
|
||||
type="button"
|
||||
@click="visibility = 'private'"
|
||||
>
|
||||
비공개
|
||||
</button>
|
||||
<button
|
||||
class="radio-chip"
|
||||
:class="{ sel: visibility === 'public' }"
|
||||
type="button"
|
||||
@click="visibility = 'public'"
|
||||
>
|
||||
공개
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 템플릿 -->
|
||||
<div class="fblock">
|
||||
<div class="flabel">
|
||||
@@ -402,10 +375,6 @@ async function unlinkRepo(repo: ApiRepo) {
|
||||
<div class="repo-main">
|
||||
<div class="repo-title">
|
||||
<span class="repo-name">{{ repo.name }}</span>
|
||||
<span
|
||||
class="vis"
|
||||
:class="repo.visibility"
|
||||
>{{ repo.visibility === 'private' ? '비공개' : '공개' }}</span>
|
||||
<span
|
||||
v-if="repo.giteaMissing"
|
||||
class="missing"
|
||||
@@ -603,27 +572,6 @@ textarea.tarea {
|
||||
.fstatus.bad {
|
||||
color: var(--red);
|
||||
}
|
||||
.radio-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.radio-chip {
|
||||
height: 2.25rem;
|
||||
padding: 0 1rem;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius);
|
||||
background: #fff;
|
||||
font-family: inherit;
|
||||
font-size: 0.844rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
.radio-chip.sel {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent-weak);
|
||||
color: var(--accent);
|
||||
}
|
||||
/* 템플릿 select */
|
||||
.select-wrap {
|
||||
position: relative;
|
||||
@@ -717,18 +665,6 @@ textarea.tarea {
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
.vis {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-2);
|
||||
background: #f1f2f4;
|
||||
border-radius: 20px;
|
||||
padding: 0.125rem 0.5rem;
|
||||
}
|
||||
.vis.public {
|
||||
color: #1b7a3d;
|
||||
background: var(--green-weak);
|
||||
}
|
||||
.missing {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { Visibility } from '@/mock/relay.mock'
|
||||
|
||||
// 저장소 멤버(API 원시) — PublicUser 와 동일
|
||||
export interface ApiMember {
|
||||
id: string
|
||||
@@ -17,7 +15,6 @@ export interface ApiRepo {
|
||||
owner: string
|
||||
slug: string // owner/slugName
|
||||
desc: string | null
|
||||
visibility: Visibility
|
||||
branch: string
|
||||
cloneUrl: string | null // Gitea HTTPS clone URL (미연동 시 null)
|
||||
htmlUrl: string | null // Gitea 웹 UI URL (미연동 시 null)
|
||||
@@ -32,7 +29,6 @@ export interface ApiRepo {
|
||||
export interface CreateRepoPayload {
|
||||
slug: string // 영문 저장소 이름
|
||||
name: string // 표시 제목(한글)
|
||||
visibility: Visibility
|
||||
description: string // 프로젝트 설명(필수)
|
||||
branch?: string
|
||||
useTemplate?: boolean // 템플릿 저장소 복제 여부
|
||||
@@ -48,6 +44,5 @@ export interface RepoCreateMeta {
|
||||
export interface UpdateRepoPayload {
|
||||
name?: string
|
||||
description?: string
|
||||
visibility?: Visibility
|
||||
branch?: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user