From 826e5d99bffe03e0f87cdbada8176512b47de4b6 Mon Sep 17 00:00:00 2001 From: Ambrose Chua Date: Fri, 3 Mar 2017 21:09:40 +0800 Subject: [PATCH] Added bootstrap v4.0.0-beta.6 and more scaffolding --- .bootstraprc | 5 + .eslintrc | 2 +- Gruntfile.js | 33 +++- app/components/button.jsx | 23 +++ app/components/layouts/column.jsx | 38 ++++ app/components/layouts/container.jsx | 16 ++ app/components/layouts/row.jsx | 17 ++ app/index.html | 2 + app/index.jsx | 13 +- app/navigation.jsx | 28 +++ app/pages/login.jsx | 17 ++ app/pages/main.jsx | 23 +++ package.json | 5 +- server/api.js | 7 + server/database.js | 256 ++++++++++++++------------- 15 files changed, 355 insertions(+), 130 deletions(-) create mode 100644 .bootstraprc create mode 100644 app/components/button.jsx create mode 100644 app/components/layouts/column.jsx create mode 100644 app/components/layouts/container.jsx create mode 100644 app/components/layouts/row.jsx create mode 100644 app/navigation.jsx create mode 100644 app/pages/login.jsx create mode 100644 app/pages/main.jsx diff --git a/.bootstraprc b/.bootstraprc new file mode 100644 index 0000000..b9ef34d --- /dev/null +++ b/.bootstraprc @@ -0,0 +1,5 @@ +{ + "bootstrapVersion": 4, + "useFlexbox": true, + "preBootstrapCustomizations": "app/assets/_bs-variables.scss" +} diff --git a/.eslintrc b/.eslintrc index a63e357..ffe9416 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1 +1 @@ -{"rules": {"indent": ["error", "tab"], "no-tabs": "off", "react/jsx-indent": ["error", "tab"]}, "extends": "airbnb", "env": {"browser": true, "node": true}} +{"rules": {"indent": ["error", "tab"], "no-tabs": "off", "react/jsx-indent": ["error", "tab"], "react/jsx-indent-props": ["error", "tab"]}, "extends": "airbnb", "env": {"browser": true, "node": true}} diff --git a/Gruntfile.js b/Gruntfile.js index db09d1c..5603698 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -9,6 +9,9 @@ module.exports = function (grunt) { filename: 'bundle.js', path: './dist/app/' }, + resolve: { + extensions: ['.js', '.jsx'], + }, module: { loaders: [ { @@ -38,6 +41,12 @@ module.exports = function (grunt) { cwd: './app/', src: ['assets/**'], dest: './dist/app/assets/' + }, + { + expand: true, + cwd: './node_modules/bootstrap/', + src: ['dist/**'], + dest: './dist/app/assets/bootstrap/' } ] } @@ -58,15 +67,33 @@ module.exports = function (grunt) { { expand: true, cwd: './server/', - src: ['*.js'], + src: ['**/*.js'], dest: './dist/server/' } ] } }, eslint: { - app: ['./app/*.js*'], - server: ['./server/*.js'] + app: { + files: [ + { + expand: true, + cwd: './app/', + src: ['**/*.js*'], + dest: './dist/app/' + } + ] + }, + server: { + files: [ + { + expand: true, + cwd: './server/', + src: ['**/*.js'], + dest: './dist/server/' + } + ] + } } }); diff --git a/app/components/button.jsx b/app/components/button.jsx new file mode 100644 index 0000000..fab9e07 --- /dev/null +++ b/app/components/button.jsx @@ -0,0 +1,23 @@ +import React from 'react'; + +// eslint-disable-next-line react/prefer-stateless-function +export default class Button extends React.Component { + render() { + return ( + + ); + } +} + +Button.propTypes = { + children: React.PropTypes.node.isRequired, + onClick: React.PropTypes.func, + color: React.PropTypes.string, +}; + +Button.defaultProps = { + onClick: () => {}, + color: 'primary', +}; diff --git a/app/components/layouts/column.jsx b/app/components/layouts/column.jsx new file mode 100644 index 0000000..9ba75c7 --- /dev/null +++ b/app/components/layouts/column.jsx @@ -0,0 +1,38 @@ +import React from 'react'; + +// eslint-disable-next-line react/prefer-stateless-function +export default class Column extends React.Component { + constructor() { + super(); + this.className = this.className.bind(this); + } + + className() { + if (this.props.width) { + if (this.props.breakpoint) { + return `col-${this.props.breakpoint}-${this.props.width}`; + } + return `col-${this.props.width}`; + } + return 'col'; + } + + render() { + return ( +
+ { this.props.children } +
+ ); + } +} + +Column.propTypes = { + children: React.PropTypes.node.isRequired, + width: React.PropTypes.number, + breakpoint: React.PropTypes.string, +}; + +Column.defaultProps = { + width: null, + breakpoint: null, +}; diff --git a/app/components/layouts/container.jsx b/app/components/layouts/container.jsx new file mode 100644 index 0000000..9ee0c67 --- /dev/null +++ b/app/components/layouts/container.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +// eslint-disable-next-line react/prefer-stateless-function +export default class Container extends React.Component { + render() { + return ( +
+ { this.props.children } +
+ ); + } +} + +Container.propTypes = { + children: React.PropTypes.node.isRequired, +}; diff --git a/app/components/layouts/row.jsx b/app/components/layouts/row.jsx new file mode 100644 index 0000000..aded209 --- /dev/null +++ b/app/components/layouts/row.jsx @@ -0,0 +1,17 @@ +import React from 'react'; + +// eslint-disable-next-line react/prefer-stateless-function +export default class Row extends React.Component { + render() { + return ( +
+ { this.props.children } +
+ ); + } +} + +Row.propTypes = { + children: React.PropTypes.node.isRequired, +}; + diff --git a/app/index.html b/app/index.html index 0a00260..a2fa9e0 100644 --- a/app/index.html +++ b/app/index.html @@ -1,10 +1,12 @@ Chronos +
+ diff --git a/app/index.jsx b/app/index.jsx index 38ce17c..bfd1769 100644 --- a/app/index.jsx +++ b/app/index.jsx @@ -1,7 +1,18 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { BrowserRouter as Router, Route } from 'react-router-dom'; + +import Navigation from './navigation'; +import PageLogin from './pages/login'; +import PageMain from './pages/main'; ReactDOM.render( -

Hello, world!

, + +
+ + + +
+
, document.getElementById('root'), ); diff --git a/app/navigation.jsx b/app/navigation.jsx new file mode 100644 index 0000000..7b6af0d --- /dev/null +++ b/app/navigation.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +import Container from './components/layouts/container'; +import Button from './components/button'; + +// eslint-disable-next-line react/prefer-stateless-function +export default class Navigation extends React.Component { + render() { + return ( + + ); + } +} diff --git a/app/pages/login.jsx b/app/pages/login.jsx new file mode 100644 index 0000000..2697c23 --- /dev/null +++ b/app/pages/login.jsx @@ -0,0 +1,17 @@ +import React from 'react'; + +import Container from '../components/layouts/container'; +import Button from '../components/button'; + +// eslint-disable-next-line react/prefer-stateless-function +export default class PageLogin extends React.Component { + render() { + return ( + + + + ); + } +} diff --git a/app/pages/main.jsx b/app/pages/main.jsx new file mode 100644 index 0000000..6e40081 --- /dev/null +++ b/app/pages/main.jsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import Container from '../components/layouts/container'; +import Row from '../components/layouts/row'; +import Column from '../components/layouts/column'; + +// eslint-disable-next-line react/prefer-stateless-function +export default class PageMain extends React.Component { + render() { + return ( + + + +

