feat: chat — group chat creation (create-group view, backend endpoint, group room display)

This commit is contained in:
2026-04-14 17:36:04 +03:00
parent b91a75c89f
commit 8353eb7c7f
4 changed files with 176 additions and 11 deletions

View File

@@ -97,12 +97,17 @@ const chatRoutes: FastifyPluginAsync = async (fastify) => {
(SELECT u.name FROM chat_room_members crm JOIN users u ON u.id = crm.user_id WHERE crm.room_id = r.id AND crm.user_id != $2 LIMIT 1) AS other_user_name,
(SELECT u.role FROM chat_room_members crm JOIN users u ON u.id = crm.user_id WHERE crm.room_id = r.id AND crm.user_id != $2 LIMIT 1) AS other_user_role,
(SELECT crm.user_id FROM chat_room_members crm WHERE crm.room_id = r.id AND crm.user_id != $2 LIMIT 1) AS other_user_id,
(SELECT COUNT(*) FROM chat_room_members crm WHERE crm.room_id = r.id) AS member_count,
(SELECT json_agg(u.name ORDER BY u.name) FROM chat_room_members crm JOIN users u ON u.id = crm.user_id WHERE crm.room_id = r.id AND crm.user_id != $2 LIMIT 4) AS member_names,
(SELECT rs.last_read FROM chat_read_status rs WHERE rs.room_id = r.id AND rs.user_id != $2 LIMIT 1) AS other_user_last_read
FROM chat_rooms r
WHERE r.hotel_id = $1
AND (r.type IN ('general', 'notifications') OR EXISTS (
SELECT 1 FROM chat_room_members crm WHERE crm.room_id = r.id AND crm.user_id = $2
))
AND (r.type != 'group' OR EXISTS (
SELECT 1 FROM chat_room_members crm WHERE crm.room_id = r.id AND crm.user_id = $2
))
ORDER BY last_message_at DESC NULLS LAST, r.type = 'general' DESC`,
[hotelId, userId],
)
@@ -324,6 +329,68 @@ const chatRoutes: FastifyPluginAsync = async (fastify) => {
},
)
// ── POST group room ───────────────────────────────────────────────────────
fastify.post<SlugParam & { Body: { name: string; memberIds: string[] } }>(
'/api/hotels/:slug/chat/group',
{ onRequest: [fastify.authenticate] },
async (request, reply) => {
const { slug } = request.params
if (!canAccess(request.user.hotelSlug, request.user.role, slug))
return reply.code(403).send({ error: 'Forbidden' })
const hotelId = await getHotelId(slug)
if (!hotelId) return reply.code(404).send({ error: 'Hotel not found' })
const { name, memberIds } = request.body
if (!name?.trim()) return reply.code(400).send({ error: 'Name required' })
const userId = request.user.sub
const allMembers = [...new Set([userId, ...memberIds])]
const { rows: [room] } = await db.query(
`INSERT INTO chat_rooms (hotel_id, type, name) VALUES ($1, 'group', $2) RETURNING id`,
[hotelId, name.trim()],
)
const memberValues = allMembers.map((_, i) => `($1, $${i + 2})`).join(', ')
await db.query(
`INSERT INTO chat_room_members (room_id, user_id) VALUES ${memberValues}`,
[room.id, ...allMembers],
)
return reply.code(201).send({ roomId: room.id })
},
)
// ── PATCH group room (rename / add/remove members) ────────────────────────
fastify.patch<RoomParam & { Body: { name?: string; addMemberIds?: string[]; removeMemberIds?: string[] } }>(
'/api/hotels/:slug/chat/rooms/:roomId/group',
{ onRequest: [fastify.authenticate] },
async (request, reply) => {
const { slug, roomId } = request.params
if (!canAccess(request.user.hotelSlug, request.user.role, slug))
return reply.code(403).send({ error: 'Forbidden' })
const { name, addMemberIds = [], removeMemberIds = [] } = request.body
if (name) {
await db.query('UPDATE chat_rooms SET name = $1 WHERE id = $2', [name.trim(), roomId])
}
if (addMemberIds.length > 0) {
const vals = addMemberIds.map((_, i) => `($1, $${i + 2})`).join(', ')
await db.query(
`INSERT INTO chat_room_members (room_id, user_id) VALUES ${vals} ON CONFLICT DO NOTHING`,
[roomId, ...addMemberIds],
)
}
if (removeMemberIds.length > 0) {
await db.query(
`DELETE FROM chat_room_members WHERE room_id = $1 AND user_id = ANY($2::uuid[])`,
[roomId, removeMemberIds],
)
}
return { ok: true }
},
)
// ── PATCH mark read ───────────────────────────────────────────────────────
fastify.patch<RoomParam>(