From 938cf97e5ed46dd2ae61e8f47937292cbfbc11b2 Mon Sep 17 00:00:00 2001 From: Tathagat Sharma Date: Mon, 9 Feb 2026 17:29:52 +0530 Subject: [PATCH] feat: add temporary API to replace enabled/disabled lists --- package-lock.json | 31 ++++++++- package.json | 2 +- src/controllers/configuration.js | 87 +++++++++++++++++++++++++ src/routes/index.js | 3 + test/controllers/configurations.test.js | 1 + test/routes/index.test.js | 4 ++ 6 files changed, 124 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index f2e5cb8ad..a9d28ca8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@adobe/spacecat-shared-ahrefs-client": "1.10.5", "@adobe/spacecat-shared-athena-client": "1.9.2", "@adobe/spacecat-shared-brand-client": "1.1.35", - "@adobe/spacecat-shared-data-access": "2.104.0", + "@adobe/spacecat-shared-data-access": "https://gist.github.com/tathagat2241/baa0d24fc1d1b06a50238574767b273d/raw/870d2b8936308897410341aab6f13012372ebc27/adobe-spacecat-shared-data-access-2.104.0.tgz", "@adobe/spacecat-shared-gpt-client": "1.6.16", "@adobe/spacecat-shared-http-utils": "1.20.0", "@adobe/spacecat-shared-ims-client": "1.11.9", @@ -808,6 +808,7 @@ "resolved": "https://registry.npmjs.org/@adobe/helix-universal/-/helix-universal-5.4.0.tgz", "integrity": "sha512-3ZfFdjYtpv7RCgul9yyOBsRVsxLNapwt0YjASBhyzJGNjnPxrWDlqDtbpBdwAgA1Nuh9nmjzFDFu8CJWv6BMKw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@adobe/fetch": "4.2.3", "aws4": "1.13.2" @@ -1178,8 +1179,8 @@ }, "node_modules/@adobe/spacecat-shared-data-access": { "version": "2.104.0", - "resolved": "https://registry.npmjs.org/@adobe/spacecat-shared-data-access/-/spacecat-shared-data-access-2.104.0.tgz", - "integrity": "sha512-m/bICOybOQk0bpKuyjcueNypQBZ52E0Zw1Zfl32nXga9FGzrNJZX8v3VBrmbWVavHd6pZ0D12pUoiIpRIyO6xw==", + "resolved": "https://gist.github.com/tathagat2241/baa0d24fc1d1b06a50238574767b273d/raw/870d2b8936308897410341aab6f13012372ebc27/adobe-spacecat-shared-data-access-2.104.0.tgz", + "integrity": "sha512-3/xrV3FDnX9ANywrCeuaf9SlOOmgN/dA/wpqC4VmHxqh/xUpijUcJuLlgzTHKRP5g66PO6QW84T5trIc1ql6uw==", "license": "Apache-2.0", "dependencies": { "@adobe/spacecat-shared-utils": "1.81.1", @@ -2943,6 +2944,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.940.0.tgz", "integrity": "sha512-u2sXsNJazJbuHeWICvsj6RvNyJh3isedEfPvB21jK/kxcriK+dE/izlKC2cyxUjERCmku0zTFNzY9FhrLbYHjQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -7975,6 +7977,7 @@ "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.80.tgz", "integrity": "sha512-vcJDV2vk1AlCwSh3aBm/urQ1ZrlXFFBocv11bz/NBUfLWD5/UDNMzwPdaAd2dKvNmTWa9FM2lirLU3+JCf4cRA==", "license": "MIT", + "peer": true, "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", @@ -8201,6 +8204,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -8407,6 +8411,7 @@ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -8570,6 +8575,7 @@ "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", @@ -10489,6 +10495,7 @@ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -10795,6 +10802,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -10841,6 +10849,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11316,6 +11325,7 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.12.0.tgz", "integrity": "sha512-lwalRdxXRy+Sn49/vN7W507qqmBRk5Fy2o0a9U6XTjL9IV+oR5PUiiptoBrOcaYCiVuGld8OEbNqhm6wvV3m6A==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -11966,6 +11976,7 @@ "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -14142,6 +14153,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -18124,6 +18136,7 @@ "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -19179,6 +19192,7 @@ "integrity": "sha512-UczzB+0nnwGotYSgllfARAqWCJ5e/skuV2K/l+Zyck/H6pJIhLXuBnz+6vn2i211o7DtbE78HQtsYEKICHGI+g==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" @@ -22325,6 +22339,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -22994,6 +23009,7 @@ "resolved": "https://registry.npmjs.org/openai/-/openai-5.12.2.tgz", "integrity": "sha512-xqzHHQch5Tws5PcKR2xsZGX9xtch+JQFz5zb14dGqlshmmDAFBFEWmeIpf7wVqWV+w7Emj7jRgkNJakyKE0tYQ==", "license": "Apache-2.0", + "peer": true, "bin": { "openai": "bin/cli" }, @@ -24111,6 +24127,7 @@ "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -24121,6 +24138,7 @@ "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -24830,6 +24848,7 @@ "integrity": "sha512-phCkJ6pjDi9ANdhuF5ElS10GGdAKY6R1Pvt9lT3SFhOwM4T7QZE7MLpBDbNruUx/Q3gFD92/UOFringGipRqZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -25716,6 +25735,7 @@ "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "^13.0.5", @@ -26281,6 +26301,7 @@ "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", @@ -27346,6 +27367,7 @@ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "license": "MIT", + "peer": true, "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", @@ -28079,6 +28101,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -28340,6 +28363,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -28349,6 +28373,7 @@ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", "license": "ISC", + "peer": true, "peerDependencies": { "zod": "^3.25 || ^4" } diff --git a/package.json b/package.json index 83cdd06e9..e735205c6 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "@adobe/spacecat-shared-ahrefs-client": "1.10.5", "@adobe/spacecat-shared-athena-client": "1.9.2", "@adobe/spacecat-shared-brand-client": "1.1.35", - "@adobe/spacecat-shared-data-access": "2.104.0", + "@adobe/spacecat-shared-data-access": "https://gist.github.com/tathagat2241/baa0d24fc1d1b06a50238574767b273d/raw/870d2b8936308897410341aab6f13012372ebc27/adobe-spacecat-shared-data-access-2.104.0.tgz", "@adobe/spacecat-shared-gpt-client": "1.6.16", "@adobe/spacecat-shared-http-utils": "1.20.0", "@adobe/spacecat-shared-ims-client": "1.11.9", diff --git a/src/controllers/configuration.js b/src/controllers/configuration.js index 2ad7a518b..f6e5288e7 100644 --- a/src/controllers/configuration.js +++ b/src/controllers/configuration.js @@ -251,6 +251,92 @@ function ConfigurationController(ctx) { } }; + /** + * Replaces enabled/disabled lists for a handler. + * This is a temporary API that replaces (not merges) the provided arrays. + * + * TEMPORARY API: This endpoint was created to clean up enabled and disabled lists + * by removing unnecessary site IDs. Unlike the existing updateHandler endpoint + * which merges arrays, this endpoint completely replaces the specified arrays. + * This API will be removed once the cleanup task is completed. + * + * @param {UniversalContext} context - Context of the request. + * @return {Promise} Updated configuration response. + */ + /* c8 ignore start - temporary API, no unit tests needed */ + const replaceHandlerEnabledDisabled = async (context) => { + if (!accessControlUtil.hasAdminAccess()) { + return forbidden('Only admins can update handler configuration'); + } + + const { handlerType } = context.params; + const { data } = context; + + if (!hasText(handlerType)) { + return badRequest('Handler type is required'); + } + + if (!isNonEmptyObject(data)) { + return badRequest('Request body is required and cannot be empty'); + } + + // Validate that at least one of enabled or disabled is provided + const hasEnabled = data.enabled !== undefined; + const hasDisabled = data.disabled !== undefined; + + if (!hasEnabled && !hasDisabled) { + return badRequest('At least one of enabled or disabled must be provided'); + } + + // Validate that at least one array is provided within enabled/disabled + // (empty arrays are allowed) + if (hasEnabled) { + const hasEnabledSites = data.enabled.sites !== undefined; + const hasEnabledOrgs = data.enabled.orgs !== undefined; + if (!hasEnabledSites && !hasEnabledOrgs) { + return badRequest('At least one of enabled.sites or enabled.orgs must be provided'); + } + // Validate that if provided, they must be arrays + if (hasEnabledSites && !Array.isArray(data.enabled.sites)) { + return badRequest('enabled.sites must be an array'); + } + if (hasEnabledOrgs && !Array.isArray(data.enabled.orgs)) { + return badRequest('enabled.orgs must be an array'); + } + } + + if (hasDisabled) { + const hasDisabledSites = data.disabled.sites !== undefined; + const hasDisabledOrgs = data.disabled.orgs !== undefined; + if (!hasDisabledSites && !hasDisabledOrgs) { + return badRequest('At least one of disabled.sites or disabled.orgs must be provided'); + } + // Validate that if provided, they must be arrays + if (hasDisabledSites && !Array.isArray(data.disabled.sites)) { + return badRequest('disabled.sites must be an array'); + } + if (hasDisabledOrgs && !Array.isArray(data.disabled.orgs)) { + return badRequest('disabled.orgs must be an array'); + } + } + + try { + const configuration = await Configuration.findLatest(); + if (!configuration) { + return notFound('Configuration not found'); + } + + configuration.replaceHandlerEnabledDisabled(handlerType, data); + setUpdatedBy(configuration, context); + await configuration.save(); + + return ok(ConfigurationDto.toJSON(configuration)); + } catch (error) { + return badRequest(error.message); + } + }; + /* c8 ignore stop */ + /** * Updates the entire configuration or specific sections. * Allows updating handlers, jobs, and/or queues in a single request. @@ -362,6 +448,7 @@ function ConfigurationController(ctx) { updateQueues, updateJob, updateHandler, + replaceHandlerEnabledDisabled, updateConfiguration, restoreVersion, }; diff --git a/src/routes/index.js b/src/routes/index.js index 6c8232aaf..56ba55bc1 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -141,6 +141,9 @@ export default function getRouteHandlers( 'PUT /configurations/latest/queues': configurationController.updateQueues, 'PATCH /configurations/latest/jobs/:jobType': configurationController.updateJob, 'PATCH /configurations/latest/handlers/:handlerType': configurationController.updateHandler, + /* TEMPORARY: This route is for cleanup task and will be removed once cleanup is done */ + /* c8 ignore next - temporary route, no unit tests needed */ + 'PUT /configurations/latest/handlers/:handlerType/replace-enabled-disabled': configurationController.replaceHandlerEnabledDisabled, 'PATCH /configurations/sites/audits': sitesAuditsToggleController.execute, 'POST /event/fulfillment': fulfillmentController.processFulfillmentEvents, 'POST /event/fulfillment/:eventType': fulfillmentController.processFulfillmentEvents, diff --git a/test/controllers/configurations.test.js b/test/controllers/configurations.test.js index 54dfa8fee..458514c84 100644 --- a/test/controllers/configurations.test.js +++ b/test/controllers/configurations.test.js @@ -117,6 +117,7 @@ describe('Configurations Controller', () => { 'updateQueues', 'updateJob', 'updateHandler', + 'replaceHandlerEnabledDisabled', 'updateConfiguration', 'restoreVersion', ]; diff --git a/test/routes/index.test.js b/test/routes/index.test.js index 1d4daeaed..3ac1db37d 100755 --- a/test/routes/index.test.js +++ b/test/routes/index.test.js @@ -34,6 +34,9 @@ describe('getRouteHandlers', () => { updateQueues: sinon.stub(), updateJob: sinon.stub(), updateHandler: sinon.stub(), + // TEMPORARY: Mock for temporary cleanup API - will be removed once cleanup is done + // This is added here to prevent route tests from failing, not for unit testing the endpoint + replaceHandlerEnabledDisabled: sinon.stub(), restoreVersion: sinon.stub(), }; @@ -433,6 +436,7 @@ describe('getRouteHandlers', () => { 'GET /configurations/:version', 'DELETE /configurations/audits/:auditType', 'PATCH /configurations/latest/handlers/:handlerType', + 'PUT /configurations/latest/handlers/:handlerType/replace-enabled-disabled', 'PATCH /configurations/latest/jobs/:jobType', 'POST /hooks/site-detection/cdn/:hookSecret', 'POST /hooks/site-detection/rum/:hookSecret',