- /profile теперь обёртка MyProfilePage над UserProfilePage (один экран для своего и чужого профиля) - self-вкладки из ProfileSettings перенесены: Уведомления, Offline, Интерфейс, Безопасность (components/profile/*) - ProfileSettings.tsx удалён - админ видит вкладки Уведомления и Offline на чужой карточке с данными целевого пользователя (userId-параметр в GET/POST /api/subscriptions, PATCH/DELETE по владельцу, GET /api/sync/config?userId=; проверка canManageUser) - Роль/Активен/Статус disabled для не-админа (сервер их и раньше игнорировал) - пункт меню «Настройки профиля» переименован в «Мой профиль»
224 lines
10 KiB
TypeScript
224 lines
10 KiB
TypeScript
import { Router } from "express";
|
||
import { storage } from "../storage";
|
||
import { authenticateToken, type AuthenticatedRequest } from "../middleware/auth.middleware";
|
||
import { tenantIsolation } from "../middleware/tenant.middleware";
|
||
import { validateRequest } from "../middleware/validation.middleware";
|
||
import { createSubscriptionSchema, updateSubscriptionSchema } from "@shared/schema";
|
||
import { notificationService } from "../services/notification.service";
|
||
import { canManageUser } from "../utils/user-access";
|
||
|
||
export function registerChatNotificationRoutes(router: Router): void {
|
||
// Get user notifications
|
||
router.get('/api/notifications',
|
||
authenticateToken,
|
||
tenantIsolation,
|
||
async (req: AuthenticatedRequest, res) => {
|
||
try {
|
||
const { unread } = req.query;
|
||
const isReadFilter = unread === 'true' ? false : undefined;
|
||
const notifications = await storage.getUserNotifications(
|
||
req.user!.id, req.organizationId!, isReadFilter
|
||
);
|
||
res.json({ success: true, notifications });
|
||
} catch (error) {
|
||
console.error('Get notifications error:', error);
|
||
res.status(500).json({ success: false, error: 'Ошибка при загрузке уведомлений' });
|
||
}
|
||
}
|
||
);
|
||
|
||
// Mark notification as read
|
||
router.put('/api/notifications/:id/read',
|
||
authenticateToken,
|
||
tenantIsolation,
|
||
async (req: AuthenticatedRequest, res) => {
|
||
try {
|
||
const notificationId = parseInt(req.params.id);
|
||
if (isNaN(notificationId)) {
|
||
return res.status(400).json({ success: false, error: 'Некорректный ID уведомления' });
|
||
}
|
||
const updatedNotification = await storage.markNotificationAsRead(
|
||
notificationId, req.user!.id, req.organizationId!
|
||
);
|
||
res.json({ success: true, message: 'Уведомление отмечено как прочитанное', notification: updatedNotification });
|
||
} catch (error) {
|
||
console.error('Mark notification as read error:', error);
|
||
res.status(500).json({ success: false, error: 'Ошибка при обновлении уведомления' });
|
||
}
|
||
}
|
||
);
|
||
|
||
// Mark all notifications as read
|
||
router.put('/api/notifications/read-all',
|
||
authenticateToken,
|
||
tenantIsolation,
|
||
async (req: AuthenticatedRequest, res) => {
|
||
try {
|
||
await storage.markAllNotificationsAsRead(req.user!.id, req.organizationId!);
|
||
res.json({ success: true, message: 'Все уведомления отмечены как прочитанные' });
|
||
} catch (error) {
|
||
console.error('Mark all notifications as read error:', error);
|
||
res.status(500).json({ success: false, error: 'Ошибка при обновлении уведомлений' });
|
||
}
|
||
}
|
||
);
|
||
|
||
// Delete notification
|
||
router.delete('/api/notifications/:id',
|
||
authenticateToken,
|
||
tenantIsolation,
|
||
async (req: AuthenticatedRequest, res) => {
|
||
try {
|
||
const notificationId = parseInt(req.params.id);
|
||
if (isNaN(notificationId)) {
|
||
return res.status(400).json({ success: false, error: 'Некорректный ID уведомления' });
|
||
}
|
||
await storage.deleteNotification(notificationId, req.user!.id, req.organizationId!);
|
||
res.json({ success: true, message: 'Уведомление удалено' });
|
||
} catch (error) {
|
||
console.error('Delete notification error:', error);
|
||
res.status(500).json({ success: false, error: 'Ошибка при удалении уведомления' });
|
||
}
|
||
}
|
||
);
|
||
|
||
// Get all event types for subscription
|
||
router.get('/api/subscriptions/event-types',
|
||
authenticateToken,
|
||
tenantIsolation,
|
||
async (req: AuthenticatedRequest, res) => {
|
||
try {
|
||
const eventTypes = await notificationService.getEventTypes();
|
||
res.json({ success: true, eventTypes });
|
||
} catch (error) {
|
||
console.error('Get event types error:', error);
|
||
res.status(500).json({ success: false, error: 'Ошибка при загрузке типов событий' });
|
||
}
|
||
}
|
||
);
|
||
|
||
// Get user subscriptions
|
||
// ?userId=N — подписки другого пользователя (только при canManageUser: админ/руководитель)
|
||
router.get('/api/subscriptions',
|
||
authenticateToken,
|
||
tenantIsolation,
|
||
async (req: AuthenticatedRequest, res) => {
|
||
try {
|
||
let targetUserId = req.user!.id;
|
||
if (req.query.userId !== undefined) {
|
||
const queryUserId = parseInt(String(req.query.userId));
|
||
if (isNaN(queryUserId)) {
|
||
return res.status(400).json({ success: false, error: 'Некорректный ID пользователя' });
|
||
}
|
||
if (queryUserId !== req.user!.id) {
|
||
if (!(await canManageUser(req.user, queryUserId, req.organizationId!))) {
|
||
return res.status(403).json({ success: false, error: 'Нет прав на просмотр подписок этого пользователя' });
|
||
}
|
||
targetUserId = queryUserId;
|
||
}
|
||
}
|
||
const subscriptions = await notificationService.getUserSubscriptions(
|
||
targetUserId, req.organizationId!
|
||
);
|
||
res.json({ success: true, subscriptions });
|
||
} catch (error) {
|
||
console.error('Get subscriptions error:', error);
|
||
res.status(500).json({ success: false, error: 'Ошибка при загрузке подписок' });
|
||
}
|
||
}
|
||
);
|
||
|
||
// Create subscription
|
||
// body.userId — создать подписку другому пользователю (только при canManageUser)
|
||
router.post('/api/subscriptions',
|
||
authenticateToken,
|
||
tenantIsolation,
|
||
validateRequest(createSubscriptionSchema),
|
||
async (req: AuthenticatedRequest, res) => {
|
||
try {
|
||
let targetUserId = req.user!.id;
|
||
if (req.body.userId !== undefined && req.body.userId !== req.user!.id) {
|
||
if (!(await canManageUser(req.user, req.body.userId, req.organizationId!))) {
|
||
return res.status(403).json({ success: false, error: 'Нет прав на управление подписками этого пользователя' });
|
||
}
|
||
targetUserId = req.body.userId;
|
||
}
|
||
const subscription = await notificationService.createSubscription({
|
||
userId: targetUserId,
|
||
organizationId: req.organizationId!,
|
||
eventTypeCode: req.body.eventTypeCode,
|
||
targetType: req.body.targetType,
|
||
targetId: req.body.targetId,
|
||
channels: req.body.channels,
|
||
});
|
||
res.status(201).json({ success: true, message: 'Подписка создана', subscription });
|
||
} catch (error) {
|
||
console.error('Create subscription error:', error);
|
||
res.status(500).json({ success: false, error: 'Ошибка при создании подписки' });
|
||
}
|
||
}
|
||
);
|
||
|
||
// Update subscription
|
||
router.patch('/api/subscriptions/:id',
|
||
authenticateToken,
|
||
tenantIsolation,
|
||
validateRequest(updateSubscriptionSchema),
|
||
async (req: AuthenticatedRequest, res) => {
|
||
try {
|
||
const subscriptionId = parseInt(req.params.id);
|
||
if (isNaN(subscriptionId)) {
|
||
return res.status(400).json({ success: false, error: 'Некорректный ID подписки' });
|
||
}
|
||
const existing = await notificationService.getSubscriptionById(subscriptionId, req.organizationId!);
|
||
if (!existing) {
|
||
return res.status(404).json({ success: false, error: 'Подписка не найдена' });
|
||
}
|
||
// Владелец подписки или canManageUser над владельцем (админ/руководитель)
|
||
if (existing.userId !== req.user!.id &&
|
||
!(await canManageUser(req.user, existing.userId, req.organizationId!))) {
|
||
return res.status(403).json({ success: false, error: 'Нет прав на изменение этой подписки' });
|
||
}
|
||
const subscription = await notificationService.updateSubscription(
|
||
subscriptionId, existing.userId, req.organizationId!, req.body
|
||
);
|
||
if (!subscription) {
|
||
return res.status(404).json({ success: false, error: 'Подписка не найдена' });
|
||
}
|
||
res.json({ success: true, message: 'Подписка обновлена', subscription });
|
||
} catch (error) {
|
||
console.error('Update subscription error:', error);
|
||
res.status(500).json({ success: false, error: 'Ошибка при обновлении подписки' });
|
||
}
|
||
}
|
||
);
|
||
|
||
// Delete subscription
|
||
router.delete('/api/subscriptions/:id',
|
||
authenticateToken,
|
||
tenantIsolation,
|
||
async (req: AuthenticatedRequest, res) => {
|
||
try {
|
||
const subscriptionId = parseInt(req.params.id);
|
||
if (isNaN(subscriptionId)) {
|
||
return res.status(400).json({ success: false, error: 'Некорректный ID подписки' });
|
||
}
|
||
const existing = await notificationService.getSubscriptionById(subscriptionId, req.organizationId!);
|
||
if (!existing) {
|
||
return res.status(404).json({ success: false, error: 'Подписка не найдена' });
|
||
}
|
||
// Владелец подписки или canManageUser над владельцем (админ/руководитель)
|
||
if (existing.userId !== req.user!.id &&
|
||
!(await canManageUser(req.user, existing.userId, req.organizationId!))) {
|
||
return res.status(403).json({ success: false, error: 'Нет прав на удаление этой подписки' });
|
||
}
|
||
await notificationService.deleteSubscription(subscriptionId, existing.userId, req.organizationId!);
|
||
res.json({ success: true, message: 'Подписка удалена' });
|
||
} catch (error) {
|
||
console.error('Delete subscription error:', error);
|
||
res.status(500).json({ success: false, error: 'Ошибка при удалении подписки' });
|
||
}
|
||
}
|
||
);
|
||
}
|