Files
hanarang-dashboard/backend/src/gitea/gitea.service.ts
narang-ai 0b23deca48 fix(sprint-002): CR-001 GiteaService graceful fallback, CR-002 DTO validation
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 })
2026-04-04 11:43:35 +09:00

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 [];
}
}
}