Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions apps/gittensory-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -14687,6 +14687,36 @@
}
]
}
},
"/v1/repos/{owner}/{repo}/focus-manifest/refresh": {
"post": {
"responses": {
"200": {
"description": "Refresh the persisted focus manifest cache from the repo file",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": {
"nullable": true
}
}
}
}
},
"403": {
"description": "Insufficient role"
}
},
"security": [
{
"GittensoryBearer": []
},
{
"GittensorySessionCookie": []
}
]
}
}
},
"servers": [
Expand Down
16 changes: 15 additions & 1 deletion src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1582,7 +1582,21 @@ export function createApp() {
const repoForbidden = await requireSessionRepoAccess(c, identity, fullName, repo);
if (repoForbidden) return repoForbidden;
}
const manifest = await loadRepoFocusManifest(c.env, fullName, { refresh: c.req.query("refresh") === "true" });
const manifest = await loadRepoFocusManifest(c.env, fullName);
return c.json({ repoFullName: fullName, manifest, policy: compileFocusManifestPolicy(manifest) });
});

app.post("/v1/repos/:owner/:repo/focus-manifest/refresh", async (c) => {
const fullName = `${c.req.param("owner")}/${c.req.param("repo")}`;
const forbidden = await requireAppRole(c, ["maintainer", "owner", "operator"]);
if (forbidden) return forbidden;
const identity = await authenticateRequestIdentity(c);
const repo = await getRepository(c.env, fullName);
if (identity?.kind === "session") {
const repoForbidden = await requireSessionRepoAccess(c, identity, fullName, repo);
if (repoForbidden) return repoForbidden;
}
const manifest = await loadRepoFocusManifest(c.env, fullName, { refresh: true });
return c.json({ repoFullName: fullName, manifest, policy: compileFocusManifestPolicy(manifest) });
});

Expand Down
8 changes: 8 additions & 0 deletions src/openapi/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,14 @@ export function buildOpenApiSpec() {
403: { description: "Insufficient role" },
},
});
registry.registerPath({
method: "post",
path: "/v1/repos/{owner}/{repo}/focus-manifest/refresh",
responses: {
200: { description: "Refresh the persisted focus manifest cache from the repo file", content: { "application/json": { schema: z.record(z.string(), z.unknown()) } } },
403: { description: "Insufficient role" },
},
});
registry.registerPath({
method: "put",
path: "/v1/repos/{owner}/{repo}/focus-manifest",
Expand Down
33 changes: 28 additions & 5 deletions test/unit/routes-focus-manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,39 @@ describe("focus-manifest route auth", () => {
});
});

it("bypasses cached manifest when refresh=true", async () => {
it("does not refresh cached manifests from GET query parameters", async () => {
const app = createApp();
const env = createTestEnv({ GITTENSORY_DRIFT_ISSUE_REPO: "JSONbored/gittensory" });
const headers = apiHeaders(env);
const first = await app.request(FOCUS_MANIFEST_PATH, { headers }, env);
expect(first.status).toBe(200);
const refreshed = await app.request(`${FOCUS_MANIFEST_PATH}?refresh=true`, { headers }, env);
const putResponse = await app.request(
FOCUS_MANIFEST_PATH,
{ method: "PUT", headers, body: JSON.stringify({ wantedPaths: ["private-cache/"] }) },
env,
);
expect(putResponse.status).toBe(200);

const response = await app.request(`${FOCUS_MANIFEST_PATH}?refresh=true`, { headers }, env);
expect(response.status).toBe(200);
await expect(response.json()).resolves.toMatchObject({
manifest: { present: true, source: "api_record", wantedPaths: ["private-cache/"] },
});
});

it("refreshes cached manifests from an unsafe POST endpoint", async () => {
const app = createApp();
const env = createTestEnv({ GITTENSORY_DRIFT_ISSUE_REPO: "JSONbored/gittensory" });
const headers = apiHeaders(env);
const putResponse = await app.request(
FOCUS_MANIFEST_PATH,
{ method: "PUT", headers, body: JSON.stringify({ wantedPaths: ["private-cache/"] }) },
env,
);
expect(putResponse.status).toBe(200);

const refreshed = await app.request(`${FOCUS_MANIFEST_PATH}/refresh`, { method: "POST", headers }, env);
expect(refreshed.status).toBe(200);
await expect(refreshed.json()).resolves.toMatchObject({
manifest: { present: true, wantedPaths: expect.arrayContaining(["apps/gittensory-ui/"]) },
manifest: { present: true, source: "repo_file", wantedPaths: expect.arrayContaining(["apps/gittensory-ui/"]) },
});
});

Expand Down
Loading