feat: chat — group chat creation (create-group view, backend endpoint, group room display)
This commit is contained in:
4
backend/migrations/076_chat_group_rooms.sql
Normal file
4
backend/migrations/076_chat_group_rooms.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
-- Allow 'group' room type (drop old check constraint, add new one)
|
||||
ALTER TABLE chat_rooms DROP CONSTRAINT IF EXISTS chat_rooms_type_check;
|
||||
ALTER TABLE chat_rooms ADD CONSTRAINT chat_rooms_type_check
|
||||
CHECK (type IN ('general', 'direct', 'notifications', 'group'));
|
||||
@@ -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>(
|
||||
|
||||
Reference in New Issue
Block a user