From 85f2c655c18d22aafb327944f4a0b9103d95c7a0 Mon Sep 17 00:00:00 2001 From: khoa Date: Thu, 1 Oct 2020 17:58:20 -0700 Subject: [PATCH 1/9] closes #232 - init commit, add arango functions to start,close,createacc,deleteacc --- database/arango/arango.js | 75 ++++++++++++++++++ database/arango/arango.test.js | 104 ++++++++++++++++++++++++ database/elasticsearch/elastic.js | 74 ++++++++++-------- lib/sendFetch.js | 17 ++++ lib/sendFetch.test.js | 48 ++++++++++++ package-lock.json | 126 ++++++++++++++++++++++++++++++ package.json | 2 + src/server.js | 6 +- 8 files changed, 417 insertions(+), 35 deletions(-) create mode 100644 database/arango/arango.js create mode 100644 database/arango/arango.test.js create mode 100644 lib/sendFetch.js create mode 100644 lib/sendFetch.test.js diff --git a/database/arango/arango.js b/database/arango/arango.js new file mode 100644 index 00000000..7078c81f --- /dev/null +++ b/database/arango/arango.js @@ -0,0 +1,75 @@ +require("dotenv").config(); +const { Database } = require("arangojs"); +const { sendFetch } = require("../../lib/sendFetch"); +const logger = require("./../../lib/log")(__filename); + +const arangoModule = {}; +let db; +//left off here. need to make tests for this function +// need to check if function has been called yet +arangoModule.checkIfDatabaseExists = async (name) => { + const names = await db.databases(); + return names.map((db) => db._name).includes(name); +}; + +arangoModule.startArangoDB = async () => { + db = new Database({ + url: process.env.ARANGO_URL, + databaseName: "_system", + auth: { + username: process.env.ARANGO_USER, + password: process.env.ARANGO_PW, + }, + }); +}; + +arangoModule.closeArangoDB = () => db.close(); + +arangoModule.createAccount = async (username, password) => { + if (!username || !password) return; + if (await arangoModule.checkIfDatabaseExists(username)) return; + + try { + await db.createDatabase(username, { + users: [{ username, password }], + }); + } catch (err) { + logger.error(err); + throw new Error("Error in creating arango database :(", err); + } +}; + +arangoModule.deleteAccount = async (username) => { + if (!username) return; + if (!(await arangoModule.checkIfDatabaseExists(username))) return; + + try { + // grabs JWT token for use in second fetch + const { + jwt, + } = await sendFetch( + "https://arangodb.songz.dev/_db/_system/_open/auth", + "post", + { username: process.env.ARANGO_USER, password: process.env.PW } + ); + + // uses jwt token to authenticate request to delete user from arango database + await sendFetch( + `https://arangodb.songz.dev/_db/_system/_api/user/${username}`, + "delete", + null, + `bearer ${jwt}` + ); + + // deletes database(username and database names are the same for each user) + await db.dropDatabase(username); + } catch (err) { + logger.error(err); + throw new Error( + "Sum ting wong... deletion of arango user has failed.", + err + ); + } +}; + +module.exports = arangoModule; diff --git a/database/arango/arango.test.js b/database/arango/arango.test.js new file mode 100644 index 00000000..20964911 --- /dev/null +++ b/database/arango/arango.test.js @@ -0,0 +1,104 @@ +jest.mock("arangojs"); +jest.mock("../../lib/sendFetch"); +const { sendFetch } = require("../../lib/sendFetch"); +const Arango = require("arangojs"); +jest.mock("../../lib/log"); +const logGen = require("../../lib/log"); +const logger = { + info: jest.fn(), + error: jest.fn(), +}; +logGen.mockReturnValue(logger); +jest.mock("dotenv"); +Arango.Database = jest.fn().mockReturnValue({ + close: jest.fn(), + createDatabase: jest.fn(), +}); + +const { + startArangoDB, + closeArangoDB, + createAccount, + deleteAccount, + checkIfDatabaseExists, +} = require("./arango"); + +describe("ArangoDB functions", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test("should call Database function", () => { + startArangoDB(); + expect(Arango.Database).toHaveBeenCalledTimes(1); + }); + + test("should retun false", async () => { + const db = new Arango.Database(); + db.databases = jest.fn().mockReturnValue([{ _name: "lol" }]); + const res = await checkIfDatabaseExists("hi"); + expect(res).toEqual(false); + }); + + test("should retun true", async () => { + const db = new Arango.Database(); + db.databases = jest.fn().mockReturnValue([{ _name: "lol" }]); + const res = await checkIfDatabaseExists("lol"); + expect(res).toEqual(true); + }); + + test("should call db.close function", () => { + const db = new Arango.Database(); + closeArangoDB(); + expect(db.close).toHaveBeenCalledTimes(1); + }); + + test("should call db.createDatabase function, logger.error when theres an \ + error, and do nothing if either arguments are invalid", async () => { + const db = new Arango.Database(); + await createAccount(); + expect(db.createDatabase).toHaveBeenCalledTimes(0); + expect(logger.error).toHaveBeenCalledTimes(0); + + db.databases = jest.fn().mockReturnValue([{ _name: "lol" }]); + await createAccount("lol", "hi"); + expect(db.createDatabase).toHaveBeenCalledTimes(0); + + try { + db.databases = jest.fn().mockReturnValue([{ _name: "lol" }]); + await createAccount("hi", "hi"); + expect(db.createDatabase).toHaveBeenCalledTimes(1); + + db.createDatabase = jest.fn().mockImplementation(() => { + throw new Error(); + }); + await createAccount("hi", "hi"); + } catch (err) {} + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + test("should send two fetch requests if successful, logger.error when theres \ + an error, and do nothing if argument is invalid", async () => { + const db = new Arango.Database(); + await deleteAccount(); + expect(sendFetch).toHaveBeenCalledTimes(0); + expect(logger.error).toHaveBeenCalledTimes(0); + + db.databases = jest.fn().mockReturnValue([{ _name: "lol" }]); + sendFetch.mockReturnValue({ jwt: "lol" }); + await deleteAccount("hi"); + expect(sendFetch).toHaveBeenCalledTimes(0); + db.dropDatabase = jest.fn().mockReturnValue("lol"); + await deleteAccount("lol"); + expect(sendFetch).toHaveBeenCalledTimes(2); + expect(db.dropDatabase).toHaveBeenCalledTimes(1); + + try { + sendFetch.mockImplementation(() => { + throw new Error(); + }); + await deleteAccount("lol"); + } catch (err) {} + expect(logger.error).toHaveBeenCalledTimes(1); + }); +}); diff --git a/database/elasticsearch/elastic.js b/database/elasticsearch/elastic.js index c5129006..ff2a0533 100644 --- a/database/elasticsearch/elastic.js +++ b/database/elasticsearch/elastic.js @@ -1,7 +1,6 @@ -const fetch = require("node-fetch"); const logger = require("./../../lib/log")(__filename); require("dotenv").config(); - +const { sendFetch } = require("../../lib/sendFetch"); const es = {}; const ES_HOST = process.env.ES_HOST || "http://127.0.0.1:9200"; @@ -9,27 +8,27 @@ const authorization = "Basic " + Buffer.from(`elastic:${process.env.ES_PASSWORD}`).toString("base64"); -const sendESRequest = (path, method, body) => { - const options = { - method, - headers: { - Authorization: authorization, - "content-type": "application/json", - }, - }; - if (body) { - options.body = JSON.stringify(body); - } - return fetch(`${ES_HOST}${path}`, options).then((r) => r.json()); -}; +// const sendESRequest = (path, method, body) => { +// const options = { +// method, +// headers: { +// Authorization: authorization, +// "content-type": "application/json", +// }, +// }; +// if (body) { +// options.body = JSON.stringify(body); +// } +// return fetch(`${ES_HOST}${path}`, options).then((r) => r.json()); +// }; es.createAccount = async (account) => { if (!account.username || !account.dbPassword) { logger.error("Account data is invalid"); throw new Error("Account data is invalid"); } - const r1 = await sendESRequest( - `/_security/role/${account.username}`, + const r1 = await sendFetch( + `${ES_HOST}/_security/role/${account.username}`, "POST", { indices: [ @@ -38,21 +37,28 @@ es.createAccount = async (account) => { privileges: ["all"], }, ], - } + }, + authorization ); - const r2 = await sendESRequest( - `/_security/user/${account.username}`, + const r2 = await sendFetch( + `${ES_HOST}/_security/user/${account.username}`, "POST", { email: account.email, password: account.dbPassword, roles: [account.username], - } + }, + authorization + ); + const r3 = await sendFetch( + `${ES_HOST}/${account.username}-example/_doc`, + "POST", + { + message: + "Congratulations! You have created your first index at Elasticsearch!", + }, + authorization ); - const r3 = await sendESRequest(`/${account.username}-example/_doc`, "POST", { - message: - "Congratulations! You have created your first index at Elasticsearch!", - }); const err = r1.error || r2.error || r3.error; if (err) { logger.error(err); @@ -75,13 +81,17 @@ es.deleteAccount = async (account) => { logger.error("Account data is invalid"); throw new Error("Account data is invalid"); } - const r1 = await sendESRequest( - `/_security/user/${account.username}`, - "DELETE" + const r1 = await sendFetch( + `${ES_HOST}/_security/user/${account.username}`, + "DELETE", + null, + authorization ); - const r2 = await sendESRequest( - `/_security/role/${account.username}`, - "DELETE" + const r2 = await sendFetch( + `${ES_HOST}/_security/role/${account.username}`, + "DELETE", + null, + authorization ); const err = r1.error || r2.error; if (err || !r1.found || !r2.found) { @@ -99,7 +109,7 @@ es.checkAccount = async (account) => { throw new Error("Account data is invalid"); } const index = account.username + "-example"; - const r1 = await sendESRequest(`/${index}`, "GET"); + const r1 = await sendFetch(`${ES_HOST}/${index}`, "GET", null, authorization); if (r1[index]) return true; return false; }; diff --git a/lib/sendFetch.js b/lib/sendFetch.js new file mode 100644 index 00000000..97a05c9d --- /dev/null +++ b/lib/sendFetch.js @@ -0,0 +1,17 @@ +const fetch = require("node-fetch"); + +const sendFetch = (path, method, body, authorization) => { + if (!path) return; + const options = { + method, + headers: { + "content-type": "application/json", + }, + }; + body && (options.body = JSON.stringify(body)); + authorization && (options.headers.authorization = authorization); + + return fetch(path, options).then((r) => r.json()); +}; + +module.exports = { sendFetch }; diff --git a/lib/sendFetch.test.js b/lib/sendFetch.test.js new file mode 100644 index 00000000..68e639c2 --- /dev/null +++ b/lib/sendFetch.test.js @@ -0,0 +1,48 @@ +jest.mock("node-fetch"); +const fetch = require("node-fetch"); +const { sendFetch } = require("./sendFetch"); + +fetch.mockReturnValue( + Promise.resolve({ + json: () => {}, + }) +); +describe("sendFetch function", () => { + test("should not call fetch function if no path is provided", async () => { + await sendFetch(); + expect(fetch).toHaveBeenCalledTimes(0); + }); + test("should call fetch function just one time", async () => { + await sendFetch("hi", "hi", "hi", "hi"); + expect(fetch).toHaveBeenCalledTimes(1); + expect(fetch).toBeCalledWith("hi", { + method: "hi", + body: '"hi"', + headers: { + "content-type": "application/json", + authorization: "hi", + }, + }); + }); + test("should should not have authorization in fetch request if authorization \ + parameter is not passed in", async () => { + await sendFetch("hi", "hi", "hi"); + expect(fetch).toBeCalledWith("hi", { + method: "hi", + body: '"hi"', + headers: { + "content-type": "application/json", + }, + }); + }); + test("should should not have body in fetch request if body \ + parameter is not not valid", async () => { + await sendFetch("hi", "hi"); + expect(fetch).toBeCalledWith("hi", { + method: "hi", + headers: { + "content-type": "application/json", + }, + }); + }); +}); diff --git a/package-lock.json b/package-lock.json index fdf9db35..de6ce7c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1323,6 +1323,27 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.1.tgz", "integrity": "sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==" }, + "@types/node-fetch": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", + "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "@types/normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -1574,6 +1595,25 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "arangojs": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-7.0.2.tgz", + "integrity": "sha512-41wtL5u4nsowBypnA1pEVVP8bfvqdQmH0AAkNoYxM6xIJ3ZtSsH+rOqS7vhI5BrWXdK824QMyIx1v7HUPW2Pew==", + "requires": { + "@types/node": ">=13.13.4", + "es6-error": "^4.0.1", + "multi-part": "^3.0.0", + "x3-linkedlist": "1.2.0", + "xhr": "^2.4.1" + }, + "dependencies": { + "@types/node": { + "version": "14.11.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz", + "integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==" + } + } + }, "arch": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.2.tgz", @@ -3179,6 +3219,11 @@ "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", "dev": true }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, "domexception": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", @@ -3307,6 +3352,11 @@ } } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -3727,6 +3777,11 @@ "object-assign": "^4.1.0" } }, + "file-type": { + "version": "12.4.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz", + "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==" + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -4085,6 +4140,15 @@ "is-glob": "^4.0.1" } }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "requires": { + "min-document": "^2.19.0", + "process": "~0.5.1" + } + }, "global-dirs": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", @@ -4589,6 +4653,11 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, "is-generator-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", @@ -6031,6 +6100,15 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" }, + "mime-kind": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-kind/-/mime-kind-3.0.0.tgz", + "integrity": "sha512-sx9lClVP7GXY2mO3aVDWTQLhfvAdDvNhGi3o3g7+ae3aKaoybeGbEIlnreoRKjrbDpvlPltlkIryxOtatojeXQ==", + "requires": { + "file-type": "^12.1.0", + "mime-types": "^2.1.24" + } + }, "mime-types": { "version": "2.1.26", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", @@ -6050,6 +6128,14 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "^0.1.0" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -6144,6 +6230,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "multi-part": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multi-part/-/multi-part-3.0.0.tgz", + "integrity": "sha512-pDbdYQ6DLDxAsD83w9R7r7rlW56cETL7hIB5bCWX7FJYw0K+kL5JwHr0I8tRk9lGeFcAzf+2OEzXWlG/4wCnFw==", + "requires": { + "mime-kind": "^3.0.0", + "multi-part-lite": "^1.0.0" + } + }, + "multi-part-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/multi-part-lite/-/multi-part-lite-1.0.0.tgz", + "integrity": "sha512-KxIRbBZZ45hoKX1ROD/19wJr0ql1bef1rE8Y1PCwD3PuNXV42pp7Wo8lEHYuAajoT4vfAFcd3rPjlkyEEyt1nw==" + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -6734,6 +6834,11 @@ "callsites": "^3.0.0" } }, + "parse-headers": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.3.tgz", + "integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA==" + }, "parse-json": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", @@ -7155,6 +7260,11 @@ } } }, + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -9285,11 +9395,27 @@ "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", "dev": true }, + "x3-linkedlist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/x3-linkedlist/-/x3-linkedlist-1.2.0.tgz", + "integrity": "sha512-mH/YwxpYSKNa8bDNF1yOuZCMuV+K80LtDN8vcLDUAwNazCxptDNsYt+zA/EJeYiGbdtKposhKLZjErGVOR8mag==" + }, "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" }, + "xhr": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", + "integrity": "sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ==", + "requires": { + "global": "~4.3.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", diff --git a/package.json b/package.json index e2517547..7ecf1be8 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "pm2": "^4.2.3" }, "dependencies": { + "@types/node-fetch": "^2.5.7", + "arangojs": "^7.0.2", "base-x": "^3.0.8", "bcrypt": "^5.0.0", "ejs": "^3.1.3", diff --git a/src/server.js b/src/server.js index 7d573903..b5718f5c 100644 --- a/src/server.js +++ b/src/server.js @@ -16,7 +16,7 @@ const { database } = require("./routes/renderRoutes"); require("dotenv").config(); let server = null; let app = null; -const neo4jModule = require("../database/neo4j/neo4j"); +const arangoModule = require("../database/arango/arango"); const getApp = () => { return app; @@ -25,7 +25,7 @@ const getApp = () => { const startServer = async (portNumber) => { await dbModule.start(); await pgModule.startPGDB(); - await neo4jModule.startNeo4j(); + await arangoModule.startArangoDB(); return new Promise((resolve, reject) => { app = express(); @@ -81,7 +81,7 @@ const stopServer = () => { return new Promise((resolve, reject) => { dbModule.close(); pgModule.closePGDB(); - neo4jModule.closeNeo4j(); + arangoModule.closeArangoDB(); logger.info("DB has been closed"); server.close(() => { logger.info("The server has been closed"); From dc3c6b29cea4545b674c2edb3c3b080e71943ec4 Mon Sep 17 00:00:00 2001 From: khoa Date: Thu, 1 Oct 2020 18:10:04 -0700 Subject: [PATCH 2/9] closes #232 - add more comments --- database/arango/arango.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/database/arango/arango.js b/database/arango/arango.js index 7078c81f..7a72d9bf 100644 --- a/database/arango/arango.js +++ b/database/arango/arango.js @@ -5,8 +5,7 @@ const logger = require("./../../lib/log")(__filename); const arangoModule = {}; let db; -//left off here. need to make tests for this function -// need to check if function has been called yet + arangoModule.checkIfDatabaseExists = async (name) => { const names = await db.databases(); return names.map((db) => db._name).includes(name); From f6d1322f73941e562aa981193b7a4f07898cf495 Mon Sep 17 00:00:00 2001 From: khoa Date: Thu, 1 Oct 2020 18:28:11 -0700 Subject: [PATCH 3/9] closes #232 - altered createAccount function to take in object based on postgresql account model --- database/arango/arango.js | 17 ++++++++++++----- database/arango/arango.test.js | 11 ++++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/database/arango/arango.js b/database/arango/arango.js index 7a72d9bf..74c76de4 100644 --- a/database/arango/arango.js +++ b/database/arango/arango.js @@ -7,6 +7,8 @@ const arangoModule = {}; let db; arangoModule.checkIfDatabaseExists = async (name) => { + // returns array of objects containing a '_name' key. + // Ex [{ _name: 'lol' }, { _name: 'cakes' }] const names = await db.databases(); return names.map((db) => db._name).includes(name); }; @@ -24,13 +26,15 @@ arangoModule.startArangoDB = async () => { arangoModule.closeArangoDB = () => db.close(); -arangoModule.createAccount = async (username, password) => { - if (!username || !password) return; +arangoModule.createAccount = async (account) => { + if (!account) return; + const { username, dbPassword } = account; + if (!username || !dbPassword) return; if (await arangoModule.checkIfDatabaseExists(username)) return; try { await db.createDatabase(username, { - users: [{ username, password }], + users: [{ username, password: dbPassword }], }); } catch (err) { logger.error(err); @@ -47,14 +51,14 @@ arangoModule.deleteAccount = async (username) => { const { jwt, } = await sendFetch( - "https://arangodb.songz.dev/_db/_system/_open/auth", + `${process.env.ARANGO_URL}_db/_system/_open/auth`, "post", { username: process.env.ARANGO_USER, password: process.env.PW } ); // uses jwt token to authenticate request to delete user from arango database await sendFetch( - `https://arangodb.songz.dev/_db/_system/_api/user/${username}`, + `${process.env.ARANGO_URL}_db/_system/_api/user/${username}`, "delete", null, `bearer ${jwt}` @@ -71,4 +75,7 @@ arangoModule.deleteAccount = async (username) => { } }; +// arangoModule.startArangoDB(); +// arangoModule.createAccount("kirby", "star"); + module.exports = arangoModule; diff --git a/database/arango/arango.test.js b/database/arango/arango.test.js index 20964911..465cdb98 100644 --- a/database/arango/arango.test.js +++ b/database/arango/arango.test.js @@ -61,18 +61,22 @@ describe("ArangoDB functions", () => { expect(logger.error).toHaveBeenCalledTimes(0); db.databases = jest.fn().mockReturnValue([{ _name: "lol" }]); - await createAccount("lol", "hi"); + await createAccount({ username: "lol", dbPassword: "hi" }); + expect(db.createDatabase).toHaveBeenCalledTimes(0); + + db.databases = jest.fn().mockReturnValue([{ _name: "lol" }]); + await createAccount({ username: "", dbPassword: "" }); expect(db.createDatabase).toHaveBeenCalledTimes(0); try { db.databases = jest.fn().mockReturnValue([{ _name: "lol" }]); - await createAccount("hi", "hi"); + await createAccount({ username: "hi", dbPassword: "hi" }); expect(db.createDatabase).toHaveBeenCalledTimes(1); db.createDatabase = jest.fn().mockImplementation(() => { throw new Error(); }); - await createAccount("hi", "hi"); + await createAccount({ username: "hi", dbPassword: "hi" }); } catch (err) {} expect(logger.error).toHaveBeenCalledTimes(1); }); @@ -88,6 +92,7 @@ describe("ArangoDB functions", () => { sendFetch.mockReturnValue({ jwt: "lol" }); await deleteAccount("hi"); expect(sendFetch).toHaveBeenCalledTimes(0); + db.dropDatabase = jest.fn().mockReturnValue("lol"); await deleteAccount("lol"); expect(sendFetch).toHaveBeenCalledTimes(2); From 244ea0fef042cd3cb985bcdf2a70db848f344abb Mon Sep 17 00:00:00 2001 From: khoa Date: Thu, 1 Oct 2020 18:29:39 -0700 Subject: [PATCH 4/9] closes #232 - remove comments i forgot to delete --- database/arango/arango.js | 3 --- database/arango/arango.test.js | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/database/arango/arango.js b/database/arango/arango.js index 74c76de4..038587a0 100644 --- a/database/arango/arango.js +++ b/database/arango/arango.js @@ -75,7 +75,4 @@ arangoModule.deleteAccount = async (username) => { } }; -// arangoModule.startArangoDB(); -// arangoModule.createAccount("kirby", "star"); - module.exports = arangoModule; diff --git a/database/arango/arango.test.js b/database/arango/arango.test.js index 465cdb98..b14a23cd 100644 --- a/database/arango/arango.test.js +++ b/database/arango/arango.test.js @@ -54,7 +54,7 @@ describe("ArangoDB functions", () => { }); test("should call db.createDatabase function, logger.error when theres an \ - error, and do nothing if either arguments are invalid", async () => { + error, and do nothing if argument is invalid", async () => { const db = new Arango.Database(); await createAccount(); expect(db.createDatabase).toHaveBeenCalledTimes(0); From 1f9adc17dfa8a10129d70674f64854d211a6c905 Mon Sep 17 00:00:00 2001 From: khoa Date: Thu, 1 Oct 2020 18:33:43 -0700 Subject: [PATCH 5/9] closes #232 - remove sendesreqeust functoin from elastic.js --- database/elasticsearch/elastic.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/database/elasticsearch/elastic.js b/database/elasticsearch/elastic.js index ff2a0533..15abe2b7 100644 --- a/database/elasticsearch/elastic.js +++ b/database/elasticsearch/elastic.js @@ -8,20 +8,6 @@ const authorization = "Basic " + Buffer.from(`elastic:${process.env.ES_PASSWORD}`).toString("base64"); -// const sendESRequest = (path, method, body) => { -// const options = { -// method, -// headers: { -// Authorization: authorization, -// "content-type": "application/json", -// }, -// }; -// if (body) { -// options.body = JSON.stringify(body); -// } -// return fetch(`${ES_HOST}${path}`, options).then((r) => r.json()); -// }; - es.createAccount = async (account) => { if (!account.username || !account.dbPassword) { logger.error("Account data is invalid"); From f9c75843469afb7e7de484516809fe4a4938cd5f Mon Sep 17 00:00:00 2001 From: khoa Date: Thu, 1 Oct 2020 18:38:04 -0700 Subject: [PATCH 6/9] closes #232 - fix syntax error on env variable --- database/arango/arango.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/arango/arango.js b/database/arango/arango.js index 038587a0..e86fb139 100644 --- a/database/arango/arango.js +++ b/database/arango/arango.js @@ -53,7 +53,7 @@ arangoModule.deleteAccount = async (username) => { } = await sendFetch( `${process.env.ARANGO_URL}_db/_system/_open/auth`, "post", - { username: process.env.ARANGO_USER, password: process.env.PW } + { username: process.env.ARANGO_USER, password: process.env.ARANGO_PW } ); // uses jwt token to authenticate request to delete user from arango database From ec9b1283b67de35a6c17a48474128e81761a488c Mon Sep 17 00:00:00 2001 From: khoa Date: Thu, 1 Oct 2020 19:32:50 -0700 Subject: [PATCH 7/9] closes #232 - remove neo4j files --- database/neo4j/neo4j.js | 20 -------------------- database/neo4j/neo4j.test.js | 24 ------------------------ 2 files changed, 44 deletions(-) delete mode 100644 database/neo4j/neo4j.js delete mode 100644 database/neo4j/neo4j.test.js diff --git a/database/neo4j/neo4j.js b/database/neo4j/neo4j.js deleted file mode 100644 index abddeb28..00000000 --- a/database/neo4j/neo4j.js +++ /dev/null @@ -1,20 +0,0 @@ -const neo4j = require("neo4j-driver"); -require("dotenv").config(); - -let driver; -const neo4jModule = {}; - -neo4jModule.startNeo4j = () => { - driver = neo4j.driver( - process.env.NEO4J_URL, - neo4j.auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWORD) - ); - - return driver; -}; - -neo4jModule.closeNeo4j = async () => { - await driver.close(); -}; - -module.exports = neo4jModule; diff --git a/database/neo4j/neo4j.test.js b/database/neo4j/neo4j.test.js deleted file mode 100644 index 23d0dcde..00000000 --- a/database/neo4j/neo4j.test.js +++ /dev/null @@ -1,24 +0,0 @@ -jest.mock("neo4j-driver"); -const { startNeo4j, closeNeo4j } = require("./neo4j"); -const neo4j = require("neo4j-driver"); - -// neo4j.driver needed for startNeo4j -neo4j.driver = jest.fn().mockReturnValue({ - close: jest.fn().mockReturnValue(Promise.resolve()), -}); - -// driver variable needed to do any commands related to neo4j -const driver = neo4j.driver(); - -describe("Neo4j database", () => { - /* - startNeo4j initializes the `driver` variable with a value, - so it must always be called before running any neo4j related tests. - */ - beforeEach(startNeo4j); - - test("Should call driver.close when closeNeo4j is called", async () => { - await closeNeo4j(); - expect(driver.close).toHaveBeenCalledTimes(1); - }); -}); From bd35628ee0c182ccb61f0d4a7852ab64abb057cc Mon Sep 17 00:00:00 2001 From: khoa Date: Sun, 4 Oct 2020 10:10:19 -0700 Subject: [PATCH 8/9] closes #232 - add logger.info into file --- database/arango/arango.js | 35 +++++++++++++++++++++++++--------- database/arango/arango.test.js | 20 +++++++++++++++++++ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/database/arango/arango.js b/database/arango/arango.js index e86fb139..31417990 100644 --- a/database/arango/arango.js +++ b/database/arango/arango.js @@ -14,17 +14,31 @@ arangoModule.checkIfDatabaseExists = async (name) => { }; arangoModule.startArangoDB = async () => { - db = new Database({ - url: process.env.ARANGO_URL, - databaseName: "_system", - auth: { - username: process.env.ARANGO_USER, - password: process.env.ARANGO_PW, - }, - }); + try { + db = new Database({ + url: process.env.ARANGO_URL, + databaseName: "_system", + auth: { + username: process.env.ARANGO_USER, + password: process.env.ARANGO_PW, + }, + }); + logger.info("Connected to Arango Database"); + } catch (err) { + logger.error(err); + throw new Error("Error in connecting to Arango Database", err); + } }; -arangoModule.closeArangoDB = () => db.close(); +arangoModule.closeArangoDB = () => { + try { + db.close(); + logger.info("Successful closing of connection to Arango database"); + } catch (err) { + logger.error(err); + throw new Error("Error closing Arango database", err); + } +}; arangoModule.createAccount = async (account) => { if (!account) return; @@ -36,6 +50,7 @@ arangoModule.createAccount = async (account) => { await db.createDatabase(username, { users: [{ username, password: dbPassword }], }); + logger.info(`Successfully created Arango user and database ${username}`); } catch (err) { logger.error(err); throw new Error("Error in creating arango database :(", err); @@ -64,8 +79,10 @@ arangoModule.deleteAccount = async (username) => { `bearer ${jwt}` ); + logger.info(`Successfully deleted Arango user ${username}`); // deletes database(username and database names are the same for each user) await db.dropDatabase(username); + logger.info(`Successfully deleted Arango database ${username}`); } catch (err) { logger.error(err); throw new Error( diff --git a/database/arango/arango.test.js b/database/arango/arango.test.js index b14a23cd..3cdf1b4b 100644 --- a/database/arango/arango.test.js +++ b/database/arango/arango.test.js @@ -53,6 +53,18 @@ describe("ArangoDB functions", () => { expect(db.close).toHaveBeenCalledTimes(1); }); + test("should call db.close function and log error", () => { + const db = new Arango.Database(); + db.close = jest.fn().mockImplementation(() => { + throw new Error(); + }); + try { + closeArangoDB(); + } catch (err) {} + expect(db.close).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + test("should call db.createDatabase function, logger.error when theres an \ error, and do nothing if argument is invalid", async () => { const db = new Arango.Database(); @@ -106,4 +118,12 @@ describe("ArangoDB functions", () => { } catch (err) {} expect(logger.error).toHaveBeenCalledTimes(1); }); + + test("should call Database function and FAIL", () => { + Arango.Database.mockImplementation(() => { + throw new Error(); + }); + startArangoDB(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); }); From fe193b83bff44678e76b2d9cb5d05c6958ca1c85 Mon Sep 17 00:00:00 2001 From: anthonykhoa <45890848+anthonykhoa@users.noreply.github.com> Date: Mon, 5 Oct 2020 08:39:10 -0700 Subject: [PATCH 9/9] Update elastic.js merge vonflict resolutoin --- database/elasticsearch/elastic.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/elasticsearch/elastic.js b/database/elasticsearch/elastic.js index 43931368..525955b7 100644 --- a/database/elasticsearch/elastic.js +++ b/database/elasticsearch/elastic.js @@ -79,7 +79,7 @@ es.deleteAccount = async (account) => { null, authorization ); - const r3 = await sendESRequest(`/${account.username}-*`, "DELETE"); + const r3 = await sendFetch(`/${account.username}-*`, "DELETE", null, authorization); const err = r1.error || r2.error; if (err || !r1.found || !r2.found) { logger.error("Deleting Elasticsearch user failed");