From 2d17ec60dbf6cdf301d548f51af6fd01d0b82fde Mon Sep 17 00:00:00 2001 From: Maurice Date: Wed, 8 Apr 2026 18:17:08 +0200 Subject: [PATCH] fix: missing avatar URLs in notifications, admin panel, and budget - Notifications: map raw avatar filename to /uploads/avatars/ URL in getNotifications, createNotification broadcasts, and respond handler - Admin listUsers: include avatar field in SELECT and map to avatar_url - Admin page: render actual avatar image instead of initial letter only - Budget loadItemMembers: map avatar to avatar_url (fixed in prior commit) Fixes #507 --- client/src/pages/AdminPage.tsx | 11 ++++++++--- server/src/services/adminService.ts | 5 +++-- server/src/services/inAppNotifications.ts | 20 +++++++++++++++----- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/client/src/pages/AdminPage.tsx b/client/src/pages/AdminPage.tsx index c6f5d516..051ce84c 100644 --- a/client/src/pages/AdminPage.tsx +++ b/client/src/pages/AdminPage.tsx @@ -30,6 +30,7 @@ interface AdminUser { last_login?: string | null online?: boolean oidc_issuer?: string | null + avatar_url?: string | null } interface AdminStats { @@ -605,9 +606,13 @@ export default function AdminPage(): React.ReactElement {
-
- {u.username.charAt(0).toUpperCase()} -
+ {u.avatar_url ? ( + {u.username} + ) : ( +
+ {u.username.charAt(0).toUpperCase()} +
+ )}
diff --git a/server/src/services/adminService.ts b/server/src/services/adminService.ts index cdd8dbaa..9ca96604 100644 --- a/server/src/services/adminService.ts +++ b/server/src/services/adminService.ts @@ -40,8 +40,8 @@ export const isDocker = (() => { export function listUsers() { const users = db.prepare( - 'SELECT id, username, email, role, created_at, updated_at, last_login FROM users ORDER BY created_at DESC' - ).all() as Pick[]; + 'SELECT id, username, email, role, avatar, created_at, updated_at, last_login FROM users ORDER BY created_at DESC' + ).all() as (Pick & { avatar?: string | null })[]; let onlineUserIds = new Set(); try { const { getOnlineUserIds } = require('../websocket'); @@ -49,6 +49,7 @@ export function listUsers() { } catch { /* */ } return users.map(u => ({ ...u, + avatar_url: u.avatar ? `/uploads/avatars/${u.avatar}` : null, created_at: utcSuffix(u.created_at), updated_at: utcSuffix(u.updated_at as string), last_login: utcSuffix(u.last_login), diff --git a/server/src/services/inAppNotifications.ts b/server/src/services/inAppNotifications.ts index 65649155..1b09ed83 100644 --- a/server/src/services/inAppNotifications.ts +++ b/server/src/services/inAppNotifications.ts @@ -159,7 +159,7 @@ function createNotification(input: NotificationInput): number[] { notification: { ...row, sender_username: sender?.username ?? null, - sender_avatar: sender?.avatar ?? null, + sender_avatar: sender?.avatar ? `/uploads/avatars/${sender.avatar}` : null, }, }); } @@ -219,7 +219,7 @@ export function createNotificationForRecipient( notification: { ...row, sender_username: sender?.username ?? null, - sender_avatar: sender?.avatar ?? null, + sender_avatar: sender?.avatar ? `/uploads/avatars/${sender.avatar}` : null, }, }); @@ -249,7 +249,12 @@ function getNotifications( const { total } = db.prepare(`SELECT COUNT(*) as total FROM notifications ${wherePlain}`).get(userId) as { total: number }; const { unread_count } = db.prepare('SELECT COUNT(*) as unread_count FROM notifications WHERE recipient_id = ? AND is_read = 0').get(userId) as { unread_count: number }; - return { notifications: rows, total, unread_count }; + const mapped = rows.map(r => ({ + ...r, + sender_avatar: r.sender_avatar ? `/uploads/avatars/${r.sender_avatar}` : null, + })); + + return { notifications: mapped, total, unread_count }; } function getUnreadCount(userId: number): number { @@ -326,9 +331,14 @@ async function respondToBoolean( WHERE n.id = ? `).get(notificationId) as NotificationRow; - broadcastToUser(userId, { type: 'notification:updated', notification: updated }); + const mappedUpdated = { + ...updated, + sender_avatar: updated.sender_avatar ? `/uploads/avatars/${updated.sender_avatar}` : null, + }; - return { success: true, notification: updated }; + broadcastToUser(userId, { type: 'notification:updated', notification: mappedUpdated }); + + return { success: true, notification: mappedUpdated }; } export {