The store-URL support extracted the app ID into a local but never wrote it back, so steamScraper still received the full URL, keyed the Steam API response by the URL string, threw, and its caught Error object flowed into Game.create() — surfacing as misleading 'Path lastModifiedBy / createdBy / frontImage / title is required' validation errors. - add utils/normalizeSteamId: digits pass through, /app/<id>/ (plus /agecheck/app/<id>/ and query strings) extracts the ID, empty -> '', anything else -> null (no .match()[0] / .includes crash on bad input) - create(): 400 cleanly on unrecognized input, write the normalized ID back to req.body.steamId so dup checks, scraper, and stored doc agree - guard the scrape result (missing title/frontImage, false, or Error) and 400 'Steam lookup failed' instead of leaking it into Game.create
313 lines
8.6 KiB
JavaScript
313 lines
8.6 KiB
JavaScript
// noinspection DuplicatedCode
|
|
|
|
import Game from "../models/Game.js";
|
|
import steamScraper from "../scripts/scraper.js";
|
|
import asyncHandler from "../middleware/async.js";
|
|
import ErrorResponse from "../utils/errorResponse.js";
|
|
import normalizeSteamId from "../utils/normalizeSteamId.js";
|
|
import { decode, isValid } from "js-base64";
|
|
|
|
const checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
|
|
const checkForTwelveRegExp = new RegExp("^[0-9a-fA-F]{12}$");
|
|
/**
|
|
* games.js
|
|
*
|
|
* @description :: Server-side logic for managing games.
|
|
*/
|
|
|
|
/**
|
|
* gameController.list()
|
|
*/
|
|
export const list = asyncHandler(async (req, res, next) => {
|
|
const data = res.advancedResults.data;
|
|
if (data[0]?.accessedBy) {
|
|
for (let i = 0; i < data.length; i++) {
|
|
for (let x = 0; x < data[i].accessedBy.length; x++) {
|
|
if (data[i].accessedBy[x].user.toString() !== req.user.id)
|
|
data[i].accessedBy.splice(x, 1);
|
|
}
|
|
}
|
|
}
|
|
return res.status(200).json(res.advancedResults);
|
|
});
|
|
|
|
/**
|
|
* gameController.show()
|
|
*/
|
|
export const show = asyncHandler(async (req, res, next) => {
|
|
const { id } = req.params;
|
|
|
|
const gameId =
|
|
id === id.match(checkForTwelveRegExp) || id.match(checkForHexRegExp)
|
|
? "_id"
|
|
: "steamId";
|
|
|
|
const game = await Game.findOne({ [gameId]: id });
|
|
|
|
if (!game)
|
|
return next(
|
|
new ErrorResponse(`Game not found with id of ${req.params.id}`, 404),
|
|
);
|
|
|
|
if (
|
|
!game.accessedBy
|
|
.map((x) => x.user.toString() === req.user.id)
|
|
.includes(true)
|
|
)
|
|
return next(
|
|
new ErrorResponse(
|
|
`You do not have permission to access Game ID ${req.params.id}`,
|
|
401,
|
|
),
|
|
);
|
|
|
|
const userGame = await Game.findOne({ [gameId]: id }).select(
|
|
"-createdBy -__v",
|
|
);
|
|
|
|
for (let i = 0; i < userGame.accessedBy.length; i++) {
|
|
if (userGame.accessedBy[i].user.toString() !== req.user.id)
|
|
userGame.accessedBy.splice(i, 1);
|
|
}
|
|
|
|
res.status(200).json({ success: true, data: userGame });
|
|
});
|
|
|
|
/**
|
|
* gameController.create()
|
|
*/
|
|
export const create = asyncHandler(async (req, res, next) => {
|
|
let oldGame;
|
|
|
|
const steamId = normalizeSteamId(req.body.steamId);
|
|
if (steamId === null)
|
|
return next(
|
|
new ErrorResponse(
|
|
"Enter a Steam app ID or a store.steampowered.com URL.",
|
|
400,
|
|
),
|
|
);
|
|
req.body.steamId = steamId;
|
|
|
|
if (req.body.shortDesc && isValid(req.body.shortDesc))
|
|
req.body.shortDesc = decode(req.body.shortDesc);
|
|
if (req.body.reviews && isValid(req.body.reviews))
|
|
req.body.reviews = decode(req.body.reviews);
|
|
if (req.body.summary && isValid(req.body.summary))
|
|
req.body.summary = decode(req.body.summary);
|
|
isValid(req.body.systemRequirements?.windows?.minimum)
|
|
? (req.body.systemRequirements.windows.minimum = decode(
|
|
req.body.systemRequirements.windows.minimum,
|
|
))
|
|
: "";
|
|
isValid(req.body.systemRequirements?.windows?.recommended)
|
|
? (req.body.systemRequirements.windows.recommended = decode(
|
|
req.body.systemRequirements.windows.recommended,
|
|
))
|
|
: "";
|
|
isValid(req.body.systemRequirements?.mac?.minimum)
|
|
? (req.body.systemRequirements.mac.minimum = decode(
|
|
req.body.systemRequirements.mac.minimum,
|
|
))
|
|
: "";
|
|
isValid(req.body.systemRequirements?.mac?.recommended)
|
|
? (req.body.systemRequirements.mac.recommended = decode(
|
|
req.body.systemRequirements.mac.recommended,
|
|
))
|
|
: "";
|
|
isValid(req.body.systemRequirements?.linux?.minimum)
|
|
? (req.body.systemRequirements.linux.minimum = decode(
|
|
req.body.systemRequirements.linux.minimum,
|
|
))
|
|
: "";
|
|
isValid(req.body.systemRequirements?.linux?.recommended)
|
|
? (req.body.systemRequirements.linux.recommended = decode(
|
|
req.body.systemRequirements.linux.recommended,
|
|
))
|
|
: "";
|
|
|
|
steamId.length > 0
|
|
? (oldGame = await Game.findOne({
|
|
steamId: steamId,
|
|
"accessedBy.user": req.user.id,
|
|
}))
|
|
: (oldGame = await Game.findOne({
|
|
title: req.body.title,
|
|
"accessedBy.user": req.user.id,
|
|
}));
|
|
|
|
if (oldGame)
|
|
return next(
|
|
new ErrorResponse(
|
|
`The game ${oldGame.title} already exists in users account.`,
|
|
400,
|
|
),
|
|
);
|
|
|
|
steamId.length > 0
|
|
? (oldGame = await Game.findOne({ steamId: steamId }))
|
|
: (oldGame = await Game.findOne({ title: req.body.title }));
|
|
|
|
if (oldGame) {
|
|
oldGame.accessedBy.push({
|
|
user: req.user.id,
|
|
store: req.body.accessedBy[0].store,
|
|
playStatus: req.body.accessedBy[0].playStatus,
|
|
soundtrack: req.body.accessedBy[0].soundtrack,
|
|
rating: req.body.accessedBy[0].rating,
|
|
});
|
|
|
|
oldGame.lastModifiedBy = req.user.id;
|
|
|
|
await oldGame.save();
|
|
return res.status(200).json({
|
|
success: true,
|
|
data: oldGame,
|
|
});
|
|
}
|
|
|
|
req.body.createdBy = req.user.id;
|
|
req.body.lastModifiedBy = req.user.id;
|
|
req.body.accessedBy[0].user = req.user.id;
|
|
const data =
|
|
req.body.scrape === true ? await steamScraper(req.body) : req.body;
|
|
|
|
if (
|
|
req.body.scrape === true &&
|
|
(!data || typeof data !== "object" || !data.title || !data.frontImage)
|
|
)
|
|
return next(
|
|
new ErrorResponse("Steam lookup failed for that ID/URL.", 400),
|
|
);
|
|
|
|
const game = await Game.create(data);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: game,
|
|
});
|
|
});
|
|
|
|
/**
|
|
* gameController.update()
|
|
*/
|
|
export const update = asyncHandler(async (req, res, next) => {
|
|
const { id } = req.params;
|
|
|
|
const gameId =
|
|
id === id.match(checkForTwelveRegExp) || id.match(checkForHexRegExp)
|
|
? "_id"
|
|
: "steamId";
|
|
|
|
if (req.body.shortDesc && isValid(req.body.shortDesc))
|
|
req.body.shortDesc = decode(req.body.shortDesc);
|
|
if (req.body.reviews && isValid(req.body.reviews))
|
|
req.body.reviews = decode(req.body.reviews);
|
|
if (req.body.summary && isValid(req.body.summary))
|
|
req.body.summary = decode(req.body.summary);
|
|
isValid(req.body.systemRequirements?.windows?.minimum)
|
|
? (req.body.systemRequirements.windows.minimum = decode(
|
|
req.body.systemRequirements.windows.minimum,
|
|
))
|
|
: "";
|
|
isValid(req.body.systemRequirements?.windows?.recommended)
|
|
? (req.body.systemRequirements.windows.recommended = decode(
|
|
req.body.systemRequirements.windows.recommended,
|
|
))
|
|
: "";
|
|
isValid(req.body.systemRequirements?.mac?.minimum)
|
|
? (req.body.systemRequirements.mac.minimum = decode(
|
|
req.body.systemRequirements.mac.minimum,
|
|
))
|
|
: "";
|
|
isValid(req.body.systemRequirements?.mac?.recommended)
|
|
? (req.body.systemRequirements.mac.recommended = decode(
|
|
req.body.systemRequirements.mac.recommended,
|
|
))
|
|
: "";
|
|
isValid(req.body.systemRequirements?.linux?.minimum)
|
|
? (req.body.systemRequirements.linux.minimum = decode(
|
|
req.body.systemRequirements.linux.minimum,
|
|
))
|
|
: "";
|
|
isValid(req.body.systemRequirements?.linux?.recommended)
|
|
? (req.body.systemRequirements.linux.recommended = decode(
|
|
req.body.systemRequirements.linux.recommended,
|
|
))
|
|
: "";
|
|
|
|
let game = await Game.findOne({
|
|
[gameId]: id,
|
|
"accessedBy.user": req.user.id,
|
|
});
|
|
|
|
if (!game)
|
|
return next(
|
|
new ErrorResponse(`A game with the id of ${id} does not exist`, 401),
|
|
);
|
|
|
|
game = await Game.findOneAndUpdate(
|
|
{ [gameId]: id, "accessedBy.user": req.user.id },
|
|
{
|
|
$set: {
|
|
series: req.body.series,
|
|
intel: req.body.intel,
|
|
genre: req.body.genre,
|
|
wine: req.body.wine,
|
|
lastModifiedBy: req.user.id,
|
|
"accessedBy.$.store": req.body.accessedBy[0].store,
|
|
"accessedBy.$.playStatus": req.body.accessedBy[0].playStatus,
|
|
"accessedBy.$.soundtrack": req.body.accessedBy[0].soundtrack,
|
|
"accessedBy.$.rating": req.body.accessedBy[0].rating,
|
|
},
|
|
},
|
|
{ new: true, runValidators: true },
|
|
);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: game,
|
|
});
|
|
});
|
|
|
|
/**
|
|
* gameController.remove()
|
|
*/
|
|
export const remove = asyncHandler(async (req, res, next) => {
|
|
const { id } = req.params;
|
|
|
|
const gameId =
|
|
id === id.match(checkForTwelveRegExp) || id.match(checkForHexRegExp)
|
|
? "_id"
|
|
: "steamId";
|
|
|
|
let game = await Game.findOne({
|
|
[gameId]: id,
|
|
"accessedBy.user": req.user.id,
|
|
});
|
|
|
|
if (!game)
|
|
return next(
|
|
new ErrorResponse(`A game with the id of ${id} does not exist`, 401),
|
|
);
|
|
|
|
if (game.accessedBy.length > 1) {
|
|
await Game.findOneAndUpdate(
|
|
{ [gameId]: id, "accessedBy.user": req.user.id },
|
|
{
|
|
$pull: {
|
|
accessedBy: { user: req.user.id },
|
|
},
|
|
},
|
|
{ new: true, runValidators: true },
|
|
);
|
|
} else {
|
|
await Game.findOneAndDelete({ [gameId]: id });
|
|
}
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
data: {},
|
|
});
|
|
});
|