import { Router } from "express"; import crypto from 'crypto'; import { storage } from "../storage"; import { authenticateToken, requirePermission, type AuthenticatedRequest } from "../middleware/auth.middleware"; import { tenantIsolation } from "../middleware/tenant.middleware"; import { validateRequest } from "../middleware/validation.middleware"; import { createInvitationSchema } from "@shared/schema"; import { emailService } from "../services/email.service"; export function registerInvitationRoutes(router: Router): void { // Get invitation info by token (public - no auth required) router.get('/api/invitations/:token', async (req, res) => { try { const { token } = req.params; const invitation = await storage.getInvitationByToken(token); if (!invitation) { return res.status(404).json({ success: false, error: 'Приглашение не найдено' }); } if (invitation.usedAt) { return res.status(400).json({ success: false, error: 'Приглашение уже использовано' }); } if (new Date(invitation.expiresAt) < new Date()) { return res.status(400).json({ success: false, error: 'Срок действия приглашения истёк' }); } const organization = await storage.getOrganization(invitation.organizationId); res.json({ success: true, invitation: { email: invitation.email, organizationName: organization?.displayName || organization?.name, } }); } catch (error) { console.error('Get invitation error:', error); res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' }); } }); // Create invitation (admin only) router.post('/api/invitations', authenticateToken, tenantIsolation, requirePermission('users.manage'), validateRequest(createInvitationSchema), async (req: AuthenticatedRequest, res) => { try { const { email } = req.body; const existingUser = await storage.getUserByEmail(email, req.organizationId!); if (existingUser) { return res.status(400).json({ success: false, error: 'Пользователь с таким email уже существует в организации' }); } const existingInvitations = await storage.getInvitationsByOrganization(req.organizationId!); const existingInvitation = existingInvitations.find( inv => inv.email === email && !inv.usedAt && new Date(inv.expiresAt) > new Date() ); if (existingInvitation) { return res.status(400).json({ success: false, error: 'Приглашение для этого email уже существует' }); } const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(); expiresAt.setDate(expiresAt.getDate() + 7); const invitation = await storage.createInvitation({ organizationId: req.organizationId!, email, token, createdBy: req.user!.id, expiresAt, }); const organization = await storage.getOrganization(req.organizationId!); const inviter = await storage.getUser(req.user!.id); const baseUrl = process.env.BASE_URL || 'https://iistwin.ru'; const inviteLink = `${baseUrl}/invite/${token}`; const inviterName = inviter ? `${inviter.firstName} ${inviter.middleName || ''} ${inviter.lastName}`.trim() : 'Администратор'; await emailService.sendInvitationLinkEmail( email, inviterName, organization?.displayName || organization?.name || 'Организация', inviteLink ); res.status(201).json({ success: true, invitation: { id: invitation.id, email: invitation.email, token: invitation.token, expiresAt: invitation.expiresAt, inviteLink } }); } catch (error) { console.error('Create invitation error:', error); res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' }); } } ); // Get all invitations for organization (admin only) router.get('/api/invitations', authenticateToken, tenantIsolation, requirePermission('users.manage'), async (req: AuthenticatedRequest, res) => { try { const invitationsData = await storage.getInvitationsByOrganization(req.organizationId!); const baseUrl = process.env.BASE_URL || 'https://iistwin.ru'; const invitations = invitationsData.map(inv => ({ id: inv.id, email: inv.email, token: inv.token, createdAt: inv.createdAt, expiresAt: inv.expiresAt, usedAt: inv.usedAt, inviteLink: `${baseUrl}/invite/${inv.token}`, isExpired: new Date(inv.expiresAt) < new Date(), isUsed: !!inv.usedAt })); res.json({ success: true, invitations }); } catch (error) { console.error('Get invitations error:', error); res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' }); } } ); // Delete invitation (admin only) router.delete('/api/invitations/:id', authenticateToken, tenantIsolation, requirePermission('users.manage'), async (req: AuthenticatedRequest, res) => { try { const id = parseInt(req.params.id); await storage.deleteInvitation(id, req.organizationId!); res.json({ success: true, message: 'Приглашение удалено' }); } catch (error) { console.error('Delete invitation error:', error); res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' }); } } ); }