604 lines
14 KiB
TypeScript
604 lines
14 KiB
TypeScript
import { customFetch } from "@/api/mutator";
|
|
|
|
// Forgejo Connection types
|
|
export interface ForgejoConnection {
|
|
id: string;
|
|
organization_id: string;
|
|
name: string;
|
|
base_url: string;
|
|
token: null; // Always null in responses
|
|
active: boolean;
|
|
has_token: boolean;
|
|
token_last_eight: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface ForgejoConnectionCreate {
|
|
name: string;
|
|
base_url: string;
|
|
token: string;
|
|
}
|
|
|
|
export interface ForgejoConnectionUpdate {
|
|
name?: string;
|
|
base_url?: string;
|
|
token?: string;
|
|
}
|
|
|
|
// Forgejo Repository types
|
|
export interface ForgejoRepoLabel {
|
|
id: number | null;
|
|
name: string;
|
|
color: string;
|
|
description: string;
|
|
}
|
|
|
|
export interface ForgejoRepository {
|
|
id: string;
|
|
organization_id: string;
|
|
connection_id: string;
|
|
owner: string;
|
|
repo: string;
|
|
display_name: string;
|
|
default_branch: string;
|
|
active: boolean;
|
|
has_webhook_secret: boolean;
|
|
description: string | null;
|
|
open_issues_count: number;
|
|
is_archived: boolean;
|
|
topics: string[];
|
|
labels: ForgejoRepoLabel[];
|
|
connection: ForgejoConnection;
|
|
last_sync_at: string | null;
|
|
last_sync_error: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface ForgejoRepositoryCreate {
|
|
connection_id: string;
|
|
owner: string;
|
|
repo: string;
|
|
display_name?: string;
|
|
default_branch?: string;
|
|
}
|
|
|
|
export interface ForgejoRepositoryUpdate {
|
|
display_name?: string;
|
|
default_branch?: string;
|
|
webhook_secret?: string | null;
|
|
}
|
|
|
|
type ApiResponse<T> = {
|
|
data: T;
|
|
status: number;
|
|
headers: Headers;
|
|
};
|
|
|
|
async function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {
|
|
const response = await customFetch<ApiResponse<T>>(path, init ?? {});
|
|
return response.data;
|
|
}
|
|
|
|
// Forgejo Connection API
|
|
export async function getForgejoConnections(): Promise<ForgejoConnection[]> {
|
|
return fetchJson<ForgejoConnection[]>("/api/v1/forgejo/connections");
|
|
}
|
|
|
|
export async function createForgejoConnection(
|
|
data: ForgejoConnectionCreate,
|
|
): Promise<ForgejoConnection> {
|
|
return fetchJson<ForgejoConnection>("/api/v1/forgejo/connections", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
export async function getForgejoConnection(
|
|
connectionId: string,
|
|
): Promise<ForgejoConnection> {
|
|
return fetchJson<ForgejoConnection>(
|
|
`/api/v1/forgejo/connections/${connectionId}`,
|
|
);
|
|
}
|
|
|
|
export async function updateForgejoConnection(
|
|
connectionId: string,
|
|
data: ForgejoConnectionUpdate,
|
|
): Promise<ForgejoConnection> {
|
|
return fetchJson<ForgejoConnection>(
|
|
`/api/v1/forgejo/connections/${connectionId}`,
|
|
{
|
|
method: "PATCH",
|
|
body: JSON.stringify(data),
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function deleteForgejoConnection(
|
|
connectionId: string,
|
|
): Promise<void> {
|
|
await customFetch<ApiResponse<unknown>>(
|
|
`/api/v1/forgejo/connections/${connectionId}`,
|
|
{
|
|
method: "DELETE",
|
|
},
|
|
);
|
|
}
|
|
|
|
// Forgejo Repository API
|
|
export async function getForgejoRepositories(): Promise<ForgejoRepository[]> {
|
|
return fetchJson<ForgejoRepository[]>("/api/v1/forgejo/repositories");
|
|
}
|
|
|
|
export async function createForgejoRepository(
|
|
data: ForgejoRepositoryCreate,
|
|
): Promise<ForgejoRepository> {
|
|
return fetchJson<ForgejoRepository>("/api/v1/forgejo/repositories", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
export async function getForgejoRepository(
|
|
repositoryId: string,
|
|
): Promise<ForgejoRepository> {
|
|
return fetchJson<ForgejoRepository>(
|
|
`/api/v1/forgejo/repositories/${repositoryId}`,
|
|
);
|
|
}
|
|
|
|
export async function updateForgejoRepository(
|
|
repositoryId: string,
|
|
data: ForgejoRepositoryUpdate,
|
|
): Promise<ForgejoRepository> {
|
|
return fetchJson<ForgejoRepository>(
|
|
`/api/v1/forgejo/repositories/${repositoryId}`,
|
|
{
|
|
method: "PATCH",
|
|
body: JSON.stringify(data),
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function deleteForgejoRepository(
|
|
repositoryId: string,
|
|
): Promise<void> {
|
|
await customFetch<ApiResponse<unknown>>(
|
|
`/api/v1/forgejo/repositories/${repositoryId}`,
|
|
{
|
|
method: "DELETE",
|
|
},
|
|
);
|
|
}
|
|
|
|
// Pre-save connection test
|
|
export interface ForgejoConnectionTestRepo {
|
|
full_name: string;
|
|
name: string;
|
|
owner: string;
|
|
default_branch: string;
|
|
private: boolean;
|
|
description: string | null;
|
|
}
|
|
|
|
export interface ForgejoConnectionTestResult {
|
|
valid: boolean;
|
|
user_login: string | null;
|
|
user_full_name: string | null;
|
|
repo_count: number;
|
|
repos: ForgejoConnectionTestRepo[];
|
|
error: string | null;
|
|
response_time_ms: number;
|
|
}
|
|
|
|
export async function testForgejoConnection(data: {
|
|
base_url: string;
|
|
token: string;
|
|
}): Promise<ForgejoConnectionTestResult> {
|
|
return fetchJson<ForgejoConnectionTestResult>(
|
|
"/api/v1/forgejo/connections/test",
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
},
|
|
);
|
|
}
|
|
|
|
// Remote repo discovery
|
|
export interface ForgejoRemoteRepo {
|
|
full_name: string;
|
|
name: string;
|
|
owner: string;
|
|
default_branch: string;
|
|
description: string | null;
|
|
private: boolean;
|
|
html_url: string;
|
|
}
|
|
|
|
export async function getConnectionRepos(
|
|
connectionId: string,
|
|
): Promise<ForgejoRemoteRepo[]> {
|
|
return fetchJson<ForgejoRemoteRepo[]>(
|
|
`/api/v1/forgejo/connections/${connectionId}/repos`,
|
|
);
|
|
}
|
|
|
|
// Forgejo Sync & Validation API
|
|
export async function syncRepository(repositoryId: string): Promise<{
|
|
created: number;
|
|
updated: number;
|
|
open: number;
|
|
closed: number;
|
|
total: number;
|
|
}> {
|
|
return fetchJson<{
|
|
created: number;
|
|
updated: number;
|
|
open: number;
|
|
closed: number;
|
|
total: number;
|
|
}>(`/api/v1/forgejo/repositories/${repositoryId}/sync`, {
|
|
method: "POST",
|
|
});
|
|
}
|
|
|
|
export interface ForgejoValidationStatus {
|
|
ok: boolean;
|
|
status: string;
|
|
error_message?: string | null;
|
|
}
|
|
|
|
export interface ForgejoConnectionValidationResponse {
|
|
connection_id: string;
|
|
status: ForgejoValidationStatus;
|
|
response_time_ms: number;
|
|
validated_at: string;
|
|
}
|
|
|
|
export interface ForgejoRepositoryValidationResponse {
|
|
repository_id: string;
|
|
status: ForgejoValidationStatus;
|
|
response_time_ms: number;
|
|
validated_at: string;
|
|
repo_exists?: boolean | null;
|
|
}
|
|
|
|
export async function validateConnection(
|
|
connectionId: string,
|
|
): Promise<ForgejoConnectionValidationResponse> {
|
|
return fetchJson<ForgejoConnectionValidationResponse>(
|
|
`/api/v1/forgejo/connections/${connectionId}/validate`,
|
|
{
|
|
method: "POST",
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function validateRepository(
|
|
repositoryId: string,
|
|
): Promise<ForgejoRepositoryValidationResponse> {
|
|
return fetchJson<ForgejoRepositoryValidationResponse>(
|
|
`/api/v1/forgejo/repositories/${repositoryId}/validate`,
|
|
{
|
|
method: "POST",
|
|
},
|
|
);
|
|
}
|
|
|
|
// Forgejo Issue types
|
|
export interface ForgejoIssueLabel {
|
|
id?: number | null;
|
|
name: string;
|
|
color: string;
|
|
description?: string | null;
|
|
}
|
|
|
|
export interface ForgejoIssueMilestone {
|
|
id: number | null;
|
|
title: string;
|
|
state: string;
|
|
description: string | null;
|
|
due_on: string | null;
|
|
closed_at: string | null;
|
|
}
|
|
|
|
export interface ForgejoIssue {
|
|
id: string;
|
|
organization_id: string;
|
|
repository_id: string;
|
|
forgejo_issue_number: number;
|
|
title: string;
|
|
body: string | null;
|
|
body_preview: string | null;
|
|
state: string;
|
|
is_pull_request: boolean;
|
|
labels: ForgejoIssueLabel[];
|
|
assignees: Record<string, unknown>[];
|
|
milestone: ForgejoIssueMilestone | null;
|
|
author: string;
|
|
html_url: string;
|
|
forgejo_created_at: string;
|
|
forgejo_updated_at: string;
|
|
forgejo_closed_at: string | null;
|
|
last_synced_at: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface ForgejoIssueDetail extends ForgejoIssue {
|
|
forgejo_payload?: Record<string, unknown> | null;
|
|
forgejo_comments_payload?: Record<string, unknown>[];
|
|
forgejo_timeline_payload?: Record<string, unknown>[];
|
|
forgejo_reactions_payload?: Record<string, unknown>[];
|
|
}
|
|
|
|
export interface ForgejoIssueListResponse {
|
|
items: ForgejoIssue[];
|
|
total: number;
|
|
page: number;
|
|
limit: number;
|
|
}
|
|
|
|
// Forgejo Issue API
|
|
export async function getForgejoIssues(params?: {
|
|
repository_id?: string;
|
|
board_id?: string;
|
|
state?: string;
|
|
search?: string;
|
|
page?: number;
|
|
limit?: number;
|
|
}): Promise<ForgejoIssueListResponse> {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.repository_id)
|
|
searchParams.set("repository_id", params.repository_id);
|
|
if (params?.board_id) searchParams.set("board_id", params.board_id);
|
|
if (params?.state) searchParams.set("state", params.state);
|
|
if (params?.search) searchParams.set("search", params.search);
|
|
if (params?.page) searchParams.set("page", params.page.toString());
|
|
if (params?.limit) searchParams.set("limit", params.limit.toString());
|
|
|
|
const qs = searchParams.toString();
|
|
return fetchJson<ForgejoIssueListResponse>(
|
|
`/api/v1/forgejo/issues${qs ? `?${qs}` : ""}`,
|
|
);
|
|
}
|
|
|
|
export async function getForgejoIssue(
|
|
issueId: string,
|
|
): Promise<ForgejoIssueDetail> {
|
|
return fetchJson<ForgejoIssueDetail>(`/api/v1/forgejo/issues/${issueId}`);
|
|
}
|
|
|
|
export const ISSUE_TEMPLATE = `## Summary
|
|
Briefly describe the issue in 1 to 3 sentences.
|
|
|
|
## Problem
|
|
Explain what is wrong, missing, confusing, or broken.
|
|
|
|
## Affected area
|
|
Example:
|
|
- UI page:
|
|
- Backend service:
|
|
- API:
|
|
- Database:
|
|
- Auth:
|
|
- CI/CD:
|
|
- Docs:
|
|
|
|
## Affected files
|
|
Known or suspected files:
|
|
- \`path/to/file\`
|
|
- \`path/to/other-file\`
|
|
|
|
## Affected routes or endpoints
|
|
Known or suspected routes:
|
|
- \`GET /example\`
|
|
- \`POST /api/example\`
|
|
|
|
## Steps to reproduce
|
|
1.
|
|
2.
|
|
3.
|
|
|
|
## Expected behavior
|
|
Describe what should happen.
|
|
|
|
## Actual behavior
|
|
Describe what actually happens.
|
|
|
|
## Error output, logs, or screenshots
|
|
Paste relevant logs only. Redact secrets.
|
|
|
|
\`\`\`text
|
|
Paste logs here
|
|
\`\`\``;
|
|
|
|
export interface CreateIssueRequest {
|
|
repository_id: string;
|
|
title: string;
|
|
body: string;
|
|
}
|
|
|
|
export interface CreateIssueResponse {
|
|
success: boolean;
|
|
issue_id: string;
|
|
forgejo_issue_number: number;
|
|
html_url: string;
|
|
repository_id: string;
|
|
}
|
|
|
|
export async function createForgejoIssue(
|
|
data: CreateIssueRequest,
|
|
): Promise<CreateIssueResponse> {
|
|
return fetchJson<CreateIssueResponse>("/api/v1/forgejo/issues", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
export async function closeForgejoIssue(
|
|
issueId: string,
|
|
): Promise<ForgejoIssue> {
|
|
return fetchJson<ForgejoIssue>(`/api/v1/forgejo/issues/${issueId}/close`, {
|
|
method: "POST",
|
|
});
|
|
}
|
|
|
|
export interface PostCommentResponse {
|
|
success: boolean;
|
|
issue_id: string;
|
|
comment_id: number | string | null;
|
|
body: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export async function postForgejoIssueComment(
|
|
issueId: string,
|
|
body: string,
|
|
): Promise<PostCommentResponse> {
|
|
return fetchJson<PostCommentResponse>(
|
|
`/api/v1/forgejo/issues/${issueId}/comments`,
|
|
{
|
|
method: "POST",
|
|
body: JSON.stringify({ body }),
|
|
},
|
|
);
|
|
}
|
|
|
|
export interface EditIssueRequest {
|
|
title?: string;
|
|
body?: string;
|
|
state?: string;
|
|
}
|
|
|
|
export interface EditIssueResponse {
|
|
success: boolean;
|
|
issue_id: string;
|
|
forgejo_issue_number: number;
|
|
title: string;
|
|
body: string | null;
|
|
state: string;
|
|
forgejo_updated_at: string;
|
|
}
|
|
|
|
export async function editForgejoIssue(
|
|
issueId: string,
|
|
data: EditIssueRequest,
|
|
): Promise<EditIssueResponse> {
|
|
return fetchJson<EditIssueResponse>(`/api/v1/forgejo/issues/${issueId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
// Board Repository Linking API
|
|
export interface BoardForgejoRepositoryLink {
|
|
id: string;
|
|
board_id: string;
|
|
repository_id: string;
|
|
organization_id: string;
|
|
created_at: string;
|
|
repository?: ForgejoRepository;
|
|
}
|
|
|
|
export type BoardForgejoRepositoriesResponse =
|
|
| BoardForgejoRepositoryLink[]
|
|
| { repositories: BoardForgejoRepositoryLink[] };
|
|
|
|
export async function getBoardForgejoRepositories(
|
|
boardId: string,
|
|
): Promise<BoardForgejoRepositoriesResponse> {
|
|
return fetchJson<BoardForgejoRepositoriesResponse>(
|
|
`/api/v1/boards/${boardId}/forgejo/repositories`,
|
|
);
|
|
}
|
|
|
|
export async function linkBoardForgejoRepository(
|
|
boardId: string,
|
|
repositoryId: string,
|
|
): Promise<BoardForgejoRepositoryLink | { link?: BoardForgejoRepositoryLink }> {
|
|
return fetchJson<
|
|
BoardForgejoRepositoryLink | { link?: BoardForgejoRepositoryLink }
|
|
>(`/api/v1/boards/${boardId}/forgejo/repositories`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ repository_id: repositoryId }),
|
|
});
|
|
}
|
|
|
|
export async function unlinkBoardForgejoRepository(
|
|
boardId: string,
|
|
repositoryId: string,
|
|
): Promise<void> {
|
|
await customFetch<ApiResponse<unknown>>(
|
|
`/api/v1/boards/${boardId}/forgejo/repositories/${repositoryId}`,
|
|
{
|
|
method: "DELETE",
|
|
},
|
|
);
|
|
}
|
|
|
|
// Forgejo Metrics types
|
|
export interface RepositorySyncHealth {
|
|
repository_id: string;
|
|
owner: string;
|
|
repo: string;
|
|
display_name: string | null;
|
|
last_sync_at: string | null;
|
|
last_sync_error: string | null;
|
|
has_error: boolean;
|
|
}
|
|
|
|
export interface ForgejoIssueMetrics {
|
|
open_issues: number;
|
|
closed_issues: number;
|
|
closed_last_7_days: number;
|
|
closed_last_30_days: number;
|
|
stale_open_issues: number;
|
|
repositories_synced: number;
|
|
last_sync_timestamps: Record<string, string>;
|
|
sync_error_counts: Record<string, number>;
|
|
}
|
|
|
|
// Forgejo Heatmap API
|
|
export interface ForgejoHeatmapDay {
|
|
date: string; // "YYYY-MM-DD"
|
|
count: number;
|
|
}
|
|
|
|
export interface ForgejoHeatmapResponse {
|
|
days: ForgejoHeatmapDay[];
|
|
max_count: number;
|
|
}
|
|
|
|
export async function getForgejoHeatmap(params?: {
|
|
organization_id?: string;
|
|
}): Promise<ForgejoHeatmapResponse> {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.organization_id)
|
|
searchParams.set("organization_id", params.organization_id);
|
|
const qs = searchParams.toString();
|
|
return fetchJson<ForgejoHeatmapResponse>(
|
|
`/api/v1/forgejo/heatmap${qs ? `?${qs}` : ""}`,
|
|
);
|
|
}
|
|
|
|
// Forgejo Metrics API
|
|
export async function getForgejoMetrics(params?: {
|
|
organization_id?: string;
|
|
board_id?: string;
|
|
repository_id?: string;
|
|
}): Promise<ForgejoIssueMetrics> {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.organization_id)
|
|
searchParams.set("organization_id", params.organization_id);
|
|
if (params?.board_id) searchParams.set("board_id", params.board_id);
|
|
if (params?.repository_id)
|
|
searchParams.set("repository_id", params.repository_id);
|
|
const qs = searchParams.toString();
|
|
return fetchJson<ForgejoIssueMetrics>(
|
|
`/api/v1/forgejo/metrics${qs ? `?${qs}` : ""}`,
|
|
);
|
|
}
|