mysqlsyncr 1.0.4 → 1.0.7

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.
Files changed (59) hide show
  1. package/package.json +7 -2
  2. package/src/apply.js +16 -11
  3. package/src/cli.js +3 -2
  4. package/src/differences.js +45 -22
  5. package/src/dump.js +37 -1
  6. package/src/index.js +58 -40
  7. package/src/parsers.js +2 -1
  8. package/src/socketServer.js +96 -0
  9. package/ui/.editorconfig +7 -0
  10. package/ui/.prettierrc.json +6 -0
  11. package/ui/README.md +40 -0
  12. package/ui/dist/spa/assets/KFOkCnqEu92Fr1MmgVxIIzQ-Y2mpUUkw.woff +0 -0
  13. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmEU9fBBc--ViM7Ag4Q.woff +0 -0
  14. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmSU5fBBc--CrFzpgl6.woff +0 -0
  15. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmWUlfBBc--CW3_XjG_.woff +0 -0
  16. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmYUtfBBc--Dr8fnyGc.woff +0 -0
  17. package/ui/dist/spa/assets/KFOmCnqEu92Fr1Mu4mxM-DAYh6T4l.woff +0 -0
  18. package/ui/dist/spa/assets/MainLayout-C27l9N4F.js +1 -0
  19. package/ui/dist/spa/assets/axios-Y2H2Igoj.js +1 -0
  20. package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
  21. package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ-D-x-0Q06.woff2 +0 -0
  22. package/ui/dist/spa/assets/index-DKK0oGss.js +30 -0
  23. package/ui/dist/spa/assets/index-DME6fUxJ.css +5 -0
  24. package/ui/dist/spa/favicon.ico +0 -0
  25. package/ui/dist/spa/icons/favicon-128x128.png +0 -0
  26. package/ui/dist/spa/icons/favicon-16x16.png +0 -0
  27. package/ui/dist/spa/icons/favicon-32x32.png +0 -0
  28. package/ui/dist/spa/icons/favicon-96x96.png +0 -0
  29. package/ui/dist/spa/index.html +3 -0
  30. package/ui/eslint.config.js +73 -0
  31. package/ui/index.html +21 -0
  32. package/ui/jsconfig.json +3 -0
  33. package/ui/package.json +42 -0
  34. package/ui/postcss.config.js +29 -0
  35. package/ui/public/favicon.ico +0 -0
  36. package/ui/public/icons/favicon-128x128.png +0 -0
  37. package/ui/public/icons/favicon-16x16.png +0 -0
  38. package/ui/public/icons/favicon-32x32.png +0 -0
  39. package/ui/public/icons/favicon-96x96.png +0 -0
  40. package/ui/quasar.config.js +215 -0
  41. package/ui/src/App.vue +7 -0
  42. package/ui/src/assets/quasar-logo-vertical.svg +15 -0
  43. package/ui/src/boot/.gitkeep +0 -0
  44. package/ui/src/boot/axios.js +24 -0
  45. package/ui/src/components/EssentialLink.vue +44 -0
  46. package/ui/src/css/app.scss +1 -0
  47. package/ui/src/css/quasar.variables.scss +25 -0
  48. package/ui/src/layouts/MainLayout.vue +13 -0
  49. package/ui/src/pages/ApplyChanges.vue +46 -0
  50. package/ui/src/pages/CompareDatabase.vue +57 -0
  51. package/ui/src/pages/DatabaseManager.vue +42 -0
  52. package/ui/src/pages/DumpDatabase.vue +41 -0
  53. package/ui/src/pages/ErrorNotFound.vue +27 -0
  54. package/ui/src/pages/MainComponent copy.vue +322 -0
  55. package/ui/src/pages/MainComponent.vue +894 -0
  56. package/ui/src/router/index.js +30 -0
  57. package/ui/src/router/routes.js +20 -0
  58. package/ui/src/stores/example-store.js +21 -0
  59. package/ui/src/stores/index.js +20 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mysqlsyncr",
3
- "version": "1.0.4",
3
+ "version": "1.0.7",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -15,7 +15,10 @@
15
15
  "author": "",
16
16
  "license": "ISC",
17
17
  "dependencies": {
18
+ "cors": "^2.8.5",
19
+ "express": "^4.21.2",
18
20
  "mysql2": "^3.12.0",
21
+ "open": "^10.1.0",
19
22
  "yargs": "^17.7.2"
20
23
  },
21
24
  "devDependencies": {
@@ -24,7 +27,9 @@
24
27
  "jest": "^29.0.0"
25
28
  },
26
29
  "babel": {
27
- "presets": ["@babel/preset-env"]
30
+ "presets": [
31
+ "@babel/preset-env"
32
+ ]
28
33
  },
