diff --git a/src/server.js b/src/server.js index 12dbf28..05ae9b1 100644 --- a/src/server.js +++ b/src/server.js @@ -119,7 +119,7 @@ app.get('*', async (req, res, next) => { }, // Sending Material-UI theme through context muiTheme, - //send apollo client in the context + // send apollo client in the context }; diff --git a/tools/port_script/README.md b/tools/port_script/README.md new file mode 100644 index 0000000..5ca9665 --- /dev/null +++ b/tools/port_script/README.md @@ -0,0 +1,200 @@ +# Portal +*Port your MySQL data from old schema to the new schema with just a +configuration file.* + + +## How to +- Create a `config.js` file which exports an object with rules and database +config. +- `npm install` +- `node index.js` + + + +## Configuration +`config.js` must export an object with `host`, `user`, `password`, `db` and +`rules`. See below for details. + +### host [String] +- Host for database + +### user [String] +- User for database + +### password [String] +- Password for the database + +### db [Object] +- Must contain `source` and `destination` pairs with database names + + +--- +### rules [Array] +- Contains the rules for porting +- One rule for each target table +- Example: + ```javascript + rules: [ {}, {}, ... , {} ] + ``` + +#### rules[i].table [Object] +- Object with info about target and source tables +- Must contain: `old` and `new` objects + - `new`: String + - `old`: String or Object with `union` and `on` pairs +- See [old](#rulesitablesold-string-or-object) below for details +- Example: + ```javascript + table: { + new: 'NEW_TABLE', + old: 'OLD_TABLE' + } + ``` + +#### rules[i].fields [Object] +- Object with `target_attribute`s as keys and `source_attribute`s or other rules +as values +- See [fields](#rulesifields-object) below for details +- Example: + ```javascript + fields: { + user: 'login', + password: 'pass' + } + ``` + This SELECTs `login` and `pass` fields from old table and puts in `user` and + `pass` of the new table, respectively + +#### rules[i].query [String] +- A MySQL query to run instead of target and source pairs +- If found, `table` and `fields` objects are ignored +- Example: + ```javascript + query: 'INSERT INTO table (user, password) VALUES ("bits", "goa_ftw!");' + ``` + Runs the query as it is + +#### rules[i].skip [Boolean] +- If `true`, the rule is skipped +- Example: + ```javascript + rules: [ { skip: true, ... }, {}, {} ] + ``` + This rule is skipped regardless of anything else + + + +--- +### rules[i].tables.old [String] or [Object] +- Must be either a string with table name +- Or an object with `union` and `on` + - `union` must be an array with table names + - `on` must be a MySQL condition +- Example: + ```javascript + old: { + union: ['t1', 't2', 't3'], + on: 't1.id = t2.id AND t2.id = t3.id' + } + ``` + Inner joins the tables with common `id` + + + +--- +### rules[i].fields [Object] + +#### fields[0].if [Object] +- Evaluates an if condition and enters data accordingly +- Must contain `condition`, `pass` and `fail` +- See [condition](#ifcondition) below for details +- Example: + ```javascript + if: { + condition: { + eval: {} + }, + pass: 'login', + fail: { + value: 'some other' + } + } + ``` + Checks if `login` attribute of old table is equal to "bits". If yes, puts + `login` of old table in the new table else puts a string "some other" + +#### fields[0].switch [Object] +- Evaluates switch condition and enters data accordingly +- Must contain `condition`, `cases` +- `cases` must be an object of key-value pairs +- Example: + ```javascript + switch: { + condition: 'login_type', + cases: [ + { type1: { value: '1' } }, + { type2: { value: '2' } }, + { other: 'login_type' } + ] + } + ``` + If `login_type` equals "type1" put a string "1" or if "type2" put a string + "2" or if "other" put the value of `login_type` attribute of old table + +#### fields[0].value [String] +- Any field with `value` will be put as value itself rather than getting it +from the source table +- Example: + ```javascript + user: { value: 'some value' } + ``` + Puts the string "some value" in `user` attribute of the new table + + + +--- +### if.condition +#### rules[i].fields[j].if.condition [Object] +- Must contain `eval` or `or` or `and` +- `or` or `and` must be an array of objects which must contain `eval` +- See [eval](#ifconditioneval) below for details +- Example: + ```javascript + condition: { + or: [ + { eval: {C1} }, + { eval: {C2} }, + { + and: [ + { eval: {C3} }, + { eval: {C4} } + ] + } + ] + } + ``` + Is equivalent to `C1 || C2 || (C3 && C4)` + + + +--- +### if.condition.eval +#### rules[i].fields[j].switch.condition.eval [Object] +- Must contain `operator` and (`first_attribute` or `first_value`) and +(`second_attribute` or `second_value`) + - `operator` must be a constant string + - `X_attribute` must be an attribute of the table + - `X_value` must be a constant string +- Example: + ```javascript + condition: { + eval: + operator: '===', + first_attribute: 'login', + second_value: 'bits' + } + pass: { value: '1' }, + fail: { value: '0' } + }, + ``` + Put "1" or "0" if `login` of old table is `===` to `bits` of new table diff --git a/tools/port_script/config.js b/tools/port_script/config.js new file mode 100644 index 0000000..d16583d --- /dev/null +++ b/tools/port_script/config.js @@ -0,0 +1,244 @@ +const login = { + student: 1, + faculty: 2, + staff: 3, +}; + +// Configuration for the database +module.exports = { + host: 'localhost', + user: 'swd', + password: 'swd_base', + db: { + source: 'db_swd', + destination: 'new_db_swd', + }, + rules: [ + + // loginType + { + query: `INSERT INTO loginType (id, type) VALUES \ + (${login.student}, "student"), \ + (${login.faculty}, "faculty"), \ + (${login.staff}, "staff");`, + }, + + // loginId + { + skip: true, + table: { + old: 'login_ids', + new: 'loginId', + }, + fields: { + loginId: 'login_id', + type: { + switch: { + condition: 'type', + cases: { + student: login.student, + faculty: login.faculty, + warden: login.faculty, + staff: login.staff, + security: login.staff, + superintend: login.staff, + }, + }, + }, + passHash: { value: 'PASSWORD HASH HERE' }, + passSalt: { value: 'PASSWORD SALT HERE' }, + }, + }, + + // student + { + table: { + old: { + union: ['student_info', 'cgpa'], + on: 'student_info.login_id = cgpa.login_id', + }, + new: 'student', + }, + fields: { + id: 'login_id', + studentName: 'student_name', + bitsId: 'id', + hostelPs: { + if: { + condition: { + or: [ + { eval: { operator: '===', first_attribute: 'hostel', second_value: 'PS2' } }, + { eval: { operator: '===', first_attribute: 'hostel', second_value: 'Thesis' } }, + ], + }, + pass: { value: 1 }, + fail: { value: 0 }, + }, + }, + gender: 'gender', + bDay: 'bday', + phone: 'phone', + email: 'email', + address: 'address', + bloodGroup: 'bloodgroup', + cgpa: 'cgpa', + admit: 'admit', + parentName: 'father_name', + parentPhone: 'father_phone', + parentEmail: 'father_email', + }, + }, + + // hostel + { + table: { + old: { + table: 'student_info', + // TODO: find out what has to be done with the rest of the values + where: 'hostel != "Thesis" \ + && hostel != "" \ + && hostel != "Qtrs" \ + && hostel != "Graduate" \ + && hostel != "PS2" \ + && hostel != "Temp Withd" \ + && hostel != "DAY SCHOLAR"', + }, + new: 'hostel', + }, + fields: { + id: 'login_id', + hostel: 'hostel', + room: 'hostel_room', + }, + }, + + // bonafide + { + table: { + old: 'bonafide', + new: 'bonafide', + }, + fields: { + id: 'index_', + studentId: 'login_id', + reqDate: 'req_date', + reason: 'reason', + otherReason: 'other_reason', + year: 'year', + printed: { value: '0' }, + }, + }, + + // day scholars + { + table: { + new: 'dayScholar', + old: 'day_scholars', + }, + fields: { + studentId: 'login_id', + }, + }, + + // day pass + { + table: { + new: 'dayPass', + old: 'daypass', + }, + fields: { + id: 'daypass_id', + studentId: 'login_id', + date: 'Date', + reason: 'Reason', + consentType: 'consent_type', + approvedBy: 'ApprovedBy', + comment: 'Comment', + }, + }, + + // csa + { + table: { + new: 'csa', + old: { + union: ['csa', 'student_info'], + on: 'student_info.student_name = csa.name', + }, + }, + fields: { + studentId: 'login_id', + title: 'title', + csaEmail: 'email', + }, + }, + + // disco + { + skip: true, + table: { + new: 'disco', + old: 'discp', + }, + fields: { + studentId: 'login_id', + dateOfViolation: 'date', + subject: 'heading', + action: 'action', + }, + }, + + // faculty + { + table: { + new: 'faculty', + old: 'wardens', + }, + fields: { + loginId: 'login_id', + name: 'name', + chamber: 'chamber', + office: 'office', + phone: { value: '' }, + email: { value: '' }, + }, + }, + + // facultyIncharge + { + table: { + new: 'facultyIncharge', + old: 'faculty_incharge', + }, + fields: { + facultyId: 'login_id', + function: 'function', + }, + }, + + // holiday + { + table: { + new: 'holiday', + old: 'holidays', + }, + fields: { + date: 'HolDate', + institute: '0', + name: { value: 'unknown' }, + }, + }, + { + table: { + new: 'holiday', + old: 'institure_holidays', + }, + fields: { + date: 'date', + institute: '1', + name: { value: 'unknown' }, + }, + }, + + ], +}; diff --git a/tools/port_script/index.js b/tools/port_script/index.js new file mode 100644 index 0000000..d07c6bc --- /dev/null +++ b/tools/port_script/index.js @@ -0,0 +1,76 @@ +/* eslint-disable no-unused-expressions */ +/* eslint-disable no-param-reassign */ + +const mysql = require('mysql'); +const _ = require('lodash'); +const config = require('./config.js'); +const util = require('./util.js'); + +const rules = config.rules; +const DEBUG = 1; + +// Connect to the source database +const source = mysql.createConnection({ + host: config.host, + user: config.user, + password: config.password, + database: config.db.source, +}); +source.connect((err) => { + if (err) { throw err; } + console.log('Connected to source'); +}); + +// Connect to the destination +const dest = mysql.createConnection({ + host: config.host, + user: config.user, + password: config.password, + database: config.db.destination, +}); +dest.connect((err) => { + if (err) { throw err; } + console.log('Connected to destination'); +}); + + +// Start execution +_.forEach(rules, (rule) => { + if (rule.skip) { + console.log(`Skipping ${rule.query || rule.table.new}`); + } else if (rule.query) { + // if a rule is just a query, execute it + DEBUG && console.log(`${rule.query}\n`); + DEBUG || dest.query(rule.query, (error) => { + if (error) { throw error; } else { console.log(rule.query); } + }); + } else { + // Get data from the source table + DEBUG && (rule.table.old.limit = 5); + source.query(util.getSelectQuery(rule.table.old), (err, tuples) => { + if (err) { throw err; } + console.log(`\nCopying table ${rule.table.new}\n`); + + _.forEach(tuples, (tuple) => { + // Evaluate attribute for values + const values = []; + _.forEach(rule.fields, (field) => { + values.push(util.evalExp(field, tuple)); + }); + + // Build the query + const query = `INSERT INTO ${rule.table.new} \ + (${_.join(_.keys(rule.fields), ', ')}) VALUES \ + ("${_.join(values, '", "')}");`; + + // Run the query + DEBUG && console.log(`${query}\n`); + DEBUG || dest.query(query, (error) => { + if (error) { throw error; } else { + process.stdout.write('.'); + } + }); + }); + }); + } +}); diff --git a/tools/port_script/package.json b/tools/port_script/package.json new file mode 100644 index 0000000..5712d91 --- /dev/null +++ b/tools/port_script/package.json @@ -0,0 +1,24 @@ +{ + "name": "port_script", + "version": "0.0.1", + "description": "Port the swd database", + "main": "index.js", + "scripts": { + "start": "node .", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://gist.github.com/3677845cf74d5018c7706701bb0a851c.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://gist.github.com/3677845cf74d5018c7706701bb0a851c" + }, + "homepage": "https://gist.github.com/3677845cf74d5018c7706701bb0a851c", + "dependencies": { + "lodash": "^4.17.4", + "mysql": "^2.14.1" + } +} diff --git a/tools/port_script/util.js b/tools/port_script/util.js new file mode 100644 index 0000000..6d0fb4b --- /dev/null +++ b/tools/port_script/util.js @@ -0,0 +1,114 @@ +const _ = require('lodash'); + +/** + * Function to evaluate conditions recursively + * @param condition Object The condition to evaluate + * @param tuple Object The tuple so that conditions can be evaluated + * @return boolean Evaluated result + */ +function conditionEval(condition, tuple) { + if (condition.eval) { + const first = tuple[condition.eval.first_attribute] || condition.eval.first_value || null; + const second = tuple[condition.eval.second_attribute] || condition.eval.second_value || null; + + switch (condition.eval.operator) { + case '==': + case '===': + if (first === second) { + return true; + } + return false; + default: + throw new Error('Operator not supported'); + } + } else if (condition.or) { + return _.some(condition.or, obj => conditionEval(obj, tuple)); + } else if (condition.and) { + return _.every(condition.and, obj => conditionEval(obj, tuple)); + } else { + throw new Error('Could not find eval or a condition'); + } +} + +/** + * Evaluate if condition + * @param block Object The whole if block + * @param tuple Object The tuple so that conditions can be evaluated + * @return boolean Evaluated result + */ +function evalIf(block, tuple) { + if (conditionEval(block.condition, tuple)) { + return block.pass.value; + } + return block.fail.value; +} + +/** + * Evaluate switch condition. + * @param block Object The whole switch block + * @param tuple Object The tuple so that conditions can be evaluated + * @return boolean Evaluated result + */ +function evalSwitch(block, tuple) { + return block.cases[tuple[block.condition]]; +} + +/** + * Evaluate expression recursively + * @param field Object The whole attribute from the config file + * @param tuple Object The tuple so that conditions can be evaluated + * @return boolean Evaluated result + */ +function evalExp(field, tuple) { + if (field.if) { + // evaluate if block + return evalIf(field.if, tuple); + } else if (field.switch) { + // send the value as it is + return evalSwitch(field.switch, tuple); + } else if (field.value) { + // send the value as it is + return field.value; + } + // evaluate a value + return tuple[field]; +} + +/** + * Generate and return SELECT query from the config + * @param block Object The block containing the query config + * @return String The generated SELECT query + */ +function getSelectQuery(block) { + let table; + let where = ''; + let limit = ''; + + // Get table + if (block.union) { + table = `${block.union[0]} INNER JOIN \ + (${_.join(_.drop(block.union, 1), ',')}) ON (${block.on})`; + } else if (block.table) { + table = block.table; + } else { + table = block; + } + + // Get where clause + if (block.where) { + where = `WHERE ${block.where}`; + } + + // Get limit clause + if (block.limit) { + limit = `LIMIT ${block.limit}`; + } + + return `SELECT * FROM ${table} ${where} ${limit};`; +} + +// Export the modules +module.exports = { + evalExp, + getSelectQuery, +};