From 6304ef1e349067dacac2002ee3d46edfd1c96c52 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Fri, 8 Sep 2023 06:42:54 -0700 Subject: [PATCH 01/23] Setting up the router for drips --- backend/src/worker.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/backend/src/worker.js b/backend/src/worker.js index 0ef8f65..42c4414 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -99,30 +99,37 @@ export default { async fetch(request, env) { const url = new URL(request.url); const key = url.pathname.slice(1); + const method = request.method; console.log(`key: ${key}`) const routeRSS = /rss.xml/, routePostList = /posts$/, - routePost = /posts\/*/; + routePost = /posts\/*/, + routeDrip = /drip$/; switch (true) { case routeRSS.test(key): return getRSS(key, env); - break; case routePostList.test(key): return getPostList(key, env); - break; case routePost.test(key): return getPost(key, env); - break; + case routeDrip.test(key): + switch (method) { + case 'GET': + return new Response(JSON.stringify({message: `GET called on /${key}`}, null, 2), {status: 200}); + case 'POST': + return new Response(JSON.stringify({message: `POST called on /${key}`}, null, 2), {status: 200}); + default: + return new Response(JSON.stringify({message: `Error: ${method} not supported on /${key}`}, null, 2), {status: 404}); + } default: if (env.FLAG_USE_HEADERS) { return new Response(JSON.stringify({ message: `/${key} not found`}, null, 2), { status: 404, headers: { ...corsHeaders, - 'Content-type': 'application/json', - 'My-Header-test': 'did it come through?' + 'Content-type': 'application/json' }, }); } else { @@ -130,7 +137,6 @@ export default { status: 404, }); } - break; } }, }; From 055260d60abfa4c15d6f574b8f78ba094afc09c0 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Tue, 12 Sep 2023 06:54:31 -0700 Subject: [PATCH 02/23] Add in base functional test for GET and POST on /drip --- backend/test/script.js | 48 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/backend/test/script.js b/backend/test/script.js index 77c4e64..64904f7 100644 --- a/backend/test/script.js +++ b/backend/test/script.js @@ -14,10 +14,13 @@ export const options = { //let shortenLink -const BASE_URL = "https://blog-dev.flinnlab.workers.dev" +//const BASE_URL = "https://blog-dev.flinnlab.workers.dev" +const BASE_URL = "http://localhost:8787" + export default function () { - group('rss feed', function () { + // GROUP: rss + group('rss', function () { const res = http.get(`${BASE_URL}/rss.xml`) check(res, { @@ -28,13 +31,15 @@ export default function () { sleep(1) // second }) - group('list of posts', function () { + + // GROUP: post + group('post', function () { const res = http.get(`${BASE_URL}/posts`) check(res, { - 'is status 200': (r) => r.status === 200, - 'is formatted correctly': (r) => 'postList' in r.json(), - 'is not empty': (r) => r.json()['postList'].length != 0 + 'postsPage: status is 200': (r) => r.status === 200, + 'postsPage: formatted correctly': (r) => 'postList' in r.json(), + 'postsPage: is not empty': (r) => r.json()['postList'].length != 0 }) sleep(1) // second @@ -44,9 +49,9 @@ export default function () { const res = http.get(`${BASE_URL}/posts/git-monorepo`) check(res, { - 'is status 200': (r) => r.status === 200, - 'is formatted correctly': (r) => 'post' in r.json(), - 'contains all metadata': (r) => { + 'postPage: status is 200': (r) => r.status === 200, + 'postPage: formatted correctly': (r) => 'post' in r.json(), + 'postPage: contains all metadata': (r) => { return ['slug', 'title', 'published', 'description', 'body'] .map(key => key in r.json()['post'] @@ -55,6 +60,31 @@ export default function () { } }) + sleep(1) // second + }) + + + // GROUP: drip + group('drip', function () { + const res = http.get(`${BASE_URL}/drip`) + + check(res, { + 'GET: status is 200': (r) => r.status === 200, + 'GET: verify body': (r) => r.body.includes('GET called on /drip') + }) + + sleep(1) // second + }) + + // GROUP: drip + group('drip', function () { + const res = http.post(`${BASE_URL}/drip`) + + check(res, { + 'POST: status is 200': (r) => r.status === 200, + 'POST: verify body': (r) => r.body.includes('POST called on /drip') + }) + sleep(1) // second }) } From b9c91f80665c16fccb323b34ca3e217172d46980 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Wed, 13 Sep 2023 06:50:35 -0700 Subject: [PATCH 03/23] Set up headers for the drip POST endpoint --- backend/src/worker.js | 72 +++++++++++++++++++++++++++++++++++++++---- backend/wrangler.toml | 4 +-- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/backend/src/worker.js b/backend/src/worker.js index 42c4414..af3646a 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -10,10 +10,37 @@ const corsHeaders = { 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET', + 'Access-Control-Allow-Methods': 'GET, POST', 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept' } + +const isAuthorized = request => { + const psk = request.headers.get("X-Custom-PSK") + return psk == "THIS_IS_A_SECRET" ? true : false +} + + +const isValidData = request => { + const contentType = request.headers.get("content-type") + return contentType == "application/json" ? true : false +} + + +const validatePostRequest = request => { + const authorized = isAuthorized(request) + const validData = isValidData(request) + + if (!authorized) { + return [false, {message: "Not Authorized", status: 403}] + } else if (!validData) { + return [false, {message: "Please see POST /drip docs", status: 400}] + } else { + return [true, {}] + } +} + + const getRSS = async (key, env) => { console.log(`RSS match found`) const rssBlob = await env.BLOG_BUCKET.get(key); @@ -33,7 +60,8 @@ const getRSS = async (key, env) => { } }; -const getPostList = async (key, env) => { + +const getPostList = async (env) => { const postBlob = await env.BLOG_BUCKET.get('posts.json'); if (postBlob === null) { @@ -67,6 +95,7 @@ const getPostList = async (key, env) => { }) } + const getPost = async (key, env) => { const postBlob = await env.BLOG_BUCKET.get('posts.json'); @@ -95,12 +124,38 @@ const getPost = async (key, env) => { }) } + +const getDrip = async (key, env) => {} + + +const postDrip = async (request, env) => { + const [requestIsValid, error] = validatePostRequest(request) + + if (!requestIsValid) { + return new Response(JSON.stringify({message: error.message}, null, 2), {status: error.status}) + } + + const reqBody = await request.json() + + if (!("message" in reqBody)) { + return new Response(JSON.stringify({message: "Please see POST /drip docs"}, null, 2), {status: 400}) + } + + return new Response( + JSON.stringify({ + message: `POST called on /drip`, + data: reqBody + }, null, 2), + {status: 200} + ); +} + + export default { async fetch(request, env) { const url = new URL(request.url); const key = url.pathname.slice(1); const method = request.method; - console.log(`key: ${key}`) const routeRSS = /rss.xml/, routePostList = /posts$/, @@ -111,15 +166,20 @@ export default { case routeRSS.test(key): return getRSS(key, env); case routePostList.test(key): - return getPostList(key, env); + return getPostList(env); case routePost.test(key): return getPost(key, env); case routeDrip.test(key): switch (method) { case 'GET': - return new Response(JSON.stringify({message: `GET called on /${key}`}, null, 2), {status: 200}); + return new Response( + JSON.stringify({ + message: `GET called on /${key}` + }, null, 2), + {status: 200} + ); case 'POST': - return new Response(JSON.stringify({message: `POST called on /${key}`}, null, 2), {status: 200}); + return postDrip(request, env); default: return new Response(JSON.stringify({message: `Error: ${method} not supported on /${key}`}, null, 2), {status: 404}); } diff --git a/backend/wrangler.toml b/backend/wrangler.toml index 4f06772..b3a1000 100644 --- a/backend/wrangler.toml +++ b/backend/wrangler.toml @@ -25,7 +25,7 @@ r2_buckets = [ { binding = "BLOG_BUCKET", bucket_name = "flinnlab-blog-dev" } ] d1_databases = [ - { binding = "DB", database_name = "blog-drip-dev", database_id = "d898dfc0-f641-425c-8c6a-e25da2cf7468", migrations_dir = "../data/drip-db/migrations" } + { binding = "drip-db", database_name = "blog-drip-dev", database_id = "d898dfc0-f641-425c-8c6a-e25da2cf7468", migrations_dir = "../data/drip-db/migrations" } ] [env.staging.vars] @@ -40,7 +40,7 @@ r2_buckets = [ { binding = "BLOG_BUCKET", bucket_name = "flinnlab-blog" } ] d1_databases = [ - { binding = "DB", database_name = "blog-drip", database_id = "b8dc883c-78e0-4687-bfe2-45401ae32813", migrations_dir = "../data/databases/blog-drip/migrations" } + { binding = "drip-db", database_name = "blog-drip", database_id = "b8dc883c-78e0-4687-bfe2-45401ae32813", migrations_dir = "../data/databases/blog-drip/migrations" } ] [env.production.vars] From 595a8720a6283037f765b17b90c2aeb954bb5968 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Wed, 13 Sep 2023 06:53:15 -0700 Subject: [PATCH 04/23] Clean up after feature flag --- backend/src/worker.js | 91 ++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 54 deletions(-) diff --git a/backend/src/worker.js b/backend/src/worker.js index af3646a..1d9f247 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -49,15 +49,11 @@ const getRSS = async (key, env) => { return new Response('Object Not Found', { status: 404 }); } - if (env.FLAG_USE_HEADERS) { - return new Response(rssBlob.body, { status: 200, headers: { - ...corsHeaders, - 'etag': rssBlob.httpEtag, - 'Content-type': 'application/xml' - }}); - } else { - return new Response(rssBlob.body, { status: 200 }); - } + return new Response(rssBlob.body, { status: 200, headers: { + ...corsHeaders, + 'etag': rssBlob.httpEtag, + 'Content-type': 'application/xml' + }}); }; @@ -80,18 +76,14 @@ const getPostList = async (env) => { }) )) .then(postList => { - if (env.FLAG_USE_HEADERS) { - return new Response(JSON.stringify({ postList: postList }, null, 4), { - status: 200, - headers: { - ...corsHeaders, - 'etag': postBlob.httpEtag, - 'Content-type': 'application/json' - } - }); - } else { - return new Response(JSON.stringify({ postList: postList }, null, 4), { status: 200, }); - } + return new Response(JSON.stringify({ postList: postList }, null, 4), { + status: 200, + headers: { + ...corsHeaders, + 'etag': postBlob.httpEtag, + 'Content-type': 'application/json' + } + }); }) } @@ -108,24 +100,26 @@ const getPost = async (key, env) => { return postBlob.json() .then(posts => posts[postSlug]) .then(post => { - if (env.FLAG_USE_HEADERS) { - return new Response(JSON.stringify({ post: post }, null, 4), { - status: 200, - headers: { - ...corsHeaders, - 'etag': postBlob.httpEtag, - 'Content-type': 'application/json' - } - }); - } - return new Response(JSON.stringify({ post: post }, null, 4), { - status: 200, - }); + return new Response(JSON.stringify({ post: post }, null, 4), { + status: 200, + headers: { + ...corsHeaders, + 'etag': postBlob.httpEtag, + 'Content-type': 'application/json' + } + }); }) } -const getDrip = async (key, env) => {} +const getDrip = async (key, env) => { + return new Response( + JSON.stringify({ + message: `GET called on /${key}` + }, null, 2), + {status: 200} + ); +} const postDrip = async (request, env) => { @@ -172,31 +166,20 @@ export default { case routeDrip.test(key): switch (method) { case 'GET': - return new Response( - JSON.stringify({ - message: `GET called on /${key}` - }, null, 2), - {status: 200} - ); + return getDrip(key, env); case 'POST': return postDrip(request, env); default: return new Response(JSON.stringify({message: `Error: ${method} not supported on /${key}`}, null, 2), {status: 404}); } default: - if (env.FLAG_USE_HEADERS) { - return new Response(JSON.stringify({ message: `/${key} not found`}, null, 2), { - status: 404, - headers: { - ...corsHeaders, - 'Content-type': 'application/json' - }, - }); - } else { - return new Response(JSON.stringify({ message: `/${key} not found`}, null, 2), { - status: 404, - }); - } + return new Response(JSON.stringify({ message: `/${key} not found`}, null, 2), { + status: 404, + headers: { + ...corsHeaders, + 'Content-type': 'application/json' + }, + }); } }, }; From 3df4c4e0b51dd673bc0cc5f1dfcf9dbd06f34342 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Wed, 13 Sep 2023 06:54:53 -0700 Subject: [PATCH 05/23] Removing feature flag for the CORS headers --- backend/wrangler.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/wrangler.toml b/backend/wrangler.toml index b3a1000..77b90dd 100644 --- a/backend/wrangler.toml +++ b/backend/wrangler.toml @@ -8,7 +8,7 @@ r2_buckets = [ ] [vars] -FLAG_USE_HEADERS=true +#FLAG_USE_HEADERS=true [dev] ip = "0.0.0.0" @@ -29,7 +29,7 @@ d1_databases = [ ] [env.staging.vars] -FLAG_USE_HEADERS=true +#FLAG_USE_HEADERS=true [env.production] @@ -44,4 +44,4 @@ d1_databases = [ ] [env.production.vars] -FLAG_USE_HEADERS=true +#FLAG_USE_HEADERS=true From 5d9f30ef7b3af191480473b9fe1f7175266679ce Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Thu, 14 Sep 2023 06:56:06 -0700 Subject: [PATCH 06/23] Adding in the scaffolding for CRUD with drips --- backend/src/worker.js | 115 +++++++++++++++++++++++++++-------------- backend/test/script.js | 48 ++++++++++++++++- 2 files changed, 121 insertions(+), 42 deletions(-) diff --git a/backend/src/worker.js b/backend/src/worker.js index 1d9f247..8cb67c4 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -10,36 +10,11 @@ const corsHeaders = { 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST', + 'Access-Control-Allow-Methods': 'GET, POST, DELETE', 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept' } -const isAuthorized = request => { - const psk = request.headers.get("X-Custom-PSK") - return psk == "THIS_IS_A_SECRET" ? true : false -} - - -const isValidData = request => { - const contentType = request.headers.get("content-type") - return contentType == "application/json" ? true : false -} - - -const validatePostRequest = request => { - const authorized = isAuthorized(request) - const validData = isValidData(request) - - if (!authorized) { - return [false, {message: "Not Authorized", status: 403}] - } else if (!validData) { - return [false, {message: "Please see POST /drip docs", status: 400}] - } else { - return [true, {}] - } -} - const getRSS = async (key, env) => { console.log(`RSS match found`) @@ -112,7 +87,71 @@ const getPost = async (key, env) => { } -const getDrip = async (key, env) => { +const validateHeaders = headers => { + const expectedHeaders = [ + {"name": "X-Custom-PSK", "value": "THIS_IS_A_SECRET", "errorMessage": "Not Authorized", "status": 403}, + {"name": "content-type", "value": "application/json", "errorMessage": "Please see docs", "status": 400} + ] + + for (const header of expectedHeaders) { + if (headers.get(header.name) != header.value) { + return [false, {message: header.errorMessage, status: header.status}] + } + } + + return [true, {}] +} + + +const validateBody = (body, requiredData) => { + for (const datum of requiredData) { + if (!(datum in body)) { + return [false, {message: `Missing data: ${datum}`, status: 400}] + } + } + + return [true, {}] +} + + +const validateRequest = async (request, requiredData) => { + const [headersAreValid, headersError] = validateHeaders(request.headers) + if (!headersAreValid) return [false, null, headersError] + + try { + const body = await request.json() + console.log(`body: ${JSON.stringify(body, null, 2)}`) + const [bodyIsValid, bodyError] = validateBody(body, requiredData) + if (!bodyIsValid) return [false, null, bodyError] + + return [true, body, {}] + } catch { + return [false, null, {message: "missing request body", status: 400}] + } +} + + +const createOrUpdateDrip = async (request, env) => { + const [requestIsValid, body, error] = await validateRequest(request, ["message"]) + + if (!requestIsValid) { + return new Response(JSON.stringify({message: error.message}, null, 2), {status: error.status}) + } + + const action = "id" in body ? "update drip" : "create drip" + const message = `POST called on /drip. Action: ${action}` + + return new Response( + JSON.stringify({ + message: message, + data: body + }, null, 2), + {status: 200} + ); +} + + +const readDrip = async (key, env) => { return new Response( JSON.stringify({ message: `GET called on /${key}` @@ -122,23 +161,17 @@ const getDrip = async (key, env) => { } -const postDrip = async (request, env) => { - const [requestIsValid, error] = validatePostRequest(request) +const deleteDrip = async (request, env) => { + const [requestIsValid, body, error] = await validatePostRequest(request) if (!requestIsValid) { return new Response(JSON.stringify({message: error.message}, null, 2), {status: error.status}) } - const reqBody = await request.json() - - if (!("message" in reqBody)) { - return new Response(JSON.stringify({message: "Please see POST /drip docs"}, null, 2), {status: 400}) - } - return new Response( JSON.stringify({ - message: `POST called on /drip`, - data: reqBody + message: `DELETE called on /drip`, + data: body }, null, 2), {status: 200} ); @@ -165,10 +198,12 @@ export default { return getPost(key, env); case routeDrip.test(key): switch (method) { - case 'GET': - return getDrip(key, env); case 'POST': - return postDrip(request, env); + return createOrUpdateDrip(request, env); + case 'GET': + return readDrip(key, env); + case 'DELETE': + return deleteDrip(request, env); default: return new Response(JSON.stringify({message: `Error: ${method} not supported on /${key}`}, null, 2), {status: 404}); } diff --git a/backend/test/script.js b/backend/test/script.js index 64904f7..6ec8b71 100644 --- a/backend/test/script.js +++ b/backend/test/script.js @@ -81,8 +81,52 @@ export default function () { const res = http.post(`${BASE_URL}/drip`) check(res, { - 'POST: status is 200': (r) => r.status === 200, - 'POST: verify body': (r) => r.body.includes('POST called on /drip') + 'POST - auth: status is 200': (r) => r.status === 403, + 'POST - auth: verify body': (r) => r.body.includes('Not Authorized') + }) + + sleep(1) // second + }) + + // GROUP: drip + group('drip', function () { + const payload = JSON.stringify({ + message: 'hello', + }); + + const params = { + headers: { + 'X-Custom-PSK': __ENV.PSK, + 'Content-Type': 'application/json', + }, + }; + + const res = http.post(`${BASE_URL}/drip`, payload, params) + check(res, { + 'POST - create: status is 200': (r) => r.status === 200, + 'POST - create: verify body': (r) => r.body.includes('create drip') + }) + + sleep(1) // second + }) + + group('drip', function () { + const payload = JSON.stringify({ + id: 0, + message: 'hello', + }); + + const params = { + headers: { + 'X-Custom-PSK': __ENV.PSK, + 'Content-Type': 'application/json', + }, + }; + + const res = http.post(`${BASE_URL}/drip`, payload, params) + check(res, { + 'POST - update: status is 200': (r) => r.status === 200, + 'POST - update: verify body': (r) => r.body.includes('update drip') }) sleep(1) // second From 2f7b99f1e38de40f1517e2cb98e66eda675799f2 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Fri, 15 Sep 2023 06:26:26 -0700 Subject: [PATCH 07/23] Migrated the RSS path to hono backend --- backend/package-lock.json | 308 +++++++++++++++++++------------------- backend/package.json | 3 + backend/src/worker.js | 207 +------------------------ backend/src/worker.js.bak | 220 +++++++++++++++++++++++++++ backend/wrangler.toml | 4 +- 5 files changed, 389 insertions(+), 353 deletions(-) create mode 100644 backend/src/worker.js.bak diff --git a/backend/package-lock.json b/backend/package-lock.json index 2963e15..73784f3 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "blog", "version": "0.0.0", + "dependencies": { + "hono": "^3.6.1" + }, "devDependencies": { "wrangler": "^3.6.0" } @@ -21,9 +24,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20230814.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20230814.1.tgz", - "integrity": "sha512-aQUO7q7qXl+SVtOiMMlVKLNOSeL6GX43RKeflwzsD74dGgyHPiSfw5KCvXhkVbyN7u+yYF6HyFdaIvHLfn5jyA==", + "version": "1.20230904.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20230904.0.tgz", + "integrity": "sha512-/GDlmxAFbDtrQwP4zOXFbqOfaPvkDxdsCoEa+KEBcAl5uR98+7WW5/b8naBHX+t26uS7p4bLlImM8J5F1ienRQ==", "cpu": [ "x64" ], @@ -37,9 +40,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20230814.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20230814.1.tgz", - "integrity": "sha512-U2mcgi+AiuI/4EY5Wk/GmygiNoCNw/V2mcHmxESqe4r6XbJYOzBdEsjnqJ05rqd0JlEM8m64jRtE6/qBnQHygg==", + "version": "1.20230904.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20230904.0.tgz", + "integrity": "sha512-x8WXNc2xnDqr5y1iirnNdyx8GZY3rL5xiF7ebK3mKQeB+jFjkhO71yuPTkDCzUWtOvw1Wfd4jbwy4wxacMX4mQ==", "cpu": [ "arm64" ], @@ -53,9 +56,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20230814.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20230814.1.tgz", - "integrity": "sha512-Q4kITXLTCuG2i2Z01fbb5AjVRRIf3+lS4ZVsFbTbIwtcOOG4Ozcw7ee7tKsFES7hFqR4Eg9gMG4/aS0mmi+L2g==", + "version": "1.20230904.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20230904.0.tgz", + "integrity": "sha512-V58xyMS3oDpKO8Dpdh0r0BXm99OzoGgvWe9ufttVraj/1NTMGELwb6i9ySb8k3F1J9m/sO26+TV7pQc/bGC1VQ==", "cpu": [ "x64" ], @@ -69,9 +72,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20230814.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20230814.1.tgz", - "integrity": "sha512-BX5SaksXw+pkREVw3Rw2eSNXplqZw+14CcwW/5x/4oq/C6yn5qCvKxJfM7pukJGMI4wkJPOYops7B3g27FB/HA==", + "version": "1.20230904.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20230904.0.tgz", + "integrity": "sha512-VrDaW+pjb5IAKEnNWtEaFiG377kXKmk5Fu0Era4W+jKzPON2BW/qRb/4LNHXQ4yxg/2HLm7RiUTn7JZtt1qO6A==", "cpu": [ "arm64" ], @@ -85,9 +88,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20230814.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20230814.1.tgz", - "integrity": "sha512-GWHqfyhsG/1wm2W8afkYX3q3fWXUWWD8NGtHfAs6ZVTHdW3mmYyMhKR0lc6ptBwz5i5aXRlP2S+CxxxwwDbKpw==", + "version": "1.20230904.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20230904.0.tgz", + "integrity": "sha512-/R/dE8uy+8J2YeXfDhI8/Bg7YUirdbbjH5/l/Vv00ZRE0lC3nPLcYeyBXSwXIQ6/Xht3gN+lksLQgKd0ZWRd+Q==", "cpu": [ "x64" ], @@ -101,18 +104,18 @@ } }, "node_modules/@esbuild-plugins/node-globals-polyfill": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.1.1.tgz", - "integrity": "sha512-MR0oAA+mlnJWrt1RQVQ+4VYuRJW/P2YmRTv1AsplObyvuBMnPHiizUF95HHYiSsMGLhyGtWufaq2XQg6+iurBg==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", + "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", "dev": true, "peerDependencies": { "esbuild": "*" } }, "node_modules/@esbuild-plugins/node-modules-polyfill": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.1.4.tgz", - "integrity": "sha512-uZbcXi0zbmKC/050p3gJnne5Qdzw8vkXIv+c2BW0Lsc1ji1SkrxbKPUy5Efr0blbTu1SL8w4eyfpnSdPg3G0Qg==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", + "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", "dev": true, "dependencies": { "escape-string-regexp": "^4.0.0", @@ -123,9 +126,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.3.tgz", - "integrity": "sha512-mueuEoh+s1eRbSJqq9KNBQwI4QhQV6sRXIfTyLXSHGMpyew61rOK4qY21uKbXl1iBoMb0AdL1deWFCQVlN2qHA==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", "cpu": [ "arm" ], @@ -139,9 +142,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.3.tgz", - "integrity": "sha512-RolFVeinkeraDvN/OoRf1F/lP0KUfGNb5jxy/vkIMeRRChkrX/HTYN6TYZosRJs3a1+8wqpxAo5PI5hFmxyPRg==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", "cpu": [ "arm64" ], @@ -155,9 +158,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.3.tgz", - "integrity": "sha512-SFpTUcIT1bIJuCCBMCQWq1bL2gPTjWoLZdjmIhjdcQHaUfV41OQfho6Ici5uvvkMmZRXIUGpM3GxysP/EU7ifQ==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", "cpu": [ "x64" ], @@ -171,9 +174,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.3.tgz", - "integrity": "sha512-DO8WykMyB+N9mIDfI/Hug70Dk1KipavlGAecxS3jDUwAbTpDXj0Lcwzw9svkhxfpCagDmpaTMgxWK8/C/XcXvw==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", "cpu": [ "arm64" ], @@ -187,9 +190,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.3.tgz", - "integrity": "sha512-uEqZQ2omc6BvWqdCiyZ5+XmxuHEi1SPzpVxXCSSV2+Sh7sbXbpeNhHIeFrIpRjAs0lI1FmA1iIOxFozKBhKgRQ==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", "cpu": [ "x64" ], @@ -203,9 +206,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.3.tgz", - "integrity": "sha512-nJansp3sSXakNkOD5i5mIz2Is/HjzIhFs49b1tjrPrpCmwgBmH9SSzhC/Z1UqlkivqMYkhfPwMw1dGFUuwmXhw==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", "cpu": [ "arm64" ], @@ -219,9 +222,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.3.tgz", - "integrity": "sha512-TfoDzLw+QHfc4a8aKtGSQ96Wa+6eimljjkq9HKR0rHlU83vw8aldMOUSJTUDxbcUdcgnJzPaX8/vGWm7vyV7ug==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", "cpu": [ "x64" ], @@ -235,9 +238,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.3.tgz", - "integrity": "sha512-VwswmSYwVAAq6LysV59Fyqk3UIjbhuc6wb3vEcJ7HEJUtFuLK9uXWuFoH1lulEbE4+5GjtHi3MHX+w1gNHdOWQ==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", "cpu": [ "arm" ], @@ -251,9 +254,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.3.tgz", - "integrity": "sha512-7I3RlsnxEFCHVZNBLb2w7unamgZ5sVwO0/ikE2GaYvYuUQs9Qte/w7TqWcXHtCwxvZx/2+F97ndiUQAWs47ZfQ==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", "cpu": [ "arm64" ], @@ -267,9 +270,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.3.tgz", - "integrity": "sha512-X8FDDxM9cqda2rJE+iblQhIMYY49LfvW4kaEjoFbTTQ4Go8G96Smj2w3BRTwA8IHGoi9dPOPGAX63dhuv19UqA==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", "cpu": [ "ia32" ], @@ -283,9 +286,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.3.tgz", - "integrity": "sha512-hIbeejCOyO0X9ujfIIOKjBjNAs9XD/YdJ9JXAy1lHA+8UXuOqbFe4ErMCqMr8dhlMGBuvcQYGF7+kO7waj2KHw==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", "cpu": [ "loong64" ], @@ -299,9 +302,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.3.tgz", - "integrity": "sha512-znFRzICT/V8VZQMt6rjb21MtAVJv/3dmKRMlohlShrbVXdBuOdDrGb+C2cZGQAR8RFyRe7HS6klmHq103WpmVw==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", "cpu": [ "mips64el" ], @@ -315,9 +318,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.3.tgz", - "integrity": "sha512-EV7LuEybxhXrVTDpbqWF2yehYRNz5e5p+u3oQUS2+ZFpknyi1NXxr8URk4ykR8Efm7iu04//4sBg249yNOwy5Q==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", "cpu": [ "ppc64" ], @@ -331,9 +334,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.3.tgz", - "integrity": "sha512-uDxqFOcLzFIJ+r/pkTTSE9lsCEaV/Y6rMlQjUI9BkzASEChYL/aSQjZjchtEmdnVxDKETnUAmsaZ4pqK1eE5BQ==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", "cpu": [ "riscv64" ], @@ -347,9 +350,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.3.tgz", - "integrity": "sha512-NbeREhzSxYwFhnCAQOQZmajsPYtX71Ufej3IQ8W2Gxskfz9DK58ENEju4SbpIj48VenktRASC52N5Fhyf/aliQ==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", "cpu": [ "s390x" ], @@ -363,9 +366,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.3.tgz", - "integrity": "sha512-SDiG0nCixYO9JgpehoKgScwic7vXXndfasjnD5DLbp1xltANzqZ425l7LSdHynt19UWOcDjG9wJJzSElsPvk0w==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", "cpu": [ "x64" ], @@ -379,9 +382,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.3.tgz", - "integrity": "sha512-AzbsJqiHEq1I/tUvOfAzCY15h4/7Ivp3ff/o1GpP16n48JMNAtbW0qui2WCgoIZArEHD0SUQ95gvR0oSO7ZbdA==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", "cpu": [ "x64" ], @@ -395,9 +398,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.3.tgz", - "integrity": "sha512-gSABi8qHl8k3Cbi/4toAzHiykuBuWLZs43JomTcXkjMZVkp0gj3gg9mO+9HJW/8GB5H89RX/V0QP4JGL7YEEVg==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", "cpu": [ "x64" ], @@ -411,9 +414,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.3.tgz", - "integrity": "sha512-SF9Kch5Ete4reovvRO6yNjMxrvlfT0F0Flm+NPoUw5Z4Q3r1d23LFTgaLwm3Cp0iGbrU/MoUI+ZqwCv5XJijCw==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", "cpu": [ "x64" ], @@ -427,9 +430,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.3.tgz", - "integrity": "sha512-u5aBonZIyGopAZyOnoPAA6fGsDeHByZ9CnEzyML9NqntK6D/xl5jteZUKm/p6nD09+v3pTM6TuUIqSPcChk5gg==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", "cpu": [ "arm64" ], @@ -443,9 +446,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.3.tgz", - "integrity": "sha512-GlgVq1WpvOEhNioh74TKelwla9KDuAaLZrdxuuUgsP2vayxeLgVc+rbpIv0IYF4+tlIzq2vRhofV+KGLD+37EQ==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", "cpu": [ "ia32" ], @@ -459,9 +462,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.3.tgz", - "integrity": "sha512-5/JuTd8OWW8UzEtyf19fbrtMJENza+C9JoPIkvItgTBQ1FO2ZLvjbPO6Xs54vk0s5JB5QsfieUEshRQfu7ZHow==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", "cpu": [ "x64" ], @@ -538,14 +541,14 @@ ] }, "node_modules/better-sqlite3": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.5.2.tgz", - "integrity": "sha512-w/EZ/jwuZF+/47mAVC2+rhR2X/gwkZ+fd1pbX7Y90D5NRaRzDQcxrHY10t6ijGiYIonCVsBSF5v1cay07bP5sg==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.6.0.tgz", + "integrity": "sha512-jwAudeiTMTSyby+/SfbHDebShbmC2MCH8mU2+DXi0WJfv13ypEJm47cd3kljmy/H130CazEvkf2Li//ewcMJ1g==", "dev": true, "hasInstallScript": true, "dependencies": { "bindings": "^1.5.0", - "prebuild-install": "^7.1.0" + "prebuild-install": "^7.1.1" } }, "node_modules/binary-extensions": { @@ -755,9 +758,9 @@ } }, "node_modules/esbuild": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.3.tgz", - "integrity": "sha512-71f7EjPWTiSguen8X/kxEpkAS7BFHwtQKisCDDV3Y4GLGWBaoSCyD5uXkaUew6JDzA9FEN1W23mdnSwW9kqCeg==", + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", "dev": true, "hasInstallScript": true, "bin": { @@ -767,28 +770,28 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.16.3", - "@esbuild/android-arm64": "0.16.3", - "@esbuild/android-x64": "0.16.3", - "@esbuild/darwin-arm64": "0.16.3", - "@esbuild/darwin-x64": "0.16.3", - "@esbuild/freebsd-arm64": "0.16.3", - "@esbuild/freebsd-x64": "0.16.3", - "@esbuild/linux-arm": "0.16.3", - "@esbuild/linux-arm64": "0.16.3", - "@esbuild/linux-ia32": "0.16.3", - "@esbuild/linux-loong64": "0.16.3", - "@esbuild/linux-mips64el": "0.16.3", - "@esbuild/linux-ppc64": "0.16.3", - "@esbuild/linux-riscv64": "0.16.3", - "@esbuild/linux-s390x": "0.16.3", - "@esbuild/linux-x64": "0.16.3", - "@esbuild/netbsd-x64": "0.16.3", - "@esbuild/openbsd-x64": "0.16.3", - "@esbuild/sunos-x64": "0.16.3", - "@esbuild/win32-arm64": "0.16.3", - "@esbuild/win32-ia32": "0.16.3", - "@esbuild/win32-x64": "0.16.3" + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" } }, "node_modules/escape-string-regexp": { @@ -911,6 +914,14 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, + "node_modules/hono": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/hono/-/hono-3.6.1.tgz", + "integrity": "sha512-FaWXh0MSc2Hv2IrGI4vFvZEK69NHfggEgHUlNMXp2zrpKh23j7wS0Ku316Do9CFAl07OBNozBelcvruiBT8crQ==", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -1046,9 +1057,9 @@ } }, "node_modules/miniflare": { - "version": "3.20230814.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20230814.1.tgz", - "integrity": "sha512-LMgqd1Ut0+fnlvQepVbbBYQczQnyuuap8bgUwOyPETka0S9NR9NxMQSNaBgVZ0uOaG7xMJ/OVTRlz+TGB86PWA==", + "version": "3.20230904.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20230904.0.tgz", + "integrity": "sha512-+OWQqEk8hV7vZaPCoj5dk1lZr4YUy56OiyNZ45/3ITYf+ZxgQOBPWhQhpw1jCahkRKGPa9Aykz01sc+GhPZYDA==", "dev": true, "dependencies": { "acorn": "^8.8.0", @@ -1059,11 +1070,10 @@ "glob-to-regexp": "^0.4.1", "http-cache-semantics": "^4.1.0", "kleur": "^4.1.5", - "set-cookie-parser": "^2.6.0", "source-map-support": "0.5.21", "stoppable": "^1.1.0", - "undici": "^5.13.0", - "workerd": "1.20230814.1", + "undici": "^5.22.1", + "workerd": "1.20230904.0", "ws": "^8.11.0", "youch": "^3.2.2", "zod": "^3.20.6" @@ -1343,12 +1353,6 @@ "node": ">=10" } }, - "node_modules/set-cookie-parser": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", - "dev": true - }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -1535,9 +1539,9 @@ } }, "node_modules/undici": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.23.0.tgz", - "integrity": "sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.24.0.tgz", + "integrity": "sha512-OKlckxBjFl0oXxcj9FU6oB8fDAaiRUq+D8jrFWGmOfI/gIyjk/IeS75LMzgYKUaeHzLUcYvf9bbJGSrUwTfwwQ==", "dev": true, "dependencies": { "busboy": "^1.6.0" @@ -1553,9 +1557,9 @@ "dev": true }, "node_modules/workerd": { - "version": "1.20230814.1", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20230814.1.tgz", - "integrity": "sha512-zJeSEteXuAD+bpYJT8WvzTAHvIAkKPVxOV+Jy6zCLKz5e08N3OUbAF+wrvGWc8b2aB1sj+IYsdXfkv4puH+qXQ==", + "version": "1.20230904.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20230904.0.tgz", + "integrity": "sha512-t9znszH0rQGK4mJGvF9L3nN0qKEaObAGx0JkywFtAwH8OkSn+YfQbHNZE+YsJ4qa1hOz1DCNEk08UDFRBaYq4g==", "dev": true, "hasInstallScript": true, "bin": { @@ -1565,26 +1569,26 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20230814.1", - "@cloudflare/workerd-darwin-arm64": "1.20230814.1", - "@cloudflare/workerd-linux-64": "1.20230814.1", - "@cloudflare/workerd-linux-arm64": "1.20230814.1", - "@cloudflare/workerd-windows-64": "1.20230814.1" + "@cloudflare/workerd-darwin-64": "1.20230904.0", + "@cloudflare/workerd-darwin-arm64": "1.20230904.0", + "@cloudflare/workerd-linux-64": "1.20230904.0", + "@cloudflare/workerd-linux-arm64": "1.20230904.0", + "@cloudflare/workerd-windows-64": "1.20230904.0" } }, "node_modules/wrangler": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.6.0.tgz", - "integrity": "sha512-GWs4+gIUK+086svW/TgFhhxxrl/hdW2L7WASbdc10dJT7yFmCXse0SnHiqWUxbFu3ScP2t3a3LszJ08wwolWHg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.8.0.tgz", + "integrity": "sha512-sTdD+6fMEpM9ROxv+gcyxgTKpnf7tB5ftRV5+wupsdljWkow5C00UCWU/IWSOUfuitAGAj1PWATjKfrRp9Bk9w==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "^0.2.0", - "@esbuild-plugins/node-globals-polyfill": "^0.1.1", - "@esbuild-plugins/node-modules-polyfill": "^0.1.4", + "@esbuild-plugins/node-globals-polyfill": "^0.2.3", + "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "blake3-wasm": "^2.1.5", "chokidar": "^3.5.3", - "esbuild": "0.16.3", - "miniflare": "3.20230814.1", + "esbuild": "0.17.19", + "miniflare": "3.20230904.0", "nanoid": "^3.3.3", "path-to-regexp": "^6.2.0", "selfsigned": "^2.0.1", @@ -1609,9 +1613,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz", + "integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==", "dev": true, "engines": { "node": ">=10.0.0" @@ -1642,9 +1646,9 @@ "dev": true }, "node_modules/youch": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/youch/-/youch-3.2.3.tgz", - "integrity": "sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.1.tgz", + "integrity": "sha512-Rg9ioi+AkKyje2Hk4qILSVvayaFW98KTsOJ4aIkjDf97LZX5WJVIHZmFLnM4ThcVofHo/fbbwtYajfBPHFOVtg==", "dev": true, "dependencies": { "cookie": "^0.5.0", diff --git a/backend/package.json b/backend/package.json index 7fb65e6..0c53723 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,5 +8,8 @@ }, "devDependencies": { "wrangler": "^3.6.0" + }, + "dependencies": { + "hono": "^3.6.1" } } diff --git a/backend/src/worker.js b/backend/src/worker.js index 8cb67c4..a3754a3 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -1,12 +1,7 @@ -/** - * Welcome to Cloudflare Workers! This is your first worker. - * - * - Run `npm run dev` in your terminal to start a development server - * - Open a browser tab at http://localhost:8787/ to see your worker in action - * - Run `npm run deploy` to publish your worker - * - * Learn more at https://developers.cloudflare.com/workers/ - */ +import { Hono } from 'hono' + + +const app = new Hono() const corsHeaders = { 'Access-Control-Allow-Origin': '*', @@ -15,10 +10,8 @@ const corsHeaders = { } - -const getRSS = async (key, env) => { - console.log(`RSS match found`) - const rssBlob = await env.BLOG_BUCKET.get(key); +app.get('/rss.xml', async c => { + const rssBlob = await c.env.BLOG_BUCKET.get('rss.xml'); if (rssBlob === null) { return new Response('Object Not Found', { status: 404 }); @@ -29,192 +22,8 @@ const getRSS = async (key, env) => { 'etag': rssBlob.httpEtag, 'Content-type': 'application/xml' }}); -}; - - -const getPostList = async (env) => { - const postBlob = await env.BLOG_BUCKET.get('posts.json'); - - if (postBlob === null) { - return new Response('Object Not Found', { status: 404 }); - } - - return postBlob.json() - .then(posts => ( - Object.entries(posts) - .map(([slug, post]) => { - return { - slug: post.slug, - published: post.published, - title: post.title - } - }) - )) - .then(postList => { - return new Response(JSON.stringify({ postList: postList }, null, 4), { - status: 200, - headers: { - ...corsHeaders, - 'etag': postBlob.httpEtag, - 'Content-type': 'application/json' - } - }); - }) -} - - -const getPost = async (key, env) => { - const postBlob = await env.BLOG_BUCKET.get('posts.json'); - - if (postBlob === null) { - return new Response('Object Not Found', { status: 404 }); - } - - const postSlug = key.split("/")[1]; - - return postBlob.json() - .then(posts => posts[postSlug]) - .then(post => { - return new Response(JSON.stringify({ post: post }, null, 4), { - status: 200, - headers: { - ...corsHeaders, - 'etag': postBlob.httpEtag, - 'Content-type': 'application/json' - } - }); - }) -} - - -const validateHeaders = headers => { - const expectedHeaders = [ - {"name": "X-Custom-PSK", "value": "THIS_IS_A_SECRET", "errorMessage": "Not Authorized", "status": 403}, - {"name": "content-type", "value": "application/json", "errorMessage": "Please see docs", "status": 400} - ] - - for (const header of expectedHeaders) { - if (headers.get(header.name) != header.value) { - return [false, {message: header.errorMessage, status: header.status}] - } - } - - return [true, {}] -} - - -const validateBody = (body, requiredData) => { - for (const datum of requiredData) { - if (!(datum in body)) { - return [false, {message: `Missing data: ${datum}`, status: 400}] - } - } - - return [true, {}] -} - - -const validateRequest = async (request, requiredData) => { - const [headersAreValid, headersError] = validateHeaders(request.headers) - if (!headersAreValid) return [false, null, headersError] - - try { - const body = await request.json() - console.log(`body: ${JSON.stringify(body, null, 2)}`) - const [bodyIsValid, bodyError] = validateBody(body, requiredData) - if (!bodyIsValid) return [false, null, bodyError] - - return [true, body, {}] - } catch { - return [false, null, {message: "missing request body", status: 400}] - } -} - - -const createOrUpdateDrip = async (request, env) => { - const [requestIsValid, body, error] = await validateRequest(request, ["message"]) - - if (!requestIsValid) { - return new Response(JSON.stringify({message: error.message}, null, 2), {status: error.status}) - } - - const action = "id" in body ? "update drip" : "create drip" - const message = `POST called on /drip. Action: ${action}` - - return new Response( - JSON.stringify({ - message: message, - data: body - }, null, 2), - {status: 200} - ); -} - - -const readDrip = async (key, env) => { - return new Response( - JSON.stringify({ - message: `GET called on /${key}` - }, null, 2), - {status: 200} - ); -} - - -const deleteDrip = async (request, env) => { - const [requestIsValid, body, error] = await validatePostRequest(request) - - if (!requestIsValid) { - return new Response(JSON.stringify({message: error.message}, null, 2), {status: error.status}) - } - - return new Response( - JSON.stringify({ - message: `DELETE called on /drip`, - data: body - }, null, 2), - {status: 200} - ); -} - -export default { - async fetch(request, env) { - const url = new URL(request.url); - const key = url.pathname.slice(1); - const method = request.method; +}) - const routeRSS = /rss.xml/, - routePostList = /posts$/, - routePost = /posts\/*/, - routeDrip = /drip$/; - switch (true) { - case routeRSS.test(key): - return getRSS(key, env); - case routePostList.test(key): - return getPostList(env); - case routePost.test(key): - return getPost(key, env); - case routeDrip.test(key): - switch (method) { - case 'POST': - return createOrUpdateDrip(request, env); - case 'GET': - return readDrip(key, env); - case 'DELETE': - return deleteDrip(request, env); - default: - return new Response(JSON.stringify({message: `Error: ${method} not supported on /${key}`}, null, 2), {status: 404}); - } - default: - return new Response(JSON.stringify({ message: `/${key} not found`}, null, 2), { - status: 404, - headers: { - ...corsHeaders, - 'Content-type': 'application/json' - }, - }); - } - }, -}; +export default app diff --git a/backend/src/worker.js.bak b/backend/src/worker.js.bak new file mode 100644 index 0000000..8cb67c4 --- /dev/null +++ b/backend/src/worker.js.bak @@ -0,0 +1,220 @@ +/** + * Welcome to Cloudflare Workers! This is your first worker. + * + * - Run `npm run dev` in your terminal to start a development server + * - Open a browser tab at http://localhost:8787/ to see your worker in action + * - Run `npm run deploy` to publish your worker + * + * Learn more at https://developers.cloudflare.com/workers/ + */ + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, DELETE', + 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept' +} + + + +const getRSS = async (key, env) => { + console.log(`RSS match found`) + const rssBlob = await env.BLOG_BUCKET.get(key); + + if (rssBlob === null) { + return new Response('Object Not Found', { status: 404 }); + } + + return new Response(rssBlob.body, { status: 200, headers: { + ...corsHeaders, + 'etag': rssBlob.httpEtag, + 'Content-type': 'application/xml' + }}); +}; + + +const getPostList = async (env) => { + const postBlob = await env.BLOG_BUCKET.get('posts.json'); + + if (postBlob === null) { + return new Response('Object Not Found', { status: 404 }); + } + + return postBlob.json() + .then(posts => ( + Object.entries(posts) + .map(([slug, post]) => { + return { + slug: post.slug, + published: post.published, + title: post.title + } + }) + )) + .then(postList => { + return new Response(JSON.stringify({ postList: postList }, null, 4), { + status: 200, + headers: { + ...corsHeaders, + 'etag': postBlob.httpEtag, + 'Content-type': 'application/json' + } + }); + }) +} + + +const getPost = async (key, env) => { + const postBlob = await env.BLOG_BUCKET.get('posts.json'); + + if (postBlob === null) { + return new Response('Object Not Found', { status: 404 }); + } + + const postSlug = key.split("/")[1]; + + return postBlob.json() + .then(posts => posts[postSlug]) + .then(post => { + return new Response(JSON.stringify({ post: post }, null, 4), { + status: 200, + headers: { + ...corsHeaders, + 'etag': postBlob.httpEtag, + 'Content-type': 'application/json' + } + }); + }) +} + + +const validateHeaders = headers => { + const expectedHeaders = [ + {"name": "X-Custom-PSK", "value": "THIS_IS_A_SECRET", "errorMessage": "Not Authorized", "status": 403}, + {"name": "content-type", "value": "application/json", "errorMessage": "Please see docs", "status": 400} + ] + + for (const header of expectedHeaders) { + if (headers.get(header.name) != header.value) { + return [false, {message: header.errorMessage, status: header.status}] + } + } + + return [true, {}] +} + + +const validateBody = (body, requiredData) => { + for (const datum of requiredData) { + if (!(datum in body)) { + return [false, {message: `Missing data: ${datum}`, status: 400}] + } + } + + return [true, {}] +} + + +const validateRequest = async (request, requiredData) => { + const [headersAreValid, headersError] = validateHeaders(request.headers) + if (!headersAreValid) return [false, null, headersError] + + try { + const body = await request.json() + console.log(`body: ${JSON.stringify(body, null, 2)}`) + const [bodyIsValid, bodyError] = validateBody(body, requiredData) + if (!bodyIsValid) return [false, null, bodyError] + + return [true, body, {}] + } catch { + return [false, null, {message: "missing request body", status: 400}] + } +} + + +const createOrUpdateDrip = async (request, env) => { + const [requestIsValid, body, error] = await validateRequest(request, ["message"]) + + if (!requestIsValid) { + return new Response(JSON.stringify({message: error.message}, null, 2), {status: error.status}) + } + + const action = "id" in body ? "update drip" : "create drip" + const message = `POST called on /drip. Action: ${action}` + + return new Response( + JSON.stringify({ + message: message, + data: body + }, null, 2), + {status: 200} + ); +} + + +const readDrip = async (key, env) => { + return new Response( + JSON.stringify({ + message: `GET called on /${key}` + }, null, 2), + {status: 200} + ); +} + + +const deleteDrip = async (request, env) => { + const [requestIsValid, body, error] = await validatePostRequest(request) + + if (!requestIsValid) { + return new Response(JSON.stringify({message: error.message}, null, 2), {status: error.status}) + } + + return new Response( + JSON.stringify({ + message: `DELETE called on /drip`, + data: body + }, null, 2), + {status: 200} + ); +} + + +export default { + async fetch(request, env) { + const url = new URL(request.url); + const key = url.pathname.slice(1); + const method = request.method; + + const routeRSS = /rss.xml/, + routePostList = /posts$/, + routePost = /posts\/*/, + routeDrip = /drip$/; + + switch (true) { + case routeRSS.test(key): + return getRSS(key, env); + case routePostList.test(key): + return getPostList(env); + case routePost.test(key): + return getPost(key, env); + case routeDrip.test(key): + switch (method) { + case 'POST': + return createOrUpdateDrip(request, env); + case 'GET': + return readDrip(key, env); + case 'DELETE': + return deleteDrip(request, env); + default: + return new Response(JSON.stringify({message: `Error: ${method} not supported on /${key}`}, null, 2), {status: 404}); + } + default: + return new Response(JSON.stringify({ message: `/${key} not found`}, null, 2), { + status: 404, + headers: { + ...corsHeaders, + 'Content-type': 'application/json' + }, + }); + } + }, +}; diff --git a/backend/wrangler.toml b/backend/wrangler.toml index 77b90dd..fcc709e 100644 --- a/backend/wrangler.toml +++ b/backend/wrangler.toml @@ -25,7 +25,7 @@ r2_buckets = [ { binding = "BLOG_BUCKET", bucket_name = "flinnlab-blog-dev" } ] d1_databases = [ - { binding = "drip-db", database_name = "blog-drip-dev", database_id = "d898dfc0-f641-425c-8c6a-e25da2cf7468", migrations_dir = "../data/drip-db/migrations" } + { binding = "DB_DRIP", database_name = "blog-drip-dev", database_id = "d898dfc0-f641-425c-8c6a-e25da2cf7468", migrations_dir = "../data/drip-db/migrations" } ] [env.staging.vars] @@ -40,7 +40,7 @@ r2_buckets = [ { binding = "BLOG_BUCKET", bucket_name = "flinnlab-blog" } ] d1_databases = [ - { binding = "drip-db", database_name = "blog-drip", database_id = "b8dc883c-78e0-4687-bfe2-45401ae32813", migrations_dir = "../data/databases/blog-drip/migrations" } + { binding = "DB_DRIP", database_name = "blog-drip", database_id = "b8dc883c-78e0-4687-bfe2-45401ae32813", migrations_dir = "../data/databases/blog-drip/migrations" } ] [env.production.vars] From 416c469f6257a1366110637eeff82f0c7b5caf64 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Fri, 15 Sep 2023 06:29:02 -0700 Subject: [PATCH 08/23] Moved the postList endpoint to Hono --- backend/src/worker.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/backend/src/worker.js b/backend/src/worker.js index a3754a3..92c6647 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -26,4 +26,35 @@ app.get('/rss.xml', async c => { }) +app.get('/posts', async c => { + const postBlob = await c.env.BLOG_BUCKET.get('posts.json'); + + if (postBlob === null) { + return new Response('Object Not Found', { status: 404 }); + } + + return postBlob.json() + .then(posts => ( + Object.entries(posts) + .map(([slug, post]) => { + return { + slug: post.slug, + published: post.published, + title: post.title + } + }) + )) + .then(postList => { + return new Response(JSON.stringify({ postList: postList }, null, 4), { + status: 200, + headers: { + ...corsHeaders, + 'etag': postBlob.httpEtag, + 'Content-type': 'application/json' + } + }); + }) +}) + + export default app From 2f7bf88c320766224dd43e8bc59791dc686fb4ad Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Fri, 15 Sep 2023 06:32:03 -0700 Subject: [PATCH 09/23] post endpoint moved to hono --- backend/src/worker.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/backend/src/worker.js b/backend/src/worker.js index 92c6647..70de06e 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -57,4 +57,28 @@ app.get('/posts', async c => { }) +app.get('/posts/:slug', async c => { + const { slug } = c.req.param() + + const postBlob = await c.env.BLOG_BUCKET.get('posts.json'); + + if (postBlob === null) { + return new Response('Object Not Found', { status: 404 }); + } + + return postBlob.json() + .then(posts => posts[slug]) + .then(post => { + return new Response(JSON.stringify({ post: post }, null, 4), { + status: 200, + headers: { + ...corsHeaders, + 'etag': postBlob.httpEtag, + 'Content-type': 'application/json' + } + }); + }) +}) + + export default app From 88af66fe199adfb9e0f45e3864e6f104f6e94983 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Fri, 15 Sep 2023 06:57:07 -0700 Subject: [PATCH 10/23] move create/update drip endpoint to hono --- backend/src/worker.js | 29 ++++++++++++++++ backend/src/worker.js.bak | 72 --------------------------------------- 2 files changed, 29 insertions(+), 72 deletions(-) diff --git a/backend/src/worker.js b/backend/src/worker.js index 70de06e..f77be8f 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -1,4 +1,6 @@ import { Hono } from 'hono' +import { validator } from 'hono/validator' +import { bearerAuth } from 'hono/bearer-auth' const app = new Hono() @@ -9,6 +11,8 @@ const corsHeaders = { 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept' } +const token = "THIS_IS_A_SECRET" + app.get('/rss.xml', async c => { const rssBlob = await c.env.BLOG_BUCKET.get('rss.xml'); @@ -81,4 +85,29 @@ app.get('/posts/:slug', async c => { }) +app.post( + '/drip', + bearerAuth({ token }), + validator('json', (value, c) => { + if (!("message" in value)) return c.text('Invalid body', 400) + + return value + }), + async c => { + const body = await c.req.json() + + const action = 'id' in body ? 'update drip' : 'create drip' + const response = `POST called on /drip. Action: ${action}` + + return c.text( + JSON.stringify({ + message: response, + data: body + }, null, 2), + 200 + ) + } +) + + export default app diff --git a/backend/src/worker.js.bak b/backend/src/worker.js.bak index 8cb67c4..f814ecb 100644 --- a/backend/src/worker.js.bak +++ b/backend/src/worker.js.bak @@ -15,78 +15,6 @@ const corsHeaders = { } - -const getRSS = async (key, env) => { - console.log(`RSS match found`) - const rssBlob = await env.BLOG_BUCKET.get(key); - - if (rssBlob === null) { - return new Response('Object Not Found', { status: 404 }); - } - - return new Response(rssBlob.body, { status: 200, headers: { - ...corsHeaders, - 'etag': rssBlob.httpEtag, - 'Content-type': 'application/xml' - }}); -}; - - -const getPostList = async (env) => { - const postBlob = await env.BLOG_BUCKET.get('posts.json'); - - if (postBlob === null) { - return new Response('Object Not Found', { status: 404 }); - } - - return postBlob.json() - .then(posts => ( - Object.entries(posts) - .map(([slug, post]) => { - return { - slug: post.slug, - published: post.published, - title: post.title - } - }) - )) - .then(postList => { - return new Response(JSON.stringify({ postList: postList }, null, 4), { - status: 200, - headers: { - ...corsHeaders, - 'etag': postBlob.httpEtag, - 'Content-type': 'application/json' - } - }); - }) -} - - -const getPost = async (key, env) => { - const postBlob = await env.BLOG_BUCKET.get('posts.json'); - - if (postBlob === null) { - return new Response('Object Not Found', { status: 404 }); - } - - const postSlug = key.split("/")[1]; - - return postBlob.json() - .then(posts => posts[postSlug]) - .then(post => { - return new Response(JSON.stringify({ post: post }, null, 4), { - status: 200, - headers: { - ...corsHeaders, - 'etag': postBlob.httpEtag, - 'Content-type': 'application/json' - } - }); - }) -} - - const validateHeaders = headers => { const expectedHeaders = [ {"name": "X-Custom-PSK", "value": "THIS_IS_A_SECRET", "errorMessage": "Not Authorized", "status": 403}, From c79ff1705f10c7c8c9b1c84a280a9f1a0c3ca38c Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Fri, 15 Sep 2023 07:01:35 -0700 Subject: [PATCH 11/23] Add header validation to post drip endpoint --- backend/src/worker.js | 9 +++++- backend/src/worker.js.bak | 64 --------------------------------------- 2 files changed, 8 insertions(+), 65 deletions(-) diff --git a/backend/src/worker.js b/backend/src/worker.js index f77be8f..2425ff2 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -88,13 +88,20 @@ app.get('/posts/:slug', async c => { app.post( '/drip', bearerAuth({ token }), + validator('header', (value, c) => { + if (!value["content-type"] || value["content-type"] != "application/json") { + return c.text("Invalid headers", 400) + } + return value + }), validator('json', (value, c) => { if (!("message" in value)) return c.text('Invalid body', 400) return value }), async c => { - const body = await c.req.json() + const headers = c.req.valid('header') + const body = c.req.valid('json') const action = 'id' in body ? 'update drip' : 'create drip' const response = `POST called on /drip. Action: ${action}` diff --git a/backend/src/worker.js.bak b/backend/src/worker.js.bak index f814ecb..88bd547 100644 --- a/backend/src/worker.js.bak +++ b/backend/src/worker.js.bak @@ -15,70 +15,6 @@ const corsHeaders = { } -const validateHeaders = headers => { - const expectedHeaders = [ - {"name": "X-Custom-PSK", "value": "THIS_IS_A_SECRET", "errorMessage": "Not Authorized", "status": 403}, - {"name": "content-type", "value": "application/json", "errorMessage": "Please see docs", "status": 400} - ] - - for (const header of expectedHeaders) { - if (headers.get(header.name) != header.value) { - return [false, {message: header.errorMessage, status: header.status}] - } - } - - return [true, {}] -} - - -const validateBody = (body, requiredData) => { - for (const datum of requiredData) { - if (!(datum in body)) { - return [false, {message: `Missing data: ${datum}`, status: 400}] - } - } - - return [true, {}] -} - - -const validateRequest = async (request, requiredData) => { - const [headersAreValid, headersError] = validateHeaders(request.headers) - if (!headersAreValid) return [false, null, headersError] - - try { - const body = await request.json() - console.log(`body: ${JSON.stringify(body, null, 2)}`) - const [bodyIsValid, bodyError] = validateBody(body, requiredData) - if (!bodyIsValid) return [false, null, bodyError] - - return [true, body, {}] - } catch { - return [false, null, {message: "missing request body", status: 400}] - } -} - - -const createOrUpdateDrip = async (request, env) => { - const [requestIsValid, body, error] = await validateRequest(request, ["message"]) - - if (!requestIsValid) { - return new Response(JSON.stringify({message: error.message}, null, 2), {status: error.status}) - } - - const action = "id" in body ? "update drip" : "create drip" - const message = `POST called on /drip. Action: ${action}` - - return new Response( - JSON.stringify({ - message: message, - data: body - }, null, 2), - {status: 200} - ); -} - - const readDrip = async (key, env) => { return new Response( JSON.stringify({ From 89d1e9793d4240e37ef4a5803c5b10b1c9456945 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Fri, 15 Sep 2023 07:04:56 -0700 Subject: [PATCH 12/23] Moving get and delete drips to hono (not yet passing tests) --- backend/src/worker.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/backend/src/worker.js b/backend/src/worker.js index 2425ff2..a16fe30 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -117,4 +117,43 @@ app.post( ) +app.get('/drip', async c => { + return c.text( + JSON.stringify({ + message: `GET called on /drip` + }, null, 2), + 200 + ); + +}) + + +app.delete( + '/drip', + bearerAuth({ token }), + validator('header', (value, c) => { + if (!value["content-type"] || value["content-type"] != "application/json") { + return c.text("Invalid headers", 400) + } + return value + }), + validator('json', (value, c) => { + if (!("message" in value)) return c.text('Invalid body', 400) + + return value + }), + async c => { + const headers = c.req.valid('header') + const body = c.req.valid('json') + + return c.text( + JSON.stringify({ + message: 'DELETE called on /drip', + data: body + }, null, 2), + 200 + ) + } +) + export default app From 03674ebb1faa17864fb9df45021495fed855cc5e Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Sat, 16 Sep 2023 05:51:34 -0700 Subject: [PATCH 13/23] Tests updated --- backend/test/script.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/backend/test/script.js b/backend/test/script.js index 6ec8b71..fbf54f0 100644 --- a/backend/test/script.js +++ b/backend/test/script.js @@ -12,6 +12,8 @@ export const options = { } */ +const SLEEP_TIME = 0.5 + //let shortenLink //const BASE_URL = "https://blog-dev.flinnlab.workers.dev" @@ -28,7 +30,7 @@ export default function () { 'verify RSS feed': (r) => r.body.includes('flinnlab.com') }) - sleep(1) // second + sleep(SLEEP_TIME) // second }) @@ -42,7 +44,7 @@ export default function () { 'postsPage: is not empty': (r) => r.json()['postList'].length != 0 }) - sleep(1) // second + sleep(SLEEP_TIME) // second }) group('post', function () { @@ -60,7 +62,7 @@ export default function () { } }) - sleep(1) // second + sleep(SLEEP_TIME) // second }) @@ -73,7 +75,7 @@ export default function () { 'GET: verify body': (r) => r.body.includes('GET called on /drip') }) - sleep(1) // second + sleep(SLEEP_TIME) // second }) // GROUP: drip @@ -81,11 +83,11 @@ export default function () { const res = http.post(`${BASE_URL}/drip`) check(res, { - 'POST - auth: status is 200': (r) => r.status === 403, - 'POST - auth: verify body': (r) => r.body.includes('Not Authorized') + 'POST - auth: status is 403': (r) => r.status === 401, + 'POST - auth: verify body': (r) => r.body.includes('Unauthorized') }) - sleep(1) // second + sleep(SLEEP_TIME) // second }) // GROUP: drip @@ -96,7 +98,7 @@ export default function () { const params = { headers: { - 'X-Custom-PSK': __ENV.PSK, + 'Authorization': `Bearer ${__ENV.PSK}`, 'Content-Type': 'application/json', }, }; @@ -107,7 +109,7 @@ export default function () { 'POST - create: verify body': (r) => r.body.includes('create drip') }) - sleep(1) // second + sleep(SLEEP_TIME) // second }) group('drip', function () { @@ -118,7 +120,7 @@ export default function () { const params = { headers: { - 'X-Custom-PSK': __ENV.PSK, + 'Authorization': `Bearer ${__ENV.PSK}`, 'Content-Type': 'application/json', }, }; @@ -129,6 +131,6 @@ export default function () { 'POST - update: verify body': (r) => r.body.includes('update drip') }) - sleep(1) // second + sleep(SLEEP_TIME) // second }) } From 85118db3fe5cab2cd4906bc862e1eec88b89c2ae Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Sat, 16 Sep 2023 06:48:24 -0700 Subject: [PATCH 14/23] Initial commit for the connecting to the databases --- backend/src/worker.js | 76 +++++++++++++++++------------------ backend/src/worker.js.bak | 84 --------------------------------------- backend/test/script.js | 4 +- backend/wrangler.toml | 4 ++ 4 files changed, 43 insertions(+), 125 deletions(-) delete mode 100644 backend/src/worker.js.bak diff --git a/backend/src/worker.js b/backend/src/worker.js index a16fe30..233b6ad 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -1,15 +1,15 @@ import { Hono } from 'hono' import { validator } from 'hono/validator' import { bearerAuth } from 'hono/bearer-auth' +import { cors } from 'hono/cors' +import { etag } from 'hono/etag' const app = new Hono() -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, DELETE', - 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept' -} +app.use('/*', cors()); +app.use('/*', etag()); + const token = "THIS_IS_A_SECRET" @@ -17,16 +17,11 @@ const token = "THIS_IS_A_SECRET" app.get('/rss.xml', async c => { const rssBlob = await c.env.BLOG_BUCKET.get('rss.xml'); - if (rssBlob === null) { - return new Response('Object Not Found', { status: 404 }); - } - - return new Response(rssBlob.body, { status: 200, headers: { - ...corsHeaders, - 'etag': rssBlob.httpEtag, - 'Content-type': 'application/xml' - }}); + if (rssBlob === null) return c.text('Object not found', 404) + c.header('content-type', 'application/xml') + c.header('etag', rssBlob.httpEtag) + return c.text(rssBlob.body, 200) }) @@ -49,14 +44,9 @@ app.get('/posts', async c => { }) )) .then(postList => { - return new Response(JSON.stringify({ postList: postList }, null, 4), { - status: 200, - headers: { - ...corsHeaders, - 'etag': postBlob.httpEtag, - 'Content-type': 'application/json' - } - }); + c.header('content-type', 'application/json') + c.header('etag', postBlob.httpEtag) + return c.text(JSON.stringify({ postList: postList }), 200) }) }) @@ -73,14 +63,9 @@ app.get('/posts/:slug', async c => { return postBlob.json() .then(posts => posts[slug]) .then(post => { - return new Response(JSON.stringify({ post: post }, null, 4), { - status: 200, - headers: { - ...corsHeaders, - 'etag': postBlob.httpEtag, - 'Content-type': 'application/json' - } - }); + c.header('content-type', 'application/json') + c.header('etag', postBlob.httpEtag) + return c.text(JSON.stringify({ post: post}), 200) }) }) @@ -103,21 +88,35 @@ app.post( const headers = c.req.valid('header') const body = c.req.valid('json') - const action = 'id' in body ? 'update drip' : 'create drip' + const action = 'id' in body ? 'update' : 'create' const response = `POST called on /drip. Action: ${action}` - return c.text( - JSON.stringify({ - message: response, - data: body - }, null, 2), - 200 - ) + if (action == "update") { + if (!("message" in body)) return c.text('Invalid body for update', 400) + + const { success } = await c.env.DB_DRIP.prepare(` + update drip set message=? where id=? + `).bind(body['message'], body['id']).run() + + } else { + console.log(`c.env: ${JSON.stringify(c.env, null, 2)}`) + const { success } = await c.env.DB_DRIP.prepare(` + insert into drip (message) values (?) + `).bind(body['message']).run() + } + + if (success) return c.text(JSON.stringify({ message: 'drip created' }, null, 2), 201) + + return c.text(JSON.stringify({ message: 'something went wrong'}, null, 2), 500) } ) app.get('/drip', async c => { + const { success } = await c.env.DB_DRIP.prepare(` + select * from drip LIMIT=1 + `).bind(body['message']).run() + return c.text( JSON.stringify({ message: `GET called on /drip` @@ -127,7 +126,6 @@ app.get('/drip', async c => { }) - app.delete( '/drip', bearerAuth({ token }), diff --git a/backend/src/worker.js.bak b/backend/src/worker.js.bak deleted file mode 100644 index 88bd547..0000000 --- a/backend/src/worker.js.bak +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Welcome to Cloudflare Workers! This is your first worker. - * - * - Run `npm run dev` in your terminal to start a development server - * - Open a browser tab at http://localhost:8787/ to see your worker in action - * - Run `npm run deploy` to publish your worker - * - * Learn more at https://developers.cloudflare.com/workers/ - */ - -const corsHeaders = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, DELETE', - 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept' -} - - -const readDrip = async (key, env) => { - return new Response( - JSON.stringify({ - message: `GET called on /${key}` - }, null, 2), - {status: 200} - ); -} - - -const deleteDrip = async (request, env) => { - const [requestIsValid, body, error] = await validatePostRequest(request) - - if (!requestIsValid) { - return new Response(JSON.stringify({message: error.message}, null, 2), {status: error.status}) - } - - return new Response( - JSON.stringify({ - message: `DELETE called on /drip`, - data: body - }, null, 2), - {status: 200} - ); -} - - -export default { - async fetch(request, env) { - const url = new URL(request.url); - const key = url.pathname.slice(1); - const method = request.method; - - const routeRSS = /rss.xml/, - routePostList = /posts$/, - routePost = /posts\/*/, - routeDrip = /drip$/; - - switch (true) { - case routeRSS.test(key): - return getRSS(key, env); - case routePostList.test(key): - return getPostList(env); - case routePost.test(key): - return getPost(key, env); - case routeDrip.test(key): - switch (method) { - case 'POST': - return createOrUpdateDrip(request, env); - case 'GET': - return readDrip(key, env); - case 'DELETE': - return deleteDrip(request, env); - default: - return new Response(JSON.stringify({message: `Error: ${method} not supported on /${key}`}, null, 2), {status: 404}); - } - default: - return new Response(JSON.stringify({ message: `/${key} not found`}, null, 2), { - status: 404, - headers: { - ...corsHeaders, - 'Content-type': 'application/json' - }, - }); - } - }, -}; diff --git a/backend/test/script.js b/backend/test/script.js index fbf54f0..bc422bb 100644 --- a/backend/test/script.js +++ b/backend/test/script.js @@ -106,7 +106,7 @@ export default function () { const res = http.post(`${BASE_URL}/drip`, payload, params) check(res, { 'POST - create: status is 200': (r) => r.status === 200, - 'POST - create: verify body': (r) => r.body.includes('create drip') + 'POST - create: verify body': (r) => r.body.includes('create') }) sleep(SLEEP_TIME) // second @@ -128,7 +128,7 @@ export default function () { const res = http.post(`${BASE_URL}/drip`, payload, params) check(res, { 'POST - update: status is 200': (r) => r.status === 200, - 'POST - update: verify body': (r) => r.body.includes('update drip') + 'POST - update: verify body': (r) => r.body.includes('update') }) sleep(SLEEP_TIME) // second diff --git a/backend/wrangler.toml b/backend/wrangler.toml index fcc709e..5abadfc 100644 --- a/backend/wrangler.toml +++ b/backend/wrangler.toml @@ -7,6 +7,10 @@ r2_buckets = [ { binding = "BLOG_BUCKET", bucket_name = "flinnlab-blog-dev", preview_bucket_name = "flinnlab-blog-dev"} ] +d1_databases = [ + { binding = "DB_DRIP", database_name = "blog-drip-dev", database_id = "d898dfc0-f641-425c-8c6a-e25da2cf7468", migrations_dir = "../data/drip-db/migrations" } +] + [vars] #FLAG_USE_HEADERS=true From 21d61515c9cba2dd288b6a97f52d69c6cd9d54bc Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Sat, 16 Sep 2023 06:49:08 -0700 Subject: [PATCH 15/23] Adding edda to the main shell.nix file --- shell.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell.nix b/shell.nix index 091aeaa..e343284 100644 --- a/shell.nix +++ b/shell.nix @@ -13,6 +13,6 @@ in pkgs.mkShell { ]; shellHook = '' - export PATH="$PWD/frontend/node_modules/.bin/:$PWD/backend/node_modules/.bin/:$PATH" + export PATH="$PWD/frontend/node_modules/.bin/:$PWD/backend/node_modules/.bin/:$PWD/data/tools/edda/result/bin:$PATH" ''; } From 63be1c8c2092e0ce1e99240682c0e7047bc5e2a4 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Sat, 16 Sep 2023 07:44:09 -0700 Subject: [PATCH 16/23] Drip backend done --- backend/src/worker.js | 51 ++++++++++++------------------------ data/tools/edda/src/utils.py | 2 +- 2 files changed, 18 insertions(+), 35 deletions(-) diff --git a/backend/src/worker.js b/backend/src/worker.js index 233b6ad..dde3f76 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -97,60 +97,43 @@ app.post( const { success } = await c.env.DB_DRIP.prepare(` update drip set message=? where id=? `).bind(body['message'], body['id']).run() - + if (success) return c.text(JSON.stringify({ message: 'drip updated' }, null, 2), 201) } else { - console.log(`c.env: ${JSON.stringify(c.env, null, 2)}`) const { success } = await c.env.DB_DRIP.prepare(` insert into drip (message) values (?) `).bind(body['message']).run() + if (success) return c.text(JSON.stringify({ message: 'drip created' }, null, 2), 201) } - if (success) return c.text(JSON.stringify({ message: 'drip created' }, null, 2), 201) - return c.text(JSON.stringify({ message: 'something went wrong'}, null, 2), 500) } ) app.get('/drip', async c => { - const { success } = await c.env.DB_DRIP.prepare(` - select * from drip LIMIT=1 - `).bind(body['message']).run() + const { success, results } = await c.env.DB_DRIP.prepare(` + select * from drip ORDER BY created_at DESC LIMIT 1 + `).bind().all() + + if (!success) return c.text(JSON.stringify({ message: "something went wrong"}), 400) - return c.text( - JSON.stringify({ - message: `GET called on /drip` - }, null, 2), - 200 - ); + return c.text( JSON.stringify({ data: results }, null, 2), 200); }) + app.delete( - '/drip', + '/drip/:id', bearerAuth({ token }), - validator('header', (value, c) => { - if (!value["content-type"] || value["content-type"] != "application/json") { - return c.text("Invalid headers", 400) - } - return value - }), - validator('json', (value, c) => { - if (!("message" in value)) return c.text('Invalid body', 400) - - return value - }), async c => { - const headers = c.req.valid('header') - const body = c.req.valid('json') + const { id } = c.req.param() + const { success } = await c.env.DB_DRIP.prepare(` + delete from drip where id=? + `).bind(id).run() + + if (success) return c.text(JSON.stringify({ message: `drip deleted` }, null, 2), 201) - return c.text( - JSON.stringify({ - message: 'DELETE called on /drip', - data: body - }, null, 2), - 200 - ) + return c.text('something went wrong', 400) } ) diff --git a/data/tools/edda/src/utils.py b/data/tools/edda/src/utils.py index 641388c..627d721 100644 --- a/data/tools/edda/src/utils.py +++ b/data/tools/edda/src/utils.py @@ -243,7 +243,7 @@ def get_status(env, env_db, migration_table) -> Migrations: results, results_code = execute_sql(sql, env, env_db) if results_code == 1: - return [] + return Migrations('remote', []) return Migrations( "remote", From 09619a5f22c0a4dc86d0d869b449c2c6fd7a1298 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Thu, 21 Sep 2023 07:14:25 -0700 Subject: [PATCH 17/23] initial commit of pulling out the hardcoded development bearer token --- backend/.gitignore | 1 + backend/README.md | 8 ++++++++ backend/src/worker.js | 26 +++++++++++++++++++------- 3 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 backend/README.md diff --git a/backend/.gitignore b/backend/.gitignore index 3c3629e..6ddea64 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1 +1,2 @@ node_modules +.dev.vars diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..d9606d5 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,8 @@ +## Backend + +### Development + +``` +# .dev.vars +TOKEN=${TOKEN} +``` diff --git a/backend/src/worker.js b/backend/src/worker.js index dde3f76..340ac18 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -11,9 +11,6 @@ app.use('/*', cors()); app.use('/*', etag()); -const token = "THIS_IS_A_SECRET" - - app.get('/rss.xml', async c => { const rssBlob = await c.env.BLOG_BUCKET.get('rss.xml'); @@ -59,7 +56,6 @@ app.get('/posts/:slug', async c => { if (postBlob === null) { return new Response('Object Not Found', { status: 404 }); } - return postBlob.json() .then(posts => posts[slug]) .then(post => { @@ -70,9 +66,17 @@ app.get('/posts/:slug', async c => { }) +app.post('/drip', async(c, next) => { + const token = c.env.TOKEN + const auth = bearerAuth({ + token + }) + return auth(c, next) +}) + + app.post( '/drip', - bearerAuth({ token }), validator('header', (value, c) => { if (!value["content-type"] || value["content-type"] != "application/json") { return c.text("Invalid headers", 400) @@ -122,10 +126,18 @@ app.get('/drip', async c => { }) +app.delete('/drip/*', async(c, next) => { + const token = c.env.TOKEN + const auth = bearerAuth({ + token + }) + return auth(c, next) +}) + + app.delete( '/drip/:id', - bearerAuth({ token }), - async c => { + async (c, next) => { const { id } = c.req.param() const { success } = await c.env.DB_DRIP.prepare(` delete from drip where id=? From 477e733e1d85a1a60d9a5fd4b8a97199b39b47eb Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Thu, 21 Sep 2023 15:57:25 -0700 Subject: [PATCH 18/23] fixing tests with the dynamic secret --- backend/src/worker.js | 2 +- backend/test/script.js | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/backend/src/worker.js b/backend/src/worker.js index 340ac18..f75dffc 100644 --- a/backend/src/worker.js +++ b/backend/src/worker.js @@ -137,7 +137,7 @@ app.delete('/drip/*', async(c, next) => { app.delete( '/drip/:id', - async (c, next) => { + async c => { const { id } = c.req.param() const { success } = await c.env.DB_DRIP.prepare(` delete from drip where id=? diff --git a/backend/test/script.js b/backend/test/script.js index bc422bb..417e05e 100644 --- a/backend/test/script.js +++ b/backend/test/script.js @@ -70,15 +70,15 @@ export default function () { group('drip', function () { const res = http.get(`${BASE_URL}/drip`) + const json = res.json() check(res, { 'GET: status is 200': (r) => r.status === 200, - 'GET: verify body': (r) => r.body.includes('GET called on /drip') + 'GET: verify body': (r) => "data" in json }) sleep(SLEEP_TIME) // second }) - // GROUP: drip group('drip', function () { const res = http.post(`${BASE_URL}/drip`) @@ -90,7 +90,6 @@ export default function () { sleep(SLEEP_TIME) // second }) - // GROUP: drip group('drip', function () { const payload = JSON.stringify({ message: 'hello', @@ -105,7 +104,7 @@ export default function () { const res = http.post(`${BASE_URL}/drip`, payload, params) check(res, { - 'POST - create: status is 200': (r) => r.status === 200, + 'POST - create: status is 201': (r) => r.status === 201, 'POST - create: verify body': (r) => r.body.includes('create') }) @@ -127,7 +126,7 @@ export default function () { const res = http.post(`${BASE_URL}/drip`, payload, params) check(res, { - 'POST - update: status is 200': (r) => r.status === 200, + 'POST - update: status is 201': (r) => r.status === 201, 'POST - update: verify body': (r) => r.body.includes('update') }) From dd43f6e83bca0f80289583bcd1d32d98b9f37013 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Thu, 21 Sep 2023 16:13:25 -0700 Subject: [PATCH 19/23] Update testing directions --- backend/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/README.md b/backend/README.md index d9606d5..2ccfb20 100644 --- a/backend/README.md +++ b/backend/README.md @@ -6,3 +6,9 @@ # .dev.vars TOKEN=${TOKEN} ``` + +### Testing + +``` +k6 -e PSK=${TOKEN} test/script.js +``` From d074c8be046b25849abec14aba713155dddc0003 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Thu, 21 Sep 2023 16:38:04 -0700 Subject: [PATCH 20/23] Install dependencies to build/deploy backend --- .github/workflows/ci.yml | 10 ++++++++++ backend/package.json | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c69b376..fdc89e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,6 +84,16 @@ jobs: - name: Checkout uses: actions/checkout@v3.5.0 + - name: Set up Node + uses: actions/setup-node@v3.6.0 + with: + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + node-version: '18' + + - name: Install dependencies + run: npm ci + - name: Deploy backend uses: cloudflare/wrangler-action@2.0.0 with: diff --git a/backend/package.json b/backend/package.json index 0c53723..674e63a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -3,11 +3,11 @@ "version": "0.0.0", "private": true, "scripts": { - "deploy": "wrangler deploy", - "start": "wrangler dev --remote" + "dev": "wrangler dev --remote", + "deploy": "wrangler deploy" }, "devDependencies": { - "wrangler": "^3.6.0" + "wrangler": "^3.7.0" }, "dependencies": { "hono": "^3.6.1" From ad637383717c7027b045ab4609c13d9b9e7e560e Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Thu, 21 Sep 2023 16:39:46 -0700 Subject: [PATCH 21/23] Fix path --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fdc89e0..794c6fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,11 +88,13 @@ jobs: uses: actions/setup-node@v3.6.0 with: cache: 'npm' - cache-dependency-path: '**/package-lock.json' + cache-dependency-path: 'backend/package-lock.json' node-version: '18' - name: Install dependencies - run: npm ci + run: | + cd backend + npm ci - name: Deploy backend uses: cloudflare/wrangler-action@2.0.0 From f38f3746245f64227c39a7bd3c464de170bbf572 Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Thu, 21 Sep 2023 16:55:16 -0700 Subject: [PATCH 22/23] Updating testing framework to support testing both locally and in staging --- .github/workflows/ci.yml | 3 +++ backend/package.json | 1 + backend/test/script.js | 5 +---- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 794c6fc..3e49c32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,5 +106,8 @@ jobs: - name: Run Integration Tests uses: grafana/k6-action@v0.3.0 + env: + ENV: staging + PSK: ${{ secrets.BLOG_ADMIN_TOKEN }} with: filename: backend/test/script.js diff --git a/backend/package.json b/backend/package.json index 674e63a..31188cc 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "dev": "wrangler dev --remote", + "test": "k6 -e ENV=dev -e PSK=${TOKEN} run/script.js", "deploy": "wrangler deploy" }, "devDependencies": { diff --git a/backend/test/script.js b/backend/test/script.js index 417e05e..fb0f653 100644 --- a/backend/test/script.js +++ b/backend/test/script.js @@ -14,10 +14,7 @@ export const options = { const SLEEP_TIME = 0.5 -//let shortenLink - -//const BASE_URL = "https://blog-dev.flinnlab.workers.dev" -const BASE_URL = "http://localhost:8787" +const BASE_URL = __ENV.ENV == "staging" ? "https://blog-dev.flinnlab.workers.dev" : "http://localhost:8787" export default function () { From 61bde00a7384f2da5206ec2e5364b37c8682706c Mon Sep 17 00:00:00 2001 From: Joseph Flinn Date: Thu, 21 Sep 2023 17:02:27 -0700 Subject: [PATCH 23/23] fix the staging backend url --- backend/test/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/test/script.js b/backend/test/script.js index fb0f653..97da66f 100644 --- a/backend/test/script.js +++ b/backend/test/script.js @@ -14,7 +14,7 @@ export const options = { const SLEEP_TIME = 0.5 -const BASE_URL = __ENV.ENV == "staging" ? "https://blog-dev.flinnlab.workers.dev" : "http://localhost:8787" +const BASE_URL = __ENV.ENV == "staging" ? "https://blog-backend-dev.flinnlab.workers.dev" : "http://localhost:8787" export default function () {