Files
picpeak/backend/src/routes/adminSystem.js
T
paul 71e7179145
Mirror to GitHub / mirror (push) Successful in 45s
Test and Lint / backend-test (push) Successful in 1m37s
Test and Lint / frontend-test (push) Successful in 2m8s
Version and Release / version-bump (push) Successful in 1m0s
Version and Release / trigger-drone (push) Successful in 3s
Harden auth cookies and fix native schema for event creation
2025-09-18 15:59:26 +02:00

235 lines
7.3 KiB
JavaScript

const express = require('express');
const { db, withRetry } = require('../database/db');
const { adminAuth } = require('../middleware/auth-enhanced-v2');
const fs = require('fs').promises;
const path = require('path');
const os = require('os');
const { formatBoolean } = require('../utils/dbCompat');
const logger = require('../utils/logger');
const router = express.Router();
// Get system version
router.get('/version', adminAuth, async (req, res) => {
try {
// Read backend version from package.json
let backendVersion = '1.0.0';
try {
const packagePath = path.join(__dirname, '../../package.json');
const packageContent = await fs.readFile(packagePath, 'utf8');
const packageJson = JSON.parse(packageContent);
backendVersion = packageJson.version || '1.0.0';
} catch (err) {
console.error('Could not read package.json:', err);
}
res.json({
backend: backendVersion,
frontend: '1.0.0', // This will be set by frontend
node: process.version,
environment: process.env.NODE_ENV || 'production'
});
} catch (error) {
console.error('Error fetching version:', error);
res.status(500).json({ error: 'Failed to fetch version information' });
}
});
// Get comprehensive system status
router.get('/status', adminAuth, async (req, res) => {
try {
// Database size - check if PostgreSQL or SQLite
let dbSize = 0;
const dbClient = process.env.DATABASE_CLIENT || 'sqlite3';
if (dbClient === 'pg') {
// PostgreSQL - query database size
try {
const dbName = process.env.DB_NAME || 'picpeak';
const result = await db.raw(`
SELECT pg_database_size(?) as size
`, [dbName]);
dbSize = result.rows[0]?.size || 0;
} catch (error) {
console.error('Error getting PostgreSQL database size:', error);
}
} else {
// SQLite - check file size
const dbPath = path.join(__dirname, '../../data/photo_sharing.db');
try {
const stats = await fs.stat(dbPath);
dbSize = stats.size;
} catch (error) {
console.error('Error getting SQLite database size:', error);
}
}
// Count various entities
const [eventsCount] = await db('events').count('* as count');
const [photosCount] = await db('photos').count('* as count');
const [adminsCount] = await db('admin_users').count('* as count');
const [categoriesCount] = await db('photo_categories').count('* as count');
// Email queue status
const [pendingEmails] = await db('email_queue').where('status', 'pending').count('* as count');
const [processableEmails] = await db('email_queue')
.where('status', 'pending')
.where('retry_count', '<', 3)
.count('* as count');
const [sentEmails] = await db('email_queue').where('status', 'sent').count('* as count');
const [failedEmails] = await db('email_queue').where('status', 'failed').count('* as count');
const [stuckEmails] = await db('email_queue')
.where('status', 'pending')
.where('retry_count', '>=', 3)
.count('* as count');
// Activity logs count
const [activityCount] = await db('activity_logs').count('* as count');
// Storage info
const [{ totalPhotoStorage }] = await db('photos')
.sum('size_bytes as totalPhotoStorage');
const archives = await db('events')
.where('is_archived', formatBoolean(true))
.whereNotNull('archive_path')
.select('archive_path');
let archiveStorage = 0;
const storagePath = process.env.STORAGE_PATH || path.join(__dirname, '../../../storage');
for (const archive of archives) {
if (archive.archive_path) {
try {
const fullArchivePath = path.join(storagePath, archive.archive_path);
const stats = await fs.stat(fullArchivePath);
archiveStorage += stats.size;
} catch (error) {
console.error('Archive file not found:', archive.archive_path);
}
}
}
const totalStorage = (parseInt(totalPhotoStorage) || 0) + archiveStorage;
// System info
const systemInfo = {
platform: os.platform(),
arch: os.arch(),
hostname: os.hostname(),
uptime: Math.floor(process.uptime()),
nodeVersion: process.version,
memory: {
total: os.totalmem(),
free: os.freemem(),
used: os.totalmem() - os.freemem()
},
cpu: {
model: os.cpus()[0]?.model || 'Unknown',
cores: os.cpus().length
}
};
// Build response
const status = {
database: {
size: dbSize,
tables: {
events: eventsCount.count,
photos: photosCount.count,
admins: adminsCount.count,
categories: categoriesCount.count,
activityLogs: activityCount.count
}
},
storage: {
totalUsed: totalStorage,
photoStorage: parseInt(totalPhotoStorage) || 0,
archiveStorage: archiveStorage
},
emailQueue: {
pending: pendingEmails.count,
processable: processableEmails.count,
stuck: stuckEmails.count,
sent: sentEmails.count,
failed: failedEmails.count
},
system: systemInfo,
services: {
fileWatcher: { status: 'active' }, // These would ideally check actual service status
expirationChecker: { status: 'active' },
emailProcessor: { status: 'active' }
},
timestamp: new Date()
};
res.json(status);
} catch (error) {
console.error('Error fetching system status:', error);
res.status(500).json({ error: 'Failed to fetch system status' });
}
});
// Get database statistics
router.get('/database', adminAuth, async (req, res) => {
try {
// Get table info
const tables = [
'events', 'photos', 'admin_users', 'photo_categories',
'cms_pages', 'email_templates', 'email_queue', 'activity_logs',
'app_settings', 'email_configs', 'access_logs', 'migrations'
];
const tableInfo = [];
for (const table of tables) {
try {
const [count] = await db(table).count('* as count');
// Get last update time
let lastUpdate = null;
try {
const lastRow = await db(table)
.orderBy('updated_at', 'desc')
.orOrderBy('created_at', 'desc')
.orOrderBy('timestamp', 'desc')
.orOrderBy('applied_at', 'desc')
.first();
if (lastRow) {
lastUpdate = lastRow.updated_at || lastRow.created_at || lastRow.timestamp || lastRow.applied_at;
}
} catch (e) {
// Table might not have timestamp columns
}
tableInfo.push({
name: table,
rows: count.count,
lastUpdate
});
} catch (error) {
// Table might not exist
logger.warn('Failed to retrieve table info', {
table,
error: error.message
});
tableInfo.push({
name: table,
rows: 0,
error: 'Unable to retrieve table details'
});
}
}
res.json({
tables: tableInfo,
timestamp: new Date()
});
} catch (error) {
console.error('Error fetching database info:', error);
res.status(500).json({ error: 'Failed to fetch database information' });
}
});
module.exports = router;