restructured user, auth, role models

This commit is contained in:
Sascha Kühl 2025-04-06 14:03:17 +02:00
parent 26edfd46fe
commit 69427f1809
19 changed files with 377 additions and 268 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.idea/
node_modules/
backend/.env

20
backend/.env.template Normal file
View File

@ -0,0 +1,20 @@
DB_TEST_USER=dev_user
DB_TEST_PASSWORD=dev_password
DB_TEST_NAME=dev_db
DB_TEST_HOST=127.0.0.1
DB_TEST_PORT=5432
DB_TEST_DIALECT=postgres
DB_DEV_USER=dev_user
DB_DEV_PASSWORD=dev_password
DB_DEV_NAME=dev_db
DB_DEV_HOST=127.0.0.1
DB_DEV_PORT=5432
DB_DEV_DIALECT=postgres
DB_PROD_USER=prod_user
DB_PROD_PASSWORD=prod_password
DB_PROD_NAME=prod_db
DB_PROD_HOST=prod-db-server.com
DB_PROD_PORT=5432
DB_PROD_DIALECT=postgres

View File

@ -1,6 +1,5 @@
const express = require("express");
const cors = require("cors");
const bcrypt = require("bcryptjs");
const app = express();
@ -14,13 +13,7 @@ app.use(express.json());
// parse requests of content-type - application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));
// database
const db = require("./models");
const Role = db.Role;
const User = db.User;
const PitchType = db.PitchType;
db.sequelize.sync();
// db.sequelize.sync();
// force: true will drop the table if it already exists
// db.sequelize.sync({force: true}).then(() => {
// console.log('Drop and Re-sync Database with { force: true }');
@ -38,33 +31,33 @@ require('./routes/user.routes')(app);
require('./routes/pitchType.routes')(app);
require('./routes/bullpenSession.routes')(app);
function initial() {
Role.bulkCreate([
{ name: 'user' },
{ name: 'administrator' },
]);
User.bulkCreate([
{ firstName: 'Nolan', lastName: 'Ryan', dateOfBirth: new Date(1947, 1, 31), email: 'ryan.nolan@bullpen.com', password: bcrypt.hashSync('nolan', 8) },
{ firstName: 'Sandy', lastName: 'Koufax', dateOfBirth: new Date(1935, 12, 30), email: 'sandy.koufax@bullpen.com', password: bcrypt.hashSync('sandy', 8) },
{ firstName: 'Pedro', lastName: 'Martinez', dateOfBirth: new Date(1971, 10, 25), email: 'pedro.martinez@bullpen.com', password: bcrypt.hashSync('pedro', 8) },
{ firstName: 'randy', lastName: 'johnson', dateOfBirth: new Date(1963, 9, 10), email: 'randy.johnson@bullpen.com', password: bcrypt.hashSync('randy', 8) }
]);
User.findAll().then(users => {
users.forEach(user => {
user.setRoles([1]);
});
});
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' },
]);
}
// function initial() {
// Role.bulkCreate([
// { name: 'user' },
// { name: 'administrator' },
// ]);
// User.bulkCreate([
// { firstName: 'Nolan', lastName: 'Ryan', dateOfBirth: new Date(1947, 1, 31), email: 'ryan.nolan@bullpen.com', password: bcrypt.hashSync('nolan', 8) },
// { firstName: 'Sandy', lastName: 'Koufax', dateOfBirth: new Date(1935, 12, 30), email: 'sandy.koufax@bullpen.com', password: bcrypt.hashSync('sandy', 8) },
// { firstName: 'Pedro', lastName: 'Martinez', dateOfBirth: new Date(1971, 10, 25), email: 'pedro.martinez@bullpen.com', password: bcrypt.hashSync('pedro', 8) },
// { firstName: 'randy', lastName: 'johnson', dateOfBirth: new Date(1963, 9, 10), email: 'randy.johnson@bullpen.com', password: bcrypt.hashSync('randy', 8) }
// ]);
//
// User.findAll().then(users => {
// users.forEach(user => {
// user.setRoles([1]);
// });
// });
//
// 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' },
// ]);
// }
module.exports = app;

View File

