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

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:
2025-07-24 16:57:07 +02:00
co-authored by Claude
commit 1773ed5f95
354 changed files with 61508 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
import React, { useEffect, useState, useMemo } from 'react';
import { useTheme } from '../../contexts/ThemeContext';
import { GalleryLayout, PhotoFilterBar } from '../../components/gallery';
import { Card } from '../../components/common';
import { Camera } from 'lucide-react';
// Mock photo data for preview
const generateMockPhotos = (count: number) => {
return Array.from({ length: count }, (_, i) => ({
id: i + 1,
filename: `photo-${i + 1}.jpg`,
url: '',
thumbnail_url: '',
type: i % 3 === 0 ? 'collage' : 'individual',
category_id: (i % 4) + 1,
category_name: ['Ceremony', 'Reception', 'Portraits', 'Party'][i % 4],
category_slug: ['ceremony', 'reception', 'portraits', 'party'][i % 4],
size: Math.floor(Math.random() * 5000000) + 1000000,
uploaded_at: new Date().toISOString(),
}));
};
const mockCategories = [
{ id: 1, name: 'Ceremony', slug: 'ceremony', is_global: true },
{ id: 2, name: 'Reception', slug: 'reception', is_global: true },
{ id: 3, name: 'Portraits', slug: 'portraits', is_global: true },
{ id: 4, name: 'Party', slug: 'party', is_global: true },
];
export const PreviewPage: React.FC = () => {
const { setTheme } = useTheme();
const [brandingSettings, setBrandingSettings] = useState<any>(null);
const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(null);
const [searchTerm, setSearchTerm] = useState('');
const [sortBy, setSortBy] = useState<'date' | 'name' | 'size'>('date');
const mockPhotos = useMemo(() => generateMockPhotos(12), []);
const mockEvent = {
event_name: 'Preview Wedding Gallery',
event_date: new Date().toISOString(),
expires_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
};
useEffect(() => {
// Listen for theme preview messages from the branding page
const handleMessage = (event: MessageEvent) => {
if (event.data.type === 'THEME_PREVIEW') {
setTheme(event.data.theme);
setBrandingSettings(event.data.branding);
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [setTheme]);
// Filter photos
const filteredPhotos = useMemo(() => {
let photos = [...mockPhotos];
// Apply category filter
if (selectedCategoryId) {
photos = photos.filter(photo => photo.category_id === selectedCategoryId);
}
// Apply search filter
if (searchTerm) {
photos = photos.filter(photo =>
photo.filename.toLowerCase().includes(searchTerm.toLowerCase())
);
}
// Apply sorting
photos.sort((a, b) => {
switch (sortBy) {
case 'name':
return a.filename.localeCompare(b.filename);
case 'size':
return b.size - a.size;
case 'date':
default:
return new Date(b.uploaded_at).getTime() - new Date(a.uploaded_at).getTime();
}
});
return photos;
}, [mockPhotos, selectedCategoryId, searchTerm, sortBy]);
// Custom photo renderer for preview
const PreviewPhotoGrid: React.FC<{ photos: any[] }> = ({ photos }) => (
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
{photos.map((photo) => (
<Card key={photo.id} className="overflow-hidden group cursor-pointer">
<div className="aspect-[4/3] bg-gradient-to-br from-neutral-200 to-neutral-300 relative">
<div className="absolute inset-0 flex items-center justify-center">
<Camera className="w-12 h-12 text-neutral-400" />
</div>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/60 to-transparent p-2">
<p className="text-white text-xs truncate">{photo.filename}</p>
{photo.category_name && (
<p className="text-white/70 text-xs">{photo.category_name}</p>
)}
</div>
</div>
</Card>
))}
</div>
);
return (
<GalleryLayout
event={mockEvent}
brandingSettings={brandingSettings}
showLogout={false}
showDownloadAll={false}
>
<div className="mt-8">
<div className="text-center mb-6">
<h2 className="text-xl font-semibold text-neutral-900">Theme Preview</h2>
<p className="text-neutral-600">This is how your galleries will look with the current theme settings</p>
</div>
{/* Filters */}
<PhotoFilterBar
categories={mockCategories}
photos={mockPhotos}
selectedCategoryId={selectedCategoryId}
onCategoryChange={setSelectedCategoryId}
searchTerm={searchTerm}
onSearchChange={setSearchTerm}
sortBy={sortBy}
onSortChange={setSortBy}
photoCount={filteredPhotos.length}
/>
{/* Photo Grid */}
<div className="mt-6">
<PreviewPhotoGrid photos={filteredPhotos} />
</div>
</div>
</GalleryLayout>
);
};