Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 44 additions & 20 deletions backend/common/errors/errorHandler.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,82 @@
const logger = require('../../misc/logger')

const logError = (error, request) => {
logger.error({
message: error.message,
error: {
stack: error.stack,
complete: error,
},
request: {
requestBody: request.body,
requestParams: request.params,
requestQuery: request.query,
},
})
}

const errorHandler = (error, request, response, next) => {
logError(error, request)
switch (error.name) {
case 'UnauthorizedError': {
return response.status(401).json({
message: error.message || 'Unauthorized',
status: 401,
})
}
case 'NotFoundError': {
return response.status(404).json({
message: error.message || 'Not found',
status: 404,
})
}
case 'InsufficientPrivilegesError': {
return response.status(401).json({
message: 'Insufficient privileges',
message: error.message || 'Insufficient privileges',
status: 401,
})
}
case 'ForbiddenError': {
case 'EmailVerificationError': {
return response.status(403).json({
message: error.message || 'Forbidden',
message: error.message || 'Email verification required',
status: 403,
})
}
case 'NotFoundError': {
return response.status(404).json({
message: error.message || 'Not found',
status: 404,
})
}
case 'ValidationError': {
return response.status(400).json({
message: error.message,
message: error.message || 'Validation error',
errors: error.errors,
status: 400,
})
}
case 'ForbiddenError': {
return response.status(403).json({
message: error.message || 'Forbidden',
status: 403,
})
}
case 'AlreadyExistsError': {
return response.status(400).json({
message: error.message || 'Already exists',
status: 400,
})
}
case 'MongoError': {
if (error.code === 11000) {
return response.status(400).json({
message: 'Report to tech support',
type: 'unique-violation',
status: 400,
})
}
break
return response.status(400).json({
message: error.message || 'Validation error',
status: 400,
})
}
default:
logger.error({
message: 'Unhandled error',
error: {
message: error.message,
stack: error.stack,
},
})

return response.status(500).json({
message: 'Unexpected error',
message: error.message || 'Unexpected error',
status: 500,
})
}
Expand Down
33 changes: 21 additions & 12 deletions backend/common/errors/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,26 @@ class CustomError extends Error {
}

class NotFoundError extends CustomError {
constructor(message) {
constructor(message, details) {
const name = 'NotFoundError'
const code = 1
super(message, name, code)
super(message, name, code, details)
}
}

class InsufficientPrivilegesError extends CustomError {
constructor(message) {
constructor(message, details) {
const name = 'InsufficientPrivilegesError'
const code = 2
super(message, name, code)
super(message, name, code, details)
}
}

class EmailVerificationError extends CustomError {
constructor(message) {
constructor(message, details) {
const name = 'EmailVerificationError'
const code = 3
super(message, name, code)
super(message, name, code, details)
}
}

Expand All @@ -50,26 +50,34 @@ class ValidationError extends CustomError {
}

class ForbiddenError extends CustomError {
constructor(message) {
constructor(message, details) {
const name = 'ForbiddenError'
const code = 5
super(message, name, code)
super(message, name, code, details)
}
}

class UnauthorizedError extends CustomError {
constructor(message) {
constructor(message, details) {
const name = 'UnauthorizedError'
const code = 6
super(message, name, code)
super(message, name, code, details)
}
}

class AlreadyExistsError extends CustomError {
constructor(message) {
constructor(message, details) {
const name = 'AlreadyExistsError'
const code = 7
super(message, name, code)
super(message, name, code, details)
}
}

class MongoError extends CustomError {
constructor(message, details) {
const name = 'MongoError'
const code = 8
super(message, name, code, details)
}
}

Expand All @@ -81,4 +89,5 @@ module.exports = {
ForbiddenError,
UnauthorizedError,
AlreadyExistsError,
MongoError,
}
3 changes: 1 addition & 2 deletions backend/modules/voting-token/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ controller.getVotesByProject = async event => {
})
return result
}, [])
const sorted = _.orderBy(results, ['votes'], 'desc')
return sorted
return results
}

controller.getToken = id => {
Expand Down
46 changes: 43 additions & 3 deletions backend/modules/winner-votes/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const _ = require('lodash')
const WinnerVote = require('./model')
const projectController = require('../project/controller')
const tokenVotingController = require('../voting-token/controller')
const { ValidationError, MongoError } = require('../../common/errors/errors')

const controller = {}

Expand All @@ -17,8 +18,7 @@ controller.getFinalistProjectsWithAllVotes = async event => {
return projectObject
})
const userVotes = await controller.getVotesForEvent(event)
const tokenVotes = await tokenVotingController.getVotesByProject(event._id)
if (userVotes) {
if (userVotes && userVotes.length > 0) {
userVotes.map(v => {
const projectWithVotes = _.find(
finalistProjectsWithVotes,
Expand All @@ -29,7 +29,8 @@ controller.getFinalistProjectsWithAllVotes = async event => {
}
})
}
if (tokenVotes) {
const tokenVotes = await tokenVotingController.getVotesByProject(event._id)
if (tokenVotes && tokenVotes.length > 0) {
tokenVotes.map(v => {
const projectWithVotes = _.find(
finalistProjectsWithVotes,
Expand Down Expand Up @@ -65,4 +66,43 @@ controller.getVotesForEvent = async event => {
return results
}

controller.submitVote = async (eventId, userId, projectId) => {
if (!eventId || !projectId || !userId) {
throw new ValidationError(
'Some data is missing, try again. If the problem persists, contact support.',
)
}

let vote = await WinnerVote.findOne({
event: eventId,
user: userId,
})

if (vote) {
vote.project = projectId
} else {
vote = new WinnerVote({
event: eventId,
user: userId,
project: projectId,
})
}

if (!vote) {
throw new Error(
'Unknown error, please try again. If the problem persists, contact support.',
)
}

try {
const result = await vote.save()
return result
} catch (err) {
throw new MongoError(
'Vote could not be saved, reload the page and try again.',
err,
)
}
}

module.exports = controller
3 changes: 2 additions & 1 deletion backend/modules/winner-votes/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const WinnerVoteSchema = new mongoose.Schema({
},
user: {
type: String,
required: true,
},
project: {
type: mongoose.Schema.Types.ObjectId,
Expand All @@ -24,7 +25,7 @@ WinnerVoteSchema.index(
},
{
unique: true,
}
},
)

const WinnerVote = mongoose.model('WinnerVote', WinnerVoteSchema)
Expand Down
94 changes: 24 additions & 70 deletions backend/modules/winner-votes/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ const {
} = require('../../common/middleware/events')

const votingController = require('./controller')
const errorHandler = require('../../common/errors/errorHandler')

const getProjectsWithVotesForEvent = asyncHandler(async (req, res) => {
const finalistProjectsWithAllVotes =
await votingController.getFinalistProjectsWithAllVotes(req.event)
return res.status(200).json(finalistProjectsWithAllVotes)
try {
const finalistProjectsWithAllVotes =
await votingController.getFinalistProjectsWithAllVotes(req.event)
return res.status(200).json(finalistProjectsWithAllVotes)
} catch (e) {
return errorHandler(e, req, res)
}
})

const getVote = asyncHandler(async (req, res) => {
Expand All @@ -30,79 +35,28 @@ const getVote = asyncHandler(async (req, res) => {
})

const submitVote = asyncHandler(async (req, res) => {
const vote = await WinnerVote.findOne({
event: req.event._id,
user: req.user.sub,
})
if (vote) {
vote.project = req.body.projectId
const result = await vote.save()
const eventId = req.event._id
const userId = req.user.sub
const projectId = req.body.projectId
try {
const result = await votingController.submitVote(
eventId,
userId,
projectId,
)
return res.status(200).json(result)
} catch (e) {
return errorHandler(e, req, res)
}
const newVote = new WinnerVote({
event: req.event._id,
user: req.user.sub,
project: req.body.projectId,
})
const result = await newVote.save()
return res.status(200).json(result)
})

router
.route('/:slug')
.get(
hasToken,
hasRegisteredToEvent,
getVote,
// asyncHandler(async (req, res) => {
// const vote = await WinnerVote.findOne({
// event: req.event._id,
// user: req.user.sub,
// })

// return res.status(200).json(vote)
// }),
)
.post(
hasToken,
hasRegisteredToEvent,
submitVote,
// asyncHandler(async (req, res) => {
// const vote = await WinnerVote.findOne({
// event: req.event._id,
// user: req.user.sub,
// })
// if (vote) {
// vote.project = req.body.projectId
// const result = await vote.save()
// return res.status(200).json(result)
// }
// const newVote = new WinnerVote({
// event: req.event._id,
// user: req.user.sub,
// project: req.body.projectId,
// })
// const result = await newVote.save()
// return res.status(200).json(result)
// }),
)

router.route('/:slug/results').get(
hasToken,
isEventOrganiser,
getProjectsWithVotesForEvent,
// asyncHandler(async (req, res) => {
// const votes = await WinnerVote.find({
// event: req.event._id,
// })

// const grouped = _.groupBy(votes, 'project')
// console.log('Grouped votes:', grouped)
// getProjectsWithVotesForEvent(req, res)
.get(hasToken, hasRegisteredToEvent, getVote)
.post(hasToken, hasRegisteredToEvent, submitVote)

// return res.status(200).json(grouped)
// }
)
// )
router
.route('/:slug/results')
.get(hasToken, isEventOrganiser, getProjectsWithVotesForEvent)

module.exports = router
Loading