From 7e9dc206324e1d0f0bb8b81b59a35b3c39c50950 Mon Sep 17 00:00:00 2001 From: Anna Date: Wed, 2 Jul 2025 10:52:11 -0400 Subject: [PATCH] feat: add a database client --- package.json | 1 + pnpm-lock.yaml | 104 +++++++++++++++++++++++++++++++++++++++++ prod.env | 3 +- src/config/channels.ts | 8 ++-- src/index.ts | 5 ++ src/models/channel.ts | 11 +++++ src/utils/database.ts | 47 +++++++++++++++++++ src/utils/logger.ts | 1 + 8 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 src/models/channel.ts create mode 100644 src/utils/database.ts diff --git a/package.json b/package.json index bebb392..aaad461 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dependencies": { "@nhcarrigan/logger": "1.0.0", "discord.js": "14.21.0", + "mongodb": "6.17.0", "node-schedule": "2.1.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f6f406..1442fc7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: discord.js: specifier: 14.21.0 version: 14.21.0 + mongodb: + specifier: ^6.17.0 + version: 6.17.0 node-schedule: specifier: 2.1.1 version: 2.1.1 @@ -405,6 +408,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@mongodb-js/saslprep@1.3.0': + resolution: {integrity: sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==} + '@nhcarrigan/eslint-config@5.2.0': resolution: {integrity: sha512-YpTTqhviKMlRwKF+RC/GYiA5i2jTCmg8uftuiufldneNV5HMbGpTfBbV7tpa8++5mpYJc4+eZaf40QbDiz84dQ==} engines: {node: '>=22', pnpm: '>=9'} @@ -584,6 +590,12 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/webidl-conversions@7.0.3': + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + + '@types/whatwg-url@11.0.5': + resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==} + '@types/ws@8.5.14': resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==} @@ -849,6 +861,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bson@6.10.4: + resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==} + engines: {node: '>=16.20.1'} + builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -1641,6 +1657,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1667,6 +1686,36 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mongodb-connection-string-url@3.0.2: + resolution: {integrity: sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==} + + mongodb@6.17.0: + resolution: {integrity: sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==} + engines: {node: '>=16.20.1'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.188.0 + '@mongodb-js/zstd': ^1.1.0 || ^2.0.0 + gcp-metadata: ^5.2.0 + kerberos: ^2.0.1 + mongodb-client-encryption: '>=6.0.0 <7' + snappy: ^7.2.2 + socks: ^2.7.1 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1978,6 +2027,9 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -2091,6 +2143,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + ts-api-utils@1.4.3: resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} engines: {node: '>=16'} @@ -2245,6 +2301,14 @@ packages: jsdom: optional: true + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -2650,6 +2714,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@mongodb-js/saslprep@1.3.0': + dependencies: + sparse-bitfield: 3.0.3 + '@nhcarrigan/eslint-config@5.2.0(@typescript-eslint/utils@8.25.0(eslint@9.30.0)(typescript@5.8.3))(eslint@9.30.0)(playwright@1.50.1)(react@19.0.0)(typescript@5.8.3)(vitest@3.2.4(@types/node@24.0.6))': dependencies: '@eslint-community/eslint-plugin-eslint-comments': 4.4.1(eslint@9.30.0) @@ -2804,6 +2872,12 @@ snapshots: '@types/normalize-package-data@2.4.4': {} + '@types/webidl-conversions@7.0.3': {} + + '@types/whatwg-url@11.0.5': + dependencies: + '@types/webidl-conversions': 7.0.3 + '@types/ws@8.5.14': dependencies: '@types/node': 24.0.6 @@ -3163,6 +3237,8 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.2(browserslist@4.24.4) + bson@6.10.4: {} + builtin-modules@3.3.0: {} cac@6.7.14: {} @@ -4129,6 +4205,8 @@ snapshots: math-intrinsics@1.1.0: {} + memory-pager@1.5.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -4150,6 +4228,17 @@ snapshots: minipass@7.1.2: {} + mongodb-connection-string-url@3.0.2: + dependencies: + '@types/whatwg-url': 11.0.5 + whatwg-url: 14.2.0 + + mongodb@6.17.0: + dependencies: + '@mongodb-js/saslprep': 1.3.0 + bson: 6.10.4 + mongodb-connection-string-url: 3.0.2 + ms@2.1.3: {} nanoid@3.3.8: {} @@ -4499,6 +4588,10 @@ snapshots: source-map-js@1.2.1: {} + sparse-bitfield@3.0.3: + dependencies: + memory-pager: 1.5.0 + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -4634,6 +4727,10 @@ snapshots: dependencies: is-number: 7.0.0 + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + ts-api-utils@1.4.3(typescript@5.8.3): dependencies: typescript: 5.8.3 @@ -4795,6 +4892,13 @@ snapshots: - tsx - yaml + webidl-conversions@7.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 diff --git a/prod.env b/prod.env index 22ae048..7d375bb 100644 --- a/prod.env +++ b/prod.env @@ -1,2 +1,3 @@ DISCORD_TOKEN="op://Environment Variables - Naomi/Standup Bot/discord_token" -LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth" \ No newline at end of file +LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth" +MONGO_DB_URL="op://Environment Variables - Naomi/Database/mongo_url" \ No newline at end of file diff --git a/src/config/channels.ts b/src/config/channels.ts index b901b0b..bfd2cb8 100644 --- a/src/config/channels.ts +++ b/src/config/channels.ts @@ -4,14 +4,12 @@ * @author Naomi Carrigan */ +import type { ReminderChannel } from "../models/channel"; + /** * The channels to post standup reminders in. */ -export const channels: Array<{ - channelId: string; - roleId: string; - name: string; -}> = [ +export const channels: Array = [ { channelId: "1382093555228606484", name: "red-script", diff --git a/src/index.ts b/src/index.ts index f73b833..bbae56b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import { Client, Events, GatewayIntentBits } from "discord.js"; import { scheduleJob } from "node-schedule"; import { channels } from "./config/channels.js"; import { standup } from "./modules/standup.js"; +import { closeDatabase, initializeDatabase } from "./utils/database.js"; import { logger } from "./utils/logger.js"; const activeIds: Array = []; @@ -17,6 +18,7 @@ process.on("unhandledRejection", (error) => { return; } void logger.error("unhandled rejection", new Error(String(error))); + void closeDatabase(); }); process.on("uncaughtException", (error) => { @@ -25,12 +27,15 @@ process.on("uncaughtException", (error) => { return; } void logger.error("uncaught exception", new Error(String(error))); + void closeDatabase(); }); const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages ], }); +await initializeDatabase(); + client.on(Events.ClientReady, () => { void logger.log("debug", "Bot is ready."); scheduleJob("reminders", "0 9 * * 1-5", async() => { diff --git a/src/models/channel.ts b/src/models/channel.ts new file mode 100644 index 0000000..7dc3ffe --- /dev/null +++ b/src/models/channel.ts @@ -0,0 +1,11 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Anna + */ + +export interface ReminderChannel { + channelId: string; + roleId: string; + name: string; +} diff --git a/src/utils/database.ts b/src/utils/database.ts new file mode 100644 index 0000000..0a56855 --- /dev/null +++ b/src/utils/database.ts @@ -0,0 +1,47 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Anna + */ + +import { type Collection, MongoClient } from "mongodb"; +import type { ReminderChannel } from "../models/channel"; + +const client = new MongoClient(process.env.MONGO_DB_URL ?? ""); + +const databaseName = "Maribelle"; + +// eslint-disable-next-line @typescript-eslint/init-declarations -- I need a reference to the collection to insert records +let collection: Collection; + +/** + * Initializes a connection to the database + * using the environment variable. + */ +async function initializeDatabase(): Promise { + await client.connect(); + const database = client.db(databaseName); + collection = database.collection("channel"); +} + +/** + * Inserts a new channel into the database. + * @param channelId - The id of the channel to post in. + * @param roleId - The id of the role to ping. + * @param name - The name of the group. + */ +async function insertRecord(channelId: string, + roleId: string, + name: string): Promise { + const document = { channelId, name, roleId }; + await collection.insertOne(document); +} + +/** + * Closes the connection to the database. + */ +async function closeDatabase(): Promise { + await client.close(); +} + +export { closeDatabase, initializeDatabase, insertRecord }; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 8ee863e..932bec2 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -10,3 +10,4 @@ export const logger = new Logger( "Maribelle", process.env.LOG_TOKEN ?? "", ); +