From 33483cf32dfae57f8da51c0765353792239135f9 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Sun, 22 Feb 2026 22:27:21 +0100 Subject: [PATCH] fix: resolve issues #194, #195, #196, #197 - #194: Send full date format object instead of just format string to prevent JSON parse errors - #195: Remove non-functional forgot password link, fix README port 3005 -> 3000 - #196: Use ADMIN_PASSWORD env var in migration, update existing user in create-admin script instead of failing - #197: Convert camelCase filter keys to snake_case in photo export to match backend PhotoFilterBuilder --- README.md | 2 +- backend/migrations/core/001_init.js | 4 +- backend/scripts/create-admin.js | 47 ++++++++++++------- .../src/components/admin/PhotoExportMenu.tsx | 15 +++++- .../settings/hooks/useSettingsState.ts | 6 +-- frontend/src/pages/admin/AdminLoginPage.tsx | 5 +- 6 files changed, 48 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 339197c2..0c8357f6 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ nano .env # Start with Docker Compose docker compose up -d -# Access at http://localhost:3005 +# Access at http://localhost:3000 ``` Note on Docker file permissions (PUID/PGID) diff --git a/backend/migrations/core/001_init.js b/backend/migrations/core/001_init.js index 4da7fd1a..2c24dcaa 100644 --- a/backend/migrations/core/001_init.js +++ b/backend/migrations/core/001_init.js @@ -14,8 +14,8 @@ exports.up = async function(knex) { // Create default admin user if none exists const adminExists = await knex('admin_users').first(); if (!adminExists) { - // Generate a secure random password - const generatedPassword = generateReadablePassword(); + // Use ADMIN_PASSWORD from environment if set, otherwise generate a random one + const generatedPassword = process.env.ADMIN_PASSWORD || generateReadablePassword(); const passwordHash = await bcrypt.hash(generatedPassword, 12); // Increased rounds for better security // Get admin credentials from environment or use defaults diff --git a/backend/scripts/create-admin.js b/backend/scripts/create-admin.js index 675311ce..8450aec1 100644 --- a/backend/scripts/create-admin.js +++ b/backend/scripts/create-admin.js @@ -44,28 +44,39 @@ async function createAdmin() { .orWhere('username', username) .first(); - if (existingUser) { - console.error(`Error: User with email "${email}" or username "${username}" already exists`); - process.exit(1); - } - // Hash password const passwordHash = await bcrypt.hash(password, 10); - // Create admin user - await db('admin_users').insert({ - username, - email, - password_hash: passwordHash, - is_active: true, - created_at: new Date(), - updated_at: new Date() - }); + if (existingUser) { + // Update existing user's password + await db('admin_users') + .where('id', existingUser.id) + .update({ + password_hash: passwordHash, + updated_at: new Date() + }); + + console.log(`✅ Admin user updated successfully!`); + console.log(` Email: ${existingUser.email}`); + console.log(` Username: ${existingUser.username}`); + console.log(` Password has been reset to the provided value`); + console.log(` Login URL: ${process.env.ADMIN_URL || 'http://localhost:3000'}/admin/login`); + } else { + // Create new admin user + await db('admin_users').insert({ + username, + email, + password_hash: passwordHash, + is_active: true, + created_at: new Date(), + updated_at: new Date() + }); - console.log(`✅ Admin user created successfully!`); - console.log(` Email: ${email}`); - console.log(` Username: ${username}`); - console.log(` Login URL: ${process.env.ADMIN_URL || 'http://localhost:3000'}/admin/login`); + console.log(`✅ Admin user created successfully!`); + console.log(` Email: ${email}`); + console.log(` Username: ${username}`); + console.log(` Login URL: ${process.env.ADMIN_URL || 'http://localhost:3000'}/admin/login`); + } process.exit(0); } catch (error) { diff --git a/frontend/src/components/admin/PhotoExportMenu.tsx b/frontend/src/components/admin/PhotoExportMenu.tsx index 6a904eb0..c28e5314 100644 --- a/frontend/src/components/admin/PhotoExportMenu.tsx +++ b/frontend/src/components/admin/PhotoExportMenu.tsx @@ -75,7 +75,20 @@ export const PhotoExportMenu: React.FC = ({ if (selectedPhotoIds.length > 0) { options.photo_ids = selectedPhotoIds; } else if (filters) { - options.filter = filters; + // Convert camelCase filter keys to snake_case for backend + options.filter = { + min_rating: filters.minRating, + max_rating: filters.maxRating, + has_likes: filters.hasLikes, + min_likes: filters.minLikes, + has_favorites: filters.hasFavorites, + min_favorites: filters.minFavorites, + has_comments: filters.hasComments, + category_id: filters.categoryId, + logic: filters.logic, + sort: filters.sort, + order: filters.order, + }; } exportMutation.mutate(options); diff --git a/frontend/src/features/settings/hooks/useSettingsState.ts b/frontend/src/features/settings/hooks/useSettingsState.ts index ffae38fc..fabdf384 100644 --- a/frontend/src/features/settings/hooks/useSettingsState.ts +++ b/frontend/src/features/settings/hooks/useSettingsState.ts @@ -256,11 +256,7 @@ export function useSettingsState() { mutationFn: async () => { const settingsData: Record = {}; Object.entries(generalSettings).forEach(([key, value]) => { - if (key === 'date_format' && typeof value === 'object' && value.format) { - settingsData[`general_${key}`] = value.format; - } else { - settingsData[`general_${key}`] = value; - } + settingsData[`general_${key}`] = value; }); return settingsService.updateSettings(settingsData); }, diff --git a/frontend/src/pages/admin/AdminLoginPage.tsx b/frontend/src/pages/admin/AdminLoginPage.tsx index 78ea0f39..5c512b02 100644 --- a/frontend/src/pages/admin/AdminLoginPage.tsx +++ b/frontend/src/pages/admin/AdminLoginPage.tsx @@ -200,7 +200,7 @@ export const AdminLoginPage: React.FC = () => { - {/* Remember Me & Forgot Password */} + {/* Remember Me */}
- - {t('adminLogin.forgotPassword')} -
{/* reCAPTCHA */}