CR-001: GiteaService constructor throw 제거
- 토큰 미설정 시 client=null + warn 로그로 graceful fallback
- isAvailable() 가드 추가, 모든 메서드 빈 배열/null 반환
CR-002: DTO class-validator 적용
- CreateSprintDto: @IsNumber @Min(1), @IsString
- UpdateTaskDto: @IsOptional @IsString @IsIn(VALID_STATUSES)
- main.ts: ValidationPipe({ whitelist, forbidNonWhitelisted, transform })
91 lines
2.3 KiB
TypeScript
91 lines
2.3 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import axios, { AxiosInstance } from 'axios';
|
|
|
|
export interface GiteaRepo {
|
|
id: number;
|
|
name: string;
|
|
full_name: string;
|
|
description: string;
|
|
html_url: string;
|
|
default_branch: string;
|
|
open_issues_count: number;
|
|
open_pr_counter: number;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface GiteaPR {
|
|
id: number;
|
|
number: number;
|
|
title: string;
|
|
state: string;
|
|
html_url: string;
|
|
user: { login: string };
|
|
created_at: string;
|
|
}
|
|
|
|
@Injectable()
|
|
export class GiteaService {
|
|
private readonly logger = new Logger(GiteaService.name);
|
|
private readonly client: AxiosInstance;
|
|
private readonly org: string;
|
|
|
|
constructor(private readonly config: ConfigService) {
|
|
const baseURL = config.get<string>('GITEA_BASE_URL');
|
|
const token = config.get<string>('GITEA_TOKEN');
|
|
this.org = config.get<string>('GITEA_ORG') ?? 'hanarang';
|
|
|
|
if (!baseURL || !token) {
|
|
this.logger.warn('GITEA_BASE_URL or GITEA_TOKEN not set — Gitea features disabled');
|
|
this.client = null as any;
|
|
return;
|
|
}
|
|
|
|
this.client = axios.create({
|
|
baseURL: `${baseURL}/api/v1`,
|
|
headers: { Authorization: `token ${token}` },
|
|
timeout: 10000,
|
|
});
|
|
}
|
|
|
|
private isAvailable(): boolean {
|
|
return this.client !== null;
|
|
}
|
|
|
|
async getOrgRepos(): Promise<GiteaRepo[]> {
|
|
if (!this.isAvailable()) return [];
|
|
try {
|
|
const { data } = await this.client.get<GiteaRepo[]>(`/orgs/${this.org}/repos`, {
|
|
params: { limit: 50 },
|
|
});
|
|
return data;
|
|
} catch (error) {
|
|
this.logger.warn('Failed to fetch Gitea repos');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getRepo(repoName: string): Promise<GiteaRepo | null> {
|
|
if (!this.isAvailable()) return null;
|
|
try {
|
|
const { data } = await this.client.get<GiteaRepo>(`/repos/${this.org}/${repoName}`);
|
|
return data;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async getOpenPRs(repoName: string): Promise<GiteaPR[]> {
|
|
if (!this.isAvailable()) return [];
|
|
try {
|
|
const { data } = await this.client.get<GiteaPR[]>(
|
|
`/repos/${this.org}/${repoName}/pulls`,
|
|
{ params: { state: 'open', limit: 20 } },
|
|
);
|
|
return data;
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
}
|