diff --git a/redlight.db-shm b/redlight.db-shm deleted file mode 100644 index 6ec64e4..0000000 Binary files a/redlight.db-shm and /dev/null differ diff --git a/redlight.db-wal b/redlight.db-wal deleted file mode 100644 index 1ef30dd..0000000 Binary files a/redlight.db-wal and /dev/null differ diff --git a/server/config/bbb.js b/server/config/bbb.js index 5792fca..bdb46f1 100644 --- a/server/config/bbb.js +++ b/server/config/bbb.js @@ -66,7 +66,7 @@ export async function createMeeting(room, logoutURL) { return apiCall('create', params); } -export async function joinMeeting(uid, name, isModerator = false) { +export async function joinMeeting(uid, name, isModerator = false, avatarURL = null) { const { moderatorPW, attendeePW } = getRoomPasswords(uid); const params = { meetingID: uid, @@ -74,6 +74,9 @@ export async function joinMeeting(uid, name, isModerator = false) { password: isModerator ? moderatorPW : attendeePW, redirect: 'true', }; + if (avatarURL) { + params.avatarURL = avatarURL; + } return buildUrl('join', params); } diff --git a/server/routes/auth.js b/server/routes/auth.js index 25ee5ce..0d6a350 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -203,6 +203,36 @@ router.delete('/avatar', authenticateToken, async (req, res) => { } }); +// GET /api/auth/avatar/initials/:name - Generate SVG avatar from initials (public, BBB fetches this) +router.get('/avatar/initials/:name', (req, res) => { + const name = decodeURIComponent(req.params.name).trim(); + const color = req.query.color || generateColorFromName(name); + const initials = name + .split(' ') + .map(n => n[0]) + .join('') + .toUpperCase() + .slice(0, 2) || '?'; + + const svg = ` + + ${initials} + `; + + res.setHeader('Content-Type', 'image/svg+xml'); + res.setHeader('Cache-Control', 'public, max-age=3600'); + res.send(svg); +}); + +function generateColorFromName(name) { + let hash = 0; + for (let i = 0; i < name.length; i++) { + hash = name.charCodeAt(i) + ((hash << 5) - hash); + } + const hue = Math.abs(hash) % 360; + return `hsl(${hue}, 55%, 45%)`; +} + // GET /api/auth/avatar/:filename - Serve avatar image router.get('/avatar/:filename', (req, res) => { const filepath = path.join(uploadsDir, req.params.filename); diff --git a/server/routes/rooms.js b/server/routes/rooms.js index f2dcd35..63db1bd 100644 --- a/server/routes/rooms.js +++ b/server/routes/rooms.js @@ -12,6 +12,16 @@ import { const router = Router(); +// Build avatar URL for a user (uploaded image or generated initials) +function getUserAvatarURL(req, user) { + const baseUrl = `${req.protocol}://${req.get('host')}`; + if (user.avatar_image) { + return `${baseUrl}/api/auth/avatar/${user.avatar_image}`; + } + const color = user.avatar_color ? `?color=${encodeURIComponent(user.avatar_color)}` : ''; + return `${baseUrl}/api/auth/avatar/initials/${encodeURIComponent(user.name)}${color}`; +} + // GET /api/rooms - List user's rooms router.get('/', authenticateToken, async (req, res) => { try { @@ -199,7 +209,8 @@ router.post('/:uid/start', authenticateToken, async (req, res) => { } await createMeeting(room, `${req.protocol}://${req.get('host')}`); - const joinUrl = await joinMeeting(room.uid, req.user.name, true); + const avatarURL = getUserAvatarURL(req, req.user); + const joinUrl = await joinMeeting(room.uid, req.user.name, true, avatarURL); res.json({ joinUrl }); } catch (err) { console.error('Start meeting error:', err); @@ -229,7 +240,8 @@ router.post('/:uid/join', authenticateToken, async (req, res) => { } const isModerator = room.user_id === req.user.id || room.all_join_moderator; - const joinUrl = await joinMeeting(room.uid, req.user.name, isModerator); + const avatarURL = getUserAvatarURL(req, req.user); + const joinUrl = await joinMeeting(room.uid, req.user.name, isModerator, avatarURL); res.json({ joinUrl }); } catch (err) { console.error('Join meeting error:', err); @@ -335,7 +347,9 @@ router.post('/:uid/guest-join', async (req, res) => { isModerator = true; } - const joinUrl = await joinMeeting(room.uid, name.trim(), isModerator); + const baseUrl = `${req.protocol}://${req.get('host')}`; + const guestAvatarURL = `${baseUrl}/api/auth/avatar/initials/${encodeURIComponent(name.trim())}`; + const joinUrl = await joinMeeting(room.uid, name.trim(), isModerator, guestAvatarURL); res.json({ joinUrl }); } catch (err) { console.error('Guest join error:', err);