feat(llmo-3354): add PATCH endpoint to clear edge status on 404 pages#2174
feat(llmo-3354): add PATCH endpoint to clear edge status on 404 pages#2174
Conversation
Adds POST /sites/:siteId/page-citability/status endpoint that, when called with a URL returning 404, clears the edgeDeployed flag and resets citabilityScore to 0 for that page's citability record. This allows the edge worker to notify SpaceCat when a page that was previously deployed to the edge returns a 404 (e.g. page was deleted), so the citability data stays accurate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
This PR will trigger a minor release when merged. |
There was a problem hiding this comment.
Pull request overview
Adds a new site-scoped API endpoint intended for an edge worker to notify SpaceCat when a previously deployed page starts returning 404, so the corresponding PageCitability state can be cleared.
Changes:
- Added
PATCH /sites/:siteId/page-citability/statusroute + required capability (site:write). - Implemented
SitesController.patchPageCitabilityStatusto clearisDeployedAtEdgeand resetcitabilityScorewhenhttpStatus === 404. - Updated SitesController unit test “exported functions” whitelist to include the new handler.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/controllers/sites.js |
Implements the new PATCH handler that updates PageCitability on 404. |
src/routes/index.js |
Registers the new PATCH route to the SitesController handler. |
src/routes/required-capabilities.js |
Protects the new route with site:write. |
test/controllers/sites.test.js |
Adds the new controller function to the expected exported functions list. |
| 'GET /sites/:siteId/brand-profile': sitesController.getBrandProfile, | ||
| 'POST /sites/:siteId/brand-profile': sitesController.triggerBrandProfile, | ||
| 'GET /sites/:siteId/page-citability/counts': sitesController.getPageCitabilityCounts, | ||
| 'PATCH /sites/:siteId/page-citability/status': sitesController.patchPageCitabilityStatus, | ||
| 'GET /sites/:siteId/top-pages': sitesController.getTopPages, |
There was a problem hiding this comment.
A new public HTTP endpoint was added to the router, but the OpenAPI specs under docs/openapi/ don't appear to be updated in this PR. Please add this route (and its request/response schema) to the OpenAPI entrypoint (e.g., docs/openapi/api.yaml plus a referenced path section), so generated docs stay accurate.
| const patchPageCitabilityStatus = async (context) => { | ||
| const { siteId } = context.params; | ||
| const { url, httpStatus } = context.data || {}; | ||
|
|
There was a problem hiding this comment.
patchPageCitabilityStatus updates site-scoped data but does not validate siteId, load the Site, or enforce accessControlUtil.hasAccess(site) (unlike other /sites/:siteId/* handlers in this controller). Without this, callers with the route capability may be able to clear records for sites they don’t belong to. Add the standard siteId validation + Site.findById + org access check before any update logic.
| if (!isValidUUID(siteId)) { | |
| return badRequest('Site ID required'); | |
| } | |
| const site = await Site.findById(siteId); | |
| if (!site) { | |
| return notFound('Site not found'); | |
| } | |
| if (!await accessControlUtil.hasAccess(site)) { | |
| return forbidden('Only users belonging to the organization can update page citability status'); | |
| } |
| const { PageCitability } = dataAccess; | ||
| const record = await PageCitability.findByUrl(url); | ||
| if (!record) { | ||
| return ok({ url, httpStatus, updated: false }); | ||
| } |
There was a problem hiding this comment.
The route is parameterized by :siteId, but the update is driven by PageCitability.findByUrl(url) with no verification the returned record belongs to that site. This can cause cross-site updates if the same URL can exist under different sites (or if a caller supplies a URL for a different site). Prefer a site-scoped lookup (e.g., findBySiteIdAndUrl(siteId, url)) or validate record.getSiteId() === siteId before mutating/saving.
| 'updateSite', | ||
| 'updateCdnLogsConfig', | ||
| 'getPageCitabilityCounts', | ||
| 'patchPageCitabilityStatus', | ||
| 'getTopPages', |
There was a problem hiding this comment.
A new mutating endpoint is introduced, but there are no unit tests covering patchPageCitabilityStatus behavior (404 clears fields, non-404 no-op, unknown URL returns updated:false, missing url -> 400, and access denied). Please add controller tests similar to existing SitesController tests; this change currently only updates the exported-function whitelist.
Summary
PATCH /sites/:siteId/page-citability/statusendpointisDeployedAtEdge = falseand resetscitabilityScore = 0for the correspondingPageCitabilityrecord{ updated: false }with no errorhttpStatusis not 404, returns{ updated: false }(no-op for non-404 statuses)site:writecapabilityUse Case
The edge worker observes when a previously-deployed page starts returning 404 (page deleted/moved). This endpoint lets the worker notify SpaceCat so the citability data remains accurate and the page is not shown as edge-deployed.
Test plan
PATCH /sites/:siteId/page-citability/statuswith{ url: "...", httpStatus: 404 }setsisDeployedAtEdge=falseandcitabilityScore=0on the record{ updated: false }with 200httpStatus: 200returns{ updated: false }without touching the recordFixes LLMO-3354
🤖 Generated with Claude Code