API: Add experimental Json API
in preparation for external healthchecks and the admin backend. We define a couple test routes: - / List all routes - /ping Replies pong for online-checks - /stats Get high-level bot statistics - /most-recent-drop Returns the most recent entry from dropHistories The last two routes require a valid apikey header. All routes are prefixed by /api/v1
This commit is contained in:
@@ -7,3 +7,5 @@ DB_PASSWORD=
|
||||
DB_DATABASE=
|
||||
DB_ROOTPW=
|
||||
DB_PORT=
|
||||
API_PORT=3080
|
||||
API_ACCESS_TOKEN=
|
||||
|
||||
89
api/jsonApi.js
Normal file
89
api/jsonApi.js
Normal file
@@ -0,0 +1,89 @@
|
||||
require("dotenv").config();
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const { Card, User, DropHistory, Character, Group } = require("../models");
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
const ACCESS_TOKEN = process.env.API_ACCESS_TOKEN;
|
||||
const app = express();
|
||||
const router = express.Router();
|
||||
|
||||
const PREFIX = '/api/v1';
|
||||
|
||||
app.use(bodyParser.json());
|
||||
|
||||
function isAuthorized(req, res) {
|
||||
const providedToken = req.headers['apikey'];
|
||||
if (providedToken !== ACCESS_TOKEN) {
|
||||
res.status(401).json({ error: 'Unauthorized' });
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const routes = router.stack
|
||||
.filter(layer => layer.route) // Filter out non-routes
|
||||
.map(layer => {
|
||||
return {
|
||||
route: PREFIX + layer.route.path,
|
||||
methods: layer.route.methods
|
||||
};
|
||||
});
|
||||
|
||||
res.json({ routes: routes });
|
||||
});
|
||||
|
||||
router.get('/ping', (req, res) => {
|
||||
res.json({ status: 'Pong' });
|
||||
});
|
||||
|
||||
router.get('/stats', async (req, res) => {
|
||||
|
||||
if(!isAuthorized(req, res)){return;}
|
||||
|
||||
res.json({
|
||||
users: await User.count(),
|
||||
cards: await Card.count({where: { burned: { [Op.eq]: false }}}),
|
||||
burned: await Card.count({where: { burned: { [Op.eq]: true }}}),
|
||||
drops: await DropHistory.count(),
|
||||
groups: await Group.count({where: { enabled: { [Op.eq]: true }}}),
|
||||
characters: await Character.count({where: { enabled: { [Op.eq]: true }}}),
|
||||
uptime: app.client.uptime
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/most-recent-drop', async (req, res) => {
|
||||
|
||||
if(!isAuthorized(req, res)){return;}
|
||||
|
||||
try {
|
||||
const mostRecentDrop = await DropHistory.findOne({
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
if (!mostRecentDrop) {
|
||||
return res.status(404).send('No drops found');
|
||||
}
|
||||
|
||||
const dropData = JSON.parse(mostRecentDrop.dropData);
|
||||
|
||||
const cards = await Promise.all(Object.keys(dropData).map(async key => {
|
||||
const cardData = dropData[key]?.cardData;
|
||||
if(!cardData) {return};
|
||||
const card = JSON.parse(cardData);
|
||||
return { identifier: card.identifier, quality: card.quality, character: await Character.findByPk(card.characterId) };
|
||||
}));
|
||||
|
||||
let response = { dropper: await User.findByPk(dropData.dropper), cards: cards.filter(Boolean)};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).send('Error fetching most recent drop');
|
||||
}
|
||||
}).needsAuth= true;
|
||||
|
||||
app.use(PREFIX, router);
|
||||
module.exports = app;
|
||||
@@ -10,6 +10,8 @@ services:
|
||||
depends_on:
|
||||
- "mysql"
|
||||
working_dir: /app
|
||||
ports:
|
||||
- 127.0.0.1:${API_PORT}:${API_PORT}
|
||||
volumes:
|
||||
- ./:/app
|
||||
- /usr/share/fonts/:/usr/share/fonts/
|
||||
|
||||
6
index.js
6
index.js
@@ -2,6 +2,7 @@ require("dotenv").config();
|
||||
const { Console } = require("console");
|
||||
const fs = require("fs");
|
||||
const {Client, GatewayIntentBits, Collection} = require("discord.js");
|
||||
const webApi = require('./api/jsonApi');
|
||||
const dbUtil = require("./util/db")
|
||||
|
||||
const logger = new Console({
|
||||
@@ -44,6 +45,11 @@ logger.log("Syncing database...");
|
||||
dbUtil.syncDb();
|
||||
client.login(process.env.TOKEN);
|
||||
|
||||
webApi.client = client;
|
||||
const PORT = process.env.API_PORT;
|
||||
webApi.listen(PORT, () => {
|
||||
console.log(`HTTP API listening on port ${PORT}`);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
1080
package-lock.json
generated
1080
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,9 +13,11 @@
|
||||
"dependencies": {
|
||||
"@discordjs/rest": "^0.3.0",
|
||||
"axios": "^0.27.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"discord-api-types": "^0.37.2",
|
||||
"discord.js": "^14.0.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^2.3.3",
|
||||
"nanoid": "^3.0.0",
|
||||
"nodemon": "^2.0.15",
|
||||
|
||||
Reference in New Issue
Block a user