- Notifications
You must be signed in to change notification settings - Fork6
🍻 A pub quiz web application built using the MERN stack with websockets for near real-time quiz action
License
voax/quizzer
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Made byIvo Breukers andOktay Dinler as our final assignment for the DWA course in the first semester of 2019 at the HAN University of Applied Sciences (Arnhem).
- Quizzer
The Quizzer is a web application that can be used in bars, sports canteens and maybe even prisons to play quizzes as a team. A pub quiz, basically.
There are 3 main roles in this application: The Quizz Master, The Team and The Scoreboard. The idea is that the Quizz Master can host a room which players can join. A game of Quizzer can be played with a minimum of 2 players and a maximum of 6. The game consists of 12 questions per round and can be played indefinitely untill the Quizz Master ends the quiz after the last question of a round. The question, team answers and points are all shown on the scoreboard screen which is updated in near real-time.
Clickhere for our wireframes.
Client receives:
- TEAM_APPLIED
- APPLICATION_ACCEPTED
- APPLICATION_REJECTED
- CATEGORIES_SELECTED
- QUESTION_SELECTED
- GUESS_SUBMITTED
- ROOM_CLOSED
- QUESTION_CLOSED
- SCOREBOARD_REFRESH
Clients sends:
- TEAM_APPLIED
Method | Url |
---|---|
GET | /categories/ |
GET | /categories/:categoryID/questions |
POST | /rooms |
GET | /rooms/:roomCode |
PATCH | /rooms/:roomCode |
DELETE | /rooms/:roomCode |
GET | /rooms/:roomCode/applications |
POST | /rooms/:roomCode/applications |
DELETE | /rooms/:roomCode/applications/:applicationId |
POST | /rooms/:roomCode/teams |
PATCH | /rooms/:roomCode/teams/:teamID |
PUT | /rooms/:roomCode/teams/question |
PUT | /rooms/:roomCode/categories |
POST | /rooms/:roomCode/scoreboards |
Property | Type | Default | Required |
---|---|---|---|
question | String | ❌ | ✔️ |
answer | String | ❌ | ✔️ |
category | String | ❌ | ✔️ |
language | String | ❌ | ✔️ |
Property | Type | Default | Required |
---|---|---|---|
sessionID | String | ❌ | ✔️ |
name | String | ❌ | ✔️ |
roundPoints | Number | 0 | ❌ |
roundScore | Number | 0 | ❌ |
guess | String | ❌ | ❌ |
guessCorrect | Boolean | ❌ | ❌ |
Property | Type | Default | Required |
---|---|---|---|
code | String | ❌ | ✔️ |
host | String | ❌ | ✔️ |
language | String | ❌ | ✔️ |
round | Number | 0 | ❌ |
questionNo | Number | 0 | ❌ |
roundStarted | Boolean | false | ❌ |
teams | [Team] | ❌ | ❌ |
applications | [Team] | ❌ | ❌ |
categories | [String] | ❌ | ❌ |
askedQuestions | ref:[Question] | ❌ | ❌ |
currentQuestion | Question | Question | ❌ |
questionClosed | Boolean | true | ❌ |
roomClosed | Boolean | false | ❌ |
scoreboards | [String] | ❌ | ❌ |
ended | Boolean | false | ❌ |
questionCompleted | Boolean | false | ❌ |
{ connected:false;}
{teamID:null,roomCode:{value:'',valid:false,},team:{value:'',valid:false,},roundNo:0,question:{open:false,number:0,question:'',category:'',},guess:{value:'',valid:false,},}
{roomCode:null,connectedToRoom:false,connectingToRoom:false,triedConnectingToRoom:false,round:null,teams:null,category:null,question:null,questionNo:null,questionClosed:null,}
{title:'',message:'',button:'',active:false,}
{active:false,text:'',}
{roomCode:null,language:null,selectedTeamApplication:null,teamApplications:[],approvedTeamApplications:[],roomClosed:false,round:0,roundStarted:false,selectedCategory:null,categories:[],selectedCategories:[],question:0,questions:[],questionsAsked:[],currentQuestion:null,questionClosed:true,selectedQuestion:null,approvingATeamGuess:false,},
module.exports =(fn: (req, res, next) => Promise ): (req, res, next) => void
This module exports a router wrapper for async route handlers. It allows the user to use async route handlers without having to use try/catch. When this wrapper 'catches' an error it passes tonext(error)
.
module.exports =({ defaultStatusCode: Number, defaultMessage: String }): (err, req, res, next) => void
This module exports a basic error handler middleware creator. If the user does not pass both initial arguments it will use thestatusCode
andmessage
props from the object passed fromnext()
. Furthermore the closure will also log the error usingconsole.error
to the console.
module.exports =(sessionParser): (wss): (request, socket, head) => void
An application specific function for this project which is used to prevent users who do not have a role connecting to our WebSocket server.
module.exports.isRole =(...conditions: any): (req, res, next) => void
TheisRole
function export is a middleware creator function. This function takes a 0.* arguments which are used to check for the users'ssession.role
value.
However if the type is a function it will be passed thereq
object and whether the function returns true or false it will respond with an error ornext()
it.
If any condition results into false it will execute the following code:
res.status(400).json({message:'You are not allowed to perform this action.',});
To combine multiple role middleware you can simply put them in an array. To use it like an array in your route handlers you can simply use the spread operator:
constisHost=isRole(req=>req.room&&req.sessionID===req.room.host)constisQM=isRole('QM')constisQMAndHost=[isQuizzMaster,isHost]// spread like sorouter.get('/protected', ...isQMAndHost,(req,res)=>{...})// orapp.use('/protected', ...isQMAndHost)
module.exports.sessionHasWSConnect =(errorMsg: String): (req, res, next) => void
The returned middleware closure checks for whether the request is already connected to the WebSocket server.If the user is already connected it will respond with a400
status code and with the passed inerrorMsg
parameter.
Ping the WebSocket for all team connections with the givenmsg
.
Ping the WebSocket for all scoreboard connections with the givenmsg
.
Ping the WebSocket for all team-application connections with the givenmsg
.
Ping the host's WebSocket connection with the givenmsg
.
This method determines the winnner(s) and respectively gives all the teams their points.
UpdatesroundStarted
tofalse
andquestionNo
to0
. Then it will callcalculateRP()
- Updates for all teams their
.roundScore
property if.guessCorrect
is true - Sets
currentQuestion
tonull
andquestionCompleted
totrue
- calls
.nextRound()
if the current question is>= MAX_QUESTIONS_PER_ROUND
defined inserver/.env
Incrementsround
, updatesquestionNo
to0
,categories
tocategories
,roundStarted
totrue
and updates the team'sroundScore
to0
. Then it will ping the teams with'CATEGORIES_SELECTED'
and returnsroundStarted
,round
andquestionNo
.
IncrementsquestionNo
, updatesquestionCompleted
tofalse
,questionClosed
tofalse
,currentQuestion
toquestion
, adds thequestion._id
to theaskedQuestions
array, next it updates the team'sguess
to''
andguessCorrect
tofalse
. Then it will ping the teams with'QUESTION_SELECTED'
and the scoreboards with'SCOREBOARD_REFRESH'
. Then it returnsquestionClosed
andquestionNo
.
Ping the team's WebSocket connection with the givenmsg
.
About
🍻 A pub quiz web application built using the MERN stack with websockets for near real-time quiz action