diff --git a/.gitignore b/.gitignore index 6704566..48ec7bf 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,133 @@ dist # TernJS port file .tern-port +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/worker/.eslintrc.json b/worker/.eslintrc.json new file mode 100644 index 0000000..62a4635 --- /dev/null +++ b/worker/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "env": { + "browser": true, + "es2021": true, + "serviceworker": true + }, + "overrides": [], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": {} +} diff --git a/worker/.gitignore b/worker/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/worker/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/worker/functions/create-repo.js b/worker/functions/create-repo.js new file mode 100644 index 0000000..37d5eee --- /dev/null +++ b/worker/functions/create-repo.js @@ -0,0 +1,20 @@ +const generateUniqueName = (username) => { + return `${username}-photohub-${new Date(Date.now()).toLocaleDateString( + "en-CA" + )}`; +}; + +const createRepo = async (octokit, username, repoName) => { + try { + await octokit.rest.repos.createForAuthenticatedUser({ + name: repoName ? repoName : generateUniqueName(username), + description: "Your repository generated using my-photohub", + private: false, + }); + return true; + } catch (err) { + console.error(err); + return false; + } +}; +export default createRepo; diff --git a/worker/functions/get-user.js b/worker/functions/get-user.js new file mode 100644 index 0000000..304cf49 --- /dev/null +++ b/worker/functions/get-user.js @@ -0,0 +1,7 @@ +const getUser = async (octokit) => { + const { + data: { login }, + } = await octokit.rest.users.getAuthenticated(); + return login; +}; +export default getUser; diff --git a/worker/functions/handle-post-req.js b/worker/functions/handle-post-req.js new file mode 100644 index 0000000..370f4d5 --- /dev/null +++ b/worker/functions/handle-post-req.js @@ -0,0 +1,25 @@ +import { Octokit } from "@octokit/rest"; +import { StatusCodes } from "http-status-codes"; +import isValidInput from "./validate-input"; +import getUser from "./get-user"; +import createRepo from "./create-repo"; + +const handlePost = async (request) => { + const body = await request.formData(); + const { pat_token, repo_name } = Object.fromEntries(body); + const validInput = isValidInput(pat_token, repo_name); + + if (!validInput) return StatusCodes.BAD_REQUEST; + + const octokit = new Octokit({ auth: pat_token }); + try { + const username = await getUser(octokit); + if (!username) return StatusCodes.UNAUTHORIZED; // authentication error + const repoCreated = await createRepo(octokit, username, repo_name); + if (!repoCreated) return StatusCodes.CONFLICT; // failure + return StatusCodes.OK; + } catch (err) { + console.error(err); + } +}; +export default handlePost; diff --git a/worker/functions/on-req-post.js b/worker/functions/on-req-post.js new file mode 100644 index 0000000..56ae66e --- /dev/null +++ b/worker/functions/on-req-post.js @@ -0,0 +1,6 @@ +import handlePost from "./handle-post-req"; + +export const onRequestPost = async (request) => { + return await handlePost(request); +}; +export default onRequestPost; diff --git a/worker/functions/validate-input.js b/worker/functions/validate-input.js new file mode 100644 index 0000000..1b07627 --- /dev/null +++ b/worker/functions/validate-input.js @@ -0,0 +1,7 @@ +const isValidInput = (token, repoName) => { + const isValidPAT = /^ghp_[A-Za-z-0-9]+$/; + const isValidRepo = /^[A-Z-a-z-0-9\-_.]{0,100}$/; + + return isValidPAT.test(token) && isValidRepo.test(repoName); +}; +export default isValidInput; diff --git a/worker/index.js b/worker/index.js new file mode 100644 index 0000000..ab2d5f2 --- /dev/null +++ b/worker/index.js @@ -0,0 +1,69 @@ +/** + * Welcome to Cloudflare Workers! This is your first worker. + * + * - Run `npx wrangler dev src/index.js` in your terminal to start a development server + * - Open a browser tab at http://localhost:8080/ to see your worker in action + * - Run `npx wrangler publish src/index.js --name my-worker` to publish your worker + * + * Learn more at https://developers.cloudflare.com/workers/ + */ +import { onRequestPost } from "./functions/on-req-post"; +import { StatusCodes } from "http-status-codes"; + +const loginForm = (errorMessage = "") => { + return ` + +
+ + + +MyPhotoHub uses GitHub Personal Access Token (PAT) to authenticate users.
+ + ${errorMessage} + +`; +}; +/** + * rawHtmlResponse returns HTML inputted directly + * into the worker script +// * @param {string} html +// */ +const rawHtmlResponse = (html) => { + const init = { + headers: { + "content-type": "text/html;charset=UTF-8", + }, + }; + return new Response(html, init); +}; + +const handleRequest = async (request) => { + const result = await onRequestPost(request); + if (result === StatusCodes.CONFLICT) { + const error = "ERROR: Repo already exists
"; + return rawHtmlResponse(loginForm(error)); + } + return new Response(result); +}; + +addEventListener("fetch", (event) => { + const { request } = event; + const { url } = request; + // GET request + if (new URL(url).pathname === "/" && request.method === "GET") { + return event.respondWith(rawHtmlResponse(loginForm())); + // POST requests + } else if (request.method === "POST") { + return event.respondWith(handleRequest(request)); + } +}); diff --git a/worker/index.test.js b/worker/index.test.js new file mode 100644 index 0000000..1443745 --- /dev/null +++ b/worker/index.test.js @@ -0,0 +1,25 @@ +const { unstable_dev } = require("wrangler"); + +describe("Worker", () => { + let worker; + + beforeAll(async () => { + worker = await unstable_dev( + "src/index.js", + {}, + { disableExperimentalWarning: true } + ); + }); + + afterAll(async () => { + await worker.stop(); + }); + + it("should return Hello World", async () => { + const resp = await worker.fetch(); + if (resp) { + const text = await resp.text(); + expect(text).toMatchInlineSnapshot(`"Hello World!"`); + } + }); +}); diff --git a/worker/wrangler.toml b/worker/wrangler.toml new file mode 100644 index 0000000..deb69a0 --- /dev/null +++ b/worker/wrangler.toml @@ -0,0 +1,6 @@ +name = "my-photohub" +main = "index.js" +compatibility_date = "2022-11-09" +[dev] +port = 8080 +local_protocol = "http" \ No newline at end of file