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 ForgejoRepositoryBoardSummary { id: string; name: 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[]; linked_boards: ForgejoRepositoryBoardSummary[]; 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 = { data: T; status: number; headers: Headers; }; async function fetchJson(path: string, init?: RequestInit): Promise { const response = await customFetch>(path, init ?? {}); return response.data; } // Forgejo Connection API export async function getForgejoConnections(): Promise { return fetchJson("/api/v1/forgejo/connections"); } export async function createForgejoConnection( data: ForgejoConnectionCreate, ): Promise { return fetchJson("/api/v1/forgejo/connections", { method: "POST", body: JSON.stringify(data), }); } export async function getForgejoConnection( connectionId: string, ): Promise { return fetchJson( `/api/v1/forgejo/connections/${connectionId}`, ); } export async function updateForgejoConnection( connectionId: string, data: ForgejoConnectionUpdate, ): Promise { return fetchJson( `/api/v1/forgejo/connections/${connectionId}`, { method: "PATCH", body: JSON.stringify(data), }, ); } export async function deleteForgejoConnection( connectionId: string, ): Promise { await customFetch>( `/api/v1/forgejo/connections/${connectionId}`, { method: "DELETE", }, ); } // Forgejo Repository API export async function getLinkedBoardsForRepository( repositoryId: string, ): Promise<{ id: string; name: string }[]> { return fetchJson<{ id: string; name: string }[]>( `/api/v1/forgejo/repositories/${repositoryId}/boards`, ); } export async function getForgejoRepositories(): Promise { return fetchJson("/api/v1/forgejo/repositories"); } export async function createForgejoRepository( data: ForgejoRepositoryCreate, ): Promise { return fetchJson("/api/v1/forgejo/repositories", { method: "POST", body: JSON.stringify(data), }); } export async function getForgejoRepository( repositoryId: string, ): Promise { return fetchJson( `/api/v1/forgejo/repositories/${repositoryId}`, ); } export async function updateForgejoRepository( repositoryId: string, data: ForgejoRepositoryUpdate, ): Promise { return fetchJson( `/api/v1/forgejo/repositories/${repositoryId}`, { method: "PATCH", body: JSON.stringify(data), }, ); } export async function deleteForgejoRepository( repositoryId: string, ): Promise { await customFetch>( `/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 { return fetchJson( "/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 { return fetchJson( `/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 { return fetchJson( `/api/v1/forgejo/connections/${connectionId}/validate`, { method: "POST", }, ); } export async function validateRepository( repositoryId: string, ): Promise { return fetchJson( `/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[]; 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 | null; forgejo_comments_payload?: Record[]; forgejo_timeline_payload?: Record[]; forgejo_reactions_payload?: Record[]; } 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 { 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( `/api/v1/forgejo/issues${qs ? `?${qs}` : ""}`, ); } export async function getForgejoIssue( issueId: string, ): Promise { return fetchJson(`/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 interface AssignIssueAgentRequest { board_id?: string; assigned_agent_id?: string; priority?: string; status?: "inbox" | "in_progress"; start_immediately?: boolean; instructions?: string; } export interface AssignIssueAgentResponse { success: boolean; created: boolean; issue_id: string; task_id: string; board_id: string; assigned_agent_id: string | null; status: string; title: string; } export async function assignIssueToAgent( issueId: string, data: AssignIssueAgentRequest, ): Promise { return fetchJson( `/api/v1/forgejo/issues/${issueId}/task`, { method: "POST", body: JSON.stringify(data), }, ); } export async function createForgejoIssue( data: CreateIssueRequest, ): Promise { return fetchJson("/api/v1/forgejo/issues", { method: "POST", body: JSON.stringify(data), }); } export async function closeForgejoIssue( issueId: string, ): Promise { return fetchJson(`/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 { return fetchJson( `/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 { return fetchJson(`/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 { return fetchJson( `/api/v1/boards/${boardId}/forgejo/repositories`, ); } export async function linkBoardForgejoRepository( boardId: string, repositoryId: string, ): Promise { 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 { await customFetch>( `/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; sync_error_counts: Record; } // Forgejo Heatmap API export interface ForgejoHeatmapDay { date: string; // "YYYY-MM-DD" count: number; } export interface ForgejoLastPush { sha: string; message: string; author: string; repo: string; branch: string; pushed_at: string; // ISO-8601 } export async function getForgejoLastPush(params?: { organization_id?: string; }): Promise { const searchParams = new URLSearchParams(); if (params?.organization_id) searchParams.set("organization_id", params.organization_id); const qs = searchParams.toString(); return fetchJson( `/api/v1/forgejo/last-push${qs ? `?${qs}` : ""}`, ); } export interface ForgejoHeatmapResponse { days: ForgejoHeatmapDay[]; max_count: number; total_additions: number; total_deletions: number; has_line_stats: boolean; } export async function getForgejoHeatmap(params?: { organization_id?: string; }): Promise { const searchParams = new URLSearchParams(); if (params?.organization_id) searchParams.set("organization_id", params.organization_id); const qs = searchParams.toString(); return fetchJson( `/api/v1/forgejo/heatmap${qs ? `?${qs}` : ""}`, ); } // Mass Import export interface MassImportRepoResult { repository_id: string; name: string; created: number; updated: number; stale_closed: number; open: number; closed: number; total: number; error: string | null; } export interface MassImportResponse { results: MassImportRepoResult[]; total_created: number; total_updated: number; total_stale_closed: number; succeeded: number; failed: number; run: MassImportRunRead | null; } export interface MassImportRunRead { id: string; organization_id: string; requested_by_user_id: string | null; repository_ids: string[]; results: MassImportRepoResult[]; total_created: number; total_updated: number; total_stale_closed: number; succeeded: number; failed: number; started_at: string; finished_at: string | null; duration_ms: number | null; created_at: string; } export async function massImportRepositories( repositoryIds?: string[], ): Promise { return fetchJson("/api/v1/forgejo/repositories/import", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify( repositoryIds ? { repository_ids: repositoryIds } : {}, ), }); } export async function getMassImportRuns( limit = 5, ): Promise { return fetchJson( `/api/v1/forgejo/repositories/import-runs?limit=${limit}`, ); } // Forgejo Metrics API export async function getForgejoMetrics(params?: { organization_id?: string; board_id?: string; repository_id?: string; }): Promise { 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( `/api/v1/forgejo/metrics${qs ? `?${qs}` : ""}`, ); }