- Rename pull endpoint /travelline → /netup-pms (cleaner branding) - Add GET/DELETE /api/hotels/:slug/netup/log endpoints - TvWelcomePage: new 'Диагностика' tab shows incoming NetUP requests with method, URL, timestamp list + detail panel (headers, query, body) - Remove 'TravelLine' wording from UI, replace with 'Pull-интеграция' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
116 lines
3.8 KiB
TypeScript
116 lines
3.8 KiB
TypeScript
/**
|
|
* NetUP PMS pull-integration endpoint.
|
|
*
|
|
* NetUP polls this URL every N minutes (configured as "TravelLine" integration type in NetUP).
|
|
* We log every request so we can see exactly what NetUP sends, and respond with active bookings.
|
|
*
|
|
* Configure in NetUP:
|
|
* Integration type: TravelLine
|
|
* API URL: https://api.hotelsync.ru/netup-pms
|
|
* Token: <tl_token from netup_settings>
|
|
*/
|
|
|
|
import type { FastifyPluginAsync } from 'fastify'
|
|
import { db } from '../db'
|
|
|
|
// In-memory ring buffer — last 100 captured requests (shared with netup.ts via export)
|
|
const MAX_CAPTURE = 100
|
|
export const captured: {
|
|
ts: string
|
|
method: string
|
|
url: string
|
|
headers: Record<string, string | string[] | undefined>
|
|
query: Record<string, unknown>
|
|
body: unknown
|
|
}[] = []
|
|
|
|
function recordRequest(request: {
|
|
method: string
|
|
url: string
|
|
headers: Record<string, string | string[] | undefined>
|
|
query: unknown
|
|
body: unknown
|
|
}) {
|
|
captured.push({
|
|
ts: new Date().toISOString(),
|
|
method: request.method,
|
|
url: request.url,
|
|
headers: request.headers,
|
|
query: request.query as Record<string, unknown>,
|
|
body: request.body,
|
|
})
|
|
if (captured.length > MAX_CAPTURE) captured.shift()
|
|
}
|
|
|
|
const netupPms: FastifyPluginAsync = async (fastify) => {
|
|
|
|
// Catch /netup-pms and /netup-pms/* — everything NetUP might call
|
|
for (const pattern of ['/netup-pms', '/netup-pms/*']) {
|
|
fastify.all(pattern, async (request, reply) => {
|
|
recordRequest(request as Parameters<typeof recordRequest>[0])
|
|
fastify.log.info({ method: request.method, url: request.url }, 'NetUP PMS poll')
|
|
|
|
const token = extractToken(request)
|
|
if (!token) return reply.code(401).send({ error: 'Unauthorized' })
|
|
|
|
const { rows: [row] } = await db.query(
|
|
`SELECT hs.hotel_id FROM netup_settings hs WHERE hs.tl_token = $1`,
|
|
[token],
|
|
).catch(() => ({ rows: [] as { hotel_id: string }[] }))
|
|
|
|
if (!row) return reply.send(buildEmptyResponse())
|
|
|
|
return reply.send(await buildResponse(row.hotel_id))
|
|
})
|
|
}
|
|
}
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
function extractToken(request: { headers: Record<string, string | string[] | undefined>; query: unknown }): string | null {
|
|
const auth = request.headers['authorization'] as string | undefined
|
|
if (auth?.startsWith('Bearer ')) return auth.slice(7)
|
|
if (auth?.startsWith('Token ')) return auth.slice(6)
|
|
const q = request.query as Record<string, string>
|
|
return q?.token ?? q?.api_key ?? null
|
|
}
|
|
|
|
function buildEmptyResponse() {
|
|
return { success: true, reservations: [], rooms: [] }
|
|
}
|
|
|
|
async function buildResponse(hotelId: string) {
|
|
const { rows: bookings } = await db.query(
|
|
`SELECT b.id, b.guest_name, b.guest_email, b.guest_phone,
|
|
b.check_in, b.check_out, b.status, b.adults, b.children,
|
|
r.number as room_number, r.id as room_id,
|
|
m.netup_room_number
|
|
FROM bookings b
|
|
JOIN rooms r ON r.id = b.room_id
|
|
LEFT JOIN netup_room_mapping m ON m.pms_room_id = r.id AND m.hotel_id = $1
|
|
WHERE b.hotel_id = $1
|
|
AND b.status IN ('confirmed','checked_in')
|
|
AND b.check_out >= CURRENT_DATE
|
|
ORDER BY b.check_in`,
|
|
[hotelId],
|
|
)
|
|
|
|
const reservations = bookings.map((b: Record<string, unknown>) => ({
|
|
id: b.id,
|
|
status: b.status === 'checked_in' ? 'CheckedIn' : 'Confirmed',
|
|
guestName: b.guest_name,
|
|
guestEmail: b.guest_email ?? '',
|
|
guestPhone: b.guest_phone ?? '',
|
|
roomNumber: b.netup_room_number ?? b.room_number,
|
|
pmsRoomId: b.room_id,
|
|
checkIn: b.check_in,
|
|
checkOut: b.check_out,
|
|
adults: b.adults,
|
|
children: b.children ?? 0,
|
|
}))
|
|
|
|
return { success: true, reservations }
|
|
}
|
|
|
|
export default netupPms
|