From 44036f40927b87abda28c40c3209d34ff057e3c5 Mon Sep 17 00:00:00 2001 From: Joe Ma Date: Wed, 1 Jun 2022 02:11:52 +0800 Subject: [PATCH] Initial commit --- .gitignore | 191 +++++++++++++++++++++++++++++++++++++++ package.json | 13 +++ src/index.ts | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 105 ++++++++++++++++++++++ wrangler.toml | 11 +++ 5 files changed, 565 insertions(+) create mode 100644 .gitignore create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 tsconfig.json create mode 100644 wrangler.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9452459 --- /dev/null +++ b/.gitignore @@ -0,0 +1,191 @@ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-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 + +# 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 variables file +.env +.env.test + +# 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 + +# 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/package.json b/package.json new file mode 100644 index 0000000..8ca6d90 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "paste", + "version": "1.0.0", + "dependencies": { + "aws4fetch": "^1.0.13", + "nanoid": "^3.3.4" + }, + "devDependencies": { + "@cloudflare/workers-types": "^3.11.0", + "typescript": "^4.7.2", + "wrangler": "^2.0.7" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f356f85 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,245 @@ +import {AwsClient} from "aws4fetch"; +import { customAlphabet } from 'nanoid' + +// Constants +const SERVICE_URL = "https://paste.nekoul.com" + +export interface Env { + PASTE_INDEX: KVNamespace; + AWS_ACCESS_KEY_ID: string; + AWS_SECRET_ACCESS_KEY: string; + ENDPOINT: string +} + +const API_DOCS = + `Paste service https://paste.nekoul.com + +[API Draft] +GET / Fetch the HTML for uploading text/file [ ] +GET / Fetch the paste by uuid [x] +GET // Fetch the paste (code) in rendered HTML with syntax highlighting [ ] +GET //settings Fetch the paste information [x] +GET /status Fetch service information [x] +PUT / Create new paste [x] +POST / Update the paste by uuid [x] +DELETE / Delete paste by uuid [x] +POST //settings Update paste setting, i.e., passcode and valid time [ ] + +[ ] indicated not implemented + +Limitation +* Max. 10MB file size upload (Max. 100MB body size for free Cloudflare plan) + +Last update on 30 May. +`; + +const gen_id = customAlphabet( + "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 8); + +export default { + async fetch( + request: Request, + env: Env, + ctx: ExecutionContext + ): Promise { + const {url, method, headers} = request; + const {hostname, pathname, searchParams} = new URL(url); + const path = pathname.replace(/\/+$/, "") || "/" + const s3 = new AwsClient({ + accessKeyId: env.AWS_ACCESS_KEY_ID, + secretAccessKey: env.AWS_SECRET_ACCESS_KEY + }); + + // if (hostname !== SERVICE_URL) { + // // Invalid case + // return new Response(null, { status: 403 }) + // } + + if (path === "/") { + switch (method) { + // Fetch the HTML for uploading text/file + case "GET": + return new Response(API_DOCS); + + // Create new paste + case "PUT": + let uuid = gen_id(); + let buffer = await request.arrayBuffer(); + // Check request.body size <= 10MB + if (buffer.byteLength > 10485760) { + return new Response("File size must be under 10MB.\n"); + } + + let res = await s3.fetch(`${env.ENDPOINT}/${uuid}`, { + method: "PUT", + body: buffer + }); + + if (res.ok) { + // Upload success + let descriptor: PasteIndexEntry = { + title: headers.get("title") || undefined, + last_modified: Date.now(), + password: undefined, + editable: undefined // Default: true + }; + + let counter = await env.PASTE_INDEX.get("__count__") || "0"; + await env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor)); + await env.PASTE_INDEX.put("__count__", (Number(counter) + 1).toString()); + return new Response(get_paste_info(uuid, descriptor)); + } else { + return new Response("Unable to upload the paste.\n", { + status: 500 + }); + } + + } + + } else if (path.length >= 9) { + // RegExpr to match //