feat: M4-06 — project center with Gitea API integration
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 41s
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 41s
- GiteaService: query repos, milestones, issues, releases, runners - ProjectCenterController: 5 AAPI endpoints - Replace iframe-only GiteaEmbed with full ProjectCenter page (repos table, issues, milestones cards, releases, runners, Gitea embed tab) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
b188988e82
commit
8abf94872a
@ -54,6 +54,7 @@ import { CacheModule } from './common/cache/cache.module';
|
|||||||
import { AdminCacheModule } from './modules/admin-cache/admin-cache.module';
|
import { AdminCacheModule } from './modules/admin-cache/admin-cache.module';
|
||||||
import { BackupModule } from './modules/backup/backup.module';
|
import { BackupModule } from './modules/backup/backup.module';
|
||||||
import { ReportingModule } from './modules/reporting/reporting.module';
|
import { ReportingModule } from './modules/reporting/reporting.module';
|
||||||
|
import { ProjectCenterModule } from './modules/project-center/project-center.module';
|
||||||
|
|
||||||
import { JwtAuthGuard } from './common/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from './common/guards/jwt-auth.guard';
|
||||||
import { RolesGuard } from './common/guards/roles.guard';
|
import { RolesGuard } from './common/guards/roles.guard';
|
||||||
@ -153,6 +154,7 @@ import appleConfig from './config/apple.config';
|
|||||||
AdminCacheModule,
|
AdminCacheModule,
|
||||||
BackupModule,
|
BackupModule,
|
||||||
ReportingModule,
|
ReportingModule,
|
||||||
|
ProjectCenterModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: APP_GUARD, useClass: RateLimitGuard },
|
{ provide: APP_GUARD, useClass: RateLimitGuard },
|
||||||
|
|||||||
58
src/modules/project-center/gitea.service.ts
Normal file
58
src/modules/project-center/gitea.service.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
const GITEA_BASE = 'https://git.admin.longde.cloud/api/v1';
|
||||||
|
const GITEA_TOKEN = process.env.GITEA_TOKEN || '0b190ab69b751c6f4ce3f92cbd3ed762e5cd5bfa';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GiteaService {
|
||||||
|
private readonly logger = new Logger(GiteaService.name);
|
||||||
|
|
||||||
|
private async giteaGet<T>(path: string): Promise<T | null> {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${GITEA_BASE}${path}`, {
|
||||||
|
headers: { Authorization: `token ${GITEA_TOKEN}`, Accept: 'application/json' },
|
||||||
|
});
|
||||||
|
if (!res.ok) { this.logger.warn(`Gitea GET ${path}: ${res.status}`); return null; }
|
||||||
|
return await res.json() as T;
|
||||||
|
} catch (err: any) {
|
||||||
|
this.logger.warn(`Gitea GET ${path} failed: ${err.message}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRepos() {
|
||||||
|
const repos = await this.giteaGet<any[]>('/repos/search?limit=50');
|
||||||
|
if (!repos) return [];
|
||||||
|
return Promise.all(repos.map(async (r: any) => {
|
||||||
|
const [issues, pulls, milestones] = await Promise.all([
|
||||||
|
this.giteaGet<any[]>(`/repos/${r.full_name}/issues?state=all&limit=1`).then(d => d?.length ?? 0).catch(() => 0),
|
||||||
|
this.giteaGet<any[]>(`/repos/${r.full_name}/pulls?state=all&limit=1`).then(d => d?.length ?? 0).catch(() => 0),
|
||||||
|
this.giteaGet<any[]>(`/repos/${r.full_name}/milestones?state=open`).catch(() => []),
|
||||||
|
]);
|
||||||
|
return {
|
||||||
|
id: r.id, name: r.name, fullName: r.full_name, description: r.description,
|
||||||
|
owner: r.owner?.login, stars: r.stars_count, forks: r.forks_count,
|
||||||
|
openIssues: r.open_issues_count, openPulls: pulls, milestones: milestones?.length ?? 0,
|
||||||
|
defaultBranch: r.default_branch, updatedAt: r.updated_at,
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMilestones(owner: string, repo: string) {
|
||||||
|
return this.giteaGet<any[]>(`/repos/${owner}/${repo}/milestones?state=all`) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIssues(owner: string, repo: string, milestone?: string, state = 'open') {
|
||||||
|
let path = `/repos/${owner}/${repo}/issues?state=${state}&limit=50`;
|
||||||
|
if (milestone) path += `&milestone=${milestone}`;
|
||||||
|
return this.giteaGet<any[]>(path) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRunners() {
|
||||||
|
return this.giteaGet<any[]>('/admin/runners') ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getReleases(owner: string, repo: string) {
|
||||||
|
return this.giteaGet<any[]>(`/repos/${owner}/${repo}/releases?limit=20`) ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/modules/project-center/project-center.controller.ts
Normal file
48
src/modules/project-center/project-center.controller.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiBearerAuth, ApiOperation, ApiQuery } from '@nestjs/swagger';
|
||||||
|
import { GiteaService } from './gitea.service';
|
||||||
|
import { AdminAuthGuard } from '../../common/guards/admin-auth.guard';
|
||||||
|
import { AdminRolesGuard } from '../../common/guards/admin-roles.guard';
|
||||||
|
|
||||||
|
@ApiTags('admin-project-center')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Controller('admin-api/projects')
|
||||||
|
@UseGuards(AdminAuthGuard, AdminRolesGuard)
|
||||||
|
export class ProjectCenterController {
|
||||||
|
constructor(private readonly gitea: GiteaService) {}
|
||||||
|
|
||||||
|
@Get('repos')
|
||||||
|
@ApiOperation({ summary: 'Gitea 仓库列表' })
|
||||||
|
async getRepos() {
|
||||||
|
return this.gitea.getRepos();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('repos/:owner/:repo/milestones')
|
||||||
|
@ApiOperation({ summary: '仓库里程碑' })
|
||||||
|
async getMilestones(@Param('owner') owner: string, @Param('repo') repo: string) {
|
||||||
|
return this.gitea.getMilestones(owner, repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('repos/:owner/:repo/issues')
|
||||||
|
@ApiOperation({ summary: '仓库 Issue 列表' })
|
||||||
|
@ApiQuery({ name: 'milestone', required: false })
|
||||||
|
@ApiQuery({ name: 'state', required: false })
|
||||||
|
async getIssues(
|
||||||
|
@Param('owner') owner: string, @Param('repo') repo: string,
|
||||||
|
@Query('milestone') milestone?: string, @Query('state') state?: string,
|
||||||
|
) {
|
||||||
|
return this.gitea.getIssues(owner, repo, milestone, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('repos/:owner/:repo/releases')
|
||||||
|
@ApiOperation({ summary: '仓库 Release 列表' })
|
||||||
|
async getReleases(@Param('owner') owner: string, @Param('repo') repo: string) {
|
||||||
|
return this.gitea.getReleases(owner, repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('runners')
|
||||||
|
@ApiOperation({ summary: 'Gitea Runner 状态' })
|
||||||
|
async getRunners() {
|
||||||
|
return this.gitea.getRunners();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/modules/project-center/project-center.module.ts
Normal file
10
src/modules/project-center/project-center.module.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ProjectCenterController } from './project-center.controller';
|
||||||
|
import { GiteaService } from './gitea.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [ProjectCenterController],
|
||||||
|
providers: [GiteaService],
|
||||||
|
exports: [GiteaService],
|
||||||
|
})
|
||||||
|
export class ProjectCenterModule {}
|
||||||
Loading…
x
Reference in New Issue
Block a user