import { storage } from "../storage"; import { authenticateToken, type AuthenticatedRequest } from "../middleware/auth.middleware"; export function registerPushRoutes(app: import("express").Express): void { // ===================== // Device Tokens for Push Notifications // ===================== /** * @swagger * /api/devices/register: * post: * tags: [Devices] * summary: Зарегистрировать устройство для push-уведомлений * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [token, platform] * properties: * token: * type: string * description: Pushy device token * platform: * type: string * enum: [android, ios] * responses: * 200: * description: Устройство зарегистрировано * /api/devices/unregister: * post: * tags: [Devices] * summary: Удалить устройство из push-уведомлений * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [token] * properties: * token: * type: string * responses: * 200: * description: Удалено */ // Register a device token app.post('/api/devices/register', authenticateToken, async (req: AuthenticatedRequest, res) => { try { const { token, platform, deviceInfo } = req.body; if (!token || !platform) { return res.status(400).json({ error: 'Токен и платформа обязательны' }); } if (!['android', 'ios', 'web'].includes(platform)) { return res.status(400).json({ error: 'Неверная платформа. Допустимые значения: android, ios, web' }); } const userId = req.user!.userId; const organizationId = req.user!.organizationId; await storage.registerDeviceToken(userId, organizationId, token, platform, deviceInfo); await storage.updateUserPresence(userId, organizationId, true); res.json({ success: true }); } catch (error) { console.error('Register device token error:', error); res.status(500).json({ error: 'Ошибка регистрации устройства' }); } }); // Unregister a device token app.post('/api/devices/unregister', authenticateToken, async (req: AuthenticatedRequest, res) => { try { const { token } = req.body; if (!token) { return res.status(400).json({ error: 'Токен обязателен' }); } const userId = req.user!.userId; const organizationId = req.user!.organizationId; await storage.unregisterDeviceToken(userId, organizationId, token); res.json({ success: true }); } catch (error) { console.error('Unregister device token error:', error); res.status(500).json({ error: 'Ошибка удаления токена устройства' }); } }); // Heartbeat endpoint to update presence app.post('/api/devices/heartbeat', authenticateToken, async (req: AuthenticatedRequest, res) => { try { const userId = req.user!.userId; const organizationId = req.user!.organizationId; await storage.heartbeatUserPresence(userId, organizationId); res.json({ success: true }); } catch (error) { console.error('Heartbeat error:', error); res.status(500).json({ error: 'Ошибка обновления статуса' }); } }); // Get user's registered devices app.get('/api/devices', authenticateToken, async (req: AuthenticatedRequest, res) => { try { const userId = req.user!.userId; const organizationId = req.user!.organizationId; const tokens = await storage.getDeviceTokensByUser(userId, organizationId); res.json({ success: true, devices: tokens.map(t => ({ id: t.id, platform: t.platform, lastActiveAt: t.lastActiveAt, deviceInfo: t.deviceInfo, }))}); } catch (error) { console.error('Get devices error:', error); res.status(500).json({ error: 'Ошибка получения списка устройств' }); } }); /** * @swagger * /api/web-push/vapid-key: * get: * tags: [Devices] * summary: Получить VAPID публичный ключ для Web Push * description: Возвращает VAPID публичный ключ, необходимый для создания подписки Web Push на стороне клиента (PushManager.subscribe). * responses: * 200: * description: VAPID публичный ключ * content: * application/json: * schema: * type: object * properties: * vapidPublicKey: * type: string * description: Base64url-encoded VAPID публичный ключ * /api/web-push/subscribe: * post: * tags: [Devices] * summary: Подписать браузер на Web Push уведомления * description: Регистрирует Web Push подписку текущего пользователя. Subscription-объект получается из `PushManager.subscribe()` браузерного API. Требует авторизации. * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [subscription] * properties: * subscription: * type: object * description: Объект подписки из PushManager.subscribe() * required: [endpoint] * properties: * endpoint: * type: string * description: URL эндпоинта push-сервиса браузера * keys: * type: object * properties: * p256dh: * type: string * description: Публичный ключ шифрования * auth: * type: string * description: Секрет аутентификации * responses: * 200: * description: Подписка зарегистрирована * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * 400: * description: Subscription обязательна * 401: * description: Не авторизован * /api/web-push/unsubscribe: * post: * tags: [Devices] * summary: Отписать браузер от Web Push уведомлений * description: Удаляет Web Push подписку по endpoint. Требует авторизации. * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [endpoint] * properties: * endpoint: * type: string * description: URL эндпоинта push-сервиса браузера (из subscription.endpoint) * responses: * 200: * description: Подписка удалена * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * 400: * description: Endpoint обязателен * 401: * description: Не авторизован */ // Web Push - Get VAPID public key app.get('/api/web-push/vapid-key', async (_req, res) => { try { const { webPushService } = await import('../services/web-push.service'); res.json({ vapidPublicKey: webPushService.getVapidPublicKey() }); } catch (error) { console.error('Get VAPID key error:', error); res.status(500).json({ error: 'Ошибка получения VAPID ключа' }); } }); // Web Push - Register subscription app.post('/api/devices/register-web-push', authenticateToken, async (req: AuthenticatedRequest, res) => { try { const { subscription } = req.body; if (!subscription || !subscription.endpoint) { return res.status(400).json({ error: 'Subscription обязательна' }); } const userId = req.user!.id; const organizationId = req.user!.organizationId; const { webPushService } = await import('../services/web-push.service'); await webPushService.registerSubscription(userId, organizationId, subscription); res.json({ success: true }); } catch (error) { console.error('Register web push error:', error); res.status(500).json({ error: 'Ошибка регистрации web push подписки' }); } }); // Web Push - Unregister subscription app.post('/api/devices/unregister-web-push', authenticateToken, async (req: AuthenticatedRequest, res) => { try { const { endpoint } = req.body; if (!endpoint) { return res.status(400).json({ error: 'Endpoint обязателен' }); } const { webPushService } = await import('../services/web-push.service'); await webPushService.unregisterSubscription(endpoint); res.json({ success: true }); } catch (error) { console.error('Unregister web push error:', error); res.status(500).json({ error: 'Ошибка удаления web push подписки' }); } }); // Web Push - Subscribe (canonical path) app.post('/api/web-push/subscribe', authenticateToken, async (req: AuthenticatedRequest, res) => { try { const { subscription } = req.body; if (!subscription || !subscription.endpoint) { return res.status(400).json({ error: 'Subscription обязательна' }); } const userId = req.user!.id; const organizationId = req.user!.organizationId; const { webPushService } = await import('../services/web-push.service'); await webPushService.registerSubscription(userId, organizationId, subscription); res.json({ success: true }); } catch (error) { console.error('Web push subscribe error:', error); res.status(500).json({ error: 'Ошибка регистрации web push подписки' }); } }); // Web Push - Unsubscribe (canonical path) app.post('/api/web-push/unsubscribe', authenticateToken, async (req: AuthenticatedRequest, res) => { try { const { endpoint } = req.body; if (!endpoint) { return res.status(400).json({ error: 'Endpoint обязателен' }); } const { webPushService } = await import('../services/web-push.service'); await webPushService.unregisterSubscription(endpoint); res.json({ success: true }); } catch (error) { console.error('Web push unsubscribe error:', error); res.status(500).json({ error: 'Ошибка удаления web push подписки' }); } }); }