feat: group chat owner protection — prevent removing creator, transfer ownership before leaving
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -80,7 +80,7 @@ const chatRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
const userId = request.user.sub
|
||||
|
||||
const { rows } = await db.query(
|
||||
`SELECT r.id, r.type, r.name, r.avatar_url,
|
||||
`SELECT r.id, r.type, r.name, r.avatar_url, r.created_by,
|
||||
(SELECT COUNT(*) FROM chat_messages m
|
||||
WHERE m.room_id = r.id AND m.deleted_at IS NULL
|
||||
AND m.created_at > COALESCE(
|
||||
@@ -374,8 +374,8 @@ const chatRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
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()],
|
||||
`INSERT INTO chat_rooms (hotel_id, type, name, created_by) VALUES ($1, 'group', $2, $3) RETURNING id`,
|
||||
[hotelId, name.trim(), userId],
|
||||
)
|
||||
const memberValues = allMembers.map((_, i) => `($1, $${i + 2})`).join(', ')
|
||||
await db.query(
|
||||
@@ -388,7 +388,7 @@ const chatRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
|
||||
// ── PATCH group room (rename / add/remove members) ────────────────────────
|
||||
|
||||
fastify.patch<RoomParam & { Body: { name?: string; avatarUrl?: string; avatar_url?: string; addMemberIds?: string[]; add_member_ids?: string[]; removeMemberIds?: string[]; remove_member_ids?: string[] } }>(
|
||||
fastify.patch<RoomParam & { Body: { name?: string; avatarUrl?: string; avatar_url?: string; addMemberIds?: string[]; add_member_ids?: string[]; removeMemberIds?: string[]; remove_member_ids?: string[]; transferOwnerTo?: string; transfer_owner_to?: string } }>(
|
||||
'/api/hotels/:slug/chat/rooms/:roomId/group',
|
||||
{ onRequest: [fastify.authenticate] },
|
||||
async (request, reply) => {
|
||||
@@ -403,6 +403,25 @@ const chatRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
: Array.isArray(b.addMemberIds) ? b.addMemberIds as string[] : []
|
||||
const removeMemberIds: string[] = Array.isArray(b.remove_member_ids) ? b.remove_member_ids as string[]
|
||||
: Array.isArray(b.removeMemberIds) ? b.removeMemberIds as string[] : []
|
||||
const transferOwnerTo = (b.transfer_owner_to ?? b.transferOwnerTo) as string | undefined
|
||||
|
||||
// Fetch current room to get created_by
|
||||
const roomRes = await db.query('SELECT created_by FROM chat_rooms WHERE id = $1', [roomId])
|
||||
const createdBy: string | null = roomRes.rows[0]?.created_by ?? null
|
||||
|
||||
// Block removal of the creator
|
||||
if (createdBy && removeMemberIds.includes(createdBy)) {
|
||||
return reply.code(403).send({ error: 'Cannot remove the group creator' })
|
||||
}
|
||||
|
||||
// Transfer ownership (only current owner or admin can do this)
|
||||
if (transferOwnerTo) {
|
||||
const userId = request.user.sub
|
||||
if (createdBy !== userId && request.user.role !== 'admin') {
|
||||
return reply.code(403).send({ error: 'Only the group creator can transfer ownership' })
|
||||
}
|
||||
await db.query('UPDATE chat_rooms SET created_by = $1 WHERE id = $2', [transferOwnerTo, roomId])
|
||||
}
|
||||
|
||||
if (name) {
|
||||
await db.query('UPDATE chat_rooms SET name = $1 WHERE id = $2', [name.trim(), roomId])
|
||||
|
||||
Reference in New Issue
Block a user