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

235
test-admin-routes.js Normal file
View File

@@ -0,0 +1,235 @@
#!/usr/bin/env node
/**
* Admin Routes Test Script
* Tests if admin routes are properly registered and accessible
*/
import axios from 'axios';
const API_URL = 'http://localhost:3000';
const COLORS = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
};
function colorize(text, color) {
return `${COLORS[color]}${text}${COLORS.reset}`;
}
function logSuccess(message) {
console.log(`${colorize('✅', 'green')} ${message}`);
}
function logError(message) {
console.log(`${colorize('❌', 'red')} ${message}`);
}
function logInfo(message) {
console.log(`${colorize('', 'blue')} ${message}`);
}
function logWarning(message) {
console.log(`${colorize('⚠️', 'yellow')} ${message}`);
}
async function testHealth() {
console.log('\n' + colorize('='.repeat(60), 'cyan'));
console.log(colorize('TEST 1: Health Check', 'cyan'));
console.log(colorize('='.repeat(60), 'cyan'));
try {
const response = await axios.get(`${API_URL}/health`, { timeout: 5000 });
logSuccess('Backend is running');
logInfo(` Uptime: ${Math.floor(response.data.uptime)}s`);
logInfo(` Environment: ${response.data.environment}`);
return true;
} catch (error) {
logError('Backend is not accessible');
logError(` Error: ${error.message}`);
return false;
}
}
async function testRoutesList() {
console.log('\n' + colorize('='.repeat(60), 'cyan'));
console.log(colorize('TEST 2: Routes List (Development Only)', 'cyan'));
console.log(colorize('='.repeat(60), 'cyan'));
try {
const response = await axios.get(`${API_URL}/api/routes`, { timeout: 5000 });
const adminRoutes = response.data.routes.filter(r => r.url.includes('/api/admin'));
const configRoutes = response.data.routes.filter(r => r.url.includes('/api/config'));
logSuccess(`Found ${adminRoutes.length} admin routes`);
logSuccess(`Found ${configRoutes.length} config routes`);
console.log('\n' + colorize('Admin Routes:', 'yellow'));
adminRoutes.slice(0, 10).forEach(route => {
console.log(` ${colorize(route.method.padEnd(6), 'green')} ${route.url}`);
});
if (adminRoutes.length > 10) {
console.log(` ... and ${adminRoutes.length - 10} more`);
}
console.log('\n' + colorize('Config Routes:', 'yellow'));
configRoutes.forEach(route => {
console.log(` ${colorize(route.method.padEnd(6), 'green')} ${route.url}`);
});
return adminRoutes.length > 0;
} catch (error) {
logWarning('Routes list not accessible (may be production mode)');
logInfo(' This is normal in production');
return null; // Not a failure, just unavailable
}
}
async function testAdminConfigNoAuth() {
console.log('\n' + colorize('='.repeat(60), 'cyan'));
console.log(colorize('TEST 3: Admin Config (No Auth)', 'cyan'));
console.log(colorize('='.repeat(60), 'cyan'));
try {
await axios.get(`${API_URL}/api/admin/config`, { timeout: 5000 });
logWarning('Admin config accessible without auth (security issue!)');
return false;
} catch (error) {
if (error.response?.status === 401) {
logSuccess('Admin config properly protected (401 Unauthorized)');
return true;
} else if (error.response?.status === 404) {
logError('Admin config route not found (404)');
logError(' Routes may not be registered. Restart the server!');
return false;
} else {
logError(`Unexpected error: ${error.response?.status || error.message}`);
return false;
}
}
}
async function testAdminUsersNoAuth() {
console.log('\n' + colorize('='.repeat(60), 'cyan'));
console.log(colorize('TEST 4: Admin Users Search (No Auth)', 'cyan'));
console.log(colorize('='.repeat(60), 'cyan'));
try {
await axios.get(`${API_URL}/api/admin/users/search?query=test&limit=1`, { timeout: 5000 });
logWarning('Admin users route accessible without auth (security issue!)');
return false;
} catch (error) {
if (error.response?.status === 401) {
logSuccess('Admin users route properly protected (401 Unauthorized)');
return true;
} else if (error.response?.status === 404) {
logError('Admin users route not found (404)');
logError(' Routes may not be registered. Restart the server!');
return false;
} else {
logError(`Unexpected error: ${error.response?.status || error.message}`);
return false;
}
}
}
async function testPublicConfig() {
console.log('\n' + colorize('='.repeat(60), 'cyan'));
console.log(colorize('TEST 5: Public Config Status', 'cyan'));
console.log(colorize('='.repeat(60), 'cyan'));
try {
const response = await axios.get(`${API_URL}/api/config/status`, { timeout: 5000 });
logSuccess('Public config status accessible');
logInfo(` Maintenance Mode: ${response.data.maintenance?.enabled ? 'ON' : 'OFF'}`);
logInfo(` Trading: ${response.data.trading ? 'Enabled' : 'Disabled'}`);
logInfo(` Market: ${response.data.market ? 'Enabled' : 'Disabled'}`);
return true;
} catch (error) {
if (error.response?.status === 404) {
logError('Public config route not found (404)');
logError(' Routes may not be registered. Restart the server!');
return false;
} else {
logError(`Error accessing public config: ${error.message}`);
return false;
}
}
}
async function runTests() {
console.log('\n' + colorize('╔' + '═'.repeat(58) + '╗', 'cyan'));
console.log(colorize('║' + ' '.repeat(12) + 'ADMIN ROUTES TEST SUITE' + ' '.repeat(23) + '║', 'cyan'));
console.log(colorize('╚' + '═'.repeat(58) + '╝', 'cyan'));
const results = {
health: false,
routes: null,
configNoAuth: false,
usersNoAuth: false,
publicConfig: false,
};
// Run tests
results.health = await testHealth();
if (!results.health) {
console.log('\n' + colorize('='.repeat(60), 'red'));
logError('Backend is not running. Start it with: npm run dev');
console.log(colorize('='.repeat(60), 'red'));
process.exit(1);
}
results.routes = await testRoutesList();
results.configNoAuth = await testAdminConfigNoAuth();
results.usersNoAuth = await testAdminUsersNoAuth();
results.publicConfig = await testPublicConfig();
// Summary
console.log('\n' + colorize('╔' + '═'.repeat(58) + '╗', 'cyan'));
console.log(colorize('║' + ' '.repeat(21) + 'TEST SUMMARY' + ' '.repeat(24) + '║', 'cyan'));
console.log(colorize('╚' + '═'.repeat(58) + '╝', 'cyan'));
const passed = [
results.health,
results.routes !== false,
results.configNoAuth,
results.usersNoAuth,
results.publicConfig,
].filter(Boolean).length;
const total = 5;
console.log(`\n${colorize(`Passed: ${passed}/${total}`, passed === total ? 'green' : 'yellow')}`);
if (results.configNoAuth === false || results.usersNoAuth === false || results.publicConfig === false) {
console.log('\n' + colorize('⚠️ ACTION REQUIRED:', 'yellow'));
console.log(colorize(' Routes are not registered properly.', 'yellow'));
console.log(colorize(' Please RESTART the backend server:', 'yellow'));
console.log(colorize(' 1. Stop server (Ctrl+C)', 'yellow'));
console.log(colorize(' 2. Run: npm run dev', 'yellow'));
console.log(colorize(' 3. Run this test again', 'yellow'));
} else {
console.log('\n' + colorize('✅ All routes are properly registered!', 'green'));
console.log(colorize(' Admin panel should work correctly.', 'green'));
console.log(colorize(' Access it at: http://localhost:5173/admin', 'green'));
}
console.log('\n' + colorize('='.repeat(60), 'cyan'));
console.log(colorize('Note: These tests check route registration, not authentication.', 'blue'));
console.log(colorize('To test full admin access, use the Debug panel in /admin', 'blue'));
console.log(colorize('='.repeat(60), 'cyan') + '\n');
process.exit(passed === total ? 0 : 1);
}
// Run tests
runTests().catch(error => {
console.error('\n' + colorize('Fatal Error:', 'red'), error.message);
process.exit(1);
});