Files
hotelsync/backend/src/app.ts
HotelSync 512e3c6659 feat: full role permissions system — new modules, API persistence, sidebar context
- Backend: migration 080_role_permissions table (hotel-scoped, upsert)
- Backend: routes GET/PUT/DELETE /api/hotels/:slug/role-permissions
- Frontend: RolePermissionsContext — loads saved perms from API, provides can()
- Frontend: Sidebar uses context can() instead of hardcoded ROLE_PERMS
- Frontend: fixed module ID→permKey mapping (room-service, olap-reports, website-builder)
- Frontend: Documents page added to Управление nav (was missing)
- Frontend: equipment/wifi/ttlock get own permission keys (not bundled under 'settings')
- Frontend: floor_map gets own permission key (not bundled under 'rooms')
- Frontend: new module group 'Технологии': wifi, equipment, ttlock, floor_map
- Frontend: 'schedule' added to Администрирование module group
- Updated INITIAL_ROLE_PERMISSIONS: receptionist+availability+reports+documents+website, accountant+documents, technician+floor_map+equipment+ttlock

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:16:15 +03:00

159 lines
7.1 KiB
TypeScript

import Fastify, { FastifyRequest, FastifyReply } from 'fastify'
import jwt from '@fastify/jwt'
import cookie from '@fastify/cookie'
import cors from '@fastify/cors'
import helmet from '@fastify/helmet'
import rateLimit from '@fastify/rate-limit'
import multipart from '@fastify/multipart'
import staticFiles from '@fastify/static'
import { join } from 'path'
import { config } from './config'
import './types' // side-effect: augments fastify types
import fastifyWebsocket from '@fastify/websocket'
import wsRoutes from './routes/ws'
import authRoutes from './routes/auth'
import hotelsRoutes from './routes/hotels'
import roomsRoutes from './routes/rooms'
import bookingsRoutes from './routes/bookings'
import housekeepingRoutes from './routes/housekeeping'
import channelsRoutes from './routes/channels'
import usersRoutes from './routes/users'
import netupRoutes from './routes/netup'
import guestsRoutes from './routes/guests'
import bookingGuestsRoutes from './routes/booking-guests'
import hotelSettingsRoutes from './routes/hotel-settings'
import rentalRoutes from './routes/rental'
import categoriesRoutes from './routes/categories'
import tariffsRoutes from './routes/tariffs'
import ratePeriodsRoutes from './routes/rate-periods'
import rateOverridesRoutes from './routes/rate-overrides'
import uploadRoutes from './routes/upload'
import housekeepingSettingsRoutes from './routes/housekeeping-settings'
import notificationsRoutes from './routes/notifications'
import scheduleRoutes from './routes/schedule'
import loyaltyRoutes from './routes/loyalty'
import chatRoutes from './routes/chat'
import pushRoutes from './routes/push'
import workstationRoutes from './routes/workstations'
import agentReleaseRoutes from './routes/agent-release'
import wifiSettingsRoutes, { wifiAuthVerify } from './routes/wifi-settings'
import ttlockRoutes from './routes/ttlock'
import checklistsRoutes from './routes/checklists'
import minibarRoutes from './routes/minibar'
import depositRoutes from './routes/deposit'
import paymentsRoutes from './routes/payments'
import paymentMethodsRoutes from './routes/paymentMethods'
import paymentGatewaysRoutes from './routes/paymentGateways'
import publicWidgetRoutes from './routes/publicWidget'
import yookassaWebhookRoutes from './routes/yookassaWebhook'
import rolePermissionsRoutes from './routes/role-permissions'
import { setupAgentWsRoute } from './agent-ws'
import { startJobs } from './jobs'
import { initWebPush } from './push'
export async function buildApp() {
const fastify = Fastify({
logger: {
level: config.nodeEnv === 'production' ? 'warn' : 'info',
...(config.nodeEnv !== 'production' && {
transport: { target: 'pino-pretty', options: { colorize: true } },
}),
},
bodyLimit: 20 * 1024 * 1024, // 20MB — for base64 photo uploads
trustProxy: true, // get real IP from X-Real-IP / X-Forwarded-For via nginx
})
// ── File uploads & static ─────────────────────────────────────────────────
await fastify.register(multipart)
const uploadsDir = process.env.UPLOADS_DIR ?? join(process.cwd(), 'uploads')
await fastify.register(staticFiles, { root: uploadsDir, prefix: '/uploads/' })
const agentUpdatesDir = process.env.AGENT_UPDATES_DIR ?? join(process.cwd(), '..', 'agent-updates')
await fastify.register(staticFiles, { root: agentUpdatesDir, prefix: '/agent-updates/', decorateReply: false })
// ── WebSocket ──────────────────────────────────────────────────────────────
await fastify.register(fastifyWebsocket)
// ── Security ───────────────────────────────────────────────────────────────
await fastify.register(helmet, { contentSecurityPolicy: false, crossOriginResourcePolicy: { policy: 'cross-origin' } })
await fastify.register(cors, {
origin: config.cors.origins,
credentials: true,
methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'],
})
await fastify.register(rateLimit, {
max: 200,
timeWindow: '1 minute',
})
// ── Cookies & JWT ──────────────────────────────────────────────────────────
await fastify.register(cookie)
await fastify.register(jwt, {
secret: config.jwt.secret,
})
// Authenticate decorator used as onRequest hook in routes
fastify.decorate(
'authenticate',
async (request: FastifyRequest, reply: FastifyReply) => {
try {
await request.jwtVerify()
} catch (err) {
reply.code(401).send({ error: 'Unauthorized' })
}
},
)
// ── Health check ───────────────────────────────────────────────────────────
fastify.get('/health', async () => ({ status: 'ok', ts: new Date().toISOString() }))
// ── Routes ─────────────────────────────────────────────────────────────────
await fastify.register(wsRoutes)
await fastify.register(authRoutes)
await fastify.register(hotelsRoutes)
await fastify.register(roomsRoutes)
await fastify.register(bookingsRoutes)
await fastify.register(housekeepingRoutes)
await fastify.register(channelsRoutes)
await fastify.register(usersRoutes)
await fastify.register(netupRoutes)
await fastify.register(guestsRoutes)
await fastify.register(bookingGuestsRoutes)
await fastify.register(hotelSettingsRoutes)
await fastify.register(rentalRoutes)
await fastify.register(categoriesRoutes)
await fastify.register(tariffsRoutes)
await fastify.register(ratePeriodsRoutes)
await fastify.register(rateOverridesRoutes)
await fastify.register(uploadRoutes)
await fastify.register(housekeepingSettingsRoutes)
await fastify.register(notificationsRoutes)
await fastify.register(scheduleRoutes)
await fastify.register(loyaltyRoutes)
await fastify.register(chatRoutes)
await fastify.register(pushRoutes)
await fastify.register(workstationRoutes)
await fastify.register(agentReleaseRoutes)
await fastify.register(wifiSettingsRoutes)
await fastify.register(wifiAuthVerify)
await fastify.register(ttlockRoutes)
await fastify.register(checklistsRoutes)
await fastify.register(minibarRoutes)
await fastify.register(depositRoutes)
await fastify.register(paymentsRoutes)
await fastify.register(paymentMethodsRoutes)
await fastify.register(paymentGatewaysRoutes)
await fastify.register(publicWidgetRoutes)
await fastify.register(yookassaWebhookRoutes)
await fastify.register(rolePermissionsRoutes)
await fastify.register(setupAgentWsRoute)
startJobs()
initWebPush()
return fastify
}