add frame express app
This commit is contained in:
57
frame/src/app.ts
Normal file
57
frame/src/app.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import * as createError from 'http-errors';
|
||||
import * as express from 'express';
|
||||
import * as path from 'path';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
import * as logger from 'morgan';
|
||||
import * as passport from 'passport';
|
||||
import * as helmet from 'helmet';
|
||||
|
||||
require('./components/authentication/passportSetup'); // runs some passport initialization
|
||||
import { sessionsSetup } from './components/sessions/sessions';
|
||||
|
||||
var indexRouter = require('./routes/index');
|
||||
var usersRouter = require('./components/users/usersRoutes');
|
||||
// SYSTEM-BUILDER-router.require
|
||||
|
||||
|
||||
const debug = require('debug')('frame:app-root');
|
||||
var app = express();
|
||||
|
||||
sessionsSetup(app);
|
||||
app.use(helmet());
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.use('/', indexRouter);
|
||||
app.use('/api/v1/users', usersRouter);
|
||||
|
||||
// SYSTEM-BUILDER-app.use
|
||||
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function(err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
});
|
||||
|
||||
module.exports = app;
|
95
frame/src/bin/www.ts
Normal file
95
frame/src/bin/www.ts
Normal file
@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import '../env';
|
||||
import { appReady } from '../initializeServices';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('frame:server');
|
||||
var http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
appReady.then(() => {
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
});
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
17
frame/src/components/Bergx/bergxService.ts
Normal file
17
frame/src/components/Bergx/bergxService.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import BergxSDK, { TryResponse } from 'bergx-sdk';
|
||||
import { userService } from '../users/userService';
|
||||
import { DBUser } from '../users/usersMapper';
|
||||
|
||||
const bergxService = new BergxSDK({
|
||||
clientId: process.env.BX_CLIENT_ID,
|
||||
clientSecret: process.env.BX_CLIENT_SECRET,
|
||||
updateAccessTokenCallback: (userSub: string, newAccessToken: string) => {
|
||||
// finds the user in the database and updates the accessToken
|
||||
userService.find(userSub).then((user: DBUser) => {
|
||||
user.accessToken = newAccessToken;
|
||||
userService.update(user);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default bergxService;
|
20
frame/src/components/authentication/authenticationRoutes.ts
Normal file
20
frame/src/components/authentication/authenticationRoutes.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import * as express from 'express';
|
||||
import * as passport from 'passport';
|
||||
var router = express.Router();
|
||||
|
||||
const debug = require('debug')('frame:passportSetup');
|
||||
|
||||
router.get('/callback', [
|
||||
passport.authenticate('oauth2', { failureRedirect: '/login' }),
|
||||
(req, res, next) => {
|
||||
res.redirect('/');
|
||||
}
|
||||
]);
|
||||
|
||||
router.get('/logout', (req, res, next) => {
|
||||
req.session.destroy((err) => {
|
||||
res.redirect('/');
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
43
frame/src/components/authentication/passportSetup.ts
Normal file
43
frame/src/components/authentication/passportSetup.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import * as passport from 'passport';
|
||||
import * as refresh from 'passport-oauth2-refresh';
|
||||
import * as OAuth2Strategy from 'passport-oauth2';
|
||||
|
||||
import { userService } from '../users/userService';
|
||||
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user.sub);
|
||||
});
|
||||
|
||||
passport.deserializeUser((sub, done) => {
|
||||
userService.find(sub).then((user) => {
|
||||
if (typeof user !== 'undefined') {
|
||||
done(null, user);
|
||||
} else {
|
||||
done('User not found');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var strategy = new OAuth2Strategy({
|
||||
authorizationURL: process.env.AUTHORIZATION_URL,
|
||||
tokenURL: process.env.TOKEN_URL,
|
||||
clientID: process.env.CLIENT_ID,
|
||||
clientSecret: process.env.CLIENT_SECRET,
|
||||
callbackURL: process.env.CALLBACK_URL,
|
||||
state: true,
|
||||
pkce: true
|
||||
},
|
||||
function(accessToken, refreshToken, params, _, cb) {
|
||||
console.log(params);
|
||||
userService.findOrCreate(params.profile).then((user) => {
|
||||
user.refreshToken = refreshToken;
|
||||
user.accessToken = accessToken;
|
||||
return cb(null, user);
|
||||
}).catch((err) => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
passport.use(strategy);
|
||||
refresh.use(strategy);
|
42
frame/src/components/sessions/sessions.ts
Normal file
42
frame/src/components/sessions/sessions.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import * as session from 'express-session';
|
||||
import * as mySQLSession from 'express-mysql-session';
|
||||
|
||||
const debug = require('debug')('frame:session');
|
||||
|
||||
function sessionsSetup(app) {
|
||||
// setup sessions
|
||||
let MySQLStore = mySQLSession(session);
|
||||
const DOMAIN = process.env.DOMAIN || 'localhost';
|
||||
debug(`Domain: ${DOMAIN}`);
|
||||
let mysqlSessionOptions = {
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT || 3306,
|
||||
user: process.env.SESSION_DB_USER,
|
||||
password: process.env.SESSION_DB_PASSWORD,
|
||||
database: process.env.SESSION_DB_NAME
|
||||
};
|
||||
let sessionStore = new MySQLStore(mysqlSessionOptions);
|
||||
let sess = {
|
||||
secret: process.env.SESSION_SECRET, // can be an array. Use an array when rolling this key
|
||||
cookie: {
|
||||
secure: false,
|
||||
domain: DOMAIN
|
||||
},
|
||||
resave: false,
|
||||
store: sessionStore,
|
||||
saveUninitialized: false
|
||||
};
|
||||
|
||||
debug(`Environment: ${app.get('env')}`);
|
||||
if (app.get('env') === 'production') {
|
||||
app.set('trust proxy', 1) // trust first proxy (usually nginx or loadbalancer)
|
||||
// sess.cookie.secure = true // serve secure cookies
|
||||
sess.cookie.secure = false // serve insecure cookies
|
||||
}
|
||||
|
||||
app.use(session(sess));
|
||||
}
|
||||
|
||||
export {
|
||||
sessionsSetup
|
||||
}
|
45
frame/src/components/users/userService.ts
Normal file
45
frame/src/components/users/userService.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { db } from '../../databases';
|
||||
import { UsersMapper, DBUser } from './usersMapper';
|
||||
|
||||
|
||||
class UserService {
|
||||
private userMapper = new UsersMapper(db);
|
||||
|
||||
public initialize() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public create(profile: DBUser) {
|
||||
return this.userMapper.createUser(profile.sub, profile.email, profile.accessToken, profile.refreshToken);
|
||||
}
|
||||
|
||||
public find(sub: string): Promise<DBUser> {
|
||||
return this.userMapper.findUserBySub(sub).then((matchingUsers) => {
|
||||
if (matchingUsers.length) {
|
||||
return matchingUsers[0];
|
||||
} else {
|
||||
throw new Error('User Not Found');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public update(profile: DBUser) {
|
||||
return this.userMapper.updateUser(profile.sub, profile.email, profile.accessToken, profile.refreshToken);
|
||||
}
|
||||
|
||||
public findOrCreate(profile: DBUser) {
|
||||
return this.find(profile.sub).then((matchingUser: DBUser) => {
|
||||
return this.update(profile);
|
||||
}).catch((err) => {
|
||||
return this.create(profile);
|
||||
});
|
||||
}
|
||||
|
||||
// SYSTEM-BUILDER-userService
|
||||
}
|
||||
|
||||
const userService = new UserService();
|
||||
|
||||
export {
|
||||
userService
|
||||
}
|
29
frame/src/components/users/usersMapper.ts
Normal file
29
frame/src/components/users/usersMapper.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Mapper } from '../../db/Mapper';
|
||||
|
||||
class UsersMapper extends Mapper {
|
||||
|
||||
public findUserBySub(sub: string): Promise<DBUser[]> {
|
||||
return super.runQuery('SELECT * FROM Users WHERE sub = ?', sub);
|
||||
}
|
||||
|
||||
public createUser(sub: string, email: string, accessToken: string, refreshToken: string) {
|
||||
|
||||
}
|
||||
|
||||
public updateUser(sub: string, email: string, accessToken: string, refreshToken: string) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface DBUser {
|
||||
sub: string;
|
||||
email: string;
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
}
|
||||
|
||||
export {
|
||||
UsersMapper,
|
||||
DBUser
|
||||
}
|
25
frame/src/components/users/usersRoutes.ts
Normal file
25
frame/src/components/users/usersRoutes.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import * as express from 'express';
|
||||
import * as passport from 'passport';
|
||||
import bergxService from '../Bergx/bergxService';
|
||||
|
||||
var router = express.Router();
|
||||
|
||||
const debug = require('debug')('frame:usersRoutes');
|
||||
|
||||
router.get('/profile/me', [
|
||||
passport.authenticate('oauth2'),
|
||||
(req, res, next) => {
|
||||
bergxService.getProfile(req.user).then((info) => {
|
||||
res.status(200);
|
||||
res.json(info);
|
||||
}).catch((err) => {
|
||||
res.status(500);
|
||||
res.json({
|
||||
status: 'error',
|
||||
message: err.message
|
||||
});
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
module.exports = router;
|
@ -0,0 +1,9 @@
|
||||
import { Mapper } from '../../db/Mapper';
|
||||
|
||||
class {{Component}}Mapper extends Mapper {
|
||||
// SYSTEM-BUILDER-{{component}}-mapper
|
||||
}
|
||||
|
||||
export {
|
||||
{{Component}}Mapper
|
||||
}
|
10
frame/src/components/{{component}}/{{component}}Routes.ts
Normal file
10
frame/src/components/{{component}}/{{component}}Routes.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import * as express from 'express';
|
||||
import * as passport from 'passport';
|
||||
|
||||
var router = express.Router();
|
||||
|
||||
const debug = require('debug')('frame:{{component}}Routes');
|
||||
|
||||
// SYSTEM-BUILDER-{{component}}-routes
|
||||
|
||||
module.exports = router;
|
18
frame/src/components/{{component}}/{{component}}Service.ts
Normal file
18
frame/src/components/{{component}}/{{component}}Service.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { db } from '../../databases';
|
||||
import { {{Component}}Mapper } from './componentMapper';
|
||||
|
||||
class {{Component}}Service {
|
||||
private {{component}}Mapper = new {{Component}}Mapper(db);
|
||||
|
||||
public initialize() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// SYSTEM-BUILDER-{{component}}-service
|
||||
}
|
||||
|
||||
const {{component}}Service = new {{Component}}Service();
|
||||
|
||||
export {
|
||||
{{component}}Service
|
||||
}
|
24
frame/src/databases.ts
Normal file
24
frame/src/databases.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Database } from './db/db';
|
||||
import { Mapper } from './db/Mapper';
|
||||
import { Altitude, Job } from './db/migrations/Altitude';
|
||||
import jobs from './migrationJobs';
|
||||
|
||||
let db = new Database({
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT || '3306',
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
});
|
||||
let mapper = new Mapper(db);
|
||||
let altitude = new Altitude(mapper);
|
||||
|
||||
jobs.map((job) => {
|
||||
altitude.addMigration(job);
|
||||
});
|
||||
|
||||
export {
|
||||
altitude,
|
||||
db,
|
||||
mapper
|
||||
};
|
25
frame/src/db/Mapper.ts
Normal file
25
frame/src/db/Mapper.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Database } from '../db/db';
|
||||
|
||||
class Mapper {
|
||||
constructor(public database: Database) {}
|
||||
|
||||
public runQuery(query: string, values: any): Promise<any[]> {
|
||||
let defer = new Promise<any[]>((resolve, reject) => {
|
||||
let connection = this.database.getConnection();
|
||||
connection.connect();
|
||||
connection.query(query, values, (error, results, fields) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(results);
|
||||
}
|
||||
});
|
||||
connection.end();
|
||||
});
|
||||
return defer;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
Mapper
|
||||
};
|
23
frame/src/db/db.ts
Normal file
23
frame/src/db/db.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import * as mysql from 'mysql';
|
||||
|
||||
interface DbInfo {
|
||||
host: string;
|
||||
port: string;
|
||||
user: string;
|
||||
password: string;
|
||||
database: string;
|
||||
}
|
||||
|
||||
class Database {
|
||||
|
||||
constructor(public connectionOptions: DbInfo) {}
|
||||
|
||||
public getConnection() {
|
||||
return mysql.createConnection(this.connectionOptions);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
DbInfo,
|
||||
Database
|
||||
};
|
243
frame/src/db/migrations/Altitude.ts
Normal file
243
frame/src/db/migrations/Altitude.ts
Normal file
@ -0,0 +1,243 @@
|
||||
import { Mapper } from '../Mapper';
|
||||
|
||||
class Altitude {
|
||||
private migrations: Job[];
|
||||
private ready: Promise<any>;
|
||||
constructor(private mapper: Mapper) {
|
||||
let self = this;
|
||||
this.migrations = [];
|
||||
this.ready = new Promise((resolve, reject) => {
|
||||
this.shouldInitDB().then((shouldInit: boolean) => {
|
||||
if (shouldInit) {
|
||||
console.log(`Initializing ${this.mapper.database.connectionOptions.database}`);
|
||||
this.mapper.runQuery(`CREATE TABLE CHANGE_LOG (
|
||||
id int NOT NULL AUTO_INCREMENT,
|
||||
name VARCHAR(255),
|
||||
creator VARCHAR(255),
|
||||
UpdatedTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
);`, []).then(() => {
|
||||
console.log(`Created CHANGE_LOG table.`);
|
||||
resolve(true);
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public addMigration(newMigration: Job) {
|
||||
if (this.validateJob(newMigration)) {
|
||||
this.migrations.push(newMigration);
|
||||
} else {
|
||||
throw `The provided migration job (${newMigration.name}) is not valid.`;
|
||||
}
|
||||
}
|
||||
|
||||
public removeMigration(migrationName: string) {
|
||||
this.migrations = this.migrations.filter((migration: Job) => {
|
||||
return migration.name !== migrationName;
|
||||
});
|
||||
}
|
||||
|
||||
public runMigrationByName(jobName: string) {
|
||||
let migrationsToRun: Job[] = this.migrations.filter((job: Job) => {
|
||||
return job.name === jobName;
|
||||
});
|
||||
|
||||
return this.runMigrations(migrationsToRun);
|
||||
}
|
||||
|
||||
public runMigrations(migrationsToRun: Job[] = this.migrations) {
|
||||
let self = this;
|
||||
let defer = this.ready;
|
||||
migrationsToRun.forEach((job: Job) => {
|
||||
defer = defer.then((): Promise<any> => {
|
||||
return self.runMigration(job);
|
||||
});
|
||||
});
|
||||
return defer;
|
||||
}
|
||||
|
||||
private validateJob(job: Job) {
|
||||
let valid = true;
|
||||
if (job.preCondition) {
|
||||
valid = valid && typeof job.preCondition === 'string';
|
||||
}
|
||||
if (job.preConditionFieldName) {
|
||||
valid = valid && typeof job.preConditionFieldName === 'string';
|
||||
}
|
||||
if (job.preConditionValidation) {
|
||||
valid = valid && typeof job.preConditionValidation === 'function';
|
||||
}
|
||||
if (job.postCondition) {
|
||||
valid = valid && typeof job.postCondition === 'string';
|
||||
}
|
||||
if (job.postConditionFieldName) {
|
||||
valid = valid && typeof job.postConditionFieldName === 'string';
|
||||
}
|
||||
if (job.postConditionValidation) {
|
||||
valid = valid && typeof job.postConditionValidation === 'function';
|
||||
}
|
||||
if (job.rollback) {
|
||||
valid = valid && typeof job.rollback === 'string';
|
||||
}
|
||||
valid = valid && typeof job.name === 'string';
|
||||
valid = valid && typeof job.query === 'string';
|
||||
valid = valid && typeof job.creator === 'string';
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
private shouldInitDB() {
|
||||
return this.mapper.runQuery(`SELECT table_name FROM information_schema.tables WHERE table_schema = '${this.mapper.database.connectionOptions.database}' AND table_name = 'CHANGE_LOG';`, [])
|
||||
.then((results: any[]) => {
|
||||
return results.length === 0;
|
||||
});
|
||||
}
|
||||
|
||||
private alreadyRun(migration: Job) {
|
||||
return this.mapper.runQuery(`SELECT name FROM CHANGE_LOG WHERE name = '${migration.name}';`, [])
|
||||
.then((results: any[]) => {
|
||||
return results.length !== 0;
|
||||
});
|
||||
}
|
||||
|
||||
private runMigration(migration: Job) {
|
||||
let self = this;
|
||||
return this.alreadyRun(migration)
|
||||
.then((alreadyRun: boolean) => {
|
||||
if (alreadyRun) {
|
||||
return `Skipping migration ${migration.name} because it has already been run.`;
|
||||
} else {
|
||||
return doMigration();
|
||||
}
|
||||
});
|
||||
|
||||
function doMigration() {
|
||||
return self.runPreCondition(migration)
|
||||
.then((preConditionResult) => {
|
||||
if (preConditionResult) {
|
||||
let defer: Promise<any> = Promise.resolve();
|
||||
let queries: string[] = migration.query.split(';');
|
||||
queries = queries.filter((query) => query.length);
|
||||
queries.forEach((query: string) => {
|
||||
defer = defer.then((result) => {
|
||||
if (result) console.log(result);
|
||||
return self.mapper.runQuery(query, []);
|
||||
});
|
||||
});
|
||||
return defer;
|
||||
} else {
|
||||
throw 'PreCondition was not met.';
|
||||
}
|
||||
})
|
||||
.then((mainQueryResult) => {
|
||||
console.log(mainQueryResult);
|
||||
return self.runPostCondition(migration);
|
||||
})
|
||||
.then((postConditionResult) => {
|
||||
if (!postConditionResult) {
|
||||
return self.runRollback(migration);
|
||||
} else {
|
||||
return self.recordRun(migration)
|
||||
.then(() => {
|
||||
return 'Success';
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private recordRun(migration: Job) {
|
||||
return this.mapper.runQuery(`INSERT INTO CHANGE_LOG SET ?;`, [new ChangeLog(migration)]);
|
||||
}
|
||||
|
||||
private runPreCondition(migration: Job) {
|
||||
let defer = new Promise((resolve, reject) => {
|
||||
if (migration.preCondition) {
|
||||
this.mapper.runQuery(migration.preCondition, []).then((result) => {
|
||||
console.log(result);
|
||||
let isValid = typeof migration.preConditionValidation === 'function' ? migration.preConditionValidation(result) : result.length > 0 && result[0][migration.preConditionFieldName || 'precondition'] !== 0;
|
||||
console.log(isValid);
|
||||
resolve(isValid);
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
return defer;
|
||||
}
|
||||
|
||||
private runPostCondition(migration: Job) {
|
||||
let defer = new Promise((resolve, reject) => {
|
||||
if (migration.postCondition) {
|
||||
this.mapper.runQuery(migration.postCondition, []).then((result) => {
|
||||
console.log(result);
|
||||
console.log(result[0][migration.postConditionFieldName || 'postcondition'] !== 0);
|
||||
resolve(result[0][migration.postConditionFieldName || 'postcondition'] !== 0);
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
return defer;
|
||||
}
|
||||
|
||||
private runRollback(migration: Job) {
|
||||
let defer = new Promise((resolve, reject) => {
|
||||
if (migration.rollback) {
|
||||
this.mapper.runQuery(migration.rollback, []).then((result) => {
|
||||
resolve(result);
|
||||
}).catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
return defer;
|
||||
}
|
||||
}
|
||||
|
||||
interface ValidationFunction {
|
||||
(results: any[]): boolean;
|
||||
}
|
||||
|
||||
interface Job {
|
||||
preCondition?: string;
|
||||
preConditionFieldName?: string;
|
||||
preConditionValidation?: ValidationFunction;
|
||||
postCondition?: string;
|
||||
postConditionFieldName?: string;
|
||||
postConditionValidation?: ValidationFunction;
|
||||
rollback?: string;
|
||||
name: string;
|
||||
query: string;
|
||||
creator: string;
|
||||
}
|
||||
|
||||
class ChangeLog {
|
||||
public name: string;
|
||||
public creator: string;
|
||||
|
||||
constructor(migration: Job) {
|
||||
this.name = migration.name;
|
||||
this.creator = migration.creator;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
Altitude,
|
||||
Job
|
||||
}
|
42
frame/src/db/migrations/README.md
Normal file
42
frame/src/db/migrations/README.md
Normal file
@ -0,0 +1,42 @@
|
||||
# Altitude
|
||||
|
||||
Altitude is a custom migration tool for updating a database with migrations. The only dependency is the MySQL library. Migrations are simple. Just create jobs for Altitude to do.
|
||||
|
||||
```Typescript
|
||||
interface Job {
|
||||
preCondition?: string;
|
||||
preConditionFieldName?: stirng;
|
||||
postCondition?: string;
|
||||
postConditionFieldName?: stirng;
|
||||
rollback?: string;
|
||||
name: string;
|
||||
query: string;
|
||||
}
|
||||
```
|
||||
|
||||
Then add the job to Altitude and tell it to run the migration.
|
||||
|
||||
```Typescript
|
||||
let altitude = new Altitude();
|
||||
|
||||
let job: Job = {
|
||||
name: 'test',
|
||||
query: 'CREATE TABLE test_table (test VARCHAR(20));'
|
||||
};
|
||||
|
||||
altitude.addMigration(job);
|
||||
|
||||
altitude.runMigrationByName('test');
|
||||
```
|
||||
|
||||
All options are optional except for `name` and `query`. `query` can have multiple queries in the string as long as each are separated with a semicolon `;`. `name` must be unique otherwise only the first migration with that name will ever be run.
|
||||
|
||||
```Typescript
|
||||
// optional properties
|
||||
|
||||
preCondition: `SELECT @@version NOT LIKE '5.0%' AND @@version NOT LIKE '5.1%' AS precondition;`, // query ran before the migration to check the current state of the database.
|
||||
preConditionFieldName: 'precondition', // if the field in the precondition that needs to be checked isn't named precondition you can specify the correct field name here.
|
||||
postCondition: `SELECT COUNT(*) as postcondition FROM StormTest.Users;` // same thing as precondition except run after the migration
|
||||
postConditionFieldName: string; // same thing as the preConditionFieldName except for the postCondition
|
||||
rollback: `DROP TABLE test_table;` // query that is run if the post condition fails.
|
||||
```
|
2
frame/src/env.ts
Normal file
2
frame/src/env.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { config } from 'dotenv';
|
||||
config({ silent: true })
|
18
frame/src/initializeServices.ts
Normal file
18
frame/src/initializeServices.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { userService } from './components/users/userService';
|
||||
|
||||
// SYSTEM-BUILDER-initialize-import
|
||||
|
||||
import { altitude } from './databases';
|
||||
|
||||
const appReady = altitude.runMigrations().then(() => {
|
||||
let servicesPromise = [
|
||||
userService.initialize(),
|
||||
|
||||
// SYSTEM-BUILDER-initialize-service
|
||||
];
|
||||
return Promise.all(servicesPromise);
|
||||
});
|
||||
|
||||
export {
|
||||
appReady
|
||||
}
|
12
frame/src/migrationJobs/index.ts
Normal file
12
frame/src/migrationJobs/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Job } from '../db/migrations/Altitude';
|
||||
|
||||
let jobs: Job[] = [];
|
||||
jobs.push({
|
||||
creator: 'Mason Payne',
|
||||
name: 'initializeDB',
|
||||
query: `CREATE TABLE Users (sub VARCHAR(36) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE, accessToken VARCHAR(255), refreshToken VARCHAR(255), CreationTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (sub));`
|
||||
});
|
||||
|
||||
// SYSTEM-BUILDER-jobs.push
|
||||
|
||||
export default jobs;
|
8
frame/src/public/stylesheets/style.css
Normal file
8
frame/src/public/stylesheets/style.css
Normal file
@ -0,0 +1,8 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
9
frame/src/routes/index.ts
Normal file
9
frame/src/routes/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
3
frame/src/views/error.ejs
Normal file
3
frame/src/views/error.ejs
Normal file
@ -0,0 +1,3 @@
|
||||
<h1><%= message %></h1>
|
||||
<h2><%= error.status %></h2>
|
||||
<pre><%= error.stack %></pre>
|
11
frame/src/views/index.ejs
Normal file
11
frame/src/views/index.ejs
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= title %></title>
|
||||
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||
</head>
|
||||
<body>
|
||||
<h1><%= title %></h1>
|
||||
<p>Welcome to <%= title %></p>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user