diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7067e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +worker/ +.cargo-ok +package-lock.json +.idea +.env* +local.js +assets/pages.js +assets/partials.js +.wrangler \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..1ca15f0 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,6 @@ +# .husky/pre-commit +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +sh ./.husky/precommit.sh + diff --git a/.husky/precommit.sh b/.husky/precommit.sh new file mode 100644 index 0000000..6eb3dce --- /dev/null +++ b/.husky/precommit.sh @@ -0,0 +1,10 @@ +#!/bin/sh +FILE="./client/wrangler.toml" +if [ -f "$FILE" ]; then + sed -i -e 's/account_id = ".*"/account_id = "**********"/' $FILE + echo "$FILE updated" + git add -A +else + echo "$FILE does not exist." +fi + diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..f220cf9 --- /dev/null +++ b/client/README.md @@ -0,0 +1,25 @@ +## Router + +This template demonstrates using the [`itty-router`](https://github.com/kwhitley/itty-router) package to add routing to your Cloudflare Workers. + +[`index.js`](https://github.com/cloudflare/worker-template-router/blob/master/index.js) is the content of the Workers script. + +#### Wrangler + +You can use [wrangler](https://github.com/cloudflare/wrangler) to generate a new Cloudflare Workers project based on this template by running the following command from your terminal: + +``` +wrangler generate myapp https://github.com/cloudflare/worker-template-router +``` + +Before publishing your code you need to edit `wrangler.toml` file and add your Cloudflare `account_id` - more information about configuring and publishing your code can be found [in the documentation](https://developers.cloudflare.com/workers/learning/getting-started#7-configure-your-project-for-deployment). + +Once you are ready, you can publish your code by running the following command: + +``` +wrangler publish +``` + +[wrangler config](https://developers.cloudflare.com/workers/wrangler/configuration/) +[handlebars](https://marnixkok.nl/news/blog/handlebars-templates-in-cloudflare-workers) +[serverless rendering](https://blog.cloudflare.com/serverless-rendering-with-cloudflare-workers) \ No newline at end of file diff --git a/client/assets/pages.js b/client/assets/pages.js new file mode 100644 index 0000000..8fb7b8e --- /dev/null +++ b/client/assets/pages.js @@ -0,0 +1,24 @@ +import Handlebars from 'handlebars/runtime.js'; + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['body'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return "\r\n\r\n" + + ((stack1 = container.invokePartial(lookupProperty(partials,"structure/htmlhead"),depth0,{"name":"structure/htmlhead","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") + + "\r\n Cloudflare Workers with Handlebars\r\n\r\n\r\n\r\n" + + ((stack1 = container.invokePartial(lookupProperty(partials,"structure/header"),depth0,{"name":"structure/header","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") + + "\r\n
\r\n
\r\n
\r\n\r\n
\r\n
\r\n" + + ((stack1 = container.invokePartial(lookupProperty(partials,"logos/cloudflare"),depth0,{"name":"logos/cloudflare","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") + + "
\r\n
\r\n\r\n

This template is rendered by Handlebars in a Cloudflare Worker

\r\n\r\n

\r\n Your name: " + + alias3(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === "function" ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":24,"column":44},"end":{"line":24,"column":52}}}) : helper))) + + "\r\n

