Files
Jeopardy-Server/src/games.js
Jonas Kappa 284615573f Release 2.0.0
Reworked default points
2026-01-03 00:37:33 +01:00

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,
});
});
}