diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 014721a..9edf3a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,6 +59,27 @@ localUser2@kurthutten.com: `abc123` localAdmin@kurthutten.com: `abc123` +### Discord bot setup + +To set up the discord bot to notify when users publish new content (see also [the discord JS tutorial](https://discordjs.guide/preparations/setting-up-a-bot-application.html)): + +1. If you're setting up the bot in a dev environment, create a new discord server (the "plus" button on the left when logged into the Discord webpage). Make note of the name of the project. +2. With [developer mode turned on](https://www.howtogeek.com/714348/how-to-enable-or-disable-developer-mode-on-discord/), right click the channel you wish the bot to announce on and select "Copy ID". Add this to `.env.defaults` as `DISCORD_CHANNEL_ID`. +3. [create a new application](https://discord.com/developers/applications), or navigate to an existing one. +4. Create a bot within that application. Copy the bot token and add it to `.env.defaults` as `DISCORD_TOKEN`. +5. Go to the "URL Generator" under "OAuth2" and create a URL with scope "bot" and text permission "Send Messages". +6. Copy the generated URL and open it in a new tab. Follow the instructions on the page to add the bot to your discord server. + +When you next start CADHub, you should see in the logs `Discord: logged in as ` and you should see a startup message from the bot in the channel. + +To send messages as the bot when things happen in the service, use the `sendChat` helper function: + +```typescript +import { sendChat } from 'src/lib/discord' + +sendChat("hello world!") +``` + ## Designs In progress, though can be [seen on Figma](https://www.figma.com/file/VUh53RdncjZ7NuFYj0RGB9/CadHub?node-id=0%3A1) diff --git a/app/.env.defaults b/app/.env.defaults index f698efd..c6799db 100644 --- a/app/.env.defaults +++ b/app/.env.defaults @@ -18,9 +18,9 @@ CLOUDINARY_API_KEY=476712943135152 # trace | info | debug | warn | error | silent # LOG_LEVEL=debug - # EMAIL_PASSWORD=abc123 - +# DISCORD_TOKEN=abc123 +# DISCORD_CHANNEL_ID=12345 # CAD_LAMBDA_BASE_URL="http://localhost:8080" diff --git a/app/api/package.json b/app/api/package.json index 55d32ad..1f5f6ae 100644 --- a/app/api/package.json +++ b/app/api/package.json @@ -9,6 +9,7 @@ "axios": "^0.21.1", "cloudinary": "^1.23.0", "cors": "^2.8.5", + "discord.js": "^13.5.1", "express": "^4.17.1", "human-id": "^2.0.1", "middy": "^0.36.0", @@ -21,4 +22,4 @@ "concurrently": "^6.0.0", "nodemon": "^2.0.7" } -} \ No newline at end of file +} diff --git a/app/api/src/lib/discord.ts b/app/api/src/lib/discord.ts new file mode 100644 index 0000000..649566e --- /dev/null +++ b/app/api/src/lib/discord.ts @@ -0,0 +1,34 @@ +import {Client, Intents, MessageAttachment} from "discord.js" + +const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES]}) + +export async function sendDiscordMessage(text: string, url?: string) { + if (!client.isReady()) { + console.error(`Discord: client is not ready to send message ("${text}")`); + } else { + const channel = await client.channels.fetch(process.env.DISCORD_CHANNEL_ID); + if (url) { + channel.send({ embeds: [{ + title: text, + image: { + url: url, + }, + }] }); + + } else { + channel.send(text) + } + + } +} + +client.on("ready", async () => { + console.log(`Discord: logged in as ${client.user.tag}`) +}) + +if (!process.env.DISCORD_TOKEN || !process.env.DISCORD_CHANNEL_ID) { + console.warn("Discord bot not configured - please set process.env.DISCORD_TOKEN and process.env.DISCORD_CHANNEL_ID to send discord chats"); +} else { + console.log(`Discord: logging in (token ${process.env.DISCORD_TOKEN})`); + client.login(process.env.DISCORD_TOKEN); +} diff --git a/app/api/src/services/projects/projects.ts b/app/api/src/services/projects/projects.ts index 200e40b..2660a67 100644 --- a/app/api/src/services/projects/projects.ts +++ b/app/api/src/services/projects/projects.ts @@ -12,6 +12,8 @@ import { } from 'src/services/helpers' import { requireAuth } from 'src/lib/auth' import { requireOwnership, requireProjectOwnership } from 'src/lib/owner' +import { sendDiscordMessage } from 'src/lib/discord' + export const projects = ({ userName }) => { if (!userName) { @@ -243,7 +245,19 @@ export const updateProjectImages = async ({ const [updatedProject] = await Promise.all([ projectPromise, imageDestroyPromise, - ]) + ]).then(async (result) => { + const { userName } = await db.user.findUnique({ + where: { id: project.userId }, + }) + sendDiscordMessage([ + `${userName} just added an image to their ${project.cadPackage} project:`, + ` => ${project.title}`, + ``, + `Check it out, leave a comment, make them feel welcome!`, + `https://cadhub.xyz/u/${userName}/${project.title}` + ].join('\n'), `https://res.cloudinary.com/irevdev/image/upload/c_scale,w_700/v1/${mainImage}`) + return result + }) return updatedProject } diff --git a/app/redwood.toml b/app/redwood.toml index ba5c9a4..4177995 100644 --- a/app/redwood.toml +++ b/app/redwood.toml @@ -17,7 +17,7 @@ 'SENTRY_AUTH_TOKEN', 'SENTRY_ORG', 'SENTRY_PROJECT', - 'EMAIL_PASSWORD' + 'EMAIL_PASSWORD', ] # experimentalFastRefresh = true # this seems to break cascadeStudio [api] diff --git a/app/yarn.lock b/app/yarn.lock index c4f9b3e..d0d64a5 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -1203,6 +1203,22 @@ resolved "https://registry.yarnpkg.com/@chevrotain/utils/-/utils-9.1.0.tgz#a34ab0696f9491dee934e848984517d226356f21" integrity sha512-llLJZ8OAlZrjGlBvamm6Zdo/HmGAcCLq5gx7cSwUX8No+n/8ip+oaC4x33IdZIif8+Rh5dQUIZXmfbSghiOmNQ== +"@discordjs/builders@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-0.11.0.tgz#4102abe3e0cd093501f3f71931df43eb92f5b0cc" + integrity sha512-ZTB8yJdJKrKlq44dpWkNUrAtEJEq0gqpb7ASdv4vmq6/mZal5kOv312hQ56I/vxwMre+VIkoHquNUAfnTbiYtg== + dependencies: + "@sindresorhus/is" "^4.2.0" + discord-api-types "^0.26.0" + ts-mixer "^6.0.0" + tslib "^2.3.1" + zod "^3.11.6" + +"@discordjs/collection@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.4.0.tgz#b6488286a1cc7b41b644d7e6086f25a1c1e6f837" + integrity sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw== + "@discoveryjs/json-ext@^0.5.0", "@discoveryjs/json-ext@^0.5.3": version "0.5.5" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" @@ -2811,6 +2827,11 @@ dependencies: any-observable "^0.3.0" +"@sapphire/async-queue@^1.1.9": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.1.9.tgz#ce69611c8753c4affd905a7ef43061c7eb95c01b" + integrity sha512-CbXaGwwlEMq+l1TRu01FJCvySJ1CEFKFclHT48nIfNeZXaAAmmwwy7scUKmYHPUa3GhoMp6Qr1B3eAJux6XgOQ== + "@sentry/browser@^6.5.1": version "6.13.3" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.13.3.tgz#d4511791b1e484ad48785eba3bce291fdf115c1e" @@ -2894,6 +2915,11 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== +"@sindresorhus/is@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.2.1.tgz#b88b5724283db80b507cd612caee9a1947412a20" + integrity sha512-BrzrgtaqEre0qfvI8sMTaEvx+bayuhPmfe2rfeUGPPHYr/PLxCOqkOe4TQTDPb+qcqgNcsAtXV/Ew74mcDIE8w== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -4037,7 +4063,7 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== -"@types/node-fetch@^2.5.7": +"@types/node-fetch@^2.5.12", "@types/node-fetch@^2.5.7": version "2.5.12" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" integrity sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw== @@ -4274,6 +4300,13 @@ dependencies: "@types/node" "*" +"@types/ws@^8.2.2": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21" + integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" @@ -7692,6 +7725,26 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +discord-api-types@^0.26.0: + version "0.26.1" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.26.1.tgz#726f766ddc37d60da95740991d22cb6ef2ed787b" + integrity sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ== + +discord.js@^13.5.1: + version "13.5.1" + resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.5.1.tgz#c0af11c7bfdcf6ac3f6bf28c7d96855c0c6a8997" + integrity sha512-ejEG5MXzB0eda9Nt+VzqgdvDWVO5U/GynGzq6DRPLaCH1yyn2YRU9J+vCMl77pWA1rzYGX+b/9RI31x0wt3qXA== + dependencies: + "@discordjs/builders" "^0.11.0" + "@discordjs/collection" "^0.4.0" + "@sapphire/async-queue" "^1.1.9" + "@types/node-fetch" "^2.5.12" + "@types/ws" "^8.2.2" + discord-api-types "^0.26.0" + form-data "^4.0.0" + node-fetch "^2.6.1" + ws "^8.4.0" + dlv@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" @@ -9250,7 +9303,7 @@ fork-ts-checker-webpack-plugin@^6.0.4: semver "^7.3.2" tapable "^1.0.0" -form-data@4.0.0: +form-data@4.0.0, form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== @@ -17465,6 +17518,11 @@ ts-log@^2.2.3: resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.2.3.tgz#4da5640fe25a9fb52642cd32391c886721318efb" integrity sha512-XvB+OdKSJ708Dmf9ore4Uf/q62AYDTzFcAdxc8KNML1mmAWywRFVt/dn1KYJH8Agt5UJNujfM3znU5PxgAzA2w== +ts-mixer@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.0.tgz#4e631d3a36e3fa9521b973b132e8353bc7267f9f" + integrity sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ== + ts-morph@12.0.0: version "12.0.0" resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-12.0.0.tgz#a601c3538703755cbfa2d42b62c52df73e9dbbd7" @@ -17510,7 +17568,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.2, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@~2.3.0: +tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@~2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== @@ -18618,6 +18676,11 @@ ws@^8.1.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== +ws@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.0.tgz#f05e982a0a88c604080e8581576e2a063802bed6" + integrity sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ== + xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" @@ -18811,6 +18874,11 @@ zip-stream@^3.0.1: compress-commons "^3.0.0" readable-stream "^3.6.0" +zod@^3.11.6: + version "3.11.6" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.11.6.tgz#e43a5e0c213ae2e02aefe7cb2b1a6fa3d7f1f483" + integrity sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg== + zstddec@^0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.0.2.tgz#57e2f28dd1ff56b750e07d158a43f0611ad9eeb4"