added sqlite backend and express routes
This commit is contained in:
parent
20cba85fd3
commit
27f636e3fc
|
|
@ -0,0 +1,9 @@
|
|||
function pickRandom(args) {
|
||||
return args[Math.floor(Math.random() * args.length)];
|
||||
}
|
||||
|
||||
function randomDate() {
|
||||
return new Date(new Date() - 200000000000 * Math.random());
|
||||
}
|
||||
|
||||
module.exports = { pickRandom, randomDate };
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
const sequelize = require('../sequelize');
|
||||
const { pickRandom, randomDate } = require('./helpers/random');
|
||||
|
||||
async function reset() {
|
||||
console.log('Will rewrite the SQLite example database, adding some dummy data.');
|
||||
|
||||
await sequelize.sync({ force: true });
|
||||
|
||||
await sequelize.models.user.bulkCreate([
|
||||
{ firstName: 'Nolan', lastName: 'Ryan', email: 'ryan.nolan@bullpen.com', password: 'nolan' },
|
||||
{ firstName: 'Sandy', lastName: 'Koufax', email: 'sandy.koufax@bullpen.com', password: 'sandy' },
|
||||
{ firstName: 'Pedro', lastName: 'Martinez', email: 'pedro.martinez@bullpen.com', password: 'pedro' },
|
||||
{ firstName: 'randy', lastName: 'johnson', email: 'randy.johnson@bullpen.com', password: 'randy' },
|
||||
]);
|
||||
|
||||
await sequelize.models.pitchType.bulkCreate([
|
||||
{ name: 'Fastball', abbreviation: 'FB' },
|
||||
{ name: 'Curveball', abbreviation: 'CB' },
|
||||
{ name: 'Slider', abbreviation: 'SL' },
|
||||
{ name: 'Changeup', abbreviation: 'CH' },
|
||||
{ name: 'Cutter', abbreviation: 'CUT' },
|
||||
{ name: 'Sweeper', abbreviation: 'SW' },
|
||||
{ name: 'Slurve', abbreviation: 'SLV' },
|
||||
]);
|
||||
|
||||
// Let's create random instruments for each orchestra
|
||||
// for (const orchestra of await sequelize.models.orchestra.findAll()) {
|
||||
// for (let i = 0; i < 10; i++) {
|
||||
// const type = pickRandom([
|
||||
// 'violin',
|
||||
// 'trombone',
|
||||
// 'flute',
|
||||
// 'harp',
|
||||
// 'trumpet',
|
||||
// 'piano',
|
||||
// 'guitar',
|
||||
// 'pipe organ',
|
||||
// ]);
|
||||
//
|
||||
// await orchestra.createInstrument({
|
||||
// type: type,
|
||||
// purchaseDate: randomDate()
|
||||
// });
|
||||
//
|
||||
// // The following would be equivalent in this case:
|
||||
// // await sequelize.models.instrument.create({
|
||||
// // type: type,
|
||||
// // purchaseDate: randomDate(),
|
||||
// // orchestraId: orchestra.id
|
||||
// // });
|
||||
// }
|
||||
// }
|
||||
|
||||
console.log('Done!');
|
||||
}
|
||||
|
||||
reset();
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
const app = require('./services/app');
|
||||
const sequelize = require('./sequelize');
|
||||
const PORT = 8080;
|
||||
|
||||
async function assertDatabaseConnectionOk() {
|
||||
console.log(`Checking database connection...`);
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('Database connection OK!');
|
||||
} catch (error) {
|
||||
console.log('Unable to connect to the database:');
|
||||
console.log(error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await assertDatabaseConnectionOk();
|
||||
|
||||
console.log(`Starting Sequelize + Express example on port ${PORT}...`);
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Express server started on port ${PORT}. Try some routes, such as '/api/users'.`);
|
||||
});
|
||||
}
|
||||
|
||||
init().then(r => console.log('finished'));
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const Sequelize = require('sequelize');
|
||||
const process = require('process');
|
||||
const basename = path.basename(__filename);
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const config = require(__dirname + '/../config/config.json')[env];
|
||||
const db = {};
|
||||
|
||||
let sequelize;
|
||||
if (config.use_env_variable) {
|
||||
sequelize = new Sequelize(process.env[config.use_env_variable], config);
|
||||
} else {
|
||||
sequelize = new Sequelize(config.database, config.username, config.password, config);
|
||||
}
|
||||
|
||||
fs
|
||||
.readdirSync(__dirname)
|
||||
.filter(file => {
|
||||
return (
|
||||
file.indexOf('.') !== 0 &&
|
||||
file !== basename &&
|
||||
file.slice(-3) === '.js' &&
|
||||
file.indexOf('.test.js') === -1
|
||||
);
|
||||
})
|
||||
.forEach(file => {
|
||||
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
|
||||
db[model.name] = model;
|
||||
});
|
||||
|
||||
Object.keys(db).forEach(modelName => {
|
||||
if (db[modelName].associate) {
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
});
|
||||
|
||||
db.sequelize = sequelize;
|
||||
db.Sequelize = Sequelize;
|
||||
|
||||
module.exports = db;
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
'use strict';
|
||||
const Sequelize = require("sequelize");
|
||||
const { Model } = require('sequelize');
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
class User extends Model {
|
||||
/**
|
||||
* Helper method for defining associations.
|
||||
* This method is not a part of Sequelize lifecycle.
|
||||
* The `models/index` file will call this method automatically.
|
||||
*/
|
||||
static associate(models) {
|
||||
// define association here
|
||||
}
|
||||
}
|
||||
|
||||
User.init({
|
||||
id: {
|
||||
type: Sequelize.UUIDV4,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
},
|
||||
firstName: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
lastName: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
email: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
password: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'User',
|
||||
tableName: "Users",
|
||||
});
|
||||
return User;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,15 +3,23 @@
|
|||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node index.js",
|
||||
"setup-example-db": "node database/setup.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"author": "skuehl1972@gmail.com",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.3",
|
||||
"express": "^4.21.2",
|
||||
"pg": "^8.13.3",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.37.5"
|
||||
"sequelize": "^6.37.5",
|
||||
"sqlite3": "^5.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sequelize-cli": "^6.6.2"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
function applyExtraSetup(sequelize) {
|
||||
const { user, pitch, pitchType, bullpenSession } = sequelize.models;
|
||||
|
||||
bullpenSession.hasMany(pitch);
|
||||
bullpenSession.belongsTo(user);
|
||||
pitch.belongsTo(bullpenSession);
|
||||
pitchType.belongsTo(pitchType);
|
||||
}
|
||||
|
||||
module.exports = { applyExtraSetup };
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
const { Sequelize } = require('sequelize');
|
||||
const { applyExtraSetup } = require('./extra-setup');
|
||||
|
||||
// In a real app, you should keep the database connection URL as an environment variable.
|
||||
// But for this example, we will just use a local SQLite database.
|
||||
// const sequelize = new Sequelize(process.env.DB_CONNECTION_URL);
|
||||
const sequelize = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage: 'database/example-db.sqlite',
|
||||
logQueryParameters: true,
|
||||
benchmark: true
|
||||
});
|
||||
|
||||
const modelDefiners = [
|
||||
require('./models/user.model'),
|
||||
require('./models/pitchType.model'),
|
||||
require('./models/pitch.model'),
|
||||
require('./models/bullpenSession.model'),
|
||||
];
|
||||
|
||||
// We define all models according to their files.
|
||||
for (const modelDefiner of modelDefiners) {
|
||||
modelDefiner(sequelize);
|
||||
}
|
||||
|
||||
// We execute any extra setup after the models are defined, such as adding associations.
|
||||
applyExtraSetup(sequelize);
|
||||
|
||||
// We export the sequelize connection instance to be used around our app.
|
||||
module.exports = sequelize;
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
sequelize.define('bullpenSession', {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
pitcher: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: { // User belongsTo User 1:1
|
||||
model: 'User',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
aimedArea: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
hitArea: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'BullpenSession',
|
||||
tableName: 'BullpenSessions',
|
||||
})
|
||||
};
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
sequelize.define('pitch', {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: { // User belongsTo PitchType 1:1
|
||||
model: 'PitchType',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
bullpenSessionId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: { // User belongsTo PitchType 1:1
|
||||
model: 'BullpenSession',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
aimedArea: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
hitArea: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Pitch',
|
||||
tableName: 'Pitches',
|
||||
})
|
||||
};
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
|
||||
module.exports = (sequelize) => {
|
||||
sequelize.define('pitchType', {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
abbreviation: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'PitchType',
|
||||
tableName: 'PitchTypes'
|
||||
})
|
||||
};
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
const { DataTypes } = require('sequelize');
|
||||
|
||||
// We export a function that defines the model.
|
||||
// This function will automatically receive as parameter the Sequelize connection object.
|
||||
module.exports = (sequelize) => {
|
||||
sequelize.define('user', {
|
||||
// The following specification of the 'id' attribute could be omitted
|
||||
// since it is the default.
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
firstName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
lastName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
validate: {
|
||||
// We require usernames to have length of at least 3, and
|
||||
// only use letters, numbers and underscores.
|
||||
is: /^\w{3,}$/
|
||||
}
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'User',
|
||||
tableName: "Users",
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
const routes = {
|
||||
users: require('./routes/users'),
|
||||
pitch_types: require('./routes/pitchTypes'),
|
||||
// Add more routes here...
|
||||
// items: require('./routes/items'),
|
||||
};
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
// We create a wrapper to workaround async errors not being transmitted correctly.
|
||||
function makeHandlerAwareOfAsyncErrors(handler) {
|
||||
return async function(req, res, next) {
|
||||
try {
|
||||
await handler(req, res);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// We provide a root route just as an example
|
||||
app.get('/', (req, res) => {
|
||||
res.send(`
|
||||
<h2>Hello, Sequelize + Express!</h2>
|
||||
<p>Make sure you have executed <b>npm run setup-example-db</b> once to have a populated example database. Otherwise, you will get <i>'no such table'</i> errors.</p>
|
||||
<p>Try some routes, such as <a href='/api/users'>/api/users</a> or <a href='/api/orchestras?includeInstruments'>/api/orchestras?includeInstruments</a>!</p>
|
||||
<p>To experiment with POST/PUT/DELETE requests, use a tool for creating HTTP requests such as <a href='https://github.com/jakubroztocil/httpie#readme'>HTTPie</a>, <a href='https://www.postman.com/downloads/'>Postman</a>, or even <a href='https://en.wikipedia.org/wiki/CURL'>the curl command</a>, or write some JS code for it with <a href='https://github.com/sindresorhus/got#readme'>got</a>, <a href='https://github.com/sindresorhus/ky#readme'>ky</a> or <a href='https://github.com/axios/axios#readme'>axios</a>.</p>
|
||||
`);
|
||||
});
|
||||
|
||||
// We define the standard REST APIs for each route (if they exist).
|
||||
for (const [routeName, routeController] of Object.entries(routes)) {
|
||||
if (routeController.getAll) {
|
||||
app.get(
|
||||
`/api/${routeName}`,
|
||||
makeHandlerAwareOfAsyncErrors(routeController.getAll)
|
||||
);
|
||||
}
|
||||
if (routeController.getById) {
|
||||
app.get(
|
||||
`/api/${routeName}/:id`,
|
||||
makeHandlerAwareOfAsyncErrors(routeController.getById)
|
||||
);
|
||||
}
|
||||
if (routeController.create) {
|
||||
app.post(
|
||||
`/api/${routeName}`,
|
||||
makeHandlerAwareOfAsyncErrors(routeController.create)
|
||||
);
|
||||
}
|
||||
if (routeController.update) {
|
||||
app.put(
|
||||
`/api/${routeName}/:id`,
|
||||
makeHandlerAwareOfAsyncErrors(routeController.update)
|
||||
);
|
||||
}
|
||||
if (routeController.remove) {
|
||||
app.delete(
|
||||
`/api/${routeName}/:id`,
|
||||
makeHandlerAwareOfAsyncErrors(routeController.remove)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = app;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// A helper function to assert the request ID param is valid
|
||||
// and convert it to a number (since it comes as a string by default)
|
||||
function getIdParam(req) {
|
||||
const id = req.params.id;
|
||||
if (/^\d+$/.test(id)) {
|
||||
return Number.parseInt(id, 10);
|
||||
}
|
||||
throw new TypeError(`Invalid ':id' param: "${id}"`);
|
||||
}
|
||||
|
||||
module.exports = { getIdParam };
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
const { models } = require('../../sequelize');
|
||||
const { getIdParam } = require('../helpers');
|
||||
|
||||
async function getAll(req, res) {
|
||||
const pitchType = await models.pitchType.findAll();
|
||||
res.status(200).json(pitchType);
|
||||
}
|
||||
|
||||
async function getById(req, res) {
|
||||
const id = getIdParam(req);
|
||||
const pitchType = await models.pitchType.findByPk(id);
|
||||
if (pitchType) {
|
||||
res.status(200).json(pitchType);
|
||||
} else {
|
||||
res.status(404).send('404 - Not found');
|
||||
}
|
||||
}
|
||||
|
||||
async function create(req, res) {
|
||||
if (req.body.id) {
|
||||
res.status(400).send(`Bad request: ID should not be provided, since it is determined automatically by the database.`)
|
||||
} else {
|
||||
await models.pitchType.create(req.body);
|
||||
res.status(201).end();
|
||||
}
|
||||
}
|
||||
|
||||
async function update(req, res) {
|
||||
const id = getIdParam(req);
|
||||
|
||||
// We only accept an UPDATE request if the `:id` param matches the body `id`
|
||||
if (req.body.id === id) {
|
||||
await models.pitchType.update(req.body, {
|
||||
where: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
res.status(200).end();
|
||||
} else {
|
||||
res.status(400).send(`Bad request: param ID (${id}) does not match body ID (${req.body.id}).`);
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(req, res) {
|
||||
const id = getIdParam(req);
|
||||
await models.pitchType.destroy({
|
||||
where: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAll,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
};
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
const { models } = require('../../sequelize');
|
||||
const { getIdParam } = require('../helpers');
|
||||
|
||||
async function getAll(req, res) {
|
||||
const users = await models.user.findAll();
|
||||
res.status(200).json(users);
|
||||
}
|
||||
|
||||
async function getById(req, res) {
|
||||
const id = getIdParam(req);
|
||||
const user = await models.user.findByPk(id);
|
||||
if (user) {
|
||||
res.status(200).json(user);
|
||||
} else {
|
||||
res.status(404).send('404 - Not found');
|
||||
}
|
||||
}
|
||||
|
||||
async function create(req, res) {
|
||||
if (req.body.id) {
|
||||
res.status(400).send(`Bad request: ID should not be provided, since it is determined automatically by the database.`)
|
||||
} else {
|
||||
await models.user.create(req.body);
|
||||
res.status(201).end();
|
||||
}
|
||||
}
|
||||
|
||||
async function update(req, res) {
|
||||
const id = getIdParam(req);
|
||||
|
||||
// We only accept an UPDATE request if the `:id` param matches the body `id`
|
||||
if (req.body.id === id) {
|
||||
await models.user.update(req.body, {
|
||||
where: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
res.status(200).end();
|
||||
} else {
|
||||
res.status(400).send(`Bad request: param ID (${id}) does not match body ID (${req.body.id}).`);
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(req, res) {
|
||||
const id = getIdParam(req);
|
||||
await models.user.destroy({
|
||||
where: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
res.status(200).end();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getAll,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
};
|
||||
Loading…
Reference in New Issue