*Added onlineCount endpoint
*Added launch with PM2 *Code clenaup
This commit is contained in:
parent
5dfa4d83e7
commit
b400beea6a
18 changed files with 381 additions and 321 deletions
43
src/app.js
43
src/app.js
|
|
@ -8,6 +8,7 @@ const cors = require('cors');
|
|||
const compression = require('compression');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const expressWinston = require('express-winston');
|
||||
const moment = require('moment-timezone');
|
||||
const { logger } = require('./utils/logger');
|
||||
const path = require('path');
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ const passwordResetEmailRouter = require('./routes/launcher/passwordResetEmail')
|
|||
const passwordChangeRouter = require('./routes/launcher/changePassword');
|
||||
const verificationEmailRouter = require('./routes/launcher/verificationEmail');
|
||||
const launcherUpdaterRouter = require('./routes/launcher/launcherUpdater');
|
||||
const onlineCountRouter = require('./routes/onlineCount');
|
||||
|
||||
// Set up rate limiter
|
||||
const limiter = rateLimit({
|
||||
|
|
@ -60,6 +62,7 @@ app.use('/accountApi/sendPasswordResetEmail', limiter , passwordResetEmailRouter
|
|||
app.use('/accountApi/changePassword', limiter , passwordChangeRouter);
|
||||
app.use('/accountApi/sendVerificationEmail', limiter , verificationEmailRouter);
|
||||
app.use('/launcherApi/launcherUpdater', launcherUpdaterRouter);
|
||||
app.use('/serverApi/onlineCount', limiter , onlineCountRouter);
|
||||
|
||||
// Serve static files from public folder
|
||||
app.use(express.static('../public'));
|
||||
|
|
@ -89,14 +92,50 @@ app.use((err, req, res, next) => {
|
|||
} else {
|
||||
logger.info(err.stack);
|
||||
}
|
||||
res.status(500).send('Something went wrong' + err.stack);
|
||||
res.status(500).send('A error ocurred. Try again later.');
|
||||
});
|
||||
|
||||
// Node.js version
|
||||
const nodeVersion = process.version;
|
||||
|
||||
// timezone
|
||||
const timezone = process.env.TZ || new Date().toLocaleString('en-US', { timeZoneName: 'short' });
|
||||
const offsetInMinutes = moment.tz(timezone).utcOffset();
|
||||
const offsetHours = Math.floor(Math.abs(offsetInMinutes) / 60);
|
||||
const offsetMinutes = Math.abs(offsetInMinutes) % 60;
|
||||
const offsetSign = offsetInMinutes >= 0 ? '+' : '-';
|
||||
const offsetString = `${offsetSign}${offsetHours.toString().padStart(2, '0')}:${offsetMinutes.toString().padStart(2, '0')}`;
|
||||
|
||||
const memoryUsage = process.memoryUsage();
|
||||
|
||||
// Function to format bytes as human-readable string
|
||||
function formatBytes(bytes) {
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
if (bytes === 0) {
|
||||
return '0 B';
|
||||
}
|
||||
const i = Math.floor(Math.log2(bytes) / 10);
|
||||
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;
|
||||
}
|
||||
|
||||
// Start server
|
||||
const port = process.env.PORT || 3000;
|
||||
const publicIP = process.env.PUBLIC_IP || '0.0.0.0';
|
||||
|
||||
console.log('--------------------------------------------------');
|
||||
console.log(`Rusty Hearts API Version: 1.1`)
|
||||
console.log(`Node.js Version: ${nodeVersion}`);
|
||||
console.log(`Timezone: ${timezone} (${offsetString})`);
|
||||
console.log('Memory Usage:');
|
||||
console.log(` RSS: ${formatBytes(memoryUsage.rss)}`);
|
||||
console.log(` Heap Total: ${formatBytes(memoryUsage.heapTotal)}`);
|
||||
console.log(` Heap Used: ${formatBytes(memoryUsage.heapUsed)}`);
|
||||
console.log(` External: ${formatBytes(memoryUsage.external)}`);
|
||||
console.log(` Array Buffers: ${formatBytes(memoryUsage.arrayBuffers)}`);
|
||||
console.log('--------------------------------------------------');
|
||||
|
||||
app.listen(port, publicIP, () => {
|
||||
logger.info(`API listening on ${publicIP}:${port}`);
|
||||
logger.info(`Auth API listening on 127.0.0.1:${authPort}`);
|
||||
logger.info(`Billing API listening on 127.0.0.1:${billingPort}`);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,77 +1,86 @@
|
|||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const bcrypt = require('bcrypt');
|
||||
const xml2js = require('xml2js');
|
||||
const parser = new xml2js.Parser();
|
||||
const sql = require('mssql');
|
||||
const router = express.Router();
|
||||
const { authLogger } = require('../utils/logger');
|
||||
const Joi = require('joi');
|
||||
|
||||
// Set up database connection
|
||||
const { authLogger } = require('../utils/logger');
|
||||
const { connAccount } = require('../utils/dbConfig');
|
||||
|
||||
// Route for handling login requests
|
||||
router.post('/', bodyParser.text({
|
||||
type: '*/xml'
|
||||
}), async (req, res) => {
|
||||
try {
|
||||
const xml = req.body;
|
||||
const result = await parser.parseStringPromise(xml);
|
||||
const loginRequest = result['login-request'];
|
||||
const account = loginRequest.account[0];
|
||||
const password = loginRequest.password[0];
|
||||
const game = loginRequest.game[0];
|
||||
const ip = loginRequest.ip[0];
|
||||
const router = express.Router();
|
||||
const parser = new xml2js.Parser({ explicitArray: false });
|
||||
|
||||
authLogger.info(`[Auth] Account [${account}] is trying to login from [${ip}]`);
|
||||
|
||||
// Create a connection pool for the database
|
||||
const pool = await connAccount;
|
||||
|
||||
// Get the account information from the database
|
||||
const {
|
||||
recordset
|
||||
} = await pool
|
||||
.request()
|
||||
.input('Identifier', sql.VarChar(50), account)
|
||||
.execute('GetAccount');
|
||||
|
||||
// Check if the account exists
|
||||
const row = recordset[0];
|
||||
if (!row || row.Result !== 'AccountExists') {
|
||||
authLogger.info(`[Auth] Account [${account}] login failed from [${ip}]`);
|
||||
return res.send('<status>failed</status>');
|
||||
}
|
||||
|
||||
// Verify the password using bcrypt
|
||||
const passwordMatch = await bcrypt.compare(password, row.AccountPwd);
|
||||
|
||||
// Authenticate the user and update the database
|
||||
const {
|
||||
recordset: authRecordset
|
||||
} = await pool
|
||||
.request()
|
||||
.input('Identifier', sql.VarChar(50), account)
|
||||
.input('password_verify_result', sql.Bit, passwordMatch ? 1 : 0)
|
||||
.input('LastLoginIP', sql.VarChar(50), ip)
|
||||
.execute('AuthenticateUser');
|
||||
|
||||
const authRow = authRecordset[0];
|
||||
if (!authRow || authRow.Result !== 'LoginSuccess') {
|
||||
authLogger.info(`[Auth] Account [${account}] login failed from [${ip}]`);
|
||||
return res.send('<status>failed</status>');
|
||||
}
|
||||
|
||||
// Send the authentication response
|
||||
const response = `<userid>${authRow.AuthID}</userid><user-type>F</user-type><status>success</status>`;
|
||||
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||
res.send(response);
|
||||
|
||||
authLogger.info(`[Auth] Account [${account}] successfully logged in from [${ip}]`);
|
||||
} catch (error) {
|
||||
authLogger.error('Error handling login request: ' + error.message);
|
||||
res.status(500).send('<status>failed</status>');
|
||||
}
|
||||
// Joi schema for request body validation
|
||||
const schema = Joi.object({
|
||||
'login-request': Joi.object({
|
||||
account: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
game: Joi.string().required(),
|
||||
ip: Joi.string().required(),
|
||||
}).required(),
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
// Route for handling login requests
|
||||
router.post('/', express.text({ type: '*/xml' }), async (req, res) => {
|
||||
try {
|
||||
const xml = req.body;
|
||||
const result = await parser.parseStringPromise(xml);
|
||||
|
||||
// Validate the request body against the schema
|
||||
const { error, value } = schema.validate(result);
|
||||
if (error) {
|
||||
authLogger.info(`[Auth] Invalid login request: ${error.message}`);
|
||||
return res.send('<status>failed</status>');
|
||||
}
|
||||
|
||||
const { 'login-request': loginRequest } = value;
|
||||
const { account, password, game, ip } = loginRequest;
|
||||
|
||||
authLogger.info(`[Auth] Account [${account}] is trying to login from [${ip}]`);
|
||||
|
||||
// Create a connection pool for the database
|
||||
const pool = await connAccount;
|
||||
|
||||
// Get the account information from the database
|
||||
const { recordset } = await pool
|
||||
.request()
|
||||
.input('Identifier', sql.VarChar(50), account)
|
||||
.execute('GetAccount');
|
||||
|
||||
// Check if the account exists
|
||||
const row = recordset[0];
|
||||
if (!row || row.Result !== 'AccountExists') {
|
||||
authLogger.info(`[Auth] Account [${account}] login failed from [${ip}]`);
|
||||
return res.send('<status>failed</status>');
|
||||
}
|
||||
|
||||
// Verify the password using bcrypt
|
||||
const passwordMatch = await bcrypt.compare(password, row.AccountPwd);
|
||||
|
||||
// Authenticate the user and update the database
|
||||
const { recordset: authRecordset } = await pool
|
||||
.request()
|
||||
.input('Identifier', sql.VarChar(50), account)
|
||||
.input('password_verify_result', sql.Bit, passwordMatch ? 1 : 0)
|
||||
.input('LastLoginIP', sql.VarChar(50), ip)
|
||||
.execute('AuthenticateUser');
|
||||
|
||||
const authRow = authRecordset[0];
|
||||
if (!authRow || authRow.Result !== 'LoginSuccess') {
|
||||
authLogger.info(`[Auth] Account [${account}] login failed from [${ip}]`);
|
||||
return res.send('<status>failed</status>');
|
||||
}
|
||||
|
||||
// Send the authentication response
|
||||
const response = `<userid>${authRow.AuthID}</userid><user-type>F</user-type><status>success</status>`;
|
||||
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||
res.send(response);
|
||||
|
||||
authLogger.info(`[Auth] Account [${account}] successfully logged in from [${ip}]`);
|
||||
} catch (error) {
|
||||
authLogger.error(`Error handling login request: ${error.message}`);
|
||||
res.status(500).send('<status>failed</status>');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -1,122 +1,138 @@
|
|||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const xml2js = require('xml2js');
|
||||
const parser = new xml2js.Parser();
|
||||
const sql = require('mssql');
|
||||
const router = express.Router();
|
||||
const { billingLogger} = require('../utils/logger');;
|
||||
const { billingLogger } = require('../utils/logger');
|
||||
const Joi = require('joi');
|
||||
|
||||
// Set up database connection
|
||||
const { connAccount } = require('../utils/dbConfig');
|
||||
|
||||
// Define the validation schema for currency requests
|
||||
const currencySchema = Joi.object({
|
||||
userid: Joi.string().required(),
|
||||
server: Joi.string().required(),
|
||||
game: Joi.string().required(),
|
||||
}).required();
|
||||
|
||||
// Define the validation schema for item purchase requests
|
||||
const itemPurchaseSchema = Joi.object({
|
||||
userid: Joi.string().required(),
|
||||
server: Joi.string().required(),
|
||||
charid: Joi.string().required(),
|
||||
game: Joi.number().required(),
|
||||
uniqueid: Joi.string().required(),
|
||||
amount: Joi.number().required(),
|
||||
itemid: Joi.string().required(),
|
||||
count: Joi.number().required(),
|
||||
}).required();
|
||||
|
||||
|
||||
// Route for handling billing requests
|
||||
router.post('/', bodyParser.text({
|
||||
type: '*/xml'
|
||||
type: '*/xml'
|
||||
}), async (req, res) => {
|
||||
try {
|
||||
const xml = req.body;
|
||||
const result = await parser.parseStringPromise(xml);
|
||||
const name = result['currency-request'] ? 'currency-request' : 'item-purchase-request';
|
||||
const request = result[name];
|
||||
const userid = request.userid[0];
|
||||
const server = request.server[0];
|
||||
try {
|
||||
const xml = req.body;
|
||||
const result = await xml2js.parseStringPromise(xml, { explicitArray: false });
|
||||
const name = result['currency-request'] ? 'currency-request' : 'item-purchase-request';
|
||||
const request = result[name];
|
||||
|
||||
billingLogger.info(`[Billing] Received [${name}] from user [${userid}]`);
|
||||
// Validate the request against the appropriate schema
|
||||
const { error, value } = name === 'currency-request'
|
||||
? currencySchema.validate(request)
|
||||
: itemPurchaseSchema.validate(request);
|
||||
|
||||
// Create a connection pool for the database
|
||||
const pool = await connAccount;
|
||||
|
||||
switch (name) {
|
||||
case 'currency-request':
|
||||
const {
|
||||
recordset
|
||||
} = await pool
|
||||
.request()
|
||||
.input('UserId', sql.VarChar(50), userid)
|
||||
.input('ServerId', sql.VarChar(50), server)
|
||||
.execute('GetCurrency');
|
||||
|
||||
const row = recordset[0];
|
||||
|
||||
if (row && row.Result === 'Success') {
|
||||
const response = `<result><balance>${row.Zen}</balance></result>`;
|
||||
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||
res.send(response);
|
||||
} else {
|
||||
res.send('<status>failed</status>');
|
||||
return res.status(400).send(row.Result);
|
||||
}
|
||||
break;
|
||||
case 'item-purchase-request':
|
||||
const charid = request.charid[0];
|
||||
const uniqueid = request.uniqueid[0];
|
||||
const amount = request.amount[0];
|
||||
const itemid = request.itemid[0];
|
||||
const itemcount = request.count[0];
|
||||
|
||||
const {
|
||||
recordset: currencyRecordset
|
||||
} = await pool
|
||||
.request()
|
||||
.input('UserId', sql.VarChar(50), userid)
|
||||
.input('ServerId', sql.VarChar(50), server)
|
||||
.execute('GetCurrency');
|
||||
|
||||
const currencyRow = currencyRecordset[0];
|
||||
|
||||
if (currencyRow && currencyRow.Result === 'Success') {
|
||||
const balance = currencyRow.Zen;
|
||||
|
||||
if (amount > 0) {
|
||||
if (amount > balance) {
|
||||
res.send('<status>failed</status>');
|
||||
billingLogger.info(`[Billing] Item purchase with id [${uniqueid}] from user [${userid}] failed. Not enough Zen [${balance}]. charid: [${charid}] itemid: [${itemid}] itemcount: [${itemcount}] price: [${amount}]`);
|
||||
} else {
|
||||
const newbalance = balance - amount;
|
||||
|
||||
await pool
|
||||
.request()
|
||||
.input('UserId', sql.VarChar(50), userid)
|
||||
.input('ServerId', sql.VarChar(50), server)
|
||||
.input('NewBalance', sql.BigInt, newbalance)
|
||||
.execute('SetCurrency');
|
||||
|
||||
await pool
|
||||
.request()
|
||||
.input('userid', sql.VarChar(50), userid)
|
||||
.input('charid', sql.VarChar(50), charid)
|
||||
.input('uniqueid', sql.VarChar(50), uniqueid)
|
||||
.input('amount', sql.BigInt, amount)
|
||||
.input('itemid', sql.VarChar(50), itemid)
|
||||
.input('itemcount', sql.Int, itemcount)
|
||||
.execute('SetBillingLog');
|
||||
|
||||
billingLogger.info(`[Billing] Item purchase with id [${uniqueid}] from user [${userid}] success. charid: [${charid}] itemid: [${itemid}] itemcount: [${itemcount}] price: [${amount}]`);
|
||||
billingLogger.info(`[Billing] Item purchase from user [${userid}] success. New zen balance: [${newbalance}]`);
|
||||
|
||||
const response = `<result><status>success</status><new-balance>${newbalance}</new-balance></result>`;
|
||||
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||
res.send(response);
|
||||
}
|
||||
} else {
|
||||
const response = `<result><balance>${currencyRow.Zen}</balance></result>`;
|
||||
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||
res.send(response);
|
||||
}
|
||||
} else {
|
||||
res.send('<status>failed</status>');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
res.send('<status>failed</status>');
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
billingLogger.error(`[Billing] Error handling request: $ {
|
||||
error.message
|
||||
}`);
|
||||
res.status(500).send('<status>failed</status>');
|
||||
if (error) {
|
||||
billingLogger.info(`[Billing] Invalid request: ${error.message}`);
|
||||
return res.status(400).send('<status>failed</status>');
|
||||
}
|
||||
|
||||
const { userid, server, game, charid, uniqueid, amount, itemid, count } = value;
|
||||
|
||||
// Create a connection pool for the database
|
||||
const pool = await connAccount;
|
||||
|
||||
switch (name) {
|
||||
case 'currency-request':
|
||||
const { recordset } = await pool
|
||||
.request()
|
||||
.input('UserId', sql.VarChar(50), userid)
|
||||
.input('ServerId', sql.VarChar(50), server)
|
||||
.execute('GetCurrency');
|
||||
|
||||
const row = recordset[0];
|
||||
|
||||
if (row && row.Result === 'Success') {
|
||||
const response = `<result><balance>${row.Zen}</balance></result>`;
|
||||
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||
return res.send(response);
|
||||
} else {
|
||||
billingLogger.error(`[Billing] Currency request from user [${userid}] failed: ${row.Result}`);
|
||||
return res.status(400).send('<status>failed</status>');
|
||||
}
|
||||
|
||||
case 'item-purchase-request':
|
||||
billingLogger.info(`[Billing] Received [${name}] from user [${userid}]`);
|
||||
const { recordset: currencyRecordset } = await pool
|
||||
.request()
|
||||
.input('UserId', sql.VarChar(50), userid)
|
||||
.input('ServerId', sql.VarChar(50), server)
|
||||
.execute('GetCurrency');
|
||||
|
||||
const currencyRow = currencyRecordset[0];
|
||||
|
||||
if (currencyRow && currencyRow.Result === 'Success') {
|
||||
const balance = currencyRow.Zen;
|
||||
|
||||
if (amount > 0) {
|
||||
if (amount > balance) {
|
||||
billingLogger.warn(`[Billing] Item purchase with id [${uniqueid}] from user [${userid}] failed. Not enough Zen [${balance}]. charid: [${charid}] itemid: [${itemid}] itemcount: [${count}] price: [${amount}]`);
|
||||
return res.status(400).send('<status>failed</status>');
|
||||
} else {
|
||||
const newbalance = balance - amount;
|
||||
|
||||
await pool
|
||||
.request()
|
||||
.input('UserId', sql.VarChar(50), userid)
|
||||
.input('ServerId', sql.VarChar(50), server)
|
||||
.input('NewBalance', sql.BigInt, newbalance)
|
||||
.execute('SetCurrency');
|
||||
|
||||
await pool
|
||||
.request()
|
||||
.input('userid', sql.VarChar(50), userid)
|
||||
.input('charid', sql.VarChar(50), charid)
|
||||
.input('uniqueid', sql.VarChar(50), uniqueid)
|
||||
.input('amount', sql.BigInt, amount)
|
||||
.input('itemid', sql.VarChar(50), itemid)
|
||||
.input('itemcount', sql.Int, count)
|
||||
.execute('SetBillingLog');
|
||||
|
||||
billingLogger.info(`[Billing] Item purchase with id [${uniqueid}] from user [${userid}] success. charid: [${charid}] itemid: [${itemid}] itemcount: [${count}] price: [${amount}]`);
|
||||
billingLogger.info(`[Billing] [${userid}] Zen balance before purchase: [${balance}] | New zen balance: [${newbalance}]`);
|
||||
|
||||
const response = `<result><status>success</status><new-balance>${newbalance}</new-balance></result>`;
|
||||
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||
return res.send(response);
|
||||
}
|
||||
} else {
|
||||
const response = `<result><balance>${currencyRow.Zen}</balance></result>`;
|
||||
res.set('Content-Type', 'text/xml; charset=utf-8');
|
||||
return res.send(response);
|
||||
}
|
||||
} else {
|
||||
return res.status(400).send('<status>failed</status>');
|
||||
}
|
||||
|
||||
default:
|
||||
return res.status(400).send('<status>failed</status>');
|
||||
}
|
||||
} catch (error) {
|
||||
billingLogger.error(`[Billing] Error handling request: ${error.message}`);
|
||||
return res.status(500).send('<status>failed</status>');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const net = require('net');
|
||||
const { logger } = require('../utils/logger');
|
||||
|
||||
// Define the gateway route
|
||||
router.get('/', (req, res) => {
|
||||
|
|
@ -38,14 +39,17 @@ router.get('/status', async (req, res) => {
|
|||
|
||||
// Handle the socket events to check the connection status
|
||||
socket.on('connect', () => {
|
||||
logger.info(`[Gateway] Connection attempt success from IP: ${req.ip}`);
|
||||
res.status(200).json({ status: 'online' });
|
||||
socket.destroy();
|
||||
});
|
||||
socket.on('timeout', () => {
|
||||
logger.warn(`[Gateway] Connection attempt timeout from IP: ${req.ip}`);
|
||||
res.status(408).json({ status: 'offline' });
|
||||
socket.destroy();
|
||||
});
|
||||
socket.on('error', () => {
|
||||
logger.error(`[Gateway] Connection failed from IP: ${req.ip}`);
|
||||
res.status(503).json({ status: 'offline' });
|
||||
socket.destroy();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -100,8 +100,8 @@ router.post('/', async (req, res) => {
|
|||
}
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[Account] Database query failed: ' + error.message);
|
||||
return res.status(500).send('[Account] Database query failed: ' + error.message);
|
||||
logger.error('[Account] A error ocourred: ' + error.message);
|
||||
return res.status(500).send('A error ocourred. Please try again later.');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ router.post('/', async (req, res) => {
|
|||
return res.status(200).send(row.Result);
|
||||
} catch (error) {
|
||||
logger.error('Database query failed: ' + error.message);
|
||||
return res.status(500).send('Database query failed: ' + error.message);
|
||||
return res.status(500).send('A error ocourred. Please try again later.');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,17 +23,17 @@ router.post('/', async (req, res) => {
|
|||
return res.status(400).send(error.details[0].message);
|
||||
}
|
||||
|
||||
const account = value.account;
|
||||
const password = value.password;
|
||||
const account = req.body.account;
|
||||
const password = req.body.password;
|
||||
const userIp = req.ip;
|
||||
|
||||
// Check the format of the account identifier
|
||||
if (
|
||||
!/^[A-Za-z0-9_-]{6,50}$/.test(account) &&
|
||||
!/^[\w\d._%+-]+@[\w\d.-]+\.[\w]{2,}$/i.test(account)
|
||||
) {
|
||||
return res.status(400).json({ Result: 'InvalidUsernameFormat' });
|
||||
}
|
||||
if (
|
||||
!/^[a-z0-9_-]{6,50}$/.test(account) &&
|
||||
!/^[\w\d._%+-]+@[\w\d.-]+\.[\w]{2,}$/i.test(account)
|
||||
) {
|
||||
return res.status(400).json({ Result: 'InvalidUsernameFormat' });
|
||||
}
|
||||
|
||||
// Use a prepared statement to retrieve the account information
|
||||
const pool = await connAccount;
|
||||
|
|
@ -51,6 +51,7 @@ router.post('/', async (req, res) => {
|
|||
.createHash('md5')
|
||||
.update(windyCode + password)
|
||||
.digest('hex');
|
||||
|
||||
const password_verify_result = await bcrypt.compare(
|
||||
md5_password,
|
||||
hash
|
||||
|
|
@ -85,15 +86,13 @@ router.post('/', async (req, res) => {
|
|||
});
|
||||
}
|
||||
} else {
|
||||
return res.status(400).json({ Result: 'AccountNotFound' });
|
||||
return res.status(400).json({ Result: 'AccountNotFound' });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
'[Account] Launcher Login: Database query failed: ' + error.message
|
||||
);
|
||||
return res
|
||||
.status(500)
|
||||
.send('Database query failed: ' + error.message);
|
||||
return res.status(500).send('Login failed. Please try again later.');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ router.post('/', async (req, res) => {
|
|||
return res.status(200).send('EmailSent');
|
||||
}
|
||||
else {
|
||||
accountLogger.info(`[Account] Failed to insert verification code for email: ${email}`);
|
||||
accountLogger.error(`[Account] Failed to insert verification code for email: ${email}`);
|
||||
return res.status(500).send(insertRow.Result);
|
||||
}
|
||||
} else if (row && row.Result === 'AccountNotFound') {
|
||||
|
|
@ -67,7 +67,7 @@ router.post('/', async (req, res) => {
|
|||
}
|
||||
} catch (error) {
|
||||
logger.error('[Account] Database query failed: ' + error.message);
|
||||
return res.status(500).send('Database query failed: ' + error.message);
|
||||
return res.status(500).send('A error ocourred. Please try again later.');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const { connAccount } = require('../../utils/dbConfig');
|
|||
|
||||
// Joi schema for validating request data
|
||||
const schema = Joi.object({
|
||||
windyCode: Joi.string().alphanum().min(1).max(16).required(),
|
||||
windyCode: Joi.string().alphanum().min(6).max(16).required(),
|
||||
email: Joi.string().email().required(),
|
||||
password: Joi.string().min(6).required(),
|
||||
});
|
||||
|
|
@ -29,6 +29,13 @@ router.post('/', async (req, res) => {
|
|||
const email = value.email;
|
||||
const password = value.password;
|
||||
const userIp = req.ip;
|
||||
|
||||
if (
|
||||
!/^[a-z0-9_-]{6,50}$/.test(windyCode) &&
|
||||
!/^[\w\d._%+-]+@[\w\d.-]+\.[\w]{2,}$/i.test(email)
|
||||
) {
|
||||
return res.status(400).send('InvalidUsernameFormat');
|
||||
}
|
||||
|
||||
const md5_password = crypto.createHash('md5').update(windyCode + password).digest('hex'); // Generate MD5 hash
|
||||
|
||||
|
|
@ -55,12 +62,12 @@ router.post('/', async (req, res) => {
|
|||
|
||||
return res.status(200).send('Success');
|
||||
} else {
|
||||
accountLogger.info(`[Account] Account [${windyCode}] creation failed: ${row.Result}`);
|
||||
accountLogger.error(`[Account] Account [${windyCode}] creation failed: ${row.Result}`);
|
||||
return res.status(400).send(row.Result);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[Account] Database query failed: ' + error.message);
|
||||
return res.status(500).send('Database query failed: ' + error.message);
|
||||
return res.status(500).send('A error ocourred. Please try again later.');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ router.post('/', async (req, res) => {
|
|||
}
|
||||
} catch (error) {
|
||||
logger.error('[Account] Database query failed: ' + error.message);
|
||||
return res.status(500).send('Database query failed: ' + error.message);
|
||||
return res.status(500).send('A error ocourred. Please try again later.');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
47
src/routes/onlineCount.js
Normal file
47
src/routes/onlineCount.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const NodeCache = require('node-cache');
|
||||
const { logger } = require('../utils/logger');
|
||||
const sql = require('mssql');
|
||||
const { authDBConfig } = require('../utils/dbConfig.js');
|
||||
|
||||
// Set up the cache
|
||||
const cache = new NodeCache({ stdTTL: 60, checkperiod: 120 });
|
||||
|
||||
// Route for getting the count of online players
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
// Check if the count exists in the cache
|
||||
const cacheKey = 'onlineCount';
|
||||
let count = cache.get(cacheKey);
|
||||
|
||||
if (count === undefined) {
|
||||
// Count not found in cache, fetch it from the database
|
||||
const connAuth = new sql.ConnectionPool(authDBConfig);
|
||||
await connAuth.connect();
|
||||
|
||||
const request = connAuth.request();
|
||||
|
||||
// Declare the @online parameter and set its value to 1
|
||||
request.input('online', sql.Int, 1);
|
||||
|
||||
const result = await request.query('SELECT COUNT(*) AS OnlineCount FROM AuthTable WHERE online = @online');
|
||||
|
||||
count = result.recordset[0].OnlineCount;
|
||||
|
||||
// Store the count in the cache
|
||||
cache.set(cacheKey, count);
|
||||
|
||||
// Close the database connection
|
||||
await connAuth.close();
|
||||
}
|
||||
|
||||
// Return the count as the response
|
||||
return res.status(200).json({ count });
|
||||
} catch (error) {
|
||||
logger.error('Database query failed: ' + error.message);
|
||||
return res.status(500).send('Database query failed. Please try again later.');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -9,13 +9,30 @@ const dbConfig = {
|
|||
database: process.env.DB_DATABASE,
|
||||
options: {
|
||||
encrypt: process.env.DB_ENCRYPT === 'true'
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const connAccount = new sql.ConnectionPool(dbConfig);
|
||||
connAccount.connect();
|
||||
logger.info(`Database: Connected.`);
|
||||
|
||||
connAccount.connect().then(() => {
|
||||
logger.info(`Account Database: Connected.`);
|
||||
logger.info(`$ Ready $`);
|
||||
|
||||
}).catch((error) => {
|
||||
logger.error(`Failed to connect to the Account Database: ${error}`);
|
||||
});
|
||||
|
||||
const authDBConfig = {
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
server: process.env.DB_SERVER,
|
||||
database: 'RustyHearts_Auth',
|
||||
options: {
|
||||
encrypt: process.env.DB_ENCRYPT === 'true'
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
connAccount
|
||||
connAccount,
|
||||
authDBConfig
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const util = require("util");
|
||||
const winston = require("winston");
|
||||
|
||||
const logsDirectory = 'logs';
|
||||
|
|
@ -9,131 +7,44 @@ if (!fs.existsSync(logsDirectory)) {
|
|||
fs.mkdirSync(logsDirectory);
|
||||
}
|
||||
|
||||
const authLogger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `)
|
||||
),
|
||||
transports: [
|
||||
function createLogger(filename, level, filter, showConsole) {
|
||||
const transports = [
|
||||
new winston.transports.File({
|
||||
filename: `logs/auth-${new Date().toISOString().slice(0, 10)}.log`,
|
||||
level: 'info',
|
||||
filter: (log) => log.message.includes('[Auth]')
|
||||
filename: `${logsDirectory}/${filename}-${new Date().toISOString().slice(0, 10)}.log`,
|
||||
level,
|
||||
filter
|
||||
})
|
||||
]
|
||||
});
|
||||
];
|
||||
|
||||
if (process.env.LOG_AUTH_CONSOLE === 'true') {
|
||||
authLogger.add(new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple(),
|
||||
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`)
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
const billingLogger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `)
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.File({
|
||||
filename: `logs/billing-${new Date().toISOString().slice(0, 10)}.log`,
|
||||
level: 'info',
|
||||
filter: (log) => log.message.includes('[Billing]')
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
if (process.env.LOG_BILLING_CONSOLE === 'true') {
|
||||
billingLogger.add(new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple(),
|
||||
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`)
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
const mailerLogger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `)
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.File({
|
||||
filename: `logs/mailer-${new Date().toISOString().slice(0, 10)}.log`,
|
||||
level: 'info',
|
||||
filter: (log) => log.message.includes('[Mailer]')
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
if (process.env.LOG_MAILER_CONSOLE === 'true') {
|
||||
mailerLogger.add(new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple(),
|
||||
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`)
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
const accountLogger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `)
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.File({
|
||||
filename: `logs/account-${new Date().toISOString().slice(0, 10)}.log`,
|
||||
level: 'info',
|
||||
filter: (log) => log.message.includes('[Account]')
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
if (process.env.LOG_ACCOUNT_CONSOLE === 'true') {
|
||||
accountLogger.add(new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple(),
|
||||
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`)
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `)
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
if (showConsole) {
|
||||
transports.push(new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple(),
|
||||
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}]`)
|
||||
)
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: `logs/api-${new Date().toISOString().slice(0, 10)}.log`,
|
||||
level: 'info'
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: `logs/error-${new Date().toISOString().slice(0, 10)}.log`,
|
||||
level: 'error'
|
||||
})
|
||||
]
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level,
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
winston.format.printf(info => `${info.level}: ${info.message} [${info.timestamp}] `)
|
||||
),
|
||||
transports
|
||||
});
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
const logLevel = process.env.LOG_LEVEL || 'info';
|
||||
|
||||
const authLogger = createLogger('auth', logLevel, log => log.message.includes('[Auth]'), process.env.LOG_AUTH_CONSOLE === 'true');
|
||||
const billingLogger = createLogger('billing', logLevel, log => log.message.includes('[Billing]'), process.env.LOG_BILLING_CONSOLE === 'true');
|
||||
const mailerLogger = createLogger('mailer', logLevel, log => log.message.includes('[Mailer]'), process.env.LOG_MAILER_CONSOLE === 'true');
|
||||
const accountLogger = createLogger('account', logLevel, log => log.message.includes('[Account]'), process.env.LOG_ACCOUNT_CONSOLE === 'true');
|
||||
const logger = createLogger('api', logLevel, null, true);
|
||||
|
||||
module.exports = {
|
||||
authLogger,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue