diff --git a/.env.sample b/.env.sample index 3ff6081..c40eeaa 100644 --- a/.env.sample +++ b/.env.sample @@ -5,3 +5,8 @@ CLIENT_ID = # invite link: # https://discord.com/api/oauth2/authorize?CLIENT_ID=&permissions=0&scope=bot%20applications.commands SERVER_ID = +# Mongo configuration +MONGO_URI= +MONGO_PORT= +MONGO_USERNAME= +MONGO_PASSWORD= diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..9253b88 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,18 @@ +--- +name: Question +about: I have a question about the bots functionality. +title: '' +labels: discussion, question +assignees: '' +--- + +\*_My Question is:_ +Your Question about BingusBoingus. + +**What I've tried so far** + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.gitignore b/.gitignore index f32240c..9b647c8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ node_modules/ tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo -bingusboingus.code-workspace \ No newline at end of file +bingusboingus.code-workspace +.vscode/settings.* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ef35eea..7f2915c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [1.0.1](https://github.com/Blvckleg/BingusBoingus/compare/v1.0.0...v1.0.1) (2024-02-16) + ### Bug Fixes * **workflows:** i think this should do it ([141f4f2](https://github.com/Blvckleg/BingusBoingus/commit/141f4f2b51d49abbadf20d59df24585bce12dfe2)) diff --git a/docker-compose.yml b/docker-compose.yml index 7181435..bcde1ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,3 +11,31 @@ services: ports: - 3000:3000 restart: unless-stopped + + mongo: + image: mongo:6.0.3 + hostname: mongo + volumes: + - ./infra/mongo:/db/data:rw + - ./infra/mongo/mongo.conf:/etc/mongo/mongo.conf:ro + - ./infra/mongo/init.js:/docker-entrypoint-initdb.d/init.js:ro + env_file: + - ./infra/mongo/.env + command: + - --config + - /etc/mongo/mongo.conf + ports: + - 27017:27017 + healthcheck: + test: + [ + 'CMD', + 'sh', + '-c', + 'echo ''db.runCommand("ping").ok'' | mongosh localhost:27017 --quiet', + ] + interval: 10s + timeout: 10s + retries: 5 + start_period: 40s + restart: unless-stopped diff --git a/infra/mongo/.env.sample b/infra/mongo/.env.sample new file mode 100644 index 0000000..1368deb --- /dev/null +++ b/infra/mongo/.env.sample @@ -0,0 +1,5 @@ +MONGO_INITDB_ROOT_USERNAME= +MONGO_INITDB_ROOT_PASSWORD= +MONGO_USER= +MONGO_PASSWORD= +MONGO_INITDB_DATABASE= diff --git a/infra/mongo/init.js b/infra/mongo/init.js new file mode 100644 index 0000000..79c8291 --- /dev/null +++ b/infra/mongo/init.js @@ -0,0 +1,5 @@ +db.createUser({ + user: _getEnv('MONGO_USER'), + pwd: _getEnv('MONGO_PASSWORD'), + roles: ['readWrite', 'dbAdmin'], +}); diff --git a/infra/mongo/mongo.conf b/infra/mongo/mongo.conf new file mode 100644 index 0000000..e01d764 --- /dev/null +++ b/infra/mongo/mongo.conf @@ -0,0 +1,42 @@ +# mongod.conf + +# for documentation of all options, see: +# http://docs.mongodb.org/manual/reference/configuration-options/ + +# Where and how to store data. +# storage: +# dbPath: /var/lib/mongodb +# journal: +# enabled: true +# engine: +# wiredTiger: + +# where to write logging data. +systemLog: + destination: file + logAppend: true + path: /var/log/mongodb/mongod.log + +# network interfaces +net: + port: 27017 + bindIp: 0.0.0.0 + + +# how the process runs +processManagement: + timeZoneInfo: /usr/share/zoneinfo + +#security: + +#operationProfiling: + +#replication: + +#sharding: + +## Enterprise-Only Options: + +#auditLog: + +#snmp: diff --git a/package-lock.json b/package-lock.json index 58c6357..f7b95e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "1.0.1", "license": "MIT", "dependencies": { + "@nestjs/common": "^10.2.10", "@nestjs/config": "^3.1.1", "@nestjs/core": "^10.2.6", + "@nestjs/mongoose": "^10.0.2", "@nestjs/platform-express": "^10.2.6", "@nestjs/testing": "^10.2.6", "@types/jest": "^29.5.5", @@ -20,6 +22,7 @@ "express": "^4.18.2", "jest": "^29.7.0", "joi": "^17.10.2", + "mongoose": "^8.0.1", "ngrok": "^5.0.0-beta.2", "node-fetch": "^3.3.2", "rimraf": "^5.0.1", @@ -1202,11 +1205,18 @@ "node": ">=8" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz", + "integrity": "sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nestjs/common": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.2.6.tgz", - "integrity": "sha512-ma8R7n+FXsWM4XF9QXjjrsRceyRzid/xKmNKVOa/sTJntkVG8lL71BHBEfjtFvO6EJUqjs/15LbDc0iaN5nCwA==", - "peer": true, + "version": "10.2.10", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.2.10.tgz", + "integrity": "sha512-fwAk931rjW8CNH2Mgwawq/7HWHH1dxkOLdcgs7U52ddLk8CtHXjejm1cbNahewlSbNhvlOl7y1STLHutE6sUqw==", "dependencies": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -1296,6 +1306,18 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==" }, + "node_modules/@nestjs/mongoose": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-10.0.2.tgz", + "integrity": "sha512-ITHh075DynjPIaKeJh6WkarS21WXYslu4nrLkNPbWaCP6JfxVAOftaA2X5tPSiiE/gNJWgs+QFWsfCFZUUenow==", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "mongoose": "^6.0.2 || ^7.0.0 || ^8.0.0", + "reflect-metadata": "^0.1.12", + "rxjs": "^7.0.0" + } + }, "node_modules/@nestjs/platform-express": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.2.6.tgz", @@ -1754,6 +1776,20 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/ws": { "version": "8.5.5", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", @@ -2106,6 +2142,14 @@ "node-int64": "^0.4.0" } }, + "node_modules/bson": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.2.0.tgz", + "integrity": "sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -4225,6 +4269,14 @@ "node": ">=6" } }, + "node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -4371,6 +4423,11 @@ "node": ">= 0.6" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -4488,6 +4545,136 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mongodb": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.2.0.tgz", + "integrity": "sha512-d7OSuGjGWDZ5usZPqfvb36laQ9CPhnWkAGHT61x5P95p/8nMVeH8asloMwW6GcYFeB0Vj4CB/1wOTDG2RA9BFA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^6.2.0", + "mongodb-connection-string-url": "^2.6.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.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 + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongoose": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.0.1.tgz", + "integrity": "sha512-O3TJrtLCt4H1eGf2HoHGcnOcCTWloQkpmIP3hA9olybX3OX2KUjdIIq135HD5paGjZEDJYKn9fw4eH5N477zqQ==", + "dependencies": { + "bson": "^6.2.0", + "kareem": "2.5.1", + "mongodb": "6.2.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/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/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4915,6 +5102,14 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.3.tgz", @@ -5224,6 +5419,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -5265,6 +5465,14 @@ "source-map": "^0.6.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", diff --git a/package.json b/package.json index c8fd549..ce8ee54 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "bingusboingus", + "version": "1.0.0", "version": "1.0.1", "description": "Hey look it's Bingus.... or Boingus?? OR BOTH??", "main": "src/main.ts", @@ -27,8 +28,10 @@ }, "homepage": "https://github.com/Blvckleg/BingusBoingus/#readme", "dependencies": { + "@nestjs/common": "^10.2.10", "@nestjs/config": "^3.1.1", "@nestjs/core": "^10.2.6", + "@nestjs/mongoose": "^10.0.2", "@nestjs/platform-express": "^10.2.6", "@nestjs/testing": "^10.2.6", "@types/jest": "^29.5.5", @@ -38,6 +41,7 @@ "express": "^4.18.2", "jest": "^29.7.0", "joi": "^17.10.2", + "mongoose": "^8.0.1", "ngrok": "^5.0.0-beta.2", "node-fetch": "^3.3.2", "rimraf": "^5.0.1", diff --git a/src/app.module.ts b/src/app.module.ts index 39d6c44..a206cd3 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,6 +4,7 @@ import { DiscordModule } from './modules/discord/discord.module'; import { CommandModule } from './modules/command/command.module'; import { EventModule } from './modules/event/event.module'; import { DeployModule } from './deployment/deploy.module'; +import { MongoDatabaseProviderModule } from './config/database/mongo/provider/mongo-provider.module'; @Module({ imports: [ @@ -12,6 +13,7 @@ import { DeployModule } from './deployment/deploy.module'; CommandModule, DeployModule, EventModule, + MongoDatabaseProviderModule, ], }) export class AppModule {} diff --git a/src/config/config.service.ts b/src/config/config.service.ts index 220bc13..dd1f622 100644 --- a/src/config/config.service.ts +++ b/src/config/config.service.ts @@ -9,7 +9,7 @@ import { ConfigService } from '@nestjs/config'; export class AppConfigService { constructor(private configService: ConfigService) {} - get port(): number { + get appPort(): number { return Number(this.configService.get('app.port')); } get botToken(): string { diff --git a/src/config/database/mongo/config/config.module.ts b/src/config/database/mongo/config/config.module.ts new file mode 100644 index 0000000..d529dca --- /dev/null +++ b/src/config/database/mongo/config/config.module.ts @@ -0,0 +1,32 @@ +import * as Joi from 'joi'; +import { Module } from '@nestjs/common'; +import configuration from './configuration'; +import { MongoConfigService } from './config.service'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +/** + * Import and provide app configuration related classes. + * + * @module + */ +const ENV = process.env.NODE_ENV; +@Module({ + imports: [ + ConfigModule.forRoot({ + load: [configuration], + envFilePath: !ENV ? '.env' : `.env.${ENV}`, + validationSchema: Joi.object({ + MONGO_URI: Joi.string().default('localhost'), + MONGO_PORT: Joi.number().default(27017), + MONGO_USERNAME: Joi.string(), + MONGO_PASSWORD: Joi.string(), + MONGO_DATABASE: Joi.string().default('bingus'), + MONGO_RUN_MIGRATION: Joi.boolean().default(false), + MONGO_ENTITIES: Joi.string().default('dist/**/*.entity.*{ts,js}'), + MONGO_RUN_SYNCHRONIZE: Joi.boolean().default(false), + }), + }), + ], + providers: [ConfigService, MongoConfigService], + exports: [ConfigService, MongoConfigService], +}) +export class MongoConfigModule {} diff --git a/src/config/database/mongo/config/config.service.ts b/src/config/database/mongo/config/config.service.ts new file mode 100644 index 0000000..d4b8ae7 --- /dev/null +++ b/src/config/database/mongo/config/config.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { + MongooseOptionsFactory, + MongooseModuleOptions, +} from '@nestjs/mongoose'; +/** + * Service dealing with app config based operations. + * + * @class + */ +@Injectable() +export class MongoConfigService implements MongooseOptionsFactory { + constructor(private configService: ConfigService) {} + + createMongooseOptions(): MongooseModuleOptions { + if (this.username && this.password) { + return { + uri: `mongodb://${this.username}:${this.password}@${this.uri}`, + }; + } else { + return { uri: this.uri }; + } + } + + get uri(): string { + return this.configService.get('mongo.uri'); + } + get port(): number { + return Number(this.configService.get('mongo.port')); + } + get username(): string { + return this.configService.get('mongo.username'); + } + get password(): string { + return this.configService.get('mongo.password'); + } + get database(): string { + return this.configService.get('mongo.database'); + } + get migrationsRun(): boolean { + return JSON.parse(this.configService.get('mongo.migrationsRun')); + } + get synchronizeRun(): boolean { + return JSON.parse(this.configService.get('mongo.synchronizeRun')); + } + get entities(): string { + return this.configService.get('mongo.entities'); + } +} diff --git a/src/config/database/mongo/config/configuration.ts b/src/config/database/mongo/config/configuration.ts new file mode 100644 index 0000000..fd56378 --- /dev/null +++ b/src/config/database/mongo/config/configuration.ts @@ -0,0 +1,12 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('mongo', () => ({ + uri: process.env.MONGO_URI, + port: process.env.MONGO_PORT, + username: process.env.MONGO_USERNAME, + password: process.env.MONGO_PASSWORD, + database: process.env.MONGO_DATABASE, + migrationsRun: process.env.MONGO_RUN_MIGRATION, + synchronizeRun: process.env.MONGO_RUN_SYNCHRONIZE, + entities: process.env.MONGO_ENTITIES, +})); diff --git a/src/config/database/mongo/provider/mongo-provider.module.ts b/src/config/database/mongo/provider/mongo-provider.module.ts new file mode 100644 index 0000000..4710d53 --- /dev/null +++ b/src/config/database/mongo/provider/mongo-provider.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule, MongooseModuleAsyncOptions } from '@nestjs/mongoose'; +import { MongoConfigModule } from '../config/config.module'; +import { MongoConfigService } from '../config/config.service'; + +@Module({ + imports: [ + MongooseModule.forRootAsync({ + imports: [MongoConfigModule], + useExisting: MongoConfigService, + } as MongooseModuleAsyncOptions), + ], +}) +export class MongoDatabaseProviderModule {} diff --git a/src/main.ts b/src/main.ts index adb44a4..1d2b9a6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,7 +17,7 @@ async function bootstrap() { await app.startAllMicroservices(); - await app.listen(appConfig.port); + await app.listen(appConfig.appPort); } bootstrap(); diff --git a/src/modules/command/command.abstract.ts b/src/modules/command/command.abstract.ts index eb10ff5..4782301 100644 --- a/src/modules/command/command.abstract.ts +++ b/src/modules/command/command.abstract.ts @@ -1,9 +1,18 @@ -import { CacheType, Interaction, SlashCommandBuilder } from 'discord.js'; +import { + CacheType, + CommandInteraction, + Interaction, + SlashCommandBuilder, +} from 'discord.js'; export abstract class ACommand { - data: SlashCommandBuilder; + data: + | SlashCommandBuilder + | Omit; - public abstract execute(arg: Interaction): Promise; + public abstract execute( + arg: Interaction | CommandInteraction, + ): Promise; protected async run(command: () => any): Promise { try { diff --git a/src/modules/command/command.module.ts b/src/modules/command/command.module.ts index d64d32d..8866f6a 100644 --- a/src/modules/command/command.module.ts +++ b/src/modules/command/command.module.ts @@ -7,6 +7,9 @@ import { ReportedCommand } from './commands/reported'; import { BugReport } from './commands/bug'; import { CoinflipCommand } from './commands/coinflip'; import { GoldCommand } from './commands/gold'; +import SomeoneOnceSaidCommand from './commands/someone-once-said'; +import { SomeoneOnceSaidModule } from '../someone-once-said/modules/someone-once-said.module'; +import GetRandomQuote from './commands/get-a-quote'; @Module({ providers: [ @@ -18,7 +21,10 @@ import { GoldCommand } from './commands/gold'; BugReport, CoinflipCommand, GoldCommand, + SomeoneOnceSaidCommand, + GetRandomQuote, ], + imports: [SomeoneOnceSaidModule], exports: [CommandService], }) export class CommandModule {} diff --git a/src/modules/command/command.service.ts b/src/modules/command/command.service.ts index 97f22c6..c8c7eeb 100644 --- a/src/modules/command/command.service.ts +++ b/src/modules/command/command.service.ts @@ -1,13 +1,13 @@ import { Injectable } from '@nestjs/common'; import { Collection } from 'discord.js'; -import { PingPongCommand } from './commands/pingpong'; import { CBDCommand } from './commands/cbd'; -import { HelloCommand } from './commands/hello'; import { ACommand } from './command.abstract'; import { ReportedCommand } from './commands/reported'; import { BugReport } from './commands/bug'; import { CoinflipCommand } from './commands/coinflip'; import { GoldCommand } from './commands/gold'; +import SomeoneOnceSaidCommand from './commands/someone-once-said'; +import GetRandomQuote from './commands/get-a-quote'; @Injectable() export class CommandService { @@ -21,6 +21,8 @@ export class CommandService { bugReportModule: BugReport, coinflipModule: CoinflipCommand, goldModule: GoldCommand, + someoneOnceSaidModule: SomeoneOnceSaidCommand, + getRandomQuoteModule: GetRandomQuote, ) { const commands: ACommand[] = [ //pingpongModule, @@ -30,6 +32,8 @@ export class CommandService { bugReportModule, coinflipModule, goldModule, + someoneOnceSaidModule, + getRandomQuoteModule, ]; commands.forEach((command) => { if (command.data.name && command.execute) { diff --git a/src/modules/command/commands/get-a-quote.ts b/src/modules/command/commands/get-a-quote.ts new file mode 100644 index 0000000..d94614d --- /dev/null +++ b/src/modules/command/commands/get-a-quote.ts @@ -0,0 +1,36 @@ +import { + CacheType, + CommandInteraction, + EmbedBuilder, + SlashCommandBuilder, +} from 'discord.js'; +import { ACommand } from '../command.abstract'; +import { SomeoneOnceSaidService } from '../../someone-once-said/services/someone-once-said.service'; +import { Inject } from '@nestjs/common'; + +export default class GetRandomQuote extends ACommand { + constructor( + @Inject(SomeoneOnceSaidService) + private readonly someoneonceSaidService: SomeoneOnceSaidService, + ) { + super(); + } + data = new SlashCommandBuilder() + .setName('randomquote') + .setDescription('get a random quote of a user'); + + async execute(arg: CommandInteraction): Promise { + const someoneOnceSaid = await this.someoneonceSaidService.getRandomQuote(); + if (!someoneOnceSaid) return; + const quoteEmbed = new EmbedBuilder() + .setTitle( + `${someoneOnceSaid.secName ?? someoneOnceSaid.username} once said 🤓`, + ) + .setDescription(someoneOnceSaid.phrase) + .setFooter({ + text: someoneOnceSaid?.secName ?? someoneOnceSaid.username, + }) + .setTimestamp(someoneOnceSaid.createdAt); + arg.reply({ embeds: [quoteEmbed] }); + } +} diff --git a/src/modules/command/commands/someone-once-said.ts b/src/modules/command/commands/someone-once-said.ts new file mode 100644 index 0000000..d753cd8 --- /dev/null +++ b/src/modules/command/commands/someone-once-said.ts @@ -0,0 +1,54 @@ +import { + CacheType, + CommandInteraction, + EmbedBuilder, + SlashCommandBuilder, +} from 'discord.js'; +import { ACommand } from '../command.abstract'; +import { SomeoneOnceSaidService } from '../../someone-once-said/services/someone-once-said.service'; +import { Inject } from '@nestjs/common'; +import { SomeoneOnceSaid } from '../../../schemas/someone-once-said.schema'; + +export default class SomeoneOnceSaidCommand extends ACommand { + constructor( + @Inject(SomeoneOnceSaidService) + private readonly someoneonceSaidService: SomeoneOnceSaidService, + ) { + super(); + } + data = new SlashCommandBuilder() + .setName('quote') + .setDescription('Make it a quote') + .addStringOption((option) => + option.setName('phrase').setDescription('What was said'), + ); + + async execute(arg: CommandInteraction): Promise { + let phrase = arg.options.get('phrase'); + if (!phrase) { + await arg.reply({ + content: 'ok but what did he say 4head ?XP', + ephemeral: true, + }); + return; + } + let phraseValue = (phrase.value as unknown as string).replaceAll( + '\\n', + '\n', + ); + const instance = new SomeoneOnceSaid({ + phrase: phraseValue, + username: arg.user.username, + secName: arg.user.displayName, + }); + const created = await this.someoneonceSaidService.create(instance); + const quoteEmbed = new EmbedBuilder() + .setTitle('Someone once said 🤓') + .setDescription(created.phrase) + .setFooter({ + text: created?.secName ?? created.username, + }) + .setTimestamp(created.createdAt); + arg.channel.send({ embeds: [quoteEmbed] }); + } +} diff --git a/src/modules/someone-once-said/modules/someone-once-said.module.ts b/src/modules/someone-once-said/modules/someone-once-said.module.ts new file mode 100644 index 0000000..a11a9b9 --- /dev/null +++ b/src/modules/someone-once-said/modules/someone-once-said.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { SomeoneOnceSaidService } from '../services/someone-once-said.service'; +import { + SomeoneOnceSaid, + SomeoneOnceSaidSchema, +} from '../../../schemas/someone-once-said.schema'; + +@Module({ + imports: [ + MongooseModule.forFeature([ + { + name: SomeoneOnceSaid.name, + schema: SomeoneOnceSaidSchema, + }, + ]), + ], + controllers: [], + providers: [SomeoneOnceSaidService], + exports: [SomeoneOnceSaidService], +}) +export class SomeoneOnceSaidModule {} diff --git a/src/modules/someone-once-said/services/someone-once-said.service.ts b/src/modules/someone-once-said/services/someone-once-said.service.ts new file mode 100644 index 0000000..2a6101b --- /dev/null +++ b/src/modules/someone-once-said/services/someone-once-said.service.ts @@ -0,0 +1,53 @@ +import { InjectModel } from '@nestjs/mongoose'; +import { Model, Error } from 'mongoose'; +import { SomeoneOnceSaidEntity } from '../../../schemas/someone-once-said-entity.model'; +import { SomeoneOnceSaidDocument } from '../../../schemas/someone-once-said.schema'; + +export class SomeoneOnceSaidService { + constructor( + @InjectModel('SomeoneOnceSaid') + private readonly someoneOnceSaid: Model, + ) {} + + async create( + quoteDto: SomeoneOnceSaidEntity, + ): Promise { + try { + return await this.someoneOnceSaid.create({ + phrase: quoteDto.phrase, + username: quoteDto.username, + secName: quoteDto?.secName, + createdAt: new Date(), + }); + } catch (e) { + return null; + } + } + + async deleteProductionOrderForUser(username: string) { + try { + await this.someoneOnceSaid.deleteMany({ username: username }); + } catch (e) { + return new Error('Error deleting Quotes for username: ' + username); + } + return; + } + + async getRandomQuote(): Promise { + try { + const count = await this.someoneOnceSaid.countDocuments(); + + const randomIndex = Math.floor(Math.random() * count); + + const randomQuote = await this.someoneOnceSaid + .findOne() + .skip(randomIndex) + .limit(1); + + return randomQuote; + } catch (error) { + console.error('Error fetching random quote:', error); + return null; + } + } +} diff --git a/src/schemas/auto-response.schema.ts b/src/schemas/auto-response.schema.ts new file mode 100644 index 0000000..e86c615 --- /dev/null +++ b/src/schemas/auto-response.schema.ts @@ -0,0 +1,50 @@ +import { Schema, model, Model, Document } from 'mongoose'; +import { ResponseType } from './command.schema'; + +export interface AutoResponseDocument extends Document { + regex: RegExp; + response: string; + responseType?: ResponseType; +} + +const regexValidator = { + validator: (value: any) => { + try { + // Attempt to create a RegExp object from the provided value + new RegExp(value); + return true; + } catch (error) { + return false; + } + }, + message: 'Invalid regular expression format', +}; + +export const AutoResponseSchema: Schema = new Schema( + { + regex: { + type: Schema.Types.Mixed, + required: true, + unique: true, + validate: regexValidator, + }, + response: { + type: String, + required: true, + }, + responseType: { + type: String, + enum: Object.values(ResponseType), + default: ResponseType.reply, + }, + }, + { + timestamps: true, + strict: false, + }, +); + +export const AutoResponseModel: Model = model< + AutoResponseDocument, + Model +>('AutoResponse', AutoResponseSchema); diff --git a/src/schemas/command.schema.ts b/src/schemas/command.schema.ts new file mode 100644 index 0000000..3cbbb50 --- /dev/null +++ b/src/schemas/command.schema.ts @@ -0,0 +1,41 @@ +import { Schema, model, Model, Document } from 'mongoose'; + +export interface CommandDocument extends Document { + commandName: string; + response: string; + responseType?: ResponseType; +} + +export enum ResponseType { + reply = 'replyToMessage', + channel = 'replyToChannel', + dm = 'replyInDirectMessage', +} + +export const CommandSchema: Schema = new Schema( + { + commandName: { + type: String, + required: true, + unique: true, + }, + response: { + type: String, + required: true, + }, + responseType: { + type: String, + enum: Object.values(ResponseType), + default: ResponseType.reply, + }, + }, + { + timestamps: true, + strict: false, + }, +); + +export const CommandModel: Model = model< + CommandDocument, + Model +>('Command', CommandSchema); diff --git a/src/schemas/someone-once-said-entity.model.ts b/src/schemas/someone-once-said-entity.model.ts new file mode 100644 index 0000000..0eb6d1a --- /dev/null +++ b/src/schemas/someone-once-said-entity.model.ts @@ -0,0 +1,6 @@ +export class SomeoneOnceSaidEntity { + phrase: string; + username: string; + secName?: string; + createdAt: Date; +} diff --git a/src/schemas/someone-once-said.schema.ts b/src/schemas/someone-once-said.schema.ts new file mode 100644 index 0000000..1392e66 --- /dev/null +++ b/src/schemas/someone-once-said.schema.ts @@ -0,0 +1,25 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; + +@Schema() +export class SomeoneOnceSaid { + @Prop({ required: true }) + phrase: string; + + @Prop({ required: true }) + username: string; + + @Prop({ required: false }) + secName: string; + + @Prop({ required: true }) + createdAt: Date; + + constructor(data) { + Object.assign(this, data); + } +} + +export type SomeoneOnceSaidDocument = SomeoneOnceSaid & Document; + +export const SomeoneOnceSaidSchema = + SchemaFactory.createForClass(SomeoneOnceSaid);