Skip to content

Commit 93afa61

Browse files
authored
v1.1.1 - Add Random Character Route (#36)
1 parent 41c4021 commit 93afa61

File tree

7 files changed

+178
-180
lines changed

7 files changed

+178
-180
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jetsetradio-api",
3-
"version": "1.1.0",
3+
"version": "1.1.1",
44
"description": "A Data Provider relating to the JSR/JSRF universe",
55
"type": "module",
66
"main": "src/app.js",

src/controllers/characterController.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {all} from "axios";
12
import {
23
performJSRAction,
34
performJSRFAction,
@@ -30,6 +31,28 @@ export const getAllCharacters = async (req, res) => {
3031
}
3132
};
3233

34+
export const getRandomCharacter = async (req, res) => {
35+
try {
36+
const jsrCharacters = await fetchJSRCharacters(req);
37+
const jsrfCharacters = await fetchJSRFCharacters(req);
38+
const brcCharacters = await fetchBRCCharacters(req);
39+
40+
const allCharacters = [
41+
...jsrCharacters,
42+
...jsrfCharacters,
43+
...brcCharacters,
44+
];
45+
46+
const randomCharacter =
47+
allCharacters[Math.floor(Math.random() * allCharacters.length)];
48+
49+
res.json(randomCharacter);
50+
} catch (err) {
51+
LOGGER.error(`Could not fetch random character \n${err}`);
52+
res.status(500).json({error: "Failed to fetch random character"});
53+
}
54+
};
55+
3356
export const getJSRCharacters = async (req, res) => {
3457
try {
3558
res.send(await fetchJSRCharacters(req));

src/managers/MiddlewareManager.js

Lines changed: 97 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,143 @@
1-
import express from 'express';
2-
import listEndpoints from 'express-list-endpoints';
3-
import { fileURLToPath } from 'url';
4-
import path, { dirname } from 'path';
5-
import cors from 'cors';
6-
import * as bcrypt from 'bcrypt';
7-
import swaggerUi from 'swagger-ui-express';
8-
import rateLimit from 'express-rate-limit';
9-
import MemoryCache from 'memory-cache';
10-
import favicon from 'serve-favicon';
11-
import { createRequire } from 'module';
1+
import express from "express";
2+
import listEndpoints from "express-list-endpoints";
3+
import {fileURLToPath} from "url";
4+
import path, {dirname} from "path";
5+
import cors from "cors";
6+
import * as bcrypt from "bcrypt";
7+
import swaggerUi from "swagger-ui-express";
8+
import rateLimit from "express-rate-limit";
9+
import MemoryCache from "memory-cache";
10+
import favicon from "serve-favicon";
11+
import {createRequire} from "module";
1212
const require = createRequire(import.meta.url);
13-
const data = require('../utils/swagger-docs.json');
14-
import dotenv from 'dotenv';
13+
const data = require("../utils/swagger-docs.json");
14+
import dotenv from "dotenv";
1515
dotenv.config();
1616

17-
import Constants from '../constants/dbConstants.js';
18-
import HealthCheckManager from './HealthCheckManager.js';
19-
import router from '../routes/router.js';
20-
import { renderHome, renderDocs } from '../controllers/indexController.js';
21-
import { listCollections } from '../config/db.js';
22-
import LOGGER from '../utils/logger.js';
23-
import { Actions } from '../config/dbActions.js';
24-
import { performCoreAdminAction } from '../config/db.js';
25-
17+
import Constants from "../constants/dbConstants.js";
18+
import HealthCheckManager from "./HealthCheckManager.js";
19+
import router from "../routes/router.js";
20+
import {renderHome, renderDocs} from "../controllers/indexController.js";
21+
import {listCollections} from "../config/db.js";
22+
import LOGGER from "../utils/logger.js";
23+
import {Actions} from "../config/dbActions.js";
24+
import {performCoreAdminAction} from "../config/db.js";
2625

2726
const cache = new MemoryCache.Cache();
2827
const __dirname = dirname(fileURLToPath(import.meta.url));
2928
const healthCheckManager = new HealthCheckManager();
30-
const { CORE_DB, JSR_DB, JSRF_DB } = Constants;
29+
const {CORE_DB, JSR_DB, JSRF_DB, BRC_DB} = Constants;
3130

3231
class MiddlewareManager {
33-
3432
setMiddleware(app) {
35-
app.set('views', path.join(__dirname, '..', 'views'));
36-
app.set('view engine', 'ejs');
37-
33+
app.set("views", path.join(__dirname, "..", "views"));
34+
app.set("view engine", "ejs");
35+
3836
app.use(cors());
39-
app.use(express.static(path.join(__dirname, '..', 'public')));
40-
app.use(express.urlencoded({ extended: true }));
37+
app.use(express.static(path.join(__dirname, "..", "public")));
38+
app.use(express.urlencoded({extended: true}));
4139
app.use(express.json());
42-
app.use(favicon(path.join(__dirname, '..', 'public', 'img', 'favicon.ico')));
43-
44-
app.get('/', (req, res) => renderHome(req, res));
45-
app.get('/docs', (req, res) => renderDocs(req, res));
46-
app.get('/health', (req, res) => res.send(healthCheckManager.getAppHealth()));
47-
app.get('/endpoints', async (req, res) => res.send(await filterPipeRoutes(req, listEndpoints(app))));
48-
app.post('/cache/clear', async (req, res) => await clearCache(req, res));
49-
app.use('/v1/api', [limiter, cacheMiddleware], router);
50-
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(data))
40+
app.use(
41+
favicon(path.join(__dirname, "..", "public", "img", "favicon.ico"))
42+
);
43+
44+
app.get("/", (req, res) => renderHome(req, res));
45+
app.get("/docs", (req, res) => renderDocs(req, res));
46+
app.get("/health", (req, res) =>
47+
res.send(healthCheckManager.getAppHealth())
48+
);
49+
app.get("/endpoints", async (req, res) =>
50+
res.send(await filterPipeRoutes(req, listEndpoints(app)))
51+
);
52+
app.post("/cache/clear", async (req, res) => await clearCache(req, res));
53+
app.use("/v1/api", [limiter, cacheMiddleware], router);
54+
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(data));
5155
}
52-
5356
}
5457

5558
/**
5659
* Return a list of Available Endpoints
5760
* Pass pipe=true to filter the results to just the routes needed to populate a development database
58-
*
61+
*
5962
* @param {*} req : the express request object
6063
* @param {*} endpoints : the unfiltered endpoints object list
61-
* @returns
64+
* @returns
6265
*/
6366
const filterPipeRoutes = async (req, endpoints) => {
6467
const pipe = req?.query?.pipe;
6568
const coreCollectionsObj = await listCollections(CORE_DB);
6669
const jsrCollectionsObj = await listCollections(JSR_DB);
6770
const jsrfCollectionsObj = await listCollections(JSRF_DB);
68-
const coreCollections = coreCollectionsObj.map(col => { return `${col.name.toLowerCase()}s` });
69-
const jsrCollections = jsrCollectionsObj.map(col => { return `${col.name.toLowerCase()}s` });
70-
const jsrfCollections = jsrfCollectionsObj.map(col => { return `${col.name.toLowerCase()}s` });
71+
const brcCollectionsObj = await listCollections(BRC_DB);
72+
73+
const coreCollections = coreCollectionsObj.map((col) => {
74+
return `${col.name.toLowerCase()}s`;
75+
});
76+
const jsrCollections = jsrCollectionsObj.map((col) => {
77+
return `${col.name.toLowerCase()}s`;
78+
});
79+
const jsrfCollections = jsrfCollectionsObj.map((col) => {
80+
return `${col.name.toLowerCase()}s`;
81+
});
82+
const brcCollections = brcCollectionsObj.map((col) => {
83+
return `${col.name.toLowerCase()}s`;
84+
});
7185

7286
endpoints = endpoints
73-
.filter(endpoint => pipe
74-
? !endpoint.path.includes(':') && endpoint.path.includes('/v1/api')
75-
: endpoint)
76-
.map(endpoint => { return endpoint.path })
87+
.filter((endpoint) =>
88+
pipe
89+
? !endpoint.path.includes(":") && endpoint.path.includes("/v1/api")
90+
: endpoint
91+
)
92+
.map((endpoint) => {
93+
return endpoint.path;
94+
});
7795

7896
if (pipe) {
7997
const filteredEndpoints = [];
8098
for (const endpoint of endpoints) {
81-
const model = endpoint.split('/')[3].replace('-', '');
99+
const model = endpoint.split("/")[3].replace("-", "");
100+
console.log("processing model: ", model, " and endpoint: ", endpoint);
82101
if (coreCollections.includes(model)) {
83102
filteredEndpoints.push(endpoint);
84103
}
85-
if (jsrCollections.includes(model) && endpoint.includes('jsr')) {
104+
if (jsrCollections.includes(model) && endpoint.includes("jsr")) {
86105
filteredEndpoints.push(endpoint);
87106
}
88-
if (jsrfCollections.includes(model) && endpoint.includes('jsrf')) {
107+
if (jsrfCollections.includes(model) && endpoint.includes("jsrf")) {
108+
filteredEndpoints.push(endpoint);
109+
}
110+
if (
111+
brcCollections.includes(model) &&
112+
(endpoint.includes("brc") || endpoint.includes("collectibles"))
113+
) {
89114
filteredEndpoints.push(endpoint);
90115
}
91116
}
92117
endpoints = filteredEndpoints;
93118
}
94119
return [...new Set(endpoints)];
95-
}
120+
};
96121

122+
/* Rate Limiting */
97123
const limiter = rateLimit({
98124
windowMs: 60 * 60 * 1000, // 1 hour time range
99125
max: 1000, // 1000 requests limit
100-
keyGenerator: (req) => {
126+
keyGenerator: (req) => {
101127
return req.ip;
102128
},
103129
});
104130

131+
/* Set Cache */
105132
const cacheMiddleware = (req, res, next) => {
106-
const clientIp = req.ip;
133+
const clientIp = req.ip;
107134
const cacheKey = `cache_${clientIp}_${req.originalUrl || req.url}`;
108135

136+
/* Don't cache 'Random' routes */
137+
if (req?.path.includes("random")) {
138+
return next();
139+
}
140+
109141
const cachedData = cache.get(cacheKey);
110142
if (cachedData) {
111143
LOGGER.info(`Cache hit for url ${req.url}`);
@@ -128,33 +160,34 @@ const cacheMiddleware = (req, res, next) => {
128160
next();
129161
};
130162

163+
/* Clear Cache */
131164
const clearCache = async (req, res) => {
132165
const username = req?.body?.username;
133166
const password = req?.body?.password;
134167
const adminUser = await performCoreAdminAction(Actions.fetchAdmin, username);
135168
if (!adminUser) {
136-
LOGGER.error('Admin User Not Found');
137-
return res.status(400).send()
169+
LOGGER.error("Admin User Not Found");
170+
return res.status(400).send();
138171
}
139172
const authenticated = await validatePassword(password, adminUser?.password);
140173
if (!authenticated) {
141-
LOGGER.error('Invalid Admin Creds!');
142-
return res.status(401).send('Unauthorized');
174+
LOGGER.error("Invalid Admin Creds!");
175+
return res.status(401).send("Unauthorized");
143176
}
144177
if (adminUser && authenticated) {
145178
cache.clear();
146-
LOGGER.info('All Caches Cleared');
147-
return res.send('Cache Cleared');
179+
LOGGER.info("All Caches Cleared");
180+
return res.send("Cache Cleared");
148181
}
149-
res.status(500).send('Unexpected Behavior');
150-
}
182+
res.status(500).send("Unexpected Behavior");
183+
};
151184

152185
const validatePassword = async (password, hashedPassword) => {
153186
try {
154187
return await bcrypt.compare(password, hashedPassword);
155188
} catch (err) {
156189
LOGGER.warn(`Error validating Admin password ${err}`);
157190
}
158-
}
191+
};
159192

160-
export default MiddlewareManager;
193+
export default MiddlewareManager;

src/routes/characterRouter.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import express from 'express';
2-
import { getAllCharacters, getJSRCharacters, getJSRFCharacters, getJSRCharacterById, getJSRFCharacterById, getBRCCharacters, getBRCCharacterById } from '../controllers/characterController.js';
2+
import { getAllCharacters, getRandomCharacter, getJSRCharacters, getJSRFCharacters, getJSRCharacterById, getJSRFCharacterById, getBRCCharacters, getBRCCharacterById } from '../controllers/characterController.js';
33

44

55
const characters = express.Router();
66

77
characters.get('/', async (req, res) => /* #swagger.tags = ['Characters'] */ await getAllCharacters(req, res));
8+
characters.get('/random', async (req, res) => /* #swagger.tags = ['Characters'] */ await getRandomCharacter(req, res));
89
characters.get('/jsr', async (req, res) => /* #swagger.tags = ['Characters'] */ await getJSRCharacters(req, res));
910
characters.get('/jsr/:id', async (req, res) => /* #swagger.tags = ['Characters'] */ await getJSRCharacterById(req, res));
1011
characters.get('/jsrf', async (req, res) => /* #swagger.tags = ['Characters'] */ await getJSRFCharacters(req, res));

src/routes/collectibleRouter.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66

77
const collectibles = express.Router();
88

9-
collectibles.get("/", async (req, res) => /* #swagger.tags = ['Collectible'] */ await getAllCollectibles(req, res));
10-
collectibles.get("/:id", async (req, res) => /* #swagger.tags = ['Collectible'] */ await getBRCCollectibleById(req, res));
9+
collectibles.get("/", async (req, res) => /* #swagger.tags = ['Collectibles'] */ await getAllCollectibles(req, res));
10+
collectibles.get("/:id", async (req, res) => /* #swagger.tags = ['Collectibles'] */ await getBRCCollectibleById(req, res));
1111

1212
export default collectibles;

0 commit comments

Comments
 (0)