Added bootstrap v4.0.0-beta.6 and more scaffolding
parent
0559058dc2
commit
826e5d99bf
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"bootstrapVersion": 4,
|
||||
"useFlexbox": true,
|
||||
"preBootstrapCustomizations": "app/assets/_bs-variables.scss"
|
||||
}
|
|
@ -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}}
|
||||
|
|
33
Gruntfile.js
33
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/'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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 className={`btn btn-${this.props.color}`} onClick={this.props.onClick}>
|
||||
{ this.props.children }
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Button.propTypes = {
|
||||
children: React.PropTypes.node.isRequired,
|
||||
onClick: React.PropTypes.func,
|
||||
color: React.PropTypes.string,
|
||||
};
|
||||
|
||||
Button.defaultProps = {
|
||||
onClick: () => {},
|
||||
color: 'primary',
|
||||
};
|
|
@ -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 (
|
||||
<div className={this.className()}>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Column.propTypes = {
|
||||
children: React.PropTypes.node.isRequired,
|
||||
width: React.PropTypes.number,
|
||||
breakpoint: React.PropTypes.string,
|
||||
};
|
||||
|
||||
Column.defaultProps = {
|
||||
width: null,
|
||||
breakpoint: null,
|
||||
};
|
|
@ -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 (
|
||||
<div className="container">
|
||||
{ this.props.children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Container.propTypes = {
|
||||
children: React.PropTypes.node.isRequired,
|
||||
};
|
|
@ -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 (
|
||||
<div className="row">
|
||||
{ this.props.children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Row.propTypes = {
|
||||
children: React.PropTypes.node.isRequired,
|
||||
};
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Chronos</title>
|
||||
<link rel="stylesheet" href="assets/bootstrap/dist/css/bootstrap.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
|
||||
</div>
|
||||
<script src="bundle.js"></script>
|
||||
<script src="assets/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
|
|
|
@ -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(
|
||||
<h1>Hello, world!</h1>,
|
||||
<Router>
|
||||
<div>
|
||||
<Navigation />
|
||||
<Route exact path="/" component={PageMain} />
|
||||
<Route path="/login" component={PageLogin} />
|
||||
</div>
|
||||
</Router>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
|
|
|
@ -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 (
|
||||
<nav className="navbar navbar-inverse bg-inverse sticky-top">
|
||||
<Container>
|
||||
<div className="d-flex flex-row">
|
||||
<span className="navbar-brand">Chronos</span>
|
||||
<ul className="navbar-nav mr-auto">
|
||||
<li className="nav-item">
|
||||
<Link className="nav-link" to="/login">Login</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="form-inline">
|
||||
<Button color="secondary" onClick={() => alert('test')}>Logout</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<Container>
|
||||
<Button>
|
||||
Login
|
||||
</Button>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<Container>
|
||||
<Row>
|
||||
<Column width="3" breakpoint="lg">
|
||||
<h1>Hello, world! </h1>
|
||||
</Column>
|
||||
<Column>
|
||||
<h1>Hello, world! </h1>
|
||||
</Column>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue