Add external server stats API
This commit is contained in:
@@ -5,6 +5,7 @@ const express = require('express')
|
||||
const cors = require('cors')
|
||||
const session = require('express-session')
|
||||
const FileStoreFactory = require('session-file-store')
|
||||
const crypto = require('crypto')
|
||||
|
||||
function loadEnvFileIfExists(fileName) {
|
||||
const envPath = path.join(__dirname, '..', fileName)
|
||||
@@ -23,7 +24,7 @@ if (nodeEnv === 'production') {
|
||||
if (!loadedLocal) loadEnvFileIfExists('.env.production')
|
||||
}
|
||||
|
||||
const { ensureData } = require('./src/db')
|
||||
const { ensureData, getServerStats } = require('./src/db')
|
||||
const authRoutes = require('./src/routes/auth')
|
||||
const topicsRoutes = require('./src/routes/topics')
|
||||
const tierListsRoutes = require('./src/routes/tierlists')
|
||||
@@ -42,9 +43,30 @@ const allowedOrigins = (process.env.CORS_ORIGINS || '')
|
||||
.split(',')
|
||||
.map((origin) => origin.trim())
|
||||
.filter(Boolean)
|
||||
const SERVER_STATS_TOKEN = String(process.env.SERVER_STATS_TOKEN || '').trim()
|
||||
|
||||
const FileStore = FileStoreFactory(session)
|
||||
|
||||
function getBearerToken(req) {
|
||||
const authorization = String(req.headers.authorization || '')
|
||||
const match = authorization.match(/^Bearer\s+(.+)$/i)
|
||||
if (match) return match[1].trim()
|
||||
return String(req.query.token || '').trim()
|
||||
}
|
||||
|
||||
function isEqualSecret(a, b) {
|
||||
const left = Buffer.from(String(a || ''))
|
||||
const right = Buffer.from(String(b || ''))
|
||||
return left.length === right.length && crypto.timingSafeEqual(left, right)
|
||||
}
|
||||
|
||||
function requireServerStatsToken(req, res, next) {
|
||||
if (!SERVER_STATS_TOKEN) return res.status(503).json({ error: 'server_stats_token_not_configured' })
|
||||
const token = getBearerToken(req)
|
||||
if (!token || !isEqualSecret(token, SERVER_STATS_TOKEN)) return res.status(401).json({ error: 'unauthorized' })
|
||||
next()
|
||||
}
|
||||
|
||||
;['uploads/avatars', 'uploads/topics', 'uploads/custom', 'uploads/tierlists', '.sessions'].forEach((relativePath) => {
|
||||
fs.mkdirSync(path.join(__dirname, relativePath), { recursive: true })
|
||||
})
|
||||
@@ -101,6 +123,10 @@ app.use(async (req, res, next) => {
|
||||
})
|
||||
|
||||
app.use('/api/auth', authRoutes)
|
||||
app.get('/api/server-stats', requireServerStatsToken, async (req, res) => {
|
||||
const stats = await getServerStats()
|
||||
res.json(stats)
|
||||
})
|
||||
app.use('/api/topics', topicsRoutes)
|
||||
app.use('/api/tierlists', tierListsRoutes)
|
||||
app.use('/api/comments', commentsRoutes)
|
||||
|
||||
@@ -3140,6 +3140,117 @@ async function summarizeAdminTierLists({ queryText = '', topicId = '', minFavori
|
||||
}
|
||||
}
|
||||
|
||||
async function getServerStats() {
|
||||
const [
|
||||
userRows,
|
||||
tierListRows,
|
||||
topicRows,
|
||||
templateRequestRows,
|
||||
customItemRows,
|
||||
topicItemRows,
|
||||
commentRows,
|
||||
favoriteRows,
|
||||
followRows,
|
||||
] = await Promise.all([
|
||||
query(
|
||||
`SELECT
|
||||
COUNT(*) AS total_count,
|
||||
COALESCE(SUM(CASE WHEN is_admin = 1 THEN 1 ELSE 0 END), 0) AS admin_count,
|
||||
COALESCE(SUM(CASE WHEN email_verified = 1 THEN 1 ELSE 0 END), 0) AS verified_count,
|
||||
COALESCE(SUM(CASE WHEN last_login_at > 0 THEN 1 ELSE 0 END), 0) AS logged_in_count
|
||||
FROM users`
|
||||
),
|
||||
query(
|
||||
`SELECT
|
||||
COUNT(*) AS total_count,
|
||||
COALESCE(SUM(CASE WHEN is_public = 1 THEN 1 ELSE 0 END), 0) AS public_count,
|
||||
COALESCE(SUM(CASE WHEN is_featured = 1 THEN 1 ELSE 0 END), 0) AS featured_count
|
||||
FROM tierlists`
|
||||
),
|
||||
query(
|
||||
`SELECT
|
||||
COUNT(*) AS total_count,
|
||||
COALESCE(SUM(CASE WHEN is_public = 1 THEN 1 ELSE 0 END), 0) AS public_count
|
||||
FROM topics
|
||||
WHERE id <> ?`,
|
||||
[FREEFORM_TOPIC_ID]
|
||||
),
|
||||
query(
|
||||
`SELECT
|
||||
COUNT(*) AS total_count,
|
||||
COALESCE(SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END), 0) AS pending_count,
|
||||
COALESCE(SUM(CASE WHEN status = 'reviewing' THEN 1 ELSE 0 END), 0) AS reviewing_count,
|
||||
COALESCE(SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END), 0) AS completed_count,
|
||||
COALESCE(SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END), 0) AS rejected_count,
|
||||
COALESCE(SUM(CASE WHEN request_type = 'create' THEN 1 ELSE 0 END), 0) AS create_count,
|
||||
COALESCE(SUM(CASE WHEN request_type = 'update' THEN 1 ELSE 0 END), 0) AS update_count
|
||||
FROM template_requests`
|
||||
),
|
||||
query('SELECT COUNT(*) AS total_count FROM custom_items'),
|
||||
query('SELECT COUNT(*) AS total_count FROM topic_items'),
|
||||
query('SELECT COUNT(*) AS total_count FROM tierlist_comments'),
|
||||
query('SELECT COUNT(*) AS total_count FROM favorite_tierlists'),
|
||||
query('SELECT COUNT(*) AS total_count FROM user_follows'),
|
||||
])
|
||||
|
||||
const users = userRows[0] || {}
|
||||
const tierLists = tierListRows[0] || {}
|
||||
const topics = topicRows[0] || {}
|
||||
const templateRequests = templateRequestRows[0] || {}
|
||||
const customItems = customItemRows[0] || {}
|
||||
const topicItems = topicItemRows[0] || {}
|
||||
const comments = commentRows[0] || {}
|
||||
const favorites = favoriteRows[0] || {}
|
||||
const follows = followRows[0] || {}
|
||||
const tierListTotal = Number(tierLists.total_count || 0)
|
||||
const tierListPublicCount = Number(tierLists.public_count || 0)
|
||||
const topicTotal = Number(topics.total_count || 0)
|
||||
const topicPublicCount = Number(topics.public_count || 0)
|
||||
const userCustomItemCount = Number(customItems.total_count || 0)
|
||||
const templateItemCount = Number(topicItems.total_count || 0)
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
generatedAt: now(),
|
||||
users: {
|
||||
total: Number(users.total_count || 0),
|
||||
admins: Number(users.admin_count || 0),
|
||||
verified: Number(users.verified_count || 0),
|
||||
loggedInAtLeastOnce: Number(users.logged_in_count || 0),
|
||||
},
|
||||
tierLists: {
|
||||
total: tierListTotal,
|
||||
publicCount: tierListPublicCount,
|
||||
privateCount: Math.max(0, tierListTotal - tierListPublicCount),
|
||||
featuredCount: Number(tierLists.featured_count || 0),
|
||||
favoriteCount: Number(favorites.total_count || 0),
|
||||
commentCount: Number(comments.total_count || 0),
|
||||
},
|
||||
topics: {
|
||||
total: topicTotal,
|
||||
publicCount: topicPublicCount,
|
||||
privateCount: Math.max(0, topicTotal - topicPublicCount),
|
||||
},
|
||||
templateRequests: {
|
||||
total: Number(templateRequests.total_count || 0),
|
||||
pending: Number(templateRequests.pending_count || 0),
|
||||
reviewing: Number(templateRequests.reviewing_count || 0),
|
||||
completed: Number(templateRequests.completed_count || 0),
|
||||
rejected: Number(templateRequests.rejected_count || 0),
|
||||
create: Number(templateRequests.create_count || 0),
|
||||
update: Number(templateRequests.update_count || 0),
|
||||
},
|
||||
customItems: {
|
||||
total: userCustomItemCount + templateItemCount,
|
||||
userUploaded: userCustomItemCount,
|
||||
template: templateItemCount,
|
||||
},
|
||||
social: {
|
||||
follows: Number(follows.total_count || 0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function findTierListById(id, currentUserId = '') {
|
||||
const rows = await query(
|
||||
`
|
||||
@@ -3589,6 +3700,7 @@ module.exports = {
|
||||
listUserTierLists,
|
||||
listAdminTierLists,
|
||||
summarizeAdminTierLists,
|
||||
getServerStats,
|
||||
findTierListById,
|
||||
updateAdminTierListMeta,
|
||||
updateTierListFeaturedStatus,
|
||||
|
||||
Reference in New Issue
Block a user