diff --git a/.gitignore b/.gitignore index a2912ec..e2b00f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ static/assets/diagrams/ + node_modules/ + +config/*.json +!config/*_sample* + +*.swp diff --git a/config/db_sample.json b/config/db_sample.json new file mode 100644 index 0000000..ff8292e --- /dev/null +++ b/config/db_sample.json @@ -0,0 +1,7 @@ +{ + "host": "hostname", + "port": 1111, + "user": "username", + "password": "password", + "database": "dbname" +} diff --git a/package-lock.json b/package-lock.json index 43f21fc..449a89b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,234 @@ "": { "name": "stratos", "version": "1.0.0", - "license": "MIT" + "license": "MIT", + "dependencies": { + "mysql2": "^2.3.3" + } + }, + "node_modules/denque": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mysql2": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", + "integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==", + "dependencies": { + "denque": "^2.0.1", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^4.0.0", + "lru-cache": "^6.0.0", + "named-placeholders": "^1.1.2", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz", + "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", + "dependencies": { + "lru-cache": "^4.1.3" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/named-placeholders/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" + }, + "node_modules/sqlstring": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", + "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + }, + "dependencies": { + "denque": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "requires": { + "is-property": "^1.0.2" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "mysql2": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", + "integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==", + "requires": { + "denque": "^2.0.1", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^4.0.0", + "lru-cache": "^6.0.0", + "named-placeholders": "^1.1.2", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + } + }, + "named-placeholders": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz", + "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", + "requires": { + "lru-cache": "^4.1.3" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" + }, + "sqlstring": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", + "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index e6534a0..dd0c298 100644 --- a/package.json +++ b/package.json @@ -17,5 +17,6 @@ }, "homepage": "https://github.com/matt-fidd/stratos#readme", "dependencies": { + "mysql2": "^2.3.3" } } diff --git a/utility/db/dbInit.js b/utility/db/dbInit.js new file mode 100644 index 0000000..5155204 --- /dev/null +++ b/utility/db/dbInit.js @@ -0,0 +1,331 @@ +'use strict'; + +// Import required modules +const mysql = require('mysql2/promise'); +const path = require('path'); + +// Initialise maps for table creation and constraint queries +const tableCreate = new Map(); +const tableConstraints = new Map(); + +// For each table, set tableCreate.tableName equal to the creation statment +// for that table + +tableCreate.set('account', ` + CREATE TABLE IF NOT EXISTS account ( + accountId varchar(36) NOT NULL PRIMARY KEY, + email varchar(255) NOT NULL , + firstName varchar(50) NOT NULL , + otherNames varchar(255) NOT NULL , + lastName varchar(50) NOT NULL , + password varchar(60) NOT NULL , + CONSTRAINT Unq_account_email UNIQUE ( email ) + ); +`); + +tableCreate.set('parent', ` + CREATE TABLE IF NOT EXISTS parent ( + parentId varchar(36) NOT NULL PRIMARY KEY, + email varchar(255) NOT NULL , + firstName varchar(50) NOT NULL , + otherNames varchar(50) NOT NULL , + lastName varchar(50) NOT NULL , + password varchar(60) NOT NULL , + CONSTRAINT Unq_parent UNIQUE ( email ) + ); +`); + +tableCreate.set('student', ` + CREATE TABLE IF NOT EXISTS student ( + studentId varchar(36) NOT NULL PRIMARY KEY, + email varchar(255) NOT NULL , + firstName varchar(50) NOT NULL , + otherNames varchar(50) NOT NULL , + lastName varchar(50) NOT NULL , + password varchar(60) NOT NULL , + CONSTRAINT Unq_student UNIQUE ( email ) + ); +`); + +tableCreate.set('studentParentLink', ` + CREATE TABLE IF NOT EXISTS studentParentLink ( + studentid varchar(36) NOT NULL , + parentId varchar(36) NOT NULL , + CONSTRAINT pk_studentparentlink_studentid + PRIMARY KEY ( studentid, parentId ) + ); +`); + +tableCreate.set('subject', ` + CREATE TABLE IF NOT EXISTS subject ( + subjectId int NOT NULL AUTO_INCREMENT PRIMARY KEY, + name varchar(100) NOT NULL + ); +`); + +tableCreate.set('testTemplate', ` + CREATE TABLE IF NOT EXISTS testTemplate ( + testTemplateId varchar(36) NOT NULL PRIMARY KEY, + accountId varchar(36) NOT NULL , + name varchar(100) NOT NULL , + maxMark int NOT NULL + ); +`); + +tableCreate.set('class', ` + CREATE TABLE IF NOT EXISTS class ( + classId varchar(36) NOT NULL PRIMARY KEY, + name varchar(50) NOT NULL , + subjectId int NOT NULL + ); +`); + +tableCreate.set('passwordReset', ` + CREATE TABLE IF NOT EXISTS passwordReset ( + accountId varchar(36) NOT NULL PRIMARY KEY, + token varchar(60) NOT NULL , + nonce varchar(16) NOT NULL , + expires datetime NOT NULL + ); +`); + +tableCreate.set('studentClassLink', ` + CREATE TABLE IF NOT EXISTS studentClassLink ( + studentId varchar(36) NOT NULL , + classId varchar(36) NOT NULL , + CONSTRAINT Pk_studentClassLink_studentId + PRIMARY KEY ( studentId, classId ) + ); +`); + +tableCreate.set('test', ` + CREATE TABLE IF NOT EXISTS test ( + testId varchar(36) NOT NULL PRIMARY KEY, + testTemplateId varchar(36) NOT NULL , + classId varchar(36) NOT NULL , + testDate date NOT NULL , + CONSTRAINT Unq_test UNIQUE ( testTemplateId, classId, testDate ) + ); +`); + +tableCreate.set('testResult', ` + CREATE TABLE IF NOT EXISTS testResult ( + studentId varchar(36) NOT NULL , + testId varchar(36) NOT NULL , + accountId varchar(36) NOT NULL , + mark int NOT NULL , + CONSTRAINT primarykey PRIMARY KEY ( studentId, testId ) + ); +`); + +tableCreate.set('accountClassLink', ` + CREATE TABLE IF NOT EXISTS accountClassLink ( + accountId varchar(36) NOT NULL , + classId varchar(36) NOT NULL , + CONSTRAINT primarykey PRIMARY KEY ( accountId, classId ) + ); +`); + +// For each table constraint, set tableConstraints.constraitName equal to the +// creation statment for that constraint +tableConstraints.set('accountClassLink_fk0', ` + ALTER TABLE accountClassLink + ADD CONSTRAINT accountClassLink_fk0 + FOREIGN KEY IF NOT EXISTS ( accountId ) + REFERENCES account( accountId ) + ON DELETE CASCADE + ON UPDATE NO ACTION; +`); + +tableConstraints.set('accountClassLink_fk1', ` + ALTER TABLE accountClassLink + ADD CONSTRAINT accountClassLink_fk1 + FOREIGN KEY IF NOT EXISTS ( classId ) + REFERENCES class( classId ) + ON DELETE CASCADE + ON UPDATE NO ACTION; +`); + +tableConstraints.set('class_fk0', ` + ALTER TABLE class + ADD CONSTRAINT class_fk0 + FOREIGN KEY IF NOT EXISTS ( subjectId ) + REFERENCES subject( subjectId ) + ON DELETE RESTRICT + ON UPDATE NO ACTION; +`); + +tableConstraints.set('fk_passwordreset_account', ` + ALTER TABLE passwordReset + ADD CONSTRAINT fk_passwordreset_account + FOREIGN KEY IF NOT EXISTS ( accountId ) + REFERENCES account( accountId ) + ON DELETE CASCADE + ON UPDATE NO ACTION; +`); + +tableConstraints.set('fk_passwordreset_parent', ` + ALTER TABLE passwordReset + ADD CONSTRAINT fk_passwordreset_parent + FOREIGN KEY IF NOT EXISTS ( accountId ) + REFERENCES parent( parentId ) + ON DELETE CASCADE + ON UPDATE NO ACTION; +`); + +tableConstraints.set('fk_passwordreset_student', ` + ALTER TABLE passwordReset + ADD CONSTRAINT fk_passwordreset_student + FOREIGN KEY IF NOT EXISTS ( accountId ) + REFERENCES student( studentId ) + ON DELETE CASCADE + ON UPDATE NO ACTION; +`); + +tableConstraints.set('fk_studentclasslink_student', ` + ALTER TABLE studentClassLink + ADD CONSTRAINT fk_studentclasslink_student + FOREIGN KEY IF NOT EXISTS ( studentId ) + REFERENCES student( studentId ) + ON DELETE CASCADE + ON UPDATE NO ACTION; +`); + +tableConstraints.set('fk_studentclasslink_class', ` + ALTER TABLE studentClassLink + ADD CONSTRAINT fk_studentclasslink_class + FOREIGN KEY IF NOT EXISTS ( classId ) + REFERENCES class( classId ) + ON DELETE CASCADE + ON UPDATE NO ACTION; +`); + +tableConstraints.set('fk_studentclasslink_class', ` + ALTER TABLE studentParentLink + ADD CONSTRAINT studentParentLink_fk0 + FOREIGN KEY IF NOT EXISTS ( studentid ) + REFERENCES student( studentId ) + ON DELETE CASCADE + ON UPDATE NO ACTION; +`); + +tableConstraints.set('studentParentLink_fk1', ` + ALTER TABLE studentParentLink + ADD CONSTRAINT studentParentLink_fk1 + FOREIGN KEY IF NOT EXISTS ( parentId ) + REFERENCES parent( parentId ) + ON DELETE CASCADE + ON UPDATE NO ACTION; +`); + +tableConstraints.set('fk_test_account', ` + ALTER TABLE test + ADD CONSTRAINT fk_test_account + FOREIGN KEY IF NOT EXISTS ( classId ) + REFERENCES class( classId ) + ON DELETE RESTRICT + ON UPDATE NO ACTION; +`); + +tableConstraints.set('fk_test_testtemplate', ` + ALTER TABLE test + ADD CONSTRAINT fk_test_testtemplate + FOREIGN KEY IF NOT EXISTS ( testTemplateId ) + REFERENCES testTemplate( testTemplateId ) + ON DELETE NO ACTION + ON UPDATE NO ACTION; +`); + +tableConstraints.set('testResult_fk0', ` + ALTER TABLE testResult + ADD CONSTRAINT testResult_fk0 + FOREIGN KEY IF NOT EXISTS ( studentId ) + REFERENCES student( studentId ) + ON DELETE CASCADE + ON UPDATE NO ACTION; +`); + +tableConstraints.set('fk_testresult_test', ` + ALTER TABLE testResult + ADD CONSTRAINT fk_testresult_test + FOREIGN KEY IF NOT EXISTS ( testId ) + REFERENCES test( testId ) + ON DELETE CASCADE + ON UPDATE NO ACTION; +`); + +tableConstraints.set('fk_testresult_account', ` + ALTER TABLE testResult + ADD CONSTRAINT fk_testresult_account + FOREIGN KEY IF NOT EXISTS ( accountId ) + REFERENCES account( accountId ) + ON DELETE NO ACTION + ON UPDATE NO ACTION; +`); + +tableConstraints.set('test_fk0', ` + ALTER TABLE testTemplate + ADD CONSTRAINT test_fk0 + FOREIGN KEY IF NOT EXISTS ( accountId ) + REFERENCES account( accountId ) + ON DELETE NO ACTION + ON UPDATE NO ACTION; +`); + +async function initialise(dbOptions) { + /* + Initialises a database using the options object provided + and applies schema to it + + Arguments: + - database options object loaded in from + config/db.json + */ + + // Connect to the database + const c = await mysql.createConnection(dbOptions); + + // Run the creation statment for each table + for (const [ tableName, sql ] of tableCreate) { + console.log(`Creating table ${tableName}`); + + try { + await c.execute(sql); + } catch (e) { + console.error(e); + throw new Error(`Unable to create table ${tableName}`); + } + } + + console.log('\n'); + + // Run the creation statment for each constraint + for (const [ fkName, sql ] of tableConstraints) { + console.log(`Creating constraint ${fkName}`); + + try { + await c.execute(sql); + } catch (e) { + console.error(e); + throw new Error('Unable to create constraint ' + + `${fkName}`); + } + } + + // Drop the database connection + c.end(); + + console.log('\nFinished initialising database'); +} + +// Import data from config/db.json +let dbOptions; +try { + dbOptions = require(path.join(__dirname, '../../config/db.json')); + console.log('DB config loaded\n'); +} catch { + return console.error('Missing or malformed config ' + + '(config/db.json)'); +} + +initialise(dbOptions);