Skip to content
This repository has been archived by the owner on Nov 26, 2020. It is now read-only.

Commit

Permalink
Add POST /api/users/:id/games endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
derekhouck committed Jul 27, 2019
1 parent 07d0989 commit 35a4c7e
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 30 deletions.
50 changes: 24 additions & 26 deletions routes/games.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const passport = require('passport');
const Game = require('../models/game');
const Tag = require('../models/tag');

const { isValidId } = require('./validators');
const { isValidId, requiredFields } = require('./validators');

const jwtAuth = passport.authenticate('jwt', { session: false, failWithError: true });
const router = express.Router();
Expand Down Expand Up @@ -120,33 +120,31 @@ router.get('/:id', jwtAuth, isValidId, (req, res, next) => {
});

// POST /api/games
router.post('/', jwtAuth, validatePlayers, (req, res, next) => {
const { title, minPlayers, maxPlayers, tags } = req.body;
const userId = req.user.id;
router.post('/',
jwtAuth,
validatePlayers,
requiredFields('title'),
(req, res, next) => {
const { title, minPlayers, maxPlayers, tags } = req.body;
const userId = req.user.id;

if (!title) {
const err = new Error('Missing `title` in request body');
err.status = 400;
return next(err);
}
const newGame = {
title,
players: {
min: minPlayers,
max: maxPlayers
},
tags,
userId
};

const newGame = {
title,
players: {
min: minPlayers,
max: maxPlayers
},
tags,
userId
};

validateTagIds(newGame.tags, userId)
.then(() => Game.create(newGame))
.then(result => {
res.location(`${req.originalUrl}/${result.id}`).status(201).json(result);
})
.catch(err => next(err));
});
validateTagIds(newGame.tags, userId)
.then(() => Game.create(newGame))
.then(result => {
res.location(`${req.originalUrl}/${result.id}`).status(201).json(result);
})
.catch(err => next(err));
});

// PUT /api/games/:id
router.put('/:id',
Expand Down
25 changes: 24 additions & 1 deletion routes/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const passport = require('passport');
const User = require('../models/user');
const Game = require('../models/game');

const { isValidId } = require('./validators');
const { isFieldValidId, isValidId, requiredFields } = require('./validators');

const router = express.Router();

Expand Down Expand Up @@ -143,6 +143,29 @@ router.post('/',
});
});

router.post('/:id/games',
jwtAuth,
requiredFields('gameId'),
isFieldValidId('gameId'),
(req, res, next) => {
const { gameId } = req.body;
const userId = req.user.id;

return Game.findById(gameId)
.then(game => {
if (game) {
return User.findByIdAndUpdate(userId, {
$push: { "games": gameId }
}, { new: true })
} else {
return next();
}
})
.then(user => Game.find({ _id: { $in: user.games } }))
.then(games => res.json(games))
.catch(err => next(err));
});

// GET /api/users
router.get('/', jwtAuth, (req, res, next) => {
requiresAdmin(req, res, next);
Expand Down
36 changes: 35 additions & 1 deletion routes/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,38 @@ const isValidId = (req, res, next) => {
}
};

module.exports = { isValidId };
const isFieldValidId = idField => {
return (req, res, next) => {
const id = idField ? req.body[idField] : req.params.id;
if (!mongoose.Types.ObjectId.isValid(id)) {
const err = new Error('The `id` is not valid');
err.status = 400;
return next(err);
} else {
next();
}
}
};

const requiredFields = fields => {
return (req, res, next) => {
const missingFields = [];
if (!Array.isArray(fields)) {
fields = [fields];
}
fields.forEach(field => {
if (!(field in req.body) || !req.body[field]) {
missingFields.push(field);
}
});
if (missingFields.length > 0) {
const err = new Error(`Missing ${missingFields.join(' and ')} in request body`);
err.status = 400;
return next(err);
} else {
return next();
}
};
};

module.exports = { isFieldValidId, isValidId, requiredFields };
4 changes: 2 additions & 2 deletions test/games.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ describe('My Board Game Shelf API - Games', function () {
expect(res).to.have.status(400);
expect(res).to.be.json;
expect(res.body).to.be.a('object');
expect(res.body.message).to.equal('Missing `title` in request body');
expect(res.body.message).to.equal('Missing title in request body');
});
});

Expand All @@ -316,7 +316,7 @@ describe('My Board Game Shelf API - Games', function () {
expect(res).to.have.status(400);
expect(res).to.be.json;
expect(res.body).to.be.a('object');
expect(res.body.message).to.equal('Missing `title` in request body');
expect(res.body.message).to.equal('Missing title in request body');
});
});

Expand Down
84 changes: 84 additions & 0 deletions test/users.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,90 @@ describe("My Board Game Shelf API - Users", function () {
});
});

describe("POST /api/users/:id/games", function () {
it('should add a game when provided a valid gameId', function () {
return Game.findOne()
.then(game => {
const reqBody = { gameId: game.id };

return chai.request(app)
.post(`/api/users/${user.id}/games`)
.set('Authorization', `Bearer ${token}`)
.send(reqBody);
})
.then(res => {
expect(res).to.have.status(200);
expect(res.body).to.be.an('array');
expect(res.body.length).to.equal(user.games.length + 1);
});
});

it('should return an error when missing "gameId" field', function () {
const reqBody = {};

return chai.request(app)
.post(`/api/users/${user.id}/games`)
.set('Authorization', `Bearer ${token}`)
.send(reqBody)
.then(res => {
expect(res).to.have.status(400);
expect(res).to.be.json;
expect(res.body).to.be.a('object');
expect(res.body.message).to.equal('Missing gameId in request body');
});
});

it('should return an error when "gameId" is not valid', function () {
const reqBody = {
gameId: 'NOT-A-VALID-ID'
};

return chai.request(app)
.post(`/api/users/${user.id}/games`)
.set('Authorization', `Bearer ${token}`)
.send(reqBody)
.then(res => {
expect(res).to.have.status(400);
expect(res).to.be.json;
expect(res.body).to.be.a('object');
expect(res.body.message).to.equal('The `id` is not valid');
});
});

it('should respond with a 404 for an id that does not exist', function () {
const reqBody = {
gameId: 'DOESNOTEXIST'
}

return chai.request(app)
.post(`/api/users/${user.id}/games`)
.set('Authorization', `Bearer ${token}`)
.send(reqBody)
.then(res => {
expect(res).to.have.status(404);
})
});

it('should catch errors and respond properly', function () {
sandbox.stub(Game.schema.options.toJSON, 'transform').throws('FakeError');
return Game.findOne()
.then(game => {
const reqBody = { gameId: game.id };

return chai.request(app)
.post(`/api/users/${user.id}/games`)
.set('Authorization', `Bearer ${token}`)
.send(reqBody);
})
.then(res => {
expect(res).to.have.status(500);
expect(res).to.be.json;
expect(res.body).to.be.a('object');
expect(res.body.message).to.equal('Internal Server Error');
});
});
});

describe("GET /api/users/:id/games", function () {
it('should return the correct number of Games', function () {
return Promise.all([
Expand Down

0 comments on commit 35a4c7e

Please sign in to comment.