From f407c42087a1c1f25996ade8513efb5eae80cc1e Mon Sep 17 00:00:00 2001 From: Chegele Date: Tue, 3 Oct 2023 17:29:19 -0400 Subject: [PATCH] MongoDB for Ledger --- example.env | 12 +- package-lock.json | 227 ++++++++++++++++++++++++++++++++++- package.json | 2 + src/app.module.ts | 34 ++++-- src/util/ledger-connector.ts | 92 ++++++++++++++ 5 files changed, 349 insertions(+), 18 deletions(-) create mode 100644 src/util/ledger-connector.ts diff --git a/example.env b/example.env index d1f6f42..f678fba 100644 --- a/example.env +++ b/example.env @@ -4,7 +4,11 @@ # 2. Update the below template with the correct values # 3. Rename this file to .env -DB_HOST=database_host_name -DB_PORT=database_port -DB_USER=database_user -DB_PASS=database_password \ No newline at end of file +DB_HOST=neo4j_host_name +DB_PORT=neo4j_port +DB_USER=neo4j_user +DB_PASS=neo4j_password + +LEDGER_HOST=mongo_host_name +LEDGER_USER=mongo_user +LEDGER_PASS=mongo_password \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 577b1e7..408827e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,11 @@ "@nestjs/core": "^10.0.0", "@nestjs/graphql": "^12.0.1", "@nestjs/platform-express": "^10.0.0", + "@types/mongoose": "^5.11.97", "apollo-server-core": "^3.12.0", "dotenv": "^16.3.1", "graphql": "^16.6.0", + "mongoose": "^7.5.3", "neo4j-driver": "^5.11.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", @@ -1683,6 +1685,15 @@ "node": ">=8" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.0.tgz", + "integrity": "sha512-Xfijy7HvfzzqiOAhAepF4SGN5e9leLkMvg/OPOF97XemjfVCYN/oWa75wnkc6mltMSTwY+XlbhWgUOJmkFspSw==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nestjs/apollo": { "version": "12.0.7", "resolved": "https://registry.npmjs.org/@nestjs/apollo/-/apollo-12.0.7.tgz", @@ -2477,6 +2488,15 @@ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, + "node_modules/@types/mongoose": { + "version": "5.11.97", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.11.97.tgz", + "integrity": "sha512-cqwOVYT3qXyLiGw7ueU2kX9noE8DPGRY6z8eUxudhXY8NZ7DMKYAxyZkLSevGfhCX3dO/AoX5/SO9lAzfjon0Q==", + "deprecated": "Mongoose publishes its own types, so you do not need to install this package.", + "dependencies": { + "mongoose": "*" + } + }, "node_modules/@types/node": { "version": "20.4.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz", @@ -2557,6 +2577,20 @@ "@types/superagent": "*" } }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.1.tgz", + "integrity": "sha512-8hKOnOan+Uu+NgMaCouhg3cT9x5fFZ92Jwf+uDLXLu/MFRbXxlWwGeQY7KVHkeSft6RvY+tdxklUBuyY9eIEKg==" + }, + "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/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -3742,6 +3776,14 @@ "node-int64": "^0.4.0" } }, + "node_modules/bson": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.0.tgz", + "integrity": "sha512-B+QB4YmDx9RStKv8LLSl/aVIEV3nYJc3cJNNTK2Cd1TL+7P+cNpw9mAPeCgc5K+j01Dv6sxUzcITXDx7ZU3F0w==", + "engines": { + "node": ">=14.20.1" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -4207,7 +4249,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -5595,6 +5636,11 @@ "node": ">= 0.10" } }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -6479,6 +6525,14 @@ "graceful-fs": "^4.1.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/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -6682,6 +6736,12 @@ "node": ">= 4.0.0" } }, + "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==", + "optional": true + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -6800,11 +6860,135 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mongodb": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.8.1.tgz", + "integrity": "sha512-wKyh4kZvm6NrCPH8AxyzXm3JBoEf4Xulo0aUWh3hCgwgYJxyQ1KLST86ZZaSWdj6/kxYUA3+YZuyADCE61CMSg==", + "dependencies": { + "bson": "^5.4.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "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": "7.5.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.5.3.tgz", + "integrity": "sha512-QyYzhZusux0wIJs+4rYyHvel0kJm0CT887trNd1WAB3iQnDuJow0xEnjETvuS/cTjHQUVPihOpN7OHLlpJc52w==", + "dependencies": { + "bson": "^5.4.0", + "kareem": "2.5.1", + "mongodb": "5.8.1", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=14.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", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multer": { "version": "1.4.4-lts.1", @@ -7460,7 +7644,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, "engines": { "node": ">=6" } @@ -8048,6 +8231,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": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -8069,6 +8257,28 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -8097,6 +8307,15 @@ "node": ">=0.10.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==", + "optional": true, + "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 df1960e..0a5422e 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,11 @@ "@nestjs/core": "^10.0.0", "@nestjs/graphql": "^12.0.1", "@nestjs/platform-express": "^10.0.0", + "@types/mongoose": "^5.11.97", "apollo-server-core": "^3.12.0", "dotenv": "^16.3.1", "graphql": "^16.6.0", + "mongoose": "^7.5.3", "neo4j-driver": "^5.11.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", diff --git a/src/app.module.ts b/src/app.module.ts index 7ba8ba5..128845f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -9,9 +9,12 @@ import { MemberResolver } from './services/reputation/components/member/member.r import { PostResolver } from './services/reputation/components/post/post.resolver'; import { CitationsResolver } from './services/reputation/components/citation/citation.resolver'; +import { LedgerConnector} from './util/ledger-connector'; import { DatabaseConnector } from './util/database-connector'; import { MemberRepository } from './services/reputation/components/member/member.repository'; + const dbConnector = new DatabaseConnector(new Logger('DatabaseConnector')); +const ledgerConnector = LedgerConnector.getInstance(new Logger('LedgerConnector')); @Injectable() export class StartupService implements OnModuleInit { @@ -20,20 +23,30 @@ export class StartupService implements OnModuleInit { const testSuite = process.env.TEST_SUITE; if (testSuite?.toLowerCase().includes("integration")) return; - const host = process.env.DB_HOST; - const port = process.env.DB_PORT; - const user = process.env.DB_USER; - const pass = process.env.DB_PASS; + const dbOptions = { + host: process.env.DB_HOST, + port: process.env.DB_PORT, + user: process.env.DB_USER, + pass: process.env.DB_PASS + } - if (!host) throw new Error('Missing environment variable, DB_HOST'); - if (!port) throw new Error('Missing environment variable, DB_PORT'); - if (!user) throw new Error('Missing environment variable, DB_USER'); - if (!pass) throw new Error('Missing environment variable, DB_PASSWORD'); + const ledgerOptions = { + host: process.env.LEDGER_HOST, + user: process.env.LEDGER_USER, + pass: process.env.LEDGER_PASS + } - const options = {host, port, user, pass}; - await dbConnector.connect(options, [ + if (!dbOptions.host) throw new Error('Missing environment variable, DB_HOST'); + if (!dbOptions.port) throw new Error('Missing environment variable, DB_PORT'); + if (!dbOptions.user) throw new Error('Missing environment variable, DB_USER'); + if (!dbOptions.pass) throw new Error('Missing environment variable, DB_PASSWORD'); + + await dbConnector.connect(dbOptions, [ MemberRepository ]); + + await ledgerConnector.connect(ledgerOptions); + } } @@ -41,6 +54,7 @@ export class StartupService implements OnModuleInit { export class ShutdownService implements OnModuleDestroy { async onModuleDestroy() { await dbConnector.disconnect(); + await ledgerConnector.disconnect(); } } diff --git a/src/util/ledger-connector.ts b/src/util/ledger-connector.ts new file mode 100644 index 0000000..8dabbd8 --- /dev/null +++ b/src/util/ledger-connector.ts @@ -0,0 +1,92 @@ + +import { Logger } from "@nestjs/common"; +import mongoose, { Document, Model, Schema } from "mongoose"; + +export interface LedgerModel extends Document { + _id: string + timestamp: string; + type: string; + postId: string; + postAuthorId: string; + citationId: string; + citationAuthorId: string; + change: number; + balance: number; +} + +export interface LedgerOptions { + host: string; + user: string; + pass: string; +} + +export class LedgerConnector { + + private static instance: LedgerConnector; + private logger: Logger; + private ledgerModel: Model; + + private LedgerSchema = new Schema({ + _id: String, + timestamp: String, + type: String, + postId: String, + postAuthorId: String, + citationId: String, + citationAuthorId: String, + change: Number, + balance: Number + }); + + private constructor(logger: Logger) { + this.logger = logger; + } + + public static getInstance(logger: Logger) { + if (!LedgerConnector.instance) LedgerConnector.instance = new LedgerConnector(logger); + return LedgerConnector.instance; + } + + public async connect(options: LedgerOptions) { + const connectionString = `mongodb+srv://${options.user}:${options.pass}@${options.host}`; + this.logger.log(`Connecting to the Ledger @ ${options.host} as ${options.user}...`); + try { + await mongoose.connect(connectionString, {}); + this.logger.log('Successfully connected to the Ledger'); + this.ledgerModel = mongoose.model('Ledger', this.LedgerSchema) as Model; + } catch(error) { + this.logger.error(`Failed to connect to Ledger - ${error.message}`); + throw error; + } + } + + public async disconnect() { + this.logger.log('Closing Ledger connection...'); + await mongoose.disconnect(); + } + + public async addEntry(entry: LedgerModel): Promise { + return await this.ledgerModel.create(entry); + } + + public async get(id: string): Promise { + return await this.ledgerModel.findById(id); + } + + public async getByPostId(postId: string): Promise { + return await this.ledgerModel.find({ postId }); + } + + public async getByCitationId(citationId: string): Promise { + return await this.ledgerModel.find({ citationId }); + } + + public async getByPostAuthorId(postAuthorId: string): Promise { + return await this.ledgerModel.find({ postAuthorId }); + } + + public async getCitationAuthorId(citationAuthorId: string): Promise { + return await this.ledgerModel.find({ citationAuthorId }); + } + +} \ No newline at end of file