- Add Firebase client for FCM push notifications - Update notification service with push token management - Add push token registration/removal endpoints - Update all queries to use auth schema - Add comprehensive unit tests for notification.service - Add unit tests for distribution.job Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
293 lines
6.8 KiB
TypeScript
293 lines
6.8 KiB
TypeScript
/**
|
|
* Notification Controller
|
|
* Handles notification-related HTTP requests
|
|
*/
|
|
|
|
import { Response } from 'express';
|
|
import { AuthenticatedRequest } from '../../../core/guards/auth.guard';
|
|
import { notificationService } from '../services/notification.service';
|
|
import { logger } from '../../../shared/utils/logger';
|
|
|
|
/**
|
|
* GET /api/v1/notifications
|
|
* Get user's notifications
|
|
*/
|
|
export async function getNotifications(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.user!.id;
|
|
const { limit, offset, unreadOnly } = req.query;
|
|
|
|
const notifications = await notificationService.getUserNotifications(userId, {
|
|
limit: limit ? parseInt(limit as string, 10) : 50,
|
|
offset: offset ? parseInt(offset as string, 10) : 0,
|
|
unreadOnly: unreadOnly === 'true',
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: notifications,
|
|
});
|
|
} catch (error) {
|
|
logger.error('[NotificationController] Failed to get notifications:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get notifications',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/notifications/unread-count
|
|
* Get unread notification count
|
|
*/
|
|
export async function getUnreadCount(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.user!.id;
|
|
const count = await notificationService.getUnreadCount(userId);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { count },
|
|
});
|
|
} catch (error) {
|
|
logger.error('[NotificationController] Failed to get unread count:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get unread count',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PATCH /api/v1/notifications/:notificationId/read
|
|
* Mark notification as read
|
|
*/
|
|
export async function markAsRead(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.user!.id;
|
|
const { notificationId } = req.params;
|
|
|
|
const success = await notificationService.markAsRead(notificationId, userId);
|
|
|
|
if (!success) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'Notification not found',
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Notification marked as read',
|
|
});
|
|
} catch (error) {
|
|
logger.error('[NotificationController] Failed to mark as read:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to mark notification as read',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/notifications/read-all
|
|
* Mark all notifications as read
|
|
*/
|
|
export async function markAllAsRead(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.user!.id;
|
|
const count = await notificationService.markAllAsRead(userId);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { markedCount: count },
|
|
});
|
|
} catch (error) {
|
|
logger.error('[NotificationController] Failed to mark all as read:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to mark notifications as read',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /api/v1/notifications/:notificationId
|
|
* Delete a notification
|
|
*/
|
|
export async function deleteNotification(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.user!.id;
|
|
const { notificationId } = req.params;
|
|
|
|
const success = await notificationService.deleteNotification(notificationId, userId);
|
|
|
|
if (!success) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'Notification not found',
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Notification deleted',
|
|
});
|
|
} catch (error) {
|
|
logger.error('[NotificationController] Failed to delete notification:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to delete notification',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/notifications/preferences
|
|
* Get notification preferences
|
|
*/
|
|
export async function getPreferences(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.user!.id;
|
|
const preferences = await notificationService.getUserPreferences(userId);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: preferences,
|
|
});
|
|
} catch (error) {
|
|
logger.error('[NotificationController] Failed to get preferences:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get notification preferences',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PATCH /api/v1/notifications/preferences
|
|
* Update notification preferences
|
|
*/
|
|
export async function updatePreferences(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.user!.id;
|
|
const updates = req.body;
|
|
|
|
await notificationService.updateUserPreferences(userId, updates);
|
|
|
|
const preferences = await notificationService.getUserPreferences(userId);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: preferences,
|
|
});
|
|
} catch (error) {
|
|
logger.error('[NotificationController] Failed to update preferences:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to update notification preferences',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/notifications/push-token
|
|
* Register a push notification token
|
|
*/
|
|
export async function registerPushToken(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.user!.id;
|
|
const { token, platform, deviceInfo } = req.body;
|
|
|
|
if (!token || !platform) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Token and platform are required',
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!['web', 'ios', 'android'].includes(platform)) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid platform. Must be web, ios, or android',
|
|
});
|
|
return;
|
|
}
|
|
|
|
await notificationService.registerPushToken(userId, token, platform, deviceInfo);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Push token registered successfully',
|
|
});
|
|
} catch (error) {
|
|
logger.error('[NotificationController] Failed to register push token:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to register push token',
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /api/v1/notifications/push-token
|
|
* Remove a push notification token
|
|
*/
|
|
export async function removePushToken(
|
|
req: AuthenticatedRequest,
|
|
res: Response
|
|
): Promise<void> {
|
|
try {
|
|
const userId = req.user!.id;
|
|
const { token } = req.body;
|
|
|
|
if (!token) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Token is required',
|
|
});
|
|
return;
|
|
}
|
|
|
|
await notificationService.removePushToken(userId, token);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Push token removed successfully',
|
|
});
|
|
} catch (error) {
|
|
logger.error('[NotificationController] Failed to remove push token:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to remove push token',
|
|
});
|
|
}
|
|
}
|