Initial commit - Project start (July 17, 2025)
Mirror to GitHub / mirror (push) Successful in 26s
Test and Lint / backend-test (push) Successful in 1m11s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m28s
Version and Release / version-bump (push) Successful in 32s
Version and Release / trigger-drone (push) Has been skipped
Mirror to GitHub / mirror (push) Successful in 26s
Test and Lint / backend-test (push) Successful in 1m11s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m28s
Version and Release / version-bump (push) Successful in 32s
Version and Release / trigger-drone (push) Has been skipped
Original: feat: enhance security logging and ensure rate limit blocks are properly tracked - Add comprehensive logging for rate limit blocks with full request details - IP address (with proper proxy detection), user agent, headers, timestamps - Rate limit info (current count, limit, remaining, reset time) - Separate tracking for auth vs general endpoints - Enhance authentication failure logging - JWT validation failures with detailed error info - Admin auth attempts without token - Failed token validation with user context - All events include IP, path, method, user agent - Improve Winston logger configuration for production - Add automatic log rotation (10MB errors, 50MB combined) - Create separate security.log for auth/rate limit events - Ensure logs directory exists automatically - Add structured JSON format for log aggregation - Support container logging with LOG_TO_CONSOLE env var - Create comprehensive documentation - Security logging guide with examples - Monitoring recommendations - Configuration reference - Add test script to verify logging functionality All rate limit settings remain configurable via admin panel: - Window duration, max requests, auth limits - Skip authenticated requests option - Public endpoints only option 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
const express = require('express');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { db, logActivity } = require('../database/db');
|
||||
const { formatBoolean } = require('../utils/dbCompat');
|
||||
const { adminAuth } = require('../middleware/auth-enhanced-v2');
|
||||
const router = express.Router();
|
||||
|
||||
// Get all global categories
|
||||
router.get('/global', adminAuth, async (req, res) => {
|
||||
try {
|
||||
const categories = await db('photo_categories')
|
||||
.where('is_global', formatBoolean(true))
|
||||
.orderBy('name', 'asc');
|
||||
|
||||
res.json(categories);
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch categories' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get categories for a specific event (global + event-specific)
|
||||
router.get('/event/:eventId', adminAuth, async (req, res) => {
|
||||
try {
|
||||
const { eventId } = req.params;
|
||||
|
||||
const categories = await db('photo_categories')
|
||||
.where(function() {
|
||||
this.where('is_global', formatBoolean(true))
|
||||
.orWhere('event_id', eventId);
|
||||
})
|
||||
.orderBy('is_global', 'desc')
|
||||
.orderBy('name', 'asc');
|
||||
|
||||
res.json(categories);
|
||||
} catch (error) {
|
||||
console.error('Error fetching event categories:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch categories' });
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new category
|
||||
router.post('/', adminAuth, [
|
||||
body('name').notEmpty().withMessage('Category name is required'),
|
||||
body('slug').optional(),
|
||||
body('is_global').optional().isBoolean(),
|
||||
body('event_id').optional().isInt()
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { name, slug, is_global = true, event_id = null } = req.body;
|
||||
|
||||
// Generate slug if not provided
|
||||
const categorySlug = slug || name.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.trim();
|
||||
|
||||
// Check if slug already exists for this scope
|
||||
const existing = await db('photo_categories')
|
||||
.where('slug', categorySlug)
|
||||
.where(function() {
|
||||
if (is_global) {
|
||||
this.where('is_global', formatBoolean(true));
|
||||
} else {
|
||||
this.where('event_id', event_id);
|
||||
}
|
||||
})
|
||||
.first();
|
||||
|
||||
if (existing) {
|
||||
return res.status(400).json({ error: 'Category with this slug already exists' });
|
||||
}
|
||||
|
||||
// Create category
|
||||
const insertResult = await db('photo_categories').insert({
|
||||
name,
|
||||
slug: categorySlug,
|
||||
is_global,
|
||||
event_id: is_global ? null : event_id
|
||||
}).returning('id');
|
||||
|
||||
const categoryId = insertResult[0]?.id || insertResult[0];
|
||||
|
||||
const category = await db('photo_categories').where('id', categoryId).first();
|
||||
|
||||
// Log activity
|
||||
await logActivity('category_created',
|
||||
{ categoryName: name, isGlobal: is_global },
|
||||
event_id,
|
||||
{ type: 'admin', id: req.admin.id, name: req.admin.username }
|
||||
);
|
||||
|
||||
res.json(category);
|
||||
} catch (error) {
|
||||
console.error('Error creating category:', error);
|
||||
res.status(500).json({ error: 'Failed to create category' });
|
||||
}
|
||||
});
|
||||
|
||||
// Update a category
|
||||
router.put('/:id', adminAuth, [
|
||||
body('name').notEmpty().withMessage('Category name is required')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const { name } = req.body;
|
||||
|
||||
const category = await db('photo_categories').where('id', id).first();
|
||||
if (!category) {
|
||||
return res.status(404).json({ error: 'Category not found' });
|
||||
}
|
||||
|
||||
await db('photo_categories')
|
||||
.where('id', id)
|
||||
.update({
|
||||
name,
|
||||
slug: name.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.trim()
|
||||
});
|
||||
|
||||
const updated = await db('photo_categories').where('id', id).first();
|
||||
|
||||
// Log activity
|
||||
await logActivity('category_updated',
|
||||
{ categoryName: name },
|
||||
category.event_id,
|
||||
{ type: 'admin', id: req.admin.id, name: req.admin.username }
|
||||
);
|
||||
|
||||
res.json(updated);
|
||||
} catch (error) {
|
||||
console.error('Error updating category:', error);
|
||||
res.status(500).json({ error: 'Failed to update category' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete a category
|
||||
router.delete('/:id', adminAuth, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const category = await db('photo_categories').where('id', id).first();
|
||||
if (!category) {
|
||||
return res.status(404).json({ error: 'Category not found' });
|
||||
}
|
||||
|
||||
// Check if category has photos
|
||||
const photoCount = await db('photos').where('category_id', id).count('id as count').first();
|
||||
if (photoCount.count > 0) {
|
||||
return res.status(400).json({
|
||||
error: 'Cannot delete category with photos. Please reassign photos first.'
|
||||
});
|
||||
}
|
||||
|
||||
await db('photo_categories').where('id', id).delete();
|
||||
|
||||
// Log activity
|
||||
await logActivity('category_deleted',
|
||||
{ categoryName: category.name },
|
||||
category.event_id,
|
||||
{ type: 'admin', id: req.admin.id, name: req.admin.username }
|
||||
);
|
||||
|
||||
res.json({ message: 'Category deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting category:', error);
|
||||
res.status(500).json({ error: 'Failed to delete category' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user