dbgate-api 4.6.3 → 4.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dbgate-api",
3
3
  "main": "src/index.js",
4
- "version": "4.6.3",
4
+ "version": "4.7.2",
5
5
  "homepage": "https://dbgate.org/",
6
6
  "repository": {
7
7
  "type": "git",
@@ -25,9 +25,9 @@
25
25
  "compare-versions": "^3.6.0",
26
26
  "cors": "^2.8.5",
27
27
  "cross-env": "^6.0.3",
28
- "dbgate-query-splitter": "^4.6.3",
29
- "dbgate-sqltree": "^4.6.3",
30
- "dbgate-tools": "^4.6.3",
28
+ "dbgate-query-splitter": "^4.7.2",
29
+ "dbgate-sqltree": "^4.7.2",
30
+ "dbgate-tools": "^4.7.2",
31
31
  "diff": "^5.0.0",
32
32
  "diff2html": "^3.4.13",
33
33
  "eslint": "^6.8.0",
@@ -63,7 +63,7 @@
63
63
  "devDependencies": {
64
64
  "@types/fs-extra": "^9.0.11",
65
65
  "@types/lodash": "^4.14.149",
66
- "dbgate-types": "^4.6.3",
66
+ "dbgate-types": "^4.7.2",
67
67
  "env-cmd": "^10.1.0",
68
68
  "node-loader": "^1.0.2",
69
69
  "nodemon": "^2.0.2",
@@ -21,11 +21,13 @@ module.exports = {
21
21
  const name = await this.getNewAppFolder({ name: folder });
22
22
  await fs.mkdir(path.join(appdir(), name));
23
23
  socket.emitChanged('app-folders-changed');
24
+ this.emitChangedDbApp(folder);
24
25
  return name;
25
26
  },
26
27
 
27
28
  files_meta: true,
28
29
  async files({ folder }) {
30
+ if (!folder) return [];
29
31
  const dir = path.join(appdir(), folder);
30
32
  if (!(await fs.exists(dir))) return [];
31
33
  const files = await fs.readdir(dir);
@@ -93,6 +95,8 @@ module.exports = {
93
95
  if (!folder) throw new Error('Missing folder parameter');
94
96
  await fs.rmdir(path.join(appdir(), folder), { recursive: true });
95
97
  socket.emitChanged(`app-folders-changed`);
98
+ socket.emitChanged(`app-files-changed-${folder}`);
99
+ socket.emitChanged('used-apps-changed');
96
100
  },
97
101
 
98
102
  async getNewAppFolder({ name }) {
@@ -141,6 +141,13 @@ module.exports = {
141
141
  });
142
142
  },
143
143
 
144
+ saveText_meta: true,
145
+ async saveText({ folder, file, text }) {
146
+ await fs.writeFile(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), text);
147
+ socket.emitChanged(`archive-files-changed-${folder}`);
148
+ return true;
149
+ },
150
+
144
151
  async getNewArchiveFolder({ database }) {
145
152
  const isLink = database.endsWith(database);
146
153
  const name = isLink ? database.slice(0, -5) : database;
@@ -1,25 +1,29 @@
1
1
  const fs = require('fs-extra');
2
+ const os = require('os');
2
3
  const path = require('path');
3
4
  const axios = require('axios');
4
5
  const { datadir } = require('../utility/directories');
5
6
  const hasPermission = require('../utility/hasPermission');
6
7
  const socket = require('../utility/socket');
7
8
  const _ = require('lodash');
9
+ const AsyncLock = require('async-lock');
8
10
 
9
11
  const currentVersion = require('../currentVersion');
10
12
  const platformInfo = require('../utility/platformInfo');
11
13
  const connections = require('../controllers/connections');
12
14
 
15
+ const lock = new AsyncLock();
16
+
13
17
  module.exports = {
14
- settingsValue: {},
18
+ // settingsValue: {},
15
19
 
16
- async _init() {
17
- try {
18
- this.settingsValue = JSON.parse(await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' }));
19
- } catch (err) {
20
- this.settingsValue = {};
21
- }
22
- },
20
+ // async _init() {
21
+ // try {
22
+ // this.settingsValue = JSON.parse(await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' }));
23
+ // } catch (err) {
24
+ // this.settingsValue = {};
25
+ // }
26
+ // },
23
27
 
24
28
  get_meta: true,
25
29
  async get() {
@@ -40,24 +44,45 @@ module.exports = {
40
44
 
41
45
  getSettings_meta: true,
42
46
  async getSettings() {
43
- return this.settingsValue;
47
+ try {
48
+ return this.fillMissingSettings(
49
+ JSON.parse(await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' }))
50
+ );
51
+ } catch (err) {
52
+ return this.fillMissingSettings({});
53
+ }
54
+ },
55
+
56
+ fillMissingSettings(value) {
57
+ const res = {
58
+ ...value,
59
+ };
60
+ if (value['app.useNativeMenu'] !== true && value['app.useNativeMenu'] !== false) {
61
+ res['app.useNativeMenu'] = os.platform() == 'darwin' ? true : false;
62
+ }
63
+ return res;
44
64
  },
45
65
 
46
66
  updateSettings_meta: true,
47
67
  async updateSettings(values) {
48
68
  if (!hasPermission(`settings/change`)) return false;
49
- try {
50
- const updated = {
51
- ...this.settingsValue,
52
- ...values,
53
- };
54
- await fs.writeFile(path.join(datadir(), 'settings.json'), JSON.stringify(updated, undefined, 2));
55
- this.settingsValue = updated;
56
- socket.emitChanged(`settings-changed`);
57
- return updated;
58
- } catch (err) {
59
- return false;
60
- }
69
+
70
+ const res = await lock.acquire('update', async () => {
71
+ const currentValue = await this.getSettings();
72
+ try {
73
+ const updated = {
74
+ ...currentValue,
75
+ ...values,
76
+ };
77
+ await fs.writeFile(path.join(datadir(), 'settings.json'), JSON.stringify(updated, undefined, 2));
78
+ // this.settingsValue = updated;
79
+ socket.emitChanged(`settings-changed`);
80
+ return updated;
81
+ } catch (err) {
82
+ return false;
83
+ }
84
+ });
85
+ return res;
61
86
  },
62
87
 
63
88
  changelog_meta: true,
@@ -62,9 +62,10 @@ module.exports = {
62
62
  delete this.requests[msgid];
63
63
  },
64
64
  handle_status(conid, database, { status }) {
65
+ // console.log('HANDLE SET STATUS', status);
65
66
  const existing = this.opened.find(x => x.conid == conid && x.database == database);
66
67
  if (!existing) return;
67
- if (existing.status == status) return;
68
+ if (existing.status && status && existing.status.counter > status.counter) return;
68
69
  existing.status = status;
69
70
  socket.emitChanged(`database-status-changed-${conid}-${database}`);
70
71
  },
@@ -109,7 +110,7 @@ module.exports = {
109
110
  msgtype: 'connect',
110
111
  connection: { ...connection, database },
111
112
  structure: lastClosed ? lastClosed.structure : null,
112
- globalSettings: config.settingsValue,
113
+ globalSettings: await config.getSettings(),
113
114
  });
114
115
  return newOpened;
115
116
  },
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const lineReader = require('line-reader');
3
3
  const _ = require('lodash');
4
+ const { __ } = require('lodash/fp');
4
5
  const DatastoreProxy = require('../utility/DatastoreProxy');
5
6
  const { saveFreeTableData } = require('../utility/freeTableStorage');
6
7
  const getJslFileName = require('../utility/getJslFileName');
@@ -10,7 +11,10 @@ const socket = require('../utility/socket');
10
11
  function readFirstLine(file) {
11
12
  return new Promise((resolve, reject) => {
12
13
  lineReader.open(file, (err, reader) => {
13
- if (err) reject(err);
14
+ if (err) {
15
+ reject(err);
16
+ return;
17
+ }
14
18
  if (reader.hasNextLine()) {
15
19
  reader.nextLine((err, line) => {
16
20
  if (err) reject(err);
@@ -108,7 +112,16 @@ module.exports = {
108
112
  async getInfo({ jslid }) {
109
113
  const file = getJslFileName(jslid);
110
114
  const firstLine = await readFirstLine(file);
111
- if (firstLine) return JSON.parse(firstLine);
115
+ if (firstLine) {
116
+ const parsed = JSON.parse(firstLine);
117
+ if (parsed.__isStreamHeader) {
118
+ return parsed;
119
+ }
120
+ return {
121
+ __isStreamHeader: true,
122
+ __isDynamicStructure: true,
123
+ };
124
+ }
112
125
  return null;
113
126
  },
114
127
 
@@ -132,7 +145,7 @@ module.exports = {
132
145
  },
133
146
 
134
147
  async notifyChangedStats(stats) {
135
- console.log('SENDING STATS', JSON.stringify(stats));
148
+ // console.log('SENDING STATS', JSON.stringify(stats));
136
149
  const datastore = this.datastores[stats.jslid];
137
150
  if (datastore) await datastore.notifyChanged();
138
151
  socket.emit(`jsldata-stats-${stats.jslid}`, stats);
@@ -151,4 +164,10 @@ module.exports = {
151
164
  saveFreeTableData(getJslFileName(jslid), data);
152
165
  return true;
153
166
  },
167
+
168
+ saveText_meta: true,
169
+ async saveText({ jslid, text }) {
170
+ await fs.promises.writeFile(getJslFileName(jslid), text);
171
+ return true;
172
+ },
154
173
  };
@@ -48,7 +48,7 @@ module.exports = {
48
48
  async write({ data }) {
49
49
  const fileName = path.join(datadir(), 'query-history.jsonl');
50
50
  await fs.appendFile(fileName, JSON.stringify(data) + '\n');
51
- socket.emitChanged('query-history-changed');
51
+ socket.emit('query-history-changed');
52
52
  return 'OK';
53
53
  },
54
54
  };
@@ -69,7 +69,7 @@ module.exports = {
69
69
  if (newOpened.disconnected) return;
70
70
  this.close(conid, false);
71
71
  });
72
- subprocess.send({ msgtype: 'connect', ...connection, globalSettings: config.settingsValue });
72
+ subprocess.send({ msgtype: 'connect', ...connection, globalSettings: await config.getSettings() });
73
73
  return newOpened;
74
74
  });
75
75
  return res;
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '4.6.3',
4
- buildTime: '2022-02-07T20:42:15.958Z'
3
+ version: '4.7.2',
4
+ buildTime: '2022-03-07T17:31:11.445Z'
5
5
  };
package/src/main.js CHANGED
@@ -28,8 +28,7 @@ const queryHistory = require('./controllers/queryHistory');
28
28
 
29
29
  const { rundir } = require('./utility/directories');
30
30
  const platformInfo = require('./utility/platformInfo');
31
-
32
- let checkLocalhostOrigin = null;
31
+ const getExpressPath = require('./utility/getExpressPath');
33
32
 
34
33
  function start() {
35
34
  // console.log('process.argv', process.argv);
@@ -50,32 +49,13 @@ function start() {
50
49
  );
51
50
  }
52
51
 
53
- app.use(function (req, res, next) {
54
- if (checkLocalhostOrigin) {
55
- if (
56
- req.headers.origin &&
57
- req.headers.origin != checkLocalhostOrigin &&
58
- req.headers.origin != `http://${checkLocalhostOrigin}`
59
- ) {
60
- console.log('API origin check FAILED');
61
- console.log('HEADERS', { ...req.headers, authorization: '***' });
62
- return res.status(403).json({ error: 'Not authorized!' });
63
- }
64
- if (!req.headers.origin && req.headers.host != checkLocalhostOrigin) {
65
- console.log('API host check FAILED');
66
- console.log('HEADERS', { ...req.headers, authorization: '***' });
67
- return res.status(403).json({ error: 'Not authorized!' });
68
- }
69
- }
70
- next();
71
- });
72
-
73
52
  app.use(cors());
74
53
 
75
- app.get('/stream', async function (req, res) {
54
+ app.get(getExpressPath('/stream'), async function (req, res) {
76
55
  res.set({
77
56
  'Cache-Control': 'no-cache',
78
57
  'Content-Type': 'text/event-stream',
58
+ 'X-Accel-Buffering': 'no',
79
59
  Connection: 'keep-alive',
80
60
  });
81
61
  res.flushHeaders();
@@ -88,7 +68,7 @@ function start() {
88
68
  app.use(bodyParser.json({ limit: '50mb' }));
89
69
 
90
70
  app.use(
91
- '/uploads',
71
+ getExpressPath('/uploads'),
92
72
  fileUpload({
93
73
  limits: { fileSize: 4 * 1024 * 1024 },
94
74
  })
@@ -100,21 +80,21 @@ function start() {
100
80
  // app.use('/pages', express.static(process.env.PAGES_DIRECTORY));
101
81
  // }
102
82
 
103
- app.use('/runners/data', express.static(rundir()));
83
+ app.use(getExpressPath('/runners/data'), express.static(rundir()));
104
84
 
105
85
  if (platformInfo.isDocker) {
106
86
  // server static files inside docker container
107
- app.use(express.static('/home/dbgate-docker/public'));
87
+ app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
108
88
  } else {
109
89
  if (!platformInfo.isNpmDist) {
110
- app.get('/', (req, res) => {
90
+ app.get(getExpressPath('/'), (req, res) => {
111
91
  res.send('DbGate API');
112
92
  });
113
93
  }
114
94
  }
115
95
 
116
96
  if (platformInfo.isNpmDist) {
117
- app.use(express.static(path.join(__dirname, '../../dbgate-web/public')));
97
+ app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
118
98
  getPort({ port: 5000 }).then(port => {
119
99
  server.listen(port, () => {
120
100
  console.log(`DbGate API listening on port ${port}`);
@@ -165,4 +145,4 @@ function initializeElectronSender(electronSender) {
165
145
  socket.setElectronSender(electronSender);
166
146
  }
167
147
 
168
- module.exports = { start, useAllControllers, initializeElectronSender };
148
+ module.exports = { start, useAllControllers, initializeElectronSender, configController: config };
@@ -18,6 +18,12 @@ let lastStatus = null;
18
18
  let analysedTime = 0;
19
19
  let serverVersion;
20
20
 
21
+ let statusCounter = 0;
22
+ function getStatusCounter() {
23
+ statusCounter += 1;
24
+ return statusCounter;
25
+ }
26
+
21
27
  async function checkedAsyncCall(promise) {
22
28
  try {
23
29
  const res = await promise;
@@ -79,7 +85,7 @@ function handleSyncModel() {
79
85
  function setStatus(status) {
80
86
  const statusString = stableStringify(status);
81
87
  if (lastStatus != statusString) {
82
- process.send({ msgtype: 'status', status });
88
+ process.send({ msgtype: 'status', status: { ...status, counter: getStatusCounter() } });
83
89
  lastStatus = statusString;
84
90
  }
85
91
  }
@@ -2,12 +2,17 @@ const EnsureStreamHeaderStream = require('../utility/EnsureStreamHeaderStream');
2
2
 
3
3
  function copyStream(input, output) {
4
4
  return new Promise((resolve, reject) => {
5
- const ensureHeader = new EnsureStreamHeaderStream();
6
5
  const finisher = output['finisher'] || output;
7
6
  finisher.on('finish', resolve);
8
7
  finisher.on('error', reject);
9
- input.pipe(ensureHeader);
10
- ensureHeader.pipe(output);
8
+
9
+ if (output.requireFixedStructure) {
10
+ const ensureHeader = new EnsureStreamHeaderStream();
11
+ input.pipe(ensureHeader);
12
+ ensureHeader.pipe(output);
13
+ } else {
14
+ input.pipe(output);
15
+ }
11
16
  });
12
17
  }
13
18
 
@@ -11,10 +11,7 @@ class StringifyStream extends stream.Transform {
11
11
  let skip = false;
12
12
 
13
13
  if (!this.wasHeader) {
14
- skip =
15
- chunk.__isStreamHeader ||
16
- // TODO remove isArray test
17
- Array.isArray(chunk.columns);
14
+ skip = chunk.__isStreamHeader;
18
15
  this.wasHeader = true;
19
16
  }
20
17
  if (!skip) {
@@ -12,14 +12,14 @@ class ParseStream extends stream.Transform {
12
12
  _transform(chunk, encoding, done) {
13
13
  const obj = JSON.parse(chunk);
14
14
  if (!this.wasHeader) {
15
- if (
16
- !obj.__isStreamHeader &&
17
- // TODO remove isArray test
18
- !Array.isArray(obj.columns)
19
- ) {
20
- this.push({ columns: Object.keys(obj).map(columnName => ({ columnName })) });
15
+ if (!obj.__isStreamHeader) {
16
+ this.push({
17
+ __isStreamHeader: true,
18
+ __isDynamicStructure: true,
19
+ // columns: Object.keys(obj).map(columnName => ({ columnName })),
20
+ });
21
21
  }
22
-
22
+
23
23
  this.wasHeader = true;
24
24
  }
25
25
  if (!this.limitRows || this.rowsWritten < this.limitRows) {
@@ -10,11 +10,7 @@ class StringifyStream extends stream.Transform {
10
10
  _transform(chunk, encoding, done) {
11
11
  let skip = false;
12
12
  if (!this.wasHeader) {
13
- skip =
14
- (chunk.__isStreamHeader ||
15
- // TODO remove isArray test
16
- Array.isArray(chunk.columns)) &&
17
- !this.header;
13
+ skip = (chunk.__isStreamHeader && !this.header) || (chunk.__isStreamHeader && chunk.__isDynamicStructure);
18
14
  this.wasHeader = true;
19
15
  }
20
16
  if (!skip) {
@@ -14,11 +14,7 @@ class SqlizeStream extends stream.Transform {
14
14
  _transform(chunk, encoding, done) {
15
15
  let skip = false;
16
16
  if (!this.wasHeader) {
17
- if (
18
- chunk.__isStreamHeader ||
19
- // TODO remove isArray test
20
- Array.isArray(chunk.columns)
21
- ) {
17
+ if (chunk.__isStreamHeader) {
22
18
  skip = true;
23
19
  this.tableName = chunk.pureName;
24
20
  if (chunk.engine) {
@@ -13,11 +13,7 @@ class EnsureStreamHeaderStream extends stream.Transform {
13
13
  return;
14
14
  }
15
15
 
16
- if (
17
- !chunk.__isStreamHeader &&
18
- // TODO remove isArray test
19
- !Array.isArray(chunk.columns)
20
- ) {
16
+ if (!chunk.__isStreamHeader) {
21
17
  this.push({
22
18
  __isStreamHeader: true,
23
19
  __isComputedStructure: true,
@@ -27,6 +27,7 @@ class JsonLinesDatastore {
27
27
  this.reader = null;
28
28
  this.readedDataRowCount = 0;
29
29
  this.readedSchemaRow = false;
30
+ // this.firstRowToBeReturned = null;
30
31
  this.notifyChangedCallback = null;
31
32
  this.currentFilter = null;
32
33
  }
@@ -37,6 +38,7 @@ class JsonLinesDatastore {
37
38
  this.reader = null;
38
39
  this.readedDataRowCount = 0;
39
40
  this.readedSchemaRow = false;
41
+ // this.firstRowToBeReturned = null;
40
42
  this.currentFilter = null;
41
43
  reader.close(() => {});
42
44
  }
@@ -61,6 +63,11 @@ class JsonLinesDatastore {
61
63
  }
62
64
 
63
65
  async _readLine(parse) {
66
+ // if (this.firstRowToBeReturned) {
67
+ // const res = this.firstRowToBeReturned;
68
+ // this.firstRowToBeReturned = null;
69
+ // return res;
70
+ // }
64
71
  for (;;) {
65
72
  const line = await fetchNextLineFromReader(this.reader);
66
73
  if (!line) {
@@ -70,7 +77,11 @@ class JsonLinesDatastore {
70
77
 
71
78
  if (!this.readedSchemaRow) {
72
79
  this.readedSchemaRow = true;
73
- return true;
80
+ const parsedLine = JSON.parse(line);
81
+ if (parsedLine.__isStreamHeader) {
82
+ // skip to next line
83
+ continue;
84
+ }
74
85
  }
75
86
  if (this.currentFilter) {
76
87
  const parsedLine = JSON.parse(line);
@@ -130,11 +141,21 @@ class JsonLinesDatastore {
130
141
  this.reader = reader;
131
142
  this.currentFilter = filter;
132
143
  }
133
- if (!this.readedSchemaRow) {
134
- await this._readLine(false); // skip structure
135
- }
144
+ // if (!this.readedSchemaRow) {
145
+ // const line = await this._readLine(true); // skip structure
146
+ // if (!line.__isStreamHeader) {
147
+ // // line contains data
148
+ // this.firstRowToBeReturned = line;
149
+ // }
150
+ // }
136
151
  while (this.readedDataRowCount < offset) {
137
- await this._readLine(false);
152
+ const line = await this._readLine(false);
153
+ if (line == null) break;
154
+ // if (this.firstRowToBeReturned) {
155
+ // this.firstRowToBeReturned = null;
156
+ // } else {
157
+ // await this._readLine(false);
158
+ // }
138
159
  }
139
160
  }
140
161
 
@@ -148,7 +169,6 @@ class JsonLinesDatastore {
148
169
  res.push(line);
149
170
  }
150
171
  });
151
- // console.log('RETURN', res.length);
152
172
  return res;
153
173
  }
154
174
  }
@@ -0,0 +1,10 @@
1
+ function getExpressPath(path) {
2
+ path = path.replace(/\/*$/, '').replace(/^\/*/, '');
3
+ const root = (process.env.WEB_ROOT || '').replace(/^\/*/, '').replace(/\/*$/, '');
4
+ if (root) {
5
+ return `/${root}/${path}`;
6
+ }
7
+ return `/${path}`;
8
+ }
9
+
10
+ module.exports = getExpressPath;
@@ -5,6 +5,7 @@ let init = '';
5
5
  module.exports = {
6
6
  setSseResponse(value) {
7
7
  sseResponse = value;
8
+ setInterval(() => this.emit('ping'), 29 * 1000);
8
9
  },
9
10
  setElectronSender(value) {
10
11
  electronSender = value;
@@ -23,7 +24,8 @@ module.exports = {
23
24
  }
24
25
  },
25
26
  emitChanged(key) {
26
- this.emit('clean-cache', key);
27
- this.emit(key);
27
+ // console.log('EMIT CHANGED', key);
28
+ this.emit('changed-cache', key);
29
+ // this.emit(key);
28
30
  },
29
31
  };
@@ -1,5 +1,6 @@
1
1
  const _ = require('lodash');
2
2
  const express = require('express');
3
+ const getExpressPath = require('./getExpressPath');
3
4
 
4
5
  /**
5
6
  * @param {string} route
@@ -74,6 +75,6 @@ module.exports = function useController(app, electron, route, controller) {
74
75
  }
75
76
 
76
77
  if (app) {
77
- app.use(route, router);
78
+ app.use(getExpressPath(route), router);
78
79
  }
79
80
  };
package/webpack.config.js CHANGED
@@ -10,16 +10,16 @@ var config = {
10
10
  target: 'node',
11
11
  node: {
12
12
  __dirname: false,
13
- },
13
+ },
14
14
  output: {
15
15
  path: path.resolve(__dirname, 'dist'),
16
16
  filename: 'bundle.js',
17
17
  libraryTarget: 'commonjs2',
18
18
  },
19
19
 
20
- // optimization: {
21
- // minimize: false,
22
- // },
20
+ // optimization: {
21
+ // minimize: false,
22
+ // },
23
23
 
24
24
  module: {
25
25
  rules: [
@@ -45,6 +45,9 @@ var config = {
45
45
  },
46
46
  }),
47
47
  ],
48
+ externals: {
49
+ 'better-sqlite3': 'commonjs better-sqlite3',
50
+ },
48
51
  };
49
52
 
50
53
  module.exports = config;