refactor: simplified and improved project structure

This commit is contained in:
Fabio 2020-09-24 13:09:01 +02:00
parent 437e41bff0
commit c1cdd03938
17 changed files with 229 additions and 225 deletions

View File

@ -51,9 +51,9 @@
"electron-log": "^4.2.4",
"electron-updater": "^4.3.5",
"lodash": "^4.17.20",
"moment": "^2.27.0",
"moment": "^2.29.0",
"monaco-editor": "^0.20.0",
"mssql": "^6.2.1",
"mssql": "^6.2.2",
"mysql": "^2.18.1",
"pg": "^8.3.3",
"source-map-support": "^0.5.16",
@ -80,7 +80,7 @@
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.2.2",
"monaco-editor-webpack-plugin": "^1.9.0",
"monaco-editor-webpack-plugin": "^1.9.1",
"node-sass": "^4.14.1",
"sass-loader": "^10.0.2",
"standard-version": "^9.0.0",
@ -89,6 +89,6 @@
"stylelint-scss": "^3.18.0",
"vue": "^2.6.12",
"vue-template-compiler": "^2.6.12",
"webpack": "^4.44.1"
"webpack": "^4.44.2"
}
}

View File

@ -1,12 +1,10 @@
import { ipcMain } from 'electron';
import { ClientsFactory } from '../libs/ClientsFactory';
import InformationSchema from '../models/InformationSchema';
import Generic from '../models/Generic';
export default connections => {
ipcMain.handle('test-connection', async (event, conn) => {
const Connection = ClientsFactory.getConnection({
const connection = ClientsFactory.getConnection({
client: conn.client,
params: {
host: conn.host,
@ -16,11 +14,11 @@ export default connections => {
}
});
await Connection.connect();
await connection.connect();
try {
await InformationSchema.testConnection(Connection);
Connection.destroy();
await connection.select('1+1').run();
connection.destroy();
return { status: 'success' };
}
@ -34,7 +32,7 @@ export default connections => {
});
ipcMain.handle('connect', async (event, conn) => {
const Connection = ClientsFactory.getConnection({
const connection = ClientsFactory.getConnection({
client: conn.client,
params: {
host: conn.host,
@ -46,10 +44,16 @@ export default connections => {
});
try {
await Connection.connect();
await connection.connect();
const { rows: structure } = await InformationSchema.getStructure(Connection);
connections[conn.uid] = Connection;
const { rows: structure } = await connection
.select('*')
.schema('information_schema')
.from('TABLES')
.orderBy({ TABLE_SCHEMA: 'ASC', TABLE_NAME: 'ASC' })
.run();
connections[conn.uid] = connection;
return { status: 'success', response: structure };
}
@ -63,9 +67,15 @@ export default connections => {
delete connections[uid];
});
ipcMain.handle('refresh', async (event, uid) => {
ipcMain.handle('get-structure', async (event, uid) => {
try {
const { rows: structure } = await InformationSchema.getStructure(connections[uid]);
const { rows: structure } = await connections[uid]
.select('*')
.schema('information_schema')
.from('TABLES')
.orderBy({ TABLE_SCHEMA: 'ASC', TABLE_NAME: 'ASC' })
.run();
return { status: 'success', response: structure };
}
catch (err) {
@ -75,8 +85,13 @@ export default connections => {
ipcMain.handle('raw-query', async (event, { uid, query, schema }) => {
if (!query) return;
try {
const result = await Generic.raw(connections[uid], query, schema);
if (schema)
await connections[uid].use(schema);
const result = await connections[uid].raw(query, true);
return { status: 'success', response: result };
}
catch (err) {

View File

@ -2,12 +2,14 @@ import connection from './connection';
import tables from './tables';
import updates from './updates';
import application from './application';
import properties from './properties';
const connections = {};
export default () => {
connection(connections);
tables(connections);
properties(connections);
updates();
application();
};

View File

@ -0,0 +1,14 @@
import { ipcMain } from 'electron';
export default (connections) => {
ipcMain.handle('get-collations', async (event, uid) => {
try {
const result = await connections[uid].getCollations();
return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
};

View File

@ -1,13 +1,39 @@
import { ipcMain } from 'electron';
import InformationSchema from '../models/InformationSchema';
import Tables from '../models/Tables';
import { sqlEscaper } from 'common/libs/sqlEscaper';
import { TEXT, LONG_TEXT, NUMBER, BLOB } from 'common/fieldTypes';
import fs from 'fs';
// TODO: remap objects based on client
export default (connections) => {
ipcMain.handle('get-table-columns', async (event, { uid, schema, table }) => {
try {
const result = await InformationSchema.getTableColumns(connections[uid], schema, table);// TODO: uniform column properties
const { rows } = await connections[uid]
.select('*')
.schema('information_schema')
.from('COLUMNS')
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
.orderBy({ ORDINAL_POSITION: 'ASC' })
.run();
const result = rows.map(field => {
return {
name: field.COLUMN_NAME,
key: field.COLUMN_KEY.toLowerCase(),
type: field.DATA_TYPE,
schema: field.TABLE_SCHEMA,
table: field.TABLE_NAME,
numPrecision: field.NUMERIC_PRECISION,
datePrecision: field.DATETIME_PRECISION,
charLength: field.CHARACTER_MAXIMUM_LENGTH,
isNullable: field.IS_NULLABLE,
default: field.COLUMN_DEFAULT,
charset: field.CHARACTER_SET_NAME,
collation: field.COLLATION_NAME,
autoIncrement: field.EXTRA.includes('auto_increment'),
comment: field.COLUMN_COMMENT
};
});
return { status: 'success', response: result };
}
catch (err) {
@ -17,7 +43,13 @@ export default (connections) => {
ipcMain.handle('get-table-data', async (event, { uid, schema, table }) => {
try {
const result = await Tables.getTableData(connections[uid], schema, table);
const result = await connections[uid]
.select('*')
.schema(schema)
.from(table)
.limit(1000)
.run();
return { status: 'success', response: result };
}
catch (err) {
@ -27,7 +59,27 @@ export default (connections) => {
ipcMain.handle('get-key-usage', async (event, { uid, schema, table }) => {
try {
const result = await InformationSchema.getKeyUsage(connections[uid], schema, table);
const { rows } = await connections[uid]
.select('*')
.schema('information_schema')
.from('KEY_COLUMN_USAGE')
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
.run();
const result = rows.map(field => {
return {
schema: field.TABLE_SCHEMA,
table: field.TABLE_NAME,
column: field.COLUMN_NAME,
position: field.ORDINAL_POSITION,
constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT,
constraintName: field.CONSTRAINT_NAME,
refSchema: field.REFERENCED_TABLE_SCHEMA,
refTable: field.REFERENCED_TABLE_NAME,
refColumn: field.REFERENCED_COLUMN_NAME
};
});
return { status: 'success', response: result };
}
catch (err) {
@ -37,8 +89,34 @@ export default (connections) => {
ipcMain.handle('update-table-cell', async (event, params) => {
try {
const result = await Tables.updateTableCell(connections[params.uid], params);
return { status: 'success', response: result };
let escapedParam;
let reload = false;
const id = typeof params.id === 'number' ? params.id : `"${params.id}"`;
if (NUMBER.includes(params.type))
escapedParam = params.content;
else if ([...TEXT, ...LONG_TEXT].includes(params.type))
escapedParam = `"${sqlEscaper(params.content)}"`;
else if (BLOB.includes(params.type)) {
if (params.content) {
const fileBlob = fs.readFileSync(params.content);
escapedParam = `0x${fileBlob.toString('hex')}`;
reload = true;
}
else
escapedParam = '""';
}
else
escapedParam = `"${sqlEscaper(params.content)}"`;
await connections[params.uid]
.update({ [params.field]: `= ${escapedParam}` })
.schema(params.schema)
.from(params.table)
.where({ [params.primary]: `= ${id}` })
.run();
return { status: 'success', response: { reload } };
}
catch (err) {
return { status: 'error', response: err.toString() };
@ -47,7 +125,12 @@ export default (connections) => {
ipcMain.handle('delete-table-rows', async (event, params) => {
try {
const result = await Tables.deleteTableRows(connections[params.uid], params);
const result = await connections[params.uid]
.schema(params.schema)
.delete(params.table)
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
.run();
return { status: 'success', response: result };
}
catch (err) {
@ -57,7 +140,39 @@ export default (connections) => {
ipcMain.handle('insert-table-rows', async (event, params) => {
try {
await Tables.insertTableRows(connections[params.uid], params);
const insertObj = {};
for (const key in params.row) {
const type = params.fields[key];
let escapedParam;
if (params.row[key] === null)
escapedParam = 'NULL';
else if (NUMBER.includes(type))
escapedParam = params.row[key];
else if ([...TEXT, ...LONG_TEXT].includes(type))
escapedParam = `"${sqlEscaper(params.row[key])}"`;
else if (BLOB.includes(type)) {
if (params.row[key]) {
const fileBlob = fs.readFileSync(params.row[key]);
escapedParam = `0x${fileBlob.toString('hex')}`;
}
else
escapedParam = '""';
}
else
escapedParam = `"${sqlEscaper(params.row[key])}"`;
insertObj[key] = escapedParam;
}
for (let i = 0; i < params.repeat; i++) {
await connections[params.uid]
.schema(params.schema)
.into(params.table)
.insert(insertObj)
.run();
}
return { status: 'success' };
}
catch (err) {
@ -67,7 +182,17 @@ export default (connections) => {
ipcMain.handle('get-foreign-list', async (event, params) => {
try {
const results = await Tables.getForeignList(connections[params.uid], params);
const query = connections[params.uid]
.select(`${params.column} AS foreignColumn`)
.schema(params.schema)
.from(params.table)
.orderBy('foreignColumn ASC');
if (params.description)
query.select(`LEFT(${params.description}, 20) AS foreignDescription`);
const results = await query.run();
return { status: 'success', response: results };
}
catch (err) {

View File

@ -31,6 +31,11 @@ export class MySQLClient extends AntaresCore {
return this.raw(sql);
}
getCollations () {
const sql = 'SHOW COLLATION';
return this.raw(sql);
}
/**
* @returns {string} SQL string
* @memberof MySQLClient

View File

@ -1,14 +0,0 @@
'use strict';
export default class {
static async raw (connection, query, schema) {
if (schema) {
try {
await connection.use(schema);
}
catch (err) {
return err;
}
}
return connection.raw(query, true);
}
}

View File

@ -1,67 +0,0 @@
'use strict';
export default class {
static testConnection (connection) {
return connection.select('1+1').run();
}
static getStructure (connection) {
return connection
.select('*')
.schema('information_schema')
.from('TABLES')
.orderBy({ TABLE_SCHEMA: 'ASC', TABLE_NAME: 'ASC' })
.run();
}
static async getTableColumns (connection, schema, table) {
const { rows } = await connection
.select('*')
.schema('information_schema')
.from('COLUMNS')
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
.orderBy({ ORDINAL_POSITION: 'ASC' })
.run();
return rows.map(field => {
return {
name: field.COLUMN_NAME,
key: field.COLUMN_KEY.toLowerCase(),
type: field.DATA_TYPE,
schema: field.TABLE_SCHEMA,
table: field.TABLE_NAME,
numPrecision: field.NUMERIC_PRECISION,
datePrecision: field.DATETIME_PRECISION,
charLength: field.CHARACTER_MAXIMUM_LENGTH,
isNullable: field.IS_NULLABLE,
default: field.COLUMN_DEFAULT,
charset: field.CHARACTER_SET_NAME,
collation: field.COLLATION_NAME,
autoIncrement: field.EXTRA.includes('auto_increment'),
comment: field.COLUMN_COMMENT
};
});
}
static async getKeyUsage (connection, schema, table) {
const { rows } = await connection
.select('*')
.schema('information_schema')
.from('KEY_COLUMN_USAGE')
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
.run();
return rows.map(field => {
return {
schema: field.TABLE_SCHEMA,
table: field.TABLE_NAME,
column: field.COLUMN_NAME,
position: field.ORDINAL_POSITION,
constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT,
constraintName: field.CONSTRAINT_NAME,
refSchema: field.REFERENCED_TABLE_SCHEMA,
refTable: field.REFERENCED_TABLE_NAME,
refColumn: field.REFERENCED_COLUMN_NAME
};
});
}
}

View File

@ -1,102 +0,0 @@
'use strict';
import { sqlEscaper } from 'common/libs/sqlEscaper';
import { TEXT, LONG_TEXT, NUMBER, BLOB } from 'common/fieldTypes';
import fs from 'fs';
export default class {
static async getTableData (connection, schema, table) {
return connection
.select('*')
.schema(schema)
.from(table)
.limit(1000)
.run();
}
static async updateTableCell (connection, params) {
let escapedParam;
let reload = false;
const id = typeof params.id === 'number' ? params.id : `"${params.id}"`;
if (NUMBER.includes(params.type))
escapedParam = params.content;
else if ([...TEXT, ...LONG_TEXT].includes(params.type))
escapedParam = `"${sqlEscaper(params.content)}"`;
else if (BLOB.includes(params.type)) {
if (params.content) {
const fileBlob = fs.readFileSync(params.content);
escapedParam = `0x${fileBlob.toString('hex')}`;
reload = true;
}
else
escapedParam = '""';
}
else
escapedParam = `"${sqlEscaper(params.content)}"`;
await connection
.update({ [params.field]: `= ${escapedParam}` })
.schema(params.schema)
.from(params.table)
.where({ [params.primary]: `= ${id}` })
.run();
return { reload };
}
static async deleteTableRows (connection, params) {
return connection
.schema(params.schema)
.delete(params.table)
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
.run();
}
static async insertTableRows (connection, params) {
const insertObj = {};
for (const key in params.row) {
const type = params.fields[key];
let escapedParam;
if (params.row[key] === null)
escapedParam = 'NULL';
else if (NUMBER.includes(type))
escapedParam = params.row[key];
else if ([...TEXT, ...LONG_TEXT].includes(type))
escapedParam = `"${sqlEscaper(params.row[key])}"`;
else if (BLOB.includes(type)) {
if (params.row[key]) {
const fileBlob = fs.readFileSync(params.row[key]);
escapedParam = `0x${fileBlob.toString('hex')}`;
}
else
escapedParam = '""';
}
else
escapedParam = `"${sqlEscaper(params.row[key])}"`;
insertObj[key] = escapedParam;
}
for (let i = 0; i < params.repeat; i++) {
await connection
.schema(params.schema)
.into(params.table)
.insert(insertObj)
.run();
}
}
static async getForeignList (connection, params) {
const query = connection
.select(`${params.column} AS foreignColumn`)
.schema(params.schema)
.from(params.table)
.orderBy('foreignColumn ASC');
if (params.description)
query.select(`LEFT(${params.description}, 20) AS foreignDescription`);
return query.run();
}
}

View File

@ -10,7 +10,7 @@
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body">
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<div class="form-group">

View File

@ -10,7 +10,7 @@
</div>
<a class="btn btn-clear c-hand" @click="closeModal" />
</div>
<div class="modal-body">
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<fieldset class="m-0" :disabled="isTesting">
@ -113,13 +113,13 @@
</fieldset>
</form>
</div>
</div>
<div class="modal-footer text-light">
<BaseToast
class="mb-2"
:message="toast.message"
:status="toast.status"
/>
</div>
<div class="modal-footer text-light">
<button
class="btn btn-gray mr-2"
:class="{'loading': isTesting}"

View File

@ -10,7 +10,7 @@
</div>
<a class="btn btn-clear c-hand" @click="closeModal" />
</div>
<div class="modal-body">
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<fieldset class="m-0" :disabled="isTesting">
@ -117,13 +117,13 @@
</fieldset>
</form>
</div>
</div>
<div class="modal-footer text-light">
<BaseToast
class="mb-2"
:message="toast.message"
:status="toast.status"
/>
</div>
<div class="modal-footer text-light">
<button
class="btn btn-gray mr-2"
:class="{'loading': isTesting}"

View File

@ -10,7 +10,7 @@
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body">
<div class="modal-body pb-0">
<div class="content">
<form class="form-horizontal">
<fieldset :disabled="isInserting">

View File

@ -156,6 +156,7 @@ export default {
try { // Key usage (foreign keys)
const { status, response } = await Tables.getKeyUsage(params);
if (status === 'success') {
this.keyUsage = response;// Needed to add new rows
keysArr.push(response);

View File

@ -39,7 +39,9 @@ module.exports = {
data: 'Data',
properties: 'Properties',
insert: 'Insert',
connecting: 'Connecting'
connecting: 'Connecting',
name: 'Name',
collation: 'Collation'
},
message: {
appWelcome: 'Welcome to Antares SQL Client!',
@ -71,7 +73,9 @@ module.exports = {
addNewRow: 'Add new row',
numberOfInserts: 'Number of inserts',
openNewTab: 'Open a new tab',
affectedRows: 'Affected rows'
affectedRows: 'Affected rows',
createNewDatabase: 'Create new Database',
databaseName: 'Database name'
},
// Date and Time
short: {

View File

@ -18,8 +18,12 @@ export default class {
return ipcRenderer.invoke('disconnect', uid);
}
static refresh (uid) {
return ipcRenderer.invoke('refresh', uid);
static getStructure (uid) {
return ipcRenderer.invoke('get-structure', uid);
}
static getCollations (uid) {
return ipcRenderer.invoke('get-collations', uid);
}
static rawQuery (params) {

View File

@ -57,6 +57,9 @@ export default {
REFRESH_STRUCTURE (state, { uid, structure }) {
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, structure } : workspace);
},
REFRESH_COLLATIONS (state, { uid, collations }) { // TODO: Save collations
// state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, structure } : workspace);
},
ADD_WORKSPACE (state, workspace) {
state.workspaces.push(workspace);
},
@ -136,7 +139,7 @@ export default {
}
},
actions: {
selectWorkspace ({ commit }, uid) {
selectWorkspace ({ commit, dispatch }, uid) {
commit('SELECT_WORKSPACE', uid);
},
async connectWorkspace ({ dispatch, commit }, connection) {
@ -144,8 +147,10 @@ export default {
const { status, response } = await Connection.connect(connection);
if (status === 'error')
dispatch('notifications/addNotification', { status, message: response }, { root: true });
else
else {
commit('ADD_CONNECTED', { uid: connection.uid, structure: remapStructure(response) });
dispatch('refreshCollations', connection.uid);
}
}
catch (err) {
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
@ -153,7 +158,7 @@ export default {
},
async refreshStructure ({ dispatch, commit }, uid) {
try {
const { status, response } = await Connection.refresh(uid);
const { status, response } = await Connection.getStructure(uid);
if (status === 'error')
dispatch('notifications/addNotification', { status, message: response }, { root: true });
else
@ -163,6 +168,18 @@ export default {
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
}
},
async refreshCollations ({ dispatch, commit }, uid) {
try {
const { status, response } = await Connection.getCollations(uid);
if (status === 'error')
dispatch('notifications/addNotification', { status, message: response }, { root: true });
else
commit('REFRESH_COLLATIONS', { uid, collations: response });
}
catch (err) {
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
}
},
removeConnected ({ commit }, uid) {
Connection.disconnect(uid);
commit('REMOVE_CONNECTED', uid);