From 83f781153dc5dddd86b41b35b0b817348832896f Mon Sep 17 00:00:00 2001 From: Kaipo Wilmeth Date: Fri, 5 Sep 2025 12:12:47 -0500 Subject: [PATCH 1/4] levels --- .env.example | 1 + package.json | 5 +- src/config/db.js | 68 +--- src/config/dbActions.js | 3 +- src/constants/dbConstants.js | 5 + src/controllers/artistController.js | 97 +++-- src/controllers/characterController.js | 124 +++--- src/controllers/collectibleController.js | 55 ++- src/controllers/gameController.js | 36 +- src/controllers/graffitiTagController.js | 90 +++-- src/controllers/indexController.js | 15 +- src/controllers/locationController.js | 138 ++++--- src/controllers/songController.js | 159 +++++--- src/docs/CONTRIBUTE.md | 6 + src/docs/DEV_SETUP.md | 8 +- src/managers/MiddlewareManager.js | 10 +- src/public/docs.html | 19 +- src/public/js/examples/collectibleExample.js | 5 +- src/routes/collectibleRouter.js | 10 +- src/routes/locationRouter.js | 2 + src/routes/songRouter.js | 3 +- src/utils/swagger-docs.json | 279 +++++++++++-- test/helper/mongodbMemoryTest.js | 9 +- test/locations.test.js | 396 ++++++++++--------- 24 files changed, 965 insertions(+), 578 deletions(-) diff --git a/.env.example b/.env.example index 4fbcb22..baaa604 100644 --- a/.env.example +++ b/.env.example @@ -13,4 +13,5 @@ MONGO_DOMAIN= # MONGO DATABASES (names do not matter) JSR_DB= JSRF_DB= +BRC_DB= CORE_DB= diff --git a/package.json b/package.json index 84a3e68..81ffa44 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,11 @@ "type": "module", "main": "src/app.js", "scripts": { - "test": "export $(cat ./qa.env | egrep -v '#|^$' | xargs) && node --no-warnings --experimental-vm-modules ./node_modules/jest/bin/jest.js", "build": "npm install", "prod": "export $(cat ./prod.env | egrep -v '#|^$' | xargs) && node src/utils/swagger.js", - "qa": "export $(cat ./qa.env | egrep -v '#|^$' | xargs) && nodemon --inspect --ignore src/utils/ src/utils/swagger.js" + "qa": "export $(cat ./qa.env | egrep -v '#|^$' | xargs) && nodemon --inspect --ignore src/utils/ src/utils/swagger.js", + "test": "export $(cat ./qa.env | egrep -v '#|^$' | xargs) && node --no-warnings --experimental-vm-modules ./node_modules/jest/bin/jest.js", + "test:file": "export $(cat ./qa.env | egrep -v '#|^$' | xargs) && node --no-warnings --experimental-vm-modules ./node_modules/jest/bin/jest.js" }, "keywords": [], "author": "RazzNBlue", diff --git a/src/config/db.js b/src/config/db.js index a246d37..0771db6 100644 --- a/src/config/db.js +++ b/src/config/db.js @@ -6,7 +6,7 @@ dotenv.config(); import LOGGER from "../utils/logger.js"; import Constants from "../constants/dbConstants.js"; -const {CORE_DB, JSR_DB, JSRF_DB, BRC_DB} = Constants; +const {CORE_DB} = Constants; const buildMongoUri = () => { const user = process.env.MONGO_USER; @@ -33,7 +33,7 @@ const buildMongoUri = () => { const client = new MongoClient(buildMongoUri()); /* Database Connections */ -export const performCoreAdminAction = async (action, username) => { +export const performAdminAction = async (action, username) => { try { await client.connect(); return await action(client, CORE_DB, "Admin", username); @@ -45,73 +45,13 @@ export const performCoreAdminAction = async (action, username) => { } }; -export const performCoreAction = async (action, collection, id, qps) => { +export const performDBAction = async (action, dbName, collection, id, qps) => { try { await client.connect(); const queryActions = [getSortQuery(qps), getLimitSize(qps)]; return await action( client, - CORE_DB, - collection, - id, - getQueryObject(qps), - queryActions - ); - } catch (err) { - console.error(err); - return err; - } finally { - await client.close(); - } -}; - -export const performJSRAction = async (action, collection, id, qps) => { - try { - await client.connect(); - const queryActions = [getSortQuery(qps), getLimitSize(qps)]; - return await action( - client, - JSR_DB, - collection, - id, - getQueryObject(qps), - queryActions - ); - } catch (err) { - console.error(err); - return err; - } finally { - await client.close(); - } -}; - -export const performJSRFAction = async (action, collection, id, qps) => { - try { - await client.connect(); - const queryActions = [getSortQuery(qps), getLimitSize(qps)]; - return await action( - client, - JSRF_DB, - collection, - id, - getQueryObject(qps), - queryActions - ); - } catch (err) { - console.error(err); - return err; - } finally { - await client.close(); - } -}; - -export const performBRCAction = async (action, collection, id, qps) => { - try { - await client.connect(); - const queryActions = [getSortQuery(qps), getLimitSize(qps)]; - return await action( - client, - BRC_DB, + dbName, collection, id, getQueryObject(qps), diff --git a/src/config/dbActions.js b/src/config/dbActions.js index d462194..9a92442 100644 --- a/src/config/dbActions.js +++ b/src/config/dbActions.js @@ -5,5 +5,6 @@ export const Actions = { fetchAll: async (client, dbName, collectionName, id, qps, sortValue) => { return await client.db(dbName).collection(collectionName).find({}).sort(sortValue).toArray() }, fetchWithQuery: async (client, dbName, collectionName, id, qps, queryActions) => { return await client.db(dbName).collection(collectionName).find(qps).sort(queryActions[0]).limit(queryActions[1]).toArray() }, fetchById: async (client, dbName, collectionName, id) => { return await client.db(dbName).collection(collectionName).findOne({ _id: new ObjectId(id) }) }, - fetchAdmin: async (client, dbName, collectionName, username) => { return await client.db(dbName).collection(collectionName).findOne({ username: username }) } + fetchAdmin: async (client, dbName, collectionName, username) => { return await client.db(dbName).collection(collectionName).findOne({ username: username }) }, + fetchRandom: async (client, dbName, collectionName) => { return await client.db(dbName).collection(collectionName).aggregate([{ $sample: { size: 1 } }]).toArray(); } } diff --git a/src/constants/dbConstants.js b/src/constants/dbConstants.js index 7d9ad8b..c849335 100644 --- a/src/constants/dbConstants.js +++ b/src/constants/dbConstants.js @@ -6,6 +6,11 @@ const Constants = { JSR_DB: process.env.JSR_DB, JSRF_DB: process.env.JSRF_DB, BRC_DB: process.env.BRC_DB, + gameMap: { + jsr: process.env.JSR_DB, + jsrf: process.env.JSRF_DB, + brc: process.env.BRC_DB, + }, }; export default Constants; diff --git a/src/controllers/artistController.js b/src/controllers/artistController.js index 0129a33..3e33fa5 100644 --- a/src/controllers/artistController.js +++ b/src/controllers/artistController.js @@ -1,60 +1,103 @@ -import { performBRCAction, performCoreAction, performJSRAction, performJSRFAction } from "../config/db.js"; -import { Actions } from "../config/dbActions.js"; -import { ObjectId } from "mongodb"; +import {ObjectId} from "mongodb"; +import Constants from "../constants/dbConstants.js"; +import {Actions} from "../config/dbActions.js"; +import {performDBAction} from "../config/db.js"; +import LOGGER from "../utils/logger.js"; - -const Artist = 'Artist'; -const Song = 'Song'; +const Artist = "Artist"; +const Song = "Song"; +const {CORE_DB, JSR_DB, JSRF_DB, BRC_DB} = Constants; export const getArtists = async (req, res) => { try { const artists = await fetchArtists(req); if (artists) { return res.send(artists.length === 1 ? artists[0] : artists); - } + } res.status(404).send(); - } catch(err) { - res.status(500).send(`Could not fetch ALL Artists due to error: \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch ALL Artists`, err); + res.status(500).send(`Could not fetch ALL Artists due to error`, err); } -} +}; export const getArtistById = async (req, res) => { try { - const artist = await performCoreAction(Actions.fetchById, Artist, req?.params?.id); + const artist = await performDBAction( + Actions.fetchById, + CORE_DB, + Artist, + req?.params?.id + ); if (artist) { return res.send(artist); } - res.status(404).send(`Artist Resource could not be found at requested location`); - } catch(err) { - res.status(500).send(`Could not fetch Artist with ID: ${req.params.id} \n${err}`); + res + .status(404) + .send(`Artist Resource could not be found at requested location`); + } catch (err) { + LOGGER.error(`Could not fetch Artist by Id ${req?.params?.id}`, err); + res + .status(500) + .send(`Could not fetch Artist with ID: ${req.params.id}`, err); } -} +}; export const getSongsByArtist = async (req, res) => { try { const artistId = req?.params?.id; if (!artistId) { - return res.status(400).send('Invalid ArtistId'); + return res.status(400).send("Invalid ArtistId"); } res.send(await fetchSongsByArtistId(artistId)); - } catch(err) { - res.status(500).send(`Could not fetch Songs by Artist with ID: ${req.params.id} \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch Songs By Artist ${req?.params?.id}`, err); + res + .status(500) + .send(`Could not fetch Songs by Artist with ID: ${req.params.id}`, err); } -} - +}; export const fetchArtists = async (req) => { if (req?.query) { - return await performCoreAction(Actions.fetchWithQuery, Artist, null, req?.query); + return await performDBAction( + Actions.fetchWithQuery, + CORE_DB, + Artist, + null, + req?.query + ); } - return await performCoreAction(Actions.fetchAll, Artist, null); -} + return await performDBAction(Actions.fetchAll, CORE_DB, Artist, null); +}; export const fetchSongsByArtistId = async (artistId) => { const songs = []; - const jsrSongs = await performJSRAction(Actions.fetchWithQuery, Song, null, { artistId: new ObjectId(artistId) }); - const jsrfSongs = await performJSRFAction(Actions.fetchWithQuery, Song, null, { artistId: new ObjectId(artistId) }); - const brcSongs = await performBRCAction(Actions.fetchWithQuery, Song, null, { artistId: new ObjectId(artistId) }); + const jsrSongs = await performDBAction( + Actions.fetchWithQuery, + JSR_DB, + Song, + null, + { + artistId: new ObjectId(artistId), + } + ); + const jsrfSongs = await performDBAction( + Actions.fetchWithQuery, + JSRF_DB, + Song, + null, + {artistId: new ObjectId(artistId)} + ); + const brcSongs = await performDBAction( + Actions.fetchWithQuery, + BRC_DB, + Song, + null, + { + artistId: new ObjectId(artistId), + } + ); if (jsrSongs && jsrSongs.length > 0) { songs.push(jsrSongs); } @@ -65,4 +108,4 @@ export const fetchSongsByArtistId = async (artistId) => { songs.push(brcSongs); } return songs.flat(1); -} \ No newline at end of file +}; diff --git a/src/controllers/characterController.js b/src/controllers/characterController.js index 5d6fda0..ae0b18f 100644 --- a/src/controllers/characterController.js +++ b/src/controllers/characterController.js @@ -1,130 +1,140 @@ -import {all} from "axios"; -import { - performJSRAction, - performJSRFAction, - performBRCAction, -} from "../config/db.js"; +import Constants from "../constants/dbConstants.js"; import {Actions} from "../config/dbActions.js"; -import LOGGER from "../utils/logger.js"; +import {performDBAction} from "../config/db.js"; import {sortObjects} from "../utils/utility.js"; +import LOGGER from "../utils/logger.js"; const Character = "Character"; +const {JSR_DB, JSRF_DB, BRC_DB, gameMap} = Constants; export const getAllCharacters = async (req, res) => { try { const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined; const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc"; - const jsrCharacters = await fetchJSRCharacters(req); - const jsrfCharacters = await fetchJSRFCharacters(req); - const brcCharacters = await fetchBRCCharacters(req); + const characters = await fetchCharacters(req, "ALL"); if (sortByValue) { - const characters = [ - ...jsrCharacters, - ...jsrfCharacters, - ...brcCharacters, - ]; return res.send(characters.sort(sortObjects(sortByValue, sortOrder))); } - res.send([...jsrCharacters, ...jsrfCharacters, ...brcCharacters]); + res.send(characters); } catch (err) { - LOGGER.error(`Could not fetch ALL Characters \n${err}`); + LOGGER.error(`Could not fetch ALL Characters`, err); + res.status(500).json({message: "Failed to fetch ALL characters", err: err}); } }; export const getRandomCharacter = async (req, res) => { try { - const jsrCharacters = await fetchJSRCharacters(req); - const jsrfCharacters = await fetchJSRFCharacters(req); - const brcCharacters = await fetchBRCCharacters(req); - - const allCharacters = [ - ...jsrCharacters, - ...jsrfCharacters, - ...brcCharacters, - ]; - - const randomCharacter = - allCharacters[Math.floor(Math.random() * allCharacters.length)]; - - res.json(randomCharacter); + const games = [JSR_DB, JSRF_DB, BRC_DB]; + const userSelectedGame = req?.query?.game; + let game = + gameMap[userSelectedGame] || + games[Math.floor(Math.random() * games.length)]; + const randomCharacter = await fetchRandomCharacter(req, game); + res.json(randomCharacter[0]); } catch (err) { - LOGGER.error(`Could not fetch random character \n${err}`); + LOGGER.error(`Could not fetch random character`, err); res.status(500).json({error: "Failed to fetch random character"}); } }; export const getJSRCharacters = async (req, res) => { try { - res.send(await fetchJSRCharacters(req)); + res.send(await fetchCharacters(req, JSR_DB)); } catch (err) { - LOGGER.error(`Could not fetch JSR Characters \n${err}`); + LOGGER.error(`Could not fetch JSR Characters`, err); + res.status(500).json({error: "Failed to fetch JSR characters"}); } }; export const getJSRFCharacters = async (req, res) => { try { - res.send(await fetchJSRFCharacters(req)); + res.send(await fetchCharacters(req, JSRF_DB)); } catch (err) { - LOGGER.error(`Could not fetch JSRF Characters \n${err}`); + LOGGER.error(`Could not fetch JSRF Characters`, err); + res.status(500).json({error: "Failed to fetch JSRF characters"}); } }; export const getBRCCharacters = async (req, res) => { try { - res.send(await fetchBRCCharacters(req)); + res.send(await fetchCharacters(req, BRC_DB)); } catch (err) { - LOGGER.error(`Could not fetch BRC Characters \n${err}`); + LOGGER.error(`Could not fetch BRC Characters`, err); + res.status(500).json({error: "Failed to fetch BRC characters"}); } }; export const getJSRCharacterById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performJSRAction(Actions.fetchById, Character, id)); + if (!id) { + return res.status(400).json({error: "Missing character ID"}); + } + res.send(await performDBAction(Actions.fetchById, JSR_DB, Character, id)); } catch (err) { - LOGGER.error(`Could not fetch JSR Character With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch JSR Character With ID: ${id}`, err); + res + .status(500) + .json({error: `Failed to fetch JSR character with ID ${id}`}); } }; export const getJSRFCharacterById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performJSRFAction(Actions.fetchById, Character, id)); + if (!id) { + return res.status(400).json({error: "Missing character ID"}); + } + res.send(await performDBAction(Actions.fetchById, JSRF_DB, Character, id)); } catch (err) { - LOGGER.error(`Could not fetch JSRF Character With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch JSRF Character With ID: ${id}`, err); + res + .status(500) + .json({error: `Failed to fetch JSRF character with ID ${id}`}); } }; export const getBRCCharacterById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performBRCAction(Actions.fetchById, Character, id)); + if (!id) { + return res.status(400).json({error: "Missing character ID"}); + } + res.send(await performDBAction(Actions.fetchById, BRC_DB, Character, id)); } catch (err) { - LOGGER.error(`Could not fetch BRC Character With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch BRC Character With ID: ${id}`, err); + res + .status(500) + .json({error: `Failed to fetch BRC character with ID ${id}`}); } }; -export const fetchJSRCharacters = async (req) => { - return await performJSRAction( - Actions.fetchWithQuery, - Character, - null, - req?.query - ); -}; +export const fetchCharacters = async (req, dbName) => { + if (dbName === "ALL") { + const jsrCharacters = await fetchCharacters(req, JSR_DB); + const jsrfCharacters = await fetchCharacters(req, JSRF_DB); + const brcCharacters = await fetchCharacters(req, BRC_DB); + const allCharacters = [ + ...jsrCharacters, + ...jsrfCharacters, + ...brcCharacters, + ]; + return allCharacters; + } -export const fetchJSRFCharacters = async (req) => { - return await performJSRFAction( + return await performDBAction( Actions.fetchWithQuery, + dbName, Character, null, req?.query ); }; -export const fetchBRCCharacters = async (req) => { - return await performBRCAction( - Actions.fetchWithQuery, +export const fetchRandomCharacter = async (req, dbName) => { + return await performDBAction( + Actions.fetchRandom, + dbName, Character, null, req?.query diff --git a/src/controllers/collectibleController.js b/src/controllers/collectibleController.js index 7ff003d..d63653f 100644 --- a/src/controllers/collectibleController.js +++ b/src/controllers/collectibleController.js @@ -1,50 +1,71 @@ -import {performBRCAction} from "../config/db.js"; +import Constants from "../constants/dbConstants.js"; import {Actions} from "../config/dbActions.js"; -import LOGGER from "../utils/logger.js"; +import {performDBAction} from "../config/db.js"; import {sortObjects} from "../utils/utility.js"; +import LOGGER from "../utils/logger.js"; const Collectible = "Collectible"; +const {BRC_DB} = Constants; -export const getAllCollectibles = async (req, res) => { +export const getCollectibles = async (req, res) => { try { const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined; const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc"; - const brcCollectibles = await fetchBRCCollectibles(req); + const allCollectibles = await fetchCollectibles(req); if (sortByValue) { - const collectibles = [...brcCollectibles]; + const collectibles = [...allCollectibles]; return res.send(collectibles.sort(sortObjects(sortByValue, sortOrder))); } - res.send([...brcCollectibles]); + res.send([...allCollectibles]); } catch (err) { - LOGGER.error(`Could not fetch ALL Collectibles \n${err}`); + LOGGER.error(`Could not fetch ALL Collectibles`, err); + res + .status(500) + .json({message: "Failed to fetch ALL Collectibles", err: err}); } }; -export const getBRCCollectibles = async (req, res) => { +export const getRandomCollectible = async (req, res) => { try { - res.send(await fetchBRCTags(req)); + const randomCollectible = await fetchRandomCollectible(req, BRC_DB); + res.json(randomCollectible[0]); } catch (err) { - LOGGER.error(`Could not fetch BRC Collectible \n${err}`); + LOGGER.error(`Could not fetch random collectible`, err); + res.status(500).json({error: "Failed to fetch random collectible"}); } }; -export const getBRCCollectibleById = async (req, res) => { +export const getCollectibleById = async (req, res) => { try { - const tagId = req?.params?.id; - res.send(await performBRCAction(Actions.fetchById, Collectible, tagId)); + const id = req?.params?.id; + res.send(await performDBAction(Actions.fetchById, BRC_DB, Collectible, id)); } catch (err) { - LOGGER.error(`Could not fetch BRC Collectible With ID: ${tagId} \n${err}`); + LOGGER.error(`Could not fetch Collectible With ID: ${tagId}`, err); + res + .status(500) + .json({message: `Failed to fetch Collectible by Id ${id}`, err: err}); } }; -export const fetchBRCCollectibles = async (req) => { +export const fetchCollectibles = async (req) => { if (req?.query) { - return await performBRCAction( + return await performDBAction( Actions.fetchWithQuery, + BRC_DB, Collectible, null, req?.query ); } - return await performBRCAction(Actions.fetchAll, Collectible, null); + return await performDBAction(Actions.fetchAll, BRC_DB, Collectible, null); +}; + +export const fetchRandomCollectible = async (req, dbName) => { + return await performDBAction( + Actions.fetchRandom, + dbName, + Collectible, + null, + req?.query + ); }; diff --git a/src/controllers/gameController.js b/src/controllers/gameController.js index 81e4259..b7a29b8 100644 --- a/src/controllers/gameController.js +++ b/src/controllers/gameController.js @@ -1,30 +1,36 @@ -import { performCoreAction } from "../config/db.js"; -import { Actions } from "../config/dbActions.js"; +import Constants from "../constants/dbConstants.js"; +import {Actions} from "../config/dbActions.js"; +import {performDBAction} from "../config/db.js"; - -const Game = 'Game'; +const Game = "Game"; +const {CORE_DB} = Constants; export const getAllGames = async (req, res) => { try { res.send(await fetchGames(req?.query)); - } catch(err) { - res.status(500).send(err); + } catch (err) { + res.status(500).send("Error fetching ALL Games", err); } -} +}; export const getGameById = async (req, res) => { try { res.send(await fetchGameById(req?.params?.id)); - } catch(err) { - res.send(err); + } catch (err) { + res.send(`Error fetching game by ID ${req?.params?.id}`, err); } -} - +}; export const fetchGames = async (query) => { - return await performCoreAction(Actions.fetchWithQuery, Game, null, query); -} + return await performDBAction( + Actions.fetchWithQuery, + CORE_DB, + Game, + null, + query + ); +}; export const fetchGameById = async (id) => { - return await performCoreAction(Actions.fetchById, Game, id) -} \ No newline at end of file + return await performDBAction(Actions.fetchById, CORE_DB, Game, id); +}; diff --git a/src/controllers/graffitiTagController.js b/src/controllers/graffitiTagController.js index c5de182..112cc94 100644 --- a/src/controllers/graffitiTagController.js +++ b/src/controllers/graffitiTagController.js @@ -1,15 +1,16 @@ -import { performJSRAction, performJSRFAction } from "../config/db.js"; -import { Actions } from "../config/dbActions.js"; +import Constants from "../constants/dbConstants.js"; +import {Actions} from "../config/dbActions.js"; +import {performDBAction} from "../config/db.js"; +import {sortObjects} from "../utils/utility.js"; import LOGGER from "../utils/logger.js"; -import { sortObjects } from "../utils/utility.js"; - -const GraffitiTag = 'GraffitiTag'; +const GraffitiTag = "GraffitiTag"; +const {JSR_DB, JSRF_DB} = Constants; export const getAllGraffitiTags = async (req, res) => { try { const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined; - const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : 'asc'; + const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc"; const jsrTags = await fetchJSRTags(req); const jsrfTags = await fetchJSRFTags(req); if (sortByValue) { @@ -17,55 +18,86 @@ export const getAllGraffitiTags = async (req, res) => { return res.send(tags.sort(sortObjects(sortByValue, sortOrder))); } res.send([...jsrTags, ...jsrfTags]); - } catch(err) { - LOGGER.error(`Could not fetch ALL GraffitiTags \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch ALL GraffitiTags`, err); + res + .status(500) + .json({message: "Failed to fetch ALL GraffitiTags", err: err}); } -} +}; export const getJSRGraffitiTags = async (req, res) => { try { res.send(await fetchJSRTags(req)); - } catch(err) { - LOGGER.error(`Could not fetch JSR GraffitiTags \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch JSR GraffitiTags`, err); + res + .status(500) + .json({message: "Failed to fetch JSR GraffitiTags", err: err}); } -} +}; export const getJSRFGraffitiTags = async (req, res) => { try { res.send(await fetchJSRFTags(req)); - } catch(err) { - LOGGER.error(`Could not fetch JSRF GraffitiTags \n${err}`) + } catch (err) { + LOGGER.error(`Could not fetch JSRF GraffitiTags`, err); + res + .status(500) + .json({message: "Failed to fetch JSRF GraffitiTags", err: err}); } -} +}; export const getJSRGraffitiTagById = async (req, res) => { try { const tagId = req?.params?.id; - res.send(await performJSRAction(Actions.fetchById, GraffitiTag, tagId)); - } catch(err) { - LOGGER.error(`Could not fetch JSR GraffitiTag With ID: ${tagId} \n${err}`); + res.send( + await performDBAction(Actions.fetchById, JSR_DB, GraffitiTag, tagId) + ); + } catch (err) { + LOGGER.error(`Could not fetch JSR GraffitiTag With ID: ${tagId}`, err); + res + .status(500) + .json({message: "Failed to fetch JSR GraffitiTag By ID", err: err}); } -} +}; export const getJSRFGraffitiTagById = async (req, res) => { try { const tagId = req?.params?.id; - res.send(await performJSRFAction(Actions.fetchById, GraffitiTag, tagId)); - } catch(err) { - LOGGER.error(`Could not fetch JSRF GraffitiTag With ID: ${tagId} \n${err}`); + res.send( + await performDBAction(Actions.fetchById, JSRF_DB, GraffitiTag, tagId) + ); + } catch (err) { + LOGGER.error(`Could not fetch JSRF GraffitiTag With ID: ${tagId}`, err); + res + .status(500) + .json({message: "Failed to fetch JSRF GraffitiTag By ID", err: err}); } -} +}; export const fetchJSRTags = async (req) => { if (req?.query) { - return await performJSRAction(Actions.fetchWithQuery, GraffitiTag, null, req?.query); + return await performDBAction( + Actions.fetchWithQuery, + JSR_DB, + GraffitiTag, + null, + req?.query + ); } - return await performJSRAction(Actions.fetchAll, GraffitiTag, null); -} + return await performDBAction(Actions.fetchAll, JSR_DB, GraffitiTag, null); +}; export const fetchJSRFTags = async (req) => { if (req?.query) { - return await performJSRFAction(Actions.fetchWithQuery, GraffitiTag, null, req?.query); + return await performDBAction( + Actions.fetchWithQuery, + JSRF_DB, + GraffitiTag, + null, + req?.query + ); } - return await performJSRFAction(Actions.fetchAll, GraffitiTag, null); -} \ No newline at end of file + return await performDBAction(Actions.fetchAll, JSRF_DB, GraffitiTag, null); +}; diff --git a/src/controllers/indexController.js b/src/controllers/indexController.js index 843219c..95c4d01 100644 --- a/src/controllers/indexController.js +++ b/src/controllers/indexController.js @@ -1,15 +1,14 @@ -import { fileURLToPath } from 'url'; -import path, { dirname } from 'path'; -import dotenv from 'dotenv'; +import {fileURLToPath} from "url"; +import path, {dirname} from "path"; +import dotenv from "dotenv"; dotenv.config(); - const __dirname = dirname(fileURLToPath(import.meta.url)); export const renderHome = (req, res) => { - res.sendFile(path.join(__dirname, '..', 'public', 'index.html')); -} + res.sendFile(path.join(__dirname, "..", "public", "index.html")); +}; export const renderDocs = (req, res) => { - res.sendFile(path.join(__dirname, '..', 'public', 'docs.html')) -} \ No newline at end of file + res.sendFile(path.join(__dirname, "..", "public", "docs.html")); +}; diff --git a/src/controllers/locationController.js b/src/controllers/locationController.js index 929fe9c..7330728 100644 --- a/src/controllers/locationController.js +++ b/src/controllers/locationController.js @@ -1,80 +1,103 @@ -import { - performJSRAction, - performJSRFAction, - performBRCAction, -} from "../config/db.js"; +import Constants from "../constants/dbConstants.js"; import {Actions} from "../config/dbActions.js"; -import LOGGER from "../utils/logger.js"; +import {performDBAction} from "../config/db.js"; import {sortObjects} from "../utils/utility.js"; +import LOGGER from "../utils/logger.js"; const Location = "Location"; const Level = "Level"; +const {JSR_DB, JSRF_DB, BRC_DB, gameMap} = Constants; export const getLocations = async (req, res) => { try { const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined; const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc"; - const jsrLocations = await fetchJSRLocations(req); - const jsrfLocations = await fetchJSRFLocations(req); - const brcLocations = await fetchBRCLocations(req); + const locations = await fetchLocations(req, "ALL"); if (sortByValue) { - const locations = [...jsrLocations, ...jsrfLocations, ...brcLocations]; return res.send(locations.sort(sortObjects(sortByValue, sortOrder))); } - res.send([...jsrLocations, ...jsrfLocations, ...brcLocations]); + res.send(locations); + } catch (err) { + LOGGER.error(`Could not fetch ALL Locations`, err); + res.status(500).json({message: "Failed to fetch ALL locations", err: err}); + } +}; + +export const getRandomLocation = async (req, res) => { + try { + const games = [JSR_DB, JSRF_DB, BRC_DB]; + const userSelectedGame = req?.query?.game; + let game = + gameMap[userSelectedGame] || + games[Math.floor(Math.random() * games.length)]; + const randomLocation = await fetchRandomLocation(req, game); + res.json(randomLocation[0]); } catch (err) { - LOGGER.error(`Could not fetch ALL Locations \n${err}`); + LOGGER.error(`Could not fetch random location`, err); + res.status(500).json({error: "Failed to fetch random location"}); } }; export const getJSRLocations = async (req, res) => { try { - res.send(await fetchJSRLocations(req)); + res.send(await fetchLocations(req, JSR_DB)); } catch (err) { - LOGGER.error(`Could not fetch JSR Locations \n${err}`); + LOGGER.error(`Could not fetch JSR Locations`, err); + res.status(500).json({message: "Failed to fetch JSR locations", err: err}); } }; export const getJSRLocationById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performJSRAction(Actions.fetchById, Location, id)); + res.send(await performDBAction(Actions.fetchById, JSR_DB, Location, id)); } catch (err) { - LOGGER.error(`Could not fetch JSR Location With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch JSR Location With ID: ${id}`, err); + res + .status(500) + .json({message: `Failed to fetch JSR location By ID ${id}`, err: err}); } }; export const getJSRFLocations = async (req, res) => { try { - res.send(await fetchJSRFLocations(req)); + res.send(await fetchLocations(req, JSRF_DB)); } catch (err) { - LOGGER.error(`Could not fetch JSRF Locations \n${err}`); + LOGGER.error(`Could not fetch JSRF Locations`, err); + res.status(500).json({message: "Failed to fetch JSRF locations", err: err}); } }; export const getJSRFLocationById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performJSRFAction(Actions.fetchById, Location, id)); + res.send(await performDBAction(Actions.fetchById, JSRF_DB, Location, id)); } catch (err) { - LOGGER.error(`Could not fetch JSRF Location With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch JSRF Location With ID: ${id}`, err); + res + .status(500) + .json({message: `Failed to fetch JSRF location By ID ${id}`, err: err}); } }; export const getBRCLocations = async (req, res) => { try { - res.send(await fetchBRCLocations(req)); + res.send(await fetchLocations(req, BRC_DB)); } catch (err) { - LOGGER.error(`Could not fetch BRC Locations \n${err}`); + LOGGER.error(`Could not fetch BRC Locations`, err); + res.status(500).json({message: "Failed to fetch BRC locations", err: err}); } }; export const getBRCLocationById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performBRCAction(Actions.fetchById, Location, id)); + res.send(await performDBAction(Actions.fetchById, BRC_DB, Location, id)); } catch (err) { - LOGGER.error(`Could not fetch BRC Location With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch BRC Location With ID: ${id}`, err); + res + .status(500) + .json({message: `Failed to fetch BRC location By ID ${id}`, err: err}); } }; @@ -82,63 +105,60 @@ export const getLevels = async (req, res) => { try { res.send(await fetchJSRLevels(req)); } catch (err) { - LOGGER.error(`Could not fetch JSR Levels \n${err}`); + LOGGER.error(`Could not fetch JSR Levels`, err); + res.status(500).json({message: "Failed to fetch JSR levels", err: err}); } }; export const getLevelById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performJSRAction(Actions.fetchById, Level, id)); + res.send(await performDBAction(Actions.fetchById, JSR_DB, Level, id)); } catch (err) { - LOGGER.error(`Could not fetch JSR Level with ID ${id} \n${err}`); + LOGGER.error(`Could not fetch JSR Level with ID ${id}`, err); + res + .status(500) + .json({message: `Failed to fetch JSR level By ID ${id}`, err: err}); } }; export const fetchJSRLevels = async (req) => { if (req?.query) { - return await performJSRAction( + return await performDBAction( Actions.fetchWithQuery, + JSR_DB, Level, null, req?.query ); } - return await performJSRAction(Actions.fetchAll, Level, null); + return await performDBAction(Actions.fetchAll, JSR_DB, Level, null); }; -export const fetchJSRLocations = async (req) => { - if (req?.query) { - return await performJSRAction( - Actions.fetchWithQuery, - Location, - null, - req?.query - ); +export const fetchLocations = async (req, dbName) => { + if (dbName === "ALL") { + const jsrLocations = await fetchLocations(req, JSR_DB); + const jsrfLocations = await fetchLocations(req, JSRF_DB); + const brcLocations = await fetchLocations(req, BRC_DB); + const allLocations = [...jsrLocations, ...jsrfLocations, ...brcLocations]; + return allLocations; } - return await performJSRAction(Actions.fetchAll, Location, null); -}; -export const fetchJSRFLocations = async (req) => { - if (req?.query) { - return await performJSRFAction( - Actions.fetchWithQuery, - Location, - null, - req?.query - ); - } - return await performJSRFAction(Actions.fetchAll, Location, null); + return await performDBAction( + Actions.fetchWithQuery, + dbName, + Location, + null, + req?.query + ); }; -export const fetchBRCLocations = async (req) => { - if (req?.query) { - return await performBRCAction( - Actions.fetchWithQuery, - Location, - null, - req?.query - ); - } - return await performBRCAction(Actions.fetchAll, Location, null); +export const fetchRandomLocation = async (req, dbName) => { + return await performDBAction( + Actions.fetchRandom, + dbName, + Location, + null, + req?.query + ); }; diff --git a/src/controllers/songController.js b/src/controllers/songController.js index c0f1c6a..a2a2cbf 100644 --- a/src/controllers/songController.js +++ b/src/controllers/songController.js @@ -1,107 +1,164 @@ -import { performJSRAction, performJSRFAction, performBRCAction } from "../config/db.js"; -import { Actions } from "../config/dbActions.js"; -import { sortObjects } from "../utils/utility.js"; +import Constants from "../constants/dbConstants.js"; +import {Actions} from "../config/dbActions.js"; +import {performDBAction} from "../config/db.js"; +import {sortObjects} from "../utils/utility.js"; +import LOGGER from "../utils/logger.js"; - -const Song = 'Song'; +const Song = "Song"; +const {JSR_DB, JSRF_DB, BRC_DB, gameMap} = Constants; export const getSongs = async (req, res) => { try { const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined; - const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : 'asc'; - const jsrSongs = await fetchJSRSongs(req); - const jsrfSongs = await fetchJSRFSongs(req); - const brcSongs = await fetchBRCSongs(req); + const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc"; + const songs = await fetchSongs(req, "ALL"); if (sortByValue) { - const songs = [...jsrSongs, ...jsrfSongs, ...brcSongs]; return res.send(songs.sort(sortObjects(sortByValue, sortOrder))); } - res.send([...jsrSongs, ...jsrfSongs, ...brcSongs]); - } catch(err) { - res.status(500).send(`Could not fetch ALL SONGS due to error: \n${err}`); + res.send(songs); + } catch (err) { + LOGGER.error(`Could not fetch ALL Songs`, err); + res.status(500).send(`Could not fetch ALL SONGS due to error:`, err); + } +}; + +export const getRandomSong = async (req, res) => { + try { + const games = [JSR_DB, JSRF_DB, BRC_DB]; + const userSelectedGame = req?.query?.game; + let game = + gameMap[userSelectedGame] || + games[Math.floor(Math.random() * games.length)]; + const randomSong = await fetchRandomSong(req, game); + res.json(randomSong[0]); + } catch (err) { + LOGGER.error(`Could not fetch random song`, err); + res.status(500).json({error: "Failed to fetch random song"}); } -} +}; export const getJSRSongs = async (req, res) => { try { - const jsrSongs = await fetchJSRSongs(req); + const jsrSongs = await fetchSongs(req, JSR_DB); if (jsrSongs) { return res.send(jsrSongs); } res.status(404).send(); - } catch(err) { - res.status(500).send(`Could not fetch JSR SONGS \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch JSR Songs`, err); + res.status(500).send(`Could not fetch JSR SONGS`, err); } -} +}; export const getJSRSongById = async (req, res) => { try { - const jsrSong = await performJSRAction(Actions.fetchById, Song, id); + const id = req?.params?.id; + const jsrSong = await performDBAction(Actions.fetchById, JSR_DB, Song, id); if (jsrSong) { return res.send(jsrSong); } - res.status(404).send(`JSR Song Resource could not be found at requested location`); - } catch(err) { - res.status(500).send(`Could not fetch JSR SONG with ID: ${req.params.id} \n${err}`); + res + .status(404) + .send(`JSR Song Resource could not be found at requested location`); + } catch (err) { + LOGGER.error(`Could not fetch JSR Song by ID ${req?.params?.id}`, err); + res + .status(500) + .send(`Could not fetch JSR SONG with ID: ${req.params.id}`, err); } -} +}; export const getJSRFSongs = async (req, res) => { try { - const jsrfSongs = await fetchJSRFSongs(req); + const jsrfSongs = await fetchSongs(req, JSRF_DB); if (jsrfSongs) { return res.send(jsrfSongs); } res.status(404).send(); - } catch(err) { - res.status(500).send(`Could not fetch JSRF Songs \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch JSRF Songs`, err); + res.status(500).send(`Could not fetch JSRF Songs`, err); } -} +}; export const getJSRFSongById = async (req, res) => { try { - const jsrfSong = await performJSRFAction(Actions.fetchById, Song, id); + const id = req?.params?.id; + const jsrfSong = await performDBAction( + Actions.fetchById, + JSRF_DB, + Song, + id + ); if (jsrfSong) { return res.send(jsrfSong); } - res.status(404).send(`JSRF Song Resource could not be found at requested location`); - } catch(err) { - res.status(500).send(`Could not fetch JSRF SONG with ID: ${req.params.id} \n${err}`); + res + .status(404) + .send(`JSRF Song Resource could not be found at requested location`); + } catch (err) { + LOGGER.error(`Could not fetch JSRF Song by ID`, err); + res + .status(500) + .send(`Could not fetch JSRF SONG with ID: ${req.params.id}`, err); } -} +}; export const getBRCSongs = async (req, res) => { try { - const brcSongs = await fetchBRCSongs(req); + const brcSongs = await fetchSongs(req, BRC_DB); if (brcSongs) { return res.send(brcSongs); } res.status(404).send(); - } catch(err) { - res.status(500).send(`Could not fetch BRC Songs \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch BRC Songs`, err); + res.status(500).send(`Could not fetch BRC Songs`, err); } -} +}; export const getBRCSongById = async (req, res) => { try { - const brcSong = await performBRCAction(Actions.fetchById, Song, id); + const id = req?.params?.id; + const brcSong = await performDBAction(Actions.fetchById, BRC_DB, Song, id); if (brcSong) { return res.send(brcSong); } - res.status(404).send(`BRC Song Resource could not be found at requested location`); - } catch(err) { - res.status(500).send(`Could not fetch BRC SONG with ID: ${req.params.id} \n${err}`); + res + .status(404) + .send(`BRC Song Resource could not be found at requested location`); + } catch (err) { + LOGGER.error(`Could not fetch BRC Song By Id`, err); + res + .status(500) + .send(`Could not fetch BRC Song with ID: ${req.params.id}`, err); + } +}; + +export const fetchSongs = async (req, dbName) => { + if (dbName === "ALL") { + const jsrSongs = await fetchSongs(req, JSR_DB); + const jsrfSongs = await fetchSongs(req, JSRF_DB); + const brcSongs = await fetchSongs(req, BRC_DB); + const songs = [...jsrSongs, ...jsrfSongs, ...brcSongs]; + return songs; } -} -export const fetchJSRSongs = async (req) => { - return await performJSRAction(Actions.fetchWithQuery, Song, null, req?.query); -} - -export const fetchJSRFSongs = async (req) => { - return await performJSRFAction(Actions.fetchWithQuery, Song, null, req?.query); -} + return await performDBAction( + Actions.fetchWithQuery, + dbName, + Song, + null, + req?.query + ); +}; -export const fetchBRCSongs = async (req) => { - return await performBRCAction(Actions.fetchWithQuery, Song, null, req?.query); -} \ No newline at end of file +export const fetchRandomSong = async (req, dbName) => { + return await performDBAction( + Actions.fetchRandom, + dbName, + Song, + null, + req?.query + ); +}; diff --git a/src/docs/CONTRIBUTE.md b/src/docs/CONTRIBUTE.md index ab0de78..21f063c 100644 --- a/src/docs/CONTRIBUTE.md +++ b/src/docs/CONTRIBUTE.md @@ -29,3 +29,9 @@ Test - Every other file contains unit and integration tests relating to a specific resource. Before submitting your changes, make sure to run `npm run test` to verify all tests pass successfully. + +How to run tests: + - To run ALL Tests: `npm run test` + - To test individual files + - `npm run test:file -- test/locations.test.js` + diff --git a/src/docs/DEV_SETUP.md b/src/docs/DEV_SETUP.md index 86879d7..a770f6c 100644 --- a/src/docs/DEV_SETUP.md +++ b/src/docs/DEV_SETUP.md @@ -13,9 +13,10 @@ This page will guide you on setting up a development environment for this projec ```sh git clone git@github.com:Jet-Set-Radio-API/JetSetRadio-API.git ``` + 2. Install Dependencies ```sh - npm install + nvm use && npm install ``` 3. [Create](https://account.mongodb.com/account/login) a local MongoDB Database or in Atlas @@ -42,15 +43,16 @@ This page will guide you on setting up a development environment for this projec # MONGO DATABASES (names do not matter) JSR_DB= JSRF_DB= + BRC_DB CORE_DB= ``` - The databases section in the env file are names of the databases. For development purposes it does not matter what these names are just as long as you can distinguish one from the other and you know which one is Core, JSR, or JSRF. + The databases section in the env file are names of the databases. For development purposes it does not matter what these names are just as long as you can distinguish one from the other and you know which one is which. 6. Run the project ```sh npm run qa ``` -7. (Optional) - To populate your local database with production data, hit the /pipe route in your browser or using Postman. The logs should indicate if the piping was successful. +7. (Optional) - To populate your local database with production data, hit the /pipe route in your browser or using Postman. The logs will indicate if the piping was successful. ```sh http://localhost:3005/pipe ``` diff --git a/src/managers/MiddlewareManager.js b/src/managers/MiddlewareManager.js index 79cbb4c..8b08edc 100644 --- a/src/managers/MiddlewareManager.js +++ b/src/managers/MiddlewareManager.js @@ -21,7 +21,7 @@ import {renderHome, renderDocs} from "../controllers/indexController.js"; import {listCollections} from "../config/db.js"; import LOGGER from "../utils/logger.js"; import {Actions} from "../config/dbActions.js"; -import {performCoreAdminAction} from "../config/db.js"; +import {performAdminAction} from "../config/db.js"; const cache = new MemoryCache.Cache(); const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -97,11 +97,13 @@ const filterPipeRoutes = async (req, endpoints) => { const filteredEndpoints = []; for (const endpoint of endpoints) { const model = endpoint.split("/")[3].replace("-", ""); - console.log("processing model: ", model, " and endpoint: ", endpoint); if (coreCollections.includes(model)) { filteredEndpoints.push(endpoint); } - if (jsrCollections.includes(model) && endpoint.includes("jsr") || endpoint.includes("collectibles")) { + if ( + (jsrCollections.includes(model) && endpoint.includes("jsr")) || + endpoint.includes("levels") + ) { filteredEndpoints.push(endpoint); } if (jsrfCollections.includes(model) && endpoint.includes("jsrf")) { @@ -164,7 +166,7 @@ const cacheMiddleware = (req, res, next) => { const clearCache = async (req, res) => { const username = req?.body?.username; const password = req?.body?.password; - const adminUser = await performCoreAdminAction(Actions.fetchAdmin, username); + const adminUser = await performAdminAction(Actions.fetchAdmin, username); if (!adminUser) { LOGGER.error("Admin User Not Found"); return res.status(400).send(); diff --git a/src/public/docs.html b/src/public/docs.html index 6b29bc7..97998a0 100644 --- a/src/public/docs.html +++ b/src/public/docs.html @@ -114,6 +114,10 @@

Characters

Endpoints: