1
0
Fork 0

Initial commit

master
Ambrose Chua 2017-03-02 09:55:04 +08:00
parent 990dd9d413
commit 0559058dc2
10 changed files with 340 additions and 0 deletions

1
.eslintrc Normal file
View File

@ -0,0 +1 @@
{"rules": {"indent": ["error", "tab"], "no-tabs": "off", "react/jsx-indent": ["error", "tab"]}, "extends": "airbnb", "env": {"browser": true, "node": true}}

81
Gruntfile.js Normal file
View File

@ -0,0 +1,81 @@
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
webpack: {
app: {
entry: './app/index.jsx',
output: {
filename: 'bundle.js',
path: './dist/app/'
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
sourceMap: true,
presets: ['env', 'react']
}
}
]
}
}
},
copy: {
app: {
files: [
{
expand: true,
cwd: './app/',
src: ['index.html'],
dest: './dist/app/'
},
{
expand: true,
cwd: './app/',
src: ['assets/**'],
dest: './dist/app/assets/'
}
]
}
},
babel: {
options: {
sourceMap: true,
presets: [
['env', {
targets: {
node: 'current'
}
}]
]
},
server: {
files: [
{
expand: true,
cwd: './server/',
src: ['*.js'],
dest: './dist/server/'
}
]
}
},
eslint: {
app: ['./app/*.js*'],
server: ['./server/*.js']
}
});
grunt.loadNpmTasks('grunt-webpack');
grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.registerTask('all', ['eslint', 'webpack', 'copy', 'babel']);
grunt.registerTask('default', ['all']);
};

0
app/assets/core.css Normal file
View File

10
app/index.html Normal file
View File

@ -0,0 +1,10 @@
<head>
<meta charset="UTF-8">
<title>Chronos</title>
</head>
<body>
<div id="root">
</div>
<script src="bundle.js"></script>
</body>

7
app/index.jsx Normal file
View File

@ -0,0 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root'),
);

42
package.json Normal file
View File

@ -0,0 +1,42 @@
{
"name": "Chronos",
"version": "0.1.0",
"description": "A school event planner and timetable",
"scripts": {
"start": "node dist/server/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ambrosechua/chronos.git"
},
"keywords": [],
"author": "",
"license": "proprietary",
"bugs": {
"url": "https://github.com/ambrosechua/chronos/issues"
},
"homepage": "https://github.com/ambrosechua/chronos#readme",
"devDependencies": {
"babel-loader": "^6.3.2",
"babel-preset-env": "^1.1.10",
"babel-preset-react": "^6.23.0",
"eslint": "^3.16.1",
"eslint-config-airbnb": "^14.1.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-react": "^6.10.0",
"grunt": "^1.0.1",
"grunt-babel": "^6.0.0",
"grunt-contrib-copy": "^1.0.0",
"grunt-eslint": "^19.0.0",
"grunt-webpack": "^2.0.1",
"webpack": "^2.2.1"
},
"dependencies": {
"express": "^4.14.1",
"mysql": "^2.13.0",
"react": "^15.4.2",
"react-dom": "^15.4.2"
}
}

13
server/api.js Normal file
View File

@ -0,0 +1,13 @@
import Router from 'express';
export default class API {
constructor() {
this.router = Router({
strict: true,
});
this.router.get('/', (req, res) => {
res.end('API v1');
});
}
}

156
server/database.js Normal file
View File

@ -0,0 +1,156 @@
import mysql from 'mysql';
import { fatal, getVersion } from './utils';
const DATABASE = 'chronos';
export default class Database {
constructor(options) {
this.connection = mysql.createConnection(options);
this.connection.connect();
this.checkAndMigrate();
}
query(query, values) {
console.log('QUERY:', query);
return new Promise((resolve, reject) => {
this.connection.query(query, values, (err, results, fields) => {
if (err) {
reject(err);
}
resolve(results, fields);
});
});
}
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;
}))
.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()}')
`);
return true;
}
// for now
return this.query(`DROP DATABASE ${DATABASE}`, [])
.then(() => this.checkAndMigrate());
}
}

17
server/index.js Normal file
View File

@ -0,0 +1,17 @@
import path from 'path';
import express from 'express';
import Database from './database';
import API from './api';
const app = express();
const database = new Database({
host: 'localhost',
user: 'root',
password: '',
});
const api = new API(database);
app.use('/', express.static(path.join(__dirname, '..', 'app')));
app.use('/api/v1', api.router);
app.listen(8080);

13
server/utils.js Normal file
View File

@ -0,0 +1,13 @@
const getVersion = () => {
if (process.env.npm_package_version) {
return process.env.npm_package_version;
}
throw new Error('Unable to obtain running version');
};
const fatal = (e) => {
console.error(e);
process.exit(1);
};
export { getVersion, fatal };