\r\n\r\n
"
+    + alias3((lookupProperty(helpers,"asyncTest")||(depth0 && lookupProperty(depth0,"asyncTest"))||alias2).call(alias1,{"name":"asyncTest","hash":{"age":38,"name":"Vic Tester"},"data":data,"loc":{"start":{"line":27,"column":17},"end":{"line":27,"column":55}}}))
+    + "
\r\n\r\n
\r\n
\r\n
\r\n\r\n" + + ((stack1 = container.invokePartial(lookupProperty(partials,"structure/footer"),depth0,{"name":"structure/footer","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") + + "\r\n"; +},"usePartial":true,"useData":true}); diff --git a/client/assets/partials.js b/client/assets/partials.js new file mode 100644 index 0000000..16b8393 --- /dev/null +++ b/client/assets/partials.js @@ -0,0 +1,20 @@ +import Handlebars from 'handlebars/runtime.js'; + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +Handlebars.partials['logos/cloudflare'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) { + return "\r\n\r\n \r\n \r\n \r\n Cloudflare logo\r\n \r\n \r\n \r\n \r\n"; +},"useData":true}); +Handlebars.partials['logos/handlebars'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) { + return ""; +},"useData":true}); +Handlebars.partials['structure/footer'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) { + return ""; +},"useData":true}); +Handlebars.partials['structure/footerlibs'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) { + return ""; +},"useData":true}); +Handlebars.partials['structure/header'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) { + return "\r\n
"; +},"useData":true}); +Handlebars.partials['structure/htmlhead'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) { + return ""; +},"useData":true}); diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..1acdd43 --- /dev/null +++ b/client/package.json @@ -0,0 +1,29 @@ +{ + "name": "cf-worker-router-tech-tools", + "private": true, + "version": "1.0.0", + "description": "tech tools hosted on cloudflare worker", + "main": "index.js", + "scripts": { + "build": "npm run compilehbs && npm run transpilehbs && rm src/*-original.js", + "compilehbs": "handlebars -e hbs -f src/pages-original.js src/views/pages/ && handlebars -e hbs -p -f src/partials-original.js src/views/partials/", + "deploy": "wrangler deploy src/index.js", + "prepare": "husky", + "start": "wrangler dev src/index.js", + "test": "echo \"Error: no test specified\" && exit 1", + "transpilehbs": "hbs-import-transpile src/pages-original.js > assets/pages.js && hbs-import-transpile src/partials-original.js > assets/partials.js" + }, + "type": "module", + "author": "", + "license": "ISC", + "devDependencies": { + "hbs-import-transpile": "^1.0.4" + }, + "dependencies": { + "buffer": "^6.0.3", + "handlebars": "^4.7.8", + "hbs-async-render": "^1.0.1", + "itty-router": "^2.6.6", + "serverless-cloudflare-workers": "^1.2.0" + } +} diff --git a/client/src/index.js b/client/src/index.js new file mode 100644 index 0000000..fda9fb6 --- /dev/null +++ b/client/src/index.js @@ -0,0 +1,29 @@ +import { Router } from 'itty-router' +import { base64Handler, postHandler, rootHandler } from './routers' +import { registerHBHelper } from './utils/hbsAsyncHelper.js' +import '../assets/pages.js'; +import '../assets/partials.js'; + +// Register HB +registerHBHelper(); + +// Create a new router +const router = Router(); +router.get("/", rootHandler); +router.get("/base64/:text", base64Handler); +router.post("/post", postHandler); + +/** + * This is the last route we define, it will match anything that hasn't hit a route we've defined + * above, therefore it's useful as a 404 (and avoids us hitting worker exceptions, so make sure + * to include it!). +*/ +router.all("*", () => new Response("404, not found!", { status: 404 })); + +/** + * This snippet ties our worker to the router we defined above, all incoming requests + * are passed to the router where your routes are called and the response is sent. +*/ +addEventListener('fetch', (e) => { + e.respondWith(router.handle(e.request)) +}); diff --git a/client/src/routers/encoding/base64/router.js b/client/src/routers/encoding/base64/router.js new file mode 100644 index 0000000..d056ef5 --- /dev/null +++ b/client/src/routers/encoding/base64/router.js @@ -0,0 +1,19 @@ +import { Buffer } from 'buffer'; + +export const base64Handler = ({ params }) => { + // Decode text like "Hello%20world" into "Hello world" + let input = decodeURIComponent(params.text) + + // Construct a buffer from our input + let buffer = Buffer.from(input, "utf8") + + // Serialise the buffer into a base64 string + let base64 = buffer.toString("base64") + + // Return the HTML with the string to the client + return new Response(`

Base64 encoding: ${base64}

`, { + headers: { + "Content-Type": "text/html" + } + }) +}; \ No newline at end of file diff --git a/client/src/routers/index.js b/client/src/routers/index.js new file mode 100644 index 0000000..03606a1 --- /dev/null +++ b/client/src/routers/index.js @@ -0,0 +1,9 @@ +import { base64Handler } from './encoding/base64/router.js'; +import { postHandler } from './post/router.js'; +import { rootHandler } from './root/router.js'; + +export { + base64Handler, + postHandler, + rootHandler +} \ No newline at end of file diff --git a/client/src/routers/post/router.js b/client/src/routers/post/router.js new file mode 100644 index 0000000..52cc4bd --- /dev/null +++ b/client/src/routers/post/router.js @@ -0,0 +1,30 @@ +/* +This shows a different HTTP method, a POST. + +Try send a POST request using curl or another tool. + +Try the below curl command to send JSON: + +$ curl -X POST -H "Content-Type: application/json" -d '{"abc": "def"}' +*/ +export const postHandler = async request => { + // Create a base object with some fields. + let fields = { + "asn": request.cf.asn, + "colo": request.cf.colo + } + + // If the POST data is JSON then attach it to our response. + if (request.headers.get("Content-Type") === "application/json") { + fields["json"] = await request.json() + } + + // Serialise the JSON to a string. + const returnData = JSON.stringify(fields, null, 2); + + return new Response(returnData, { + headers: { + "Content-Type": "application/json" + } + }) +}; \ No newline at end of file diff --git a/client/src/routers/root/html.js b/client/src/routers/root/html.js new file mode 100644 index 0000000..385e715 --- /dev/null +++ b/client/src/routers/root/html.js @@ -0,0 +1,6 @@ +export const html = ` + + +

Hello, world! This is the root page of your Worker template.

+ `; \ No newline at end of file diff --git a/client/src/routers/root/router.js b/client/src/routers/root/router.js new file mode 100644 index 0000000..5d6d40c --- /dev/null +++ b/client/src/routers/root/router.js @@ -0,0 +1,14 @@ +import Handlebars from 'handlebars/runtime.js'; + +import { hbsAsyncRender } from 'hbs-async-render' +// import { html } from './html'; + +export const rootHandler = async () => { + const output = await hbsAsyncRender(Handlebars, 'body', {name: "Victor E."}); + return new Response(output, {headers: {'Content-Type': 'text/html'}}); + // return new Response(html, { + // headers: { + // "Content-Type": "text/html" + // } + // }) +}; \ No newline at end of file diff --git a/client/src/utils/hbsAsyncHelper.js b/client/src/utils/hbsAsyncHelper.js new file mode 100644 index 0000000..716d52e --- /dev/null +++ b/client/src/utils/hbsAsyncHelper.js @@ -0,0 +1,20 @@ +import Handlebars from 'handlebars/runtime.js'; + +import { registerAsyncHelper } from 'hbs-async-render' + +/** + * Register an asynchronous helper that waits for a second and then resolves with some information + * that is going to be rendered in the place where `asyncTest` has been used in the Handlebar templates. + */ +export const registerHBHelper = () => registerAsyncHelper(Handlebars,'asyncTest', function (options, context) { + + return new Promise((resolve, reject) => { + setTimeout( + function() { + resolve(`Async render with params: ${options.hash.name} || ${options.hash.age}`) + }, + 200 + ); + }); + +}) diff --git a/client/src/views/pages/body.hbs b/client/src/views/pages/body.hbs new file mode 100644 index 0000000..b67348b --- /dev/null +++ b/client/src/views/pages/body.hbs @@ -0,0 +1,35 @@ + + + {{> structure/htmlhead }} + + Cloudflare Workers with Handlebars + + + +{{> structure/header }} + +
+
+
+ +
+
+ {{> logos/cloudflare }} +
+
+ +

This template is rendered by Handlebars in a Cloudflare Worker

+ +

+ Your name: {{name}} +

+ +
{{asyncTest name='Vic Tester' age=38}}
+ +
+
+
+ +{{> structure/footer }} + + \ No newline at end of file diff --git a/client/src/views/partials/logos/cloudflare.hbs b/client/src/views/partials/logos/cloudflare.hbs new file mode 100644 index 0000000..4df92a9 --- /dev/null +++ b/client/src/views/partials/logos/cloudflare.hbs @@ -0,0 +1,27 @@ + + + + + + Cloudflare logo + + + + + \ No newline at end of file diff --git a/client/src/views/partials/logos/handlebars.hbs b/client/src/views/partials/logos/handlebars.hbs new file mode 100644 index 0000000..e69de29 diff --git a/client/src/views/partials/structure/footer.hbs b/client/src/views/partials/structure/footer.hbs new file mode 100644 index 0000000..a5ccfb3 --- /dev/null +++ b/client/src/views/partials/structure/footer.hbs @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/client/src/views/partials/structure/footerlibs.hbs b/client/src/views/partials/structure/footerlibs.hbs new file mode 100644 index 0000000..e4fd0f6 --- /dev/null +++ b/client/src/views/partials/structure/footerlibs.hbs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/views/partials/structure/header.hbs b/client/src/views/partials/structure/header.hbs new file mode 100644 index 0000000..1fed4d2 --- /dev/null +++ b/client/src/views/partials/structure/header.hbs @@ -0,0 +1,6 @@ + +
\ No newline at end of file diff --git a/client/src/views/partials/structure/htmlhead.hbs b/client/src/views/partials/structure/htmlhead.hbs new file mode 100644 index 0000000..2cbf7c8 --- /dev/null +++ b/client/src/views/partials/structure/htmlhead.hbs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/wrangler.toml b/client/wrangler.toml new file mode 100644 index 0000000..1724e7e --- /dev/null +++ b/client/wrangler.toml @@ -0,0 +1,8 @@ +name = "tech" +account_id = "**********" +workers_dev = true +compatibility_date = "2024-07-11" + +[build] +command="npm run build" +watch_dir="views/" \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6217734 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "serverless-microapp-cf-lambda", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "npm run test-client && npm run test-server", + "test-client": "cd client && npm run test", + "test-server": "cd server && npm run test", + "install": "npm run install-client && npm run install-server", + "install-client": "cd client && npm i", + "install-server": "cd server && npm i", + "develop": "npm run start-server && npm run start-client", + "start-client": "cd client && npm run dev", + "start-server": "cd server && npm run dev" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "husky": "^9.0.11" + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..b25c8ae --- /dev/null +++ b/server/package.json @@ -0,0 +1,12 @@ +{ + "name": "server", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +}