database-connector for neo4j

This commit is contained in:
Chegele 2023-09-26 15:29:55 -04:00
parent f83eac3ccc
commit b142a1eded
10 changed files with 216 additions and 5 deletions

5
.gitignore vendored
View File

@ -32,4 +32,7 @@ lerna-debug.log*
!.vscode/settings.json !.vscode/settings.json
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/launch.json !.vscode/launch.json
!.vscode/extensions.json !.vscode/extensions.json
# Environement
.env

10
example.env Normal file
View File

@ -0,0 +1,10 @@
# DO NOT COMMIT THIS FILE!
# 1. Validate that the .gitignore file includes .env
# 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

71
package-lock.json generated
View File

@ -16,7 +16,9 @@
"@nestjs/graphql": "^12.0.1", "@nestjs/graphql": "^12.0.1",
"@nestjs/platform-express": "^10.0.0", "@nestjs/platform-express": "^10.0.0",
"apollo-server-core": "^3.12.0", "apollo-server-core": "^3.12.0",
"dotenv": "^16.3.1",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"neo4j-driver": "^5.11.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"ts-morph": "^19.0.0" "ts-morph": "^19.0.0"
@ -3582,7 +3584,6 @@
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -4346,6 +4347,17 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/dotenv": {
"version": "16.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/motdotla/dotenv?sponsor=1"
}
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -5465,7 +5477,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -6844,6 +6855,62 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true "dev": true
}, },
"node_modules/neo4j-driver": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/neo4j-driver/-/neo4j-driver-5.11.0.tgz",
"integrity": "sha512-2IPKXH9najfKJyczIZ8R15p/oYsb4P+nwp76XRjO46Zl+ssc22+gMe/8FLRYw3tRJYc3b87ikx2s8ZNuseOAxQ==",
"dependencies": {
"neo4j-driver-bolt-connection": "5.11.0",
"neo4j-driver-core": "5.11.0",
"rxjs": "^7.8.1"
}
},
"node_modules/neo4j-driver-bolt-connection": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/neo4j-driver-bolt-connection/-/neo4j-driver-bolt-connection-5.11.0.tgz",
"integrity": "sha512-jfptm6W/a4CIoip4S/KubxrPIIV3hdOJ8B5t2RtMJwVfup8uJFzRsQLW/ljg7PJdMiE1hHQ94/qcVKd3gCC3og==",
"dependencies": {
"buffer": "^6.0.3",
"neo4j-driver-core": "5.11.0",
"string_decoder": "^1.3.0"
}
},
"node_modules/neo4j-driver-bolt-connection/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/neo4j-driver-bolt-connection/node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/neo4j-driver-core": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/neo4j-driver-core/-/neo4j-driver-core-5.11.0.tgz",
"integrity": "sha512-HIZrX1wIkwb1BlXtDk0thbyzYrlDKQK9PuzcgeKF9/fTORxr5K39kdIiwVi3gkoGOcFCSoBu+fTnlnav1BcgRg=="
},
"node_modules/node-abort-controller": { "node_modules/node-abort-controller": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",

View File

@ -27,7 +27,9 @@
"@nestjs/graphql": "^12.0.1", "@nestjs/graphql": "^12.0.1",
"@nestjs/platform-express": "^10.0.0", "@nestjs/platform-express": "^10.0.0",
"apollo-server-core": "^3.12.0", "apollo-server-core": "^3.12.0",
"dotenv": "^16.3.1",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"neo4j-driver": "^5.11.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"ts-morph": "^19.0.0" "ts-morph": "^19.0.0"

View File

@ -1,4 +1,5 @@
import { Module, Post } from '@nestjs/common'; import 'dotenv/config';
import { Injectable, Logger, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { GraphQLModule } from '@nestjs/graphql'; import { GraphQLModule } from '@nestjs/graphql';
@ -8,6 +9,41 @@ import { MemberResolver } from './services/reputation/components/member/member.r
import { PostResolver } from './services/reputation/components/post/post.resolver'; import { PostResolver } from './services/reputation/components/post/post.resolver';
import { CitationsResolver } from './services/reputation/components/citation/citation.resolver'; import { CitationsResolver } from './services/reputation/components/citation/citation.resolver';
import { DatabaseConnector } from './util/database-connector';
import { MemberRepository } from './services/reputation/components/member/member.repository';
const dbConnector = new DatabaseConnector(new Logger('DatabaseConnector'));
@Injectable()
export class StartupService implements OnModuleInit {
async 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;
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 options = {host, port, user, pass};
await dbConnector.connect(options, [
MemberRepository
]);
}
}
@Injectable()
export class ShutdownService implements OnModuleDestroy {
async onModuleDestroy() {
await dbConnector.disconnect();
}
}
@Module({ @Module({
imports: [ imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({ GraphQLModule.forRoot<ApolloDriverConfig>({
@ -19,6 +55,14 @@ import { CitationsResolver } from './services/reputation/components/citation/cit
}), }),
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppService, MemberResolver, PostResolver, CitationsResolver], providers: [
AppService,
StartupService,
ShutdownService,
MemberResolver,
PostResolver,
CitationsResolver
],
}) })
export class AppModule {} export class AppModule {}

View File

@ -110,6 +110,7 @@ describe('Rep Service Citation - Unit Tests', () => {
describe ('Rep Service Citation - Integration Tests', () => { describe ('Rep Service Citation - Integration Tests', () => {
process.env.TEST_SUITE = "Rep Service Citation - Integration Tests";
let testServer: ApolloTestServer; let testServer: ApolloTestServer;
let createUser1 = `createMember(id: "testingUser1") { id }`; let createUser1 = `createMember(id: "testingUser1") { id }`;

View File

@ -0,0 +1,23 @@
import { DatabaseConnector, Neo4jRepo } from "src/util/database-connector"
export class MemberRepository implements Neo4jRepo {
private static instance: MemberRepository;
private static connector: DatabaseConnector;
private constructor() { }
public getInstance() { return MemberRepository.getInstance(); }
public static getInstance() {
if (!MemberRepository.instance) MemberRepository.instance = new MemberRepository();
return MemberRepository.instance;
}
public setConnector(connector: DatabaseConnector) { MemberRepository.setConnector(connector); }
public static setConnector(connector: DatabaseConnector) {
MemberRepository.connector = connector;
}
}

View File

@ -76,6 +76,7 @@ describe('Rep Service Member - Unit Tests', () => {
describe('Rep Service Member - Integration Tests', () => { describe('Rep Service Member - Integration Tests', () => {
process.env.TEST_SUITE = "Rep Service Member - Integration Tests";
let testServer: ApolloTestServer; let testServer: ApolloTestServer;
let createUser1 = `createMember(id: "testingUser1") { id }`; let createUser1 = `createMember(id: "testingUser1") { id }`;

View File

@ -170,6 +170,7 @@ describe('Rep Service Post - Unit Tests', () => {
describe('Rep Service Post - Integration Tests', () => { describe('Rep Service Post - Integration Tests', () => {
process.env.TEST_SUITE = "Rep Service Post - Integration Tests";
let testServer: ApolloTestServer; let testServer: ApolloTestServer;
let createUser1 = `createMember(id: "testingUser1") { id }`; let createUser1 = `createMember(id: "testingUser1") { id }`;

View File

@ -0,0 +1,59 @@
import neo4j, { Driver } from 'neo4j-driver';
import { Logger } from '@nestjs/common';
export interface DBOptions {
host: string;
port: string;
user: string;
pass: string;
}
export interface Neo4jRepo {
getInstance(): Neo4jRepo;
setConnector(connector: DatabaseConnector): void
}
export class DatabaseConnector {
private driver: Driver;
private logger: Logger
constructor(logger: Logger) {
this.logger = logger;
}
async connect(options: DBOptions, repos: Neo4jRepo[]) {
const connectionString = `neo4j+s://${options.host}:${options.port}`;
this.logger.log(`Connection: ${connectionString}`);
const authentication = neo4j.auth.basic(options.user, options.pass);
try {
this.driver = neo4j.driver(connectionString, authentication);
const info = await this.driver.getServerInfo();
if (!info) throw new Error('Unable to retrieve database info');
this.logger.log(`Successfully connected to ${info.agent}`);
for (const repo of repos) {
this.logger.log(`Attaching to ${(<any>repo).name}`);
repo.getInstance().setConnector(this);
}
} catch(error) {
this.logger.error(`Failed to connect to Database - ${error.message}`);
throw error;
}
}
async disconnect() {
this.logger.log('Closing database connection...');
if (this.driver) await this.driver.close();
}
async runQuery(query: string, parameters: any = {}) {
const session = this.driver.session();
try {
const result = await session.writeTransaction(tx => tx.run(query, parameters));
return result.records;
} finally {
session.close();
}
}
}