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: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Filename: <env>.env

PORT=
BASE_URL=http://localhost:<PORT>
LOG_LEVEL=
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v14.16.0
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@

# <img src="https://media-library-swgu.netlify.app/jetsetradio-api-core/jsr-logo.png" width=6% /> JetSetRadio-API

Easily consume Jet Set Radio data into your applications! This public API includes data from both JSR and JSRF.
Easily consume Jet Set Radio data into your applications! This public API includes data from:
- Jet Set Radio
- Jet Set Radio Future
- Bomb Rush Cyberfunk


## Purpose
Expand Down
7,043 changes: 93 additions & 6,950 deletions package-lock.json

Large diffs are not rendered by default.

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.0.4",
"version": "1.1.0",
"description": "A Data Provider relating to the JSR/JSRF universe",
"type": "module",
"main": "src/app.js",
Expand Down
144 changes: 100 additions & 44 deletions src/config/db.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { MongoClient } from 'mongodb';
import { ObjectId } from 'mongodb';
import dotenv from 'dotenv';
import {MongoClient} from "mongodb";
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";
import Constants from "../constants/dbConstants.js";


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

const buildMongoUri = () => {
const user = process.env.MONGO_USER;
Expand All @@ -18,7 +17,9 @@ const buildMongoUri = () => {
return LOGGER.error(`Invalid admin user found while building mongo uri`);
}
if (!password) {
return LOGGER.error(`Invalid admin password found while building mongo uri`);
return LOGGER.error(
`Invalid admin password found while building mongo uri`
);
}
if (!clusterName) {
return LOGGER.error(`Invalid cluster name found while building mongo uri`);
Expand All @@ -27,97 +28,153 @@ const buildMongoUri = () => {
return LOGGER.error(`Invalid domain name found while building mongo uri`);
}
return `mongodb+srv://${user}:${password}@${clusterName}.${domainName}?retryWrites=true&w=majority`;
}
};

const client = new MongoClient(buildMongoUri());

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

export const performCoreAction = async (action, 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) {
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) {
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) {
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,
collection,
id,
getQueryObject(qps),
queryActions
);
} catch (err) {
console.error(err);
return err;
} finally {
await client.close();
}
}
};

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

const excludedKeys = ['sortBy', 'orderBy', 'filter', 'page', 'limit'];
const objectIdKeys = ['gameId', 'locationId', 'levelId', 'graffitiTagId', 'characterId', 'artistId', 'songId',
'game.id', 'location.id', 'adjacentLocations.id', 'level.id', 'graffitiTag.id', 'character.id', 'artist.id', 'song.id'];
};

const excludedKeys = ["sortBy", "orderBy", "filter", "page", "limit"];
const objectIdKeys = [
"gameId",
"locationId",
"levelId",
"graffitiTagId",
"characterId",
"artistId",
"songId",
"game.id",
"location.id",
"adjacentLocations.id",
"level.id",
"graffitiTag.id",
"character.id",
"artist.id",
"song.id",
];

// Prepare the queryParameters as one single object for mongoDB query
const getQueryObject = (qps) => {
if (qps) {
const queryMap = {};

/* Remove special keys that will be used for query suffix */
excludedKeys.forEach(key => delete qps[key]);
excludedKeys.forEach((key) => delete qps[key]);

/* Transform the user query object into a format MongoDB can understand */
for (let [key, value] of Object.entries(qps)) {
if (typeof value === String && value.includes(',')) {
value = value.split(',')
if (typeof value === String && value.includes(",")) {
value = value.split(",");
queryMap[key] = {$all: value}; // Uses AND currently...
} else if (value === 'true' || value === 'false') {
queryMap[key] = value === 'true' ? true : false;
} else if (typeof value === String && value.toLowerCase() === 'female') {
queryMap[key] = 'Female';
} else if (typeof value === String && value.toLowerCase() === 'male') {
queryMap[key] = 'Male';
} else if (value === "true" || value === "false") {
queryMap[key] = value === "true" ? true : false;
} else if (typeof value === String && value.toLowerCase() === "female") {
queryMap[key] = "Female";
} else if (typeof value === String && value.toLowerCase() === "male") {
queryMap[key] = "Male";
} else if (objectIdKeys.includes(key) && ObjectId.isValid(value)) {
queryMap[key] = new ObjectId(value);
} else {
Expand All @@ -127,17 +184,16 @@ const getQueryObject = (qps) => {
return queryMap;
}
return {};
}
};

const getSortQuery = (query) => {
const field = query?.sortBy;
const order = query?.orderBy || 'asc';
const orderMap = { asc: 1, desc: -1 };
const order = query?.orderBy || "asc";
const orderMap = {asc: 1, desc: -1};
return field ? {[field]: orderMap[order]} : {};
}
};

const getLimitSize = (query) => {
const limit = query?.limit || '0';
return !isNaN(Number(limit)) && limit !== '0' ? Number(limit) : 0;
}

const limit = query?.limit || "0";
return !isNaN(Number(limit)) && limit !== "0" ? Number(limit) : 0;
};
7 changes: 4 additions & 3 deletions src/constants/dbConstants.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import dotenv from 'dotenv';
import dotenv from "dotenv";
dotenv.config();

const Constants = {
CORE_DB: process.env.CORE_DB,
JSR_DB: process.env.JSR_DB,
JSRF_DB: process.env.JSRF_DB,
}
BRC_DB: process.env.BRC_DB,
};

export default Constants;
export default Constants;
7 changes: 5 additions & 2 deletions src/controllers/artistController.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { performCoreAction, performJSRAction, performJSRFAction } from "../config/db.js";
import { performBRCAction, performCoreAction, performJSRAction, performJSRFAction } from "../config/db.js";
import { Actions } from "../config/dbActions.js";
import { ObjectId } from "mongodb";

Expand Down Expand Up @@ -40,7 +40,6 @@ export const getSongsByArtist = async (req, res) => {
} catch(err) {
res.status(500).send(`Could not fetch Songs by Artist with ID: ${req.params.id} \n${err}`);
}

}


Expand All @@ -55,11 +54,15 @@ 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) });
if (jsrSongs && jsrSongs.length > 0) {
songs.push(jsrSongs);
}
if (jsrfSongs && jsrfSongs.length > 0) {
songs.push(jsrfSongs);
}
if (brcSongs && brcSongs.length > 0) {
songs.push(brcSongs);
}
return songs.flat(1);
}
Loading