Add mirror health telemetry to settings API
swadhinbiswas committed 8 months ago
2cc7f45
1 parent 608805b
commit 2cc7f45
4 files changed
+35 -3
Showing 4 changed files+35-3
▼doc/feature_audit.md
+1-1
| @@ -28,7 +28,7 @@ | ||
28 | 28 | | :--- | :---: | :--- | |
29 | 29 | | Full Git protocol (SSH/HTTPS) | ✅ | `ssh.ts`, `git-server.ts` fully implemented | |
30 | 30 | | Repository hosting | ✅ | Multiple storage adapters (S3, R2, local, GDrive) | |
31 | -| Repository mirroring | ⚠️ | Background sync cron plus repository mirror config/manual-sync APIs are implemented (`settings/mirror`, `settings/mirror/sync`); scheduling/monitoring depth remains partial | | |
31 | +| Repository mirroring | ⚠️ | Background sync cron plus repository mirror config/manual-sync APIs are implemented (`settings/mirror`, `settings/mirror/sync`) with derived health telemetry (`isHealthy`, `isStale`, `lastSyncAgeMinutes`); scheduling/monitoring automation depth remains partial | | |
32 | 32 | | Forks & pull-based workflows | ✅ | `fork.ts` API, UI implemented | |
33 | 33 | | Monorepo support | ⚠️ | Basic support, no advanced path scoping | |
34 | 34 | | Git LFS | ✅ | `lfs.ts` with batch API (upload/download) | |
35 | 35 | |
▼src/lib/openapi.ts
+1-0
| @@ -868,6 +868,7 @@ export const openApiSpec = { | ||
868 | 868 | get: { |
869 | 869 | tags: ["Repositories"], |
870 | 870 | summary: "Get repository mirror settings", |
871 | + description: "Returns mirror configuration and derived health fields (`isHealthy`, `isStale`, `lastSyncAgeMinutes`).", | |
871 | 872 | security: [{ bearerAuth: [] }], |
872 | 873 | parameters: [ |
873 | 874 | { name: "owner", in: "path", required: true, schema: { type: "string" } }, |
874 | 875 | |
▼src/pages/api/repos/[owner]/[repo]/settings/mirror.ts
+31-1
| @@ -27,6 +27,34 @@ async function resolveRepository(owner: string, repoName: string) { | ||
27 | 27 | }); |
28 | 28 | } |
29 | 29 | |
30 | +const MIRROR_STALE_MINUTES = 24 * 60; | |
31 | + | |
32 | +function deriveMirrorHealth(repository: { | |
33 | + isMirror: boolean | null; | |
34 | + mirrorSyncStatus: string | null; | |
35 | + lastMirrorSyncAt: Date | null; | |
36 | +}) { | |
37 | + if (!repository.isMirror) { | |
38 | + return { | |
39 | + lastSyncAgeMinutes: null as number | null, | |
40 | + isStale: false, | |
41 | + isHealthy: false, | |
42 | + }; | |
43 | + } | |
44 | + | |
45 | + const nowMs = Date.now(); | |
46 | + const lastSyncMs = repository.lastMirrorSyncAt ? repository.lastMirrorSyncAt.getTime() : null; | |
47 | + const lastSyncAgeMinutes = lastSyncMs ? Math.max(0, Math.floor((nowMs - lastSyncMs) / 60000)) : null; | |
48 | + const isStale = lastSyncAgeMinutes === null ? true : lastSyncAgeMinutes > MIRROR_STALE_MINUTES; | |
49 | + const isHealthy = repository.mirrorSyncStatus === "success" && !isStale; | |
50 | + | |
51 | + return { | |
52 | + lastSyncAgeMinutes, | |
53 | + isStale, | |
54 | + isHealthy, | |
55 | + }; | |
56 | +} | |
57 | + | |
30 | 58 | export const GET: APIRoute = withErrorHandler(async ({ params, request }) => { |
31 | 59 | const owner = params.owner; |
32 | 60 | const repoName = params.repo; |
| @@ -42,11 +70,14 @@ export const GET: APIRoute = withErrorHandler(async ({ params, request }) => { | ||
42 | 70 | return notFound("Repository not found"); |
43 | 71 | } |
44 | 72 | |
73 | + const health = deriveMirrorHealth(repository); | |
74 | + | |
45 | 75 | return success({ |
46 | 76 | isMirror: repository.isMirror, |
47 | 77 | mirrorUrl: repository.mirrorUrl, |
48 | 78 | mirrorSyncStatus: repository.mirrorSyncStatus, |
49 | 79 | lastMirrorSyncAt: repository.lastMirrorSyncAt, |
80 | + ...health, | |
50 | 81 | }); |
51 | 82 | }); |
52 | 83 | |
| @@ -101,4 +132,3 @@ export const DELETE: APIRoute = withErrorHandler(async ({ params, request }) => | ||
101 | 132 | |
102 | 133 | return success({ configured: false }); |
103 | 134 | }); |
104 | - | |
105 | 135 | |
▼tests/integration/repo-mirror-settings-route.test.ts
+2-1
| @@ -95,6 +95,8 @@ describe("repository mirror settings routes", () => { | ||
95 | 95 | const body = await readJson(response); |
96 | 96 | expect(response.status).toBe(200); |
97 | 97 | expect(body?.data?.isMirror).toBe(true); |
98 | + expect(typeof body?.data?.isHealthy).toBe("boolean"); | |
99 | + expect(typeof body?.data?.isStale).toBe("boolean"); | |
98 | 100 | }); |
99 | 101 | |
100 | 102 | it("configures mirror for repo admins", async () => { |
| @@ -139,4 +141,3 @@ describe("repository mirror settings routes", () => { | ||
139 | 141 | expect(disableMirrorMock).toHaveBeenCalledWith("repo-1"); |
140 | 142 | }); |
141 | 143 | }); |
142 | - | |
143 | 144 | |