From 3cbb4674575669cc570ee67fd3ed673ba5b1e825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20K=C3=BChl?= Date: Sun, 9 Mar 2025 22:27:57 +0100 Subject: [PATCH] refactored backend structure and added login --- app/src/data/pitchers.ts | 60 +++---- app/src/router/index.ts | 7 +- app/src/services/BullpenSessionService.ts | 10 +- app/src/services/PitchTypeService.ts | 25 ++- app/src/services/PitcherService.ts | 8 +- app/src/types/Pitcher.ts | 10 +- app/src/views/Login.vue | 133 ++++++++++++++++ app/src/views/PitcherList.vue | 2 +- backend/config/auth.config.js | 3 + backend/config/config.json | 23 --- backend/config/postgres.config.js | 13 ++ backend/config/sqlite.config.js | 6 + backend/controllers/auth.controller.js | 93 +++++++++++ backend/controllers/pitchType.controller.js | 36 +++++ backend/controllers/user.controller.js | 55 +++++++ backend/database/example-db.sqlite | Bin 32768 -> 45056 bytes backend/index.js | 89 ++++++++--- backend/middleware/authJwt.js | 90 +++++++++++ backend/middleware/index.js | 7 + backend/middleware/verifySignUp.js | 43 +++++ .../models/bullpenSession.model.js | 22 +-- backend/models/index.js | 54 +++++++ backend/{sequelize => }/models/pitch.model.js | 6 +- .../{sequelize => }/models/pitchType.model.js | 6 +- backend/models/role.model.js | 22 +++ backend/models/user.model.js | 55 +++++++ backend/package-lock.json | 148 ++++++++++++++++++ backend/package.json | 4 + backend/routes/auth.routes.js | 23 +++ backend/routes/pitchType.routes.js | 15 ++ backend/routes/user.routes.js | 39 +++++ backend/sequelize/extra-setup.js | 10 -- backend/sequelize/index.js | 30 ---- backend/sequelize/models/user.model.js | 46 ------ backend/services/app.js | 71 --------- backend/services/helpers.js | 11 -- backend/services/routes/pitchTypes.js | 60 ------- backend/services/routes/users.js | 60 ------- 38 files changed, 995 insertions(+), 400 deletions(-) create mode 100644 app/src/views/Login.vue create mode 100644 backend/config/auth.config.js delete mode 100644 backend/config/config.json create mode 100644 backend/config/postgres.config.js create mode 100644 backend/config/sqlite.config.js create mode 100644 backend/controllers/auth.controller.js create mode 100644 backend/controllers/pitchType.controller.js create mode 100644 backend/controllers/user.controller.js create mode 100644 backend/middleware/authJwt.js create mode 100644 backend/middleware/index.js create mode 100644 backend/middleware/verifySignUp.js rename backend/{sequelize => }/models/bullpenSession.model.js (62%) create mode 100644 backend/models/index.js rename backend/{sequelize => }/models/pitch.model.js (92%) rename backend/{sequelize => }/models/pitchType.model.js (85%) create mode 100644 backend/models/role.model.js create mode 100644 backend/models/user.model.js create mode 100644 backend/routes/auth.routes.js create mode 100644 backend/routes/pitchType.routes.js create mode 100644 backend/routes/user.routes.js delete mode 100644 backend/sequelize/extra-setup.js delete mode 100644 backend/sequelize/index.js delete mode 100644 backend/sequelize/models/user.model.js delete mode 100644 backend/services/app.js delete mode 100644 backend/services/helpers.js delete mode 100644 backend/services/routes/pitchTypes.js delete mode 100644 backend/services/routes/users.js diff --git a/app/src/data/pitchers.ts b/app/src/data/pitchers.ts index f4c1686..edbc19e 100644 --- a/app/src/data/pitchers.ts +++ b/app/src/data/pitchers.ts @@ -5,89 +5,89 @@ export const pitchers: Map = new Map([ 1, { id: 1, - first_name: "Nolan", - last_name: "Ryan", - date_of_birth: new Date("1947-01-31") + firstName: "Nolan", + lastName: "Ryan", + dateOfBirth: new Date("1947-01-31") } ], [ 2, { id: 2, - first_name: "Sandy", - last_name: "Koufax", - date_of_birth: new Date("1935-12-30") + firstName: "Sandy", + lastName: "Koufax", + dateOfBirth: new Date("1935-12-30") } ], [ 3, { id: 3, - first_name: "Pedro", - last_name: "Martinez", - date_of_birth: new Date("1971-10-25") + firstName: "Pedro", + lastName: "Martinez", + dateOfBirth: new Date("1971-10-25") } ], [ 4, { id: 4, - first_name: "Randy", - last_name: "Johnson", - date_of_birth: new Date("1963-09-10") + firstName: "Randy", + lastName: "Johnson", + dateOfBirth: new Date("1963-09-10") } ], [ 5, { id: 5, - first_name: "Greg", - last_name: "Maddux", - date_of_birth: new Date("1966-04-14") + firstName: "Greg", + lastName: "Maddux", + dateOfBirth: new Date("1966-04-14") } ], [ 6, { id: 6, - first_name: "Bob", - last_name: "Gibson", - date_of_birth: new Date("1935-11-09") + firstName: "Bob", + lastName: "Gibson", + dateOfBirth: new Date("1935-11-09") } ], [ 7, { id: 7, - first_name: "Tom", - last_name: "Seaver", - date_of_birth: new Date("1944-11-17") + firstName: "Tom", + lastName: "Seaver", + dateOfBirth: new Date("1944-11-17") } ], [ 8, { id: 8, - first_name: "Roger", - last_name: "Clemens", - date_of_birth: new Date("1962-08-04") + firstName: "Roger", + lastName: "Clemens", + dateOfBirth: new Date("1962-08-04") } ], [ 9, { id: 9, - first_name: "Walter", - last_name: "Johnson", - date_of_birth: new Date("1887-11-06") + firstName: "Walter", + lastName: "Johnson", + dateOfBirth: new Date("1887-11-06") } ], [ 10, { id: 10, - first_name: "Clayton", - last_name: "Kershaw", - date_of_birth: new Date("1988-03-19") + firstName: "Clayton", + lastName: "Kershaw", + dateOfBirth: new Date("1988-03-19") } ]]); \ No newline at end of file diff --git a/app/src/router/index.ts b/app/src/router/index.ts index cc452b1..46b9f9e 100644 --- a/app/src/router/index.ts +++ b/app/src/router/index.ts @@ -1,6 +1,7 @@ import { createRouter, createWebHistory } from '@ionic/vue-router'; import { RouteRecordRaw } from 'vue-router'; import PitcherList from '../views/PitcherList.vue' +import Login from '../views/Login.vue' import PreparePitch from "@/views/PreparePitch.vue"; import FinalizePitch from "@/views/FinalizePitch.vue"; import BullpenStats from "@/views/BullpenStats.vue"; @@ -8,7 +9,11 @@ import BullpenStats from "@/views/BullpenStats.vue"; const routes: Array = [ { path: '/', - redirect: '/pitchers' + redirect: '/login' + }, { + path: '/login', + name: 'Login', + component: Login }, { path: '/pitchers', name: 'Pitchers', diff --git a/app/src/services/BullpenSessionService.ts b/app/src/services/BullpenSessionService.ts index 562feee..fe415f2 100644 --- a/app/src/services/BullpenSessionService.ts +++ b/app/src/services/BullpenSessionService.ts @@ -6,9 +6,13 @@ export class BullpenSessionService { private static instance: BullpenSessionService; private static nullPitcher: Pitcher = { id: 0, - first_name: "", - last_name: "", - date_of_birth: new Date(0) + firstName: "", + lastName: "", + email: "", + password: "", + dateOfBirth: new Date(0), + createdAt: new Date(0), + updatedAt: new Date(0), }; private static nullPitchType: PitchType = { id: 0, diff --git a/app/src/services/PitchTypeService.ts b/app/src/services/PitchTypeService.ts index 3ef621a..4bc348b 100644 --- a/app/src/services/PitchTypeService.ts +++ b/app/src/services/PitchTypeService.ts @@ -1,5 +1,12 @@ -import {pitchTypes} from "@/data/pitchTypes"; import PitchType from "@/types/PitchType"; +import axios, {AxiosInstance} from "axios"; + +const apiClient: AxiosInstance = axios.create({ + baseURL: "http://localhost:8080/api", + headers: { + "Content-type": "application/json", + }, +}); class PitchTypeService { private static instance: PitchTypeService; @@ -15,16 +22,18 @@ class PitchTypeService { } async getAllPitchTypes(): Promise { - return pitchTypes.values().toArray(); + const users = await apiClient.get('/pitch_types'); + return users.data; } async getPitchType(id: number): Promise { - const pitcher = pitchTypes.get(id); - if (pitcher !== undefined) { - return pitcher; - } else { - return Promise.reject(); - } + const pitchType = await apiClient.get(`/pitch_types/${id}`); + return pitchType.data; + // if (pitcher !== undefined) { + // return pitcher; + // } else { + // return Promise.reject(); + // } } } diff --git a/app/src/services/PitcherService.ts b/app/src/services/PitcherService.ts index e9eface..186c694 100644 --- a/app/src/services/PitcherService.ts +++ b/app/src/services/PitcherService.ts @@ -1,5 +1,4 @@ import Pitcher from "@/types/Pitcher"; -import {pitchers} from "@/data/pitchers"; import axios, { AxiosInstance } from "axios"; const apiClient: AxiosInstance = axios.create({ @@ -22,12 +21,13 @@ class PitcherService { } async getAllPitchers(): Promise { - return apiClient.get('/pitchers') - // return pitchers.values().toArray(); + const users = await apiClient.get('/users'); + return users.data; } async getPitcher(id: number): Promise { - return apiClient.get(`/pitchers/${id}`) + const pitcher = await apiClient.get(`/users/${id}`); + return pitcher.data; // const pitcher = pitchers.get(id); // if (pitcher !== undefined) { // return pitcher; diff --git a/app/src/types/Pitcher.ts b/app/src/types/Pitcher.ts index 9efec25..03c4f69 100644 --- a/app/src/types/Pitcher.ts +++ b/app/src/types/Pitcher.ts @@ -1,6 +1,10 @@ export default interface Pitcher { id: number, - first_name: string, - last_name: string, - date_of_birth: Date, + firstName: string, + lastName: string, + dateOfBirth: Date, + email: string, + createdAt: Date, + updatedAt: Date, + password: string } diff --git a/app/src/views/Login.vue b/app/src/views/Login.vue new file mode 100644 index 0000000..56ccac8 --- /dev/null +++ b/app/src/views/Login.vue @@ -0,0 +1,133 @@ + + + + + \ No newline at end of file diff --git a/app/src/views/PitcherList.vue b/app/src/views/PitcherList.vue index 775ab92..4c60919 100644 --- a/app/src/views/PitcherList.vue +++ b/app/src/views/PitcherList.vue @@ -15,7 +15,7 @@ - {{ pitcher.first_name }} {{ pitcher.last_name }} + {{ pitcher.firstName }} {{ pitcher.lastName }} diff --git a/backend/config/auth.config.js b/backend/config/auth.config.js new file mode 100644 index 0000000..af453d5 --- /dev/null +++ b/backend/config/auth.config.js @@ -0,0 +1,3 @@ +module.exports = { + secret: "bullpen-secret-key" +}; \ No newline at end of file diff --git a/backend/config/config.json b/backend/config/config.json deleted file mode 100644 index 2faa8ff..0000000 --- a/backend/config/config.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "development": { - "username": "test", - "password": "test123!", - "database": "bullpen", - "host": "127.0.0.1", - "dialect": "postgres" - }, - "test": { - "username": "test", - "password": "test123!", - "database": "bullpen", - "host": "127.0.0.1", - "dialect": "postgres" - }, - "production": { - "username": "test", - "password": "test123!", - "database": "bullpen", - "host": "127.0.0.1", - "dialect": "postgres" - } -} diff --git a/backend/config/postgres.config.js b/backend/config/postgres.config.js new file mode 100644 index 0000000..13c21e3 --- /dev/null +++ b/backend/config/postgres.config.js @@ -0,0 +1,13 @@ +module.exports = { + HOST: "localhost", + USER: "postgres", + PASSWORD: "123", + DB: "testdb", + dialect: "postgres", + pool: { + max: 5, + min: 0, + acquire: 30000, + idle: 10000 + } +}; \ No newline at end of file diff --git a/backend/config/sqlite.config.js b/backend/config/sqlite.config.js new file mode 100644 index 0000000..cacebfb --- /dev/null +++ b/backend/config/sqlite.config.js @@ -0,0 +1,6 @@ +module.exports = { + dialect: 'sqlite', + storage: 'database/example-db.sqlite', + logQueryParameters: true, + benchmark: true +} diff --git a/backend/controllers/auth.controller.js b/backend/controllers/auth.controller.js new file mode 100644 index 0000000..de7bff7 --- /dev/null +++ b/backend/controllers/auth.controller.js @@ -0,0 +1,93 @@ +const db = require("../models"); +const config = require("../config/auth.config"); +const User = db.user; +const Role = db.role; + +const Op = db.Sequelize.Op; + +const jwt = require("jsonwebtoken"); +const bcrypt = require("bcryptjs"); + +exports.signup = (req, res) => { + // Save User to Database + User.create({ + firstName: req.body.firstName, + lastName: req.body.lastName, + email: req.body.email, + dateOfBirth: req.body.dateOfBirth, + password: bcrypt.hashSync(req.body.password, 8) + }) + .then(user => { + if (req.body.roles) { + Role.findAll({ + where: { + name: { + [Op.or]: req.body.roles + } + } + }).then(roles => { + user.setRoles(roles).then(() => { + res.send({ message: "User registered successfully!" }); + }); + }); + } else { + // user role = 1 + user.setRoles([1]).then(() => { + res.send({ message: "User registered successfully!" }); + }); + } + }) + .catch(err => { + res.status(500).send({ message: err.message }); + }); +}; + +exports.signin = (req, res) => { + User.findOne({ + where: { + username: req.body.username + } + }) + .then(user => { + if (!user) { + return res.status(404).send({ message: "User Not found." }); + } + + const passwordIsValid = bcrypt.compareSync( + req.body.password, + user.password + ); + + if (!passwordIsValid) { + return res.status(401).send({ + accessToken: null, + message: "Invalid Password!" + }); + } + + const token = jwt.sign({ id: user.id }, + config.secret, + { + algorithm: 'HS256', + allowInsecureKeySizes: true, + expiresIn: 86400, // 24 hours + }); + + const authorities = []; + user.getRoles().then(roles => { + for (let i = 0; i < roles.length; i++) { + authorities.push("ROLE_" + roles[i].name.toUpperCase()); + } + res.status(200).send({ + id: user.id, + username: user.username, + email: user.email, + roles: authorities, + accessToken: token + }); + }); + }) + .catch(err => { + res.status(500).send({ message: err.message }); + }); +}; \ No newline at end of file diff --git a/backend/controllers/pitchType.controller.js b/backend/controllers/pitchType.controller.js new file mode 100644 index 0000000..a96ade7 --- /dev/null +++ b/backend/controllers/pitchType.controller.js @@ -0,0 +1,36 @@ +const db = require("../models"); +const PitchType = db.pitchType; +const Op = db.Sequelize.Op; + +exports.findAll = (req, res) => { + PitchType.findAll() + .then(data => { + res.send(data); + }) + .catch(err => { + res.status(500).send({ + message: + err.message || "Some error occurred while retrieving pitch types." + }); + }); +}; + +exports.findOne = (req, res) => { + const id = req.params.id; + + PitchType.findByPk(id) + .then(data => { + if (data) { + res.send(data); + } else { + res.status(404).send({ + message: `Cannot find pitch type with id=${id}.` + }); + } + }) + .catch(err => { + res.status(500).send({ + message: "Error retrieving pitch type with id=" + id + }); + }); +}; diff --git a/backend/controllers/user.controller.js b/backend/controllers/user.controller.js new file mode 100644 index 0000000..68dc303 --- /dev/null +++ b/backend/controllers/user.controller.js @@ -0,0 +1,55 @@ +const db = require("../models"); +const User = db.user; +const Op = db.Sequelize.Op; + +exports.findAll = (req, res) => { + const title = req.query.title; + const condition = title ? {title: {[Op.iLike]: `%${title}%`}} : null; + + User.findAll({ where: condition }) + .then(data => { + res.send(data); + }) + .catch(err => { + res.status(500).send({ + message: + err.message || "Some error occurred while retrieving users." + }); + }); +}; + +exports.findOne = (req, res) => { + const id = req.params.id; + + User.findByPk(id) + .then(data => { + if (data) { + res.send(data); + } else { + res.status(404).send({ + message: `Cannot find user with id=${id}.` + }); + } + }) + .catch(err => { + res.status(500).send({ + message: "Error retrieving user with id=" + id + }); + }); +}; + +// exports.allAccess = (req, res) => { +// res.status(200).send("Public Content."); +// }; +// +// exports.userBoard = (req, res) => { +// res.status(200).send("User Content."); +// }; +// +// exports.adminBoard = (req, res) => { +// res.status(200).send("Admin Content."); +// }; +// +// exports.moderatorBoard = (req, res) => { +// res.status(200).send("Moderator Content."); +// }; diff --git a/backend/database/example-db.sqlite b/backend/database/example-db.sqlite index 2f452048ec34f2bf038001a065f267ec82a3d923..8519508b92bd06f0342aaea8ca3bf67a2a3cf188 100644 GIT binary patch literal 45056 zcmeI*&u`mg7zc1WPMoG)V_+IlDP3J5#Gx?A#@5V1cW z4t^yJ`X39ymEqTwACxyvyc7IoFc&!Ke?0JOw;iz^0uX=z1R!w41-4T|{?NpPxMRA; zTG6aIroC!cil(!d8cbxgcurGu@wue7Jx}Gw$Hkp>UVfR%XISxOqOG)=Z*I@4XY%W| zX}D$~?&j5NRAWw0X=-{gr>2*a$#ZJHRxR|(W?|`tysD>j+6^t!+*8eH*R_n6PH0&* z&x2vdvI_aIx|mk4X-SQOPsFo{_%)4fSzclX-`&EFON}_NV%8Ana1}3Q^i({vqAqAF z6i_4lz2k+$u@iDg8y5wuTrlrDcZ&4Q&Z<#!EByUxlWsNEOb%_oKj;sgJuB`^bN{(o zu~;?BS<`W>O4-@>HqhF0YJOjCqcKg-)^Ov?xkWuqyQj2tjzTu967?H(-}C&GyJ@*y z^ZM)as^zY4w)akJZ+GjItX3aX&v$qD;ZndK8XXlsyT<{S_yL^8%RoC=W9wm{+y_a`jy+m`>+ek1pLR`uR2N;ahuM z%8pa-Ri2VNbS%#}Oy8D`67}uvcqVZ(o;fph{(N}v8nm3&Z!K%=Oj%pA&AXQ2()rk> zFqP`#tO##k_4-4hkhrtjyae;huj6TNWb4WCLMQo#Wjk)Vdm}0CqH$0Ob_rkHn6qqm zv-4HnEE!g@i;UW2tQwAUuVT~XzN2@agiDglE}|up2bmwAQ_ ziF34PSb0sD;+-;fP^ZX4GXI`odO!}{nCN!#-#ZspV|!`8@{cgU_TGxhd<<+~;fLd& zdi;y8E!?(`!xtI@J;r#S=3$>4x*Y1(UOrgsA9RjH4dksHri6F+dqKGy{3Cdq5?CMr z0SG_<0uX=z1Rwwb2teQ^7T6m0287V4ekG*qwoxuT_^7g3b}HrA?1kycSadQvt4>W{ zibm;QBpQvXXL*vBj5LZ~ZOo5F)+;5RF%_LUKN+27`^IK3vE463W@g@N?AtqcOOYs^ zF+EQ4ESUwnk}_=9Dx06gW-rDjW3kDpnN9&!SqvQJQUH0ewiZ5 zGNFY^ZNs=9o1MNe8KrnS0y(T;%W&@ok5ZKTECFLhU`CIvuUa&v_ z0uX=z1Rwwb2tWV=5P$##AaJAvf|4{LwmjDm@JJz%KLg+mh?L~dGD!9N|I>o zAE^c+SO`D>0uX=z1Rwwb2tWV=5P-mcB#@GP!rQuT6iQava$MVRE1iFx{$kI^B6f4< zJw9Pv*GrXx$*QvZsY?@m!YN&^(cjGJry6$u?;rk6P`*~yl~C|$@YCSC^cD*QAOHaf zKmY;|fB*y_009UbIe`VgER39_r{0S-`>vTy-tK(6_+k(CFUYbm`YKg+&ory1o&BJ% z%6#?65;fN~?Zk4fpURS6Ss0_p5}QW(L$g**-0Y{ed5J<}?JQb!ubxfzQ&-9(3*#&@ z=D%hXi;1~@s!NLXhF+(?6Ul13-dAnx`G3FiL{NUDr~kiG{!)Hco+#fOxj~MAApijg zKmY;|fB*y_009U<00PHGU{D$n>$5oiA!$@>%)!WPbA1ZMcS0HycPBu+_2O*)LK>FF zsYqk`!80U{1$Ji-*w6pzKmEf30SG_<0uX=z1Rwwb2tWV=5P-lj7Qp@gG45P64FV8= z00bZa0SG_<0uX=z1R&55!2Lgl0t6rc0SG_<0uX=z1Rwwb2teTY3$W+^8^8b0k^x!= nX!X-7)9RyDqSZqy76?E90uX=z1Rwwb2tWV=5P$##j;O%Dw2wSD delta 1198 zcmbV~&1(}u7{+HdAG6zJXB+%jVu(qsNJDMA-8K~iqA?I%l9bY<)tv0stR}K33GU=UFLzf} z%7KTkIPg-}c)nrZlOL04Y(n}!vo+}OPF;3*OAS@lR+_R#Ud0l#|48c`#{%Ot#T^3f z2Y1*B>0h}yOnDNp7iGDrD<)!~hli`CVwhgm`C5oQ0NguH=1$^0yor { + console.log('Drop and Re-sync Database with { force: true }'); + initial(); +}); + +// simple route +app.get("/", (req, res) => { + res.json({ message: "Welcome to bullpen application." }); +}); + +// routes +require('./routes/auth.routes')(app); +require('./routes/user.routes')(app); +require('./routes/pitchType.routes')(app); + +// set port, listen for requests +const PORT = process.env.PORT || 8080; +app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}.`); +}); + +function initial() { + Role.bulkCreate([ + { name: 'user' }, + { name: 'moderator' }, + { name: 'administrato' }, + ]); + User.bulkCreate([ + { firstName: 'Nolan', lastName: 'Ryan', dateOfBirth: new Date(1947, 1, 31), email: 'ryan.nolan@bullpen.com', password: 'nolan' }, + { firstName: 'Sandy', lastName: 'Koufax', dateOfBirth: new Date(1935, 12, 30), email: 'sandy.koufax@bullpen.com', password: 'sandy' }, + { firstName: 'Pedro', lastName: 'Martinez', dateOfBirth: new Date(1971, 10, 25), email: 'pedro.martinez@bullpen.com', password: 'pedro' }, + { firstName: 'randy', lastName: 'johnson', dateOfBirth: new Date(1963, 9, 10), email: 'randy.johnson@bullpen.com', password: 'randy' }, + ]); + 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' }, + ]); } - -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')); \ No newline at end of file diff --git a/backend/middleware/authJwt.js b/backend/middleware/authJwt.js new file mode 100644 index 0000000..659c406 --- /dev/null +++ b/backend/middleware/authJwt.js @@ -0,0 +1,90 @@ +const jwt = require("jsonwebtoken"); +const config = require("../config/auth.config.js"); +const db = require("../models"); +const User = db.user; + +verifyToken = (req, res, next) => { + let token = req.headers["x-access-token"]; + + if (!token) { + return res.status(403).send({ + message: "No token provided!" + }); + } + + jwt.verify(token, + config.secret, + (err, decoded) => { + if (err) { + return res.status(401).send({ + message: "Unauthorized!", + }); + } + req.userId = decoded.id; + next(); + }); +}; + +isAdmin = (req, res, next) => { + User.findByPk(req.userId).then(user => { + user.getRoles().then(roles => { + for (let i = 0; i < roles.length; i++) { + if (roles[i].name === "admin") { + next(); + return; + } + } + + res.status(403).send({ + message: "Require Admin Role!" + }); + }); + }); +}; + +isModerator = (req, res, next) => { + User.findByPk(req.userId).then(user => { + user.getRoles().then(roles => { + for (let i = 0; i < roles.length; i++) { + if (roles[i].name === "moderator") { + next(); + return; + } + } + + res.status(403).send({ + message: "Require Moderator Role!" + }); + }); + }); +}; + +isModeratorOrAdmin = (req, res, next) => { + User.findByPk(req.userId).then(user => { + user.getRoles().then(roles => { + for (let i = 0; i < roles.length; i++) { + if (roles[i].name === "moderator") { + next(); + return; + } + + if (roles[i].name === "admin") { + next(); + return; + } + } + + res.status(403).send({ + message: "Require Moderator or Admin Role!" + }); + }); + }); +}; + +const authJwt = { + verifyToken: verifyToken, + isAdmin: isAdmin, + isModerator: isModerator, + isModeratorOrAdmin: isModeratorOrAdmin +}; +module.exports = authJwt; \ No newline at end of file diff --git a/backend/middleware/index.js b/backend/middleware/index.js new file mode 100644 index 0000000..79c256d --- /dev/null +++ b/backend/middleware/index.js @@ -0,0 +1,7 @@ +const authJwt = require("./authJwt"); +const verifySignUp = require("./verifySignUp"); + +module.exports = { + authJwt, + verifySignUp +}; diff --git a/backend/middleware/verifySignUp.js b/backend/middleware/verifySignUp.js new file mode 100644 index 0000000..c9e8c9c --- /dev/null +++ b/backend/middleware/verifySignUp.js @@ -0,0 +1,43 @@ +const db = require("../models"); +const ROLES = db.ROLES; +const User = db.user; + +checkDuplicateUsernameOrEmail = (req, res, next) => { + // Username + User.findOne({ + where: { + email: req.body.email + } + }).then(user => { + if (user) { + res.status(400).send({ + message: "Failed! Email is already in use!" + }); + return; + } + + next(); + }); +}; + +checkRolesExisted = (req, res, next) => { + if (req.body.roles) { + for (let i = 0; i < req.body.roles.length; i++) { + if (!ROLES.includes(req.body.roles[i])) { + res.status(400).send({ + message: "Failed! Role does not exist = " + req.body.roles[i] + }); + return; + } + } + } + + next(); +}; + +const verifySignUp = { + checkDuplicateUsernameOrEmail: checkDuplicateUsernameOrEmail, + checkRolesExisted: checkRolesExisted +}; + +module.exports = verifySignUp; \ No newline at end of file diff --git a/backend/sequelize/models/bullpenSession.model.js b/backend/models/bullpenSession.model.js similarity index 62% rename from backend/sequelize/models/bullpenSession.model.js rename to backend/models/bullpenSession.model.js index ba4078e..05fb714 100644 --- a/backend/sequelize/models/bullpenSession.model.js +++ b/backend/models/bullpenSession.model.js @@ -1,21 +1,21 @@ const { DataTypes } = require('sequelize'); module.exports = (sequelize) => { - sequelize.define('bullpenSession', { + const BullpenSession = 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' - } - }, + // pitcher: { + // type: DataTypes.INTEGER, + // allowNull: false, + // references: { + // model: 'User', + // key: 'id' + // } + // }, aimedArea: { type: DataTypes.INTEGER, allowNull: false @@ -28,5 +28,7 @@ module.exports = (sequelize) => { sequelize, modelName: 'BullpenSession', tableName: 'BullpenSessions', - }) + }); + + return BullpenSession; }; diff --git a/backend/models/index.js b/backend/models/index.js new file mode 100644 index 0000000..cadb198 --- /dev/null +++ b/backend/models/index.js @@ -0,0 +1,54 @@ +const config = require("../config/sqlite.config"); + +const Sequelize = require("sequelize"); +const sequelize = new Sequelize({ + dialect: config.dialect, + storage: config.storage, + logQueryParameters: config.logQueryParameters, + benchmark: config.benchmark +}); + +// const sequelize = new Sequelize( +// config.DB, +// config.USER, +// config.PASSWORD, +// { +// host: config.HOST, +// dialect: config.dialect, +// pool: { +// max: config.pool.max, +// min: config.pool.min, +// acquire: config.pool.acquire, +// idle: config.pool.idle +// } +// } +// ); + +const db = {}; + +db.Sequelize = Sequelize; +db.sequelize = sequelize; + +db.user = require("../models/user.model.js")(sequelize); +db.role = require("../models/role.model.js")(sequelize); +db.pitchType = require("../models/pitchType.model.js")(sequelize); +db.pitch = require("../models/pitch.model.js")(sequelize); +db.bullpenSession = require("../models/bullpenSession.model.js")(sequelize); + +db.role.belongsToMany(db.user, { + through: "UserRoles" +}); +db.user.belongsToMany(db.role, { + through: "UserRoles" +}); + +db.ROLES = ["user", "admin", "moderator"]; + +db.bullpenSession.hasMany(db.pitch); +db.bullpenSession.belongsTo(db.user, { + as: "pitcher" +}); +db.pitch.belongsTo(db.bullpenSession); +db.pitch.belongsTo(db.pitchType); + +module.exports = db; \ No newline at end of file diff --git a/backend/sequelize/models/pitch.model.js b/backend/models/pitch.model.js similarity index 92% rename from backend/sequelize/models/pitch.model.js rename to backend/models/pitch.model.js index 043e9a9..7421d17 100644 --- a/backend/sequelize/models/pitch.model.js +++ b/backend/models/pitch.model.js @@ -1,7 +1,7 @@ const { DataTypes } = require('sequelize'); module.exports = (sequelize) => { - sequelize.define('pitch', { + const Pitch = sequelize.define('pitch', { id: { allowNull: false, autoIncrement: true, @@ -36,5 +36,7 @@ module.exports = (sequelize) => { sequelize, modelName: 'Pitch', tableName: 'Pitches', - }) + }); + + return Pitch; }; diff --git a/backend/sequelize/models/pitchType.model.js b/backend/models/pitchType.model.js similarity index 85% rename from backend/sequelize/models/pitchType.model.js rename to backend/models/pitchType.model.js index 85cc989..689e011 100644 --- a/backend/sequelize/models/pitchType.model.js +++ b/backend/models/pitchType.model.js @@ -1,7 +1,7 @@ const { DataTypes } = require('sequelize'); module.exports = (sequelize) => { - sequelize.define('pitchType', { + const PitchType = sequelize.define('pitchType', { id: { allowNull: false, autoIncrement: true, @@ -21,5 +21,7 @@ module.exports = (sequelize) => { sequelize, modelName: 'PitchType', tableName: 'PitchTypes' - }) + }); + + return PitchType; }; diff --git a/backend/models/role.model.js b/backend/models/role.model.js new file mode 100644 index 0000000..ef5ba7c --- /dev/null +++ b/backend/models/role.model.js @@ -0,0 +1,22 @@ +const { DataTypes } = require('sequelize'); + +module.exports = (sequelize) => { + const Role = sequelize.define("role", { + id: { + allowNull: false, + autoIncrement: true, + type: DataTypes.INTEGER, + primaryKey: true + }, + name: { + type: DataTypes.STRING, + allowNull: false, + } + }, { + sequelize, + modelName: 'Role', + tableName: "Roles", + }); + + return Role; +}; diff --git a/backend/models/user.model.js b/backend/models/user.model.js new file mode 100644 index 0000000..44f48cd --- /dev/null +++ b/backend/models/user.model.js @@ -0,0 +1,55 @@ +const bcrypt = require('bcryptjs'); +const { DataTypes } = require('sequelize'); + +module.exports = (sequelize) => { + const User = sequelize.define("user", { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + firstName: { + type: DataTypes.STRING, + allowNull: false + }, + lastName: { + type: DataTypes.STRING, + allowNull: false + }, + dateOfBirth: { + type: DataTypes.DATE, + 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", + hooks: { + beforeCreate: async (user) => { + const salt = await bcrypt.genSalt(10); + user.password = await bcrypt.hash(user.password, salt); + } + } + }); + + User.prototype.validPassword = async function (password) { + return bcrypt.compare(password, this.password); + }; + + return User; +}; \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index a696b53..865de27 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,8 +9,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcryptjs": "^3.0.2", "body-parser": "^1.20.3", + "cors": "^2.8.5", "express": "^4.21.2", + "express-validator": "^7.2.1", + "jsonwebtoken": "^9.0.2", "pg": "^8.13.3", "pg-hstore": "^2.3.4", "sequelize": "^6.37.5", @@ -286,6 +290,15 @@ ], "license": "MIT" }, + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -386,6 +399,12 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -754,6 +773,19 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -886,6 +918,15 @@ "dev": true, "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/editorconfig": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", @@ -1169,6 +1210,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-validator": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.1.tgz", + "integrity": "sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.12.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -1835,12 +1889,97 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -2420,6 +2559,15 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", diff --git a/backend/package.json b/backend/package.json index 1abf96f..78f601f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,8 +14,12 @@ "license": "ISC", "description": "", "dependencies": { + "bcryptjs": "^3.0.2", "body-parser": "^1.20.3", + "cors": "^2.8.5", "express": "^4.21.2", + "express-validator": "^7.2.1", + "jsonwebtoken": "^9.0.2", "pg": "^8.13.3", "pg-hstore": "^2.3.4", "sequelize": "^6.37.5", diff --git a/backend/routes/auth.routes.js b/backend/routes/auth.routes.js new file mode 100644 index 0000000..c611e08 --- /dev/null +++ b/backend/routes/auth.routes.js @@ -0,0 +1,23 @@ +const { verifySignUp } = require("../middleware"); +const controller = require("../controllers/auth.controller"); + +module.exports = function(app) { + app.use(function(req, res, next) { + res.header( + "Access-Control-Allow-Headers", + "x-access-token, Origin, Content-Type, Accept" + ); + next(); + }); + + app.post( + "/api/auth/signup", + [ + verifySignUp.checkDuplicateUsernameOrEmail, + verifySignUp.checkRolesExisted + ], + controller.signup + ); + + app.post("/api/auth/signin", controller.signin); +}; \ No newline at end of file diff --git a/backend/routes/pitchType.routes.js b/backend/routes/pitchType.routes.js new file mode 100644 index 0000000..96f409f --- /dev/null +++ b/backend/routes/pitchType.routes.js @@ -0,0 +1,15 @@ +const { authJwt } = require("../middleware"); +const controller = require("../controllers/pitchType.controller"); + +module.exports = function(app) { + app.use(function(req, res, next) { + res.header( + "Access-Control-Allow-Headers", + "x-access-token, Origin, Content-Type, Accept" + ); + next(); + }); + + app.get("/api/pitch_types", controller.findAll); + app.get("/api/pitch_types/:id", controller.findOne); +}; diff --git a/backend/routes/user.routes.js b/backend/routes/user.routes.js new file mode 100644 index 0000000..31a20eb --- /dev/null +++ b/backend/routes/user.routes.js @@ -0,0 +1,39 @@ +const { authJwt } = require("../middleware"); +const controller = require("../controllers/user.controller"); + +module.exports = function(app) { + app.use(function(req, res, next) { + res.header( + "Access-Control-Allow-Headers", + "x-access-token, Origin, Content-Type, Accept" + ); + next(); + }); + + app.get( + "/api/users", + [authJwt.verifyToken, authJwt.isAdmin], + controller.findAll); + app.get( + "/api/users/:id", + [authJwt.verifyToken], + controller.findOne); + + // app.get( + // "/api/test/user", + // [authJwt.verifyToken], + // controller.userBoard + // ); + // + // app.get( + // "/api/test/mod", + // [authJwt.verifyToken, authJwt.isModerator], + // controller.moderatorBoard + // ); + // + // app.get( + // "/api/test/admin", + // [authJwt.verifyToken, authJwt.isAdmin], + // controller.adminBoard + // ); +}; \ No newline at end of file diff --git a/backend/sequelize/extra-setup.js b/backend/sequelize/extra-setup.js deleted file mode 100644 index c90b3a0..0000000 --- a/backend/sequelize/extra-setup.js +++ /dev/null @@ -1,10 +0,0 @@ -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 }; \ No newline at end of file diff --git a/backend/sequelize/index.js b/backend/sequelize/index.js deleted file mode 100644 index 155a708..0000000 --- a/backend/sequelize/index.js +++ /dev/null @@ -1,30 +0,0 @@ -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; \ No newline at end of file diff --git a/backend/sequelize/models/user.model.js b/backend/sequelize/models/user.model.js deleted file mode 100644 index c281f4d..0000000 --- a/backend/sequelize/models/user.model.js +++ /dev/null @@ -1,46 +0,0 @@ -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 - }, - dateOfBirth: { - type: DataTypes.DATE, - 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", - }); -}; diff --git a/backend/services/app.js b/backend/services/app.js deleted file mode 100644 index f4e3325..0000000 --- a/backend/services/app.js +++ /dev/null @@ -1,71 +0,0 @@ -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(` -

Hello, Sequelize + Express!

-

Make sure you have executed npm run setup-example-db once to have a populated example database. Otherwise, you will get 'no such table' errors.

-

Try some routes, such as /api/users or /api/orchestras?includeInstruments!

-

To experiment with POST/PUT/DELETE requests, use a tool for creating HTTP requests such as HTTPie, Postman, or even the curl command, or write some JS code for it with got, ky or axios.

- `); -}); - -// 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; \ No newline at end of file diff --git a/backend/services/helpers.js b/backend/services/helpers.js deleted file mode 100644 index 98a35bd..0000000 --- a/backend/services/helpers.js +++ /dev/null @@ -1,11 +0,0 @@ -// 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 }; diff --git a/backend/services/routes/pitchTypes.js b/backend/services/routes/pitchTypes.js deleted file mode 100644 index 68dd95a..0000000 --- a/backend/services/routes/pitchTypes.js +++ /dev/null @@ -1,60 +0,0 @@ -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, -}; \ No newline at end of file diff --git a/backend/services/routes/users.js b/backend/services/routes/users.js deleted file mode 100644 index f53932b..0000000 --- a/backend/services/routes/users.js +++ /dev/null @@ -1,60 +0,0 @@ -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, -}; \ No newline at end of file