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

358
test-admin-maintenance.js Normal file
View File

@@ -0,0 +1,358 @@
/**
* Test script to verify admin can perform actions during maintenance mode
*
* This script tests:
* 1. Admin can authenticate during maintenance
* 2. Admin can access admin endpoints during maintenance
* 3. Admin can perform CRUD operations during maintenance
* 4. Non-admin users are still blocked during maintenance
*
* Usage: node test-admin-maintenance.js
*/
import fetch from 'node-fetch';
import dotenv from 'dotenv';
dotenv.config();
const API_URL = process.env.VITE_API_URL || 'http://localhost:3000';
const BASE_URL = `${API_URL}/api`;
// Colors for console output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
};
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
function logTest(name) {
console.log(`\n${colors.bright}${colors.blue}🧪 Testing: ${name}${colors.reset}`);
}
function logSuccess(message) {
log(`${message}`, 'green');
}
function logError(message) {
log(`${message}`, 'red');
}
function logWarning(message) {
log(`⚠️ ${message}`, 'yellow');
}
function logInfo(message) {
log(` ${message}`, 'blue');
}
// Helper to make authenticated requests
async function makeRequest(endpoint, options = {}) {
const url = endpoint.startsWith('http') ? endpoint : `${BASE_URL}${endpoint}`;
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
const data = await response.json();
return {
status: response.status,
ok: response.ok,
data,
};
} catch (error) {
return {
status: 0,
ok: false,
error: error.message,
};
}
}
// Test 1: Check if maintenance mode is enabled
async function testMaintenanceStatus() {
logTest('Checking maintenance status');
const result = await makeRequest('/config/status');
if (result.ok) {
const { maintenance } = result.data;
if (maintenance) {
logSuccess('Maintenance mode is ENABLED');
return true;
} else {
logWarning('Maintenance mode is DISABLED - enable it to test properly');
return false;
}
} else {
logError(`Failed to check maintenance status: ${result.status}`);
return false;
}
}
// Test 2: Check public config endpoint (should work during maintenance)
async function testPublicConfig() {
logTest('Testing public config endpoint (should work during maintenance)');
const result = await makeRequest('/config/public');
if (result.ok) {
logSuccess('Public config endpoint accessible');
return true;
} else {
logError(`Public config endpoint failed: ${result.status}`);
return false;
}
}
// Test 3: Test unauthenticated admin request (should be blocked)
async function testUnauthenticatedAdminRequest() {
logTest('Testing unauthenticated admin request (should be blocked)');
const result = await makeRequest('/admin/config');
if (result.status === 503) {
logSuccess('Unauthenticated request correctly blocked (503)');
logInfo(`Message: ${result.data.message}`);
return true;
} else if (result.status === 401) {
logSuccess('Unauthenticated request blocked by auth (401)');
return true;
} else {
logError(`Unexpected status: ${result.status}`);
return false;
}
}
// Test 4: Test admin authentication (manual test - requires actual admin cookies)
async function testAdminAuthentication(cookies) {
logTest('Testing admin authentication with provided cookies');
if (!cookies) {
logWarning('No cookies provided - skipping authenticated tests');
logInfo('To test with authentication, run: node test-admin-maintenance.js "accessToken=your_token"');
return null;
}
const result = await makeRequest('/auth/me', {
headers: {
Cookie: cookies,
},
});
if (result.ok && result.data.user) {
const { user } = result.data;
logSuccess(`Authenticated as: ${user.username}`);
logInfo(`Staff Level: ${user.staffLevel}`);
if (user.staffLevel >= 3) {
logSuccess('User is an admin (staffLevel >= 3)');
return cookies;
} else {
logWarning('User is NOT an admin (staffLevel < 3)');
return null;
}
} else {
logError('Authentication failed');
return null;
}
}
// Test 5: Test admin endpoints with authentication
async function testAdminEndpoints(cookies) {
if (!cookies) {
logWarning('Skipping admin endpoint tests - no valid admin cookies');
return false;
}
logTest('Testing admin endpoints during maintenance');
// Test 5a: Get admin config
const configResult = await makeRequest('/admin/config', {
headers: {
Cookie: cookies,
},
});
if (configResult.ok) {
logSuccess('GET /admin/config - Success');
} else {
logError(`GET /admin/config - Failed (${configResult.status})`);
if (configResult.data) {
logError(`Message: ${configResult.data.message || JSON.stringify(configResult.data)}`);
}
return false;
}
// Test 5b: Get users list
const usersResult = await makeRequest('/admin/users?page=1&limit=10', {
headers: {
Cookie: cookies,
},
});
if (usersResult.ok) {
logSuccess('GET /admin/users - Success');
logInfo(`Found ${usersResult.data.users?.length || 0} users`);
} else {
logError(`GET /admin/users - Failed (${usersResult.status})`);
if (usersResult.data) {
logError(`Message: ${usersResult.data.message || JSON.stringify(usersResult.data)}`);
}
}
// Test 5c: Get announcements
const announcementsResult = await makeRequest('/admin/announcements', {
headers: {
Cookie: cookies,
},
});
if (announcementsResult.ok) {
logSuccess('GET /admin/announcements - Success');
logInfo(`Found ${announcementsResult.data.announcements?.length || 0} announcements`);
} else {
logError(`GET /admin/announcements - Failed (${announcementsResult.status})`);
}
return true;
}
// Test 6: Test maintenance toggle (the actual action that was failing)
async function testMaintenanceToggle(cookies) {
if (!cookies) {
logWarning('Skipping maintenance toggle test - no valid admin cookies');
return false;
}
logTest('Testing maintenance mode toggle (the critical test)');
// First, get current config
const getCurrentConfig = await makeRequest('/admin/config', {
headers: {
Cookie: cookies,
},
});
if (!getCurrentConfig.ok) {
logError('Failed to get current config');
return false;
}
const currentMaintenanceState = getCurrentConfig.data.config.maintenance.enabled;
logInfo(`Current maintenance state: ${currentMaintenanceState}`);
// Try to toggle maintenance (just toggle and toggle back)
const newState = !currentMaintenanceState;
const updateResult = await makeRequest('/admin/config/maintenance', {
method: 'PUT',
headers: {
Cookie: cookies,
},
body: JSON.stringify({
enabled: newState,
message: "Test toggle during maintenance",
}),
});
if (updateResult.ok) {
logSuccess(`Successfully toggled maintenance to: ${newState}`);
// Toggle back to original state
const restoreResult = await makeRequest('/admin/config/maintenance', {
method: 'PUT',
headers: {
Cookie: cookies,
},
body: JSON.stringify({
enabled: currentMaintenanceState,
message: getCurrentConfig.data.config.maintenance.message,
}),
});
if (restoreResult.ok) {
logSuccess(`Restored maintenance to original state: ${currentMaintenanceState}`);
} else {
logWarning(`Failed to restore original state - maintenance is now ${newState}`);
}
return true;
} else {
logError(`Failed to toggle maintenance (${updateResult.status})`);
if (updateResult.data) {
logError(`Message: ${updateResult.data.message || JSON.stringify(updateResult.data)}`);
}
return false;
}
}
// Main test runner
async function runTests() {
log('\n' + '='.repeat(60), 'bright');
log('🔧 Admin Maintenance Mode Test Suite', 'bright');
log('='.repeat(60) + '\n', 'bright');
// Get cookies from command line if provided
const cookies = process.argv[2] || null;
if (!cookies) {
logWarning('No authentication cookies provided');
logInfo('To test with authentication:');
logInfo('1. Login to the site as admin');
logInfo('2. Open browser DevTools > Application > Cookies');
logInfo('3. Copy your accessToken cookie value');
logInfo('4. Run: node test-admin-maintenance.js "accessToken=YOUR_TOKEN_HERE"');
console.log();
}
let allPassed = true;
// Run tests
const maintenanceEnabled = await testMaintenanceStatus();
if (!maintenanceEnabled) {
logWarning('\n⚠ Please enable maintenance mode in admin panel first!');
process.exit(1);
}
allPassed = await testPublicConfig() && allPassed;
allPassed = await testUnauthenticatedAdminRequest() && allPassed;
const validCookies = await testAdminAuthentication(cookies);
if (validCookies) {
allPassed = await testAdminEndpoints(validCookies) && allPassed;
allPassed = await testMaintenanceToggle(validCookies) && allPassed;
}
// Summary
log('\n' + '='.repeat(60), 'bright');
if (allPassed && validCookies) {
log('✅ All tests passed!', 'green');
} else if (!validCookies) {
log('⚠️ Basic tests passed, but admin tests were skipped', 'yellow');
log('Provide admin cookies to run full test suite', 'yellow');
} else {
log('❌ Some tests failed', 'red');
}
log('='.repeat(60) + '\n', 'bright');
}
// Run the tests
runTests().catch((error) => {
logError(`Test suite failed with error: ${error.message}`);
console.error(error);
process.exit(1);
});