feat: Gitea 프론트 연동(클론바·원격없음 배지)·import-admin 인프라 마무리
- GiteaRepoBar 컴포넌트(클론 주소 복사 + Gitea 열기)를 RepoSettingsPage 에 적용 - repo.store 뷰에 cloneUrl/htmlUrl/giteaMissing 매핑 - repo-sync: import 저장소 owner-admin 자동 지정(REPO_IMPORT_ADMIN_EMAIL) - env example·docker-compose.dev 에 Gitea/import-admin 키 정비 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { UserService } from '../user/user.service';
|
||||
import type { User } from '../user/entities/user.entity';
|
||||
import { GiteaService } from '../gitea/gitea.service';
|
||||
import { Repo } from './entities/repo.entity';
|
||||
import { RepoMember } from './entities/repo-member.entity';
|
||||
|
||||
// Gitea description 에서 분리한 표시명/설명
|
||||
interface ParsedDescription {
|
||||
@@ -21,12 +25,23 @@ export class RepoSyncService implements OnApplicationBootstrap {
|
||||
private readonly logger = new Logger(RepoSyncService.name);
|
||||
// 동기화 중복 실행 방지(부트스트랩과 스케줄이 겹치는 경우)
|
||||
private running = false;
|
||||
// import(멤버 0명) 저장소에 기본 owner-admin 으로 지정할 사용자 이메일.
|
||||
// 동기화는 시스템 작업이라 실행 주체가 없으므로 env 로 지정한다. 비어 있으면 멤버 0명으로 둔다.
|
||||
private readonly importAdminEmail: string;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Repo)
|
||||
private readonly repoRepo: Repository<Repo>,
|
||||
@InjectRepository(RepoMember)
|
||||
private readonly memberRepo: Repository<RepoMember>,
|
||||
private readonly userService: UserService,
|
||||
private readonly gitea: GiteaService,
|
||||
) {}
|
||||
config: ConfigService,
|
||||
) {
|
||||
this.importAdminEmail = (
|
||||
config.get<string>('REPO_IMPORT_ADMIN_EMAIL') ?? ''
|
||||
).trim();
|
||||
}
|
||||
|
||||
// 서버 기동 직후 1회 동기화 — 부팅을 막지 않도록 비차단(백그라운드)으로 실행
|
||||
onApplicationBootstrap(): void {
|
||||
@@ -111,8 +126,12 @@ export class RepoSyncService implements OnApplicationBootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
// import(멤버 0명) 저장소에 기본 owner-admin 지정 — 멤버 할당 데드락 방지.
|
||||
// (env REPO_IMPORT_ADMIN_EMAIL 미설정/미가입 시 건너뜀)
|
||||
const adminAssigned = await this.ensureImportAdmin();
|
||||
|
||||
this.logger.log(
|
||||
`Gitea 동기화 완료 (${trigger}) — 원격 ${remote.length}개 / 신규 ${imported} · 갱신 ${relinked} · 누락표시 ${missing}`,
|
||||
`Gitea 동기화 완료 (${trigger}) — 원격 ${remote.length}개 / 신규 ${imported} · 갱신 ${relinked} · 누락표시 ${missing} · admin지정 ${adminAssigned}`,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(
|
||||
@@ -124,6 +143,47 @@ export class RepoSyncService implements OnApplicationBootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
// import(멤버 0명) Gitea 연동 저장소에 기본 owner-admin 을 지정한다.
|
||||
// env REPO_IMPORT_ADMIN_EMAIL 의 가입 사용자를 owner-admin 으로 등록(신규 import + 기존 멤버 0명 모두 대상).
|
||||
// 반환: 이번 실행에서 admin 을 새로 붙인 저장소 수.
|
||||
private async ensureImportAdmin(): Promise<number> {
|
||||
if (!this.importAdminEmail) return 0;
|
||||
|
||||
const admin = await this.userService.findByEmail(this.importAdminEmail);
|
||||
if (!admin) {
|
||||
this.logger.warn(
|
||||
`REPO_IMPORT_ADMIN_EMAIL(${this.importAdminEmail}) 에 해당하는 가입 사용자가 없어 import 저장소 admin 지정을 건너뜁니다. (가입 후 다음 동기화에서 지정됨)`,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Gitea 연동(import) 저장소 중 멤버가 0명인 것만 대상.
|
||||
// Relay 생성 저장소는 생성자(owner)가 항상 멤버이므로 제외된다.
|
||||
const linked = await this.repoRepo.find({ relations: ['members'] });
|
||||
let assigned = 0;
|
||||
for (const repo of linked) {
|
||||
if (repo.giteaRepoId === null) continue;
|
||||
if ((repo.members?.length ?? 0) > 0) continue;
|
||||
await this.assignOwnerAdmin(repo, admin);
|
||||
assigned++;
|
||||
this.logger.log(
|
||||
`import 저장소 owner-admin 지정: ${repo.slugName} ← ${admin.email}`,
|
||||
);
|
||||
}
|
||||
return assigned;
|
||||
}
|
||||
|
||||
// 저장소에 사용자를 owner-admin 멤버로 등록(Relay 생성 시 owner 등록과 동일 규약)
|
||||
private async assignOwnerAdmin(repo: Repo, user: User): Promise<void> {
|
||||
const member = this.memberRepo.create({
|
||||
repo,
|
||||
user,
|
||||
roleType: 'admin',
|
||||
isOwner: true,
|
||||
});
|
||||
await this.memberRepo.save(member);
|
||||
}
|
||||
|
||||
// 기존 저장소에 Gitea 연동정보를 채우고 누락표시를 해제 — 변경이 있었으면 true
|
||||
private backfillLinkage(
|
||||
repo: Repo,
|
||||
|
||||
Reference in New Issue
Block a user