diff --git a/api/.vscode/launch.json b/api/.vscode/launch.json new file mode 100644 index 000000000..6203425ed --- /dev/null +++ b/api/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/src/index.js" + } + ] +} \ No newline at end of file diff --git a/api/package.json b/api/package.json index 719ddbbdf..8f32c1cb2 100644 --- a/api/package.json +++ b/api/package.json @@ -12,6 +12,7 @@ "fs-extra": "^8.1.0", "mssql": "^6.0.1", "mysql": "^2.17.1", + "nedb-promises": "^4.0.1", "pg": "^7.17.0" }, "scripts": { diff --git a/api/src/controllers/connections.js b/api/src/controllers/connections.js new file mode 100644 index 000000000..ff1c3dfff --- /dev/null +++ b/api/src/controllers/connections.js @@ -0,0 +1,36 @@ +const os = require('os'); +const path = require('path'); +const fs = require('fs-extra'); +const express = require('express'); +const router = express.Router(); +const { fork } = require('child_process'); +const _ = require('lodash'); +const datadir = require('../utility/datadir'); +const nedb = require('nedb-promises'); + +module.exports = { + datastore: null, + async _init() { + const dir = await datadir(); + this.datastore = nedb.create(path.join(dir, 'connections.jsonl')); + }, + list_meta: 'get', + async list() { + return this.datastore.find(); + }, + test_meta: { + method: 'post', + raw: true, + }, + test(req, res) { + const subprocess = fork(`${__dirname}/../connectProcess.js`); + subprocess.send(req.body); + subprocess.on('message', resp => res.json(resp)); + }, + + save_meta: 'post', + async save(connection) { + const res = await this.datastore.insert(connection); + return res; + }, +}; diff --git a/api/src/index.js b/api/src/index.js index 0eff5da03..c4105f647 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -1,7 +1,9 @@ const express = require('express'); -const bodyParser = require('body-parser'); +const bodyParser = require('body-parser'); const cors = require('cors'); const connection = require('./connection'); +const useController = require('./utility/useController'); +const connections = require('./controllers/connections'); const app = express(); app.use(cors()); @@ -11,6 +13,7 @@ app.get('/', (req, res) => { res.send('DbGate API'); }); +useController(app, '/connections', connections); app.use('/connection', connection); app.listen(3000); diff --git a/api/src/utility/datadir.js b/api/src/utility/datadir.js new file mode 100644 index 000000000..dce4327d8 --- /dev/null +++ b/api/src/utility/datadir.js @@ -0,0 +1,18 @@ +const os = require('os'); +const path = require('path'); +const fs = require('fs-extra'); + +let created = false; + +module.exports = async function datadir() { + const dir = path.join(os.homedir(), 'dbgate-data'); + if (!created) { + const stat = await fs.stat(dir); + if (!stat.isDirectory) { + await fs.mkdir(dir); + } + created = true; + } + + return dir; +}; diff --git a/api/src/utility/useController.js b/api/src/utility/useController.js new file mode 100644 index 000000000..c43f85a3e --- /dev/null +++ b/api/src/utility/useController.js @@ -0,0 +1,49 @@ +const _ = require('lodash'); +const express = require('express'); + +module.exports = function useController(app, route, controller) { + const router = express.Router(); + + for (const key of _.keys(controller)) { + const obj = controller[key]; + if (!_.isFunction(obj)) continue; + const meta = controller[`${key}_meta`]; + if (!meta) continue; + + let method = 'get'; + let raw = false; + let rawParams = false; + + if (_.isString(meta)) { + method = meta; + } + if (_.isPlainObject(meta)) { + method = meta.method; + raw = meta.raw; + rawParams = meta.rawParams; + } + + const route = `/${_.kebabCase(key)}`; + if (raw) { + router[method](route, controller[key]); + } else { + router[method](route, async (req, res) => { + if (controller._init && !controller._init_called) { + await controller._init(); + controller._init_called = true; + } + try { + let params = [{ ...req.body }]; + if (rawParams) params = [req, res]; + const data = await controller[key](...params); + res.json(data); + } catch (e) { + console.log(e); + res.status(500).json({ error: e.message }); + } + }); + } + } + + app.use(route, router); +}; diff --git a/api/yarn.lock b/api/yarn.lock index dfb39cb9a..1558c944a 100644 --- a/api/yarn.lock +++ b/api/yarn.lock @@ -197,6 +197,11 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +async@0.2.10: + version "0.2.10" + resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" + integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E= + async@>=0.6.0: version "3.1.0" resolved "https://registry.yarnpkg.com/async/-/async-3.1.0.tgz#42b3b12ae1b74927b5217d8c0016baaf62463772" @@ -247,6 +252,13 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== +binary-search-tree@0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/binary-search-tree/-/binary-search-tree-0.2.5.tgz#7dbb3b210fdca082450dad2334c304af39bdc784" + integrity sha1-fbs7IQ/coIJFDa0jNMMErzm9x4Q= + dependencies: + underscore "~1.4.4" + bl@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" @@ -1083,6 +1095,11 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + import-fresh@^3.0.0: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" @@ -1351,6 +1368,20 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lie@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" + integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= + dependencies: + immediate "~3.0.5" + +localforage@^1.3.0: + version "1.7.3" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.7.3.tgz#0082b3ca9734679e1bd534995bdd3b24cf10f204" + integrity sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ== + dependencies: + lie "3.1.1" + lodash@^4.17.14, lodash@^4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" @@ -1430,7 +1461,7 @@ minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -mkdirp@^0.5.1: +mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= @@ -1486,6 +1517,24 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +nedb-promises@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nedb-promises/-/nedb-promises-4.0.1.tgz#4d0bd1553d045acca5d6713ad76eb97aa830b390" + integrity sha512-I6nVZ0zjjYGfja2UU8lDSEzjfQTS8bo+8jvn7apILpynYDKzLpl6YRfdPa+uRSUYDN9bH45wJ+gvRWcOjO2g5g== + dependencies: + nedb "^1.8.0" + +nedb@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/nedb/-/nedb-1.8.0.tgz#0e3502cd82c004d5355a43c9e55577bd7bd91d88" + integrity sha1-DjUCzYLABNU1WkPJ5VV3vXvZHYg= + dependencies: + async "0.2.10" + binary-search-tree "0.2.5" + localforage "^1.3.0" + mkdirp "~0.5.1" + underscore "~1.4.4" + negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -2311,6 +2360,11 @@ undefsafe@^2.0.2: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== +underscore@~1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" + integrity sha1-YaajIBBiKvoHljvzJSA88SI51gQ= + unique-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" diff --git a/web/src/modals/ConnectionModal.js b/web/src/modals/ConnectionModal.js index 9cd041ab6..de7d3687e 100644 --- a/web/src/modals/ConnectionModal.js +++ b/web/src/modals/ConnectionModal.js @@ -10,7 +10,7 @@ export default function ConnectionModal({ modalState }) { const [sqlConnectResult, setSqlConnectResult] = React.useState('Not connected'); const handleTest = async values => { - const resp = await axios.post('http://localhost:3000/connection/test', values); + const resp = await axios.post('http://localhost:3000/connections/test', values); console.log('resp.data', resp.data); const { error, version } = resp.data; @@ -20,7 +20,7 @@ export default function ConnectionModal({ modalState }) { }; const handleSubmit = async values => { - const resp = await axios.post('http://localhost:3000/connection/save', values); + const resp = await axios.post('http://localhost:3000/connections/save', values); console.log('resp.data', resp.data); // modalState.close();