@ -1,38 +1,31 @@
require('dotenv').config();
module.exports = {
development: {
dialect: 'postgres',
username: 'tester',
password: 'test123',
database: 'bullpen',
host: 'localhost',
port: 15432,
logging: false,
},
test: {
dialect: 'postgres',
username: 'test',
password: 'test123!',
database: 'bullpen',
host: 'localhost',
port: 5432,
username: process.env.DB_TEST_USER,
password: process.env.DB_TEST_PASSWORD,
database: process.env.DB_TEST_NAME,
host: process.env.DB_TEST_HOST,
port: process.env.DB_TEST_PORT,
dialect: process.env.DB_TEST_DIALECT,
logging: false,
},
staging: {
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT,
development: {
username: process.env.DB_DEV_USER,
password: process.env.DB_DEV_PASSWORD,
database: process.env.DB_DEV_NAME,
host: process.env.DB_DEV_HOST,
port: process.env.DB_DEV_PORT,
dialect: process.env.DB_DEV_DIALECT,
logging: false,
},
production: {
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
host: process.env.DB_HOST,
dialect: process.env.DB_DIALECT,
username: process.env.DB_PROD_USER,
password: process.env.DB_PROD_PASSWORD,
database: process.env.DB_PROD_NAME,
host: process.env.DB_PROD_HOST,
port: process.env.DB_PROD_PORT,
dialect: process.env.DB_PROD_DIALECT,
logging: false,
},
};

View File

@ -1,6 +1,6 @@
const db = require("../models/index");
const config = require("../config/auth.config");
const { User: User, Role: Role, RefreshToken: RefreshToken } = db;
const { Auth: Auth, User: User, Role: Role, RefreshToken: RefreshToken } = db;
const Op = db.Sequelize.Op;
@ -8,83 +8,90 @@ const jwt = require("jsonwebtoken");
exports.register = (req, res) => {
// Save User to Database
User.create({
firstName: req.body.firstName,
lastName: req.body.lastName,
Auth.create({
email: req.body.email,
dateOfBirth: new Date(req.body.dateOfBirth),
password: req.body.password
})
.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!" });
}, (error) => {
console.log(JSON.stringify(error, null, 2));
});
}).then((auth) => {
User.create({
firstName: req.body.firstName,
lastName: req.body.lastName,
dateOfBirth: new Date(req.body.dateOfBirth),
authId: auth.id,
gender: 'male',
handedness: 'right'
}).then(user => {
if (!req.body.roles || !req.body.roles.length === 0) {
req.body.roles = ['player'];
}
})
.catch(err => {
Role.findAll({
where: {
name: {
[Op.or]: req.body.roles
}
}
}).then(roles => {
user.setRoles(roles).then(() => {
res.send({ message: "User registered successfully!" });
});
});
}).catch(err => {
res.status(500).send({ message: err.message });
});
}).catch(err => {
res.status(500).send({ message: err.message });
});
};
exports.login = (req, res) => {
User.findOne({
Auth.findOne({
where: {
email: req.body.email
}
})
.then(async (user) => {
if (!user) {
.then(async (auth) => {
if (!auth) {
return res.status(404).send({ message: "User Not found." });
}
const passwordIsValid = user.validPassword(req.body.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: config.jwtExpiration
});
let refreshToken = await RefreshToken.createToken(user);
const authorities = [];
user.getRoles().then(roles => {
for (let i = 0; i < roles.length; i++) {
authorities.push("ROLE_" + roles[i].name.toUpperCase());
await User.findOne({
where: {
authId: auth.id
}
res.status(200).send({
id: user.id,
username: user.username,
email: user.email,
roles: authorities,
accessToken: token,
refreshToken: refreshToken
}).then(async (user) => {
const passwordIsValid = auth.validPassword(req.body.password);
if (!passwordIsValid) {
return res.status(401).send({
accessToken: null,
message: "Invalid Password!"
});
}
const token = jwt.sign({ id: auth.id },
config.secret,
{
algorithm: 'HS256',
allowInsecureKeySizes: true,
expiresIn: config.jwtExpiration
});
let refreshToken = await RefreshToken.createToken(auth);
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,
email: auth.email,
roles: authorities,
accessToken: token,
refreshToken: refreshToken
});
});
}).catch(err => {
res.status(500).send({ message: err.message });
});
})
.catch(err => {
@ -92,9 +99,9 @@ exports.login = (req, res) => {
});
};
exports.logout = (req, res) => {
}
// exports.logout = (req, res) => {
//
// }
exports.refreshToken = async (req, res) => {
const { refreshToken: requestToken } = req.body;
@ -122,8 +129,7 @@ exports.refreshToken = async (req, res) => {
return;
}
const user = await refreshToken.getUser();
let newAccessToken = jwt.sign({ id: user.id }, config.secret, {
let newAccessToken = jwt.sign({ id: auth.id }, config.secret, {
expiresIn: config.jwtExpiration,
});

View File

@ -1,14 +1,14 @@
const db = require("../models/index");
const User = db.User;
const Auth = db.Auth;
checkDuplicateUsernameOrEmail = (req, res, next) => {
// Username
User.findOne({
Auth.findOne({
where: {
email: req.body.email
}
}).then(user => {
if (user) {
}).then(auth => {
if (auth) {
res.status(400).send({
message: "Failed! Email is already in use!"
});
@ -21,6 +21,7 @@ checkDuplicateUsernameOrEmail = (req, res, next) => {
checkRolesExisted = (req, res, next) => {
if (req.body.roles) {
const ROLES = ['player', 'coach', 'admin'];
for (let i = 0; i < req.body.roles.length; i++) {
if (!ROLES.includes(req.body.roles[i])) {
res.status(400).send({

View File

@ -0,0 +1,34 @@
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('Authentications', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
validate: {isEmail: true},
},
password: {
type: Sequelize.STRING,
allowNull: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
async down(queryInterface, /*Sequelize*/) {
await queryInterface.dropTable('Authentications');
}
};

View File

@ -1,4 +1,3 @@
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
@ -9,6 +8,15 @@ module.exports = {
primaryKey: true,
type: Sequelize.INTEGER
},
authId: {
type: Sequelize.INTEGER,
references: {
model: 'Authentications',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
firstName: {
allowNull: false,
type: Sequelize.STRING
@ -17,18 +25,29 @@ module.exports = {
allowNull: false,
type: Sequelize.STRING
},
email: {
allowNull: false,
type: Sequelize.STRING
},
dateOfBirth: {
allowNull: false,
type: Sequelize.DATE
},
password: {
allowNull: false,
type: Sequelize.STRING
height: {
type: Sequelize.FLOAT
},
weight: {
type: Sequelize.FLOAT
},
gender: {
allowNull: false,
type: Sequelize.ENUM('male', 'female', 'other'),
},
handedness: {
type: Sequelize.ENUM('left', 'right', 'both'),
},
// position: {
// type: DataTypes.ENUM
// },
// preferredPosition: {
// type: DataTypes.ENUM
// }
createdAt: {
allowNull: false,
type: Sequelize.DATE
@ -39,7 +58,7 @@ module.exports = {
}
});
},
async down(queryInterface, Sequelize) {
async down(queryInterface, /*Sequelize*/) {
await queryInterface.dropTable('Users');
}
};
};

View File

@ -12,10 +12,10 @@ module.exports = {
token: {
type: Sequelize.STRING
},
userId: {
authId: {
type: Sequelize.INTEGER,
references: {
model: 'Users',
model: 'Authentications',
key: 'id'
},
onUpdate: 'CASCADE',
@ -34,7 +34,7 @@ module.exports = {
}
});
},
async down(queryInterface, Sequelize) {
async down(queryInterface, /*Sequelize*/) {
await queryInterface.dropTable('RefreshTokens');
}
};

View File

@ -0,0 +1,61 @@
const { Model } = require('sequelize');
const bcrypt = require("bcryptjs");
module.exports = (sequelize, DataTypes) => {
class Auth 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) {
Auth.hasOne(models.User, {
foreignKey: 'authId',
as: 'user'
});
Auth.hasMany(models.RefreshToken, {
foreignKey: 'authId',
as: 'tokens'
});
}
}
Auth.init({
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {isEmail: true},
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
}, {
sequelize,
modelName: 'Auth',
tableName: 'Authentications',
timestamps: true,
hooks: {
beforeCreate: async (auth) => {
const salt = await bcrypt.genSalt(10);
auth.password = await bcrypt.hash(auth.password, salt);
}
}
}, {
defaultScope: {
attributes: { exclude: ['password'] },
},
scopes: {
withSecretColumns: {
attributes: { include: ['password'] },
},
},
});
Auth.prototype.validPassword = function (password) {
return bcrypt.compareSync(password, this.password);
};
return Auth;
}

View File

@ -1,52 +0,0 @@
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Member 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) {
// Member.belongsTo(models.User, {
// foreignKey: 'loginId',
// targetKey: 'id'
// });
}
}
Member.init({
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING,
allowNull: false
},
dateOfBirth: {
type: DataTypes.DATE,
allowNull: false
},
height: {
type: DataTypes.INTEGER
},
weight: {
type: DataTypes.INTEGER
},
// handedness: {
// type: DataTypes.ENUM('LeftHandedness', 'RightHandedness'),
// },
// position: {
// type: DataTypes.ENUM
// },
// preferredPosition: {
// type: DataTypes.ENUM
// }
}, {
sequelize,
modelName: 'Member',
tableName: "Members"
});
return Member;
};

View File

@ -10,8 +10,8 @@ module.exports = (sequelize, DataTypes) => {
* The `models/index` file will call this method automatically.
*/
static associate(models) {
RefreshToken.belongsTo(models.User, {
foreignKey: 'userId',
RefreshToken.belongsTo(models.Auth, {
foreignKey: 'authId',
targetKey: 'id'
});
}
@ -31,7 +31,7 @@ module.exports = (sequelize, DataTypes) => {
tableName: 'RefreshTokens'
});
RefreshToken.createToken = async function (user) {
RefreshToken.createToken = async function (auth) {
let expiredAt = new Date();
expiredAt.setSeconds(expiredAt.getSeconds() + config.jwtRefreshExpiration);
@ -40,7 +40,7 @@ module.exports = (sequelize, DataTypes) => {
let refreshToken = await this.create({
token: _token,
userId: user.id,
authId: auth.id,
expiryDate: expiredAt.getTime(),
});

View File

@ -1,5 +1,4 @@
const { Model } = require('sequelize');
const bcrypt = require("bcryptjs");
module.exports = (sequelize, DataTypes) => {
class User extends Model {
@ -14,9 +13,9 @@ module.exports = (sequelize, DataTypes) => {
foreignKey: 'userId',
as: 'roles',
});
User.hasOne(models.RefreshToken, {
foreignKey: 'userId',
targetKey: 'id'
User.belongsTo(models.Auth, {
foreignKey: 'authId',
as: 'auth'
});
}
}
@ -30,42 +29,33 @@ module.exports = (sequelize, DataTypes) => {
allowNull: false
},
dateOfBirth: {
type: DataTypes.DATE,
type: DataTypes.DATEONLY,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
height: {
type: DataTypes.FLOAT
},
password: {
type: DataTypes.STRING,
weight: {
type: DataTypes.FLOAT
},
gender: {
type: DataTypes.ENUM('male', 'female', 'other'),
allowNull: false
}
},
handedness: {
type: DataTypes.ENUM('left', 'right', 'both'),
},
// position: {
// type: DataTypes.ENUM
// },
// preferredPosition: {
// type: DataTypes.ENUM
// }
}, {
sequelize,
modelName: 'User',
tableName: "Users",
hooks: {
beforeCreate: async (user) => {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
}
}
}, {
defaultScope: {
attributes: { exclude: ['password'] },
},
scopes: {
withSecretColumns: {
attributes: { include: ['password'] },
},
},
tableName: "Users"
});
User.prototype.validPassword = function (password) {
return bcrypt.compareSync(password, this.password);
};
return User;
};

View File

@ -1,22 +1,14 @@
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
return queryInterface.bulkInsert('Roles', [{
id: 1,
name: 'user',
createdAt: new Date(),
updatedAt: new Date(),
}, {
id: 2,
name: 'administrator',
createdAt: new Date(),
updatedAt: new Date()
}]);
async up (queryInterface, /*Sequelize*/) {
return queryInterface.bulkInsert('Roles', [
{ name: 'player', createdAt: new Date(), updatedAt: new Date() },
{ name: 'coach', createdAt: new Date(), updatedAt: new Date() },
{ name: 'admin', createdAt: new Date(), updatedAt: new Date() }
]);
},
async down (queryInterface, Sequelize) {
async down (queryInterface, /*Sequelize*/) {
return queryInterface.dropTable('Roles');
}
};

View File

@ -2,19 +2,69 @@ const bcrypt = require("bcryptjs");
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
return queryInterface.bulkInsert('Users', [{
firstName: 'Nolan',
lastName: 'Ryan',
dateOfBirth: new Date(1947, 1, 31),
email: 'ryan.nolan@bullpen.com',
password: bcrypt.hashSync('nolan', 8),
createdAt: new Date(),
updatedAt: new Date(),
}]);
async up (queryInterface, /*Sequelize*/) {
await queryInterface.bulkInsert('Authentications', [
{ email: 'player@example.com', password: bcrypt.hashSync('hash1234', 8), createdAt: new Date(), updatedAt: new Date() },
{ email: 'coach@example.com', password: bcrypt.hashSync('hash2345', 8), createdAt: new Date(), updatedAt: new Date() },
{ email: 'admin@example.com', password: bcrypt.hashSync('hash3456', 8), createdAt: new Date(), updatedAt: new Date() },
]);
const auths = await queryInterface.select(null, 'Authentications');
const playerAuthId = auths.filter((auth) => auth.email === 'player@example.com').map((auth) => auth.id).shift();
const coachAuthId = auths.filter((auth) => auth.email === 'coach@example.com').map((auth) => auth.id).shift();
const adminAuthId = auths.filter((auth) => auth.email === 'admin@example.com').map((auth) => auth.id).shift();
await queryInterface.bulkInsert('Users', [{
firstName: 'Alice',
lastName: 'Player',
dateOfBirth: '1990-01-01',
gender: 'female',
handedness: 'right',
authId: playerAuthId,
createdAt: new Date(),
updatedAt: new Date(),
}, {
firstName: 'Bob',
lastName: 'Coach',
dateOfBirth: '1985-05-05',
gender: 'male',
handedness: 'left',
authId: coachAuthId,
createdAt: new Date(),
updatedAt: new Date(),
}, {
firstName: 'Charlie',
lastName: 'Admin',
dateOfBirth: '1980-03-03',
gender: 'other',
handedness: 'both',
authId: adminAuthId,
createdAt: new Date(),
updatedAt: new Date(),
}]
);
const users = await queryInterface.select(null, 'Users');
const aliceId = users.filter((user) => user.firstName === 'Alice').map((user) => user.id).shift();
const bobId = users.filter((user) => user.firstName === 'Bob').map((user) => user.id).shift();
const charlieId = users.filter((user) => user.firstName === 'Charlie').map((user) => user.id).shift();
const roles = await queryInterface.select(null, 'Roles');
const playerId = roles.filter((role) => role.name === 'player').map((role) => role.id).shift();
const coachId = roles.filter((role) => role.name === 'coach').map((role) => role.id).shift();
const adminId = roles.filter((role) => role.name === 'admin').map((role) => role.id).shift();
await queryInterface.bulkInsert('UserRoles', [
{ userId: aliceId, roleId: playerId, createdAt: new Date(), updatedAt: new Date() },
{ userId: bobId, roleId: coachId, createdAt: new Date(), updatedAt: new Date() },
{ userId: charlieId, roleId: adminId, createdAt: new Date(), updatedAt: new Date() },
]);
},
async down (queryInterface, Sequelize) {
return queryInterface.dropTable('Users');
async down (queryInterface, /*Sequelize*/) {
await queryInterface.dropTable('Authentications');
await queryInterface.dropTable('Users');
await queryInterface.dropTable('UserRoles');
}
};

View File

@ -14,8 +14,8 @@ describe("Test bullpen session", () => {
let response = await request(app)
.post("/api/auth/login")
.send({
email: 'ryan.nolan@bullpen.com',
password: 'nolan'
email: 'player@example.com',
password: 'hash1234'
});
const user = response.body;
@ -38,6 +38,6 @@ describe("Test bullpen session", () => {
.set('x-access-token', user.accessToken);
expect(response.statusCode).toBe(200);
console.log(JSON.stringify(response.body, null, 2));
// console.log(JSON.stringify(response.body, null, 2));
});
})

View File

@ -3,7 +3,8 @@ const signupUser = {
lastName: "Zimmer",
dateOfBirth: "1956-11-23",
email: "hans.zimmer@email.com",
password: "secret123"
password: "secret123",
roles: ['admin']
}
module.exports = {

View File

@ -12,8 +12,8 @@ describe("Test retrieving pitch types", () => {
let response = await request(app)
.post("/api/auth/login")
.send({
email: 'ryan.nolan@bullpen.com',
password: 'nolan'
email: 'player@example.com',
password: 'hash1234'
});
const user = response.body;

View File

@ -22,8 +22,8 @@ describe("Test user authentication", () => {
let response = await request(app)
.post("/api/auth/login")
.send({
email: 'ryan.nolan@bullpen.com',
password: 'nolan'
email: signupUser.email,
password: signupUser.password,
});
expect(response.statusCode).toBe(200);
expect(response.body.accessToken).not.toBeNull();