Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jetsetradio-api",
"version": "1.1.2",
"version": "1.1.3",
"description": "A Data Provider relating to the JSR/JSRF universe",
"type": "module",
"main": "src/app.js",
Expand Down
20 changes: 12 additions & 8 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dotenv from "dotenv";
dotenv.config();

import LOGGER from "./utils/logger.js";
import {connectToDb} from "./config/db.js";
import MiddlewareManager from "./managers/MiddlewareManager.js";

const middlewareManager = new MiddlewareManager();
Expand All @@ -14,12 +15,15 @@ const baseUrl = process.env.BASE_URL;

middlewareManager.setMiddleware(app);

app.listen(PORT || 8080, () => {
LOGGER.info(`JSR-API Listening on port ${PORT}`);
(async () => {
await connectToDb();
app.listen(PORT || 8080, () => {
LOGGER.info(`JSR-API Listening on port ${PORT}`);

// Ping App every 10 minutes
setInterval(async () => {
const res = await axios.get(`${baseUrl}/health`);
console.log(`App Ping - ${baseUrl}. Status: ${res.data.message}`);
}, 600000);
});
// Ping App every 10 minutes
setInterval(async () => {
const res = await axios.get(`${baseUrl}/health`);
console.log(`App Ping - ${baseUrl}. Status: ${res.data.message}`);
}, 600000);
});
})();
50 changes: 19 additions & 31 deletions src/config/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,43 @@ import {ObjectId} from "mongodb";
import dotenv from "dotenv";
dotenv.config();

import LOGGER from "../utils/logger.js";
import Constants from "../constants/dbConstants.js";
import LOGGER from "../utils/logger.js";

const {CORE_DB} = Constants;

const buildMongoUri = () => {
const user = process.env.MONGO_USER;
const password = process.env.MONGO_PASS;
const clusterName = process.env.MONGO_CLUSTER;
const domainName = process.env.MONGO_DOMAIN;
if (!user) {
return LOGGER.error(`Invalid admin user found while building mongo uri`);
}
if (!password) {
return LOGGER.error(
`Invalid admin password found while building mongo uri`
);
}
if (!clusterName) {
return LOGGER.error(`Invalid cluster name found while building mongo uri`);
/* Database Connections */
const client = new MongoClient(process.env.MONGO_URI);
let isConnected = false;

export const connectToDb = async () => {
if (isConnected) {
return client; // reuse existing connection
}
if (!domainName) {
return LOGGER.error(`Invalid domain name found while building mongo uri`);
try {
await client.connect();
isConnected = true;
LOGGER.info("✅ Connected to MongoDB");
return client;
} catch (err) {
LOGGER.error("❌ Failed to connect to MongoDB", err);
throw err;
}
return `mongodb+srv://${user}:${password}@${clusterName}.${domainName}?retryWrites=true&w=majority`;
};

const client = new MongoClient(buildMongoUri());

/* Database Connections */
export const performAdminAction = async (action, username) => {
try {
await client.connect();
await connectToDb();
return await action(client, CORE_DB, "Admin", username);
} catch (err) {
console.error(err);
return err;
} finally {
await client.close();
}
};

export const performDBAction = async (action, dbName, collection, id, qps) => {
try {
await client.connect();
await connectToDb();
const queryActions = [getSortQuery(qps), getLimitSize(qps)];
return await action(
client,
Expand All @@ -60,20 +52,16 @@ export const performDBAction = async (action, dbName, collection, id, qps) => {
} catch (err) {
console.error(err);
return err;
} finally {
await client.close();
}
};

export const listCollections = async (dbName) => {
try {
await client.connect();
await connectToDb();
return await client.db(dbName).listCollections().toArray();
} catch (err) {
console.error(err);
return err;
} finally {
await client.close();
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/config/dbActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export const Actions = {
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 }) },
fetchRandom: async (client, dbName, collectionName) => { return await client.db(dbName).collection(collectionName).aggregate([{ $sample: { size: 1 } }]).toArray(); }
fetchRandom: async (client, dbName, collectionName, count) => { return await client.db(dbName).collection(collectionName).aggregate([{ $sample: { size: count } }]).toArray(); }
}
21 changes: 3 additions & 18 deletions src/controllers/characterController.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ 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 {fetchRandom} from "./utilController.js";
import LOGGER from "../utils/logger.js";

const Character = "Character";
const {JSR_DB, JSRF_DB, BRC_DB, gameMap} = Constants;
const {JSR_DB, JSRF_DB, BRC_DB} = Constants;

export const getAllCharacters = async (req, res) => {
try {
Expand All @@ -24,13 +25,7 @@ export const getAllCharacters = async (req, res) => {

export const getRandomCharacter = 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 randomCharacter = await fetchRandomCharacter(req, game);
res.json(randomCharacter[0]);
res.send(await fetchRandom(req, Character));
} catch (err) {
LOGGER.error(`Could not fetch random character`, err);
res.status(500).json({error: "Failed to fetch random character"});
Expand Down Expand Up @@ -130,13 +125,3 @@ export const fetchCharacters = async (req, dbName) => {
req?.query
);
};

export const fetchRandomCharacter = async (req, dbName) => {
return await performDBAction(
Actions.fetchRandom,
dbName,
Character,
null,
req?.query
);
};
14 changes: 4 additions & 10 deletions src/controllers/collectibleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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 {fetchRandom} from "./utilController.js";

const Collectible = "Collectible";
const {BRC_DB} = Constants;
Expand All @@ -27,8 +28,7 @@ export const getCollectibles = async (req, res) => {

export const getRandomCollectible = async (req, res) => {
try {
const randomCollectible = await fetchRandomCollectible(req, BRC_DB);
res.json(randomCollectible[0]);
res.send(await fetchRandom(req, Collectible, BRC_DB));
} catch (err) {
LOGGER.error(`Could not fetch random collectible`, err);
res.status(500).json({error: "Failed to fetch random collectible"});
Expand Down Expand Up @@ -60,12 +60,6 @@ export const fetchCollectibles = async (req) => {
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
);
export const fetchRandomCollectible = async (req, dbName, count) => {
return await performDBAction(Actions.fetchRandom, dbName, Collectible, count);
};
21 changes: 3 additions & 18 deletions src/controllers/locationController.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ 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 {fetchRandom} from "./utilController.js";
import LOGGER from "../utils/logger.js";

const Location = "Location";
const Level = "Level";
const {JSR_DB, JSRF_DB, BRC_DB, gameMap} = Constants;
const {JSR_DB, JSRF_DB, BRC_DB} = Constants;

export const getLocations = async (req, res) => {
try {
Expand All @@ -25,13 +26,7 @@ export const getLocations = async (req, res) => {

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]);
res.send(await fetchRandom(req, Location));
} catch (err) {
LOGGER.error(`Could not fetch random location`, err);
res.status(500).json({error: "Failed to fetch random location"});
Expand Down Expand Up @@ -152,13 +147,3 @@ export const fetchLocations = async (req, dbName) => {
req?.query
);
};

export const fetchRandomLocation = async (req, dbName) => {
return await performDBAction(
Actions.fetchRandom,
dbName,
Location,
null,
req?.query
);
};
21 changes: 3 additions & 18 deletions src/controllers/songController.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ 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 {fetchRandom} from "./utilController.js";
import LOGGER from "../utils/logger.js";

const Song = "Song";
const {JSR_DB, JSRF_DB, BRC_DB, gameMap} = Constants;
const {JSR_DB, JSRF_DB, BRC_DB} = Constants;

export const getSongs = async (req, res) => {
try {
Expand All @@ -24,13 +25,7 @@ export const getSongs = async (req, res) => {

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]);
res.send(await fetchRandom(req, Song));
} catch (err) {
LOGGER.error(`Could not fetch random song`, err);
res.status(500).json({error: "Failed to fetch random song"});
Expand Down Expand Up @@ -152,13 +147,3 @@ export const fetchSongs = async (req, dbName) => {
req?.query
);
};

export const fetchRandomSong = async (req, dbName) => {
return await performDBAction(
Actions.fetchRandom,
dbName,
Song,
null,
req?.query
);
};
50 changes: 50 additions & 0 deletions src/controllers/utilController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {performDBAction} from "../config/db.js";
import {Actions} from "../config/dbActions.js";
import Constants from "../constants/dbConstants.js";
import LOGGER from "../utils/logger.js";

const {JSR_DB, JSRF_DB, BRC_DB, gameMap} = Constants;

/* Helper Functions to support all other Controllers */
export const fetchRandom = async (req, resource, game) => {
try {
const games = [JSR_DB, JSRF_DB, BRC_DB];
const selectedGame = req?.query?.game;
const count = Number(req?.query?.count);
const safeCount = Number.isFinite(count) && count > 0 ? count : 1;

/* if a game is provided */
if (game || selectedGame) {
const dbName = game || gameMap[selectedGame];
return await performDBAction(
Actions.fetchRandom,
dbName,
resource,
safeCount
);
}

/* If no game is provided, select a random characters from a random games */
const shuffled = [...games].sort(() => Math.random() - 0.5);
let remaining = safeCount;
const promises = [];

for (const dbName of shuffled) {
if (remaining <= 0) break;
const take = Math.min(
remaining,
Math.floor(Math.random() * remaining) + 1
);
remaining -= take;
promises.push(
performDBAction(Actions.fetchRandom, dbName, resource, take)
);
}

const results = await Promise.all(promises);
return results.flat();
} catch (err) {
LOGGER.error(`Error fetching random ${resource} from game ${game}`, err);
return [];
}
};
Loading