feat: 补齐 Gitea PR 来源摘要

This commit is contained in:
湛兮
2026-06-12 02:12:53 +08:00
parent 370d263770
commit fb736701ca
5 changed files with 182 additions and 31 deletions
+66 -1
View File
@@ -3,7 +3,12 @@ import { ConfigService } from '@nestjs/config';
import { IntegrationRequestError, IntegrationUnavailableError } from '../../common/errors/integration-error';
import { redactSensitive } from '../../common/security/redact-sensitive';
import { EnvConfig } from '../../config/env.schema';
import { GiteaBranch, GiteaCommitSummary, GiteaTag } from './gitea.types';
import {
GiteaBranch,
GiteaCommitSummary,
GiteaPullRequestSummary,
GiteaTag,
} from './gitea.types';
@Injectable()
export class GiteaClient {
@@ -62,6 +67,66 @@ export class GiteaClient {
};
}
async getCommitPullRequest(
owner: string,
repo: string,
sha: string,
): Promise<GiteaPullRequestSummary | undefined> {
const payload = await this.optionalRequest<{
number: number;
title: string;
state: string;
merged?: boolean;
merged_at?: string;
merge_commit_sha?: string;
head?: {
ref?: string;
sha?: string;
};
base?: {
ref?: string;
};
user?: {
login?: string;
};
}>(`/api/v1/repos/${owner}/${repo}/commits/${encodeURIComponent(sha)}/pull`);
if (!payload) {
return undefined;
}
return {
number: payload.number,
title: payload.title,
state: payload.state,
merged: payload.merged === true,
mergedAt: payload.merged_at,
mergeCommitSha: payload.merge_commit_sha,
headRef: payload.head?.ref,
headSha: payload.head?.sha,
baseRef: payload.base?.ref,
authorName: payload.user?.login,
};
}
private async optionalRequest<T>(
path: string,
init: RequestInit = {},
): Promise<T | undefined> {
try {
return await this.request<T>(path, init);
} catch (error) {
if (
error instanceof IntegrationRequestError &&
error.details?.status === 404
) {
return undefined;
}
throw error;
}
}
private async request<T>(path: string, init: RequestInit = {}): Promise<T> {
this.assertConfigured();
const baseUrl = this.config.get('GITEA_BASE_URL', { infer: true }) ?? '';
+13
View File
@@ -14,3 +14,16 @@ export type GiteaCommitSummary = {
authorName?: string;
authoredAt?: string;
};
export type GiteaPullRequestSummary = {
number: number;
title: string;
state: string;
merged: boolean;
mergedAt?: string;
mergeCommitSha?: string;
headRef?: string;
headSha?: string;
baseRef?: string;
authorName?: string;
};
+28 -20
View File
@@ -16,26 +16,34 @@ export type ProjectSummary = {
environments: ProjectEnvironmentSummary[];
};
export type ProjectPullRequestSummary = {
number: number;
title: string;
state: string;
merged: boolean;
mergedAt?: string;
mergeCommitSha?: string;
headRef?: string;
headSha?: string;
baseRef?: string;
authorName?: string;
url?: string;
};
export type ProjectRefItemSummary = {
name: string;
commitSha?: string;
commit?: {
sha: string;
message: string;
authorName?: string;
authoredAt?: string;
};
pullRequest?: ProjectPullRequestSummary;
};
export type ProjectRefSummary = {
source: 'gitea' | 'fallback';
branches: Array<{
name: string;
commitSha?: string;
commit?: {
sha: string;
message: string;
authorName?: string;
authoredAt?: string;
};
}>;
tags: Array<{
name: string;
commitSha?: string;
commit?: {
sha: string;
message: string;
authorName?: string;
authoredAt?: string;
};
}>;
branches: ProjectRefItemSummary[];
tags: ProjectRefItemSummary[];
};
+32 -1
View File
@@ -21,6 +21,15 @@ describe('ProjectsService', () => {
branches?: Array<{ name: string; commitSha: string }>;
tags?: Array<{ name: string; commitSha: string }>;
commits?: Record<string, { sha: string; message: string }>;
pullRequests?: Record<
string,
{
number: number;
title: string;
state: string;
merged: boolean;
}
>;
}) {
const repository = {
findMany: jest.fn(() => Promise.resolve([project])),
@@ -46,9 +55,16 @@ describe('ProjectsService', () => {
},
),
),
getCommitPullRequest: jest.fn((owner: string, repo: string, sha: string) =>
Promise.resolve(options?.pullRequests?.[sha]),
),
} satisfies Pick<
GiteaClient,
'healthSummary' | 'listBranches' | 'listTags' | 'getCommit'
| 'healthSummary'
| 'listBranches'
| 'listTags'
| 'getCommit'
| 'getCommitPullRequest'
>;
const auth = {
listAllowedProjectKeys: jest.fn(() => Promise.resolve([project.key])),
@@ -90,6 +106,14 @@ describe('ProjectsService', () => {
abc123: { sha: 'abc123', message: 'feat: add platform refs' },
def456: { sha: 'def456', message: 'chore: tag release' },
},
pullRequests: {
abc123: {
number: 2,
title: 'feat: add platform refs',
state: 'closed',
merged: true,
},
},
});
const refs = await service.listProjectRefs(project.key);
@@ -101,6 +125,13 @@ describe('ProjectsService', () => {
name: 'feature/devops-platform',
commitSha: 'abc123',
commit: { sha: 'abc123', message: 'feat: add platform refs' },
pullRequest: {
number: 2,
title: 'feat: add platform refs',
state: 'closed',
merged: true,
url: 'https://gitea.ops.mrzhan.top/my-project/access-manage/pulls/2',
},
},
],
tags: [
+43 -9
View File
@@ -3,10 +3,19 @@ import { AuthService } from '../auth/auth.service';
import { AuthenticatedUser } from '../auth/auth.types';
import { AppError } from '../common/errors/app-error';
import { GiteaClient } from '../integrations/gitea/gitea.client';
import { GiteaCommitSummary } from '../integrations/gitea/gitea.types';
import {
GiteaCommitSummary,
GiteaPullRequestSummary,
} from '../integrations/gitea/gitea.types';
import { ProjectRepository } from './project.repository';
import { ProjectRefSummary, ProjectSummary } from './project.types';
type ParsedRepository = {
owner: string;
name: string;
webBaseUrl: string;
};
@Injectable()
export class ProjectsService {
constructor(
@@ -83,9 +92,16 @@ export class ProjectsService {
* Commit 摘要只作为发布前辅助信息;单个摘要读取失败不能影响 refs 主流程。
*/
private async enrichRefsWithCommit<T extends { name: string; commitSha?: string }>(
repo: { owner: string; name: string },
repo: ParsedRepository,
refs: T[],
): Promise<Array<T & { commit?: GiteaCommitSummary }>> {
): Promise<
Array<
T & {
commit?: GiteaCommitSummary;
pullRequest?: GiteaPullRequestSummary & { url?: string };
}
>
> {
return Promise.all(
refs.map(async (ref) => {
if (!ref.commitSha) {
@@ -93,13 +109,24 @@ export class ProjectsService {
}
try {
return {
...ref,
commit: await this.giteaClient.getCommit(
const [commit, pullRequest] = await Promise.all([
this.giteaClient.getCommit(repo.owner, repo.name, ref.commitSha),
this.giteaClient.getCommitPullRequest(
repo.owner,
repo.name,
ref.commitSha,
),
]);
return {
...ref,
commit,
pullRequest: pullRequest
? {
...pullRequest,
url: `${repo.webBaseUrl}/${repo.owner}/${repo.name}/pulls/${pullRequest.number}`,
}
: undefined,
};
} catch {
return ref;
@@ -110,14 +137,21 @@ export class ProjectsService {
private parseRepository(
project: ProjectSummary,
): { owner: string; name: string } | null {
): ParsedRepository | null {
try {
const pathname = new URL(project.repositoryUrl).pathname.replace(/\.git$/, '');
const url = new URL(project.repositoryUrl);
const pathname = url.pathname.replace(/\.git$/, '');
const parts = pathname.split('/').filter(Boolean);
const name = parts.at(-1);
const owner = parts.at(-2);
return owner && name ? { owner, name } : null;
return owner && name
? {
owner,
name,
webBaseUrl: url.origin,
}
: null;
} catch {
return null;
}