fix: chat — VARCHAR(10) overflow for notifications type, skeleton loading

- Migration 072: widen type column to VARCHAR(20), add partial unique index for general rooms
- Backend: replace brittle ON CONFLICT with explicit SELECT-then-INSERT ensureRoom helper
- Frontend: show room skeletons (Общий чат + Уведомления) during load instead of spinner,
  hide '0 чатов' subtitle when list is empty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-14 12:03:35 +03:00
parent 3c4c40da44
commit d0dbf33701
3 changed files with 44 additions and 32 deletions

View File

@@ -0,0 +1,6 @@
-- Increase type column length to fit 'notifications'
ALTER TABLE chat_rooms ALTER COLUMN type TYPE VARCHAR(20);
-- Partial unique index for general room per hotel (needed for ON CONFLICT)
CREATE UNIQUE INDEX IF NOT EXISTS uniq_chat_rooms_general_hotel
ON chat_rooms(hotel_id) WHERE type = 'general';

View File

@@ -13,39 +13,29 @@ const chatRoutes: FastifyPluginAsync = async (fastify) => {
const canAccess = (userSlug: string | null, role: string, slug: string) =>
role === 'super_admin' || userSlug === slug
// Ensure general room exists for hotel
const ensureGeneralRoom = async (hotelId: string) => {
// Ensure a room of given type exists for hotel (idempotent)
const ensureRoom = async (hotelId: string, type: string, name: string) => {
const { rows: existing } = await db.query(
`SELECT id FROM chat_rooms WHERE hotel_id = $1 AND type = $2 LIMIT 1`,
[hotelId, type],
)
if (existing[0]) return existing[0].id as string
const { rows } = await db.query(
`INSERT INTO chat_rooms (hotel_id, type, name)
VALUES ($1, 'general', 'Общий чат')
ON CONFLICT (hotel_id) WHERE type = 'general' DO NOTHING
RETURNING id`,
[hotelId],
`INSERT INTO chat_rooms (hotel_id, type, name) VALUES ($1, $2, $3)
ON CONFLICT DO NOTHING RETURNING id`,
[hotelId, type, name],
)
if (rows[0]) return rows[0].id as string
const { rows: existing } = await db.query(
`SELECT id FROM chat_rooms WHERE hotel_id = $1 AND type = 'general'`,
[hotelId],
// Concurrent insert — fetch again
const { rows: r2 } = await db.query(
`SELECT id FROM chat_rooms WHERE hotel_id = $1 AND type = $2 LIMIT 1`,
[hotelId, type],
)
return existing[0]?.id as string
return r2[0]?.id as string
}
// Ensure notifications room exists for hotel
const ensureNotificationsRoom = async (hotelId: string) => {
const { rows } = await db.query(
`INSERT INTO chat_rooms (hotel_id, type, name)
VALUES ($1, 'notifications', 'Уведомления')
ON CONFLICT DO NOTHING
RETURNING id`,
[hotelId],
)
if (rows[0]) return rows[0].id as string
const { rows: existing } = await db.query(
`SELECT id FROM chat_rooms WHERE hotel_id = $1 AND type = 'notifications'`,
[hotelId],
)
return existing[0]?.id as string
}
const ensureGeneralRoom = (hotelId: string) => ensureRoom(hotelId, 'general', 'Общий чат')
const ensureNotificationsRoom = (hotelId: string) => ensureRoom(hotelId, 'notifications', 'Уведомления')
// GET /api/hotels/:slug/chat/rooms — list rooms (general + notifications + directs for current user)
fastify.get<SlugParam>(