From 2f860eccd3c749a6e16bed858f0cde66ad1c814e Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 12 Jul 2023 11:38:08 +0100 Subject: [PATCH 001/149] feat: email contact form initial --- email-contact-form/.gitignore | 130 +++++++++++++++++++++++++ email-contact-form/.pretterrc.json | 6 ++ email-contact-form/README.md | 50 ++++++++++ email-contact-form/env.d.ts | 14 +++ email-contact-form/package-lock.json | 42 ++++++++ email-contact-form/package.json | 17 ++++ email-contact-form/src/cors.js | 21 ++++ email-contact-form/src/environment.js | 22 +++++ email-contact-form/src/mail.js | 24 +++++ email-contact-form/src/main.js | 124 +++++++++++++++++++++++ email-contact-form/static/index.html | 45 +++++++++ email-contact-form/static/success.html | 31 ++++++ 12 files changed, 526 insertions(+) create mode 100644 email-contact-form/.gitignore create mode 100644 email-contact-form/.pretterrc.json create mode 100644 email-contact-form/README.md create mode 100644 email-contact-form/env.d.ts create mode 100644 email-contact-form/package-lock.json create mode 100644 email-contact-form/package.json create mode 100644 email-contact-form/src/cors.js create mode 100644 email-contact-form/src/environment.js create mode 100644 email-contact-form/src/mail.js create mode 100644 email-contact-form/src/main.js create mode 100644 email-contact-form/static/index.html create mode 100644 email-contact-form/static/success.html diff --git a/email-contact-form/.gitignore b/email-contact-form/.gitignore new file mode 100644 index 00000000..6a7d6d8e --- /dev/null +++ b/email-contact-form/.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/email-contact-form/.pretterrc.json b/email-contact-form/.pretterrc.json new file mode 100644 index 00000000..fa51da29 --- /dev/null +++ b/email-contact-form/.pretterrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/email-contact-form/README.md b/email-contact-form/README.md new file mode 100644 index 00000000..57ca4fa6 --- /dev/null +++ b/email-contact-form/README.md @@ -0,0 +1,50 @@ +# Email Contact Form Function + +## Overview + +This function facilitates email submission from HTML forms using Appwrite. It validates form data, sends an email through an SMTP server, and handles redirection of the user based on the success or failure of the submission. + +## Usage + +### HTML Form + +To use this function, set the `action` attribute of your HTML form to your function URL, and include a hidden input with the name `_next` and the path of the redirect to on successful form submission (e.g. `/success`). + +```html +
+ + + + +
+``` + +## Environment Variables + +This function depends on the following environment variables: + +- **SMTP_HOST** - SMTP server host +- **SMTP_PORT** - SMTP server port +- **SMTP_USERNAME** - SMTP server username +- **SMTP_PASSWORD** - SMTP server password +- **SUBMIT_EMAIL** - The email address to send form submissions +- **ALLOWED_ORIGINS** - An optional comma-separated list of allowed origins for CORS (defaults to `*`) + +## Request + +### Form Data + +- **_next_** - The URL to redirect to on successful form submission +- **email** - The sender's email address + +- _Additional form data will be included in the email body_ + +## Response + +### Success Redirect + +On successful form submission, the function will redirect users to the URL provided in the `_next` form data. + +### Error Redirect + +In the case of errors such as invalid request methods, missing form data, or SMTP configuration issues, the function will redirect users back to the form URL with an appended error code for more precise error handling. Error codes include `invalid-request`, `missing-form-fields`, and generic `server-error`. diff --git a/email-contact-form/env.d.ts b/email-contact-form/env.d.ts new file mode 100644 index 00000000..6e31c21d --- /dev/null +++ b/email-contact-form/env.d.ts @@ -0,0 +1,14 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + STMP_HOST?: string; + STMP_PORT?: string; + STMP_USERNAME?: string; + STMP_PASSWORD?: string; + SUBMIT_EMAIL?: string; + ALLOWED_ORIGINS?: string; + } + } +} + +export {}; diff --git a/email-contact-form/package-lock.json b/email-contact-form/package-lock.json new file mode 100644 index 00000000..1fbc6811 --- /dev/null +++ b/email-contact-form/package-lock.json @@ -0,0 +1,42 @@ +{ + "name": "email-contact-form", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "email-contact-form", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "nodemailer": "^6.9.3" + }, + "devDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/nodemailer": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.3.tgz", + "integrity": "sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==", + "engines": { + "node": ">=6.0.0" + } + }, + "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" + } + } + } +} diff --git a/email-contact-form/package.json b/email-contact-form/package.json new file mode 100644 index 00000000..85524702 --- /dev/null +++ b/email-contact-form/package.json @@ -0,0 +1,17 @@ +{ + "name": "email-contact-form", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "scripts": { + "format": "prettier --write src/**/*.js" + }, + "author": "", + "license": "MIT", + "dependencies": { + "nodemailer": "^6.9.3" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/email-contact-form/src/cors.js b/email-contact-form/src/cors.js new file mode 100644 index 00000000..cb7a0a0b --- /dev/null +++ b/email-contact-form/src/cors.js @@ -0,0 +1,21 @@ +const getEnvironment = require("./environment"); + +/** + * @param {string} origin + */ +module.exports = function CorsService(origin) { + const { ALLOWED_ORIGINS } = getEnvironment(); + + return { + isOriginPermitted: function () { + if (!ALLOWED_ORIGINS || ALLOWED_ORIGINS === "*") return true; + const allowedOriginsArray = ALLOWED_ORIGINS.split(","); + return allowedOriginsArray.includes(origin); + }, + getHeaders: function () { + return { + "Access-Control-Allow-Origin": ALLOWED_ORIGINS === "*" ? "*" : origin, + }; + }, + }; +}; diff --git a/email-contact-form/src/environment.js b/email-contact-form/src/environment.js new file mode 100644 index 00000000..397b9052 --- /dev/null +++ b/email-contact-form/src/environment.js @@ -0,0 +1,22 @@ +module.exports = function getEnvironment() { + return { + SUBMIT_EMAIL: getRequiredEnv("SUBMIT_EMAIL"), + SMTP_HOST: getRequiredEnv("SMTP_HOST"), + SMTP_PORT: process.env.SMTP_PORT || 587, + SMTP_USERNAME: getRequiredEnv("SMTP_USERNAME"), + SMTP_PASSWORD: getRequiredEnv("SMTP_PASSWORD"), + ALLOWED_ORIGINS: process.env.ALLOWED_ORIGINS || "*", + }; +}; + +/** + * @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; +} diff --git a/email-contact-form/src/mail.js b/email-contact-form/src/mail.js new file mode 100644 index 00000000..6b3b2999 --- /dev/null +++ b/email-contact-form/src/mail.js @@ -0,0 +1,24 @@ +const getEnvironment = require("./environment"); +const nodemailer = require("nodemailer"); + +module.exports = function MailService() { + const { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD } = + getEnvironment(); + + const transport = nodemailer.createTransport({ + // @ts-ignore + // Not sure what's going on here. + host: SMTP_HOST, + port: SMTP_PORT, + auth: { user: SMTP_USERNAME, pass: SMTP_PASSWORD }, + }); + + return { + /** + * @param {import('nodemailer').SendMailOptions} mailOptions + */ + send: async function (mailOptions) { + await transport.sendMail(mailOptions); + }, + }; +}; diff --git a/email-contact-form/src/main.js b/email-contact-form/src/main.js new file mode 100644 index 00000000..5bbee69f --- /dev/null +++ b/email-contact-form/src/main.js @@ -0,0 +1,124 @@ +const querystring = require("node:querystring"); +const getEnvironment = require("./environment"); +const CorsService = require("./cors"); +const MailService = require("./mail"); +const fs = require("fs"); +const path = require("path"); + +const ErrorCode = { + INVALID_REQUEST: "invalid-request", + MISSING_FORM_FIELDS: "missing-form-fields", + SERVER_ERROR: "server-error", +}; + +const ROUTES = { + "/": "index.html", + "/index.html": "index.html", + "/success.html": "success.html", +}; + +const staticFolder = path.join(__dirname, "../static"); + +module.exports = async ({ req, res, log, error }) => { + const { SUBMIT_EMAIL, ALLOWED_ORIGINS } = getEnvironment(); + + if (ALLOWED_ORIGINS === "*") { + log( + "WARNING: Allowing requests from any origin - this is a security risk!" + ); + } + + if (req.method === "GET") { + const route = ROUTES[req.path]; + const html = fs.readFileSync(path.join(staticFolder, route)); + return res.send(html.toString(), 200, { + "Content-Type": "text/html; charset=utf-8", + }); + } + + const referer = req.headers["referer"]; + const origin = req.headers["origin"]; + if (!referer || !origin) { + log("Missing referer or origin headers."); + return res.json({ error: "Missing referer or origin headers." }, 400); + } + + if (req.headers["content-type"] !== "application/x-www-form-urlencoded") { + log("Invalid request."); + return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)); + } + + const cors = CorsService(origin); + const mail = MailService(); + + if (!cors.isOriginPermitted()) { + error("Origin not permitted."); + return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)); + } + + const form = querystring.parse(req.body); + + if ( + !( + form.email && + form._next && + typeof form.email === "string" && + typeof form._next === "string" + ) + ) { + error("Missing form data."); + return res.redirect( + urlWithCodeParam(referer, ErrorCode.MISSING_FORM_FIELDS), + 301, + cors.getHeaders() + ); + } + log("Form data is valid."); + + try { + mail.send({ + to: SUBMIT_EMAIL, + from: form.email, + subject: `New form submission: ${origin}`, + text: templateFormMessage(form), + }); + } catch (err) { + error(err.message); + return res.redirect( + urlWithCodeParam(referer, ErrorCode.SERVER_ERROR), + 301, + cors.getHeaders() + ); + } + + log("Email sent successfully."); + + return res.redirect( + new URL(form._next, origin).toString(), + 301, + cors.getHeaders() + ); +}; + +/** + * Build a message from the form data. + * @param {import("node:querystring").ParsedUrlQuery} form + * @returns {string} + */ +function templateFormMessage(form) { + return `You've received a new message.\n +${Object.entries(form) + .filter(([key]) => key !== "_next") + .map(([key, value]) => `${key}: ${value}`) + .join("\n")}`; +} + +/** + * @param {string} baseUrl + * @param {string} codeParam + */ +function urlWithCodeParam(baseUrl, codeParam) { + const url = new URL(baseUrl); + url.searchParams.set("code", codeParam); + return url.toString(); +} diff --git a/email-contact-form/static/index.html b/email-contact-form/static/index.html new file mode 100644 index 00000000..29670f76 --- /dev/null +++ b/email-contact-form/static/index.html @@ -0,0 +1,45 @@ + + + + + + + Email Contact Form + + + + +
+
+
+
+

Contact

+ +
+

+ Fill the form below to send us a message. +

+
+ + + + +
+
+
+
+ + diff --git a/email-contact-form/static/success.html b/email-contact-form/static/success.html new file mode 100644 index 00000000..94ba2a8d --- /dev/null +++ b/email-contact-form/static/success.html @@ -0,0 +1,31 @@ + + + + + + + Email Contact Form + + + + +
+
+
+
+

Success

+ +
+

+ Your message has been sent! +

