Skip to content

Instantly share code, notes, and snippets.

@ngshiheng
Last active February 7, 2022 05:44
Show Gist options
  • Save ngshiheng/4b6e7be3384c9674bc3d934828b019e2 to your computer and use it in GitHub Desktop.
Save ngshiheng/4b6e7be3384c9674bc3d934828b019e2 to your computer and use it in GitHub Desktop.
How To Build a Pastebin Clone for Free https://jerrynsh.com/how-to-build-a-pastebin-clone-for-free/
// /server/src/datasources/paste.js
const { ApolloError } = require('apollo-server-cloudflare')
const moment = require('moment')
/*
Create a new paste in `PASTE_DB`.
Fetch a new `uuid` key from `KEY_DB`.
UUID is then removed from `KEY_DB` to avoid duplicates.
*/
async createPaste(content) {
try {
const { keys } = await KEY_DB.list({ limit: 1 })
if (!keys.length) {
throw new ApolloError('Ran out of keys')
}
const { name: uuid } = keys[0]
const createdOn = moment().format()
const expireAt = moment().add(ONE_DAY_FROM_NOW, 'seconds').format()
await KEY_DB.delete(uuid) // Remove key from KGS
await PASTE_DB.put(uuid, content, {
metadata: { createdOn, expireAt },
expirationTtl: ONE_DAY_FROM_NOW,
})
return {
uuid,
content,
createdOn,
expireAt,
}
} catch (error) {
throw new ApolloError(`Failed to create paste. ${error.message}`)
}
}
// /server/src/handlers/paste.js
const { missing } = require('itty-router-extras')
const moment = require('moment')
const handler = async ({ uuid }) => {
const { value: content, metadata } = await PASTE_DB.getWithMetadata(uuid)
if (!content) {
return missing('Invalid paste link')
}
const expiringIn = moment(metadata.expireAt).from(metadata.createdOn)
return new Response(html(content, expiringIn), {
headers: { 'Content-Type': 'text/html' },
})
}
// /kgs/src/index.js
import { MAX_KEYS } from './utils/constants'
import { generateUUIDKey } from './utils/keyGenerator'
/*
Pre-generate a list of unique `uuid`s.
Ensures that pre-generated `uuid` KV list always has `MAX_KEYS` number of keys.
*/
const handleRequest = async () => {
const existingUUIDs = await KEY_DB.list()
let keysToGenerate = MAX_KEYS - existingUUIDs.keys.length
console.log(`Existing # of keys: ${existingUUIDs.keys.length}.`)
console.log(`Estimated # of keys to generate: ${keysToGenerate}.`)
while (keysToGenerate != 0) {
const newKey = await generateUUIDKey()
await KEY_DB.put(newKey, '')
console.log(`Generated new key in KEY_DB: ${newKey}.`)
keysToGenerate--
}
const currentUUIDs = await KEY_DB.list()
console.log(`Current # of keys: ${currentUUIDs.keys.length}.`)
}
addEventListener('scheduled', (event) => {
event.waitUntil(handleRequest(event))
})
// /kgs/src/utils/keyGenerator.js
import { customAlphabet } from 'nanoid'
import { ALPHABET } from './constants'
/*
Generate a `uuid` using `nanoid` package.
Keep retrying until a `uuid` that does not exist in both KV (`PASTE_DB` and `KEY_DB`) is generated.
KGS guarantees that the pre-generated keys are always unique.
*/
export const generateUUIDKey = async () => {
const nanoId = customAlphabet(ALPHABET, 8)
let uuid = nanoId()
while (
(await KEY_DB.get(uuid)) !== null &&
(await PASTE_DB.get(uuid)) !== null
) {
uuid = nanoId()
}
return uuid
}
const { ApolloError } = require('apollo-server-cloudflare')
module.exports = {
Query: {
getPaste: async (_source, { uuid }, { dataSources }) => {
return dataSources.pasteAPI.getPaste(uuid)
},
},
Mutation: {
createPaste: async (_source, { content }, { dataSources }) => {
if (!content || /^\\s*$/.test(content)) {
throw new ApolloError('Paste content is empty')
}
return dataSources.pasteAPI.createPaste(content)
},
},
}
// server/src/index.js
const { missing, ThrowableRouter, withParams } = require('itty-router-extras')
const apollo = require('./handlers/apollo')
const index = require('./handlers/index')
const paste = require('./handlers/paste')
const playground = require('./handlers/playground')
const router = ThrowableRouter()
router.get('/', index)
router.all('/graphql', playground)
router.all('/__graphql', apollo)
router.get('/:uuid', withParams, paste)
router.all('*', () => missing('Not found'))
addEventListener('fetch', (event) => {
event.respondWith(router.handle(event.request))
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment