From 3b4f75597eb63aa518eb3ca29cd1236755142eaf Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 12 Jul 2023 15:49:33 +0100 Subject: [PATCH 01/12] feat: URL Shortener function --- url-shortener/.gitignore | 130 ++++++++++++++++++++++++ url-shortener/.prettierrc.json | 6 ++ url-shortener/README.md | 44 +++++++++ url-shortener/env.d.ts | 12 +++ url-shortener/package-lock.json | 164 +++++++++++++++++++++++++++++++ url-shortener/package.json | 20 ++++ url-shortener/src/appwrite.js | 106 ++++++++++++++++++++ url-shortener/src/environment.js | 38 +++++++ url-shortener/src/main.js | 44 +++++++++ url-shortener/src/setup.js | 17 ++++ url-shortener/src/utils.js | 21 ++++ 11 files changed, 602 insertions(+) create mode 100644 url-shortener/.gitignore create mode 100644 url-shortener/.prettierrc.json create mode 100644 url-shortener/README.md create mode 100644 url-shortener/env.d.ts create mode 100644 url-shortener/package-lock.json create mode 100644 url-shortener/package.json create mode 100644 url-shortener/src/appwrite.js create mode 100644 url-shortener/src/environment.js create mode 100644 url-shortener/src/main.js create mode 100644 url-shortener/src/setup.js create mode 100644 url-shortener/src/utils.js diff --git a/url-shortener/.gitignore b/url-shortener/.gitignore new file mode 100644 index 00000000..6a7d6d8e --- /dev/null +++ b/url-shortener/.gitignore @@ -0,0 +1,130 @@ +# 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.* \ No newline at end of file diff --git a/url-shortener/.prettierrc.json b/url-shortener/.prettierrc.json new file mode 100644 index 00000000..fa51da29 --- /dev/null +++ b/url-shortener/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/url-shortener/README.md b/url-shortener/README.md new file mode 100644 index 00000000..93518532 --- /dev/null +++ b/url-shortener/README.md @@ -0,0 +1,44 @@ +# URL Shortener Function + +This function allows you to turn long URLs into shortened, more manageable URLs. A unique, short, alphanumeric code is generated for each URL and stored in a database collection. The original URL can be accessed later using the short code. + +## Environment Variables + +To ensure the function operates as intended, ensure the following variables are set: + +- **APPWRITE_API_KEY**: This is your Appwrite project's API key. +- **APPWRITE_ENDPOINT**: This is the endpoint where your Appwrite server is located. +- **APPWRITE_PROJECT_ID**: This refers to the specific ID of your Appwrite project. +- **SHORT_DOMAIN**: This is the base domain used when generating the short URLs. + +Additionally, the function has the following optional variables: + +- **DATABASE_ID**: This is the ID for the database where URLs will be stored. If not provided, it will default to "url-shortener". +- **COLLECTION_ID**: This is the ID for the collection within the database. If not provided, it defaults to "urls". + +## Database Setup + +After the installation step, jf the specified database doesn't exist, the setup script will automatically create it. It will also create a collection within the database, adding a URL attribute to the collection. + +## Usage + +This function supports two types of requests: + +1. **Creating a Short URL** + + - **Request Type:** POST + - **Content Type:** application/json + - **Body:** + - `url`: URL to be shortened + - **Response:** + - On success, the function will respond with the original URL and the shortened URL. + - Example: `{"original": "https://mylongdomain.com/videos/xHduwbGDq?t=5000&c=32i7333", "short": "https://short.domain/abc123"}` + +2. **Redirecting from a Short URL** + + - **Request Type:** GET + - **Path Parameter:** The short URL code + - **Response:** + - On success, the function will redirect the user to the original URL. + - If the code does not exist in the database, a 404 error will be returned. + diff --git a/url-shortener/env.d.ts b/url-shortener/env.d.ts new file mode 100644 index 00000000..1b575249 --- /dev/null +++ b/url-shortener/env.d.ts @@ -0,0 +1,12 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + APPWRITE_ENDPOINT?: string; + APPWRITE_PROJECT_ID?: string; + APPWRITE_API_KEY?: string; + SHORT_DOMAIN?: string; + } + } +} + +export {}; diff --git a/url-shortener/package-lock.json b/url-shortener/package-lock.json new file mode 100644 index 00000000..4e0fdc45 --- /dev/null +++ b/url-shortener/package-lock.json @@ -0,0 +1,164 @@ +{ + "name": "url-shortener", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "url-shortener", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "node-appwrite": "^9.0.0" + }, + "devDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-appwrite": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-9.0.0.tgz", + "integrity": "sha512-iTcHbuaJfr6bP/HFkRVV+FcaumKkbINqZyypQdl+tYxv6Dx0bkB/YKUXGYfTkgP18TLPWQQB++OGQhi98dlo2w==", + "dependencies": { + "axios": "^1.3.6", + "form-data": "^4.0.0" + } + }, + "node_modules/node-appwrite/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prettier": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + } + } +} diff --git a/url-shortener/package.json b/url-shortener/package.json new file mode 100644 index 00000000..b08f09de --- /dev/null +++ b/url-shortener/package.json @@ -0,0 +1,20 @@ +{ + "name": "url-shortener", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "type": "module", + "scripts": { + "format": "prettier --write src/**/*.js", + "setup": "node src/setup.js" + }, + "author": "", + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "node-appwrite": "^9.0.0" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/url-shortener/src/appwrite.js b/url-shortener/src/appwrite.js new file mode 100644 index 00000000..af56cc0a --- /dev/null +++ b/url-shortener/src/appwrite.js @@ -0,0 +1,106 @@ +import { Client, Databases } from 'node-appwrite' +import getEnvironment from './environment' + +/** + * @typedef {Object} URLEntry + * @property {string} url + * + * @typedef {import('node-appwrite').Models.Document & URLEntry} URLEntryDocument + */ + +export default function AppwriteService() { + const { + APPWRITE_ENDPOINT, + APPWRITE_PROJECT_ID, + APPWRITE_API_KEY, + DATABASE_ID, + DATABASE_NAME, + COLLECTION_ID, + COLLECTION_NAME, + } = getEnvironment() + + const client = new Client() + client + .setEndpoint(APPWRITE_ENDPOINT) + .setProject(APPWRITE_PROJECT_ID) + .setKey(APPWRITE_API_KEY) + + const databases = new Databases(client) + + return { + /** + * @param {string} shortCode + * @returns {Promise} + */ + getURLEntry: async function (shortCode) { + try { + const document = /** @type {URLEntryDocument} */ ( + await databases.getDocument(DATABASE_ID, COLLECTION_ID, shortCode) + ) + + return document + } catch (err) { + if (err.code !== 404) throw err + return null + } + }, + + /** + * @param {string} url + * @param {string} shortCode + * @returns {Promise} + */ + createURLEntry: async function (url, shortCode) { + try { + const document = /** @type {URLEntryDocument} */ ( + await databases.createDocument( + DATABASE_ID, + COLLECTION_ID, + shortCode, + { + url, + } + ) + ) + + return document + } catch (err) { + if (err.code !== 409) throw err + return null + } + }, + + /** + * @returns {Promise} + */ + doesURLEntryDatabaseExist: async function () { + try { + await databases.get(DATABASE_ID) + return true + } catch (err) { + if (err.code !== 404) throw err + return false + } + }, + + setupURLEntryDatabase: async function () { + try { + await databases.create(DATABASE_ID, DATABASE_NAME) + await databases.createCollection( + DATABASE_ID, + COLLECTION_ID, + COLLECTION_NAME + ) + await databases.createUrlAttribute( + DATABASE_ID, + COLLECTION_ID, + 'url', + true + ) + } catch (err) { + // If resource already exists, we can ignore the error + if (err.code !== 409) throw err + } + }, + } +} diff --git a/url-shortener/src/environment.js b/url-shortener/src/environment.js new file mode 100644 index 00000000..956583e4 --- /dev/null +++ b/url-shortener/src/environment.js @@ -0,0 +1,38 @@ +import { isValidURL } from './utils' + +export default function getEnvironment() { + return { + APPWRITE_API_KEY: getRequiredEnv('APPWRITE_API_KEY'), + APPWRITE_ENDPOINT: getRequiredUrlEnv('APPWRITE_ENDPOINT'), + APPWRITE_PROJECT_ID: getRequiredEnv('APPWRITE_PROJECT_ID'), + SHORT_DOMAIN: getRequiredEnv('SHORT_DOMAIN'), + DATABASE_ID: process.env.DATABASE_ID ?? 'url-shortener', + DATABASE_NAME: 'URL Shortener', + COLLECTION_ID: process.env.COLLECTION_ID ?? 'urls', + COLLECTION_NAME: 'URLs', + } +} + +/** + * @param {string} key + * @return {string} + */ +function getRequiredEnv(key) { + const value = process.env[key] + if (value === undefined) { + throw new Error(`Environment variable ${key} is not set`) + } + return value +} + +/** + * @param {string} key + * @return {string} + */ +function getRequiredUrlEnv(key) { + const value = getRequiredEnv(key) + if (!isValidURL(value)) { + throw new Error(`Environment variable ${key}=${value} is a not valid URL`) + } + return value +} diff --git a/url-shortener/src/main.js b/url-shortener/src/main.js new file mode 100644 index 00000000..e323ea93 --- /dev/null +++ b/url-shortener/src/main.js @@ -0,0 +1,44 @@ +import AppwriteService from './appwrite' +import getEnvironment from './environment' +import { isValidURL, generateShortCode } from './utils' + +export default async ({ res, req, log, error }) => { + const { SHORT_DOMAIN } = getEnvironment() + const appwrite = AppwriteService() + + if ( + req.method === 'POST' && + req.headers['content-type'] === 'application/json' + ) { + const { url } = req.body + if (!url || !isValidURL(url)) { + error('Invalid url parameter.') + return res.json({ error: 'Invalid url parameter' }, 400) + } + + const shortCode = generateShortCode() + const urlEntry = await appwrite.createURLEntry(url, shortCode) + if (!urlEntry) { + error('Failed to create url entry.') + return res.json({ error: 'Failed to create url entry' }, 500) + } + + return res.json( + { + short: `${SHORT_DOMAIN}/${urlEntry.$id}`, + url: urlEntry.url, + }, + 201 + ) + } + + const shortId = req.path.replace(/^\/|\/$/g, '') + log(`Fetching document from with ID: ${shortId}`) + + const urlEntry = await appwrite.getURLEntry(shortId) + if (!urlEntry) { + return res.send(`Not found.`, 404) + } + + return res.redirect(urlEntry.url, 302) +} diff --git a/url-shortener/src/setup.js b/url-shortener/src/setup.js new file mode 100644 index 00000000..9f472d3c --- /dev/null +++ b/url-shortener/src/setup.js @@ -0,0 +1,17 @@ +import AppwriteService from './appwrite' + +async function setup() { + console.log('Executing setup script...') + + const appwrite = AppwriteService() + + if (await appwrite.doesURLEntryDatabaseExist()) { + console.log(`Database exists.`) + return + } + + await appwrite.setupURLEntryDatabase() + console.log(`Database created.`) +} + +setup() diff --git a/url-shortener/src/utils.js b/url-shortener/src/utils.js new file mode 100644 index 00000000..54292b44 --- /dev/null +++ b/url-shortener/src/utils.js @@ -0,0 +1,21 @@ +import { customAlphabet } from 'nanoid' + +/** + * @param {string | undefined} url + * @returns {boolean} + */ +export function isValidURL(url) { + if (!url) return false + try { + new URL(url) + return true + } catch (err) { + return false + } +} + +const ALPHABET = + '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' +const nanoid = customAlphabet(ALPHABET) + +export const generateShortCode = () => nanoid(6) From a76fead18999d0fd192315c15587f7404e0642e5 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:33:24 +0100 Subject: [PATCH 02/12] fix: esm migration --- url-shortener/src/appwrite.js | 7 ++++--- url-shortener/src/environment.js | 28 +++++++++++++++------------- url-shortener/src/main.js | 12 +++++++----- url-shortener/src/setup.js | 4 +++- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/url-shortener/src/appwrite.js b/url-shortener/src/appwrite.js index af56cc0a..c38362c3 100644 --- a/url-shortener/src/appwrite.js +++ b/url-shortener/src/appwrite.js @@ -1,5 +1,4 @@ import { Client, Databases } from 'node-appwrite' -import getEnvironment from './environment' /** * @typedef {Object} URLEntry @@ -8,7 +7,7 @@ import getEnvironment from './environment' * @typedef {import('node-appwrite').Models.Document & URLEntry} URLEntryDocument */ -export default function AppwriteService() { +function AppwriteService(environment) { const { APPWRITE_ENDPOINT, APPWRITE_PROJECT_ID, @@ -17,7 +16,7 @@ export default function AppwriteService() { DATABASE_NAME, COLLECTION_ID, COLLECTION_NAME, - } = getEnvironment() + } = environment const client = new Client() client @@ -104,3 +103,5 @@ export default function AppwriteService() { }, } } + +export default AppwriteService diff --git a/url-shortener/src/environment.js b/url-shortener/src/environment.js index 956583e4..22299206 100644 --- a/url-shortener/src/environment.js +++ b/url-shortener/src/environment.js @@ -1,18 +1,5 @@ import { isValidURL } from './utils' -export default function getEnvironment() { - return { - APPWRITE_API_KEY: getRequiredEnv('APPWRITE_API_KEY'), - APPWRITE_ENDPOINT: getRequiredUrlEnv('APPWRITE_ENDPOINT'), - APPWRITE_PROJECT_ID: getRequiredEnv('APPWRITE_PROJECT_ID'), - SHORT_DOMAIN: getRequiredEnv('SHORT_DOMAIN'), - DATABASE_ID: process.env.DATABASE_ID ?? 'url-shortener', - DATABASE_NAME: 'URL Shortener', - COLLECTION_ID: process.env.COLLECTION_ID ?? 'urls', - COLLECTION_NAME: 'URLs', - } -} - /** * @param {string} key * @return {string} @@ -36,3 +23,18 @@ function getRequiredUrlEnv(key) { } return value } + +function EnvironmentService() { + return { + APPWRITE_API_KEY: getRequiredEnv('APPWRITE_API_KEY'), + APPWRITE_ENDPOINT: getRequiredUrlEnv('APPWRITE_ENDPOINT'), + APPWRITE_PROJECT_ID: getRequiredEnv('APPWRITE_PROJECT_ID'), + SHORT_DOMAIN: getRequiredEnv('SHORT_DOMAIN'), + DATABASE_ID: process.env.DATABASE_ID ?? 'url-shortener', + DATABASE_NAME: 'URL Shortener', + COLLECTION_ID: process.env.COLLECTION_ID ?? 'urls', + COLLECTION_NAME: 'URLs', + } +} + +export default EnvironmentService diff --git a/url-shortener/src/main.js b/url-shortener/src/main.js index e323ea93..24aa4422 100644 --- a/url-shortener/src/main.js +++ b/url-shortener/src/main.js @@ -1,10 +1,12 @@ -import AppwriteService from './appwrite' -import getEnvironment from './environment' -import { isValidURL, generateShortCode } from './utils' +import AppwriteService from './appwrite.js' +import EnvironmentService from './environment.js' +import { isValidURL, generateShortCode } from './utils.js' export default async ({ res, req, log, error }) => { - const { SHORT_DOMAIN } = getEnvironment() - const appwrite = AppwriteService() + const environment = EnvironmentService() + const appwrite = AppwriteService(environment) + + const { SHORT_DOMAIN } = environment if ( req.method === 'POST' && diff --git a/url-shortener/src/setup.js b/url-shortener/src/setup.js index 9f472d3c..ae1ed29c 100644 --- a/url-shortener/src/setup.js +++ b/url-shortener/src/setup.js @@ -1,8 +1,10 @@ -import AppwriteService from './appwrite' +import AppwriteService from './appwrite.js' +import EnvironmentService from './environment.js' async function setup() { console.log('Executing setup script...') + const environment = EnvironmentService() const appwrite = AppwriteService() if (await appwrite.doesURLEntryDatabaseExist()) { From 5da9688304e578dc77343a6e6c6c6ad92d1833a4 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:07:12 +0100 Subject: [PATCH 03/12] chore: add semis, del pjson extras --- url-shortener/.prettierrc.json | 2 +- url-shortener/package.json | 2 -- url-shortener/src/appwrite.js | 46 ++++++++++++++++---------------- url-shortener/src/environment.js | 18 ++++++------- url-shortener/src/main.js | 40 +++++++++++++-------------- url-shortener/src/setup.js | 20 +++++++------- url-shortener/src/utils.js | 16 +++++------ 7 files changed, 71 insertions(+), 73 deletions(-) diff --git a/url-shortener/.prettierrc.json b/url-shortener/.prettierrc.json index fa51da29..0a725205 100644 --- a/url-shortener/.prettierrc.json +++ b/url-shortener/.prettierrc.json @@ -1,6 +1,6 @@ { "trailingComma": "es5", "tabWidth": 2, - "semi": false, + "semi": true, "singleQuote": true } diff --git a/url-shortener/package.json b/url-shortener/package.json index b08f09de..ec1b6f37 100644 --- a/url-shortener/package.json +++ b/url-shortener/package.json @@ -8,8 +8,6 @@ "format": "prettier --write src/**/*.js", "setup": "node src/setup.js" }, - "author": "", - "license": "MIT", "dependencies": { "nanoid": "^3.3.6", "node-appwrite": "^9.0.0" diff --git a/url-shortener/src/appwrite.js b/url-shortener/src/appwrite.js index c38362c3..a047da28 100644 --- a/url-shortener/src/appwrite.js +++ b/url-shortener/src/appwrite.js @@ -1,4 +1,4 @@ -import { Client, Databases } from 'node-appwrite' +import { Client, Databases } from 'node-appwrite'; /** * @typedef {Object} URLEntry @@ -16,15 +16,15 @@ function AppwriteService(environment) { DATABASE_NAME, COLLECTION_ID, COLLECTION_NAME, - } = environment + } = environment; - const client = new Client() + const client = new Client(); client .setEndpoint(APPWRITE_ENDPOINT) .setProject(APPWRITE_PROJECT_ID) - .setKey(APPWRITE_API_KEY) + .setKey(APPWRITE_API_KEY); - const databases = new Databases(client) + const databases = new Databases(client); return { /** @@ -35,12 +35,12 @@ function AppwriteService(environment) { try { const document = /** @type {URLEntryDocument} */ ( await databases.getDocument(DATABASE_ID, COLLECTION_ID, shortCode) - ) + ); - return document + return document; } catch (err) { - if (err.code !== 404) throw err - return null + if (err.code !== 404) throw err; + return null; } }, @@ -60,12 +60,12 @@ function AppwriteService(environment) { url, } ) - ) + ); - return document + return document; } catch (err) { - if (err.code !== 409) throw err - return null + if (err.code !== 409) throw err; + return null; } }, @@ -74,34 +74,34 @@ function AppwriteService(environment) { */ doesURLEntryDatabaseExist: async function () { try { - await databases.get(DATABASE_ID) - return true + await databases.get(DATABASE_ID); + return true; } catch (err) { - if (err.code !== 404) throw err - return false + if (err.code !== 404) throw err; + return false; } }, setupURLEntryDatabase: async function () { try { - await databases.create(DATABASE_ID, DATABASE_NAME) + await databases.create(DATABASE_ID, DATABASE_NAME); await databases.createCollection( DATABASE_ID, COLLECTION_ID, COLLECTION_NAME - ) + ); await databases.createUrlAttribute( DATABASE_ID, COLLECTION_ID, 'url', true - ) + ); } catch (err) { // If resource already exists, we can ignore the error - if (err.code !== 409) throw err + if (err.code !== 409) throw err; } }, - } + }; } -export default AppwriteService +export default AppwriteService; diff --git a/url-shortener/src/environment.js b/url-shortener/src/environment.js index 22299206..44952f15 100644 --- a/url-shortener/src/environment.js +++ b/url-shortener/src/environment.js @@ -1,15 +1,15 @@ -import { isValidURL } from './utils' +import { isValidURL } from './utils'; /** * @param {string} key * @return {string} */ function getRequiredEnv(key) { - const value = process.env[key] + const value = process.env[key]; if (value === undefined) { - throw new Error(`Environment variable ${key} is not set`) + throw new Error(`Environment variable ${key} is not set`); } - return value + return value; } /** @@ -17,11 +17,11 @@ function getRequiredEnv(key) { * @return {string} */ function getRequiredUrlEnv(key) { - const value = getRequiredEnv(key) + const value = getRequiredEnv(key); if (!isValidURL(value)) { - throw new Error(`Environment variable ${key}=${value} is a not valid URL`) + throw new Error(`Environment variable ${key}=${value} is a not valid URL`); } - return value + return value; } function EnvironmentService() { @@ -34,7 +34,7 @@ function EnvironmentService() { DATABASE_NAME: 'URL Shortener', COLLECTION_ID: process.env.COLLECTION_ID ?? 'urls', COLLECTION_NAME: 'URLs', - } + }; } -export default EnvironmentService +export default EnvironmentService; diff --git a/url-shortener/src/main.js b/url-shortener/src/main.js index 24aa4422..ec0221f8 100644 --- a/url-shortener/src/main.js +++ b/url-shortener/src/main.js @@ -1,28 +1,28 @@ -import AppwriteService from './appwrite.js' -import EnvironmentService from './environment.js' -import { isValidURL, generateShortCode } from './utils.js' +import AppwriteService from './appwrite.js'; +import EnvironmentService from './environment.js'; +import { isValidURL, generateShortCode } from './utils.js'; export default async ({ res, req, log, error }) => { - const environment = EnvironmentService() - const appwrite = AppwriteService(environment) + const environment = EnvironmentService(); + const appwrite = AppwriteService(environment); - const { SHORT_DOMAIN } = environment + const { SHORT_DOMAIN } = environment; if ( req.method === 'POST' && req.headers['content-type'] === 'application/json' ) { - const { url } = req.body + const { url } = req.body; if (!url || !isValidURL(url)) { - error('Invalid url parameter.') - return res.json({ error: 'Invalid url parameter' }, 400) + error('Invalid url parameter.'); + return res.json({ error: 'Invalid url parameter' }, 400); } - const shortCode = generateShortCode() - const urlEntry = await appwrite.createURLEntry(url, shortCode) + const shortCode = generateShortCode(); + const urlEntry = await appwrite.createURLEntry(url, shortCode); if (!urlEntry) { - error('Failed to create url entry.') - return res.json({ error: 'Failed to create url entry' }, 500) + error('Failed to create url entry.'); + return res.json({ error: 'Failed to create url entry' }, 500); } return res.json( @@ -31,16 +31,16 @@ export default async ({ res, req, log, error }) => { url: urlEntry.url, }, 201 - ) + ); } - const shortId = req.path.replace(/^\/|\/$/g, '') - log(`Fetching document from with ID: ${shortId}`) + const shortId = req.path.replace(/^\/|\/$/g, ''); + log(`Fetching document from with ID: ${shortId}`); - const urlEntry = await appwrite.getURLEntry(shortId) + const urlEntry = await appwrite.getURLEntry(shortId); if (!urlEntry) { - return res.send(`Not found.`, 404) + return res.send(`Not found.`, 404); } - return res.redirect(urlEntry.url, 302) -} + return res.redirect(urlEntry.url, 302); +}; diff --git a/url-shortener/src/setup.js b/url-shortener/src/setup.js index ae1ed29c..5dcca4cf 100644 --- a/url-shortener/src/setup.js +++ b/url-shortener/src/setup.js @@ -1,19 +1,19 @@ -import AppwriteService from './appwrite.js' -import EnvironmentService from './environment.js' +import AppwriteService from './appwrite.js'; +import EnvironmentService from './environment.js'; async function setup() { - console.log('Executing setup script...') + console.log('Executing setup script...'); - const environment = EnvironmentService() - const appwrite = AppwriteService() + const environment = EnvironmentService(); + const appwrite = AppwriteService(); if (await appwrite.doesURLEntryDatabaseExist()) { - console.log(`Database exists.`) - return + console.log(`Database exists.`); + return; } - await appwrite.setupURLEntryDatabase() - console.log(`Database created.`) + await appwrite.setupURLEntryDatabase(); + console.log(`Database created.`); } -setup() +setup(); diff --git a/url-shortener/src/utils.js b/url-shortener/src/utils.js index 54292b44..b1c7fb7f 100644 --- a/url-shortener/src/utils.js +++ b/url-shortener/src/utils.js @@ -1,21 +1,21 @@ -import { customAlphabet } from 'nanoid' +import { customAlphabet } from 'nanoid'; /** * @param {string | undefined} url * @returns {boolean} */ export function isValidURL(url) { - if (!url) return false + if (!url) return false; try { - new URL(url) - return true + new URL(url); + return true; } catch (err) { - return false + return false; } } const ALPHABET = - '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' -const nanoid = customAlphabet(ALPHABET) + '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; +const nanoid = customAlphabet(ALPHABET); -export const generateShortCode = () => nanoid(6) +export const generateShortCode = () => nanoid(6); From 182053a99b3ea8aa39e49e56389ba76a2efb7543 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 19 Jul 2023 21:45:11 +0100 Subject: [PATCH 04/12] chore: use classes --- url-shortener/src/appwrite.js | 173 +++++++++++++++---------------- url-shortener/src/environment.js | 20 ++-- url-shortener/src/main.js | 8 +- url-shortener/src/setup.js | 4 +- 4 files changed, 100 insertions(+), 105 deletions(-) diff --git a/url-shortener/src/appwrite.js b/url-shortener/src/appwrite.js index a047da28..c23e2e2c 100644 --- a/url-shortener/src/appwrite.js +++ b/url-shortener/src/appwrite.js @@ -7,101 +7,100 @@ import { Client, Databases } from 'node-appwrite'; * @typedef {import('node-appwrite').Models.Document & URLEntry} URLEntryDocument */ -function AppwriteService(environment) { - const { - APPWRITE_ENDPOINT, - APPWRITE_PROJECT_ID, - APPWRITE_API_KEY, - DATABASE_ID, - DATABASE_NAME, - COLLECTION_ID, - COLLECTION_NAME, - } = environment; +class AppwriteService { + /** + * @param {import('./environment').default} env + */ + constructor(env) { + this.env = env; - const client = new Client(); - client - .setEndpoint(APPWRITE_ENDPOINT) - .setProject(APPWRITE_PROJECT_ID) - .setKey(APPWRITE_API_KEY); + const client = new Client(); + client + .setEndpoint(env.APPWRITE_ENDPOINT) + .setProject(env.APPWRITE_PROJECT_ID) + .setKey(env.APPWRITE_API_KEY); - const databases = new Databases(client); + this.databases = new Databases(client); + } - return { - /** - * @param {string} shortCode - * @returns {Promise} - */ - getURLEntry: async function (shortCode) { - try { - const document = /** @type {URLEntryDocument} */ ( - await databases.getDocument(DATABASE_ID, COLLECTION_ID, shortCode) - ); + /** + * @param {string} shortCode + * @returns {Promise} + */ + async getURLEntry(shortCode) { + try { + const document = /** @type {URLEntryDocument} */ ( + await this.databases.getDocument( + this.env.DATABASE_ID, + this.env.COLLECTION_ID, + shortCode + ) + ); - return document; - } catch (err) { - if (err.code !== 404) throw err; - return null; - } - }, + return document; + } catch (err) { + if (err.code !== 404) throw err; + return null; + } + } - /** - * @param {string} url - * @param {string} shortCode - * @returns {Promise} - */ - createURLEntry: async function (url, shortCode) { - try { - const document = /** @type {URLEntryDocument} */ ( - await databases.createDocument( - DATABASE_ID, - COLLECTION_ID, - shortCode, - { - url, - } - ) - ); + /** + * @param {string} url + * @param {string} shortCode + * @returns {Promise} + */ + async createURLEntry(url, shortCode) { + try { + const document = /** @type {URLEntryDocument} */ ( + await this.databases.createDocument( + this.env.DATABASE_ID, + this.env.COLLECTION_ID, + shortCode, + { + url, + } + ) + ); - return document; - } catch (err) { - if (err.code !== 409) throw err; - return null; - } - }, + return document; + } catch (err) { + if (err.code !== 409) throw err; + return null; + } + } - /** - * @returns {Promise} - */ - doesURLEntryDatabaseExist: async function () { - try { - await databases.get(DATABASE_ID); - return true; - } catch (err) { - if (err.code !== 404) throw err; - return false; - } - }, + /** + * @returns {Promise} + */ + async doesURLEntryDatabaseExist() { + try { + await this.databases.get(this.env.DATABASE_ID); + return true; + } catch (err) { + if (err.code !== 404) throw err; + return false; + } + } - setupURLEntryDatabase: async function () { - try { - await databases.create(DATABASE_ID, DATABASE_NAME); - await databases.createCollection( - DATABASE_ID, - COLLECTION_ID, - COLLECTION_NAME - ); - await databases.createUrlAttribute( - DATABASE_ID, - COLLECTION_ID, - 'url', - true - ); - } catch (err) { - // If resource already exists, we can ignore the error - if (err.code !== 409) throw err; - } - }, - }; + async setupURLEntryDatabase() { + try { + await this.databases.create(this.env.DATABASE_ID, this.env.DATABASE_NAME); + await this.databases.createCollection( + this.env.DATABASE_ID, + this.env.COLLECTION_ID, + this.env.COLLECTION_NAME + ); + await this.databases.createUrlAttribute( + this.env.DATABASE_ID, + this.env.COLLECTION_ID, + 'url', + true + ); + } catch (err) { + // If resource already exists, we can ignore the error + if (err.code !== 409) throw err; + } + } } export default AppwriteService; diff --git a/url-shortener/src/environment.js b/url-shortener/src/environment.js index 44952f15..965cebf3 100644 --- a/url-shortener/src/environment.js +++ b/url-shortener/src/environment.js @@ -24,17 +24,15 @@ function getRequiredUrlEnv(key) { return value; } -function EnvironmentService() { - return { - APPWRITE_API_KEY: getRequiredEnv('APPWRITE_API_KEY'), - APPWRITE_ENDPOINT: getRequiredUrlEnv('APPWRITE_ENDPOINT'), - APPWRITE_PROJECT_ID: getRequiredEnv('APPWRITE_PROJECT_ID'), - SHORT_DOMAIN: getRequiredEnv('SHORT_DOMAIN'), - DATABASE_ID: process.env.DATABASE_ID ?? 'url-shortener', - DATABASE_NAME: 'URL Shortener', - COLLECTION_ID: process.env.COLLECTION_ID ?? 'urls', - COLLECTION_NAME: 'URLs', - }; +class EnvironmentService { + APPWRITE_API_KEY = getRequiredEnv('APPWRITE_API_KEY'); + APPWRITE_ENDPOINT = getRequiredUrlEnv('APPWRITE_ENDPOINT'); + APPWRITE_PROJECT_ID = getRequiredEnv('APPWRITE_PROJECT_ID'); + SHORT_DOMAIN = getRequiredEnv('SHORT_DOMAIN'); + DATABASE_ID = process.env.DATABASE_ID ?? 'url-shortener'; + DATABASE_NAME = 'URL Shortener'; + COLLECTION_ID = process.env.COLLECTION_ID ?? 'urls'; + COLLECTION_NAME = 'URLs'; } export default EnvironmentService; diff --git a/url-shortener/src/main.js b/url-shortener/src/main.js index ec0221f8..3d802436 100644 --- a/url-shortener/src/main.js +++ b/url-shortener/src/main.js @@ -3,10 +3,8 @@ import EnvironmentService from './environment.js'; import { isValidURL, generateShortCode } from './utils.js'; export default async ({ res, req, log, error }) => { - const environment = EnvironmentService(); - const appwrite = AppwriteService(environment); - - const { SHORT_DOMAIN } = environment; + const env = new EnvironmentService(); + const appwrite = new AppwriteService(env); if ( req.method === 'POST' && @@ -27,7 +25,7 @@ export default async ({ res, req, log, error }) => { return res.json( { - short: `${SHORT_DOMAIN}/${urlEntry.$id}`, + short: `${env.SHORT_DOMAIN}/${urlEntry.$id}`, url: urlEntry.url, }, 201 diff --git a/url-shortener/src/setup.js b/url-shortener/src/setup.js index 5dcca4cf..3dff90e6 100644 --- a/url-shortener/src/setup.js +++ b/url-shortener/src/setup.js @@ -4,8 +4,8 @@ import EnvironmentService from './environment.js'; async function setup() { console.log('Executing setup script...'); - const environment = EnvironmentService(); - const appwrite = AppwriteService(); + const env = new EnvironmentService(); + const appwrite = new AppwriteService(env); if (await appwrite.doesURLEntryDatabaseExist()) { console.log(`Database exists.`); From e6500e001a646fff7f1a9eb53f7e8eca984f56a7 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 20 Jul 2023 10:41:22 +0100 Subject: [PATCH 05/12] chore: new structure --- {url-shortener => node/url-shortener}/.gitignore | 0 {url-shortener => node/url-shortener}/.prettierrc.json | 0 {url-shortener => node/url-shortener}/README.md | 0 {url-shortener => node/url-shortener}/env.d.ts | 0 {url-shortener => node/url-shortener}/package-lock.json | 0 {url-shortener => node/url-shortener}/package.json | 0 {url-shortener => node/url-shortener}/src/appwrite.js | 0 {url-shortener => node/url-shortener}/src/environment.js | 0 {url-shortener => node/url-shortener}/src/main.js | 0 {url-shortener => node/url-shortener}/src/setup.js | 0 {url-shortener => node/url-shortener}/src/utils.js | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename {url-shortener => node/url-shortener}/.gitignore (100%) rename {url-shortener => node/url-shortener}/.prettierrc.json (100%) rename {url-shortener => node/url-shortener}/README.md (100%) rename {url-shortener => node/url-shortener}/env.d.ts (100%) rename {url-shortener => node/url-shortener}/package-lock.json (100%) rename {url-shortener => node/url-shortener}/package.json (100%) rename {url-shortener => node/url-shortener}/src/appwrite.js (100%) rename {url-shortener => node/url-shortener}/src/environment.js (100%) rename {url-shortener => node/url-shortener}/src/main.js (100%) rename {url-shortener => node/url-shortener}/src/setup.js (100%) rename {url-shortener => node/url-shortener}/src/utils.js (100%) diff --git a/url-shortener/.gitignore b/node/url-shortener/.gitignore similarity index 100% rename from url-shortener/.gitignore rename to node/url-shortener/.gitignore diff --git a/url-shortener/.prettierrc.json b/node/url-shortener/.prettierrc.json similarity index 100% rename from url-shortener/.prettierrc.json rename to node/url-shortener/.prettierrc.json diff --git a/url-shortener/README.md b/node/url-shortener/README.md similarity index 100% rename from url-shortener/README.md rename to node/url-shortener/README.md diff --git a/url-shortener/env.d.ts b/node/url-shortener/env.d.ts similarity index 100% rename from url-shortener/env.d.ts rename to node/url-shortener/env.d.ts diff --git a/url-shortener/package-lock.json b/node/url-shortener/package-lock.json similarity index 100% rename from url-shortener/package-lock.json rename to node/url-shortener/package-lock.json diff --git a/url-shortener/package.json b/node/url-shortener/package.json similarity index 100% rename from url-shortener/package.json rename to node/url-shortener/package.json diff --git a/url-shortener/src/appwrite.js b/node/url-shortener/src/appwrite.js similarity index 100% rename from url-shortener/src/appwrite.js rename to node/url-shortener/src/appwrite.js diff --git a/url-shortener/src/environment.js b/node/url-shortener/src/environment.js similarity index 100% rename from url-shortener/src/environment.js rename to node/url-shortener/src/environment.js diff --git a/url-shortener/src/main.js b/node/url-shortener/src/main.js similarity index 100% rename from url-shortener/src/main.js rename to node/url-shortener/src/main.js diff --git a/url-shortener/src/setup.js b/node/url-shortener/src/setup.js similarity index 100% rename from url-shortener/src/setup.js rename to node/url-shortener/src/setup.js diff --git a/url-shortener/src/utils.js b/node/url-shortener/src/utils.js similarity index 100% rename from url-shortener/src/utils.js rename to node/url-shortener/src/utils.js From 270ba5ffafce8bc6233786317d340392a392c977 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 20 Jul 2023 12:10:45 +0100 Subject: [PATCH 06/12] chore: prettier script --- node/url-shortener/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/url-shortener/package.json b/node/url-shortener/package.json index ec1b6f37..8a658e67 100644 --- a/node/url-shortener/package.json +++ b/node/url-shortener/package.json @@ -5,7 +5,7 @@ "main": "src/main.js", "type": "module", "scripts": { - "format": "prettier --write src/**/*.js", + "format": "prettier --write .", "setup": "node src/setup.js" }, "dependencies": { From e6fb5d1800dac4849a1fdae9e7ed1beb66dba8a4 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 25 Jul 2023 15:15:30 +0100 Subject: [PATCH 07/12] docs: update to new template --- node/url-shortener/README.md | 131 ++++++++++++++++++++------ node/url-shortener/src/environment.js | 2 +- node/url-shortener/src/main.js | 2 +- 3 files changed, 104 insertions(+), 31 deletions(-) diff --git a/node/url-shortener/README.md b/node/url-shortener/README.md index 93518532..cd01b930 100644 --- a/node/url-shortener/README.md +++ b/node/url-shortener/README.md @@ -1,44 +1,117 @@ -# URL Shortener Function +# ⚡ URL Shortener Function -This function allows you to turn long URLs into shortened, more manageable URLs. A unique, short, alphanumeric code is generated for each URL and stored in a database collection. The original URL can be accessed later using the short code. +Stores a short ID in a database and redirect to the original URL when the short ID is visited. -## Environment Variables +## 🧰 Usage -To ensure the function operates as intended, ensure the following variables are set: +### `GET /:shortId` -- **APPWRITE_API_KEY**: This is your Appwrite project's API key. -- **APPWRITE_ENDPOINT**: This is the endpoint where your Appwrite server is located. -- **APPWRITE_PROJECT_ID**: This refers to the specific ID of your Appwrite project. -- **SHORT_DOMAIN**: This is the base domain used when generating the short URLs. +Redirects to shortId's original URL. -Additionally, the function has the following optional variables: +**Parameters** -- **DATABASE_ID**: This is the ID for the database where URLs will be stored. If not provided, it will default to "url-shortener". -- **COLLECTION_ID**: This is the ID for the collection within the database. If not provided, it defaults to "urls". +| Name | Description | Location | Type | Sample Value | +| ------- | -------------------------------- | -------- | ------ | ------------ | +| shortId | Short ID to lookup original URL. | Path | String | `s63j2W` | -## Database Setup +**Response** -After the installation step, jf the specified database doesn't exist, the setup script will automatically create it. It will also create a collection within the database, adding a URL attribute to the collection. +Sample `302` Response: -## Usage +Redirects to the original URL. -This function supports two types of requests: +```text +Location: https://mywebapp.com/pages/hugelongurl?with=query¶ms=123 +``` -1. **Creating a Short URL** +Sample `404` Response: - - **Request Type:** POST - - **Content Type:** application/json - - **Body:** - - `url`: URL to be shortened - - **Response:** - - On success, the function will respond with the original URL and the shortened URL. - - Example: `{"original": "https://mylongdomain.com/videos/xHduwbGDq?t=5000&c=32i7333", "short": "https://short.domain/abc123"}` +When no URL is found for the short ID. -2. **Redirecting from a Short URL** +```text +Not Found. +``` - - **Request Type:** GET - - **Path Parameter:** The short URL code - - **Response:** - - On success, the function will redirect the user to the original URL. - - If the code does not exist in the database, a 404 error will be returned. +### `POST /` +Create a new short ID for a URL. + +**Parameters** + +| Name | Description | Location | Type | Sample Value | +| ------------ | ------------------- | -------- | ------------------ | -------------------------------------------------------------- | +| Content-Type | Content type | Header | `application/json` | +| url | Long URL to shorten | Body | String | `https://mywebapp.com/pages/hugelongurl?with=query¶ms=123` | + +**Response** + +Sample `200` Response: + +Returns the short URL and the original URL. The short URL is constructed from the SHORT_BASE_URL variable and the short ID. + +```json +{ + "short": "https://mywebapp.com/short/s63j2W", + "url": "https://mywebapp.com/pages/hugelongurl?with=query¶ms=123" +} +``` + +Sample `400` Response: + +When the URL parameter is missing. + +```json +{ + "error": "Missing url parameter." +} +``` + +## ⚙️ Configuration + +| Setting | Value | +| ----------------- | --------------- | +| Runtime | Node (18.0) | +| Entrypoint | `src/main.js` | +| Build Commands | `npm install` | +| | `npm run setup` | +| Permissions | `any` | +| Timeout (Seconds) | 15 | + +## 🔒 Environment Variables + +### APPWRITE_API_KEY + +The API Key to talk to Appwrite backend APIs. + +| Question | Answer | +| ------------- | -------------------------------------------------------------------------------------------------- | +| Required | Yes | +| Sample Value | `d1efb...aec35` | +| Documentation | [Appwrite: Getting Started for Server](https://appwrite.io/docs/getting-started-for-server#apiKey) | + +### APPWRITE_ENDPOINT + +The URL endpoint of the Appwrite server. If not provided, it defaults to the Appwrite Cloud server: `https://cloud.appwrite.io/v1`. + +| Question | Answer | +| ------------ | ------------------------------ | +| Required | No | +| Sample Value | `https://cloud.appwrite.io/v1` | + +### APPWRITE_PROJECT_ID + +The ID of the Appwrite project associated with the function. + +| Question | Answer | +| ------------ | ------------------- | +| Required | Yes | +| Sample Value | `builtWithAppwrite` | + +### SHORT_BASE_URL + +The base URL for the short URLs. The short ID will be appended to this URL. + +| Question | Answer | +| ------------ | ----------------------------- | +| Required | Yes | +| Sample Value | `https://mywebapp.com/short/` | diff --git a/node/url-shortener/src/environment.js b/node/url-shortener/src/environment.js index 965cebf3..2edb7101 100644 --- a/node/url-shortener/src/environment.js +++ b/node/url-shortener/src/environment.js @@ -28,7 +28,7 @@ class EnvironmentService { APPWRITE_API_KEY = getRequiredEnv('APPWRITE_API_KEY'); APPWRITE_ENDPOINT = getRequiredUrlEnv('APPWRITE_ENDPOINT'); APPWRITE_PROJECT_ID = getRequiredEnv('APPWRITE_PROJECT_ID'); - SHORT_DOMAIN = getRequiredEnv('SHORT_DOMAIN'); + SHORT_BASE_URL = getRequiredEnv('SHORT_DOMAIN'); DATABASE_ID = process.env.DATABASE_ID ?? 'url-shortener'; DATABASE_NAME = 'URL Shortener'; COLLECTION_ID = process.env.COLLECTION_ID ?? 'urls'; diff --git a/node/url-shortener/src/main.js b/node/url-shortener/src/main.js index 3d802436..21ee5643 100644 --- a/node/url-shortener/src/main.js +++ b/node/url-shortener/src/main.js @@ -25,7 +25,7 @@ export default async ({ res, req, log, error }) => { return res.json( { - short: `${env.SHORT_DOMAIN}/${urlEntry.$id}`, + short: `${env.SHORT_BASE_URL}/${urlEntry.$id}`, url: urlEntry.url, }, 201 From 54227430cc38b95aa14013ff18749443b9e80c63 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 25 Jul 2023 15:15:50 +0100 Subject: [PATCH 08/12] fix: env var --- node/url-shortener/env.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/url-shortener/env.d.ts b/node/url-shortener/env.d.ts index 1b575249..dc5e662c 100644 --- a/node/url-shortener/env.d.ts +++ b/node/url-shortener/env.d.ts @@ -4,7 +4,7 @@ declare global { APPWRITE_ENDPOINT?: string; APPWRITE_PROJECT_ID?: string; APPWRITE_API_KEY?: string; - SHORT_DOMAIN?: string; + SHORT_BASE_URL?: string; } } } From ec3e0825db1c732592f69bb97b92446b66e376a2 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 31 Jul 2023 10:34:18 +0100 Subject: [PATCH 09/12] chore: new utils --- node/url-shortener/env.d.ts | 7 +++-- node/url-shortener/src/appwrite.js | 43 ++++++++++++++------------- node/url-shortener/src/environment.js | 38 ----------------------- node/url-shortener/src/main.js | 6 ++-- node/url-shortener/src/setup.js | 4 +-- 5 files changed, 31 insertions(+), 67 deletions(-) delete mode 100644 node/url-shortener/src/environment.js diff --git a/node/url-shortener/env.d.ts b/node/url-shortener/env.d.ts index dc5e662c..e597b085 100644 --- a/node/url-shortener/env.d.ts +++ b/node/url-shortener/env.d.ts @@ -3,8 +3,11 @@ declare global { interface ProcessEnv { APPWRITE_ENDPOINT?: string; APPWRITE_PROJECT_ID?: string; - APPWRITE_API_KEY?: string; - SHORT_BASE_URL?: string; + APPWRITE_FUNCTION_PROJECT_ID: string; + APPWRITE_API_KEY: string; + APPWRITE_DATABASE_ID: string; + APPWRITE_COLLECTION_ID: string; + SHORT_BASE_URL: string; } } } diff --git a/node/url-shortener/src/appwrite.js b/node/url-shortener/src/appwrite.js index c23e2e2c..b26cdbe6 100644 --- a/node/url-shortener/src/appwrite.js +++ b/node/url-shortener/src/appwrite.js @@ -8,17 +8,17 @@ import { Client, Databases } from 'node-appwrite'; */ class AppwriteService { - /** - * @param {import('./environment').default} env - */ - constructor(env) { - this.env = env; - + constructor() { const client = new Client(); client - .setEndpoint(env.APPWRITE_ENDPOINT) - .setProject(env.APPWRITE_PROJECT_ID) - .setKey(env.APPWRITE_API_KEY); + .setEndpoint( + process.env.APPWRITE_ENDPOINT ?? 'https://cloud.appwrite.io/v1' + ) + .setProject( + process.env.APPWRITE_PROJECT_ID ?? + process.env.APPWRITE_FUNCTION_PROJECT_ID + ) + .setKey(process.env.APPWRITE_API_KEY); this.databases = new Databases(client); } @@ -31,8 +31,8 @@ class AppwriteService { try { const document = /** @type {URLEntryDocument} */ ( await this.databases.getDocument( - this.env.DATABASE_ID, - this.env.COLLECTION_ID, + process.env.APPWRITE_DATABASE_ID, + process.env.APPWRITE_COLLECTION_ID, shortCode ) ); @@ -53,8 +53,8 @@ class AppwriteService { try { const document = /** @type {URLEntryDocument} */ ( await this.databases.createDocument( - this.env.DATABASE_ID, - this.env.COLLECTION_ID, + process.env.APPWRITE_DATABASE_ID, + process.env.APPWRITE_COLLECTION_ID, shortCode, { url, @@ -74,7 +74,7 @@ class AppwriteService { */ async doesURLEntryDatabaseExist() { try { - await this.databases.get(this.env.DATABASE_ID); + await this.databases.get(process.env.APPWRITE_DATABASE_ID); return true; } catch (err) { if (err.code !== 404) throw err; @@ -84,15 +84,18 @@ class AppwriteService { async setupURLEntryDatabase() { try { - await this.databases.create(this.env.DATABASE_ID, this.env.DATABASE_NAME); + await this.databases.create( + process.env.APPWRITE_DATABASE_ID, + 'URL Shortener' + ); await this.databases.createCollection( - this.env.DATABASE_ID, - this.env.COLLECTION_ID, - this.env.COLLECTION_NAME + process.env.APPWRITE_DATABASE_ID, + process.env.APPWRITE_COLLECTION_ID, + 'URLs' ); await this.databases.createUrlAttribute( - this.env.DATABASE_ID, - this.env.COLLECTION_ID, + process.env.APPWRITE_DATABASE_ID, + process.env.APPWRITE_COLLECTION_ID, 'url', true ); diff --git a/node/url-shortener/src/environment.js b/node/url-shortener/src/environment.js deleted file mode 100644 index 2edb7101..00000000 --- a/node/url-shortener/src/environment.js +++ /dev/null @@ -1,38 +0,0 @@ -import { isValidURL } from './utils'; - -/** - * @param {string} key - * @return {string} - */ -function getRequiredEnv(key) { - const value = process.env[key]; - if (value === undefined) { - throw new Error(`Environment variable ${key} is not set`); - } - return value; -} - -/** - * @param {string} key - * @return {string} - */ -function getRequiredUrlEnv(key) { - const value = getRequiredEnv(key); - if (!isValidURL(value)) { - throw new Error(`Environment variable ${key}=${value} is a not valid URL`); - } - return value; -} - -class EnvironmentService { - APPWRITE_API_KEY = getRequiredEnv('APPWRITE_API_KEY'); - APPWRITE_ENDPOINT = getRequiredUrlEnv('APPWRITE_ENDPOINT'); - APPWRITE_PROJECT_ID = getRequiredEnv('APPWRITE_PROJECT_ID'); - SHORT_BASE_URL = getRequiredEnv('SHORT_DOMAIN'); - DATABASE_ID = process.env.DATABASE_ID ?? 'url-shortener'; - DATABASE_NAME = 'URL Shortener'; - COLLECTION_ID = process.env.COLLECTION_ID ?? 'urls'; - COLLECTION_NAME = 'URLs'; -} - -export default EnvironmentService; diff --git a/node/url-shortener/src/main.js b/node/url-shortener/src/main.js index 21ee5643..031a9bff 100644 --- a/node/url-shortener/src/main.js +++ b/node/url-shortener/src/main.js @@ -1,10 +1,8 @@ import AppwriteService from './appwrite.js'; -import EnvironmentService from './environment.js'; import { isValidURL, generateShortCode } from './utils.js'; export default async ({ res, req, log, error }) => { - const env = new EnvironmentService(); - const appwrite = new AppwriteService(env); + const appwrite = new AppwriteService(); if ( req.method === 'POST' && @@ -25,7 +23,7 @@ export default async ({ res, req, log, error }) => { return res.json( { - short: `${env.SHORT_BASE_URL}/${urlEntry.$id}`, + short: `${process.env.SHORT_BASE_URL}/${urlEntry.$id}`, url: urlEntry.url, }, 201 diff --git a/node/url-shortener/src/setup.js b/node/url-shortener/src/setup.js index 3dff90e6..2901f7b4 100644 --- a/node/url-shortener/src/setup.js +++ b/node/url-shortener/src/setup.js @@ -1,11 +1,9 @@ import AppwriteService from './appwrite.js'; -import EnvironmentService from './environment.js'; async function setup() { console.log('Executing setup script...'); - const env = new EnvironmentService(); - const appwrite = new AppwriteService(env); + const appwrite = new AppwriteService(); if (await appwrite.doesURLEntryDatabaseExist()) { console.log(`Database exists.`); From f373ac8a96a5b26be5133323b424e8d717ea8765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 3 Aug 2023 08:38:50 +0000 Subject: [PATCH 10/12] PR review changes --- node/url-shortener/README.md | 48 ++++++++++-------------------- node/url-shortener/env.d.ts | 2 -- node/url-shortener/src/appwrite.js | 5 +--- node/url-shortener/src/main.js | 22 +++++++------- 4 files changed, 26 insertions(+), 51 deletions(-) diff --git a/node/url-shortener/README.md b/node/url-shortener/README.md index cd01b930..0cb74e1f 100644 --- a/node/url-shortener/README.md +++ b/node/url-shortener/README.md @@ -1,6 +1,6 @@ -# ⚡ URL Shortener Function +# 🔗 Node.js URL Shortener Function -Stores a short ID in a database and redirect to the original URL when the short ID is visited. +Generate URL with short ID and redirect to the original URL when visited. ## 🧰 Usage @@ -12,7 +12,7 @@ Redirects to shortId's original URL. | Name | Description | Location | Type | Sample Value | | ------- | -------------------------------- | -------- | ------ | ------------ | -| shortId | Short ID to lookup original URL. | Path | String | `s63j2W` | +| shortId | Short ID to lookup original URL. | Path | String | `discord` | **Response** @@ -21,7 +21,7 @@ Sample `302` Response: Redirects to the original URL. ```text -Location: https://mywebapp.com/pages/hugelongurl?with=query¶ms=123 +Location: https://discord.com/invite/GSeTUeA ``` Sample `404` Response: @@ -29,7 +29,7 @@ Sample `404` Response: When no URL is found for the short ID. ```text -Not Found. +Invalid link. ``` ### `POST /` @@ -47,12 +47,11 @@ Create a new short ID for a URL. Sample `200` Response: -Returns the short URL and the original URL. The short URL is constructed from the SHORT_BASE_URL variable and the short ID. +Returns the short URL and the original URL. The short URL is constructed from the base URL (`host` header) and the short ID. ```json { - "short": "https://mywebapp.com/short/s63j2W", - "url": "https://mywebapp.com/pages/hugelongurl?with=query¶ms=123" + "url": "https://mywebapp.com/discord" } ``` @@ -62,20 +61,20 @@ When the URL parameter is missing. ```json { + "ok": false, "error": "Missing url parameter." } ``` ## ⚙️ Configuration -| Setting | Value | -| ----------------- | --------------- | -| Runtime | Node (18.0) | -| Entrypoint | `src/main.js` | -| Build Commands | `npm install` | -| | `npm run setup` | -| Permissions | `any` | -| Timeout (Seconds) | 15 | +| Setting | Value | +| ----------------- | ------------------------------ | +| Runtime | Node (18.0) | +| Entrypoint | `src/main.js` | +| Build Commands | `npm install && npm run setup` | +| Permissions | `any` | +| Timeout (Seconds) | 15 | ## 🔒 Environment Variables @@ -98,20 +97,3 @@ The URL endpoint of the Appwrite server. If not provided, it defaults to the App | Required | No | | Sample Value | `https://cloud.appwrite.io/v1` | -### APPWRITE_PROJECT_ID - -The ID of the Appwrite project associated with the function. - -| Question | Answer | -| ------------ | ------------------- | -| Required | Yes | -| Sample Value | `builtWithAppwrite` | - -### SHORT_BASE_URL - -The base URL for the short URLs. The short ID will be appended to this URL. - -| Question | Answer | -| ------------ | ----------------------------- | -| Required | Yes | -| Sample Value | `https://mywebapp.com/short/` | diff --git a/node/url-shortener/env.d.ts b/node/url-shortener/env.d.ts index e597b085..a7cf7e0a 100644 --- a/node/url-shortener/env.d.ts +++ b/node/url-shortener/env.d.ts @@ -2,12 +2,10 @@ declare global { namespace NodeJS { interface ProcessEnv { APPWRITE_ENDPOINT?: string; - APPWRITE_PROJECT_ID?: string; APPWRITE_FUNCTION_PROJECT_ID: string; APPWRITE_API_KEY: string; APPWRITE_DATABASE_ID: string; APPWRITE_COLLECTION_ID: string; - SHORT_BASE_URL: string; } } } diff --git a/node/url-shortener/src/appwrite.js b/node/url-shortener/src/appwrite.js index b26cdbe6..da0c9647 100644 --- a/node/url-shortener/src/appwrite.js +++ b/node/url-shortener/src/appwrite.js @@ -14,10 +14,7 @@ class AppwriteService { .setEndpoint( process.env.APPWRITE_ENDPOINT ?? 'https://cloud.appwrite.io/v1' ) - .setProject( - process.env.APPWRITE_PROJECT_ID ?? - process.env.APPWRITE_FUNCTION_PROJECT_ID - ) + .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); this.databases = new Databases(client); diff --git a/node/url-shortener/src/main.js b/node/url-shortener/src/main.js index 031a9bff..28eb86aa 100644 --- a/node/url-shortener/src/main.js +++ b/node/url-shortener/src/main.js @@ -4,29 +4,26 @@ import { isValidURL, generateShortCode } from './utils.js'; export default async ({ res, req, log, error }) => { const appwrite = new AppwriteService(); - if ( - req.method === 'POST' && - req.headers['content-type'] === 'application/json' - ) { + if (req.method === 'POST') { const { url } = req.body; + if (!url || !isValidURL(url)) { error('Invalid url parameter.'); - return res.json({ error: 'Invalid url parameter' }, 400); + return res.json({ ok: false, error: 'Invalid url parameter' }, 400); } const shortCode = generateShortCode(); const urlEntry = await appwrite.createURLEntry(url, shortCode); + if (!urlEntry) { error('Failed to create url entry.'); - return res.json({ error: 'Failed to create url entry' }, 500); + return res.json({ ok: false, error: 'Failed to create url entry' }, 500); } return res.json( { - short: `${process.env.SHORT_BASE_URL}/${urlEntry.$id}`, - url: urlEntry.url, - }, - 201 + url: `${req.host}/${urlEntry.$id}` + } ); } @@ -34,9 +31,10 @@ export default async ({ res, req, log, error }) => { log(`Fetching document from with ID: ${shortId}`); const urlEntry = await appwrite.getURLEntry(shortId); + if (!urlEntry) { - return res.send(`Not found.`, 404); + return res.send('Invalid link.', 404); } - return res.redirect(urlEntry.url, 302); + return res.redirect(urlEntry.url); }; From 6f2e64a5bcf8a9b6ab410fa1070ba8422bd751bb Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 3 Aug 2023 10:29:34 +0100 Subject: [PATCH 11/12] chore: review --- node/url-shortener/README.md | 25 +++++++++++++++++++------ node/url-shortener/env.d.ts | 1 - node/url-shortener/src/main.js | 25 ++++++++++++++++++------- node/url-shortener/src/setup.js | 7 +++++++ node/url-shortener/src/utils.js | 32 ++++++++++++++++++-------------- 5 files changed, 62 insertions(+), 28 deletions(-) diff --git a/node/url-shortener/README.md b/node/url-shortener/README.md index cd01b930..7702b88d 100644 --- a/node/url-shortener/README.md +++ b/node/url-shortener/README.md @@ -98,14 +98,25 @@ The URL endpoint of the Appwrite server. If not provided, it defaults to the App | Required | No | | Sample Value | `https://cloud.appwrite.io/v1` | -### APPWRITE_PROJECT_ID -The ID of the Appwrite project associated with the function. +### APPWRITE_DATABASE_ID + +The ID of the database to store the short URLs. + +| Question | Answer | +| ------------ | ----------------------------- | +| Required | Yes | +| Sample Value | `urlShortener` | + +### APPWRITE_COLLECTION_ID + +The ID of the collection to store the short URLs. + +| Question | Answer | +| ------------ | ----------------------------- | +| Required | Yes | +| Sample Value | `urls` | -| Question | Answer | -| ------------ | ------------------- | -| Required | Yes | -| Sample Value | `builtWithAppwrite` | ### SHORT_BASE_URL @@ -115,3 +126,5 @@ The base URL for the short URLs. The short ID will be appended to this URL. | ------------ | ----------------------------- | | Required | Yes | | Sample Value | `https://mywebapp.com/short/` | + + diff --git a/node/url-shortener/env.d.ts b/node/url-shortener/env.d.ts index e597b085..28335c4b 100644 --- a/node/url-shortener/env.d.ts +++ b/node/url-shortener/env.d.ts @@ -2,7 +2,6 @@ declare global { namespace NodeJS { interface ProcessEnv { APPWRITE_ENDPOINT?: string; - APPWRITE_PROJECT_ID?: string; APPWRITE_FUNCTION_PROJECT_ID: string; APPWRITE_API_KEY: string; APPWRITE_DATABASE_ID: string; diff --git a/node/url-shortener/src/main.js b/node/url-shortener/src/main.js index 031a9bff..deeb22e3 100644 --- a/node/url-shortener/src/main.js +++ b/node/url-shortener/src/main.js @@ -1,21 +1,32 @@ import AppwriteService from './appwrite.js'; -import { isValidURL, generateShortCode } from './utils.js'; +import { generateShortCode, throwIfMissing } from './utils.js'; export default async ({ res, req, log, error }) => { + throwIfMissing(process.env, [ + 'APPWRITE_API_KEY', + 'APPWRITE_DATABASE_ID', + 'APPWRITE_COLLECTION_ID', + 'SHORT_BASE_URL', + ]); + const appwrite = new AppwriteService(); if ( req.method === 'POST' && req.headers['content-type'] === 'application/json' ) { - const { url } = req.body; - if (!url || !isValidURL(url)) { - error('Invalid url parameter.'); - return res.json({ error: 'Invalid url parameter' }, 400); + try { + throwIfMissing(req.body, ['url']); + new URL(req.body.url); + } catch (err) { + error(err.message); + return res.send({ error: err.message }, 400); } - const shortCode = generateShortCode(); - const urlEntry = await appwrite.createURLEntry(url, shortCode); + const urlEntry = await appwrite.createURLEntry( + req.body.url, + generateShortCode() + ); if (!urlEntry) { error('Failed to create url entry.'); return res.json({ error: 'Failed to create url entry' }, 500); diff --git a/node/url-shortener/src/setup.js b/node/url-shortener/src/setup.js index 2901f7b4..f356f298 100644 --- a/node/url-shortener/src/setup.js +++ b/node/url-shortener/src/setup.js @@ -1,6 +1,13 @@ import AppwriteService from './appwrite.js'; +import { throwIfMissing } from './utils.js'; async function setup() { + throwIfMissing(process.env, [ + 'APPWRITE_API_KEY', + 'APPWRITE_DATABASE_ID', + 'APPWRITE_COLLECTION_ID', + ]); + console.log('Executing setup script...'); const appwrite = new AppwriteService(); diff --git a/node/url-shortener/src/utils.js b/node/url-shortener/src/utils.js index b1c7fb7f..a2aa821f 100644 --- a/node/url-shortener/src/utils.js +++ b/node/url-shortener/src/utils.js @@ -1,21 +1,25 @@ import { customAlphabet } from 'nanoid'; -/** - * @param {string | undefined} url - * @returns {boolean} - */ -export function isValidURL(url) { - if (!url) return false; - try { - new URL(url); - return true; - } catch (err) { - return false; - } -} - const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; const nanoid = customAlphabet(ALPHABET); export const generateShortCode = () => nanoid(6); + +/** + * Throws an error if any of the keys are missing from the object + * @param {*} obj + * @param {string[]} keys + * @throws {Error} + */ +export function throwIfMissing(obj, keys) { + const missing = []; + for (let key of keys) { + if (!(key in obj) || !obj[key]) { + missing.push(key); + } + } + if (missing.length > 0) { + throw new Error(`Missing required fields: ${missing.join(', ')}`); + } +} From bd1e5b160a8d1f4c8f4d84066952c733cde44e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 3 Aug 2023 09:43:43 +0000 Subject: [PATCH 12/12] PR review changes --- node/url-shortener/README.md | 10 ---------- node/url-shortener/src/main.js | 3 +-- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/node/url-shortener/README.md b/node/url-shortener/README.md index 7a49c7bc..5a57b88a 100644 --- a/node/url-shortener/README.md +++ b/node/url-shortener/README.md @@ -114,13 +114,3 @@ The ID of the collection to store the short URLs. | ------------ | ----------------------------- | | Required | Yes | | Sample Value | `urls` | - - -### SHORT_BASE_URL - -The base URL for the short URLs. The short ID will be appended to this URL. - -| Question | Answer | -| ------------ | ----------------------------- | -| Required | Yes | -| Sample Value | `https://mywebapp.com/short/` | diff --git a/node/url-shortener/src/main.js b/node/url-shortener/src/main.js index 3542649f..169daf5f 100644 --- a/node/url-shortener/src/main.js +++ b/node/url-shortener/src/main.js @@ -6,7 +6,6 @@ export default async ({ res, req, log, error }) => { 'APPWRITE_API_KEY', 'APPWRITE_DATABASE_ID', 'APPWRITE_COLLECTION_ID', - 'SHORT_BASE_URL', ]); const appwrite = new AppwriteService(); @@ -20,7 +19,7 @@ export default async ({ res, req, log, error }) => { new URL(req.body.url); } catch (err) { error(err.message); - return res.send({ error: err.message }, 400); + return res.send({ ok: false, error: err.message }, 400); } const urlEntry = await appwrite.createURLEntry(