+
+
+
+ + From d90ae027326516727a8fd29a3158f25c3bd12e55 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 12 Jul 2023 13:04:15 +0100 Subject: [PATCH 002/149] feat: migrate to esm --- .../{.pretterrc.json => .prettierrc.json} | 0 email-contact-form/package.json | 1 + email-contact-form/src/cors.js | 28 +++-- email-contact-form/src/environment.js | 28 ++--- email-contact-form/src/mail.js | 16 +-- email-contact-form/src/main.js | 110 +++++++++--------- 6 files changed, 96 insertions(+), 87 deletions(-) rename email-contact-form/{.pretterrc.json => .prettierrc.json} (100%) diff --git a/email-contact-form/.pretterrc.json b/email-contact-form/.prettierrc.json similarity index 100% rename from email-contact-form/.pretterrc.json rename to email-contact-form/.prettierrc.json diff --git a/email-contact-form/package.json b/email-contact-form/package.json index 85524702..44332858 100644 --- a/email-contact-form/package.json +++ b/email-contact-form/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "src/main.js", + "type": "module", "scripts": { "format": "prettier --write src/**/*.js" }, diff --git a/email-contact-form/src/cors.js b/email-contact-form/src/cors.js index cb7a0a0b..f0e6a5e4 100644 --- a/email-contact-form/src/cors.js +++ b/email-contact-form/src/cors.js @@ -1,21 +1,27 @@ -const getEnvironment = require("./environment"); +import getEnvironment from './environment' /** - * @param {string} origin + * @param {string} origin Origin header of the request */ -module.exports = function CorsService(origin) { - const { ALLOWED_ORIGINS } = getEnvironment(); +export default function CorsService(origin) { + const { ALLOWED_ORIGINS } = getEnvironment() return { + /** + * @returns {boolean} Whether the origin is allowed based on the ALLOWED_ORIGINS environment variable + */ isOriginPermitted: function () { - if (!ALLOWED_ORIGINS || ALLOWED_ORIGINS === "*") return true; - const allowedOriginsArray = ALLOWED_ORIGINS.split(","); - return allowedOriginsArray.includes(origin); + if (!ALLOWED_ORIGINS || ALLOWED_ORIGINS === '*') return true + const allowedOriginsArray = ALLOWED_ORIGINS.split(',') + return allowedOriginsArray.includes(origin) }, + /** + * @returns {Object} Access-Control-Allow-Origin header to be returned in the response + */ getHeaders: function () { return { - "Access-Control-Allow-Origin": ALLOWED_ORIGINS === "*" ? "*" : origin, - }; + 'Access-Control-Allow-Origin': ALLOWED_ORIGINS === '*' ? '*' : origin, + } }, - }; -}; + } +} diff --git a/email-contact-form/src/environment.js b/email-contact-form/src/environment.js index 397b9052..3ce204fe 100644 --- a/email-contact-form/src/environment.js +++ b/email-contact-form/src/environment.js @@ -1,22 +1,22 @@ -module.exports = function getEnvironment() { - return { - SUBMIT_EMAIL: getRequiredEnv("SUBMIT_EMAIL"), - SMTP_HOST: getRequiredEnv("SMTP_HOST"), - SMTP_PORT: process.env.SMTP_PORT || 587, - SMTP_USERNAME: getRequiredEnv("SMTP_USERNAME"), - SMTP_PASSWORD: getRequiredEnv("SMTP_PASSWORD"), - ALLOWED_ORIGINS: process.env.ALLOWED_ORIGINS || "*", - }; -}; - /** * @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 +} + +export default function getEnvironment() { + return { + SUBMIT_EMAIL: getRequiredEnv('SUBMIT_EMAIL'), + SMTP_HOST: getRequiredEnv('SMTP_HOST'), + SMTP_PORT: process.env.SMTP_PORT || 587, + SMTP_USERNAME: getRequiredEnv('SMTP_USERNAME'), + SMTP_PASSWORD: getRequiredEnv('SMTP_PASSWORD'), + ALLOWED_ORIGINS: process.env.ALLOWED_ORIGINS || '*', } - return value; } diff --git a/email-contact-form/src/mail.js b/email-contact-form/src/mail.js index 6b3b2999..cb6a859b 100644 --- a/email-contact-form/src/mail.js +++ b/email-contact-form/src/mail.js @@ -1,9 +1,9 @@ -const getEnvironment = require("./environment"); -const nodemailer = require("nodemailer"); +import getEnvironment from './environment' +import nodemailer from 'nodemailer' -module.exports = function MailService() { +export default function MailService() { const { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD } = - getEnvironment(); + getEnvironment() const transport = nodemailer.createTransport({ // @ts-ignore @@ -11,14 +11,14 @@ module.exports = function MailService() { host: SMTP_HOST, port: SMTP_PORT, auth: { user: SMTP_USERNAME, pass: SMTP_PASSWORD }, - }); + }) return { /** * @param {import('nodemailer').SendMailOptions} mailOptions */ send: async function (mailOptions) { - await transport.sendMail(mailOptions); + await transport.sendMail(mailOptions) }, - }; -}; + } +} diff --git a/email-contact-form/src/main.js b/email-contact-form/src/main.js index 5bbee69f..79af167c 100644 --- a/email-contact-form/src/main.js +++ b/email-contact-form/src/main.js @@ -1,79 +1,81 @@ -const querystring = require("node:querystring"); -const getEnvironment = require("./environment"); -const CorsService = require("./cors"); -const MailService = require("./mail"); -const fs = require("fs"); -const path = require("path"); +import querystring from 'node:querystring' +import { readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import path from 'node:path' +import getEnvironment from './environment' +import CorsService from './cors' +import MailService from './mail' const ErrorCode = { - INVALID_REQUEST: "invalid-request", - MISSING_FORM_FIELDS: "missing-form-fields", - SERVER_ERROR: "server-error", -}; + INVALID_REQUEST: 'invalid-request', + MISSING_FORM_FIELDS: 'missing-form-fields', + SERVER_ERROR: 'server-error', +} const ROUTES = { - "/": "index.html", - "/index.html": "index.html", - "/success.html": "success.html", -}; + '/': 'index.html', + '/index.html': 'index.html', + '/success.html': 'success.html', +} + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) -const staticFolder = path.join(__dirname, "../static"); +const staticFolder = path.join(__dirname, '../static') -module.exports = async ({ req, res, log, error }) => { - const { SUBMIT_EMAIL, ALLOWED_ORIGINS } = getEnvironment(); +export default async ({ req, res, log, error }) => { + const { SUBMIT_EMAIL, ALLOWED_ORIGINS } = getEnvironment() - if (ALLOWED_ORIGINS === "*") { - log( - "WARNING: Allowing requests from any origin - this is a security risk!" - ); + if (ALLOWED_ORIGINS === '*') { + log('WARNING: Allowing requests from any origin - this is a security risk!') } - if (req.method === "GET") { - const route = ROUTES[req.path]; - const html = fs.readFileSync(path.join(staticFolder, route)); + if (req.method === 'GET') { + const route = ROUTES[req.path] + const html = readFileSync(path.join(staticFolder, route)) return res.send(html.toString(), 200, { - "Content-Type": "text/html; charset=utf-8", - }); + 'Content-Type': 'text/html; charset=utf-8', + }) } - const referer = req.headers["referer"]; - const origin = req.headers["origin"]; + const referer = req.headers['referer'] + const origin = req.headers['origin'] if (!referer || !origin) { - log("Missing referer or origin headers."); - return res.json({ error: "Missing referer or origin headers." }, 400); + log('Missing referer or origin headers.') + return res.json({ error: 'Missing referer or origin headers.' }, 400) } - if (req.headers["content-type"] !== "application/x-www-form-urlencoded") { - log("Invalid request."); - return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)); + if (req.headers['content-type'] !== 'application/x-www-form-urlencoded') { + log('Invalid request.') + return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)) } - const cors = CorsService(origin); - const mail = MailService(); + const cors = CorsService(origin) + const mail = MailService() if (!cors.isOriginPermitted()) { - error("Origin not permitted."); - return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)); + error('Origin not permitted.') + return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)) } - const form = querystring.parse(req.body); + const form = querystring.parse(req.body) if ( !( form.email && form._next && - typeof form.email === "string" && - typeof form._next === "string" + typeof form.email === 'string' && + typeof form._next === 'string' ) ) { - error("Missing form data."); + error('Missing form data.') return res.redirect( urlWithCodeParam(referer, ErrorCode.MISSING_FORM_FIELDS), 301, cors.getHeaders() - ); + ) } - log("Form data is valid."); + log('Form data is valid.') try { mail.send({ @@ -81,24 +83,24 @@ module.exports = async ({ req, res, log, error }) => { from: form.email, subject: `New form submission: ${origin}`, text: templateFormMessage(form), - }); + }) } catch (err) { - error(err.message); + error(err.message) return res.redirect( urlWithCodeParam(referer, ErrorCode.SERVER_ERROR), 301, cors.getHeaders() - ); + ) } - log("Email sent successfully."); + log('Email sent successfully.') return res.redirect( new URL(form._next, origin).toString(), 301, cors.getHeaders() - ); -}; + ) +} /** * Build a message from the form data. @@ -108,9 +110,9 @@ module.exports = async ({ req, res, log, error }) => { function templateFormMessage(form) { return `You've received a new message.\n ${Object.entries(form) - .filter(([key]) => key !== "_next") + .filter(([key]) => key !== '_next') .map(([key, value]) => `${key}: ${value}`) - .join("\n")}`; + .join('\n')}` } /** @@ -118,7 +120,7 @@ ${Object.entries(form) * @param {string} codeParam */ function urlWithCodeParam(baseUrl, codeParam) { - const url = new URL(baseUrl); - url.searchParams.set("code", codeParam); - return url.toString(); + const url = new URL(baseUrl) + url.searchParams.set('code', codeParam) + return url.toString() } From 9551dccf6fc56d07304932adb0d173d5fb46052a Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 12 Jul 2023 13:43:16 +0100 Subject: [PATCH 003/149] feat: default success page --- email-contact-form/src/main.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/email-contact-form/src/main.js b/email-contact-form/src/main.js index 79af167c..799a4823 100644 --- a/email-contact-form/src/main.js +++ b/email-contact-form/src/main.js @@ -60,14 +60,7 @@ export default async ({ req, res, log, error }) => { const form = querystring.parse(req.body) - if ( - !( - form.email && - form._next && - typeof form.email === 'string' && - typeof form._next === 'string' - ) - ) { + if (!(form.email && typeof form.email === 'string')) { error('Missing form data.') return res.redirect( urlWithCodeParam(referer, ErrorCode.MISSING_FORM_FIELDS), @@ -77,6 +70,9 @@ export default async ({ req, res, log, error }) => { } log('Form data is valid.') + const successUrl = + typeof form._next === 'string' && form._next ? form._next : '/success' + try { mail.send({ to: SUBMIT_EMAIL, @@ -96,7 +92,7 @@ export default async ({ req, res, log, error }) => { log('Email sent successfully.') return res.redirect( - new URL(form._next, origin).toString(), + new URL(successUrl, origin).toString(), 301, cors.getHeaders() ) From a7fb9d6478ea46fbc58b0e4a0909c1ec202f598d Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 12 Jul 2023 15:39:30 +0100 Subject: [PATCH 004/149] feat: generate pdf function --- generate-pdf/.gitignore | 130 +++++++++++++++++++++++++++++++++ generate-pdf/.prettierrc.json | 6 ++ generate-pdf/README.md | 13 ++++ generate-pdf/package-lock.json | 87 ++++++++++++++++++++++ generate-pdf/package.json | 19 +++++ generate-pdf/src/main.js | 70 ++++++++++++++++++ 6 files changed, 325 insertions(+) create mode 100644 generate-pdf/.gitignore create mode 100644 generate-pdf/.prettierrc.json create mode 100644 generate-pdf/README.md create mode 100644 generate-pdf/package-lock.json create mode 100644 generate-pdf/package.json create mode 100644 generate-pdf/src/main.js diff --git a/generate-pdf/.gitignore b/generate-pdf/.gitignore new file mode 100644 index 00000000..6a7d6d8e --- /dev/null +++ b/generate-pdf/.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/generate-pdf/.prettierrc.json b/generate-pdf/.prettierrc.json new file mode 100644 index 00000000..fa51da29 --- /dev/null +++ b/generate-pdf/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/generate-pdf/README.md b/generate-pdf/README.md new file mode 100644 index 00000000..76a3183a --- /dev/null +++ b/generate-pdf/README.md @@ -0,0 +1,13 @@ +# PDF Generator Function + +This function allows you to generate a PDF document, specifically tailored towards creating an invoice. The function generates random order information and converts it into a structured PDF invoice. + +## Usage + +This function doesn't expect any parameters and only supports one type of request: + +1. **Generating a PDF Invoice** + + - **Request Type:** GET + - **Response:** + - On success, the function will respond with a binary stream of the generated PDF document. The `Content-Type` of the response will be set as `application/pdf`. diff --git a/generate-pdf/package-lock.json b/generate-pdf/package-lock.json new file mode 100644 index 00000000..0d2f8aad --- /dev/null +++ b/generate-pdf/package-lock.json @@ -0,0 +1,87 @@ +{ + "name": "generate-pdf", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "generate-pdf", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@faker-js/faker": "^8.0.2", + "pdf-lib": "^1.17.1" + }, + "devDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/@faker-js/faker": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.0.2.tgz", + "integrity": "sha512-Uo3pGspElQW91PCvKSIAXoEgAUlRnH29sX2/p89kg7sP1m2PzCufHINd0FhTXQf6DYGiUlVncdSPa2F9wxed2A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, + "node_modules/@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "dependencies": { + "pako": "^1.0.6" + } + }, + "node_modules/@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "dependencies": { + "pako": "^1.0.10" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "dependencies": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, + "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/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } +} diff --git a/generate-pdf/package.json b/generate-pdf/package.json new file mode 100644 index 00000000..de6b8773 --- /dev/null +++ b/generate-pdf/package.json @@ -0,0 +1,19 @@ +{ + "name": "generate-pdf", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "type": "module", + "scripts": { + "format": "prettier --write src/**/*.js" + }, + "author": "", + "license": "MIT", + "dependencies": { + "@faker-js/faker": "^8.0.2", + "pdf-lib": "^1.17.1" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/generate-pdf/src/main.js b/generate-pdf/src/main.js new file mode 100644 index 00000000..c6d34ccc --- /dev/null +++ b/generate-pdf/src/main.js @@ -0,0 +1,70 @@ +import { PDFDocument } from 'pdf-lib' +import { faker } from '@faker-js/faker' + +export default async ({ res, log }) => { + const fakeOrder = generateFakeOrder() + log(`Generated fake order: ${JSON.stringify(fakeOrder, null, 2)}`) + + const pdfBuffer = await createPdf(fakeOrder) + log('PDF created.') + + return res.send(pdfBuffer, 200, { 'Content-Type': 'application/pdf' }) +} + +function generateFakeOrder() { + const items = Array.from( + { length: faker.number.int({ min: 1, max: 5 }) }, + () => ({ + description: faker.commerce.productName(), + quantity: faker.number.int({ min: 1, max: 10 }), + cost: faker.commerce.price(), + }) + ) + + return { + id: faker.string.uuid(), + date: faker.date.past(), + name: faker.person.fullName(), + items, + total: items.reduce((acc, { cost }) => acc + parseFloat(cost), 0), + } +} + +async function createPdf({ id, date, name, items, total }) { + const document = await PDFDocument.create() + const page = document.addPage([595.28, 841.89]) // A4 size + + page.drawText('Sample Invoice', { x: 50, y: 750, size: 20 }) + page.drawText(new Date(date).toLocaleDateString(), { + x: 400, + y: 750, + size: 15, + }) + + page.drawText(`Hello, ${name}!`, { + x: 50, + y: 700, + size: 30, + }) + + page.drawText(`Order ID: ${id}`, { + x: 50, + y: 650, + size: 10, + }) + + page.drawText(`Total: $${total}`, { x: 50, y: 600, size: 15 }) + + const orderList = items + .map( + ({ description, quantity, cost }) => + `${description} x ${quantity} = $${cost}` + ) + .join('\n') + + page.drawText(orderList, { x: 50, y: 550, size: 15 }) + + const pdfBytes = await document.save() + + return Buffer.from(pdfBytes.buffer) +} From 6ef0ae853b6deaebfc0634eb89589b238026b077 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 12 Jul 2023 15:41:47 +0100 Subject: [PATCH 005/149] docs: update readme --- generate-pdf/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/generate-pdf/README.md b/generate-pdf/README.md index 76a3183a..2a18fd0c 100644 --- a/generate-pdf/README.md +++ b/generate-pdf/README.md @@ -1,13 +1,12 @@ -# PDF Generator Function +# Generate PDF -This function allows you to generate a PDF document, specifically tailored towards creating an invoice. The function generates random order information and converts it into a structured PDF invoice. +This function demonstrates using functions to generate a PDF document. The function generates random order information and converts it into a structured PDF invoice. ## Usage This function doesn't expect any parameters and only supports one type of request: 1. **Generating a PDF Invoice** - - **Request Type:** GET - **Response:** - On success, the function will respond with a binary stream of the generated PDF document. The `Content-Type` of the response will be set as `application/pdf`. From 59fa20b14997f155cd0980cf50579977b2ccdfc9 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 12 Jul 2023 16:00:39 +0100 Subject: [PATCH 006/149] feat: GitHub Issue Bot function --- github-issue-bot/.gitignore | 130 +++++++++++++ github-issue-bot/.prettierrc.json | 6 + github-issue-bot/README.md | 35 ++++ github-issue-bot/package-lock.json | 275 ++++++++++++++++++++++++++++ github-issue-bot/package.json | 20 ++ github-issue-bot/src/environment.js | 19 ++ github-issue-bot/src/github.js | 25 +++ github-issue-bot/src/main.js | 32 ++++ 8 files changed, 542 insertions(+) create mode 100644 github-issue-bot/.gitignore create mode 100644 github-issue-bot/.prettierrc.json create mode 100644 github-issue-bot/README.md create mode 100644 github-issue-bot/package-lock.json create mode 100644 github-issue-bot/package.json create mode 100644 github-issue-bot/src/environment.js create mode 100644 github-issue-bot/src/github.js create mode 100644 github-issue-bot/src/main.js diff --git a/github-issue-bot/.gitignore b/github-issue-bot/.gitignore new file mode 100644 index 00000000..6a7d6d8e --- /dev/null +++ b/github-issue-bot/.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/github-issue-bot/.prettierrc.json b/github-issue-bot/.prettierrc.json new file mode 100644 index 00000000..fa51da29 --- /dev/null +++ b/github-issue-bot/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/github-issue-bot/README.md b/github-issue-bot/README.md new file mode 100644 index 00000000..fd80555d --- /dev/null +++ b/github-issue-bot/README.md @@ -0,0 +1,35 @@ +# GitHub Issue Bot + +This function allows you to automate the process of responding to newly opened issues on a GitHub repository. The bot verifies the webhook request, checks if it's a new issue event and then posts a comment on the issue, inviting the user to join your Discord for quicker support. + +## Environment Variables + +To ensure the function operates as intended, ensure the following variables are set: + +- **GITHUB_WEBHOOK_SECRET**: The webhook secret from your GitHub repository settings. +- **GITHUB_TOKEN**: A personal access token from GitHub with the necessary permissions to post comments on issues. +- **DISCORD_LINK**: The link to your Discord community where users can get support. + +## GitHub Bot Setup + +Before you can use this function, you need to set up a webhook for your GitHub repository. You can do this in the settings page of your repository. Here, you can click the 'Webhooks' option and then the 'Add webhook' button. + +## GitHub Webhooks + +This function utilizes the GitHub Webhooks service. Webhooks allow you to build or set up integrations which subscribe to certain events on GitHub. When one of those events is triggered, GitHub sends a HTTP POST payload to the webhook's configured URL. We specifically use the 'issues' event to track new issues on the repository. + +## Usage + +This function supports the interaction of new issue events coming from GitHub: + +1. **Posting a comment on newly opened issues** + + - **Webhook Event Type:** 'issues' + - **Issue Action:** 'opened' + - **Response:** + - On successfully posting a comment, the function will end with an HTTP 204 No Content response. + - If there's an error while posting the comment, the function will respond with an HTTP 500 status and the error message. + +## Error Handling + +In case of any error during the webhook request verification or interaction handling, the function will return an HTTP 401 error with the message "Invalid signature" or an HTTP 500 error with the message "Error posting comment", respectively. In case any of the required environment variables is not set, the function will throw an error specifying which environment variable is missing. \ No newline at end of file diff --git a/github-issue-bot/package-lock.json b/github-issue-bot/package-lock.json new file mode 100644 index 00000000..2d366b1d --- /dev/null +++ b/github-issue-bot/package-lock.json @@ -0,0 +1,275 @@ +{ + "name": "github-issue-bot", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "github-issue-bot", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@octokit/core": "^4.2.4", + "@octokit/rest": "^19.0.13", + "@octokit/webhooks-methods": "^4.0.0" + }, + "devDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.4.tgz", + "integrity": "sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.4.tgz", + "integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==", + "dependencies": { + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/endpoint": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.6.tgz", + "integrity": "sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==", + "dependencies": { + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/graphql": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.6.tgz", + "integrity": "sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==", + "dependencies": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz", + "integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz", + "integrity": "sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==", + "dependencies": { + "@octokit/tsconfig": "^1.0.2", + "@octokit/types": "^9.2.3" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=4" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.2.3.tgz", + "integrity": "sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA==", + "dependencies": { + "@octokit/types": "^10.0.0" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-10.0.0.tgz", + "integrity": "sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg==", + "dependencies": { + "@octokit/openapi-types": "^18.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.8.tgz", + "integrity": "sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==", + "dependencies": { + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/request-error": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz", + "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==", + "dependencies": { + "@octokit/types": "^9.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/rest": { + "version": "19.0.13", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.13.tgz", + "integrity": "sha512-/EzVox5V9gYGdbAI+ovYj3nXQT1TtTHRT+0eZPcuC05UFSWO3mdO9UY1C0i2eLF9Un1ONJkAk+IEtYGAC+TahA==", + "dependencies": { + "@octokit/core": "^4.2.1", + "@octokit/plugin-paginate-rest": "^6.1.2", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^7.1.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/tsconfig": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@octokit/tsconfig/-/tsconfig-1.0.2.tgz", + "integrity": "sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==" + }, + "node_modules/@octokit/types": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.3.2.tgz", + "integrity": "sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==", + "dependencies": { + "@octokit/openapi-types": "^18.0.0" + } + }, + "node_modules/@octokit/webhooks-methods": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.0.0.tgz", + "integrity": "sha512-M8mwmTXp+VeolOS/kfRvsDdW+IO0qJ8kYodM/sAysk093q6ApgmBXwK1ZlUvAwXVrp/YVHp6aArj4auAxUAOFw==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "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/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } +} diff --git a/github-issue-bot/package.json b/github-issue-bot/package.json new file mode 100644 index 00000000..6563dec5 --- /dev/null +++ b/github-issue-bot/package.json @@ -0,0 +1,20 @@ +{ + "name": "github-issue-bot", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "type": "module", + "scripts": { + "format": "prettier --write src/**/*.js" + }, + "author": "", + "license": "MIT", + "dependencies": { + "@octokit/core": "^4.2.4", + "@octokit/rest": "^19.0.13", + "@octokit/webhooks-methods": "^4.0.0" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/github-issue-bot/src/environment.js b/github-issue-bot/src/environment.js new file mode 100644 index 00000000..46c03fb9 --- /dev/null +++ b/github-issue-bot/src/environment.js @@ -0,0 +1,19 @@ +export default function getEnvironment() { + return { + GITHUB_WEBHOOK_SECRET: getRequiredEnv('GITHUB_WEBHOOK_SECRET'), + GITHUB_TOKEN: getRequiredEnv('GITHUB_TOKEN'), + DISCORD_LINK: getRequiredEnv('DISCORD_LINK'), + } +} + +/** + * @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 +} diff --git a/github-issue-bot/src/github.js b/github-issue-bot/src/github.js new file mode 100644 index 00000000..b9a591a5 --- /dev/null +++ b/github-issue-bot/src/github.js @@ -0,0 +1,25 @@ +import { Octokit } from '@octokit/rest' +import getEnvironment from './environment' + +export default function GithubService() { + const { GITHUB_TOKEN } = getEnvironment() + + const octokit = new Octokit({ + auth: GITHUB_TOKEN, + }) + + return { + /** + * @param {any} issue + * @param {string} comment + */ + postComment: async function (issue, comment) { + await octokit.issues.createComment({ + owner: issue.repository.owner.login, + repo: issue.repository.name, + issue_number: issue.number, + body: comment, + }) + }, + } +} diff --git a/github-issue-bot/src/main.js b/github-issue-bot/src/main.js new file mode 100644 index 00000000..7e602a5f --- /dev/null +++ b/github-issue-bot/src/main.js @@ -0,0 +1,32 @@ +import getEnvironment from './environment' +import { verify } from '@octokit/webhooks-methods' +import GithubService from './github' + +export default async ({ res, req, log, error }) => { + const { GITHUB_WEBHOOK_SECRET, DISCORD_LINK } = getEnvironment() + const github = GithubService() + + const signature = req.headers['x-hub-signature-256'] + if ( + typeof signature !== 'string' || + (await verify(GITHUB_WEBHOOK_SECRET, req.bodyString, signature)) + ) { + error('Invalid signature') + return res.json({ error: 'Invalid signature' }, 401) + } + + if (req.headers['x-github-event'] === 'issues') { + const { issue } = req.body + if (!issue || req.body.action !== 'opened') { + log('No issue provided or not opened event') + return res.json({ success: true }) + } + + await github.postComment( + issue, + `Thanks for the issue report @${issue.user.login}! I'm inviting you to join our Discord for quicker support: ${DISCORD_LINK}` + ) + } + + return res.json({ success: true }) +} From d77d50e188e26eb71e8c862ac898416dca0c5891 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 14 Jul 2023 13:30:57 +0100 Subject: [PATCH 007/149] feat: Discord Command Bot function --- discord-command-bot/.gitignore | 130 +++++++++++++++++++++++++ discord-command-bot/.prettierrc.json | 6 ++ discord-command-bot/README.md | 35 +++++++ discord-command-bot/env.d.ts | 9 ++ discord-command-bot/package-lock.json | 50 ++++++++++ discord-command-bot/package.json | 18 ++++ discord-command-bot/src/discord.js | 14 +++ discord-command-bot/src/environment.js | 17 ++++ discord-command-bot/src/main.js | 24 +++++ 9 files changed, 303 insertions(+) create mode 100644 discord-command-bot/.gitignore create mode 100644 discord-command-bot/.prettierrc.json create mode 100644 discord-command-bot/README.md create mode 100644 discord-command-bot/env.d.ts create mode 100644 discord-command-bot/package-lock.json create mode 100644 discord-command-bot/package.json create mode 100644 discord-command-bot/src/discord.js create mode 100644 discord-command-bot/src/environment.js create mode 100644 discord-command-bot/src/main.js diff --git a/discord-command-bot/.gitignore b/discord-command-bot/.gitignore new file mode 100644 index 00000000..6a7d6d8e --- /dev/null +++ b/discord-command-bot/.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/discord-command-bot/.prettierrc.json b/discord-command-bot/.prettierrc.json new file mode 100644 index 00000000..fa51da29 --- /dev/null +++ b/discord-command-bot/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/discord-command-bot/README.md b/discord-command-bot/README.md new file mode 100644 index 00000000..b15ce1c4 --- /dev/null +++ b/discord-command-bot/README.md @@ -0,0 +1,35 @@ +# Discord Command Bot + +This function allows you to implement a simple command-interaction for a Discord bot using Discord Interactions. This bot is able to verify requests and handle them. In its current implementation, it responds to the '/hello' command with a message. + +## Environment Variables + +To ensure the function operates as intended, ensure the following variable is set: + +- **DISCORD_PUBLIC_KEY**: This is the public key of your Discord bot. + +## Discord Setup + +Before you can use this function, you need to have a bot set up on the Discord Developer Portal. You can create a new bot on the [Discord Developer Portal](https://discord.com/developers/applications) and navigate to the Bot section under the settings page of your application. Here, you can click the 'Add Bot' button. + +## Discord API & Interactions + +This function utilizes the Discord Interactions API. Interactions are the foundation upon which commands, components, and future user-input features are built. Discord provides a range of interaction types like `APPLICATION_COMMAND` which we use for our '/hello' command. + +To add an interaction to your bot, you need to define it first in the Discord Developer Portal under your application settings. Go to the 'Interactions' section and add a new command. In this case, you would add a command named 'hello'. + +## Usage + +This function supports the interaction of command type coming from Discord: + +1. **Executing the '/hello' command** + + - **Interaction Type:** APPLICATION_COMMAND + - **Command:** '/hello' + - **Response:** + - On success, the function will respond with "Hello from Appwrite 👋". + - If the command is not '/hello', the function will respond with a simple acknowledgement (PONG). + +## Error Handling + +In case of any error during interaction handling, the function will return a 500 error with the message "Failed to process interaction". \ No newline at end of file diff --git a/discord-command-bot/env.d.ts b/discord-command-bot/env.d.ts new file mode 100644 index 00000000..6198fea8 --- /dev/null +++ b/discord-command-bot/env.d.ts @@ -0,0 +1,9 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + DISCORD_PUBLIC_KEY?: string; + } + } +} + +export {}; diff --git a/discord-command-bot/package-lock.json b/discord-command-bot/package-lock.json new file mode 100644 index 00000000..12dd82fa --- /dev/null +++ b/discord-command-bot/package-lock.json @@ -0,0 +1,50 @@ +{ + "name": "discord-bot", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "discord-bot", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "discord-interactions": "^3.4.0" + }, + "devDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/discord-interactions": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/discord-interactions/-/discord-interactions-3.4.0.tgz", + "integrity": "sha512-DG0Jxdd/FcK8liAPhIP4u5YHpnz50JWn9DK4OavxsLD49/WGimXtP3EdOY439MaWyCgQfsfFkA1GsTEyu63RzA==", + "dependencies": { + "tweetnacl": "^1.0.3" + }, + "engines": { + "node": ">=12" + } + }, + "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/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + } + } +} diff --git a/discord-command-bot/package.json b/discord-command-bot/package.json new file mode 100644 index 00000000..b331aff1 --- /dev/null +++ b/discord-command-bot/package.json @@ -0,0 +1,18 @@ +{ + "name": "discord-bot", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "type": "module", + "scripts": { + "format": "prettier --write src/**/*.js" + }, + "author": "", + "license": "MIT", + "dependencies": { + "discord-interactions": "^3.4.0" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/discord-command-bot/src/discord.js b/discord-command-bot/src/discord.js new file mode 100644 index 00000000..bfc780f8 --- /dev/null +++ b/discord-command-bot/src/discord.js @@ -0,0 +1,14 @@ +import getEnvironment from './environment' +import { verifyKey } from 'discord-interactions' + +export default { + verifyWebhook: async function (req) { + const { DISCORD_PUBLIC_KEY } = getEnvironment() + return await verifyKey( + req.bodyString, + req.headers['x-signature-ed25519'], + req.headers['x-signature-timestamp'], + DISCORD_PUBLIC_KEY + ) + }, +} diff --git a/discord-command-bot/src/environment.js b/discord-command-bot/src/environment.js new file mode 100644 index 00000000..4e8dae78 --- /dev/null +++ b/discord-command-bot/src/environment.js @@ -0,0 +1,17 @@ +export default function getEnvironment() { + return { + DISCORD_PUBLIC_KEY: getRequiredEnv('DISCORD_PUBLIC_KEY'), + } +} + +/** + * @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 +} diff --git a/discord-command-bot/src/main.js b/discord-command-bot/src/main.js new file mode 100644 index 00000000..74bc3167 --- /dev/null +++ b/discord-command-bot/src/main.js @@ -0,0 +1,24 @@ +import { InteractionResponseType, InteractionType } from 'discord-interactions' +import discord from './discord' + +export default async ({ req, res, log, error }) => { + if (!(await discord.verifyWebhook(req))) { + error('Invalid request.') + return res.send('Invalid request signature', 401) + } + + const interaction = req.body + if ( + interaction.type === InteractionType.APPLICATION_COMMAND && + interaction.data.name === 'hello' + ) { + return res.json({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: 'Hello from Appwrite 👋', + }, + }) + } + + return res.json(InteractionResponseType.PONG) +} From a3dbe55c7a99fd6c5ff005d214cd3835de6d0905 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:09:37 +0100 Subject: [PATCH 008/149] feat: Prompt ChatGPT function --- prompt-chatgpt/.gitignore | 1 + prompt-chatgpt/.prettierrc.json | 6 ++ prompt-chatgpt/README.md | 56 +++++++++++++ prompt-chatgpt/package-lock.json | 126 ++++++++++++++++++++++++++++++ prompt-chatgpt/package.json | 18 +++++ prompt-chatgpt/src/environment.js | 35 +++++++++ prompt-chatgpt/src/main.js | 36 +++++++++ prompt-chatgpt/static/index.html | 86 ++++++++++++++++++++ 8 files changed, 364 insertions(+) create mode 100644 prompt-chatgpt/.gitignore create mode 100644 prompt-chatgpt/.prettierrc.json create mode 100644 prompt-chatgpt/README.md create mode 100644 prompt-chatgpt/package-lock.json create mode 100644 prompt-chatgpt/package.json create mode 100644 prompt-chatgpt/src/environment.js create mode 100644 prompt-chatgpt/src/main.js create mode 100644 prompt-chatgpt/static/index.html diff --git a/prompt-chatgpt/.gitignore b/prompt-chatgpt/.gitignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/prompt-chatgpt/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/prompt-chatgpt/.prettierrc.json b/prompt-chatgpt/.prettierrc.json new file mode 100644 index 00000000..fa51da29 --- /dev/null +++ b/prompt-chatgpt/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/prompt-chatgpt/README.md b/prompt-chatgpt/README.md new file mode 100644 index 00000000..37660b79 --- /dev/null +++ b/prompt-chatgpt/README.md @@ -0,0 +1,56 @@ +# OpenAI GPT-3 Chat Completion Function + +**Warning:** This function uses the OpenAI API, which is a paid service. You will be charged for each request made to the API. For more information, see the [OpenAI pricing page](https://openai.com/pricing/). + +This function enables the interaction with OpenAI's GPT-3 model in a chat-like format. Users send a request with a prompt and receive a text response generated by the model. Additionally, there's a simple GET endpoint that serves an HTML page. + +## Environment Variables + +To ensure the function operates as intended, ensure the following variables are set: + +- **OPENAI_API_KEY**: This is your OpenAI API key. +- **OPENAI_MAX_TOKENS**: This is the maximum number of tokens that the OpenAI response should contain. Be aware that OpenAI models read and write a maximum number of tokens per API call, which varies depending on the model. For GPT-3.5-turbo, the limit is 4096 tokens. + +## Usage + +This function supports two types of requests: + +1. **Serving the HTML page** + + - **Request Type:** GET + - **Response:** + - On success, the function will respond with the HTML file contained in the static folder. + +2. **Interacting with the GPT-3 Model** + + - **Request Type:** POST + - **Body:** + - The text string that is used as a prompt for the model. + - **Response:** + - On success, the function will respond with the text generated by the GPT-3 model. + - If the request body is missing, a 400 error will be returned with the message 'Missing body with a prompt.' + +## Example + +Here's a simple example of how to use the POST endpoint: + +**Request:** + +``` +POST / HTTP/1.1 +Host: function-host +Content-Type: text/plain + +"What is the weather like today?" +``` + +**Response:** + +```json +{ + "role": "assistant", + "content": "As an AI model, I don't have real-time data capabilities, so I'm unable to provide the current weather. However, you can check a reliable weather website or app for this information." +} +``` + +Note: The response can vary as it is generated by the AI model based on the input prompt. \ No newline at end of file diff --git a/prompt-chatgpt/package-lock.json b/prompt-chatgpt/package-lock.json new file mode 100644 index 00000000..e8a91d71 --- /dev/null +++ b/prompt-chatgpt/package-lock.json @@ -0,0 +1,126 @@ +{ + "name": "prompt-chatgpt", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prompt-chatgpt", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "openai": "^3.3.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/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/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/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/openai": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.3.0.tgz", + "integrity": "sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==", + "dependencies": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + } + }, + "node_modules/openai/node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, + "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" + } + } + } +} diff --git a/prompt-chatgpt/package.json b/prompt-chatgpt/package.json new file mode 100644 index 00000000..e140bd53 --- /dev/null +++ b/prompt-chatgpt/package.json @@ -0,0 +1,18 @@ +{ + "name": "prompt-chatgpt", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "scripts": { + "format": "prettier --write src/**/*.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "openai": "^3.3.0" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/prompt-chatgpt/src/environment.js b/prompt-chatgpt/src/environment.js new file mode 100644 index 00000000..aaee1afa --- /dev/null +++ b/prompt-chatgpt/src/environment.js @@ -0,0 +1,35 @@ +module.exports = function getEnvironment() { + return { + OPENAI_API_KEY: getRequiredEnv('OPENAI_API_KEY'), + OPENAI_MAX_TOKENS: getNumberEnv('OPENAI_MAX_TOKENS') ?? 64, + } +} + +/** + * @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 {number | undefined} + */ +function getNumberEnv(key) { + const value = process.env[key] + if (value === undefined) { + return undefined + } + + try { + return parseInt(value) + } catch (e) { + throw new Error(`Environment variable ${key} is not a number`) + } +} diff --git a/prompt-chatgpt/src/main.js b/prompt-chatgpt/src/main.js new file mode 100644 index 00000000..f1733d59 --- /dev/null +++ b/prompt-chatgpt/src/main.js @@ -0,0 +1,36 @@ +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' +import { OpenAIApi, Configuration } from 'openai' +import getEnvironment from './environment' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const staticFolder = path.join(__dirname, '../static') + +export default async ({ req, res }) => { + const { OPENAI_API_KEY, OPENAI_MAX_TOKENS } = getEnvironment() + + const configuration = new Configuration({ + apiKey: OPENAI_API_KEY, + }) + const openai = new OpenAIApi(configuration) + + if (req.method === 'GET') { + let html = fs.readFileSync(path.join(staticFolder, 'index.html')).toString() + + return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }) + } + + if (!req.bodyString) { + return res.send('Missing body with a prompt.', 400) + } + + const chatCompletion = await openai.createChatCompletion({ + model: 'gpt-3.5-turbo', + max_tokens: OPENAI_MAX_TOKENS, + messages: [{ role: 'user', content: req.bodyString }], + }) + + return res.send(chatCompletion.data.choices[0].message, 200) +} diff --git a/prompt-chatgpt/static/index.html b/prompt-chatgpt/static/index.html new file mode 100644 index 00000000..3e1017f9 --- /dev/null +++ b/prompt-chatgpt/static/index.html @@ -0,0 +1,86 @@ + + + + + + + Prompt ChatGPT Demo + + + + + + + + + +
+
+
+
+

Prompt ChatGPT Demo

+ +
+

+ This is demo application. You can ue this app to ensure + implementation with Chat GPT works properly. Use input below to + enter prompts and get a response. +

+
+
+
+
+
+
+ + +
+ + +
+ +
+
+
+ + From 9f51c2508bf19f9c358a2b8cf0c8bcae3ac285e9 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:09:57 +0100 Subject: [PATCH 009/149] chore: add type module --- prompt-chatgpt/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/prompt-chatgpt/package.json b/prompt-chatgpt/package.json index e140bd53..4ec02ca1 100644 --- a/prompt-chatgpt/package.json +++ b/prompt-chatgpt/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "src/main.js", + "type": "module", "scripts": { "format": "prettier --write src/**/*.js" }, From 1fa57192be60ee79f938b277465b81d5098de6b3 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:34:58 +0100 Subject: [PATCH 010/149] fix: esm migration --- discord-command-bot/src/discord.js | 25 ++++++++++++++----------- discord-command-bot/src/environment.js | 12 ++++++------ discord-command-bot/src/main.js | 8 ++++++-- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/discord-command-bot/src/discord.js b/discord-command-bot/src/discord.js index bfc780f8..9b26ae07 100644 --- a/discord-command-bot/src/discord.js +++ b/discord-command-bot/src/discord.js @@ -1,14 +1,17 @@ -import getEnvironment from './environment' import { verifyKey } from 'discord-interactions' -export default { - verifyWebhook: async function (req) { - const { DISCORD_PUBLIC_KEY } = getEnvironment() - return await verifyKey( - req.bodyString, - req.headers['x-signature-ed25519'], - req.headers['x-signature-timestamp'], - DISCORD_PUBLIC_KEY - ) - }, +function DiscordService(environment) { + return { + verifyWebhook: async function (req) { + const { DISCORD_PUBLIC_KEY } = environment + return await verifyKey( + req.bodyString, + req.headers['x-signature-ed25519'], + req.headers['x-signature-timestamp'], + DISCORD_PUBLIC_KEY + ) + }, + } } + +export default DiscordService diff --git a/discord-command-bot/src/environment.js b/discord-command-bot/src/environment.js index 4e8dae78..627789e0 100644 --- a/discord-command-bot/src/environment.js +++ b/discord-command-bot/src/environment.js @@ -1,9 +1,3 @@ -export default function getEnvironment() { - return { - DISCORD_PUBLIC_KEY: getRequiredEnv('DISCORD_PUBLIC_KEY'), - } -} - /** * @param {string} key * @return {string} @@ -15,3 +9,9 @@ function getRequiredEnv(key) { } return value } + +export default function EnvironmentService() { + return { + DISCORD_PUBLIC_KEY: getRequiredEnv('DISCORD_PUBLIC_KEY'), + } +} diff --git a/discord-command-bot/src/main.js b/discord-command-bot/src/main.js index 74bc3167..3cc061c9 100644 --- a/discord-command-bot/src/main.js +++ b/discord-command-bot/src/main.js @@ -1,7 +1,11 @@ import { InteractionResponseType, InteractionType } from 'discord-interactions' -import discord from './discord' +import DiscordService from './discord.js' +import EnvironmentService from './environment.js' + +export default async ({ req, res, error }) => { + const environment = EnvironmentService() + const discord = DiscordService(environment) -export default async ({ req, res, log, error }) => { if (!(await discord.verifyWebhook(req))) { error('Invalid request.') return res.send('Invalid request signature', 401) From e3201c488d14b8a7e3bf91b509b4aa3b10cfa8cd Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:45:10 +0100 Subject: [PATCH 011/149] fix: esm migration --- email-contact-form/src/cors.js | 6 ++---- email-contact-form/src/environment.js | 4 +++- email-contact-form/src/mail.js | 6 ++---- email-contact-form/src/main.js | 13 +++++++------ 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/email-contact-form/src/cors.js b/email-contact-form/src/cors.js index f0e6a5e4..11c4d6b0 100644 --- a/email-contact-form/src/cors.js +++ b/email-contact-form/src/cors.js @@ -1,10 +1,8 @@ -import getEnvironment from './environment' - /** * @param {string} origin Origin header of the request */ -export default function CorsService(origin) { - const { ALLOWED_ORIGINS } = getEnvironment() +export default function CorsService(origin, environment) { + const { ALLOWED_ORIGINS } = environment return { /** diff --git a/email-contact-form/src/environment.js b/email-contact-form/src/environment.js index 3ce204fe..026927b2 100644 --- a/email-contact-form/src/environment.js +++ b/email-contact-form/src/environment.js @@ -10,7 +10,7 @@ function getRequiredEnv(key) { return value } -export default function getEnvironment() { +function EnvironmentService() { return { SUBMIT_EMAIL: getRequiredEnv('SUBMIT_EMAIL'), SMTP_HOST: getRequiredEnv('SMTP_HOST'), @@ -20,3 +20,5 @@ export default function getEnvironment() { ALLOWED_ORIGINS: process.env.ALLOWED_ORIGINS || '*', } } + +export default EnvironmentService diff --git a/email-contact-form/src/mail.js b/email-contact-form/src/mail.js index cb6a859b..d98c1a70 100644 --- a/email-contact-form/src/mail.js +++ b/email-contact-form/src/mail.js @@ -1,9 +1,7 @@ -import getEnvironment from './environment' import nodemailer from 'nodemailer' -export default function MailService() { - const { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD } = - getEnvironment() +export default function MailService(environment) { + const { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD } = environment const transport = nodemailer.createTransport({ // @ts-ignore diff --git a/email-contact-form/src/main.js b/email-contact-form/src/main.js index 799a4823..49883a87 100644 --- a/email-contact-form/src/main.js +++ b/email-contact-form/src/main.js @@ -2,9 +2,9 @@ import querystring from 'node:querystring' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import path from 'node:path' -import getEnvironment from './environment' -import CorsService from './cors' -import MailService from './mail' +import CorsService from './cors.js' +import MailService from './mail.js' +import EnvironmentService from './environment.js' const ErrorCode = { INVALID_REQUEST: 'invalid-request', @@ -24,7 +24,8 @@ const __dirname = path.dirname(__filename) const staticFolder = path.join(__dirname, '../static') export default async ({ req, res, log, error }) => { - const { SUBMIT_EMAIL, ALLOWED_ORIGINS } = getEnvironment() + const environment = EnvironmentService() + const { SUBMIT_EMAIL, ALLOWED_ORIGINS } = environment if (ALLOWED_ORIGINS === '*') { log('WARNING: Allowing requests from any origin - this is a security risk!') @@ -50,8 +51,8 @@ export default async ({ req, res, log, error }) => { return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)) } - const cors = CorsService(origin) - const mail = MailService() + const cors = CorsService(origin, environment) + const mail = MailService(environment) if (!cors.isOriginPermitted()) { error('Origin not permitted.') From 7840536b88784d791ee19461ff091528d6fb5b81 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:22:43 +0100 Subject: [PATCH 012/149] fix: esm migration --- github-issue-bot/src/environment.js | 18 ++++++++++-------- github-issue-bot/src/github.js | 19 ++++++++++++++++--- github-issue-bot/src/main.js | 17 +++++++---------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/github-issue-bot/src/environment.js b/github-issue-bot/src/environment.js index 46c03fb9..b4a790a8 100644 --- a/github-issue-bot/src/environment.js +++ b/github-issue-bot/src/environment.js @@ -1,11 +1,3 @@ -export default function getEnvironment() { - return { - GITHUB_WEBHOOK_SECRET: getRequiredEnv('GITHUB_WEBHOOK_SECRET'), - GITHUB_TOKEN: getRequiredEnv('GITHUB_TOKEN'), - DISCORD_LINK: getRequiredEnv('DISCORD_LINK'), - } -} - /** * @param {string} key * @return {string} @@ -17,3 +9,13 @@ function getRequiredEnv(key) { } return value } + +function EnvironmentService() { + return { + GITHUB_WEBHOOK_SECRET: getRequiredEnv('GITHUB_WEBHOOK_SECRET'), + GITHUB_TOKEN: getRequiredEnv('GITHUB_TOKEN'), + DISCORD_LINK: getRequiredEnv('DISCORD_LINK'), + } +} + +export default EnvironmentService diff --git a/github-issue-bot/src/github.js b/github-issue-bot/src/github.js index b9a591a5..8e23b5cf 100644 --- a/github-issue-bot/src/github.js +++ b/github-issue-bot/src/github.js @@ -1,14 +1,27 @@ import { Octokit } from '@octokit/rest' -import getEnvironment from './environment' +import { verify } from '@octokit/webhooks-methods' -export default function GithubService() { - const { GITHUB_TOKEN } = getEnvironment() +export default function GithubService(environment) { + const { GITHUB_TOKEN, GITHUB_WEBHOOK_SECRET } = environment const octokit = new Octokit({ auth: GITHUB_TOKEN, }) return { + /** + * + * @param {*} req + * @returns {Promise} + */ + verifyWebhook: async function (req) { + const signature = req.headers['x-hub-signature-256'] + + return ( + typeof signature !== 'string' || + (await verify(GITHUB_WEBHOOK_SECRET, req.bodyString, signature)) + ) + }, /** * @param {any} issue * @param {string} comment diff --git a/github-issue-bot/src/main.js b/github-issue-bot/src/main.js index 7e602a5f..b5aef249 100644 --- a/github-issue-bot/src/main.js +++ b/github-issue-bot/src/main.js @@ -1,16 +1,13 @@ -import getEnvironment from './environment' -import { verify } from '@octokit/webhooks-methods' -import GithubService from './github' +import EnvironmentService from './environment.js' +import GithubService from './github.js' export default async ({ res, req, log, error }) => { - const { GITHUB_WEBHOOK_SECRET, DISCORD_LINK } = getEnvironment() - const github = GithubService() + const environment = EnvironmentService() + const github = GithubService(environment) - const signature = req.headers['x-hub-signature-256'] - if ( - typeof signature !== 'string' || - (await verify(GITHUB_WEBHOOK_SECRET, req.bodyString, signature)) - ) { + const { DISCORD_LINK } = environment + + if (!(await github.verifyWebhook(req))) { error('Invalid signature') return res.json({ error: 'Invalid signature' }, 401) } From 0f5bdb63bd76263c53cc80ef3bcd8cbf334a1e0d Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:26:15 +0100 Subject: [PATCH 013/149] fix: esm migration --- prompt-chatgpt/src/environment.js | 16 +++++++++------- prompt-chatgpt/src/main.js | 5 ++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/prompt-chatgpt/src/environment.js b/prompt-chatgpt/src/environment.js index aaee1afa..ba31fc75 100644 --- a/prompt-chatgpt/src/environment.js +++ b/prompt-chatgpt/src/environment.js @@ -1,10 +1,3 @@ -module.exports = function getEnvironment() { - return { - OPENAI_API_KEY: getRequiredEnv('OPENAI_API_KEY'), - OPENAI_MAX_TOKENS: getNumberEnv('OPENAI_MAX_TOKENS') ?? 64, - } -} - /** * @param {string} key * @return {string} @@ -33,3 +26,12 @@ function getNumberEnv(key) { throw new Error(`Environment variable ${key} is not a number`) } } + +function EnvironmentService() { + return { + OPENAI_API_KEY: getRequiredEnv('OPENAI_API_KEY'), + OPENAI_MAX_TOKENS: getNumberEnv('OPENAI_MAX_TOKENS') ?? 64, + } +} + +export default EnvironmentService diff --git a/prompt-chatgpt/src/main.js b/prompt-chatgpt/src/main.js index f1733d59..f531f5c5 100644 --- a/prompt-chatgpt/src/main.js +++ b/prompt-chatgpt/src/main.js @@ -2,14 +2,14 @@ import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' import { OpenAIApi, Configuration } from 'openai' -import getEnvironment from './environment' +import EnvironmentService from './environment.js' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const staticFolder = path.join(__dirname, '../static') export default async ({ req, res }) => { - const { OPENAI_API_KEY, OPENAI_MAX_TOKENS } = getEnvironment() + const { OPENAI_API_KEY, OPENAI_MAX_TOKENS } = EnvironmentService() const configuration = new Configuration({ apiKey: OPENAI_API_KEY, @@ -18,7 +18,6 @@ export default async ({ req, res }) => { if (req.method === 'GET') { let html = fs.readFileSync(path.join(staticFolder, 'index.html')).toString() - return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }) } From 720e2166234b9ca0fa344493e7606ebdae346572 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 17 Jul 2023 13:45:57 +0100 Subject: [PATCH 014/149] feat: Analyze with Perspective API function --- analyze-with-perspectiveapi/.gitignore | 1 + analyze-with-perspectiveapi/.prettierrc.json | 6 ++ analyze-with-perspectiveapi/README.md | 42 +++++++++ analyze-with-perspectiveapi/env.d.ts | 9 ++ analyze-with-perspectiveapi/package-lock.json | 64 +++++++++++++ analyze-with-perspectiveapi/package.json | 19 ++++ analyze-with-perspectiveapi/src/main.js | 50 +++++++++++ analyze-with-perspectiveapi/static/index.html | 89 +++++++++++++++++++ 8 files changed, 280 insertions(+) create mode 100644 analyze-with-perspectiveapi/.gitignore create mode 100644 analyze-with-perspectiveapi/.prettierrc.json create mode 100644 analyze-with-perspectiveapi/README.md create mode 100644 analyze-with-perspectiveapi/env.d.ts create mode 100644 analyze-with-perspectiveapi/package-lock.json create mode 100644 analyze-with-perspectiveapi/package.json create mode 100644 analyze-with-perspectiveapi/src/main.js create mode 100644 analyze-with-perspectiveapi/static/index.html diff --git a/analyze-with-perspectiveapi/.gitignore b/analyze-with-perspectiveapi/.gitignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/analyze-with-perspectiveapi/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/analyze-with-perspectiveapi/.prettierrc.json b/analyze-with-perspectiveapi/.prettierrc.json new file mode 100644 index 00000000..fa51da29 --- /dev/null +++ b/analyze-with-perspectiveapi/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/analyze-with-perspectiveapi/README.md b/analyze-with-perspectiveapi/README.md new file mode 100644 index 00000000..85769c97 --- /dev/null +++ b/analyze-with-perspectiveapi/README.md @@ -0,0 +1,42 @@ +# Analyze with Perspective API Function + +This function allows you to interact with Google's Perspective API to analyze the perceived impact of a comment or a string of text. The API uses machine learning to score the text based on several aspects, such as its potential to be perceived as toxic. + +## Environment Variables + +To ensure the function operates as intended, ensure the following variable is set: + +- **PERSPECTIVE_API_KEY**: This is your Google Perspective API key. It allows your function to interact with Google's Perspective API. + +## Usage + +This function supports two types of requests: + +1. **Displaying the Input Form** + + - **Request Type:** GET + - **Response:** + - On success, the function will respond with an HTML form where users can input a string of text to be analyzed. + +2. **Analyzing a String of Text** + + - **Request Type:** POST + - **Content Type:** text/plain + - **Body:** + - The text to be analyzed + - **Response:** + - On success, the function will respond with the toxicity score of the provided text, as determined by Google's Perspective API. + - Example: `0.85`, which represents an 85% chance of the text being perceived as toxic. + +The application serves an HTML page which contains a form where the user can input text and submit it to the server for analysis. The submitted text is sent to the Perspective API, which returns a toxicity score (a number between 0 and 1, with 1 being the most toxic). This score is then displayed to the user. + +The POST request is made from the client-side JavaScript using the fetch API. The body of the request is the text to be analyzed. The request's content type is 'text/plain'. The server function reads this text, sends it to the Perspective API, and returns the toxicity score to the client. + +In the case of an error or unexpected status from the Perspective API, the server function returns a 500 status with the message 'Error analyzing text.'. + +Please note that you need to get an API key from Google's Perspective API to use this function. + +## HTML Interface + +The function has a basic page where users can type in text for analysis. The page uses AlpineJS for client-side interactivity. When a user types text and clicks the "Analyze" button, the text is sent to the server and analyzed. The analysis score, which represents the text's toxicity level, is then shown on the page. + diff --git a/analyze-with-perspectiveapi/env.d.ts b/analyze-with-perspectiveapi/env.d.ts new file mode 100644 index 00000000..1948246d --- /dev/null +++ b/analyze-with-perspectiveapi/env.d.ts @@ -0,0 +1,9 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + PERSPECTIVE_API_KEY: string + } + } +} + +export {} diff --git a/analyze-with-perspectiveapi/package-lock.json b/analyze-with-perspectiveapi/package-lock.json new file mode 100644 index 00000000..c630cf02 --- /dev/null +++ b/analyze-with-perspectiveapi/package-lock.json @@ -0,0 +1,64 @@ +{ + "name": "prompt-chatgpt", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prompt-chatgpt", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "undici": "^5.22.1" + }, + "devDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "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/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/undici": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=14.0" + } + } + } +} diff --git a/analyze-with-perspectiveapi/package.json b/analyze-with-perspectiveapi/package.json new file mode 100644 index 00000000..8ff200a5 --- /dev/null +++ b/analyze-with-perspectiveapi/package.json @@ -0,0 +1,19 @@ +{ + "name": "prompt-chatgpt", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "type": "module", + "scripts": { + "format": "prettier --write src/**/*.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "undici": "^5.22.1" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/analyze-with-perspectiveapi/src/main.js b/analyze-with-perspectiveapi/src/main.js new file mode 100644 index 00000000..ee192a76 --- /dev/null +++ b/analyze-with-perspectiveapi/src/main.js @@ -0,0 +1,50 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fetch } from 'undici' + +export default async ({ req, res }) => { + const { PERSPECTIVE_API_KEY } = process.env + + if (!PERSPECTIVE_API_KEY) { + throw new Error('Function is missing required environment variables.') + } + + if (req.method === 'GET') { + let html = fs + .readFileSync(path.join(__dirname, '../static/index.html')) + .toString() + + return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }) + } + + if (!req.bodyString) { + return res.send('Missing body with a prompt.', 400) + } + + const response = await fetch( + `https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key=${PERSPECTIVE_API_KEY}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + comment: { + text: req.bodyString, + type: 'PLAIN_TEXT', + }, + languages: ['en'], + requestedAttributes: { + TOXICITY: {}, + }, + }), + } + ) + + if (response.status !== 200) { + return res.send('Error analyzing text.', 500) + } + + const data = /** @type {*} */ (await response.json()) + return res.send(data.attributeScores.TOXICITY.summaryScore.value) +} diff --git a/analyze-with-perspectiveapi/static/index.html b/analyze-with-perspectiveapi/static/index.html new file mode 100644 index 00000000..4c6c5aa4 --- /dev/null +++ b/analyze-with-perspectiveapi/static/index.html @@ -0,0 +1,89 @@ + + + + + + + Analyze with Perspective API Demo + + + + + + + + +
+
+
+
+

Analyze with Perspective API Demo

+ +
+

+ This is demo application. You can use this app to ensure + implementation with Perspective API works properly. Use input below + to enter prompt and get toxicity of the message as response. +

+
+
+
+
+
+
+ + +
+ + +
+ +
+
+
+ + From 95463cdabdaa470256bad5745e5f871d089a1269 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 17 Jul 2023 21:19:20 +0100 Subject: [PATCH 015/149] feat: Censor with Redact function --- censor-with-redact/.gitignore | 1 + censor-with-redact/.prettierrc.json | 6 ++ censor-with-redact/README.md | 37 ++++++++++++ censor-with-redact/package-lock.json | 64 ++++++++++++++++++++ censor-with-redact/package.json | 19 ++++++ censor-with-redact/src/main.js | 41 +++++++++++++ censor-with-redact/static/index.html | 88 ++++++++++++++++++++++++++++ 7 files changed, 256 insertions(+) create mode 100644 censor-with-redact/.gitignore create mode 100644 censor-with-redact/.prettierrc.json create mode 100644 censor-with-redact/README.md create mode 100644 censor-with-redact/package-lock.json create mode 100644 censor-with-redact/package.json create mode 100644 censor-with-redact/src/main.js create mode 100644 censor-with-redact/static/index.html diff --git a/censor-with-redact/.gitignore b/censor-with-redact/.gitignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/censor-with-redact/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/censor-with-redact/.prettierrc.json b/censor-with-redact/.prettierrc.json new file mode 100644 index 00000000..fa51da29 --- /dev/null +++ b/censor-with-redact/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/censor-with-redact/README.md b/censor-with-redact/README.md new file mode 100644 index 00000000..1912ca3e --- /dev/null +++ b/censor-with-redact/README.md @@ -0,0 +1,37 @@ +# Censor with Redact Function + +This function allows you to redact sensitive information from a provided text string. It uses the Redact API by Pangea to censor the text. You can use this function to ensure any text you process does not unintentionally reveal sensitive or personally identifiable information. + +## Environment Variables + +To ensure the function operates as intended, ensure the following variables are set: + +- **PANGEA_REDACT_TOKEN**: This is your access token for the Pangea Redact API. + +## Usage + +This function supports two types of requests: + +1. **GET Request** + + - **Request Type:** GET + - **Response:** + - On success, the function will respond with an HTML page that can be used to enter text and see the redacted result. The HTML file is located in the 'static' folder, and it's named 'index.html'. + +2. **Redact Text** + + - **Request Type:** POST + - **Body:** + - The plain text string that you want to redact. + - **Response:** + - On success, the function will respond with the text string with sensitive information redacted. + +## Front-End Implementation + +The function includes a simple front-end HTML page as an example of how you could use the function from a web page. The front-end sends a POST request to the function when the 'Censor' button is clicked, and the result is displayed on the page. + +The HTML file (index.html) is located in the 'static' directory. It uses Alpine.js for interactivity and the @appwrite.io/pink CSS framework for styling. The page includes an input field where the user can type a message, a 'Censor' button to submit the message for redaction, and an area to display the redacted message. + +## Error Handling + +If the required environment variable `PANGEA_REDACT_TOKEN` is not set, the function will throw an error. Similarly, if a POST request is made without any body content, the function will return an HTTP 400 error with the message "Missing body with a prompt.". \ No newline at end of file diff --git a/censor-with-redact/package-lock.json b/censor-with-redact/package-lock.json new file mode 100644 index 00000000..11170539 --- /dev/null +++ b/censor-with-redact/package-lock.json @@ -0,0 +1,64 @@ +{ + "name": "censor-with-redact", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "censor-with-redact", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "undici": "^5.22.1" + }, + "devDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "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/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/undici": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=14.0" + } + } + } +} diff --git a/censor-with-redact/package.json b/censor-with-redact/package.json new file mode 100644 index 00000000..b8684cff --- /dev/null +++ b/censor-with-redact/package.json @@ -0,0 +1,19 @@ +{ + "name": "censor-with-redact", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "type": "module", + "scripts": { + "format": "prettier --write src/**/*.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "prettier": "^3.0.0" + }, + "dependencies": { + "undici": "^5.22.1" + } +} diff --git a/censor-with-redact/src/main.js b/censor-with-redact/src/main.js new file mode 100644 index 00000000..18fb802c --- /dev/null +++ b/censor-with-redact/src/main.js @@ -0,0 +1,41 @@ +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +const staticFolder = path.join(__dirname, '../static') + +export default async ({ req, res }) => { + const { PANGEA_REDACT_TOKEN } = process.env + + if (!PANGEA_REDACT_TOKEN) { + throw new Error('Function is missing required environment variables.') + } + + if (req.method === 'GET') { + const html = fs + .readFileSync(path.join(staticFolder, 'index.html')) + .toString() + return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }) + } + + if (!req.bodyString) { + return res.send('Missing body with a prompt.', 400) + } + + const response = await fetch(`https://redact.aws.eu.pangea.cloud/v1/redact`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${PANGEA_REDACT_TOKEN}`, + }, + body: JSON.stringify({ + text: req.bodyString, + }), + }) + + const data = /** @type {*} */ (await response.json()) + return res.send(data.result.redacted_text) +} diff --git a/censor-with-redact/static/index.html b/censor-with-redact/static/index.html new file mode 100644 index 00000000..a8177be4 --- /dev/null +++ b/censor-with-redact/static/index.html @@ -0,0 +1,88 @@ + + + + + + + Censor with Redact API Demo + + + + + + + + + +
+
+
+
+

Censor with Redact API Demo

+ +
+

+ This is demo application. You can ue this app to ensure + implementation with Redact API works properly. Use input below to + enter text and get censored message as response. +

+
+
+
+
+
+
+ + +
+ + +
+ +
+
+
+ + From 97d9dbc5dd728bd629280e3d996de87e4a0da60e Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Mon, 17 Jul 2023 22:07:37 +0100 Subject: [PATCH 016/149] feat: Sync with Algolia function --- sync-with-algolia/.gitignore | 1 + sync-with-algolia/.prettierrc.json | 6 + sync-with-algolia/README.md | 39 ++++ sync-with-algolia/package-lock.json | 270 +++++++++++++++++++++++++++ sync-with-algolia/package.json | 20 ++ sync-with-algolia/src/environment.js | 30 +++ sync-with-algolia/src/main.js | 94 ++++++++++ sync-with-algolia/static/index.html | 99 ++++++++++ 8 files changed, 559 insertions(+) create mode 100644 sync-with-algolia/.gitignore create mode 100644 sync-with-algolia/.prettierrc.json create mode 100644 sync-with-algolia/README.md create mode 100644 sync-with-algolia/package-lock.json create mode 100644 sync-with-algolia/package.json create mode 100644 sync-with-algolia/src/environment.js create mode 100644 sync-with-algolia/src/main.js create mode 100644 sync-with-algolia/static/index.html diff --git a/sync-with-algolia/.gitignore b/sync-with-algolia/.gitignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/sync-with-algolia/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/sync-with-algolia/.prettierrc.json b/sync-with-algolia/.prettierrc.json new file mode 100644 index 00000000..fa51da29 --- /dev/null +++ b/sync-with-algolia/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/sync-with-algolia/README.md b/sync-with-algolia/README.md new file mode 100644 index 00000000..b2b5c18b --- /dev/null +++ b/sync-with-algolia/README.md @@ -0,0 +1,39 @@ +# Appwrite - Algolia Sync Function + +This function helps in syncing the data between Appwrite's database and Algolia's search index. It fetches the documents from Appwrite database and updates the Algolia search index. + +## Environment Variables + +To ensure the function operates as intended, ensure the following variables are set: + +- **ALGOLIA_APP_ID**: This is your Algolia application's ID. +- **ALGOLIA_ADMIN_API_KEY**: This is your Algolia application's Admin API key. +- **ALGOLIA_INDEX_ID**: This is the ID of the Algolia index where the data is stored. +- **ALGOLIA_SEARCH_API_KEY**: This is the Search API key of your Algolia application. +- **APPWRITE_API_KEY**: This is your Appwrite project's API key. +- **APPWRITE_DATABASE_ID**: This is the ID of your Appwrite database where the data is stored. +- **APPWRITE_COLLECTION_ID**: This is the ID of the collection within the Appwrite database. +- **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. + +## Usage + +This function supports two types of requests: + +1. **Retrieving the Search Interface** + + - **Request Type:** GET + - **Response:** + - On success, the function will respond with an HTML interface for Algolia search. The response content type is `text/html`. + +2. **Syncing Appwrite Database with Algolia Index** + + - **Request Type:** POST + - **Response:** + - On success, the function will synchronize the Appwrite database with the specified Algolia index and return an empty response. The sync process operates in chunks, retrieving up to 100 documents per request from the Appwrite database and updating them on the Algolia index. + +## Limitations + +This function synchronizes the Appwrite database with the Algolia index in one direction only - from Appwrite to Algolia. If any changes are made directly on the Algolia index, they will not be reflected in the Appwrite database. + +Additionally, if any changes are made in the Appwrite database after the sync, they will not be reflected in the Algolia index until the sync function is run again. To achieve real-time sync, consider triggering this function whenever a document is added, updated, or deleted in your Appwrite database. diff --git a/sync-with-algolia/package-lock.json b/sync-with-algolia/package-lock.json new file mode 100644 index 00000000..84384a06 --- /dev/null +++ b/sync-with-algolia/package-lock.json @@ -0,0 +1,270 @@ +{ + "name": "sync-with-algolia", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sync-with-algolia", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "algoliasearch": "^4.17.2", + "node-appwrite": "^9.0.0" + }, + "devDependencies": { + "prettier": "^3.0.0" + } + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.17.2.tgz", + "integrity": "sha512-ZkVN7K/JE+qMQbpR6h3gQOGR6yCJpmucSBCmH5YDxnrYbp2CbrVCu0Nr+FGVoWzMJNznj1waShkfQ9awERulLw==", + "dependencies": { + "@algolia/cache-common": "4.17.2" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.17.2.tgz", + "integrity": "sha512-fojbhYIS8ovfYs6hwZpy1O4mBfVRxNgAaZRqsdVQd54hU4MxYDYFCxagYX28lOBz7btcDHld6BMoWXvjzkx6iQ==" + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.17.2.tgz", + "integrity": "sha512-UYQcMzPurNi+cPYkuPemTZkjKAjdgAS1hagC5irujKbrYnN4yscK4TkOI5tX+O8/KegtJt3kOK07OIrJ2QDAAw==", + "dependencies": { + "@algolia/cache-common": "4.17.2" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.17.2.tgz", + "integrity": "sha512-doSk89pBPDpDyKJSHFADIGa2XSGrBCj3QwPvqtRJXDADpN+OjW+eTR8r4hEs/7X4GGfjfAOAES8JgDx+fZntYw==", + "dependencies": { + "@algolia/client-common": "4.17.2", + "@algolia/client-search": "4.17.2", + "@algolia/transporter": "4.17.2" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.17.2.tgz", + "integrity": "sha512-V+DcXbOtD/hKwAR3qGQrtlrJ3q2f9OKfx843q744o4m3xHv5ueCAvGXB1znPsdaUrVDNAImcgEgqwI9x7EJbDw==", + "dependencies": { + "@algolia/client-common": "4.17.2", + "@algolia/client-search": "4.17.2", + "@algolia/requester-common": "4.17.2", + "@algolia/transporter": "4.17.2" + } + }, + "node_modules/@algolia/client-common": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.17.2.tgz", + "integrity": "sha512-gKBUnjxi0ukJYIJxVREYGt1Dmj1B3RBYbfGWi0dIPp1BC1VvQm+BOuNwsIwmq/x3MPO+sGuK978eKiP3tZDvag==", + "dependencies": { + "@algolia/requester-common": "4.17.2", + "@algolia/transporter": "4.17.2" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.17.2.tgz", + "integrity": "sha512-wc4UgOWxSYWz5wpuelNmlt895jA9twjZWM2ms17Ws8qCvBHF7OVGdMGgbysPB8790YnfvvDnSsWOv3CEj26Eow==", + "dependencies": { + "@algolia/client-common": "4.17.2", + "@algolia/requester-common": "4.17.2", + "@algolia/transporter": "4.17.2" + } + }, + "node_modules/@algolia/client-search": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.17.2.tgz", + "integrity": "sha512-FUjIs+gRe0upJC++uVs4sdxMw15JxfkT86Gr/kqVwi9kcqaZhXntSbW/Fw959bIYXczjmeVQsilYvBWW4YvSZA==", + "dependencies": { + "@algolia/client-common": "4.17.2", + "@algolia/requester-common": "4.17.2", + "@algolia/transporter": "4.17.2" + } + }, + "node_modules/@algolia/logger-common": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.17.2.tgz", + "integrity": "sha512-EfXuweUE+1HiSMsQidaDWA5Lv4NnStYIlh7PO5pLkI+sdhbMX0e5AO5nUAMIFM1VkEANes70RA8fzhP6OqCqQQ==" + }, + "node_modules/@algolia/logger-console": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.17.2.tgz", + "integrity": "sha512-JuG8HGVlJ+l/UEDK4h2Y8q/IEmRjQz1J0aS9tf6GPNbGYiSvMr1DDdZ+hqV3bb1XE6wU8Ypex56HisWMSpnG0A==", + "dependencies": { + "@algolia/logger-common": "4.17.2" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.17.2.tgz", + "integrity": "sha512-FKI2lYWwksALfRt2OETFmGb5+P7WVc4py2Ai3H7k8FSfTLwVvs9WVVmtlx6oANQ8RFEK4B85h8DQJTJ29TDfmA==", + "dependencies": { + "@algolia/requester-common": "4.17.2" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.17.2.tgz", + "integrity": "sha512-Rfim23ztAhYpE9qm+KCfCRo+YLJCjiiTG+IpDdzUjMpYPhUtirQT0A35YEd/gKn86YNyydxS9w8iRSjwKh+L0A==" + }, + "node_modules/@algolia/requester-node-http": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.17.2.tgz", + "integrity": "sha512-E0b0kyCDMvUIhQmDNd/mH4fsKJdEEX6PkMKrYJjzm6moo+rP22tqpq4Rfe7DZD8OB6/LsDD3zs3Kvd+L+M5wwQ==", + "dependencies": { + "@algolia/requester-common": "4.17.2" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.17.2.tgz", + "integrity": "sha512-m8pXlz5OnNzjD1rcw+duCN4jG4yEzkJBsvKYMoN22Oq6rQwy1AY5muZ+IQUs4dL+A364CYkRMLRWhvXpCZ1x+g==", + "dependencies": { + "@algolia/cache-common": "4.17.2", + "@algolia/logger-common": "4.17.2", + "@algolia/requester-common": "4.17.2" + } + }, + "node_modules/algoliasearch": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.17.2.tgz", + "integrity": "sha512-VFu43JJNYIW74awp7oeQcQsPcxOhd8psqBDTfyNO2Zt6L1NqnNMTVnaIdQ+8dtKqUDBqQZp0szPxECvX8CK2Fg==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.17.2", + "@algolia/cache-common": "4.17.2", + "@algolia/cache-in-memory": "4.17.2", + "@algolia/client-account": "4.17.2", + "@algolia/client-analytics": "4.17.2", + "@algolia/client-common": "4.17.2", + "@algolia/client-personalization": "4.17.2", + "@algolia/client-search": "4.17.2", + "@algolia/logger-common": "4.17.2", + "@algolia/logger-console": "4.17.2", + "@algolia/requester-browser-xhr": "4.17.2", + "@algolia/requester-common": "4.17.2", + "@algolia/requester-node-http": "4.17.2", + "@algolia/transporter": "4.17.2" + } + }, + "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/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/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/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/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/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/sync-with-algolia/package.json b/sync-with-algolia/package.json new file mode 100644 index 00000000..b2eb4d19 --- /dev/null +++ b/sync-with-algolia/package.json @@ -0,0 +1,20 @@ +{ + "name": "sync-with-algolia", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "type": "module", + "scripts": { + "format": "prettier --write src/**/*.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "algoliasearch": "^4.17.2", + "node-appwrite": "^9.0.0" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/sync-with-algolia/src/environment.js b/sync-with-algolia/src/environment.js new file mode 100644 index 00000000..e83fd3f6 --- /dev/null +++ b/sync-with-algolia/src/environment.js @@ -0,0 +1,30 @@ +/** + * @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 +} + +function EnvironmentService() { + return { + ALGOLIA_APP_ID: getRequiredEnv('ALGOLIA_APP_ID'), + ALGOLIA_ADMIN_API_KEY: getRequiredEnv('ALGOLIA_ADMIN_API_KEY'), + ALGOLIA_INDEX_ID: getRequiredEnv('ALGOLIA_INDEX_ID'), + ALGOLIA_SEARCH_API_KEY: getRequiredEnv('ALGOLIA_SEARCH_API_KEY'), + APPWRITE_API_KEY: getRequiredEnv('APPWRITE_API_KEY'), + APPWRITE_DATABASE_ID: getRequiredEnv('APPWRITE_DATABASE_ID'), + APPWRITE_COLLECTION_ID: getRequiredEnv('APPWRITE_COLLECTION_ID'), + APPWRITE_ENDPOINT: + process.env.APPWRITE_ENDPOINT ?? 'https://cloud.appwrite.io/v1', + APPWRITE_PROJECT_ID: + process.env.APPWRITE_PROJECT_ID ?? + getRequiredEnv('APPWRITE_FUNCTION_PROJECT_ID'), + } +} + +export default EnvironmentService diff --git a/sync-with-algolia/src/main.js b/sync-with-algolia/src/main.js new file mode 100644 index 00000000..d33112b0 --- /dev/null +++ b/sync-with-algolia/src/main.js @@ -0,0 +1,94 @@ +import { Client, Databases, Query } from 'node-appwrite' +import algoliasearch from 'algoliasearch' +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' +import EnvironmentService from './environment.js' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +const staticFolder = path.join(__dirname, '../static') + +/** + * @param {string} template + * @param {Record} values + * @returns {string} + */ +function interpolate(template, values) { + return template.replace(/{{([^}]+)}}/g, (_, key) => values[key]) +} + +export default async ({ req, res, log }) => { + const { + ALGOLIA_APP_ID, + ALGOLIA_ADMIN_API_KEY, + ALGOLIA_INDEX_ID, + ALGOLIA_SEARCH_API_KEY, + APPWRITE_API_KEY, + APPWRITE_DATABASE_ID, + APPWRITE_COLLECTION_ID, + APPWRITE_ENDPOINT, + APPWRITE_PROJECT_ID, + } = EnvironmentService() + + if (req.method === 'GET') { + const template = fs + .readFileSync(path.join(staticFolder, 'index.html')) + .toString() + + const html = interpolate(template, { + ALGOLIA_APP_ID, + ALGOLIA_INDEX_ID, + ALGOLIA_SEARCH_API_KEY, + }) + + return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }) + } + + const client = new Client() + .setEndpoint(APPWRITE_ENDPOINT) + .setProject(APPWRITE_PROJECT_ID) + .setKey(APPWRITE_API_KEY) + + const databases = new Databases(client) + + const algolia = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_ADMIN_API_KEY) + const algoliaIndex = algolia.initIndex(ALGOLIA_INDEX_ID) + + let cursor = null + + do { + const queries = [Query.limit(100)] + + if (cursor) { + queries.push(Query.cursorAfter(cursor)) + } + + const response = await databases.listDocuments( + APPWRITE_DATABASE_ID, + APPWRITE_COLLECTION_ID, + queries + ) + + if (response.documents.length > 0) { + cursor = response.documents[response.documents.length - 1].$id + } else { + log(`No more documents found.`) + cursor = null + break + } + + log(`Syncing chunk of ${response.documents.length} documents ...`) + + const records = response.documents.map((document) => ({ + ...document, + objectID: document.$id, + })) + await algoliaIndex.saveObjects(records) + } while (cursor !== null) + + log('Sync finished.') + + return res.empty() +} diff --git a/sync-with-algolia/static/index.html b/sync-with-algolia/static/index.html new file mode 100644 index 00000000..31dd82ef --- /dev/null +++ b/sync-with-algolia/static/index.html @@ -0,0 +1,99 @@ + + + + + + + Algolia Search Demo + + + + + + + + + +
+
+
+
+

Algolia Search Demo

+ +
+

+ This is demo application. You can ue this app to ensure sync between + Appwrite Databases and Algolia Seach was successful. Use search + input below to search your data. +

+
+
+
+
+ +
+
+
+
+ + + + From 915f0361072c0d2014690699a6052592b3303a31 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:39:12 +0100 Subject: [PATCH 017/149] feat: WhatsApp with Vonage function --- whatsapp-with-vonage/.gitignore | 130 +++++++++++++++++ whatsapp-with-vonage/.prettierrc.json | 6 + whatsapp-with-vonage/README.md | 36 +++++ whatsapp-with-vonage/env.d.ts | 11 ++ whatsapp-with-vonage/package-lock.json | 194 +++++++++++++++++++++++++ whatsapp-with-vonage/package.json | 21 +++ whatsapp-with-vonage/src/main.js | 58 ++++++++ whatsapp-with-vonage/static/index.html | 34 +++++ 8 files changed, 490 insertions(+) create mode 100644 whatsapp-with-vonage/.gitignore create mode 100644 whatsapp-with-vonage/.prettierrc.json create mode 100644 whatsapp-with-vonage/README.md create mode 100644 whatsapp-with-vonage/env.d.ts create mode 100644 whatsapp-with-vonage/package-lock.json create mode 100644 whatsapp-with-vonage/package.json create mode 100644 whatsapp-with-vonage/src/main.js create mode 100644 whatsapp-with-vonage/static/index.html diff --git a/whatsapp-with-vonage/.gitignore b/whatsapp-with-vonage/.gitignore new file mode 100644 index 00000000..6a7d6d8e --- /dev/null +++ b/whatsapp-with-vonage/.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/whatsapp-with-vonage/.prettierrc.json b/whatsapp-with-vonage/.prettierrc.json new file mode 100644 index 00000000..fbe0e552 --- /dev/null +++ b/whatsapp-with-vonage/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} \ No newline at end of file diff --git a/whatsapp-with-vonage/README.md b/whatsapp-with-vonage/README.md new file mode 100644 index 00000000..83bd3041 --- /dev/null +++ b/whatsapp-with-vonage/README.md @@ -0,0 +1,36 @@ +# WhatsApp Reply Function + +This function allows you to reply to a WhatsApp message through the [Vonage Messages API](https://developer.vonage.com/en/messaging/sms/overview). When a POST request is sent to this function with the necessary information, it sends a reply to the sender using the content of their original message. + +## Environment Variables + +For the function to operate correctly, the following variables must be set: + +- **VONAGE_API_KEY**: This is your Vonage project's API key. You can obtain this key from the Vonage dashboard under the "Getting Started" section. This key is used to authorize your application's requests to Vonage's APIs. + +- **VONAGE_API_SECRET**: This is the secret for your Vonage project's API key. You can obtain this secret from the same section in the Vonage dashboard where you got the API key. The API secret is used together with the API key to form the Basic Auth string for your requests. + +- **VONAGE_API_SIGNATURE_SECRET**: This is the signature secret for your Vonage project's API key. You can generate this secret in the "Settings" section of your Vonage dashboard, under "API Settings". This secret is used to verify the JWT token in the incoming request to ensure it's from an authorized source. + +## Usage + +This function supports two types of requests: + +1. **Displaying a Landing Page** + + - **Request Type:** GET + - **Response:** + - On success, the function will respond with a landing page in HTML format. + +2. **Replying to a WhatsApp Message** + + - **Request Type:** POST + - **Headers:** + - `Authorization`: Bearer token using the `VONAGE_API_SIGNATURE_SECRET` + - **Body:** + - `from`: Sender's phone number + - `text`: (Optional) Text of the sender's message + - **Response:** + - On success, the function will send a WhatsApp message to the sender's number, including the text "Hi there! You sent me: [text]" where [text] is replaced with the content of the sender's message, and respond with a status message of "OK". + - If the `from` field is missing in the request, an error message "Payload invalid." is returned. + - If the JWT token in the `Authorization` header is invalid or missing, an error message "Invalid signature." is returned. \ No newline at end of file diff --git a/whatsapp-with-vonage/env.d.ts b/whatsapp-with-vonage/env.d.ts new file mode 100644 index 00000000..22835eb4 --- /dev/null +++ b/whatsapp-with-vonage/env.d.ts @@ -0,0 +1,11 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + VONAGE_API_KEY?: string + VONAGE_API_SECRET?: string + VONAGE_API_SIGNATURE_SECRET?: string + } + } +} + +export {} diff --git a/whatsapp-with-vonage/package-lock.json b/whatsapp-with-vonage/package-lock.json new file mode 100644 index 00000000..e620314b --- /dev/null +++ b/whatsapp-with-vonage/package-lock.json @@ -0,0 +1,194 @@ +{ + "name": "censor-with-redact", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "censor-with-redact", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "jsonwebtoken": "^9.0.1", + "sha256": "^0.2.0" + }, + "devDependencies": { + "prettier": "^3.0.0", + "undici": "^5.22.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/convert-hex": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/convert-hex/-/convert-hex-0.1.0.tgz", + "integrity": "sha512-w20BOb1PiR/sEJdS6wNrUjF5CSfscZFUp7R9NSlXH8h2wynzXVEPFPJECAnkNylZ+cvf3p7TyRUHggDmrwXT9A==" + }, + "node_modules/convert-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/convert-string/-/convert-string-0.1.0.tgz", + "integrity": "sha512-1KX9ESmtl8xpT2LN2tFnKSbV4NiarbVi8DVb39ZriijvtTklyrT+4dT1wsGMHKD3CJUjXgvJzstm9qL9ICojGA==" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "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/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sha256": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sha256/-/sha256-0.2.0.tgz", + "integrity": "sha512-kTWMJUaez5iiT9CcMv8jSq6kMhw3ST0uRdcIWl3D77s6AsLXNXRp3heeqqfu5+Dyfu4hwpQnMzhqHh8iNQxw0w==", + "dependencies": { + "convert-hex": "~0.1.0", + "convert-string": "~0.1.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/undici": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", + "dev": true, + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/whatsapp-with-vonage/package.json b/whatsapp-with-vonage/package.json new file mode 100644 index 00000000..3e6fac0b --- /dev/null +++ b/whatsapp-with-vonage/package.json @@ -0,0 +1,21 @@ +{ + "name": "censor-with-redact", + "version": "1.0.0", + "description": "", + "type": "module", + "main": "src/main.js", + "scripts": { + "format": "prettier --write src/**/*.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "jsonwebtoken": "^9.0.1", + "sha256": "^0.2.0", + "undici": "^5.22.1" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/whatsapp-with-vonage/src/main.js b/whatsapp-with-vonage/src/main.js new file mode 100644 index 00000000..72457ed0 --- /dev/null +++ b/whatsapp-with-vonage/src/main.js @@ -0,0 +1,58 @@ +import jwt from 'jsonwebtoken' +import sha256 from 'sha256' +import { fetch } from 'undici' +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const staticFolder = path.join(__dirname, '../static') + +export default async ({ req, res }) => { + const { VONAGE_API_KEY, VONAGE_API_SECRET, VONAGE_API_SIGNATURE_SECRET } = + process.env + + if (!VONAGE_API_KEY || !VONAGE_API_SECRET || !VONAGE_API_SIGNATURE_SECRET) { + throw new Error('Function is missing required environment variables.') + } + + if (req.method === 'GET') { + const html = fs + .readFileSync(path.join(staticFolder, 'index.html')) + .toString() + return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }) + } + + const token = (req.headers.authorization ?? '').split(' ')[1] + var decoded = jwt.verify(token, VONAGE_API_SIGNATURE_SECRET, { + algorithms: ['HS256'], + }) + + if (sha256(req.bodyString) != decoded['payload_hash']) { + throw new Error('Invalid signature.') + } + + if (!req.body.from) { + throw new Error('Payload invalid.') + } + + const text = req.body.text ?? 'I only accept text messages.' + + await fetch(`https://messages-sandbox.nexmo.com/v1/messages`, { + method: 'POST', + body: JSON.stringify({ + from: '14157386102', + to: req.body.from, + message_type: 'text', + text: `Hi there! You sent me: ${text}`, + channel: 'whatsapp', + }), + headers: { + 'Content-Type': 'application/json', + Authorization: `Basic ${btoa(`${VONAGE_API_KEY}:${VONAGE_API_SECRET}`)}`, + }, + }) + + return res.send('OK') +} diff --git a/whatsapp-with-vonage/static/index.html b/whatsapp-with-vonage/static/index.html new file mode 100644 index 00000000..3aa89c07 --- /dev/null +++ b/whatsapp-with-vonage/static/index.html @@ -0,0 +1,34 @@ + + + + + + + Whats App Bot with Vonage + + + + + +
+
+
+
+

Whats App Bot with Vonage

+ +
+

+ This function listens to incoming webhooks from Vonage regarding + WhatsApp messages, and responds to them. To use the function, send + message to the WhatsApp user provided by Vonage. +

+
+
+
+ + From 0398e3ff94befbe170ce6f29c0ff8f9a37173062 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 18 Jul 2023 11:24:50 +0100 Subject: [PATCH 018/149] feat: Sync with Meilisearch function --- sync-with-meilisearch/.gitignore | 130 ++++++++++++++++++ sync-with-meilisearch/.prettierrc.json | 6 + sync-with-meilisearch/README.md | 41 ++++++ sync-with-meilisearch/package-lock.json | 164 +++++++++++++++++++++++ sync-with-meilisearch/package.json | 20 +++ sync-with-meilisearch/src/environment.js | 30 +++++ sync-with-meilisearch/src/main.js | 97 ++++++++++++++ sync-with-meilisearch/static/index.html | 93 +++++++++++++ 8 files changed, 581 insertions(+) create mode 100644 sync-with-meilisearch/.gitignore create mode 100644 sync-with-meilisearch/.prettierrc.json create mode 100644 sync-with-meilisearch/README.md create mode 100644 sync-with-meilisearch/package-lock.json create mode 100644 sync-with-meilisearch/package.json create mode 100644 sync-with-meilisearch/src/environment.js create mode 100644 sync-with-meilisearch/src/main.js create mode 100644 sync-with-meilisearch/static/index.html diff --git a/sync-with-meilisearch/.gitignore b/sync-with-meilisearch/.gitignore new file mode 100644 index 00000000..6a7d6d8e --- /dev/null +++ b/sync-with-meilisearch/.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/sync-with-meilisearch/.prettierrc.json b/sync-with-meilisearch/.prettierrc.json new file mode 100644 index 00000000..fa51da29 --- /dev/null +++ b/sync-with-meilisearch/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/sync-with-meilisearch/README.md b/sync-with-meilisearch/README.md new file mode 100644 index 00000000..8fb75732 --- /dev/null +++ b/sync-with-meilisearch/README.md @@ -0,0 +1,41 @@ +# Meilisearch Sync Function + +This function enables synchronization of data between your Appwrite project and a Meilisearch instance. When invoked, it retrieves documents from a specified collection in your Appwrite database and syncs them with a designated Meilisearch index. This function is designed for facilitating full-text search and other search functionalities over data stored in Appwrite using Meilisearch. + +## Environment Variables + +The function requires the following environment variables to be set: + +- **MEILISEARCH_ENDPOINT**: The URL where your Meilisearch server is located. +- **MEILISEARCH_ADMIN_API_KEY**: The admin API key of your Meilisearch instance. +- **MEILISEARCH_INDEX_NAME**: The name of the Meilisearch index where the documents will be stored. +- **MEILISEARCH_SEARCH_API_KEY**: The search API key of your Meilisearch instance. +- **APPWRITE_API_KEY**: Your Appwrite project's API key. +- **APPWRITE_DATABASE_ID**: The ID of the Appwrite database from which documents will be fetched. +- **APPWRITE_COLLECTION_ID**: The ID of the collection within the database from which documents will be fetched. +- **APPWRITE_ENDPOINT**: The URL of your Appwrite server. +- **APPWRITE_PROJECT_ID**: The ID of your Appwrite project. + +## Usage + +This function supports two types of requests: + +1. **View Search Interface** + + - **Request Type:** GET + - **Response:** + - The function will respond with an HTML page for interacting with Meilisearch. The HTML file (`index.html`) should be placed in the `static` folder. + +2. **Sync Appwrite Documents with Meilisearch** + + - **Request Type:** POST + - **Response:** + - The function will begin fetching documents from the specified Appwrite collection and syncing them with the specified Meilisearch index. If successful, an empty response is returned. The function logs the progress of the operation, which can be monitored via the Appwrite dashboard. + +## Limitations + +This function syncs a maximum of 100 documents at a time. If the collection has more than 100 documents, the function will keep fetching and syncing in chunks until all documents have been processed. + +## Caution + +Be careful when syncing large collections. This function does not implement rate limiting, which could lead to your Appwrite or Meilisearch instance getting overwhelmed by a large number of requests in a short period. Consider adding delay or rate limiting logic if you plan to sync large amounts of data. \ No newline at end of file diff --git a/sync-with-meilisearch/package-lock.json b/sync-with-meilisearch/package-lock.json new file mode 100644 index 00000000..900ab9c8 --- /dev/null +++ b/sync-with-meilisearch/package-lock.json @@ -0,0 +1,164 @@ +{ + "name": "sync-with-meilisearch", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sync-with-meilisearch", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "node-appwrite": "^9.0.0", + "undici": "^5.22.1" + }, + "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/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "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/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/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/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/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==" + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/undici": { + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=14.0" + } + } + } +} diff --git a/sync-with-meilisearch/package.json b/sync-with-meilisearch/package.json new file mode 100644 index 00000000..c0b66dd9 --- /dev/null +++ b/sync-with-meilisearch/package.json @@ -0,0 +1,20 @@ +{ + "name": "sync-with-meilisearch", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "type": "module", + "scripts": { + "format": "prettier --write src/**/*.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "node-appwrite": "^9.0.0", + "undici": "^5.22.1" + }, + "devDependencies": { + "prettier": "^3.0.0" + } +} diff --git a/sync-with-meilisearch/src/environment.js b/sync-with-meilisearch/src/environment.js new file mode 100644 index 00000000..c1356ef5 --- /dev/null +++ b/sync-with-meilisearch/src/environment.js @@ -0,0 +1,30 @@ +/** + * @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 +} + +function EnvironmentService() { + return { + MEILISEARCH_ENDPOINT: getRequiredEnv('MEILISEARCH_ENDPOINT'), + MEILISEARCH_ADMIN_API_KEY: getRequiredEnv('MEILISEARCH_ADMIN_API_KEY'), + MEILISEARCH_INDEX_NAME: getRequiredEnv('MEILISEARCH_INDEX_NAME'), + MEILISEARCH_SEARCH_API_KEY: getRequiredEnv('MEILISEARCH_SEARCH_API_KEY'), + APPWRITE_API_KEY: getRequiredEnv('APPWRITE_API_KEY'), + APPWRITE_DATABASE_ID: getRequiredEnv('APPWRITE_DATABASE_ID'), + APPWRITE_COLLECTION_ID: getRequiredEnv('APPWRITE_COLLECTION_ID'), + APPWRITE_ENDPOINT: + process.env.APPWRITE_ENDPOINT ?? 'https://cloud.appwrite.io/v1', + APPWRITE_PROJECT_ID: + process.env.APPWRITE_PROJECT_ID ?? + getRequiredEnv('APPWRITE_FUNCTION_PROJECT_ID'), + } +} + +export default EnvironmentService diff --git a/sync-with-meilisearch/src/main.js b/sync-with-meilisearch/src/main.js new file mode 100644 index 00000000..87085d77 --- /dev/null +++ b/sync-with-meilisearch/src/main.js @@ -0,0 +1,97 @@ +import { Client, Databases, Query } from 'node-appwrite' +import { fetch } from 'undici' +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' +import EnvironmentService from './environment.js' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +const staticFolder = path.join(__dirname, '../static') + +/** + * @param {string} template + * @param {Record} values + * @returns {string} + */ +function interpolate(template, values) { + return template.replace(/{{([^}]+)}}/g, (_, key) => values[key]) +} + +export default async ({ req, res, log }) => { + const { + MEILISEARCH_ENDPOINT, + MEILISEARCH_ADMIN_API_KEY, + MEILISEARCH_INDEX_NAME, + MEILISEARCH_SEARCH_API_KEY, + APPWRITE_API_KEY, + APPWRITE_DATABASE_ID, + APPWRITE_COLLECTION_ID, + APPWRITE_ENDPOINT, + APPWRITE_PROJECT_ID, + } = EnvironmentService() + + if (req.method === 'GET') { + const template = fs + .readFileSync(path.join(staticFolder, 'index.html')) + .toString() + + const html = interpolate(template, { + MEILISEARCH_ENDPOINT: MEILISEARCH_ENDPOINT, + MEILISEARCH_INDEX_NAME, + MEILISEARCH_SEARCH_API_KEY, + }) + + return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }) + } + + const client = new Client() + .setEndpoint(APPWRITE_ENDPOINT) + .setProject(APPWRITE_PROJECT_ID) + .setKey(APPWRITE_API_KEY) + + const databases = new Databases(client) + + let cursor = null + + do { + const queries = [Query.limit(100)] + + if (cursor) { + queries.push(Query.cursorAfter(cursor)) + } + + const { documents } = await databases.listDocuments( + APPWRITE_DATABASE_ID, + APPWRITE_COLLECTION_ID, + queries + ) + + if (documents.length > 0) { + cursor = documents[documents.length - 1].$id + } else { + log(`No more documents found.`) + cursor = null + break + } + + log(`Syncing chunk of ${documents.length} documents ...`) + + await fetch( + `${MEILISEARCH_ENDPOINT}/indexes/${MEILISEARCH_INDEX_NAME}/documents?primaryKey=$id`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${MEILISEARCH_ADMIN_API_KEY}`, + }, + body: JSON.stringify(documents), + } + ) + } while (cursor !== null) + + log('Sync finished.') + + return res.empty() +} diff --git a/sync-with-meilisearch/static/index.html b/sync-with-meilisearch/static/index.html new file mode 100644 index 00000000..b10f9b59 --- /dev/null +++ b/sync-with-meilisearch/static/index.html @@ -0,0 +1,93 @@ + + + + + + + Meilisearch Search Demo + + + + + + + + +
+
+
+
+

Meilisearch Search Demo

+ +
+

+ This is demo application. You can ue this app to ensure sync between + Appwrite Databases and Meilisearch Seach was successful. Use search + input below to search your data. +

+
+
+
+
+ +
+ +
+
+
+
+ + From 6dfee5842413b84a016b64d69d39cfc4d5cbbef6 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:48:53 +0100 Subject: [PATCH 019/149] chore: .gitignore --- analyze-with-perspectiveapi/.gitignore | 131 ++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/analyze-with-perspectiveapi/.gitignore b/analyze-with-perspectiveapi/.gitignore index b512c09d..6a7d6d8e 100644 --- a/analyze-with-perspectiveapi/.gitignore +++ b/analyze-with-perspectiveapi/.gitignore @@ -1 +1,130 @@ -node_modules \ No newline at end of file +# 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 From 5d9d7ba219c077e64d27023ea6bd739dbf7effd0 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:49:17 +0100 Subject: [PATCH 020/149] chore: .gitignore --- censor-with-redact/.gitignore | 131 +++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/censor-with-redact/.gitignore b/censor-with-redact/.gitignore index b512c09d..6a7d6d8e 100644 --- a/censor-with-redact/.gitignore +++ b/censor-with-redact/.gitignore @@ -1 +1,130 @@ -node_modules \ No newline at end of file +# 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 From a8c7050c2c44588dfd64d02b865c384c73d90d95 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:57:39 +0100 Subject: [PATCH 021/149] fix: esm __dirname --- analyze-with-perspectiveapi/src/main.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/analyze-with-perspectiveapi/src/main.js b/analyze-with-perspectiveapi/src/main.js index ee192a76..36c7a3d5 100644 --- a/analyze-with-perspectiveapi/src/main.js +++ b/analyze-with-perspectiveapi/src/main.js @@ -1,7 +1,12 @@ import fs from 'node:fs' import path from 'node:path' +import { fileURLToPath } from 'node:url' import { fetch } from 'undici' +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const staticFolder = path.join(__dirname, '../static') + export default async ({ req, res }) => { const { PERSPECTIVE_API_KEY } = process.env @@ -10,10 +15,9 @@ export default async ({ req, res }) => { } if (req.method === 'GET') { - let html = fs - .readFileSync(path.join(__dirname, '../static/index.html')) + const html = fs + .readFileSync(path.join(staticFolder, 'index.html')) .toString() - return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }) } From ec21ea280420307938999a748c4891178dd6b486 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:42:01 +0100 Subject: [PATCH 022/149] chore: add semis, del pjson extras --- email-contact-form/.prettierrc.json | 2 +- email-contact-form/package.json | 2 - email-contact-form/src/cors.js | 12 ++-- email-contact-form/src/environment.js | 10 +-- email-contact-form/src/mail.js | 10 +-- email-contact-form/src/main.js | 88 ++++++++++++++------------- 6 files changed, 62 insertions(+), 62 deletions(-) diff --git a/email-contact-form/.prettierrc.json b/email-contact-form/.prettierrc.json index fa51da29..0a725205 100644 --- a/email-contact-form/.prettierrc.json +++ b/email-contact-form/.prettierrc.json @@ -1,6 +1,6 @@ { "trailingComma": "es5", "tabWidth": 2, - "semi": false, + "semi": true, "singleQuote": true } diff --git a/email-contact-form/package.json b/email-contact-form/package.json index 44332858..17dde79e 100644 --- a/email-contact-form/package.json +++ b/email-contact-form/package.json @@ -7,8 +7,6 @@ "scripts": { "format": "prettier --write src/**/*.js" }, - "author": "", - "license": "MIT", "dependencies": { "nodemailer": "^6.9.3" }, diff --git a/email-contact-form/src/cors.js b/email-contact-form/src/cors.js index 11c4d6b0..e8ed5cbe 100644 --- a/email-contact-form/src/cors.js +++ b/email-contact-form/src/cors.js @@ -2,16 +2,16 @@ * @param {string} origin Origin header of the request */ export default function CorsService(origin, environment) { - const { ALLOWED_ORIGINS } = environment + const { ALLOWED_ORIGINS } = environment; return { /** * @returns {boolean} Whether the origin is allowed based on the ALLOWED_ORIGINS environment variable */ isOriginPermitted: function () { - if (!ALLOWED_ORIGINS || ALLOWED_ORIGINS === '*') return true - const allowedOriginsArray = ALLOWED_ORIGINS.split(',') - return allowedOriginsArray.includes(origin) + if (!ALLOWED_ORIGINS || ALLOWED_ORIGINS === '*') return true; + const allowedOriginsArray = ALLOWED_ORIGINS.split(','); + return allowedOriginsArray.includes(origin); }, /** * @returns {Object} Access-Control-Allow-Origin header to be returned in the response @@ -19,7 +19,7 @@ export default function CorsService(origin, environment) { getHeaders: function () { return { 'Access-Control-Allow-Origin': ALLOWED_ORIGINS === '*' ? '*' : origin, - } + }; }, - } + }; } diff --git a/email-contact-form/src/environment.js b/email-contact-form/src/environment.js index 026927b2..3a0f8893 100644 --- a/email-contact-form/src/environment.js +++ b/email-contact-form/src/environment.js @@ -3,11 +3,11 @@ * @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; } function EnvironmentService() { @@ -18,7 +18,7 @@ function EnvironmentService() { SMTP_USERNAME: getRequiredEnv('SMTP_USERNAME'), SMTP_PASSWORD: getRequiredEnv('SMTP_PASSWORD'), ALLOWED_ORIGINS: process.env.ALLOWED_ORIGINS || '*', - } + }; } -export default EnvironmentService +export default EnvironmentService; diff --git a/email-contact-form/src/mail.js b/email-contact-form/src/mail.js index d98c1a70..4d15942f 100644 --- a/email-contact-form/src/mail.js +++ b/email-contact-form/src/mail.js @@ -1,7 +1,7 @@ -import nodemailer from 'nodemailer' +import nodemailer from 'nodemailer'; export default function MailService(environment) { - const { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD } = environment + const { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD } = environment; const transport = nodemailer.createTransport({ // @ts-ignore @@ -9,14 +9,14 @@ export default function MailService(environment) { host: SMTP_HOST, port: SMTP_PORT, auth: { user: SMTP_USERNAME, pass: SMTP_PASSWORD }, - }) + }); return { /** * @param {import('nodemailer').SendMailOptions} mailOptions */ send: async function (mailOptions) { - await transport.sendMail(mailOptions) + await transport.sendMail(mailOptions); }, - } + }; } diff --git a/email-contact-form/src/main.js b/email-contact-form/src/main.js index 49883a87..c01899c2 100644 --- a/email-contact-form/src/main.js +++ b/email-contact-form/src/main.js @@ -1,78 +1,80 @@ -import querystring from 'node:querystring' -import { readFileSync } from 'node:fs' -import { fileURLToPath } from 'node:url' -import path from 'node:path' -import CorsService from './cors.js' -import MailService from './mail.js' -import EnvironmentService from './environment.js' +import querystring from 'node:querystring'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; +import CorsService from './cors.js'; +import MailService from './mail.js'; +import EnvironmentService from './environment.js'; const ErrorCode = { INVALID_REQUEST: 'invalid-request', MISSING_FORM_FIELDS: 'missing-form-fields', SERVER_ERROR: 'server-error', -} +}; const ROUTES = { '/': 'index.html', '/index.html': 'index.html', '/success.html': 'success.html', -} +}; -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); -const staticFolder = path.join(__dirname, '../static') +const staticFolder = path.join(__dirname, '../static'); export default async ({ req, res, log, error }) => { - const environment = EnvironmentService() - const { SUBMIT_EMAIL, ALLOWED_ORIGINS } = environment + const environment = EnvironmentService(); + const { SUBMIT_EMAIL, ALLOWED_ORIGINS } = environment; if (ALLOWED_ORIGINS === '*') { - log('WARNING: Allowing requests from any origin - this is a security risk!') + log( + 'WARNING: Allowing requests from any origin - this is a security risk!' + ); } if (req.method === 'GET') { - const route = ROUTES[req.path] - const html = readFileSync(path.join(staticFolder, route)) + const route = ROUTES[req.path]; + const html = readFileSync(path.join(staticFolder, route)); return res.send(html.toString(), 200, { 'Content-Type': 'text/html; charset=utf-8', - }) + }); } - const referer = req.headers['referer'] - const origin = req.headers['origin'] + const referer = req.headers['referer']; + const origin = req.headers['origin']; if (!referer || !origin) { - log('Missing referer or origin headers.') - return res.json({ error: 'Missing referer or origin headers.' }, 400) + log('Missing referer or origin headers.'); + return res.json({ error: 'Missing referer or origin headers.' }, 400); } if (req.headers['content-type'] !== 'application/x-www-form-urlencoded') { - log('Invalid request.') - return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)) + log('Invalid request.'); + return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)); } - const cors = CorsService(origin, environment) - const mail = MailService(environment) + const cors = CorsService(origin, environment); + const mail = MailService(environment); if (!cors.isOriginPermitted()) { - error('Origin not permitted.') - return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)) + error('Origin not permitted.'); + return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)); } - const form = querystring.parse(req.body) + const form = querystring.parse(req.body); if (!(form.email && typeof form.email === 'string')) { - error('Missing form data.') + error('Missing form data.'); return res.redirect( urlWithCodeParam(referer, ErrorCode.MISSING_FORM_FIELDS), 301, cors.getHeaders() - ) + ); } - log('Form data is valid.') + log('Form data is valid.'); const successUrl = - typeof form._next === 'string' && form._next ? form._next : '/success' + typeof form._next === 'string' && form._next ? form._next : '/success'; try { mail.send({ @@ -80,24 +82,24 @@ export default async ({ req, res, log, error }) => { from: form.email, subject: `New form submission: ${origin}`, text: templateFormMessage(form), - }) + }); } catch (err) { - error(err.message) + error(err.message); return res.redirect( urlWithCodeParam(referer, ErrorCode.SERVER_ERROR), 301, cors.getHeaders() - ) + ); } - log('Email sent successfully.') + log('Email sent successfully.'); return res.redirect( new URL(successUrl, origin).toString(), 301, cors.getHeaders() - ) -} + ); +}; /** * Build a message from the form data. @@ -109,7 +111,7 @@ function templateFormMessage(form) { ${Object.entries(form) .filter(([key]) => key !== '_next') .map(([key, value]) => `${key}: ${value}`) - .join('\n')}` + .join('\n')}`; } /** @@ -117,7 +119,7 @@ ${Object.entries(form) * @param {string} codeParam */ function urlWithCodeParam(baseUrl, codeParam) { - const url = new URL(baseUrl) - url.searchParams.set('code', codeParam) - return url.toString() + const url = new URL(baseUrl); + url.searchParams.set('code', codeParam); + return url.toString(); } From 0cea85cd4dbb44c0ca6bed1938df0d6e3ea82929 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:51:02 +0100 Subject: [PATCH 023/149] chore: refactor --- email-contact-form/src/main.js | 38 +++++++++++++++------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/email-contact-form/src/main.js b/email-contact-form/src/main.js index c01899c2..f0438618 100644 --- a/email-contact-form/src/main.js +++ b/email-contact-form/src/main.js @@ -1,26 +1,13 @@ import querystring from 'node:querystring'; -import { readFileSync } from 'node:fs'; +import fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; import CorsService from './cors.js'; import MailService from './mail.js'; import EnvironmentService from './environment.js'; -const ErrorCode = { - INVALID_REQUEST: 'invalid-request', - MISSING_FORM_FIELDS: 'missing-form-fields', - SERVER_ERROR: 'server-error', -}; - -const ROUTES = { - '/': 'index.html', - '/index.html': 'index.html', - '/success.html': 'success.html', -}; - const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); - const staticFolder = path.join(__dirname, '../static'); export default async ({ req, res, log, error }) => { @@ -33,9 +20,8 @@ export default async ({ req, res, log, error }) => { ); } - if (req.method === 'GET') { - const route = ROUTES[req.path]; - const html = readFileSync(path.join(staticFolder, route)); + if (req.method === 'GET' && req.path === '/') { + const html = fs.readFileSync(path.join(staticFolder, 'index.html')); return res.send(html.toString(), 200, { 'Content-Type': 'text/html; charset=utf-8', }); @@ -73,9 +59,6 @@ export default async ({ req, res, log, error }) => { } log('Form data is valid.'); - const successUrl = - typeof form._next === 'string' && form._next ? form._next : '/success'; - try { mail.send({ to: SUBMIT_EMAIL, @@ -94,8 +77,15 @@ export default async ({ req, res, log, error }) => { log('Email sent successfully.'); + if (typeof form._next !== 'string' || !form._next) { + const html = fs.readFileSync(path.join(staticFolder, 'success.html')); + return res.send(html.toString(), 200, { + 'Content-Type': 'text/html; charset=utf-8', + }); + } + return res.redirect( - new URL(successUrl, origin).toString(), + new URL(form._next, origin).toString(), 301, cors.getHeaders() ); @@ -114,6 +104,12 @@ ${Object.entries(form) .join('\n')}`; } +const ErrorCode = { + INVALID_REQUEST: 'invalid-request', + MISSING_FORM_FIELDS: 'missing-form-fields', + SERVER_ERROR: 'server-error', +}; + /** * @param {string} baseUrl * @param {string} codeParam From 0d067d1183e554562a82b5599d5b75143106c8a1 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:57:23 +0100 Subject: [PATCH 024/149] chore: add semis, del pjson extras --- analyze-with-perspectiveapi/.prettierrc.json | 2 +- analyze-with-perspectiveapi/package.json | 2 -- analyze-with-perspectiveapi/src/main.js | 34 ++++++++++---------- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/analyze-with-perspectiveapi/.prettierrc.json b/analyze-with-perspectiveapi/.prettierrc.json index fa51da29..0a725205 100644 --- a/analyze-with-perspectiveapi/.prettierrc.json +++ b/analyze-with-perspectiveapi/.prettierrc.json @@ -1,6 +1,6 @@ { "trailingComma": "es5", "tabWidth": 2, - "semi": false, + "semi": true, "singleQuote": true } diff --git a/analyze-with-perspectiveapi/package.json b/analyze-with-perspectiveapi/package.json index 8ff200a5..4968baad 100644 --- a/analyze-with-perspectiveapi/package.json +++ b/analyze-with-perspectiveapi/package.json @@ -8,8 +8,6 @@ "format": "prettier --write src/**/*.js" }, "keywords": [], - "author": "", - "license": "ISC", "dependencies": { "undici": "^5.22.1" }, diff --git a/analyze-with-perspectiveapi/src/main.js b/analyze-with-perspectiveapi/src/main.js index 36c7a3d5..cde8efbf 100644 --- a/analyze-with-perspectiveapi/src/main.js +++ b/analyze-with-perspectiveapi/src/main.js @@ -1,28 +1,28 @@ -import fs from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import { fetch } from 'undici' +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { fetch } from 'undici'; -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const staticFolder = path.join(__dirname, '../static') +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const staticFolder = path.join(__dirname, '../static'); export default async ({ req, res }) => { - const { PERSPECTIVE_API_KEY } = process.env + const { PERSPECTIVE_API_KEY } = process.env; if (!PERSPECTIVE_API_KEY) { - throw new Error('Function is missing required environment variables.') + throw new Error('Function is missing required environment variables.'); } if (req.method === 'GET') { const html = fs .readFileSync(path.join(staticFolder, 'index.html')) - .toString() - return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }) + .toString(); + return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }); } if (!req.bodyString) { - return res.send('Missing body with a prompt.', 400) + return res.send('Missing body with a prompt.', 400); } const response = await fetch( @@ -43,12 +43,12 @@ export default async ({ req, res }) => { }, }), } - ) + ); if (response.status !== 200) { - return res.send('Error analyzing text.', 500) + return res.send('Error analyzing text.', 500); } - const data = /** @type {*} */ (await response.json()) - return res.send(data.attributeScores.TOXICITY.summaryScore.value) -} + const data = /** @type {*} */ (await response.json()); + return res.send(data.attributeScores.TOXICITY.summaryScore.value); +}; From e0a0a9986dd7b5ee81dc3399283ff0b6be8bdab6 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:58:25 +0100 Subject: [PATCH 025/149] chore: add semis, del pjson extras --- censor-with-redact/.prettierrc.json | 2 +- censor-with-redact/package.json | 2 -- censor-with-redact/src/main.js | 31 ++++++++++++++--------------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/censor-with-redact/.prettierrc.json b/censor-with-redact/.prettierrc.json index fa51da29..0a725205 100644 --- a/censor-with-redact/.prettierrc.json +++ b/censor-with-redact/.prettierrc.json @@ -1,6 +1,6 @@ { "trailingComma": "es5", "tabWidth": 2, - "semi": false, + "semi": true, "singleQuote": true } diff --git a/censor-with-redact/package.json b/censor-with-redact/package.json index b8684cff..39215882 100644 --- a/censor-with-redact/package.json +++ b/censor-with-redact/package.json @@ -8,8 +8,6 @@ "format": "prettier --write src/**/*.js" }, "keywords": [], - "author": "", - "license": "ISC", "devDependencies": { "prettier": "^3.0.0" }, diff --git a/censor-with-redact/src/main.js b/censor-with-redact/src/main.js index 18fb802c..faa8ea98 100644 --- a/censor-with-redact/src/main.js +++ b/censor-with-redact/src/main.js @@ -1,28 +1,27 @@ -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -const staticFolder = path.join(__dirname, '../static') +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const staticFolder = path.join(__dirname, '../static'); export default async ({ req, res }) => { - const { PANGEA_REDACT_TOKEN } = process.env + const { PANGEA_REDACT_TOKEN } = process.env; if (!PANGEA_REDACT_TOKEN) { - throw new Error('Function is missing required environment variables.') + throw new Error('Function is missing required environment variables.'); } if (req.method === 'GET') { const html = fs .readFileSync(path.join(staticFolder, 'index.html')) - .toString() - return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }) + .toString(); + return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }); } if (!req.bodyString) { - return res.send('Missing body with a prompt.', 400) + return res.send('Missing body with a prompt.', 400); } const response = await fetch(`https://redact.aws.eu.pangea.cloud/v1/redact`, { @@ -34,8 +33,8 @@ export default async ({ req, res }) => { body: JSON.stringify({ text: req.bodyString, }), - }) + }); - const data = /** @type {*} */ (await response.json()) - return res.send(data.result.redacted_text) -} + const data = /** @type {*} */ (await response.json()); + return res.send(data.result.redacted_text); +}; From 3057f32f2f687f56eaf858d61c3b5ad3cf665100 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 19 Jul 2023 10:59:53 +0100 Subject: [PATCH 026/149] chore: add semis --- discord-command-bot/.prettierrc.json | 2 +- discord-command-bot/package.json | 2 -- discord-command-bot/src/discord.js | 10 +++++----- discord-command-bot/src/environment.js | 8 ++++---- discord-command-bot/src/main.js | 22 +++++++++++----------- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/discord-command-bot/.prettierrc.json b/discord-command-bot/.prettierrc.json index fa51da29..0a725205 100644 --- a/discord-command-bot/.prettierrc.json +++ b/discord-command-bot/.prettierrc.json @@ -1,6 +1,6 @@ { "trailingComma": "es5", "tabWidth": 2, - "semi": false, + "semi": true, "singleQuote": true } diff --git a/discord-command-bot/package.json b/discord-command-bot/package.json index b331aff1..493e4c21 100644 --- a/discord-command-bot/package.json +++ b/discord-command-bot/package.json @@ -7,8 +7,6 @@ "scripts": { "format": "prettier --write src/**/*.js" }, - "author": "", - "license": "MIT", "dependencies": { "discord-interactions": "^3.4.0" }, diff --git a/discord-command-bot/src/discord.js b/discord-command-bot/src/discord.js index 9b26ae07..e5080b7c 100644 --- a/discord-command-bot/src/discord.js +++ b/discord-command-bot/src/discord.js @@ -1,17 +1,17 @@ -import { verifyKey } from 'discord-interactions' +import { verifyKey } from 'discord-interactions'; function DiscordService(environment) { return { verifyWebhook: async function (req) { - const { DISCORD_PUBLIC_KEY } = environment + const { DISCORD_PUBLIC_KEY } = environment; return await verifyKey( req.bodyString, req.headers['x-signature-ed25519'], req.headers['x-signature-timestamp'], DISCORD_PUBLIC_KEY - ) + ); }, - } + }; } -export default DiscordService +export default DiscordService; diff --git a/discord-command-bot/src/environment.js b/discord-command-bot/src/environment.js index 627789e0..79cfa44c 100644 --- a/discord-command-bot/src/environment.js +++ b/discord-command-bot/src/environment.js @@ -3,15 +3,15 @@ * @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; } export default function EnvironmentService() { return { DISCORD_PUBLIC_KEY: getRequiredEnv('DISCORD_PUBLIC_KEY'), - } + }; } diff --git a/discord-command-bot/src/main.js b/discord-command-bot/src/main.js index 3cc061c9..cdc08b91 100644 --- a/discord-command-bot/src/main.js +++ b/discord-command-bot/src/main.js @@ -1,17 +1,17 @@ -import { InteractionResponseType, InteractionType } from 'discord-interactions' -import DiscordService from './discord.js' -import EnvironmentService from './environment.js' +import { InteractionResponseType, InteractionType } from 'discord-interactions'; +import DiscordService from './discord.js'; +import EnvironmentService from './environment.js'; export default async ({ req, res, error }) => { - const environment = EnvironmentService() - const discord = DiscordService(environment) + const environment = EnvironmentService(); + const discord = DiscordService(environment); if (!(await discord.verifyWebhook(req))) { - error('Invalid request.') - return res.send('Invalid request signature', 401) + error('Invalid request.'); + return res.send('Invalid request signature', 401); } - const interaction = req.body + const interaction = req.body; if ( interaction.type === InteractionType.APPLICATION_COMMAND && interaction.data.name === 'hello' @@ -21,8 +21,8 @@ export default async ({ req, res, error }) => { data: { content: 'Hello from Appwrite 👋', }, - }) + }); } - return res.json(InteractionResponseType.PONG) -} + return res.json(InteractionResponseType.PONG); +}; From 12821d6aefd3ae2a7bc3bbfe0c75c7dad8f75f3d Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:01:16 +0100 Subject: [PATCH 027/149] chore: add semis, remove pjson extras --- generate-pdf/.prettierrc.json | 2 +- generate-pdf/package.json | 2 -- generate-pdf/src/main.js | 42 +++++++++++++++++------------------ 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/generate-pdf/.prettierrc.json b/generate-pdf/.prettierrc.json index fa51da29..0a725205 100644 --- a/generate-pdf/.prettierrc.json +++ b/generate-pdf/.prettierrc.json @@ -1,6 +1,6 @@ { "trailingComma": "es5", "tabWidth": 2, - "semi": false, + "semi": true, "singleQuote": true } diff --git a/generate-pdf/package.json b/generate-pdf/package.json index de6b8773..5ee5cd7c 100644 --- a/generate-pdf/package.json +++ b/generate-pdf/package.json @@ -7,8 +7,6 @@ "scripts": { "format": "prettier --write src/**/*.js" }, - "author": "", - "license": "MIT", "dependencies": { "@faker-js/faker": "^8.0.2", "pdf-lib": "^1.17.1" diff --git a/generate-pdf/src/main.js b/generate-pdf/src/main.js index c6d34ccc..722d8fe6 100644 --- a/generate-pdf/src/main.js +++ b/generate-pdf/src/main.js @@ -1,15 +1,15 @@ -import { PDFDocument } from 'pdf-lib' -import { faker } from '@faker-js/faker' +import { PDFDocument } from 'pdf-lib'; +import { faker } from '@faker-js/faker'; export default async ({ res, log }) => { - const fakeOrder = generateFakeOrder() - log(`Generated fake order: ${JSON.stringify(fakeOrder, null, 2)}`) + const fakeOrder = generateFakeOrder(); + log(`Generated fake order: ${JSON.stringify(fakeOrder, null, 2)}`); - const pdfBuffer = await createPdf(fakeOrder) - log('PDF created.') + const pdfBuffer = await createPdf(fakeOrder); + log('PDF created.'); - return res.send(pdfBuffer, 200, { 'Content-Type': 'application/pdf' }) -} + return res.send(pdfBuffer, 200, { 'Content-Type': 'application/pdf' }); +}; function generateFakeOrder() { const items = Array.from( @@ -19,7 +19,7 @@ function generateFakeOrder() { quantity: faker.number.int({ min: 1, max: 10 }), cost: faker.commerce.price(), }) - ) + ); return { id: faker.string.uuid(), @@ -27,44 +27,44 @@ function generateFakeOrder() { name: faker.person.fullName(), items, total: items.reduce((acc, { cost }) => acc + parseFloat(cost), 0), - } + }; } async function createPdf({ id, date, name, items, total }) { - const document = await PDFDocument.create() - const page = document.addPage([595.28, 841.89]) // A4 size + const document = await PDFDocument.create(); + const page = document.addPage([595.28, 841.89]); // A4 size - page.drawText('Sample Invoice', { x: 50, y: 750, size: 20 }) + page.drawText('Sample Invoice', { x: 50, y: 750, size: 20 }); page.drawText(new Date(date).toLocaleDateString(), { x: 400, y: 750, size: 15, - }) + }); page.drawText(`Hello, ${name}!`, { x: 50, y: 700, size: 30, - }) + }); page.drawText(`Order ID: ${id}`, { x: 50, y: 650, size: 10, - }) + }); - page.drawText(`Total: $${total}`, { x: 50, y: 600, size: 15 }) + page.drawText(`Total: $${total}`, { x: 50, y: 600, size: 15 }); const orderList = items .map( ({ description, quantity, cost }) => `${description} x ${quantity} = $${cost}` ) - .join('\n') + .join('\n'); - page.drawText(orderList, { x: 50, y: 550, size: 15 }) + page.drawText(orderList, { x: 50, y: 550, size: 15 }); - const pdfBytes = await document.save() + const pdfBytes = await document.save(); - return Buffer.from(pdfBytes.buffer) + return Buffer.from(pdfBytes.buffer); } From c4c72d8b325f2c132dc7335a054762d9be5fa1cc Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:02:04 +0100 Subject: [PATCH 028/149] chore: add semis, del pjson extras --- github-issue-bot/.prettierrc.json | 2 +- github-issue-bot/package.json | 2 -- github-issue-bot/src/environment.js | 10 +++++----- github-issue-bot/src/github.js | 16 ++++++++-------- github-issue-bot/src/main.js | 26 +++++++++++++------------- 5 files changed, 27 insertions(+), 29 deletions(-) diff --git a/github-issue-bot/.prettierrc.json b/github-issue-bot/.prettierrc.json index fa51da29..0a725205 100644 --- a/github-issue-bot/.prettierrc.json +++ b/github-issue-bot/.prettierrc.json @@ -1,6 +1,6 @@ { "trailingComma": "es5", "tabWidth": 2, - "semi": false, + "semi": true, "singleQuote": true } diff --git a/github-issue-bot/package.json b/github-issue-bot/package.json index 6563dec5..2382c8a3 100644 --- a/github-issue-bot/package.json +++ b/github-issue-bot/package.json @@ -7,8 +7,6 @@ "scripts": { "format": "prettier --write src/**/*.js" }, - "author": "", - "license": "MIT", "dependencies": { "@octokit/core": "^4.2.4", "@octokit/rest": "^19.0.13", diff --git a/github-issue-bot/src/environment.js b/github-issue-bot/src/environment.js index b4a790a8..19adf729 100644 --- a/github-issue-bot/src/environment.js +++ b/github-issue-bot/src/environment.js @@ -3,11 +3,11 @@ * @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; } function EnvironmentService() { @@ -15,7 +15,7 @@ function EnvironmentService() { GITHUB_WEBHOOK_SECRET: getRequiredEnv('GITHUB_WEBHOOK_SECRET'), GITHUB_TOKEN: getRequiredEnv('GITHUB_TOKEN'), DISCORD_LINK: getRequiredEnv('DISCORD_LINK'), - } + }; } -export default EnvironmentService +export default EnvironmentService; diff --git a/github-issue-bot/src/github.js b/github-issue-bot/src/github.js index 8e23b5cf..42fd66c9 100644 --- a/github-issue-bot/src/github.js +++ b/github-issue-bot/src/github.js @@ -1,12 +1,12 @@ -import { Octokit } from '@octokit/rest' -import { verify } from '@octokit/webhooks-methods' +import { Octokit } from '@octokit/rest'; +import { verify } from '@octokit/webhooks-methods'; export default function GithubService(environment) { - const { GITHUB_TOKEN, GITHUB_WEBHOOK_SECRET } = environment + const { GITHUB_TOKEN, GITHUB_WEBHOOK_SECRET } = environment; const octokit = new Octokit({ auth: GITHUB_TOKEN, - }) + }); return { /** @@ -15,12 +15,12 @@ export default function GithubService(environment) { * @returns {Promise} */ verifyWebhook: async function (req) { - const signature = req.headers['x-hub-signature-256'] + const signature = req.headers['x-hub-signature-256']; return ( typeof signature !== 'string' || (await verify(GITHUB_WEBHOOK_SECRET, req.bodyString, signature)) - ) + ); }, /** * @param {any} issue @@ -32,7 +32,7 @@ export default function GithubService(environment) { repo: issue.repository.name, issue_number: issue.number, body: comment, - }) + }); }, - } + }; } diff --git a/github-issue-bot/src/main.js b/github-issue-bot/src/main.js index b5aef249..7fda252a 100644 --- a/github-issue-bot/src/main.js +++ b/github-issue-bot/src/main.js @@ -1,29 +1,29 @@ -import EnvironmentService from './environment.js' -import GithubService from './github.js' +import EnvironmentService from './environment.js'; +import GithubService from './github.js'; export default async ({ res, req, log, error }) => { - const environment = EnvironmentService() - const github = GithubService(environment) + const environment = EnvironmentService(); + const github = GithubService(environment); - const { DISCORD_LINK } = environment + const { DISCORD_LINK } = environment; if (!(await github.verifyWebhook(req))) { - error('Invalid signature') - return res.json({ error: 'Invalid signature' }, 401) + error('Invalid signature'); + return res.json({ error: 'Invalid signature' }, 401); } if (req.headers['x-github-event'] === 'issues') { - const { issue } = req.body + const { issue } = req.body; if (!issue || req.body.action !== 'opened') { - log('No issue provided or not opened event') - return res.json({ success: true }) + log('No issue provided or not opened event'); + return res.json({ success: true }); } await github.postComment( issue, `Thanks for the issue report @${issue.user.login}! I'm inviting you to join our Discord for quicker support: ${DISCORD_LINK}` - ) + ); } - return res.json({ success: true }) -} + return res.json({ success: true }); +}; From 124c53879f72edcf87cadac57dc109d378c139cc Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:03:46 +0100 Subject: [PATCH 029/149] chore: add semis, del pjson extras --- prompt-chatgpt/.gitignore | 131 +++++++++++++++++++++++++++++- prompt-chatgpt/.prettierrc.json | 2 +- prompt-chatgpt/package.json | 2 - prompt-chatgpt/src/environment.js | 18 ++-- prompt-chatgpt/src/main.js | 36 ++++---- prompt-chatgpt/static/index.html | 8 +- 6 files changed, 163 insertions(+), 34 deletions(-) diff --git a/prompt-chatgpt/.gitignore b/prompt-chatgpt/.gitignore index b512c09d..6a7d6d8e 100644 --- a/prompt-chatgpt/.gitignore +++ b/prompt-chatgpt/.gitignore @@ -1 +1,130 @@ -node_modules \ No newline at end of file +# 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/prompt-chatgpt/.prettierrc.json b/prompt-chatgpt/.prettierrc.json index fa51da29..0a725205 100644 --- a/prompt-chatgpt/.prettierrc.json +++ b/prompt-chatgpt/.prettierrc.json @@ -1,6 +1,6 @@ { "trailingComma": "es5", "tabWidth": 2, - "semi": false, + "semi": true, "singleQuote": true } diff --git a/prompt-chatgpt/package.json b/prompt-chatgpt/package.json index 4ec02ca1..b2ae445f 100644 --- a/prompt-chatgpt/package.json +++ b/prompt-chatgpt/package.json @@ -8,8 +8,6 @@ "format": "prettier --write src/**/*.js" }, "keywords": [], - "author": "", - "license": "ISC", "dependencies": { "openai": "^3.3.0" }, diff --git a/prompt-chatgpt/src/environment.js b/prompt-chatgpt/src/environment.js index ba31fc75..e9fc0dc6 100644 --- a/prompt-chatgpt/src/environment.js +++ b/prompt-chatgpt/src/environment.js @@ -3,11 +3,11 @@ * @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; } /** @@ -15,15 +15,15 @@ function getRequiredEnv(key) { * @return {number | undefined} */ function getNumberEnv(key) { - const value = process.env[key] + const value = process.env[key]; if (value === undefined) { - return undefined + return undefined; } try { - return parseInt(value) + return parseInt(value); } catch (e) { - throw new Error(`Environment variable ${key} is not a number`) + throw new Error(`Environment variable ${key} is not a number`); } } @@ -31,7 +31,7 @@ function EnvironmentService() { return { OPENAI_API_KEY: getRequiredEnv('OPENAI_API_KEY'), OPENAI_MAX_TOKENS: getNumberEnv('OPENAI_MAX_TOKENS') ?? 64, - } + }; } -export default EnvironmentService +export default EnvironmentService; diff --git a/prompt-chatgpt/src/main.js b/prompt-chatgpt/src/main.js index f531f5c5..de512b39 100644 --- a/prompt-chatgpt/src/main.js +++ b/prompt-chatgpt/src/main.js @@ -1,35 +1,37 @@ -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' -import { OpenAIApi, Configuration } from 'openai' -import EnvironmentService from './environment.js' +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { OpenAIApi, Configuration } from 'openai'; +import EnvironmentService from './environment.js'; -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const staticFolder = path.join(__dirname, '../static') +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const staticFolder = path.join(__dirname, '../static'); export default async ({ req, res }) => { - const { OPENAI_API_KEY, OPENAI_MAX_TOKENS } = EnvironmentService() + const { OPENAI_API_KEY, OPENAI_MAX_TOKENS } = EnvironmentService(); const configuration = new Configuration({ apiKey: OPENAI_API_KEY, - }) - const openai = new OpenAIApi(configuration) + }); + const openai = new OpenAIApi(configuration); if (req.method === 'GET') { - let html = fs.readFileSync(path.join(staticFolder, 'index.html')).toString() - return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }) + let html = fs + .readFileSync(path.join(staticFolder, 'index.html')) + .toString(); + return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }); } if (!req.bodyString) { - return res.send('Missing body with a prompt.', 400) + return res.send('Missing body with a prompt.', 400); } const chatCompletion = await openai.createChatCompletion({ model: 'gpt-3.5-turbo', max_tokens: OPENAI_MAX_TOKENS, messages: [{ role: 'user', content: req.bodyString }], - }) + }); - return res.send(chatCompletion.data.choices[0].message, 200) -} + return res.send(chatCompletion.data.choices[0].message, 200); +}; diff --git a/prompt-chatgpt/static/index.html b/prompt-chatgpt/static/index.html index 3e1017f9..40198a87 100644 --- a/prompt-chatgpt/static/index.html +++ b/prompt-chatgpt/static/index.html @@ -1,4 +1,4 @@ - + @@ -8,11 +8,11 @@ From b19aeb910447e2de93de305cfb160e74d14c4c64 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 27 Jul 2023 12:42:21 +0100 Subject: [PATCH 090/149] docs: move tutorial to contributing.md --- CONTRIBUTING.md | 46 ++++++++++++++++++++++++++++++++--- docs/add-function-template.md | 35 -------------------------- 2 files changed, 43 insertions(+), 38 deletions(-) delete mode 100644 docs/add-function-template.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04c23180..2dbfac53 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,17 +90,57 @@ Usage of dependencies is welcomed for purpose of simplifying template code. Plea ## Introducing New Templates +### 1. Starting a Discussion + We would 💖 you to contribute to Appwrite, but we also want to ensure Appwrite is loyal to its vision and mission statement 🙏. For us to find the right balance, please open an issue explaining your template idea before introducing a new pull request. This will allow the Appwrite community to sufficiently discuss the new template value. -## Tutorials +### 2. Preparing the Template + +Once you have the green light, you can go ahead and create a PR from your issue. + +We recommend first building your template in Node.js, as this is the most popular runtime in Appwrite and will allow you to get feedback before continuing to other runtimes. + +Let's build a basic Node.js template that prints "Hello World" to the console. + +1. Create a new folder in the `node` directory with the name of your template. In this case, we'll call it `hello-world`. + +2. Copy an existing `.gitignore`, and `package.json` file from the `node/starter` template into the new folder. + +3. Update the `package.json` file, changing the `name` and `description` fields to match your template. + +4. Create a new file at `node/hello-world/src/main.js` file in the new folder. This is where the template logic will be reside. + +> Tip: Other runtimes have a similar structure, but may have additional files. Be sure to view and understand the starter template for the runtime you're working with. + +### 3. Building the Template Logic + +Now that the template is setup, we can write the template logic. + +Update the `main.js` file with your template logic. +Here's a basic function that prints "Hello, World!" to the console. + +```javascript +export default function ({log}) { + log("Hello, World!"); +}; +``` + +If you're unsure about how to write your template logic, you can use the other templates as a guide, we recommend starting with the `node/starter` template. + +> Tip: All function templates should be easy to use and understand. If you find yourself writing a lot of code, consider breaking it up into multiple modules. + +### 4. Writing the Template Documentation + +Now that the template logic is complete, we can write the template documentation. + +Use the `_README_TEMPLATE.md` file as a guide for writing your template documentation. Be sure to fill out all the fields, and remove any fields that are not relevant to your template. -From time to time, our team will add tutorials that will help contributors find their way in the Appwrite source code. Below is a list of currently available tutorials: +Don't worry about updating the `README.md` file in the root of the repository, this will be done automatically when the template is merged. -- [Adding Function Template](./docs/tutorials/add-function-template.md) ## Other Ways to Help diff --git a/docs/add-function-template.md b/docs/add-function-template.md deleted file mode 100644 index 629cac95..00000000 --- a/docs/add-function-template.md +++ /dev/null @@ -1,35 +0,0 @@ -# Creating a new function template - -This document is part of the Appwrite contributors' guide. Before you continue reading this document make sure you have read the [Code of Conduct](https://github.com/appwrite/templates/blob/main/CONTRIBUTING.md) and the [Contributing Guide](https://github.com/appwrite/templates/blob/main/CONTRIBUTING.md). - -## 1. Getting started - -It's really easy to contribute to an open-source project, but when using GitHub, there are a few steps we need to follow. This section will take you step-by-step through the process of preparing your local version of Appwrite, where you can make any changes without affecting Appwrite right away. - -> If you are experienced with GitHub or have made a pull request before, you can skip to [Implement new runtime](https://github.com/appwrite/templates/blob/main/docs/add-function-template.md#2-implement-new-runtime). - -### 1.1 Fork the repository - -Before making any changes, you will need to fork the Templates repository. To do that, visit [the repository github page](https://github.com/appwrite/templates) and click on the fork button. - - -This will redirect you from `github.com/appwrite/templates` to `github.com/YOUR_USERNAME/templates`, meaning all changes you do are only done inside your repository. Once you are there, click the highlighted `Code` button, copy the URL and clone the repository to your computer using the `git clone` command: - -```bash -$ git clone COPIED_URL -``` - -> To fork a repository, you will need a basic understanding of CLI and git-cli binaries installed. If you are a beginner, we recommend you to use `Github Desktop`. It is a clean and simple visual Git client. - -Finally, you will need to create a `feat-XXX-YYY-template` branch based on the `main` branch and switch to it. The `XXX` should represent the issue ID and `YYY` the template name. - -## 2. Implementing new templates - -If you're looking to port an existing template to a new runtime, please skip to [Adding a new runtime for an existing template](https://github.com/appwrite/templates/blob/main/docs/add-function-template.md#21-adding-a-new-runtime-for-an-existing-template). - -If you want to contribute a new template, that doesn't have any existing templates, please skip to [Adding brand new templates](https://github.com/appwrite/templates/blob/main/docs/add-function-template.md#22-adding-brand-new-templates). - -### 2.1. Adding a new runtime for an existing template - - -### 2.2. Adding brand new templates From 97a0f8a612bb46333068e0c2b6660e2d3e2ddcb1 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 27 Jul 2023 13:21:58 +0100 Subject: [PATCH 091/149] chore: use json --- node/whatsapp-with-vonage/README.md | 61 +++++++++++++++++--- node/whatsapp-with-vonage/src/environment.js | 19 ------ node/whatsapp-with-vonage/src/main.js | 29 +++++++--- 3 files changed, 72 insertions(+), 37 deletions(-) delete mode 100644 node/whatsapp-with-vonage/src/environment.js diff --git a/node/whatsapp-with-vonage/README.md b/node/whatsapp-with-vonage/README.md index 396f8f5f..442cb50d 100644 --- a/node/whatsapp-with-vonage/README.md +++ b/node/whatsapp-with-vonage/README.md @@ -4,28 +4,61 @@ Automatically respond to messages sent to a Vonage WhatsApp number. ## 🧰 Usage -### `GET /` +### `GET` Serves a HTML page. -### `POST /` +### `POST` Receives a message, validates its signature, and sends a response back to the sender. **Parameters** -| Name | Description | Location | Type | Sample Value | -| ------------ | ---------------------------- | -------- | ------------------- | ------------ | -| Content-Type | Content type of the request | Header | `application/json ` | N/A | -| from | Sender's identifier. | Body | String | `12345` | -| text | Text content of the message. | Body | String | `Hello!` | +| Name | Description | Location | Type | Sample Value | +| ------------- | ----------------------------------- | -------- | ------------------- | ---------------- | +| Content-Type | Content type of the request | Header | `application/json ` | N/A | +| Authorization | Authorization token for the request | Header | String | `Bearer ` | +| from | Sender's identifier. | Body | String | `12345` | +| text | Text content of the message. | Body | String | `Hello!` | + + +To generate the Bearer token, you can use the following snippet: + +````js +import jwt from 'jsonwebtoken'; + +const token = jwt.sign( + { payload_hash: sha256(JSON.stringify(payload)) }, + process.env.VONAGE_API_SIGNATURE_SECRET +); +``` **Response** Sample `200` Response: -```text -OK +```json +{ + "ok": true +} +```` + +Sample `400` Response: + +```json +{ + "ok": false, + "error": "Missing required parameter: from" +} +``` + +Sample `401` Response: + +```json +{ + "ok": false, + "error": "Payload hash mismatch." +} ``` ## ⚙️ Configuration @@ -66,3 +99,13 @@ Secret to verify the JWT token sent by Vonage. | ------------ | ----------- | | Required | Yes | | Sample Value | `ijkl91011` | + +### VONAGE_WHATSAPP_NUMBER + +Vonage WhatsApp number to send messages from. + +| Question | Answer | +| ------------ | ----------- | +| Required | Yes | +| Sample Value | `123456789` | + diff --git a/node/whatsapp-with-vonage/src/environment.js b/node/whatsapp-with-vonage/src/environment.js deleted file mode 100644 index 18fab8a5..00000000 --- a/node/whatsapp-with-vonage/src/environment.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @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; -} - -class EnvironmentService { - VONAGE_API_KEY = getRequiredEnv('VONAGE_API_KEY'); - VONAGE_API_SECRET = getRequiredEnv('VONAGE_API_SECRET'); - VONAGE_API_SIGNATURE_SECRET = getRequiredEnv('VONAGE_API_SIGNATURE_SECRET'); -} - -export default EnvironmentService; diff --git a/node/whatsapp-with-vonage/src/main.js b/node/whatsapp-with-vonage/src/main.js index 4684971b..703bad59 100644 --- a/node/whatsapp-with-vonage/src/main.js +++ b/node/whatsapp-with-vonage/src/main.js @@ -4,15 +4,20 @@ import { fetch } from 'undici'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; -import EnvironmentService from './environment.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export default async ({ req, res }) => { - const { VONAGE_API_KEY, VONAGE_API_SECRET, VONAGE_API_SIGNATURE_SECRET } = - new EnvironmentService(); + if ( + !process.env.VONAGE_API_KEY || + !process.env.VONAGE_API_SECRET || + !process.env.VONAGE_API_SIGNATURE_SECRET || + !process.env.VONAGE_WHATSAPP_NUMBER + ) { + throw new Error('Missing environment variables.'); + } if (req.method === 'GET') { const html = fs @@ -22,24 +27,30 @@ export default async ({ req, res }) => { } const token = (req.headers.authorization ?? '').split(' ')[1]; - var decoded = jwt.verify(token, VONAGE_API_SIGNATURE_SECRET, { + var decoded = jwt.verify(token, process.env.VONAGE_API_SIGNATURE_SECRET, { algorithms: ['HS256'], }); if (sha256(req.bodyString) != decoded['payload_hash']) { - throw new Error('Invalid signature.'); + return res.json({ ok: false, error: 'Payload hash mismatch.' }, 401); } if (!req.body.from) { - throw new Error('Payload invalid.'); + return res.json( + { ok: false, error: 'Missing required parameter: from.' }, + 400 + ); } const text = req.body.text ?? 'I only accept text messages.'; + const basicAuthToken = btoa( + `${process.env.VONAGE_API_KEY}:${process.env.VONAGE_API_SECRET}` + ); await fetch(`https://messages-sandbox.nexmo.com/v1/messages`, { method: 'POST', body: JSON.stringify({ - from: '14157386102', + from: process.env.VONAGE_WHATSAPP_NUMBER, to: req.body.from, message_type: 'text', text: `Hi there! You sent me: ${text}`, @@ -47,9 +58,9 @@ export default async ({ req, res }) => { }), headers: { 'Content-Type': 'application/json', - Authorization: `Basic ${btoa(`${VONAGE_API_KEY}:${VONAGE_API_SECRET}`)}`, + Authorization: `Basic ${basicAuthToken}`, }, }); - return res.send('OK'); + return res.json({ ok: true }); }; From 7022d26338a1146454208f7508c308c143900bf7 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:04:28 +0100 Subject: [PATCH 092/149] chore: use json, error on fail --- node/analyze-with-perspectiveapi/README.md | 63 ++++++++++--------- node/analyze-with-perspectiveapi/src/main.js | 27 +++++--- .../static/index.html | 15 +++-- 3 files changed, 62 insertions(+), 43 deletions(-) diff --git a/node/analyze-with-perspectiveapi/README.md b/node/analyze-with-perspectiveapi/README.md index 337f3ff1..0db0e0ed 100644 --- a/node/analyze-with-perspectiveapi/README.md +++ b/node/analyze-with-perspectiveapi/README.md @@ -8,46 +8,53 @@ Interact with Google's perspective API to analyze the perceived impact of a comm Displays an HTML form where users can input a string of text to be analyzed. -**Response** - -Sample `200` Response: - -```text -The HTML form -``` - ### `POST` Returns toxicity score of the provided text, as determined by Google's Perspective API -| Name | Description | Location | Type | Sample Value | -|-------|-----------------|-------------|--------|------------------------------| -| text | Text to analyze | Body String | String | `Goodbye, have a great day!` | - +| Name | Description | Location | Type | Sample Value | +| ------------ | --------------------------- | -------- | ------------------ | ---------------------------- | +| Content-Type | Content type of the request | Header | `application/json` | N/A | +| text | Text to analyze | Body | String | `Goodbye, have a great day!` | **Response** Sample `200` Response: -```text -0.25 +```json +{ + "ok": true, + "score": 0.1 +} +``` + +Sample `400` Response: + +```json +{ + "ok": false, + "error": "Missing body with a prompt." +} ``` Sample `500` Response: -```text -Error analyzing text. +```json +{ + "ok": false, + "error": "Error fetching from perspective API" +} ``` ## ⚙️ Configuration -| Setting | Value | -|-------------------|------------------| -| Runtime | Node (18.0) | -| Entrypoint | `src/main.js` | -| Build Commands | `npm install` | -| Permissions | `any` | -| Timeout (Seconds) | 15 | +| Setting | Value | +| ----------------- | ------------- | +| Runtime | Node (18.0) | +| Entrypoint | `src/main.js` | +| Build Commands | `npm install` | +| Permissions | `any` | +| Timeout (Seconds) | 15 | ## 🔒 Environment Variables @@ -55,8 +62,8 @@ Error analyzing text. Google Perspective API key. It authenticates your function, allowing it to interact with the API. -| Question | Answer | -|----------------|------------------------| -| Required | Yes | -| Sample Value | `d1efbd3jaoja4` | -| Documentation | [Setup Perspective API](https://developers.google.com/codelabs/setup-perspective-api) | +| Question | Answer | +| ------------- | ------------------------------------------------------------------------------------- | +| Required | Yes | +| Sample Value | `d1efbd3jaoja4` | +| Documentation | [Setup Perspective API](https://developers.google.com/codelabs/setup-perspective-api) | diff --git a/node/analyze-with-perspectiveapi/src/main.js b/node/analyze-with-perspectiveapi/src/main.js index cde8efbf..b1449315 100644 --- a/node/analyze-with-perspectiveapi/src/main.js +++ b/node/analyze-with-perspectiveapi/src/main.js @@ -8,10 +8,8 @@ const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export default async ({ req, res }) => { - const { PERSPECTIVE_API_KEY } = process.env; - - if (!PERSPECTIVE_API_KEY) { - throw new Error('Function is missing required environment variables.'); + if (!process.env.PERSPECTIVE_API_KEY) { + throw new Error('Missing required environment variables.'); } if (req.method === 'GET') { @@ -21,12 +19,12 @@ export default async ({ req, res }) => { return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }); } - if (!req.bodyString) { - return res.send('Missing body with a prompt.', 400); + if (!req.body.text) { + return res.json({ ok: false, error: 'Missing body with a prompt.' }, 400); } const response = await fetch( - `https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key=${PERSPECTIVE_API_KEY}`, + `https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze?key=${process.env.PERSPECTIVE_API_KEY}`, { method: 'POST', headers: { @@ -46,9 +44,20 @@ export default async ({ req, res }) => { ); if (response.status !== 200) { - return res.send('Error analyzing text.', 500); + return res.json( + { ok: false, error: 'Error fetching from perspective API' }, + 500 + ); } const data = /** @type {*} */ (await response.json()); - return res.send(data.attributeScores.TOXICITY.summaryScore.value); + const score = data.attributeScores.TOXICITY.summaryScore.value; + if (!score) { + return res.json( + { ok: false, error: 'Error fetching from perspective API' }, + 500 + ); + } + + return res.json({ ok: true, score }); }; diff --git a/node/analyze-with-perspectiveapi/static/index.html b/node/analyze-with-perspectiveapi/static/index.html index 4c6c5aa4..fe333431 100644 --- a/node/analyze-with-perspectiveapi/static/index.html +++ b/node/analyze-with-perspectiveapi/static/index.html @@ -1,4 +1,4 @@ - + @@ -7,18 +7,21 @@ Analyze with Perspective API Demo From 7c7c93b88748188e4435c621401f2b1436c21b32 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:26:11 +0100 Subject: [PATCH 093/149] chore: matej review --- CONTRIBUTING.md | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2dbfac53..92bf2ea2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -98,49 +98,45 @@ For us to find the right balance, please open an issue explaining your template This will allow the Appwrite community to sufficiently discuss the new template value. -### 2. Preparing the Template +### 2. Preparing the Template -Once you have the green light, you can go ahead and create a PR from your issue. +Once you have the go-ahead, you can proceed to create a PR from your issue. -We recommend first building your template in Node.js, as this is the most popular runtime in Appwrite and will allow you to get feedback before continuing to other runtimes. +The preparation process is largely dependent on the runtime environment you choose to work with. We recommend starting with Node.JS and then moving on to other runtimes, after receiving feedback. Here's a generic process that could be applied to most runtimes: -Let's build a basic Node.js template that prints "Hello World" to the console. +1. **Create a new folder** in the directory for your specific runtime with the name of your template. -1. Create a new folder in the `node` directory with the name of your template. In this case, we'll call it `hello-world`. +2. **Initialize your project**. In Node.js for example, you could run `npm init` in the new folder. -2. Copy an existing `.gitignore`, and `package.json` file from the `node/starter` template into the new folder. +3. **Add a `.gitignore` file** to the new directory, to ignore files and directories that don't need to be version controlled. -3. Update the `package.json` file, changing the `name` and `description` fields to match your template. +4. **Add configuration files specific to your runtime**. This may include formatter configurations, lockfiles or others. -4. Create a new file at `node/hello-world/src/main.js` file in the new folder. This is where the template logic will be reside. +5. **Install necessary dependencies**. Using Node.js as an example, you could run `npm install `. -> Tip: Other runtimes have a similar structure, but may have additional files. Be sure to view and understand the starter template for the runtime you're working with. +6. **Create an entry point in the `src` folder**. This will be the main file where the template logic resides. + +> Tip: Be sure to take a look at `starter` templates templates to get a better understanding of how they are structured. ### 3. Building the Template Logic -Now that the template is setup, we can write the template logic. +With the template setup, you can proceed to writing the template logic in the entry point file. -Update the `main.js` file with your template logic. -Here's a basic function that prints "Hello, World!" to the console. +The writing process should focus more on readability, maintainability and simplicity. It's essential to write code that tells a story. If the logic begins to look complex, consider breaking it down into smaller, manageable files or re-using services from existing templates where applicable. -```javascript -export default function ({log}) { - log("Hello, World!"); -}; -``` +> Tip: Be sure to look at some of the existing templates to understand how we expect the code -If you're unsure about how to write your template logic, you can use the other templates as a guide, we recommend starting with the `node/starter` template. +### 4. Writing the Template Documentation -> Tip: All function templates should be easy to use and understand. If you find yourself writing a lot of code, consider breaking it up into multiple modules. +After completing the template logic, the next step is to document the template. This will be very useful to anyone who wants to understand or use your template in the future. -### 4. Writing the Template Documentation +The `_README_TEMPLATE.md` file serves as a guide for writing your template documentation. Ensure you complete all the fields and remove any that are not relevant to your template. -Now that the template logic is complete, we can write the template documentation. -Use the `_README_TEMPLATE.md` file as a guide for writing your template documentation. Be sure to fill out all the fields, and remove any fields that are not relevant to your template. -Don't worry about updating the `README.md` file in the root of the repository, this will be done automatically when the template is merged. +> Note: You don't need to update the table within the `README.md` file in the root of the repository. This will be done automatically once the template is merged. +Once all the steps are completed, you can submit your PR for review. Make sure to include any necessary details in the PR description. This makes it easier for the reviewers to understand the context and provide constructive feedback. ## Other Ways to Help @@ -168,4 +164,4 @@ Submitting documentation updates, enhancements, designs, or bug fixes, as well a ### Helping Someone -Consider searching for Appwrite on Discord, GitHub, or StackOverflow to help someone who needs help. You can also help by teaching others how to contribute to Appwrite's repo! \ No newline at end of file +Consider searching for Appwrite on Discord, GitHub, or StackOverflow to help someone who needs help. You can also help by teaching others how to contribute to Appwrite's repo! From 255bedea422bc5684f87de130360d31e98394c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 27 Jul 2023 17:49:30 +0200 Subject: [PATCH 094/149] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92bf2ea2..cc402e1c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,7 +114,7 @@ The preparation process is largely dependent on the runtime environment you choo 5. **Install necessary dependencies**. Using Node.js as an example, you could run `npm install `. -6. **Create an entry point in the `src` folder**. This will be the main file where the template logic resides. +6. **Create an entrypoint in the `src` folder**. This will be the main file where the template logic resides. > Tip: Be sure to take a look at `starter` templates templates to get a better understanding of how they are structured. From 8fc86e32f4f3e17506885ff1d504681e11f282ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 27 Jul 2023 17:50:04 +0200 Subject: [PATCH 095/149] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc402e1c..1c419038 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -120,7 +120,7 @@ The preparation process is largely dependent on the runtime environment you choo ### 3. Building the Template Logic -With the template setup, you can proceed to writing the template logic in the entry point file. +With the template setup, you can proceed to writing the template logic in the entrypoint file. The writing process should focus more on readability, maintainability and simplicity. It's essential to write code that tells a story. If the logic begins to look complex, consider breaking it down into smaller, manageable files or re-using services from existing templates where applicable. From eed86e81bc15d10f44b3403c594fb43287dfb95a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 27 Jul 2023 17:50:43 +0200 Subject: [PATCH 096/149] Update CONTRIBUTING.md --- CONTRIBUTING.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c419038..b65aef8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -131,9 +131,6 @@ The writing process should focus more on readability, maintainability and simpli After completing the template logic, the next step is to document the template. This will be very useful to anyone who wants to understand or use your template in the future. The `_README_TEMPLATE.md` file serves as a guide for writing your template documentation. Ensure you complete all the fields and remove any that are not relevant to your template. - - - > Note: You don't need to update the table within the `README.md` file in the root of the repository. This will be done automatically once the template is merged. Once all the steps are completed, you can submit your PR for review. Make sure to include any necessary details in the PR description. This makes it easier for the reviewers to understand the context and provide constructive feedback. From 44b617722d330d67b4a58688312b3d1a971fd8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 27 Jul 2023 15:52:47 +0000 Subject: [PATCH 097/149] Fix deno starter docs --- deno/starter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno/starter/README.md b/deno/starter/README.md index a5c0b41f..598daa02 100644 --- a/deno/starter/README.md +++ b/deno/starter/README.md @@ -35,7 +35,7 @@ Sample `200` Response: ## ⚙️ Configuration -| Setting | Value ----------- | +| Setting | Value | |-------------------|--------------------------| | Runtime | Deno (1.35) | | Entrypoint | `src/main.ts` | From 002322d8baecf5afb6fe13852243ad3fe59b4ce1 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:08:27 +0100 Subject: [PATCH 098/149] chore: use json, add undici --- node/censor-with-redact/README.md | 63 ++++++++++++++--------------- node/censor-with-redact/src/main.js | 17 ++++---- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/node/censor-with-redact/README.md b/node/censor-with-redact/README.md index eb039dd6..74320541 100644 --- a/node/censor-with-redact/README.md +++ b/node/censor-with-redact/README.md @@ -1,4 +1,4 @@ -# ⚡ Censor with Redact Function +# 🛑 Censor with Redact Function Cesnor sensitive information from a provided text string using Redact API by Pangea. @@ -6,16 +6,7 @@ Cesnor sensitive information from a provided text string using Redact API by Pan ### `GET` -An HTML form page that can be used to enter text and see the redacted result. - - -**Response** - -Sample `200` Response: - -```text -The HTML form -``` +Returns an HTML form page that can be used to enter text and see the redacted result. ### `POST` @@ -23,44 +14,52 @@ Returns the supplied text string with sensitive information redacted. **Parameters** -| Name | Description | Location | Type | Sample Value | -|--------|----------------|-------------|--------|-----------------------------------------------------| -| N/A | Text to redact | Text Body | String | `Hello! My email address is dennis.nedry@ingen.com` | +| Name | Description | Location | Type | Sample Value | +| ------------ | --------------------------- | -------- | ------------------ | -------------------------------------------- | +| Content-Type | Content type of the request | Header | `application/json` | N/A | +| text | Text to redact | Body | String | `My email address is dennis.nedry@ingen.com` | **Response** Sample `200` Response: Content-Type: `` -```text -Hello! my email address is -``` +```json +{ + "ok": true, + "redacted": "My email address is " +} +``` Sample `400` Response: -```text -Missing body with a prompt. +```json +{ + "ok": false, + "error": "Missing required field: text." +} + ``` ## ⚙️ Configuration -| Setting | Value | -|-------------------|------------------| -| Runtime | Node (18.0) | -| Entrypoint | `src/main.js` | -| Build Commands | `npm install` | -| Permissions | `any` | -| Timeout (Seconds) | 15 | +| Setting | Value | +| ----------------- | ------------- | +| Runtime | Node (18.0) | +| Entrypoint | `src/main.js` | +| Build Commands | `npm install` | +| Permissions | `any` | +| Timeout (Seconds) | 15 | ## 🔒 Environment Variables ### PANGEA_REDACT_TOKEN -Access token for the Pangea Redact API +Access token for the Pangea Redact API -| Question | Answer | -|----------------|------------------------| -| Required | Yes | -| Sample Value | `d1efbad42adgj` | -| Documentation | [Pangea: Configuration](https://pangea.cloud/docs/redact/getting-started/configuration) | +| Question | Answer | +| ------------- | --------------------------------------------------------------------------------------- | +| Required | Yes | +| Sample Value | `d1efbad42adgj` | +| Documentation | [Pangea: Configuration](https://pangea.cloud/docs/redact/getting-started/configuration) | diff --git a/node/censor-with-redact/src/main.js b/node/censor-with-redact/src/main.js index faa8ea98..b25cbe4a 100644 --- a/node/censor-with-redact/src/main.js +++ b/node/censor-with-redact/src/main.js @@ -1,16 +1,15 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; +import { fetch } from 'undici'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); export default async ({ req, res }) => { - const { PANGEA_REDACT_TOKEN } = process.env; - - if (!PANGEA_REDACT_TOKEN) { - throw new Error('Function is missing required environment variables.'); + if (!process.env.PANGEA_REDACT_TOKEN) { + throw new Error('Missing required environment variables.'); } if (req.method === 'GET') { @@ -20,21 +19,21 @@ export default async ({ req, res }) => { return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }); } - if (!req.bodyString) { - return res.send('Missing body with a prompt.', 400); + if (!req.body.text) { + return res.json({ ok: false, error: 'Missing require field: text.' }, 400); } const response = await fetch(`https://redact.aws.eu.pangea.cloud/v1/redact`, { method: 'POST', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${PANGEA_REDACT_TOKEN}`, + Authorization: `Bearer ${process.env.PANGEA_REDACT_TOKEN}`, }, body: JSON.stringify({ - text: req.bodyString, + text: req.body.text, }), }); const data = /** @type {*} */ (await response.json()); - return res.send(data.result.redacted_text); + return res.json({ ok: true, redacted: data.result.redacted_text }); }; From 2d23e0241b929ccbd5f2964020cc76f96056f407 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 27 Jul 2023 16:25:48 +0000 Subject: [PATCH 099/149] Auto: README table generation --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 17be8225..2ba26cc1 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ Templates for [Appwrite](https://appwrite.io/) Functions. These templates can be # List of Templates +| Template | C++ | Dart | Deno | .NET | Java | Kotlin | Node.js | PHP | Python | Ruby | Swift | +| -------- | ----------------- | ------------------ | ------------------ | -------------------- | ------------------ | -------------------- | ------------------ | ----------------- | -------------------- | ------------------ | ------------------- | +| starter | [✅](/cpp/starter) | [✅](/dart/starter) | [✅](/deno/starter) | [✅](/dotnet/starter) | [✅](/java/starter) | [✅](/kotlin/starter) | [✅](/node/starter) | [✅](/php/starter) | [✅](/python/starter) | [✅](/ruby/starter) | [✅](/swift/starter) | ✅ = Done - Function is implemented in this runtime. From 8e73a50022d7e7ece646fbcd2d8ff4aa1a209e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 27 Jul 2023 18:51:43 +0200 Subject: [PATCH 100/149] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2ba26cc1..abba44ce 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Templates for [Appwrite](https://appwrite.io/) Functions. These templates can be ✅ = Done - Function is implemented in this runtime. + ❌ = Missing - Function isn't implemented in this runtime yet. Contributions are welcomed. ## Contributing From c1006cf3a0c6b185501ca199586d0ce1b7607bc8 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:50:27 +0100 Subject: [PATCH 101/149] chore: throwIfMissing pattern --- node/prompt-chatgpt/env.d.ts | 10 ++++++++++ node/prompt-chatgpt/src/main.js | 17 +++++++++-------- node/prompt-chatgpt/src/utils.js | 12 ++++++++++++ 3 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 node/prompt-chatgpt/env.d.ts create mode 100644 node/prompt-chatgpt/src/utils.js diff --git a/node/prompt-chatgpt/env.d.ts b/node/prompt-chatgpt/env.d.ts new file mode 100644 index 00000000..2a46aaa2 --- /dev/null +++ b/node/prompt-chatgpt/env.d.ts @@ -0,0 +1,10 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + OPENAI_API_KEY?: string; + OPENAI_MAX_TOKENS?: string; + } + } +} + +export {}; diff --git a/node/prompt-chatgpt/src/main.js b/node/prompt-chatgpt/src/main.js index 19eb0ae3..2b989224 100644 --- a/node/prompt-chatgpt/src/main.js +++ b/node/prompt-chatgpt/src/main.js @@ -2,15 +2,14 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { OpenAIApi, Configuration } from 'openai'; +import { throwIfMissing } from './utils'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const staticFolder = path.join(__dirname, '../static'); -export default async ({ req, res }) => { - if (!process.env.OPENAI_API_KEY || !process.env.OPENAI_MAX_TOKENS) { - throw new Error('Missing environment variables.'); - } +export default async ({ req, res, error }) => { + throwIfMissing(process.env, ['OPENAI_API_KEY', 'OPENAI_MAX_TOKENS']); const configuration = new Configuration({ apiKey: process.env.OPENAI_API_KEY, @@ -24,17 +23,19 @@ export default async ({ req, res }) => { return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }); } - if (!req.body.prompt) { - return res.json({ ok: false, error: 'Missing body with a prompt.' }, 400); + try { + throwIfMissing(req.body, ['prompt']); + } catch (err) { + return res.json({ ok: false, error: err.message }, 400); } const response = await openai.createChatCompletion({ model: 'gpt-3.5-turbo', - max_tokens: parseInt(process.env.OPENAI_MAX_TOKENS) ?? 512, + max_tokens: parseInt(process.env.OPENAI_MAX_TOKENS ?? '512'), messages: [{ role: 'user', content: req.body.prompt }], }); - const completion = response.data.choices[0].message; + const completion = response.data.choices[0].message; if (!completion) { return res.json({ ok: false, error: 'Failed to query model.' }, 500); } diff --git a/node/prompt-chatgpt/src/utils.js b/node/prompt-chatgpt/src/utils.js new file mode 100644 index 00000000..9e22457d --- /dev/null +++ b/node/prompt-chatgpt/src/utils.js @@ -0,0 +1,12 @@ +/** + * + * @param {*} obj + * @param {string[]} keys + */ +export function throwIfMissing(obj, keys) { + for (let key of keys) { + if (!(key in obj) || !obj[key]) { + throw new Error(`Missing required value: ${key}`); + } + } +} From d594634f3d65b0a04609b26c20bae95d6422f6a8 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:52:15 +0100 Subject: [PATCH 102/149] chore: better --- node/prompt-chatgpt/src/utils.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/node/prompt-chatgpt/src/utils.js b/node/prompt-chatgpt/src/utils.js index 9e22457d..80adbd3b 100644 --- a/node/prompt-chatgpt/src/utils.js +++ b/node/prompt-chatgpt/src/utils.js @@ -4,9 +4,13 @@ * @param {string[]} keys */ export function throwIfMissing(obj, keys) { + const missing = []; for (let key of keys) { if (!(key in obj) || !obj[key]) { - throw new Error(`Missing required value: ${key}`); + missing.push(key); } } + if (missing.length > 0) { + throw new Error(`Missing required fields: ${missing.join(', ')}`); + } } From 076627b3f475001a0ca4730c22d25c0c58728396 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 28 Jul 2023 12:00:31 +0100 Subject: [PATCH 103/149] chore: utils --- node/prompt-chatgpt/src/main.js | 16 ++++------------ node/prompt-chatgpt/src/utils.js | 20 +++++++++++++++++++- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/node/prompt-chatgpt/src/main.js b/node/prompt-chatgpt/src/main.js index 2b989224..0ac3a1af 100644 --- a/node/prompt-chatgpt/src/main.js +++ b/node/prompt-chatgpt/src/main.js @@ -1,12 +1,5 @@ -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; import { OpenAIApi, Configuration } from 'openai'; -import { throwIfMissing } from './utils'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const staticFolder = path.join(__dirname, '../static'); +import { getStaticFile, throwIfMissing } from './utils'; export default async ({ req, res, error }) => { throwIfMissing(process.env, ['OPENAI_API_KEY', 'OPENAI_MAX_TOKENS']); @@ -17,10 +10,9 @@ export default async ({ req, res, error }) => { const openai = new OpenAIApi(configuration); if (req.method === 'GET') { - let html = fs - .readFileSync(path.join(staticFolder, 'index.html')) - .toString(); - return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }); + return res.send(getStaticFile('index.html'), 200, { + 'Content-Type': 'text/html; charset=utf-8', + }); } try { diff --git a/node/prompt-chatgpt/src/utils.js b/node/prompt-chatgpt/src/utils.js index 80adbd3b..5b93ba58 100644 --- a/node/prompt-chatgpt/src/utils.js +++ b/node/prompt-chatgpt/src/utils.js @@ -1,7 +1,12 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs'; + /** - * + * 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 = []; @@ -14,3 +19,16 @@ export function throwIfMissing(obj, keys) { throw new Error(`Missing required fields: ${missing.join(', ')}`); } } + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const staticFolder = path.join(__dirname, '../static'); + +/** + * Returns the contents of a file in the static folder + * @param {string} fileName + * @returns {string} Contents of static/{fileName} + */ +export function getStaticFile(fileName) { + return fs.readFileSync(path.join(staticFolder, fileName)).toString(); +} From 1d1b5bd049e5e43bb5faaca7b84b7e394c58bae8 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 28 Jul 2023 12:03:46 +0100 Subject: [PATCH 104/149] chore: new utils --- node/analyze-with-perspectiveapi/src/main.js | 27 ++++++--------- node/analyze-with-perspectiveapi/src/utils.js | 34 +++++++++++++++++++ 2 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 node/analyze-with-perspectiveapi/src/utils.js diff --git a/node/analyze-with-perspectiveapi/src/main.js b/node/analyze-with-perspectiveapi/src/main.js index b1449315..7394c300 100644 --- a/node/analyze-with-perspectiveapi/src/main.js +++ b/node/analyze-with-perspectiveapi/src/main.js @@ -1,26 +1,19 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; import { fetch } from 'undici'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const staticFolder = path.join(__dirname, '../static'); +import { getStaticFile, throwIfMissing } from './utils'; export default async ({ req, res }) => { - if (!process.env.PERSPECTIVE_API_KEY) { - throw new Error('Missing required environment variables.'); - } + throwIfMissing(process.env, ['PERSPECTIVE_API_KEY']); if (req.method === 'GET') { - const html = fs - .readFileSync(path.join(staticFolder, 'index.html')) - .toString(); - return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }); + return res.send(getStaticFile('index.html'), 200, { + 'Content-Type': 'text/html; charset=utf-8', + }); } - if (!req.body.text) { - return res.json({ ok: false, error: 'Missing body with a prompt.' }, 400); + try { + throwIfMissing(req.body, ['text']); + } catch (err) { + return res.json({ ok: false, error: err.message }, 400); } const response = await fetch( @@ -32,7 +25,7 @@ export default async ({ req, res }) => { }, body: JSON.stringify({ comment: { - text: req.bodyString, + text: req.bodyRaw, type: 'PLAIN_TEXT', }, languages: ['en'], diff --git a/node/analyze-with-perspectiveapi/src/utils.js b/node/analyze-with-perspectiveapi/src/utils.js new file mode 100644 index 00000000..5b93ba58 --- /dev/null +++ b/node/analyze-with-perspectiveapi/src/utils.js @@ -0,0 +1,34 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs'; + +/** + * 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(', ')}`); + } +} + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const staticFolder = path.join(__dirname, '../static'); + +/** + * Returns the contents of a file in the static folder + * @param {string} fileName + * @returns {string} Contents of static/{fileName} + */ +export function getStaticFile(fileName) { + return fs.readFileSync(path.join(staticFolder, fileName)).toString(); +} From 8d6bc9737da0e96da216ae928db5649e2efa8692 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 28 Jul 2023 12:19:13 +0100 Subject: [PATCH 105/149] chore: new utils --- node/censor-with-redact/src/main.js | 25 ++++++++------------ node/censor-with-redact/src/utils.js | 34 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 node/censor-with-redact/src/utils.js diff --git a/node/censor-with-redact/src/main.js b/node/censor-with-redact/src/main.js index b25cbe4a..ea14925d 100644 --- a/node/censor-with-redact/src/main.js +++ b/node/censor-with-redact/src/main.js @@ -1,26 +1,19 @@ -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; import { fetch } from 'undici'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const staticFolder = path.join(__dirname, '../static'); +import { getStaticFile, throwIfMissing } from './utils'; export default async ({ req, res }) => { - if (!process.env.PANGEA_REDACT_TOKEN) { - throw new Error('Missing required environment variables.'); - } + throwIfMissing(process.env, ['PANGEA_REDACT_TOKEN']); if (req.method === 'GET') { - const html = fs - .readFileSync(path.join(staticFolder, 'index.html')) - .toString(); - return res.send(html, 200, { 'Content-Type': 'text/html; charset=utf-8' }); + return res.send(getStaticFile('index.html'), 200, { + 'Content-Type': 'text/html; charset=utf-8', + }); } - if (!req.body.text) { - return res.json({ ok: false, error: 'Missing require field: text.' }, 400); + try { + throwIfMissing(req.body, ['text']); + } catch (err) { + return res.json({ ok: false, error: err.message }, 400); } const response = await fetch(`https://redact.aws.eu.pangea.cloud/v1/redact`, { diff --git a/node/censor-with-redact/src/utils.js b/node/censor-with-redact/src/utils.js new file mode 100644 index 00000000..5b93ba58 --- /dev/null +++ b/node/censor-with-redact/src/utils.js @@ -0,0 +1,34 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs'; + +/** + * 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(', ')}`); + } +} + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const staticFolder = path.join(__dirname, '../static'); + +/** + * Returns the contents of a file in the static folder + * @param {string} fileName + * @returns {string} Contents of static/{fileName} + */ +export function getStaticFile(fileName) { + return fs.readFileSync(path.join(staticFolder, fileName)).toString(); +} From 27545bf013680acde363f010b98dfd8014fd3b69 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 28 Jul 2023 12:39:17 +0100 Subject: [PATCH 106/149] chore: new utils --- node/email-contact-form/src/cors.js | 41 ++------ node/email-contact-form/src/environment.js | 22 ---- node/email-contact-form/src/mail.js | 16 ++- node/email-contact-form/src/main.js | 113 ++++++++------------- node/email-contact-form/src/utils.js | 57 +++++++++++ 5 files changed, 119 insertions(+), 130 deletions(-) delete mode 100644 node/email-contact-form/src/environment.js create mode 100644 node/email-contact-form/src/utils.js diff --git a/node/email-contact-form/src/cors.js b/node/email-contact-form/src/cors.js index 20509ce0..a2633a0d 100644 --- a/node/email-contact-form/src/cors.js +++ b/node/email-contact-form/src/cors.js @@ -1,32 +1,13 @@ -class CorsService { - /** - * @param {*} req - Request object - * @param {import('./environment').default} env - Environment variables - */ - constructor(req, env) { - this.env = env; - this.origin = req.headers['origin']; - } - - /** - * @returns {boolean} Whether the origin is allowed based on the ALLOWED_ORIGINS environment variable - */ - isOriginPermitted() { - if (!this.env.ALLOWED_ORIGINS || this.env.ALLOWED_ORIGINS === '*') - return true; - const allowedOriginsArray = this.env.ALLOWED_ORIGINS.split(','); - return allowedOriginsArray.includes(this.origin); - } - - /** - * @returns {Object} Access-Control-Allow-Origin header to be returned in the response - */ - getHeaders() { - return { - 'Access-Control-Allow-Origin': - this.env.ALLOWED_ORIGINS === '*' ? '*' : this.origin, - }; - } +export function isOriginPermitted(req) { + if (!process.env.ALLOWED_ORIGINS || process.env.ALLOWED_ORIGINS === '*') + return true; + const allowedOriginsArray = process.env.ALLOWED_ORIGINS.split(','); + return allowedOriginsArray.includes(this.origin); } -export default CorsService; +export function getCorsHeaders(req) { + return { + 'Access-Control-Allow-Origin': + process.env.ALLOWED_ORIGINS === '*' ? '*' : this.origin, + }; +} diff --git a/node/email-contact-form/src/environment.js b/node/email-contact-form/src/environment.js deleted file mode 100644 index 5aa7fd1f..00000000 --- a/node/email-contact-form/src/environment.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @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; -} - -class EnvironmentService { - SUBMIT_EMAIL = getRequiredEnv('SUBMIT_EMAIL'); - SMTP_HOST = getRequiredEnv('SMTP_HOST'); - SMTP_PORT = process.env.SMTP_PORT || 587; - SMTP_USERNAME = getRequiredEnv('SMTP_USERNAME'); - SMTP_PASSWORD = getRequiredEnv('SMTP_PASSWORD'); - ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS || '*'; -} - -export default EnvironmentService; diff --git a/node/email-contact-form/src/mail.js b/node/email-contact-form/src/mail.js index e18b07cc..3ed2c982 100644 --- a/node/email-contact-form/src/mail.js +++ b/node/email-contact-form/src/mail.js @@ -1,18 +1,16 @@ import nodemailer from 'nodemailer'; class MailService { - /** - * @param {import('./environment').default} env - */ - constructor(env) { - this.env = env; - + constructor() { this.transport = nodemailer.createTransport({ // @ts-ignore // Not sure what's going on here. - host: env.SMTP_HOST, - port: env.SMTP_PORT, - auth: { user: env.SMTP_USERNAME, pass: env.SMTP_PASSWORD }, + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT || 587, + auth: { + user: process.env.SMTP_USERNAME, + pass: process.env.SMTP_PASSWORD, + }, }); } diff --git a/node/email-contact-form/src/main.js b/node/email-contact-form/src/main.js index 95882444..dfdc6188 100644 --- a/node/email-contact-form/src/main.js +++ b/node/email-contact-form/src/main.js @@ -1,120 +1,95 @@ import querystring from 'node:querystring'; -import fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import path from 'node:path'; -import CorsService from './cors.js'; +import { getCorsHeaders, isOriginPermitted } from './cors.js'; import MailService from './mail.js'; -import EnvironmentService from './environment.js'; +import { + getStaticFile, + throwIfMissing, + urlWithCodeParam, + templateFormMessage, +} from './utils.js'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const staticFolder = path.join(__dirname, '../static'); +const ErrorCode = { + INVALID_REQUEST: 'invalid-request', + MISSING_FORM_FIELDS: 'missing-form-fields', + SERVER_ERROR: 'server-error', +}; export default async ({ req, res, log, error }) => { - const env = new EnvironmentService(); - - if (env.ALLOWED_ORIGINS === '*') { + throwIfMissing(process.env, [ + 'SUBMIT_EMAIL', + 'SMTP_HOST', + 'SMTP_USERNAME', + 'SMTP_PASSWORD', + ]); + + if (!process.env.ALLOWED_ORIGINS || process.env.ALLOWED_ORIGINS === '*') { log( 'WARNING: Allowing requests from any origin - this is a security risk!' ); } if (req.method === 'GET' && req.path === '/') { - const html = fs.readFileSync(path.join(staticFolder, 'index.html')); - return res.send(html.toString(), 200, { + return res.send(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } - const referer = req.headers['referer']; - const origin = req.headers['origin']; - if (!referer || !origin) { - log('Missing referer or origin headers.'); - return res.json({ error: 'Missing referer or origin headers.' }, 400); - } + throwIfMissing(req.headers, ['referer', 'origin']); if (req.headers['content-type'] !== 'application/x-www-form-urlencoded') { log('Invalid request.'); - return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)); + return res.redirect( + urlWithCodeParam(req.headers['referer'], 'Invalid request.') + ); } - const cors = new CorsService(req, env); - const mail = new MailService(env); + const mail = new MailService(); - if (!cors.isOriginPermitted()) { + if (!isOriginPermitted(req.headers['origin'])) { error('Origin not permitted.'); - return res.redirect(urlWithCodeParam(referer, ErrorCode.INVALID_REQUEST)); + return res.redirect( + urlWithCodeParam(req.headers['referer'], ErrorCode.INVALID_REQUEST) + ); } const form = querystring.parse(req.body); - - if (!(form.email && typeof form.email === 'string')) { - error('Missing form data.'); + try { + throwIfMissing(form, ['email']); + log('Form data is valid.'); + } catch (err) { return res.redirect( - urlWithCodeParam(referer, ErrorCode.MISSING_FORM_FIELDS), + urlWithCodeParam(req.headers['referer'], err.message), 301, - cors.getHeaders() + getCorsHeaders(req.headers['origin']) ); } - log('Form data is valid.'); try { mail.send({ - to: env.SUBMIT_EMAIL, - from: form.email, + to: process.env.SUBMIT_EMAIL, + from: /** @type {string} */ (form['email']), subject: `New form submission: ${origin}`, text: templateFormMessage(form), }); + log('Email sent successfully.'); } catch (err) { error(err.message); return res.redirect( - urlWithCodeParam(referer, ErrorCode.SERVER_ERROR), + urlWithCodeParam(req.headers['referer'], ErrorCode.SERVER_ERROR), 301, - cors.getHeaders() + getCorsHeaders(req.headers['origin']) ); } - log('Email sent successfully.'); - if (typeof form._next !== 'string' || !form._next) { - const html = fs.readFileSync(path.join(staticFolder, 'success.html')); - return res.send(html.toString(), 200, { + return res.send(getStaticFile('success.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', }); } return res.redirect( - new URL(form._next, origin).toString(), + new URL(form._next, req.headers['origin']).toString(), 301, - cors.getHeaders() + getCorsHeaders(req.headers['origin']) ); }; - -/** - * Build a message from the form data. - * @param {import("node:querystring").ParsedUrlQuery} form - * @returns {string} - */ -function templateFormMessage(form) { - return `You've received a new message.\n -${Object.entries(form) - .filter(([key]) => key !== '_next') - .map(([key, value]) => `${key}: ${value}`) - .join('\n')}`; -} - -const ErrorCode = { - INVALID_REQUEST: 'invalid-request', - MISSING_FORM_FIELDS: 'missing-form-fields', - SERVER_ERROR: 'server-error', -}; - -/** - * @param {string} baseUrl - * @param {string} codeParam - */ -function urlWithCodeParam(baseUrl, codeParam) { - const url = new URL(baseUrl); - url.searchParams.set('code', codeParam); - return url.toString(); -} diff --git a/node/email-contact-form/src/utils.js b/node/email-contact-form/src/utils.js new file mode 100644 index 00000000..4a6e6d5c --- /dev/null +++ b/node/email-contact-form/src/utils.js @@ -0,0 +1,57 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs'; + +/** + * 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(', ')}`); + } +} + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const staticFolder = path.join(__dirname, '../static'); + +/** + * Returns the contents of a file in the static folder + * @param {string} fileName + * @returns {string} Contents of static/{fileName} + */ +export function getStaticFile(fileName) { + return fs.readFileSync(path.join(staticFolder, fileName)).toString(); +} + +/** + * Build a message from the form data. + * @param {import("node:querystring").ParsedUrlQuery} form + * @returns {string} + */ +export function templateFormMessage(form) { + return `You've received a new message.\n + ${Object.entries(form) + .filter(([key]) => key !== '_next') + .map(([key, value]) => `${key}: ${value}`) + .join('\n')}`; +} + +/** + * @param {string} baseUrl + * @param {string} codeParam + */ +export function urlWithCodeParam(baseUrl, codeParam) { + const url = new URL(baseUrl); + url.searchParams.set('code', codeParam); + return url.toString(); +} From de81b35cf044de09825a0ee13b57232ddfb1ebf4 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 28 Jul 2023 12:41:03 +0100 Subject: [PATCH 107/149] chore: remove excess logs --- node/email-contact-form/src/main.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/node/email-contact-form/src/main.js b/node/email-contact-form/src/main.js index dfdc6188..b25d5eb2 100644 --- a/node/email-contact-form/src/main.js +++ b/node/email-contact-form/src/main.js @@ -37,7 +37,6 @@ export default async ({ req, res, log, error }) => { throwIfMissing(req.headers, ['referer', 'origin']); if (req.headers['content-type'] !== 'application/x-www-form-urlencoded') { - log('Invalid request.'); return res.redirect( urlWithCodeParam(req.headers['referer'], 'Invalid request.') ); @@ -55,7 +54,6 @@ export default async ({ req, res, log, error }) => { const form = querystring.parse(req.body); try { throwIfMissing(form, ['email']); - log('Form data is valid.'); } catch (err) { return res.redirect( urlWithCodeParam(req.headers['referer'], err.message), @@ -71,7 +69,6 @@ export default async ({ req, res, log, error }) => { subject: `New form submission: ${origin}`, text: templateFormMessage(form), }); - log('Email sent successfully.'); } catch (err) { error(err.message); return res.redirect( From 75a8cdf11e9869376748e79535cf06a1dd6eec5c Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:26:44 +0100 Subject: [PATCH 108/149] fix: esm import --- node/censor-with-redact/src/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/censor-with-redact/src/main.js b/node/censor-with-redact/src/main.js index ea14925d..e39ae297 100644 --- a/node/censor-with-redact/src/main.js +++ b/node/censor-with-redact/src/main.js @@ -1,5 +1,5 @@ import { fetch } from 'undici'; -import { getStaticFile, throwIfMissing } from './utils'; +import { getStaticFile, throwIfMissing } from './utils.js'; export default async ({ req, res }) => { throwIfMissing(process.env, ['PANGEA_REDACT_TOKEN']); From 345927df145909b35ae2fb44505e376c7a65c27e Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:27:05 +0100 Subject: [PATCH 109/149] fix: esm import --- node/analyze-with-perspectiveapi/src/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/analyze-with-perspectiveapi/src/main.js b/node/analyze-with-perspectiveapi/src/main.js index 7394c300..236e86e9 100644 --- a/node/analyze-with-perspectiveapi/src/main.js +++ b/node/analyze-with-perspectiveapi/src/main.js @@ -1,5 +1,5 @@ import { fetch } from 'undici'; -import { getStaticFile, throwIfMissing } from './utils'; +import { getStaticFile, throwIfMissing } from './utils.js'; export default async ({ req, res }) => { throwIfMissing(process.env, ['PERSPECTIVE_API_KEY']); From c07d9b10b1d350eaa2230c52ba935e7494e4e844 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:34:54 +0100 Subject: [PATCH 110/149] chore: new utils --- node/github-issue-bot/src/environment.js | 19 -------------- node/github-issue-bot/src/github.js | 14 +++++----- node/github-issue-bot/src/main.js | 33 ++++++++++++------------ node/github-issue-bot/src/utils.js | 17 ++++++++++++ 4 files changed, 41 insertions(+), 42 deletions(-) delete mode 100644 node/github-issue-bot/src/environment.js create mode 100644 node/github-issue-bot/src/utils.js diff --git a/node/github-issue-bot/src/environment.js b/node/github-issue-bot/src/environment.js deleted file mode 100644 index 6f267607..00000000 --- a/node/github-issue-bot/src/environment.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @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; -} - -class EnvironmentService { - GITHUB_WEBHOOK_SECRET = getRequiredEnv('GITHUB_WEBHOOK_SECRET'); - GITHUB_TOKEN = getRequiredEnv('GITHUB_TOKEN'); - DISCORD_LINK = getRequiredEnv('DISCORD_LINK'); -} - -export default EnvironmentService; diff --git a/node/github-issue-bot/src/github.js b/node/github-issue-bot/src/github.js index 107fead0..37d7c5db 100644 --- a/node/github-issue-bot/src/github.js +++ b/node/github-issue-bot/src/github.js @@ -2,13 +2,9 @@ import { Octokit } from '@octokit/rest'; import { verify } from '@octokit/webhooks-methods'; class GithubService { - /* - * @param {import('./environment').default} env - */ - constructor(env) { - this.env = env; + constructor() { this.octokit = new Octokit({ - auth: env.GITHUB_TOKEN, + auth: process.env.GITHUB_TOKEN, }); } @@ -21,7 +17,11 @@ class GithubService { return ( typeof signature !== 'string' || - (await verify(this.env.GITHUB_WEBHOOK_SECRET, req.bodyString, signature)) + (await verify( + /** @type {*} */ (process.env.GITHUB_WEBHOOK_SECRET), + req.bodyString, + signature + )) ); } diff --git a/node/github-issue-bot/src/main.js b/node/github-issue-bot/src/main.js index 079b0d9e..0fdc5f2b 100644 --- a/node/github-issue-bot/src/main.js +++ b/node/github-issue-bot/src/main.js @@ -1,29 +1,30 @@ -import EnvironmentService from './environment.js'; import GithubService from './github.js'; +import { throwIfMissing } from './utils.js'; export default async ({ res, req, log, error }) => { - const env = new EnvironmentService(); - const github = new GithubService(env); + throwIfMissing(process.env, [ + 'GITHUB_WEBHOOK_SECRET', + 'GITHUB_TOKEN', + 'DISCORD_LINK', + ]); - const { DISCORD_LINK } = env; + const github = new GithubService(); if (!(await github.verifyWebhook(req))) { error('Invalid signature'); return res.json({ error: 'Invalid signature' }, 401); } - if (req.headers['x-github-event'] === 'issues') { - const { issue } = req.body; - if (!issue || req.body.action !== 'opened') { - log('No issue provided or not opened event'); - return res.json({ success: true }); - } - - await github.postComment( - issue, - `Thanks for the issue report @${issue.user.login}! I'm inviting you to join our Discord for quicker support: ${DISCORD_LINK}` - ); + if ( + req.headers['x-github-event'] !== 'issues' || + !req.body.issue || + req.body.action !== 'opened' + ) { + return res.json({ success: true }); } - return res.json({ success: true }); + await github.postComment( + req.body.issue, + `Thanks for the issue report @${req.body.issue.user.login}! I'm inviting you to join our Discord for quicker support: ${process.env.DISCORD_LINK}` + ); }; diff --git a/node/github-issue-bot/src/utils.js b/node/github-issue-bot/src/utils.js new file mode 100644 index 00000000..dcca7015 --- /dev/null +++ b/node/github-issue-bot/src/utils.js @@ -0,0 +1,17 @@ +/** + * 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 31987568f8b6242ae560c4b67a6c3abd77828e41 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:35:25 +0100 Subject: [PATCH 111/149] fix: esm import --- node/prompt-chatgpt/src/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/prompt-chatgpt/src/main.js b/node/prompt-chatgpt/src/main.js index 0ac3a1af..84ba522e 100644 --- a/node/prompt-chatgpt/src/main.js +++ b/node/prompt-chatgpt/src/main.js @@ -1,5 +1,5 @@ import { OpenAIApi, Configuration } from 'openai'; -import { getStaticFile, throwIfMissing } from './utils'; +import { getStaticFile, throwIfMissing } from './utils.js'; export default async ({ req, res, error }) => { throwIfMissing(process.env, ['OPENAI_API_KEY', 'OPENAI_MAX_TOKENS']); From b6ba6650b47d85afefd65a8b3e6f398f67f3b5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sat, 29 Jul 2023 15:48:44 +0000 Subject: [PATCH 112/149] PR review changes --- node/prompt-chatgpt/README.md | 10 ++++------ node/prompt-chatgpt/src/main.js | 11 +++++------ node/prompt-chatgpt/src/utils.js | 26 +++++++++++++------------- node/prompt-chatgpt/static/index.html | 4 ++-- 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/node/prompt-chatgpt/README.md b/node/prompt-chatgpt/README.md index 95aedca1..2a782efc 100644 --- a/node/prompt-chatgpt/README.md +++ b/node/prompt-chatgpt/README.md @@ -1,6 +1,6 @@ -# 🤖 Node OpenAI ChatGPT Function +# 🤖 Node Prompt ChatGPT Function -Query the OpenAI GPT-3.5-turbo model for chat completions. +Ask question, and let OpenAI GPT-3.5-turbo answer. ## 🧰 Usage @@ -26,9 +26,7 @@ Response from the model. ```json { "ok": true, - "completion": "Monday's heavy weight, -Dawning with a sigh of grey, -Hopeful hearts await." + "completion": "Monday's heavy weight, Dawning with a sigh of grey, Hopeful hearts await." } ``` @@ -73,7 +71,7 @@ A unique key used to authenticate with the OpenAI API. Please note that this is | Question | Answer | | ------------- | --------------------------------------------------------------------------- | | Required | Yes | -| Sample Value | `d1efb...aec35` | +| Sample Value | `sk-wzG...vcy` | | Documentation | [OpenAI Docs](https://platform.openai.com/docs/quickstart/add-your-api-key) | ### OPENAI_MAX_TOKENS diff --git a/node/prompt-chatgpt/src/main.js b/node/prompt-chatgpt/src/main.js index 84ba522e..6c9ef7e3 100644 --- a/node/prompt-chatgpt/src/main.js +++ b/node/prompt-chatgpt/src/main.js @@ -4,11 +4,6 @@ import { getStaticFile, throwIfMissing } from './utils.js'; export default async ({ req, res, error }) => { throwIfMissing(process.env, ['OPENAI_API_KEY', 'OPENAI_MAX_TOKENS']); - const configuration = new Configuration({ - apiKey: process.env.OPENAI_API_KEY, - }); - const openai = new OpenAIApi(configuration); - if (req.method === 'GET') { return res.send(getStaticFile('index.html'), 200, { 'Content-Type': 'text/html; charset=utf-8', @@ -21,13 +16,17 @@ export default async ({ req, res, error }) => { return res.json({ ok: false, error: err.message }, 400); } + const openai = new OpenAIApi(new Configuration({ + apiKey: process.env.OPENAI_API_KEY, + })); + const response = await openai.createChatCompletion({ model: 'gpt-3.5-turbo', max_tokens: parseInt(process.env.OPENAI_MAX_TOKENS ?? '512'), messages: [{ role: 'user', content: req.body.prompt }], }); - const completion = response.data.choices[0].message; + const completion = response.data?.choices[0]?.message ?? ''; if (!completion) { return res.json({ ok: false, error: 'Failed to query model.' }, 500); } diff --git a/node/prompt-chatgpt/src/utils.js b/node/prompt-chatgpt/src/utils.js index 5b93ba58..02a201ab 100644 --- a/node/prompt-chatgpt/src/utils.js +++ b/node/prompt-chatgpt/src/utils.js @@ -2,6 +2,19 @@ import path from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const staticFolder = path.join(__dirname, '../static'); + +/** + * Returns the contents of a file in the static folder + * @param {string} fileName + * @returns {string} Contents of static/{fileName} + */ +export function getStaticFile(fileName) { + return fs.readFileSync(path.join(staticFolder, fileName)).toString(); +} + /** * Throws an error if any of the keys are missing from the object * @param {*} obj @@ -19,16 +32,3 @@ export function throwIfMissing(obj, keys) { throw new Error(`Missing required fields: ${missing.join(', ')}`); } } - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const staticFolder = path.join(__dirname, '../static'); - -/** - * Returns the contents of a file in the static folder - * @param {string} fileName - * @returns {string} Contents of static/{fileName} - */ -export function getStaticFile(fileName) { - return fs.readFileSync(path.join(staticFolder, fileName)).toString(); -} diff --git a/node/prompt-chatgpt/static/index.html b/node/prompt-chatgpt/static/index.html index b8239d1a..ddbe6912 100644 --- a/node/prompt-chatgpt/static/index.html +++ b/node/prompt-chatgpt/static/index.html @@ -46,7 +46,7 @@

Prompt ChatGPT Demo

style="max-width: 50rem" > This is demo application. You can ue this app to ensure - implementation with Chat GPT works properly. Use input below to + implementation with ChatGPT works properly. Use input below to enter prompts and get a response.

@@ -76,7 +76,7 @@

Prompt ChatGPT Demo

-
Chat GPT:
+
ChatGPT:
From 9710e6f4f2299eed0c0e5fc7271c1b177b706aaf Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 29 Jul 2023 15:49:10 +0000 Subject: [PATCH 113/149] Auto: README table generation --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index abba44ce..cdef9cc3 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,10 @@ Templates for [Appwrite](https://appwrite.io/) Functions. These templates can be # List of Templates -| Template | C++ | Dart | Deno | .NET | Java | Kotlin | Node.js | PHP | Python | Ruby | Swift | -| -------- | ----------------- | ------------------ | ------------------ | -------------------- | ------------------ | -------------------- | ------------------ | ----------------- | -------------------- | ------------------ | ------------------- | -| starter | [✅](/cpp/starter) | [✅](/dart/starter) | [✅](/deno/starter) | [✅](/dotnet/starter) | [✅](/java/starter) | [✅](/kotlin/starter) | [✅](/node/starter) | [✅](/php/starter) | [✅](/python/starter) | [✅](/ruby/starter) | [✅](/swift/starter) | +| Template | C++ | Dart | Deno | .NET | Java | Kotlin | Node.js | PHP | Python | Ruby | Swift | +| -------------- | ----------------- | ------------------ | ------------------ | -------------------- | ------------------ | -------------------- | ------------------------- | ----------------- | -------------------- | ------------------ | ------------------- | +| starter | [✅](/cpp/starter) | [✅](/dart/starter) | [✅](/deno/starter) | [✅](/dotnet/starter) | [✅](/java/starter) | [✅](/kotlin/starter) | [✅](/node/starter) | [✅](/php/starter) | [✅](/python/starter) | [✅](/ruby/starter) | [✅](/swift/starter) | +| prompt-chatgpt | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](/node/prompt-chatgpt) | ❌ | ❌ | ❌ | ❌ | ✅ = Done - Function is implemented in this runtime. From f26a0e3e2aeac40822d0c89062364e24cd745576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sun, 30 Jul 2023 18:37:34 +0000 Subject: [PATCH 114/149] PR review changes --- node/censor-with-redact/README.md | 8 ++++---- node/censor-with-redact/static/index.html | 16 ++++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/node/censor-with-redact/README.md b/node/censor-with-redact/README.md index 74320541..ddc724db 100644 --- a/node/censor-with-redact/README.md +++ b/node/censor-with-redact/README.md @@ -1,4 +1,4 @@ -# 🛑 Censor with Redact Function +# 🤐 Node.js Censor with Redact Function Cesnor sensitive information from a provided text string using Redact API by Pangea. @@ -6,7 +6,7 @@ Cesnor sensitive information from a provided text string using Redact API by Pan ### `GET` -Returns an HTML form page that can be used to enter text and see the redacted result. +HTML form for interacting with the model. ### `POST` @@ -17,7 +17,7 @@ Returns the supplied text string with sensitive information redacted. | Name | Description | Location | Type | Sample Value | | ------------ | --------------------------- | -------- | ------------------ | -------------------------------------------- | | Content-Type | Content type of the request | Header | `application/json` | N/A | -| text | Text to redact | Body | String | `My email address is dennis.nedry@ingen.com` | +| text | Text to redact | Body | String | `My email address is myname2000@gmail.com` | **Response** @@ -61,5 +61,5 @@ Access token for the Pangea Redact API | Question | Answer | | ------------- | --------------------------------------------------------------------------------------- | | Required | Yes | -| Sample Value | `d1efbad42adgj` | +| Sample Value | `pts_7p4...5wl4` | | Documentation | [Pangea: Configuration](https://pangea.cloud/docs/redact/getting-started/configuration) | diff --git a/node/censor-with-redact/static/index.html b/node/censor-with-redact/static/index.html index a8177be4..af43985f 100644 --- a/node/censor-with-redact/static/index.html +++ b/node/censor-with-redact/static/index.html @@ -7,18 +7,22 @@ Censor with Redact API Demo From e9748e21521292cad4d67f60957f057d13383f1f Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 30 Jul 2023 18:38:03 +0000 Subject: [PATCH 115/149] Auto: README table generation --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cdef9cc3..e7a8aa3e 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,11 @@ Templates for [Appwrite](https://appwrite.io/) Functions. These templates can be # List of Templates -| Template | C++ | Dart | Deno | .NET | Java | Kotlin | Node.js | PHP | Python | Ruby | Swift | -| -------------- | ----------------- | ------------------ | ------------------ | -------------------- | ------------------ | -------------------- | ------------------------- | ----------------- | -------------------- | ------------------ | ------------------- | -| starter | [✅](/cpp/starter) | [✅](/dart/starter) | [✅](/deno/starter) | [✅](/dotnet/starter) | [✅](/java/starter) | [✅](/kotlin/starter) | [✅](/node/starter) | [✅](/php/starter) | [✅](/python/starter) | [✅](/ruby/starter) | [✅](/swift/starter) | -| prompt-chatgpt | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](/node/prompt-chatgpt) | ❌ | ❌ | ❌ | ❌ | +| Template | C++ | Dart | Deno | .NET | Java | Kotlin | Node.js | PHP | Python | Ruby | Swift | +| ------------------ | ----------------- | ------------------ | ------------------ | -------------------- | ------------------ | -------------------- | ----------------------------- | ----------------- | -------------------- | ------------------ | ------------------- | +| starter | [✅](/cpp/starter) | [✅](/dart/starter) | [✅](/deno/starter) | [✅](/dotnet/starter) | [✅](/java/starter) | [✅](/kotlin/starter) | [✅](/node/starter) | [✅](/php/starter) | [✅](/python/starter) | [✅](/ruby/starter) | [✅](/swift/starter) | +| prompt-chatgpt | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](/node/prompt-chatgpt) | ❌ | ❌ | ❌ | ❌ | +| censor-with-redact | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | [✅](/node/censor-with-redact) | ❌ | ❌ | ❌ | ❌ | ✅ = Done - Function is implemented in this runtime. From c02f3a50fcbefdf2d3fea7c909c4316b518448a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sun, 30 Jul 2023 18:47:33 +0000 Subject: [PATCH 116/149] PR review changes --- node/analyze-with-perspectiveapi/README.md | 10 +++++----- node/analyze-with-perspectiveapi/src/main.js | 2 +- node/analyze-with-perspectiveapi/static/index.html | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/node/analyze-with-perspectiveapi/README.md b/node/analyze-with-perspectiveapi/README.md index 0db0e0ed..15ddc2a9 100644 --- a/node/analyze-with-perspectiveapi/README.md +++ b/node/analyze-with-perspectiveapi/README.md @@ -1,12 +1,12 @@ -# ⚡ Analyze with Perspective API Function +# ☢️ Node.js Analyze with Perspective API Function -Interact with Google's perspective API to analyze the perceived impact of a comment or a string of text. The API uses machine learning to score the text based on several aspects, such as its potential to be perceived as toxic. +Automate moderation by getting toxicity of messages. ## 🧰 Usage ### `GET` -Displays an HTML form where users can input a string of text to be analyzed. +HTML form for interacting with the model. ### `POST` @@ -33,7 +33,7 @@ Sample `400` Response: ```json { "ok": false, - "error": "Missing body with a prompt." + "error": "Missing required field: text" } ``` @@ -65,5 +65,5 @@ Google Perspective API key. It authenticates your function, allowing it to inter | Question | Answer | | ------------- | ------------------------------------------------------------------------------------- | | Required | Yes | -| Sample Value | `d1efbd3jaoja4` | +| Sample Value | `AIzaS...fk-fuM` | | Documentation | [Setup Perspective API](https://developers.google.com/codelabs/setup-perspective-api) | diff --git a/node/analyze-with-perspectiveapi/src/main.js b/node/analyze-with-perspectiveapi/src/main.js index 236e86e9..d3a7224b 100644 --- a/node/analyze-with-perspectiveapi/src/main.js +++ b/node/analyze-with-perspectiveapi/src/main.js @@ -25,7 +25,7 @@ export default async ({ req, res }) => { }, body: JSON.stringify({ comment: { - text: req.bodyRaw, + text: req.body.text, type: 'PLAIN_TEXT', }, languages: ['en'], diff --git a/node/analyze-with-perspectiveapi/static/index.html b/node/analyze-with-perspectiveapi/static/index.html index fe333431..d41acae9 100644 --- a/node/analyze-with-perspectiveapi/static/index.html +++ b/node/analyze-with-perspectiveapi/static/index.html @@ -6,19 +6,19 @@ Analyze with Perspective API Demo