29
34
  "jest": {
30
35
  "transform": {
package/src/apply.js CHANGED
@@ -19,17 +19,17 @@ export const applyDifferences = async (connection, database, differences) => {
19
19
  await connection.query(dropTableSQL);
20
20
 
21
21
  } else if (diff.type === 'missing_field') {
22
- const defaultValue =
23
- diff.field.Default === 'CURRENT_TIMESTAMP'
24
- ? 'DEFAULT CURRENT_TIMESTAMP'
25
- : diff.field.Default !== null
26
- ? `DEFAULT '${diff.field.Default}'`
27
- : '';
28
- const addFieldSQL = `ALTER TABLE \`${diff.tableName}\` ADD \`${diff.field.Field}\` ${diff.field.Type} ${diff.field.Null === 'NO' ? 'NOT NULL' : ''
29
- } ${defaultValue} ${diff.field.Extra || ''};`;
30
- logger(`Executing: ${addFieldSQL}`);
22
+ // Step 1: Add column with DEFAULT NULL to handle existing records
23
+ const addFieldSQL = `ALTER TABLE \`${diff.tableName}\` ADD \`${diff.field.Field}\` ${diff.field.Type} DEFAULT NULL;`;
24
+ console.log(`Executing: ${addFieldSQL}`);
31
25
  await connection.query(addFieldSQL);
32
26
 
27
+ // Step 2: If NOT NULL is required and no default was specified, modify the column to remove DEFAULT NULL
28
+ if (diff.field.NotNull && !diff.field.Default) {
29
+ const modifyFieldSQL = `ALTER TABLE \`${diff.tableName}\` MODIFY \`${diff.field.Field}\` ${diff.field.Type} NOT NULL;`;
30
+ console.log(`Executing: ${modifyFieldSQL}`);
31
+ await connection.query(modifyFieldSQL);
32
+ }
33
33
 
34
34
  } else if (diff.type === 'extra_field') {
35
35
  const dropFieldSQL = `ALTER TABLE \`${diff.tableName}\` DROP COLUMN \`${diff.field.Field}\`;`;
@@ -38,8 +38,13 @@ export const applyDifferences = async (connection, database, differences) => {
38
38
 
39
39
  } else if (diff.type === 'mismatched_field') {
40
40
  const { tableName, field } = diff;
41
- const dropFieldSQL = `ALTER TABLE \`${tableName}\` CHANGE \`${field.Field}\` \`${field.Field}\` ${field.Type} ${field.Null === 'NO' ? 'NOT NULL' : ''
42
- } ${field.Default === 'CURRENT_TIMESTAMP' ? 'DEFAULT CURRENT_TIMESTAMP' : field.Default !== null ? `DEFAULT '${field.Default}'` : ''};`;
41
+ const defaultClause = field.Default === 'CURRENT_TIMESTAMP'
42
+ ? 'DEFAULT CURRENT_TIMESTAMP'
43
+ : field.Default !== undefined && field.Default !== null
44
+ ? `DEFAULT '${field.Default}'`
45
+ : '';
46
+
47
+ const dropFieldSQL = `ALTER TABLE \`${tableName}\` CHANGE \`${field.Field}\` \`${field.Field}\` ${field.Type} ${field.NotNull ? 'NOT NULL' : ''} ${defaultClause};`;
43
48
  logger(`Executing: ${dropFieldSQL}`);
44
49
  await connection.query(dropFieldSQL);
45
50
 
package/src/cli.js CHANGED
@@ -7,14 +7,15 @@ export const cli = () => {
7
7
  .command('dump', 'Dump the database structure to files')
8
8
  .command('compare', 'Compare the database structure with JSON dumps (dry run)')
9
9
  .command('update', 'Update the database structure based on JSON dumps')
10
+ .command('ui', 'Open the UI')
10
11
  .option('host', { alias: 'h', describe: 'Database host', default: 'localhost' })
11
12
  .option('port', { alias: 'P', describe: 'Database port', default: 3306 })
12
13
  .option('user', { alias: 'u', describe: 'Database user', default: 'root' })
13
- .option('password', { alias: 'p', describe: 'Database password', demandOption: true })
14
+ .option('password', { alias: 'p', describe: 'Database password', default: '' })
14
15
  .option('database', { alias: 'd', describe: 'Target database', default: null })
15
16
  .option('output', { alias: 'o', describe: 'Output directory for dumping. default is one level up from the current directory in a new folder called db-dump', default: '../db-dump' })
16
17
  .option('verbose', { alias: 'v', describe: 'Enable verbose logging', type: 'boolean', default: false })
17
- .demandCommand(1)
18
+ // .demandCommand(1)
18
19
  .help()
19
20
  .argv;
20
21
 
@@ -58,14 +58,19 @@ export const findDifferences = (expected, current) => {
58
58
  logger('Verifying Type Null and Default for table', tableName);
59
59
  for (const field of columns) {
60
60
  const currentField = current.tables[tableName].columns.find(f => f.Field === field.Field);
61
+ if (!currentField) {
62
+ console.error('Current field not found for', field.Field, 'in table', tableName);
63
+ differences.push({ type: 'missing_field', tableName, field });
64
+ continue;
65
+ }
61
66
  if (field.Type !== currentField.Type) {
62
- differences.push({ type: 'mismatched_field', tableName, field });
67
+ differences.push({ type: 'mismatched_field', info: "Type", tableName, field, currentField });
63
68
  }
64
69
  if (field.Null !== currentField.Null) {
65
- differences.push({ type: 'mismatched_field', tableName, field });
70
+ differences.push({ type: 'mismatched_field', info: "Null", tableName, field, currentField });
66
71
  }
67
72
  if (field.Default !== currentField.Default) {
68
- differences.push({ type: 'mismatched_field', tableName, field });
73
+ differences.push({ type: 'mismatched_field', info: "Default", tableName, field, currentField });
69
74
  }
70
75
  }
71
76
 
@@ -130,14 +135,15 @@ export const findDifferences = (expected, current) => {
130
135
 
131
136
  // Compare stored procedures
132
137
  if (!expected.procedures) throw new Error('No procedures found in expected');
133
- for (const [procName, contents] of Object.entries(expected.procedures)) {
134
- if (!current.procedures[procName]) {
138
+ for (const [position, contents] of Object.entries(expected.procedures)) {
139
+ let procName = contents.Name;
140
+ let currentProc = current.procedures.find(p => p.Name === procName);
141
+ if (!currentProc) {
135
142
  console.log('missing_procedure', contents);
136
143
  differences.push({ type: 'missing_procedure', ...contents });
137
144
  continue;
138
145
  }
139
146
 
140
- const currentProc = current.procedures[procName];
141
147
 
142
148
  // Check if the procedure definition matches
143
149
  if (currentProc.Definition !== contents.Definition) {
@@ -153,24 +159,41 @@ export const findDifferences = (expected, current) => {
153
159
  }
154
160
 
155
161
  // Compare views
156
- if (!expected.views) throw new Error('No views found in expected');
157
- for (const [viewName, expectedDefinition] of Object.entries(expected.views)) {
158
- if (!current.views[viewName]) {
159
- differences.push({ type: 'missing_view', viewName, definition: expectedDefinition });
160
- continue;
162
+ if (expected.views || current.views) {
163
+ // Handle missing views (when expected has views but current doesn't have them)
164
+ if (expected.views && !current.views) {
165
+ for (const [viewName, expectedDefinition] of Object.entries(expected.views)) {
166
+ differences.push({ type: 'missing_view', viewName, definition: expectedDefinition });
167
+ }
161
168
  }
162
-
163
- const currentDefinition = current.views[viewName];
164
- if (currentDefinition !== expectedDefinition) {
165
- differences.push({ type: 'mismatched_view', viewName, definition: expectedDefinition, current: currentDefinition });
169
+ // Handle extra views (when current has views but expected doesn't have them)
170
+ else if (current.views && !expected.views) {
171
+ for (const viewName of Object.keys(current.views)) {
172
+ differences.push({ type: 'extra_view', viewName });
173
+ }
166
174
  }
167
- }
168
-
169
- // Detect extra views
170
- if (!current.views) throw new Error('No views found in current');
171
- for (const viewName of Object.keys(current.views)) {
172
- if (!expected.views[viewName]) {
173
- differences.push({ type: 'extra_view', viewName });
175
+ // Compare views when both exist
176
+ else if (expected.views && current.views) {
177
+ // Check for missing and mismatched views
178
+ for (const [viewName, expectedDefinition] of Object.entries(expected.views)) {
179
+ if (!current.views[viewName]) {
180
+ differences.push({ type: 'missing_view', viewName, definition: expectedDefinition });
181
+ } else if (current.views[viewName] !== expectedDefinition) {
182
+ differences.push({
183
+ type: 'mismatched_view',
184
+ viewName,
185
+ definition: expectedDefinition,
186
+ current: current.views[viewName]
187
+ });
188
+ }
189
+ }
190
+
191
+ // Check for extra views
192
+ for (const viewName of Object.keys(current.views)) {
193
+ if (!expected.views[viewName]) {
194
+ differences.push({ type: 'extra_view', viewName });
195
+ }
196
+ }
174
197
  }
175
198
  }
176
199
 
package/src/dump.js CHANGED
@@ -3,10 +3,46 @@ import path from 'path';
3
3
  import { getDatabases, getDatabaseStructure } from './getters.js';
4
4
 
5
5
  export const dumpAllDatabases = async (connection, command, options) => {
6
+ let response = {}
6
7
  const databases = await getDatabases(connection);
7
8
  for (const database of databases) {
8
9
  let dbStructure = await getDatabaseStructure(connection, database, command, options);
9
- await fs.writeFile(path.join(options.output, database, `${database}.json`), JSON.stringify(dbStructure, null, 4), 'utf-8');
10
+ if(Object.keys(dbStructure.views).length === 0){
11
+ delete dbStructure.views
12
+ }
13
+ if(Object.keys(dbStructure.procedures).length === 0){
14
+ delete dbStructure.procedures
15
+ }
16
+ await fs.writeFile(path.join(options.output , database, `${database}.json`), JSON.stringify(dbStructure, null, 4), 'utf-8');
17
+ response[database] = dbStructure;
10
18
  }
19
+ return response;
11
20
  }
12
21
 
22
+ export const getDatabasesFromExistingDumps = async (options) => {
23
+ console.log('Getting databases from existing dumps in ', options.output);
24
+ //check if the folder exists
25
+ let exists = await fs.stat(options.output).catch(() => false);
26
+ if (!exists) {
27
+ console.log('Folder does not exist');
28
+ return {};
29
+ }
30
+ //read directory above to get the dbs. each db is in a named folder with a matching .json.
31
+
32
+ //combine all the .jsons into a single object
33
+ let response = {}
34
+ const databases = await fs.readdir(path.join(options.output));
35
+ for (const database of databases) {
36
+ if(database.includes('diffs')){
37
+ console.log('Skipping diffs folder')
38
+ continue
39
+ }
40
+ let dbStructure = await fs.readFile(path.join(options.output, database, `${database}.json`), 'utf8');
41
+ response[database] = JSON.parse(dbStructure);
42
+ }
43
+ return response;
44
+ }
45
+
46
+
47
+
48
+
package/src/index.js CHANGED
@@ -7,13 +7,20 @@ import path from 'path';
7
7
  import { cli } from './cli.js';
8
8
  import { dumpAllDatabases } from './dump.js';
9
9
  import { compareAllDatabases } from './compare.js';
10
-
10
+ import { startApiServer } from './socketServer.js'; // Import the new socket server
11
+ import open from 'open';
11
12
 
12
13
  class DBStructureChecker {
13
14
  constructor(config, targetDatabase = null) {
14
15
  this.config = config;
15
16
  this.targetDatabase = targetDatabase;
16
17
  this.connection = mysql.createConnection(this.config);
18
+ this.dumpAllDatabases = dumpAllDatabases;
19
+ this.compareAllDatabases = compareAllDatabases;
20
+ this.getDatabases = () => {
21
+
22
+ fs.readFileSync(path.join(__dirname, 'databases.json'), 'utf8');
23
+ }
17
24
  this.connection.on('error', (err) => {
18
25
  console.error('Database connection error:', err);
19
26
  });
@@ -35,50 +42,61 @@ class DBStructureChecker {
35
42
 
36
43
  }
37
44
 
38
- // CLI Setup
45
+ // Export the DBStructureChecker class
46
+ export { DBStructureChecker };
47
+
48
+ // Start the server only if the 'ui' command is used
49
+ const PORT = process.env.PORT || 3000;
39
50
  (async () => {
40
51
  const { command, config, argv } = cli();
41
52
 
42
- const checker = new DBStructureChecker(config, argv.database);
43
- checker.connection.on('connect', async () => {
44
- console.log('Connected to the database');
45
- checker.targetDatabase = argv.database;
46
- checker.command = command;
47
- checker.options = argv;
48
- try {
49
- if (command === 'dump') {
50
- console.log('Dumping the database structure to files');
51
- await dumpAllDatabases(checker.connection, command, argv);
52
- } else if (command === 'compare') {
53
- console.log('Comparing the database structure with JSON dumps (dry run)');
54
- let diffs = await compareAllDatabases(checker.connection, argv.output, false, command, argv); // Dry run
55
- let totalDiffs = Object.values(diffs).reduce((acc, arr) => acc + arr.length, 0);
56
- console.log('Total differences count:', totalDiffs);
57
- fs.writeFileSync(path.join(argv.output, 'diffs.json'), JSON.stringify(diffs, null, 4));
58
- } else if (command === 'update') {
59
- console.log('Updating the database structure based on JSON dumps');
60
- let totalDiffs = Infinity; // Initialize with a large number
61
- let previousDiffs = totalDiffs; // Store previous differences
62
- let runCount = 0; // Counter for the number of runs
63
- let maxRuns = 6;
64
- while (runCount < maxRuns && totalDiffs > 0 && (totalDiffs < previousDiffs || previousDiffs === Infinity)) {
65
- console.log('Running update command. Run count:', runCount);
66
- let diffs = await compareAllDatabases(checker.connection, argv.output, true, command, argv); // Apply updates
67
- previousDiffs = totalDiffs; // Update previous differences
68
- totalDiffs = Object.values(diffs).reduce((acc, arr) => acc + arr.length, 0);
53
+ if (command === 'ui') {
54
+ startApiServer(PORT); // Start the Socket.IO server
55
+ console.log('Server started on port', PORT);
56
+ console.log('Open http://localhost:' + PORT + ' in your browser to use the UI');
57
+ await open(`http://localhost:${PORT}`);
58
+ } else {
59
+ // Handle other commands (dump, compare, update) as before
60
+ const checker = new DBStructureChecker(config, argv.database);
61
+ checker.connection.on('connect', async () => {
62
+ console.log('Connected to the database');
63
+ checker.targetDatabase = argv.database;
64
+ checker.command = command;
65
+ checker.options = argv;
66
+ try {
67
+ if (command === 'dump') {
68
+ console.log('Dumping the database structure to files');
69
+ await dumpAllDatabases(checker.connection, command, argv);
70
+ } else if (command === 'compare') {
71
+ console.log('Comparing the database structure with JSON dumps (dry run)');
72
+ let diffs = await compareAllDatabases(checker.connection, argv.output, false, command, argv); // Dry run
73
+ let totalDiffs = Object.values(diffs).reduce((acc, arr) => acc + arr.length, 0);
69
74
  console.log('Total differences count:', totalDiffs);
70
- runCount++;
71
- if(totalDiffs > 0 && runCount < maxRuns){
72
- console.log('Total differences count is still greater than 0. Waiting 1 second before next run. Run count:', runCount);
73
- await new Promise(resolve => setTimeout(resolve, 1000));
75
+ fs.writeFileSync(path.join(argv.output, 'diffs.json'), JSON.stringify(diffs, null, 4));
76
+ } else if (command === 'update') {
77
+ console.log('Updating the database structure based on JSON dumps');
78
+ let totalDiffs = Infinity; // Initialize with a large number
79
+ let previousDiffs = totalDiffs; // Store previous differences
80
+ let runCount = 0; // Counter for the number of runs
81
+ let maxRuns = 6;
82
+ while (runCount < maxRuns && totalDiffs > 0 && (totalDiffs < previousDiffs || previousDiffs === Infinity)) {
83
+ console.log('Running update command. Run count:', runCount);
84
+ let diffs = await compareAllDatabases(checker.connection, argv.output, true, command, argv); // Apply updates
85
+ previousDiffs = totalDiffs; // Update previous differences
86
+ totalDiffs = Object.values(diffs).reduce((acc, arr) => acc + arr.length, 0);
87
+ console.log('Total differences count:', totalDiffs);
88
+ runCount++;
89
+ if(totalDiffs > 0 && runCount < maxRuns){
90
+ console.log('Total differences count is still greater than 0. Waiting 1 second before next run. Run count:', runCount);
91
+ await new Promise(resolve => setTimeout(resolve, 1000));
92
+ }
74
93
  }
75
94
  }
95
+ } catch (err) {
96
+ console.error(err.message);
97
+ } finally {
98
+ await checker.closeConnection();
76
99
  }
77
- } catch (err) {
78
- console.error(err.message);
79
- } finally {
80
- await checker.closeConnection();
81
- }
82
- });
83
-
100
+ });
101
+ }
84
102
  })();
package/src/parsers.js CHANGED
@@ -44,7 +44,8 @@ export const parseCreateTableSQL = (createTableSQL) => {
44
44
 
45
45
  // Set Default property if it exists
46
46
  if (columnMatch[5]) {
47
- column.Default = columnMatch[5];
47
+ // console.log('columnMatch[5]', columnMatch[5], columnMatch[5].replace(/'/g, ''))
48
+ column.Default = columnMatch[5].replace(/'/g, '');
48
49
  }
49
50
 
50
51
  // Set AutoIncrement property if it exists
@@ -0,0 +1,96 @@
1
+ import express from 'express';
2
+ import http from 'http';
3
+ import { dumpAllDatabases, getDatabasesFromExistingDumps } from './dump.js'; // Import necessary functions
4
+ import { compareAllDatabases } from './compare.js';
5
+ import { DBStructureChecker } from './index.js'; // Import your DBStructureChecker class
6
+ import { applyDifferences } from './apply.js';
7
+ import cors from 'cors';
8
+
9
+ export const startApiServer = (port) => {
10
+ const app = express();
11
+ app.use(cors({origin:'*'}));
12
+ const server = http.createServer(app);
13
+ app.use(express.json()); // Middleware to parse JSON requests
14
+
15
+ app.post('/api/getDatabasesFromExistingDumps', async (req, res) => {
16
+ const { config } = req.body;
17
+ const databases = await getDatabasesFromExistingDumps(config);
18
+ res.json(databases);
19
+ });
20
+
21
+ // API endpoint to dump the database
22
+ app.post('/api/dump', async (req, res) => {
23
+ const { config, database } = req.body;
24
+ const checker = new DBStructureChecker(config, database);
25
+ checker.connection.on('connect', async () => {
26
+ console.log('Connected to the database');
27
+ checker.targetDatabase = database;
28
+ checker.command = 'dump';
29
+ checker.options = req.body;
30
+ try {
31
+ console.log('Dumping the database structure to files');
32
+ await dumpAllDatabases(checker.connection, 'dump', config);
33
+ res.json({ message: 'Dump completed successfully' });
34
+ } catch (err) {
35
+ res.status(500).json({ message: err.message });
36
+ } finally {
37
+ await checker.closeConnection();
38
+ }
39
+ });
40
+ });
41
+
42
+ // API endpoint to compare the database
43
+ app.post('/api/compare', async (req, res) => {
44
+ const { config } = req.body;
45
+ const checker = new DBStructureChecker(config, config.database);
46
+ checker.connection.on('connect', async () => {
47
+ console.log('Connected to the database');
48
+ checker.targetDatabase = config.database;
49
+ checker.command = 'compare';
50
+ checker.options = req.body;
51
+ try {
52
+ delete req.body.output;
53
+ console.log('Comparing the database structure with JSON dumps (dry run)');
54
+ let diffs = await compareAllDatabases(checker.connection, config.output, false, 'compare', config);
55
+ res.json(diffs);
56
+ } catch (err) {
57
+ console.log(err);
58
+ console.log(req.body);
59
+ res.status(500).json({ message: err.message });
60
+ } finally {
61
+ await checker.closeConnection();
62
+ }
63
+ })
64
+
65
+ });
66
+
67
+ // API endpoint to apply differences
68
+ app.post('/api/apply', async (req, res) => {
69
+ const { config, database, diffs } = req.body;
70
+ console.log('Applying differences:', diffs);
71
+ const checker = new DBStructureChecker(config, database);
72
+ checker.connection.on('connect', async () => {
73
+ console.log('Connected to the database');
74
+ checker.targetDatabase = database;
75
+ checker.command = 'apply';
76
+ checker.options = req.body;
77
+ try {
78
+ console.log('Applying the database structure based on JSON dumps');
79
+ await applyDifferences(checker.connection, database, diffs);
80
+ res.json({ message: 'Apply completed successfully' });
81
+ } catch (err) {
82
+ res.status(500).json({ message: err.message });
83
+ } finally {
84
+ await checker.closeConnection();
85
+ }
86
+ });
87
+ });
88
+
89
+ //serve the app from the ui folder
90
+ app.use(express.static('ui/dist/spa'));
91
+
92
+ // Start the server
93
+ server.listen(port, () => {
94
+ console.log(`Server is running on http://localhost:${port}`);
95
+ });
96
+ };
@@ -0,0 +1,7 @@
1
+ [*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
2
+ charset = utf-8
3
+ indent_size = 4
4
+ indent_style = space
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/prettierrc",
3
+ "semi": false,
4
+ "singleQuote": true,
5
+ "printWidth": 100
6
+ }
package/ui/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # MySQLsyncr (mysqlsyncr)
2
+
3
+ A simple ui for the sync features in mysqlsyncr
4
+
5
+ ## Install the dependencies
6
+ ```bash
7
+ yarn
8
+ # or
9
+ npm install
10
+ ```
11
+
12
+ ### Start the app in development mode (hot-code reloading, error reporting, etc.)
13
+ ```bash
14
+ quasar dev
15
+ ```
16
+
17
+
18
+ ### Lint the files
19
+ ```bash
20
+ yarn lint
21
+ # or
22
+ npm run lint
23
+ ```
24
+
25
+
26
+ ### Format the files
27
+ ```bash
28
+ yarn format
29
+ # or
30
+ npm run format
31
+ ```
32
+
33
+
34
+ ### Build the app for production
35
+ ```bash
36
+ quasar build
37
+ ```
38
+
39
+ ### Customize the configuration
40
+ See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
@@ -0,0 +1 @@
1
+ import{c as L,i as I,e as P,l as D,p as N,b as g,h as m,f as J,g as C,j as X,s as Y,w as _,k as Z,o as T,m as H,n as V,q,r as ee,t as te,u as y,v as A,x as F,y as R,z as x,A as ne,B as oe,_ as ie,C as le,D as re,E as ae,F as k,G as B}from"./index-DKK0oGss.js";const se=L({name:"QPageContainer",setup(e,{slots:p}){const{proxy:{$q:n}}=C(),t=I(D,P);if(t===P)return console.error("QPageContainer needs to be child of QLayout"),P;N(X,!0);const r=g(()=>{const a={};return t.header.space===!0&&(a.paddingTop=`${t.header.size}px`),t.right.space===!0&&(a[`padding${n.lang.rtl===!0?"Left":"Right"}`]=`${t.right.size}px`),t.footer.space===!0&&(a.paddingBottom=`${t.footer.size}px`),t.left.space===!0&&(a[`padding${n.lang.rtl===!0?"Right":"Left"}`]=`${t.left.size}px`),a});return()=>m("div",{class:"q-page-container",style:r.value},J(p.default))}}),{passive:M}=q,ce=["both","horizontal","vertical"],ue=L({name:"QScrollObserver",props:{axis:{type:String,validator:e=>ce.includes(e),default:"vertical"},debounce:[String,Number],scrollTarget:Y},emits:["scroll"],setup(e,{emit:p}){const n={position:{top:0,left:0},direction:"down",directionChanged:!1,delta:{top:0,left:0},inflectionPoint:{top:0,left:0}};let t=null,r,a;_(()=>e.scrollTarget,()=>{l(),v()});function s(){t!==null&&t();const c=Math.max(0,ee(r)),w=te(r),d={top:c-n.position.top,left:w-n.position.left};if(e.axis==="vertical"&&d.top===0||e.axis==="horizontal"&&d.left===0)return;const z=Math.abs(d.top)>=Math.abs(d.left)?d.top<0?"up":"down":d.left<0?"left":"right";n.position={top:c,left:w},n.directionChanged=n.direction!==z,n.delta=d,n.directionChanged===!0&&(n.direction=z,n.inflectionPoint=n.position),p("scroll",{...n})}function v(){r=Z(a,e.scrollTarget),r.addEventListener("scroll",i,M),i(!0)}function l(){r!==void 0&&(r.removeEventListener("scroll",i,M),r=void 0)}function i(c){if(c===!0||e.debounce===0||e.debounce==="0")s();else if(t===null){const[w,d]=e.debounce?[setTimeout(s,e.debounce),clearTimeout]:[requestAnimationFrame(s),cancelAnimationFrame];t=()=>{d(w),t=null}}}const{proxy:h}=C();return _(()=>h.$q.lang.rtl,s),T(()=>{a=h.$el.parentNode,v()}),H(()=>{t!==null&&t(),l()}),Object.assign(h,{trigger:i,getPosition:()=>n}),V}});function de(){const e=y(!A.value);return e.value===!1&&T(()=>{e.value=!0}),{isHydrated:e}}const K=typeof ResizeObserver<"u",W=K===!0?{}:{style:"display:block;position:absolute;top:0;left:0;right:0;bottom:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1;",url:"about:blank"},j=L({name:"QResizeObserver",props:{debounce:{type:[String,Number],default:100}},emits:["resize"],setup(e,{emit:p}){let n=null,t,r={width:-1,height:-1};function a(l){l===!0||e.debounce===0||e.debounce==="0"?s():n===null&&(n=setTimeout(s,e.debounce))}function s(){if(n!==null&&(clearTimeout(n),n=null),t){const{offsetWidth:l,offsetHeight:i}=t;(l!==r.width||i!==r.height)&&(r={width:l,height:i},p("resize",r))}}const{proxy:v}=C();if(v.trigger=a,K===!0){let l;const i=h=>{t=v.$el.parentNode,t?(l=new ResizeObserver(a),l.observe(t),s()):h!==!0&&F(()=>{i(!0)})};return T(()=>{i()}),H(()=>{n!==null&&clearTimeout(n),l!==void 0&&(l.disconnect!==void 0?l.disconnect():t&&l.unobserve(t))}),V}else{let l=function(){n!==null&&(clearTimeout(n),n=null),c!==void 0&&(c.removeEventListener!==void 0&&c.removeEventListener("resize",a,q.passive),c=void 0)},i=function(){l(),t&&t.contentDocument&&(c=t.contentDocument.defaultView,c.addEventListener("resize",a,q.passive),s())};const{isHydrated:h}=de();let c;return T(()=>{F(()=>{t=v.$el,t&&i()})}),H(l),()=>{if(h.value===!0)return m("object",{class:"q--avoid-card-border",style:W.style,tabindex:-1,type:"text/html",data:W.url,"aria-hidden":"true",onLoad:i})}}}}),fe=L({name:"QLayout",props:{container:Boolean,view:{type:String,default:"hhh lpr fff",validator:e=>/^(h|l)h(h|r) lpr (f|l)f(f|r)$/.test(e.toLowerCase())},onScroll:Function,onScrollHeight:Function,onResize:Function},setup(e,{slots:p,emit:n}){const{proxy:{$q:t}}=C(),r=y(null),a=y(t.screen.height),s=y(e.container===!0?0:t.screen.width),v=y({position:0,direction:"down",inflectionPoint:0}),l=y(0),i=y(A.value===!0?0:R()),h=g(()=>"q-layout q-layout--"+(e.container===!0?"containerized":"standard")),c=g(()=>e.container===!1?{minHeight:t.screen.height+"px"}:null),w=g(()=>i.value!==0?{[t.lang.rtl===!0?"left":"right"]:`${i.value}px`}:null),d=g(()=>i.value!==0?{[t.lang.rtl===!0?"right":"left"]:0,[t.lang.rtl===!0?"left":"right"]:`-${i.value}px`,width:`calc(100% + ${i.value}px)`}:null);function z(o){if(e.container===!0||document.qScrollPrevented!==!0){const u={position:o.position.top,direction:o.direction,directionChanged:o.directionChanged,inflectionPoint:o.inflectionPoint.top,delta:o.delta.top};v.value=u,e.onScroll!==void 0&&n("scroll",u)}}function U(o){const{height:u,width:b}=o;let f=!1;a.value!==u&&(f=!0,a.value=u,e.onScrollHeight!==void 0&&n("scrollHeight",u),E()),s.value!==b&&(f=!0,s.value=b),f===!0&&e.onResize!==void 0&&n("resize",o)}function G({height:o}){l.value!==o&&(l.value=o,E())}function E(){if(e.container===!0){const o=a.value>l.value?R():0;i.value!==o&&(i.value=o)}}let S=null;const O={instances:{},view:g(()=>e.view),isContainer:g(()=>e.container),rootRef:r,height:a,containerHeight:l,scrollbarWidth:i,totalWidth:g(()=>s.value+i.value),rows:g(()=>{const o=e.view.toLowerCase().split(" ");return{top:o[0].split(""),middle:o[1].split(""),bottom:o[2].split("")}}),header:x({size:0,offset:0,space:!1}),right:x({size:300,offset:0,space:!1}),footer:x({size:0,offset:0,space:!1}),left:x({size:300,offset:0,space:!1}),scroll:v,animate(){S!==null?clearTimeout(S):document.body.classList.add("q-body--layout-animate"),S=setTimeout(()=>{S=null,document.body.classList.remove("q-body--layout-animate")},155)},update(o,u,b){O[o][u]=b}};if(N(D,O),R()>0){let o=function(){f=null,$.classList.remove("hide-scrollbar")},u=function(){if(f===null){if($.scrollHeight>t.screen.height)return;$.classList.add("hide-scrollbar")}else clearTimeout(f);f=setTimeout(o,300)},b=function(Q){f!==null&&Q==="remove"&&(clearTimeout(f),o()),window[`${Q}EventListener`]("resize",u)},f=null;const $=document.body;_(()=>e.container!==!0?"add":"remove",b),e.container!==!0&&b("add"),ne(()=>{b("remove")})}return()=>{const o=oe(p.default,[m(ue,{onScroll:z}),m(j,{onResize:U})]),u=m("div",{class:h.value,style:c.value,ref:e.container===!0?void 0:r,tabindex:-1},o);return e.container===!0?m("div",{class:"q-layout-container overflow-hidden",ref:r},[m(j,{onResize:G}),m("div",{class:"absolute-full",style:w.value},[m("div",{class:"scroll",style:d.value},[u])])]):u}}}),ve={};function he(e,p){const n=le("router-view");return re(),ae(fe,{view:"lHh Lpr lFf"},{default:k(()=>[B(se,null,{default:k(()=>[B(n)]),_:1})]),_:1})}const me=ie(ve,[["render",he]]);export{me as default};
@@ -0,0 +1 @@
1
+ import{d as e,a}from"./index-DKK0oGss.js";const i=a.create({baseURL:"https://api.example.com"}),t=e(({app:o})=>{o.config.globalProperties.$axios=a,o.config.globalProperties.$api=i});export{i as api,t as default};