Hello, world!

+
+ +

Hello, world!

+
+
+
+ ); + } +} diff --git a/package.json b/package.json index cd0abb2..9ee9f16 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,12 @@ "webpack": "^2.2.1" }, "dependencies": { + "bootstrap": "^4.0.0-alpha.6", "express": "^4.14.1", "mysql": "^2.13.0", "react": "^15.4.2", - "react-dom": "^15.4.2" + "react-dom": "^15.4.2", + "react-router-dom": "^4.0.0-beta.6", + "semver": "^5.3.0" } } diff --git a/server/api.js b/server/api.js index 74367c7..76123a8 100644 --- a/server/api.js +++ b/server/api.js @@ -9,5 +9,12 @@ export default class API { this.router.get('/', (req, res) => { res.end('API v1'); }); + + this.router.all('/*', this.constructor.auth); + } + + static auth(req, res, next) { + res.end('not implemented'); + next(); } } diff --git a/server/database.js b/server/database.js index a53807a..89d16d0 100644 --- a/server/database.js +++ b/server/database.js @@ -1,4 +1,5 @@ import mysql from 'mysql'; +import semver from 'semver'; import { fatal, getVersion } from './utils'; const DATABASE = 'chronos'; @@ -12,7 +13,7 @@ export default class Database { } query(query, values) { - console.log('QUERY:', query); + console.log('QUERY:', query.replace(/[\n\t]+/g, ' ').replace(/^ /g, '').replace(/ $/g, '')); return new Promise((resolve, reject) => { this.connection.query(query, values, (err, results, fields) => { if (err) { @@ -23,134 +24,141 @@ export default class Database { }); } - checkAndMigrate() { - this.query(`CREATE DATABASE IF NOT EXISTS ${DATABASE}`, []) - .then(() => this.query(`USE ${DATABASE}`, [])) - .then(() => this.query('SELECT value FROM options WHERE option = "version"', (err, result) => { - if (err || result === undefined || getVersion() !== result[0].value) { - return this.migrate(result).catch(e => fatal(e)); - } - return true; - })) + async checkAndMigrate() { + return this.query(`CREATE DATABASE IF NOT EXISTS ${DATABASE}`) + .then(() => this.query(`USE ${DATABASE}`)) + .then(() => this.query('SELECT * FROM options WHERE option = ?', ['VERSION']) + .then(result => this.migrate(result[0] ? result[0].value : null)) + .catch(() => this.migrate())) .catch(err => fatal(err)); } async migrate(oldVersion) { - if (!oldVersion) { - // create tables - const tables = [ - `CREATE TABLE options ( - option VARCHAR(12) NOT NULL, - value VARCHAR(64) NOT NULL, - PRIMARY KEY (option) - )`, - `CREATE TABLE school ( - id INT NOT NULL AUTO_INCREMENT, - name VARCHAR(64) NOT NULL, - domain VARCHAR(32), - PRIMARY KEY (id) - )`, - `CREATE TABLE auth ( - school INT NOT NULL, - id INT NOT NULL, - type CHAR(3) NOT NULL, - oid_meta VARCHAR(128), - oid_cid VARCHAR(64), - oid_csecret VARCHAR(64), - PRIMARY KEY (school, id), - FOREIGN KEY (school) REFERENCES school(id) ON DELETE CASCADE ON UPDATE CASCADE - )`, - `CREATE TABLE holiday ( - id INT NOT NULL AUTO_INCREMENT, - name VARCHAR(64) NOT NULL, - start DATETIME NOT NULL, - end DATETIME NOT NULL, - PRIMARY KEY (id) - )`, - `CREATE TABLE observes ( - school INT NOT NULL, - holiday INT NOT NULL, - FOREIGN KEY (school) REFERENCES school(id) ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (holiday) REFERENCES holiday(id) ON DELETE CASCADE ON UPDATE CASCADE - )`, - `CREATE TABLE user ( - school INT NOT NULL, - id INT AUTO_INCREMENT NOT NULL, - name VARCHAR(64), - email VARCHAR(64), - oid_id VARCHAR(64), - pwd_hash VARCHAR(64), - role CHAR(3), - PRIMARY KEY (id), - FOREIGN KEY (school) REFERENCES school(id) ON DELETE CASCADE ON UPDATE CASCADE - )`, - `CREATE TABLE group_ ( - id INT AUTO_INCREMENT NOT NULL, - name VARCHAR(64) NOT NULL, - type CHAR(3), - PRIMARY KEY (id) - )`, - `CREATE TABLE group_mentor ( - id INT NOT NULL, - level TINYINT NOT NULL, - year YEAR NOT NULL, - PRIMARY KEY (id), - FOREIGN KEY (id) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE - )`, - `CREATE TABLE member ( - user INT NOT NULL, - group_ INT NOT NULL, - FOREIGN KEY (user) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (group_) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE - )`, - `CREATE TABLE event_once ( - group_ INT NOT NULL, - id INT AUTO_INCREMENT NOT NULL, - name VARCHAR(64) NOT NULL, - start DATETIME NOT NULL, - end DATETIME NOT NULL, - PRIMARY KEY (id), - FOREIGN KEY (group_) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE - )`, - `CREATE TABLE attachment ( - event_once INT NOT NULL, - id INT NOT NULL, - PRIMARY KEY (event_once, id), - FOREIGN KEY (event_once) REFERENCES event_once(id) ON DELETE CASCADE ON UPDATE CASCADE - )`, - `CREATE TABLE event_weekly ( - group_ INT NOT NULL, - id INT AUTO_INCREMENT NOT NULL, - name VARCHAR(64) NOT NULL, - day TINYINT NOT NULL, - starttime TIME NOT NULL, - endtime TIME NOT NULL, - PRIMARY KEY (id), - FOREIGN KEY (group_) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE - )`, - `CREATE TABLE ignored ( - user INT NOT NULL, - event_weekly INT NOT NULL, - FOREIGN KEY (user) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (event_weekly) REFERENCES event_weekly(id) ON DELETE CASCADE ON UPDATE CASCADE - )`, - ]; - - for (let i = 0; i < tables.length; i += 1) { - // eslint-disable-next-line no-await-in-loop - await this.query(tables[i], []); - } - - await this.query(` - INSERT INTO options (option, value) - VALUES ('version', '${getVersion()}') - `); - - + if (semver.satisfies(oldVersion, '~1')) { + // database is up-to-date return true; + } else if (semver.satisfies(oldVersion, '~0')) { // lmao forces database migration + // database needs to be updated + return this.query(`DROP DATABASE ${DATABASE}`) + .then(() => this.checkAndMigrate()); } - // for now - return this.query(`DROP DATABASE ${DATABASE}`, []) - .then(() => this.checkAndMigrate()); + // database does not exist + + // create tables + const tables = [ + `CREATE TABLE options ( + option VARCHAR(12) NOT NULL, + value VARCHAR(64) NOT NULL, + PRIMARY KEY (option) + )`, + `CREATE TABLE school ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(64) NOT NULL, + domain VARCHAR(32), + PRIMARY KEY (id) + )`, + `CREATE TABLE auth ( + school INT NOT NULL, + id INT NOT NULL, + type CHAR(3) NOT NULL, + oid_meta VARCHAR(128), + oid_cid VARCHAR(64), + oid_csecret VARCHAR(64), + PRIMARY KEY (school, id), + FOREIGN KEY (school) REFERENCES school(id) ON DELETE CASCADE ON UPDATE CASCADE + )`, + `CREATE TABLE holiday ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(64) NOT NULL, + start DATETIME NOT NULL, + end DATETIME NOT NULL, + PRIMARY KEY (id) + )`, + `CREATE TABLE observes ( + school INT NOT NULL, + holiday INT NOT NULL, + FOREIGN KEY (school) REFERENCES school(id) ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (holiday) REFERENCES holiday(id) ON DELETE CASCADE ON UPDATE CASCADE + )`, + `CREATE TABLE user ( + school INT NOT NULL, + id INT AUTO_INCREMENT NOT NULL, + name VARCHAR(64), + email VARCHAR(64), + oid_id VARCHAR(64), + pwd_hash VARCHAR(64), + role CHAR(3), + PRIMARY KEY (id), + FOREIGN KEY (school) REFERENCES school(id) ON DELETE CASCADE ON UPDATE CASCADE + )`, + `CREATE TABLE group_ ( + id INT AUTO_INCREMENT NOT NULL, + name VARCHAR(64) NOT NULL, + type CHAR(3), + PRIMARY KEY (id) + )`, + `CREATE TABLE group_mentor ( + id INT NOT NULL, + level TINYINT NOT NULL, + year YEAR NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (id) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE + )`, + `CREATE TABLE member ( + user INT NOT NULL, + group_ INT NOT NULL, + FOREIGN KEY (user) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (group_) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE + )`, + `CREATE TABLE event_once ( + group_ INT NOT NULL, + id INT AUTO_INCREMENT NOT NULL, + name VARCHAR(64) NOT NULL, + start DATETIME NOT NULL, + end DATETIME NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (group_) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE + )`, + `CREATE TABLE attachment ( + event_once INT NOT NULL, + id INT NOT NULL, + PRIMARY KEY (event_once, id), + FOREIGN KEY (event_once) REFERENCES event_once(id) ON DELETE CASCADE ON UPDATE CASCADE + )`, + `CREATE TABLE event_weekly ( + group_ INT NOT NULL, + id INT AUTO_INCREMENT NOT NULL, + name VARCHAR(64) NOT NULL, + day TINYINT NOT NULL, + starttime TIME NOT NULL, + endtime TIME NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (group_) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE + )`, + `CREATE TABLE ignored ( + user INT NOT NULL, + event_weekly INT NOT NULL, + FOREIGN KEY (user) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (event_weekly) REFERENCES event_weekly(id) ON DELETE CASCADE ON UPDATE CASCADE + )`, + ]; + for (let i = 0; i < tables.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + await this.query(tables[i]); + } + + // set version number + await this.query(` + INSERT INTO options (option, value) + VALUES ('version', '${getVersion()}') + `); + + // add first school + await this.query(` + INSERT INTO school (name, domain) + VALUES (?, ?) + `, ['NUS High School', 'nushigh.edu.sg']); + + return true; } }