diff --git a/.github/workflows/email-bot.yaml b/.github/workflows/email-bot.yaml index cbda711..1635ed0 100644 --- a/.github/workflows/email-bot.yaml +++ b/.github/workflows/email-bot.yaml @@ -7,7 +7,6 @@ on: branches: - main - jobs: action: if: github.event.pull_request.merged == true diff --git a/src/handlers/get.js b/src/handlers/get.js index 4c7e20b..9b4b5f8 100644 --- a/src/handlers/get.js +++ b/src/handlers/get.js @@ -11,7 +11,7 @@ */ import { getSource } from '../routes/source.js'; import getList from '../routes/list.js'; -import { getProperties } from '../routes/properties.js'; +import { getConfig } from '../routes/config.js'; function get404() { return { body: '', status: 404 }; @@ -30,7 +30,7 @@ export default async function getHandler({ env, daCtx }) { if (path.startsWith('/source')) return getSource({ env, daCtx }); if (path.startsWith('/list')) return getList({ env, daCtx }); - if (path.startsWith('/properties')) return getProperties(); + if (path.startsWith('/config')) return getConfig({ env, daCtx }); return undefined; } diff --git a/src/handlers/post.js b/src/handlers/post.js index 7600a35..4280144 100644 --- a/src/handlers/post.js +++ b/src/handlers/post.js @@ -10,13 +10,13 @@ * governing permissions and limitations under the License. */ import { postSource } from '../routes/source.js'; -import { postProperties } from '../routes/properties.js'; +import { postConfig } from '../routes/config.js'; export default async function postHandler({ req, env, daCtx }) { const { path } = daCtx; if (path.startsWith('/source')) return postSource({ req, env, daCtx }); - if (path.startsWith('/properties')) return postProperties({ req, env, daCtx }); + if (path.startsWith('/config')) return postConfig({ req, env, daCtx }); return undefined; } diff --git a/src/routes/config.js b/src/routes/config.js new file mode 100644 index 0000000..dd8bac8 --- /dev/null +++ b/src/routes/config.js @@ -0,0 +1,22 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import putKv from '../storage/kv/put.js'; +import getKv from '../storage/kv/get.js'; + +export function postConfig({ req, env, daCtx }) { + return putKv(req, env, daCtx); +} + +export function getConfig({ env, daCtx }) { + return getKv(env, daCtx); +} diff --git a/src/storage/kv/get.js b/src/storage/kv/get.js new file mode 100644 index 0000000..7730ac4 --- /dev/null +++ b/src/storage/kv/get.js @@ -0,0 +1,17 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export default async function getKv(env, daCtx) { + const body = await env.DA_CONFIG.get(daCtx.fullKey); + if (body) return { body, status: 200 }; + return { body: JSON.stringify({ error: 'not found' }), status: 404 }; +} diff --git a/src/storage/kv/put.js b/src/storage/kv/put.js new file mode 100644 index 0000000..49cd3c7 --- /dev/null +++ b/src/storage/kv/put.js @@ -0,0 +1,41 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +async function save(env, key, string) { + let body; + let status; + try { + // Parse it to at least validate its json + JSON.parse(string); + // Put it (seems to not return a response) + await env.DA_CONFIG.put(key, string); + // Validate the content is there + body = await env.DA_CONFIG.get(key); + status = 201; + } catch { + body = JSON.stringify({ error: 'Couldn\'t parse or save config.' }); + status = 400; + } + return { body, status }; +} + +export default async function putKv(req, env, daCtx) { + try { + const formData = await req.formData(); + const config = formData.get('config'); + if (config) return save(env, daCtx.fullKey, config); + } catch { + // eslint-disable-next-line no-console + console.log('No form data'); + } + return { body: JSON.stringify({ error: 'No config or form data.' }), status: 400 }; +} diff --git a/src/utils/daCtx.js b/src/utils/daCtx.js index 836bc2f..46b3df6 100644 --- a/src/utils/daCtx.js +++ b/src/utils/daCtx.js @@ -27,11 +27,18 @@ export default async function getDaCtx(req, env) { const sanitized = lower.endsWith('/') ? lower.slice(0, -1) : lower; // Get base details - const [api, org, ...parts] = sanitized.split('/'); + const split = sanitized.split('/'); + const api = split.shift(); + const fullKey = split.join('/'); + const [org, ...parts] = split; // Set base details const daCtx = { - path: pathname, api, org, users, + path: pathname, + api, + org, + users, + fullKey, }; // Get org properties @@ -53,10 +60,10 @@ export default async function getDaCtx(req, env) { [daCtx.site] = path; // Handle folders and files under a site - const split = daCtx.filename.split('.'); - daCtx.isFile = split.length > 1; - if (daCtx.isFile) daCtx.ext = split.pop(); - daCtx.name = split.join('.'); + const fileSplit = daCtx.filename.split('.'); + daCtx.isFile = fileSplit.length > 1; + if (daCtx.isFile) daCtx.ext = fileSplit.pop(); + daCtx.name = fileSplit.join('.'); // Set keys daCtx.key = keyBase; diff --git a/test/storage/kv/kv.test.js b/test/storage/kv/kv.test.js new file mode 100644 index 0000000..4349140 --- /dev/null +++ b/test/storage/kv/kv.test.js @@ -0,0 +1,73 @@ +import assert from 'assert'; + +import getKv from '../../../src/storage/kv/get.js'; +import putKv from '../../../src/storage/kv/put.js'; + +const MOCK_CONFIG = '{"mock":"data"}'; + +describe('KV storage', () => { + describe('Get success', async () => { + const env = { + DA_CONFIG: { + get: () => { return MOCK_CONFIG }, + } + }; + const daCtx = { fullKey: 'adobe/geometrixx' }; + + const resp = await getKv(env, daCtx); + assert.strictEqual(resp.body, MOCK_CONFIG); + assert.strictEqual(resp.status, 200); + }); + + describe('Get not found', async () => { + const env = { DA_CONFIG: { get: () => { return null } } }; + const daCtx = { fullKey: 'adobe/geometrixx' }; + + const resp = await getKv(env, daCtx); + assert.strictEqual(resp.body, '{"error":"not found"}'); + assert.strictEqual(resp.status, 404); + }); + + describe('Put success', async () => { + const formData = new FormData(); + formData.append('config', MOCK_CONFIG); + + const req = { formData: () => { return formData; } }; + const env = { + DA_CONFIG: { + put: () => { return undefined }, + get: () => { return MOCK_CONFIG }, + } + }; + const daCtx = { fullKey: 'adobe/geometrixx' }; + const resp = await putKv(req, env, daCtx); + assert.strictEqual(resp.body, MOCK_CONFIG); + assert.strictEqual(resp.status, 201); + }); + + describe('Put without form data', async () => { + const req = { formData: () => { return null; } }; + const env = {}; + const daCtx = { fullKey: 'adobe/geometrixx' }; + const resp = await putKv(req, env, daCtx); + assert.strictEqual(resp.body, '{"error":"No config or form data."}'); + assert.strictEqual(resp.status, 400); + }); + + describe('Put with malformed config', async () => { + const formData = new FormData(); + formData.append('config', 'abc'); + + const req = { formData: () => { return formData; } }; + const env = { + DA_CONFIG: { + put: () => { return undefined }, + get: () => { return MOCK_CONFIG }, + } + }; + const daCtx = { fullKey: 'adobe/geometrixx' }; + const resp = await putKv(req, env, daCtx); + assert.strictEqual(resp.body, '{"error":"Couldn\'t parse or save config."}'); + assert.strictEqual(resp.status, 400); + }); +}); diff --git a/wrangler.toml b/wrangler.toml index 2efcb9c..3275e28 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -3,18 +3,20 @@ main = "src/index.js" compatibility_date = "2023-10-30" kv_namespaces = [ - { binding = "DA_AUTH", id = "d6217b7c63ef40889583ba5c080c3908" } -] - -[env.dev] -vars = { ENVIRONMENT = "dev" } -kv_namespaces = [ - { binding = "DA_AUTH", id = "21693f3b20f54fcbb850ddc8947335ba" } + { binding = "DA_AUTH", id = "d6217b7c63ef40889583ba5c080c3908" }, + { binding = "DA_CONFIG", id = "feb8618620bb4ca3a866f1c71adbe8ef" } ] [env.stage] vars = { ENVIRONMENT = "stage" } kv_namespaces = [ - { binding = "DA_AUTH", id = "21693f3b20f54fcbb850ddc8947335ba" } + { binding = "DA_AUTH", id = "21693f3b20f54fcbb850ddc8947335ba" }, + { binding = "DA_CONFIG", id = "c44cb8dc69f041dc87c5aaef41b97df9" } ] +[env.dev] +vars = { ENVIRONMENT = "dev" } +kv_namespaces = [ + { binding = "DA_AUTH", id = "21693f3b20f54fcbb850ddc8947335ba" }, + { binding = "DA_CONFIG", id = "c44cb8dc69f041dc87c5aaef41b97df9" } +]