762 lines
17 KiB
JavaScript
762 lines
17 KiB
JavaScript
import { Collection, Db, ObjectId } from 'mongodb';
|
|
import { checkNumberProp, checkObjectProp, checkStringProp } from './util.js';
|
|
|
|
/**
|
|
* @type {Collection}
|
|
*/
|
|
let cGames;
|
|
/**
|
|
* @type {Collection}
|
|
*/
|
|
let cWalls;
|
|
/**
|
|
* @type {Collection}
|
|
*/
|
|
let cCategories;
|
|
/**
|
|
* @type {Collection}
|
|
*/
|
|
let cQuestions;
|
|
|
|
const QuestionType = {
|
|
SIMPLE: 'SIMPLE',
|
|
MULTIPLE_CHOICE: 'MULTIPLE_CHOICE',
|
|
IMAGE: 'IMAGE',
|
|
IMAGE_MULTIPLE_CHOICE: 'IMAGE_MULTIPLE_CHOICE',
|
|
AUDIO: 'AUDIO',
|
|
AUDIO_MULTIPLE_CHOICE: 'AUDIO_MULTIPLE_CHOICE',
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param {number} points
|
|
* @param {string} question
|
|
* @param {string} answer
|
|
* @param {ObjectId} owner
|
|
* @returns
|
|
*/
|
|
function createSimpleQuestion(points, question, answer, owner) {
|
|
return {
|
|
owner,
|
|
points,
|
|
type: QuestionType.SIMPLE,
|
|
data: createSimpleData(question, answer),
|
|
};
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} question
|
|
* @param {string} answer
|
|
* @returns
|
|
*/
|
|
function createSimpleData(question, answer) {
|
|
return {
|
|
question,
|
|
answer,
|
|
};
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {ObjectId[]} ids
|
|
*/
|
|
function splitQuestionIds(ids) {
|
|
let res = [];
|
|
for (let i = 0; i < ids.length; i += 5) {
|
|
res.push(ids.slice(i, i + 5));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {*} app
|
|
* @param {Db} db
|
|
*/
|
|
export function initGames(app, db) {
|
|
cGames = db.collection('games');
|
|
cWalls = db.collection('walls');
|
|
cCategories = db.collection('categories');
|
|
cQuestions = db.collection('questions');
|
|
app.get('/game', fetchGame);
|
|
app.post('/game', createGame);
|
|
app.delete('/game/:gameid', deleteGameRoute);
|
|
app.get('/games', fetchGames);
|
|
app.post('/game/rename', renameGame);
|
|
|
|
app.get('/wall', fetchWall);
|
|
app.get('/walls/:gameid', fetchWalls);
|
|
app.post('/wall', createWall);
|
|
app.delete('/wall/:wallid', deleteWallRoute);
|
|
app.post('/wall/rename', renameWall);
|
|
|
|
app.get('/category', fetchCategory);
|
|
app.post('/category/rename', renameCategory);
|
|
|
|
app.get('/question', fetchQuestion);
|
|
app.post('/question', updateQuestion);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
async function createGame(req, res) {
|
|
if (!checkStringProp(req.body, 'name')) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
|
|
const name = req.body.name;
|
|
|
|
cGames
|
|
.insertOne({
|
|
name,
|
|
walls: [],
|
|
owner: req.user._id,
|
|
})
|
|
.then(() => {
|
|
res.sendStatus(200);
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
res.sendStatus(500);
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
function renameGame(req, res) {
|
|
if (!checkStringProp(req.body, 'name')) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
if (!checkStringProp(req.body, 'gameid')) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
let gameid = req.body.gameid;
|
|
let _id = new ObjectId(gameid);
|
|
let name = req.body.name;
|
|
|
|
cGames
|
|
.updateOne(
|
|
{
|
|
_id,
|
|
owner: req.user._id,
|
|
},
|
|
{
|
|
$set: {
|
|
name,
|
|
},
|
|
},
|
|
)
|
|
.then((result) => {
|
|
if (result.modifiedCount === 1) res.sendStatus(200);
|
|
else {
|
|
console.error(
|
|
`Failed to modify exactly one Document. Instead modified: ${result.modifiedCount}`,
|
|
);
|
|
res.sendStatus(400);
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
res.sendStatus(500);
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
async function fetchGames(req, res) {
|
|
let list = cGames.find({
|
|
owner: req.user._id,
|
|
});
|
|
|
|
res.status(200).send(await list.toArray());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
async function fetchGame(req, res) {
|
|
if (req.query.id === undefined || req.query.id.length <= 0) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
|
|
const id = new ObjectId(req.query.id);
|
|
|
|
let game = await cGames.findOne({
|
|
_id: id,
|
|
owner: req.user._id,
|
|
});
|
|
|
|
if (game) {
|
|
res.status(200).send(game);
|
|
} else {
|
|
res.sendStatus(404);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
async function deleteGameRoute(req, res) {
|
|
let game = await cGames.findOne({
|
|
owner: req.user._id,
|
|
_id: new ObjectId(req.params.gameid),
|
|
});
|
|
|
|
if (!game) {
|
|
res.sendStatus(404);
|
|
return;
|
|
}
|
|
|
|
deleteGame(game._id)
|
|
.then(() => {
|
|
res.sendStatus(200);
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
res.sendStatus(500);
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
async function fetchQuestion(req, res) {
|
|
if (req.query.id === undefined || req.query.id.length <= 0) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
|
|
const id = new ObjectId(req.query.id);
|
|
|
|
let question = await cQuestions.findOne({
|
|
_id: id,
|
|
owner: req.user._id,
|
|
});
|
|
|
|
if (question) {
|
|
res.status(200).send(question);
|
|
} else {
|
|
res.sendStatus(500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
async function fetchCategory(req, res) {
|
|
if (req.query.id === undefined || req.query.id.length <= 0) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
|
|
const id = new ObjectId(req.query.id);
|
|
|
|
let category = await cCategories.findOne({
|
|
_id: id,
|
|
owner: req.user._id,
|
|
});
|
|
|
|
if (category) {
|
|
let questions = await cQuestions
|
|
.find(
|
|
{
|
|
_id: {
|
|
$in: category.questions,
|
|
},
|
|
owner: req.user._id,
|
|
},
|
|
{
|
|
projection: {
|
|
_id: 1,
|
|
points: 1,
|
|
},
|
|
},
|
|
)
|
|
.toArray();
|
|
|
|
if (questions.length !== 5) {
|
|
res.sendStatus(500);
|
|
return;
|
|
}
|
|
|
|
category.questions = questions;
|
|
|
|
res.status(200).send(category);
|
|
} else {
|
|
res.sendStatus(500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
function renameCategory(req, res) {
|
|
if (!checkStringProp(req.body, 'name')) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
if (!checkStringProp(req.body, 'categoryid')) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
let categoryid = req.body.categoryid;
|
|
let _id = new ObjectId(categoryid);
|
|
let name = req.body.name;
|
|
|
|
cCategories
|
|
.updateOne(
|
|
{
|
|
_id,
|
|
owner: req.user._id,
|
|
},
|
|
{
|
|
$set: {
|
|
name,
|
|
},
|
|
},
|
|
)
|
|
.then((result) => {
|
|
if (result.modifiedCount === 1) res.sendStatus(200);
|
|
else {
|
|
console.error(
|
|
`Failed to modify exactly one Document. Instead modified: ${result.modifiedCount}`,
|
|
);
|
|
res.sendStatus(400);
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
res.sendStatus(500);
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
async function fetchWall(req, res) {
|
|
if (req.query.id === undefined || req.query.id.length <= 0) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
|
|
const id = new ObjectId(req.query.id);
|
|
|
|
let wall = await cWalls.findOne({
|
|
_id: id,
|
|
owner: req.user._id,
|
|
});
|
|
|
|
if (wall) {
|
|
res.status(200).send(wall);
|
|
} else {
|
|
res.sendStatus(404);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
async function fetchWalls(req, res) {
|
|
let game = await cGames.findOne({
|
|
owner: req.user._id,
|
|
_id: new ObjectId(req.params.gameid),
|
|
});
|
|
|
|
if (!game) {
|
|
res.sendStatus(404);
|
|
return;
|
|
}
|
|
|
|
let fetchedWalls = cWalls.find({
|
|
_id: {
|
|
$in: game.walls,
|
|
},
|
|
});
|
|
|
|
res.status(200).send(await fetchedWalls.toArray());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
function renameWall(req, res) {
|
|
if (!checkStringProp(req.body, 'name')) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
if (!checkStringProp(req.body, 'wallid')) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
let wallid = req.body.wallid;
|
|
let _id = new ObjectId(wallid);
|
|
let name = req.body.name;
|
|
|
|
cWalls
|
|
.updateOne(
|
|
{
|
|
_id,
|
|
owner: req.user._id,
|
|
},
|
|
{
|
|
$set: {
|
|
name,
|
|
},
|
|
},
|
|
)
|
|
.then((result) => {
|
|
if (result.modifiedCount === 1) res.sendStatus(200);
|
|
else {
|
|
console.error(
|
|
`Failed to modify exactly one Document. Instead modified: ${result.modifiedCount}`,
|
|
);
|
|
res.sendStatus(400);
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
res.sendStatus(500);
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
async function createWall(req, res) {
|
|
if (
|
|
!checkStringProp(req.body, 'gameid') &&
|
|
!checkStringProp(req.body, 'name')
|
|
) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
const gameid = req.body.gameid;
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
const wallname = req.body.name;
|
|
|
|
let game = await cGames.findOne({
|
|
owner: req.user._id,
|
|
_id: new ObjectId(gameid),
|
|
});
|
|
|
|
if (!game) {
|
|
res.sendStatus(404);
|
|
return;
|
|
}
|
|
|
|
let newQuestions = [];
|
|
|
|
for (let id = 1; id <= 5; id++) {
|
|
for (let j = 1; j <= 5; j++) {
|
|
newQuestions.push(
|
|
createSimpleQuestion(
|
|
j * -100,
|
|
'Frage',
|
|
'Antwort',
|
|
req.user._id,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
cQuestions
|
|
.insertMany(newQuestions)
|
|
.then((insertedQuestions) => {
|
|
let questionsIds = splitQuestionIds(
|
|
Object.values(insertedQuestions.insertedIds),
|
|
);
|
|
let newCategories = [];
|
|
|
|
for (let i = 1; i <= 5; i++) {
|
|
newCategories.push({
|
|
name: `Kategorie ${i}`,
|
|
questions: questionsIds[i - 1],
|
|
owner: req.user._id,
|
|
});
|
|
}
|
|
|
|
return cCategories.insertMany(newCategories);
|
|
})
|
|
.then((insertedCategories) => {
|
|
return cWalls.insertOne({
|
|
name: wallname,
|
|
categories: Object.values(insertedCategories.insertedIds),
|
|
owner: req.user._id,
|
|
});
|
|
})
|
|
.then((insertedWall) => {
|
|
return cGames.updateOne(
|
|
{
|
|
_id: game._id,
|
|
},
|
|
{
|
|
$push: {
|
|
walls: insertedWall.insertedId,
|
|
},
|
|
},
|
|
);
|
|
})
|
|
.then(() => {
|
|
res.sendStatus(200);
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
res.sendStatus(500);
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
async function deleteWallRoute(req, res) {
|
|
let wall = await cWalls.findOne({
|
|
_id: new ObjectId(req.params.wallid),
|
|
owner: req.user._id,
|
|
});
|
|
|
|
if (!wall) {
|
|
res.sendStatus(404);
|
|
return;
|
|
}
|
|
|
|
let game = await cGames.findOne({
|
|
owner: req.user._id,
|
|
walls: wall._id,
|
|
});
|
|
|
|
if (!game) {
|
|
res.sendStatus(404);
|
|
return;
|
|
}
|
|
|
|
deleteWall(wall._id)
|
|
.then(() => {
|
|
return cGames.updateOne(
|
|
{
|
|
_id: game._id,
|
|
},
|
|
{
|
|
$pull: {
|
|
walls: wall._id,
|
|
},
|
|
},
|
|
);
|
|
})
|
|
.then(() => {
|
|
res.sendStatus(200);
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
res.sendStatus(500);
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('express').Request} req
|
|
* @param {import('express').Response} res
|
|
*/
|
|
async function updateQuestion(req, res) {
|
|
if (
|
|
!checkStringProp(req.body, '_id') &&
|
|
!checkStringProp(req.body, 'owner') &&
|
|
!checkStringProp(req.body, 'type') &&
|
|
!checkNumberProp(req.body, 'points') &&
|
|
!checkObjectProp(req.body, 'data')
|
|
) {
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
|
|
if (req.body.owner !== req.user._id.toString()) {
|
|
res.sendStatus(403);
|
|
return;
|
|
}
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
let _id = req.body._id;
|
|
|
|
let replacement;
|
|
|
|
if (
|
|
req.body.type === QuestionType.SIMPLE ||
|
|
req.body.type === QuestionType.MULTIPLE_CHOICE
|
|
) {
|
|
replacement = toNormalQuestion(req.body);
|
|
} else if (
|
|
req.body.type === QuestionType.IMAGE ||
|
|
req.body.type === QuestionType.IMAGE_MULTIPLE_CHOICE
|
|
) {
|
|
replacement = toImageQuestion(req.body);
|
|
} else if (
|
|
req.body.type === QuestionType.AUDIO ||
|
|
req.body.type === QuestionType.AUDIO_MULTIPLE_CHOICE
|
|
) {
|
|
replacement = toAudioQuestion(req.body);
|
|
}
|
|
|
|
cQuestions
|
|
.replaceOne(
|
|
{
|
|
_id: new ObjectId(_id),
|
|
owner: req.user._id,
|
|
},
|
|
replacement,
|
|
)
|
|
.then((result) => {
|
|
if (result.modifiedCount === 1) {
|
|
res.sendStatus(200);
|
|
} else res.sendStatus(500);
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
res.sendStatus(500);
|
|
});
|
|
}
|
|
|
|
function toNormalQuestion(body) {
|
|
return {
|
|
...body,
|
|
_id: new ObjectId(body._id),
|
|
owner: new ObjectId(body.owner),
|
|
};
|
|
}
|
|
|
|
function toImageQuestion(body) {
|
|
return {
|
|
...body,
|
|
_id: new ObjectId(body._id),
|
|
owner: new ObjectId(body.owner),
|
|
data: {
|
|
...body.data,
|
|
image:
|
|
body.data.image === null ? null : new ObjectId(body.data.image),
|
|
},
|
|
};
|
|
}
|
|
|
|
function toAudioQuestion(body) {
|
|
return {
|
|
...body,
|
|
_id: new ObjectId(body._id),
|
|
owner: new ObjectId(body.owner),
|
|
data: {
|
|
...body.data,
|
|
audio:
|
|
body.data.audio === null ? null : new ObjectId(body.data.audio),
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {ObjectId} _id
|
|
*/
|
|
function deleteGame(_id) {
|
|
return cGames
|
|
.findOne({ _id })
|
|
.then((game) => {
|
|
let wallDeletions = [];
|
|
for (const wallId of game.walls) {
|
|
wallDeletions.push(deleteWall(wallId));
|
|
}
|
|
|
|
return Promise.all(wallDeletions);
|
|
})
|
|
.then(() => {
|
|
return cGames.deleteOne({ _id });
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {ObjectId} _id
|
|
*/
|
|
function deleteWall(_id) {
|
|
return cWalls
|
|
.findOne({ _id })
|
|
.then((wall) => {
|
|
let categoryDeletions = [];
|
|
for (const catId of wall.categories) {
|
|
categoryDeletions.push(deleteCategory(catId));
|
|
}
|
|
return Promise.all(categoryDeletions);
|
|
})
|
|
.then(() => {
|
|
return cWalls.deleteOne({ _id: _id });
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {ObjectId} _id
|
|
*/
|
|
function deleteCategory(_id) {
|
|
return cCategories
|
|
.findOne({
|
|
_id,
|
|
})
|
|
.then((category) => {
|
|
return cQuestions.deleteMany({
|
|
_id: {
|
|
$in: category.questions,
|
|
},
|
|
});
|
|
})
|
|
.then(() => {
|
|
return cCategories.deleteOne({
|
|
_id,
|
|
});
|
|
});
|
|
}
|