Implement complete frontend with admin panel and theme system

- Add admin authentication and dashboard
- Create event management pages (list, create, edit, archive)
- Implement gallery enhancements (search, sorting, bulk download)
- Add email configuration and archive management pages
- Integrate Umami analytics with tracking throughout the app
- Add comprehensive error boundaries and loading states
- Implement accessibility features (WCAG 2.1 AA compliance)
- Create theme system with preset themes and customization
- Add branding settings and company information management
- Fix backend database initialization and health check
- Configure proper API URLs and environment variables

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-06 22:04:45 +02:00
parent 6c82958c79
commit 28632e8970
53 changed files with 13843 additions and 181 deletions
+76 -61
View File
@@ -11,75 +11,90 @@ const db = knex({
async function initializeDatabase() {
// Events table
await db.schema.createTableIfNotExists('events', (table) => {
table.increments('id').primary();
table.string('slug').unique().notNullable();
table.string('event_type').notNullable();
table.string('event_name').notNullable();
table.date('event_date').notNullable();
table.string('host_email').notNullable();
table.string('admin_email').notNullable();
table.string('password_hash').notNullable();
table.text('welcome_message');
table.string('color_theme');
table.string('share_link').unique().notNullable();
table.datetime('created_at').defaultTo(db.fn.now());
table.datetime('expires_at').notNullable();
table.boolean('is_active').defaultTo(true);
table.boolean('is_archived').defaultTo(false);
table.string('archive_path');
table.datetime('archived_at');
});
const hasEventsTable = await db.schema.hasTable('events');
if (!hasEventsTable) {
await db.schema.createTable('events', (table) => {
table.increments('id').primary();
table.string('slug').unique().notNullable();
table.string('event_type').notNullable();
table.string('event_name').notNullable();
table.date('event_date').notNullable();
table.string('host_email').notNullable();
table.string('admin_email').notNullable();
table.string('password_hash').notNullable();
table.text('welcome_message');
table.string('color_theme');
table.string('share_link').unique().notNullable();
table.datetime('created_at').defaultTo(db.fn.now());
table.datetime('expires_at').notNullable();
table.boolean('is_active').defaultTo(true);
table.boolean('is_archived').defaultTo(false);
table.string('archive_path');
table.datetime('archived_at');
});
}
// Photo metadata table
await db.schema.createTableIfNotExists('photos', (table) => {
table.increments('id').primary();
table.integer('event_id').references('id').inTable('events').onDelete('CASCADE');
table.string('filename').notNullable();
table.string('path').notNullable();
table.string('thumbnail_path');
table.string('type').notNullable(); // 'collage' or 'individual'
table.integer('size_bytes');
table.datetime('uploaded_at').defaultTo(db.fn.now());
table.integer('view_count').defaultTo(0);
table.integer('download_count').defaultTo(0);
});
const hasPhotosTable = await db.schema.hasTable('photos');
if (!hasPhotosTable) {
await db.schema.createTable('photos', (table) => {
table.increments('id').primary();
table.integer('event_id').references('id').inTable('events').onDelete('CASCADE');
table.string('filename').notNullable();
table.string('path').notNullable();
table.string('thumbnail_path');
table.string('type').notNullable(); // 'collage' or 'individual'
table.integer('size_bytes');
table.datetime('uploaded_at').defaultTo(db.fn.now());
table.integer('view_count').defaultTo(0);
table.integer('download_count').defaultTo(0);
});
}
// Access logs table
await db.schema.createTableIfNotExists('access_logs', (table) => {
table.increments('id').primary();
table.integer('event_id').references('id').inTable('events');
table.string('ip_address');
table.string('user_agent');
table.string('action'); // 'view', 'download', 'login_success', 'login_fail'
table.string('photo_id');
table.datetime('timestamp').defaultTo(db.fn.now());
});
const hasAccessLogsTable = await db.schema.hasTable('access_logs');
if (!hasAccessLogsTable) {
await db.schema.createTable('access_logs', (table) => {
table.increments('id').primary();
table.integer('event_id').references('id').inTable('events');
table.string('ip_address');
table.string('user_agent');
table.string('action'); // 'view', 'download', 'login_success', 'login_fail'
table.string('photo_id');
table.datetime('timestamp').defaultTo(db.fn.now());
});
}
// Email queue table
await db.schema.createTableIfNotExists('email_queue', (table) => {
table.increments('id').primary();
table.integer('event_id').references('id').inTable('events');
table.string('recipient_email').notNullable();
table.string('email_type').notNullable(); // 'creation', 'warning', 'expiration', 'archive_complete'
table.json('email_data');
table.string('status').defaultTo('pending'); // 'pending', 'sent', 'failed'
table.datetime('scheduled_at').defaultTo(db.fn.now());
table.datetime('sent_at');
table.text('error_message');
table.integer('retry_count').defaultTo(0);
});
const hasEmailQueueTable = await db.schema.hasTable('email_queue');
if (!hasEmailQueueTable) {
await db.schema.createTable('email_queue', (table) => {
table.increments('id').primary();
table.integer('event_id').references('id').inTable('events');
table.string('recipient_email').notNullable();
table.string('email_type').notNullable(); // 'creation', 'warning', 'expiration', 'archive_complete'
table.json('email_data');
table.string('status').defaultTo('pending'); // 'pending', 'sent', 'failed'
table.datetime('scheduled_at').defaultTo(db.fn.now());
table.datetime('sent_at');
table.text('error_message');
table.integer('retry_count').defaultTo(0);
});
}
// Admin users table
await db.schema.createTableIfNotExists('admin_users', (table) => {
table.increments('id').primary();
table.string('username').unique().notNullable();
table.string('email').unique().notNullable();
table.string('password_hash').notNullable();
table.boolean('is_active').defaultTo(true);
table.datetime('created_at').defaultTo(db.fn.now());
table.datetime('last_login');
});
const hasAdminUsersTable = await db.schema.hasTable('admin_users');
if (!hasAdminUsersTable) {
await db.schema.createTable('admin_users', (table) => {
table.increments('id').primary();
table.string('username').unique().notNullable();
table.string('email').unique().notNullable();
table.string('password_hash').notNullable();
table.boolean('is_active').defaultTo(true);
table.datetime('created_at').defaultTo(db.fn.now());
table.datetime('last_login');
});
}
}
module.exports = { db, initializeDatabase };