feat: Complete admin panel implementation

- Add user management system with all CRUD operations
- Add promotion statistics dashboard with export
- Simplify Trading & Market settings UI
- Fix promotion schema (dates now optional)
- Add missing API endpoints and PATCH support
- Add comprehensive documentation
- Fix critical bugs (deletePromotion, duplicate endpoints)

All features tested and production-ready.
This commit is contained in:
2026-01-10 21:57:55 +00:00
parent b90cdd59df
commit 63c578b0ae
52 changed files with 21810 additions and 61 deletions

146
middleware/maintenance.js Normal file
View File

@@ -0,0 +1,146 @@
import SiteConfig from "../models/SiteConfig.js";
import { verifyAccessToken } from "../utils/jwt.js";
import User from "../models/User.js";
/**
* Middleware to check if site is in maintenance mode
* Allows admins and whitelisted users to access during maintenance
*/
export const checkMaintenance = async (request, reply) => {
try {
const currentPath = request.url.split("?")[0]; // Remove query params
// Skip maintenance check for public health endpoints and auth routes
const publicEndpoints = [
"/health",
"/api/health",
"/api/config/public",
"/api/config/announcements",
"/api/config/status",
"/api/config/promotions",
];
// Allow all auth routes (needed for Steam OAuth flow)
const authRoutes = [
"/auth/steam",
"/auth/steam/return",
"/api/auth/steam",
"/api/auth/steam/return",
"/api/auth/me",
"/api/auth/verify",
"/api/auth/refresh",
"/api/auth/logout",
];
if (
publicEndpoints.includes(currentPath) ||
authRoutes.includes(currentPath) ||
currentPath.startsWith("/auth/") ||
currentPath.startsWith("/api/auth/")
) {
return;
}
const config = await SiteConfig.getConfig();
// If maintenance mode is not enabled, allow all requests
if (!config.isMaintenanceActive()) {
return;
}
// Try to verify user authentication manually if not already done
let authenticatedUser = request.user;
if (!authenticatedUser) {
// Try to get token from cookies or Authorization header
let token = null;
// Check Authorization header
const authHeader = request.headers.authorization;
if (authHeader && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7);
}
// Check cookies if no header
if (!token && request.cookies && request.cookies.accessToken) {
token = request.cookies.accessToken;
}
// If we have a token, verify it
if (token) {
try {
const decoded = verifyAccessToken(token);
if (decoded && decoded.userId) {
// Fetch user from database
authenticatedUser = await User.findById(decoded.userId);
}
} catch (error) {
// Token invalid or expired - user will be treated as unauthenticated
console.log(
"⚠️ Token verification failed in maintenance check:",
error.message
);
}
}
}
// If user is authenticated, check if they're allowed during maintenance
if (authenticatedUser) {
// Check if user is admin (staff level 3+)
if (authenticatedUser.staffLevel >= 3) {
console.log(
`✅ Admin ${authenticatedUser.username} bypassing maintenance mode for ${currentPath}`
);
return; // Allow all admin access
}
// Check if user's steamId is in the allowed list
if (config.canAccessDuringMaintenance(authenticatedUser.steamId)) {
console.log(
`✅ Whitelisted user ${authenticatedUser.username} bypassing maintenance mode`
);
return;
}
}
// User is not allowed during maintenance
console.log(`⚠️ Blocking request during maintenance: ${currentPath}`);
// For API calls, return JSON
if (currentPath.startsWith("/api/")) {
return reply.status(503).send({
success: false,
maintenance: true,
message:
config.maintenance.message ||
"We're currently performing maintenance. Please check back soon!",
scheduledEnd: config.maintenance.scheduledEnd,
redirectTo: "/maintenance",
});
}
// For regular page requests, redirect to maintenance page
// (This will be handled by frontend router)
return reply.status(503).send({
success: false,
maintenance: true,
message:
config.maintenance.message ||
"We're currently performing maintenance. Please check back soon!",
scheduledEnd: config.maintenance.scheduledEnd,
redirectTo: "/maintenance",
});
} catch (error) {
console.error("❌ Maintenance check error:", error);
// On error, allow the request to proceed (fail open)
return;
}
};
/**
* Optional middleware - only check maintenance for specific routes
*/
export const maintenanceExempt = async (request, reply) => {
// This middleware does nothing - it's used to mark routes that should bypass maintenance
return;
};