Last active
February 7, 2022 05:44
-
-
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/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// /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}`) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// /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' }, | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// /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)) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// /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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
}, | |
}, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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