add features
This commit is contained in:
131
backend/src/services/notification.service.ts
Normal file
131
backend/src/services/notification.service.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
// =============================================================================
|
||||
// Notification Service
|
||||
// =============================================================================
|
||||
|
||||
import pool from '../config/database';
|
||||
import logger from '../utils/logger';
|
||||
import { Notification, CreateNotificationData } from '../models/notification.model';
|
||||
|
||||
function rowToNotification(row: any): Notification {
|
||||
return {
|
||||
id: row.id,
|
||||
user_id: row.user_id,
|
||||
typ: row.typ,
|
||||
titel: row.titel,
|
||||
nachricht: row.nachricht,
|
||||
schwere: row.schwere,
|
||||
gelesen: row.gelesen,
|
||||
gelesen_am: row.gelesen_am ? new Date(row.gelesen_am) : null,
|
||||
link: row.link ?? null,
|
||||
quell_id: row.quell_id ?? null,
|
||||
quell_typ: row.quell_typ ?? null,
|
||||
erstellt_am: new Date(row.erstellt_am),
|
||||
};
|
||||
}
|
||||
|
||||
class NotificationService {
|
||||
/** Returns all notifications for a user (newest first, max 100). */
|
||||
async getByUser(userId: string): Promise<Notification[]> {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT * FROM notifications WHERE user_id = $1 ORDER BY erstellt_am DESC LIMIT 100`,
|
||||
[userId]
|
||||
);
|
||||
return result.rows.map(rowToNotification);
|
||||
} catch (error) {
|
||||
logger.error('NotificationService.getByUser failed', { error, userId });
|
||||
throw new Error('Notifications konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the count of unread notifications for a user. */
|
||||
async getUnreadCount(userId: string): Promise<number> {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT COUNT(*) AS cnt FROM notifications WHERE user_id = $1 AND gelesen = FALSE`,
|
||||
[userId]
|
||||
);
|
||||
return parseInt(result.rows[0].cnt, 10);
|
||||
} catch (error) {
|
||||
logger.error('NotificationService.getUnreadCount failed', { error, userId });
|
||||
throw new Error('Ungelesene Notifications konnten nicht gezählt werden');
|
||||
}
|
||||
}
|
||||
|
||||
/** Marks a single notification as read. Returns false if not found or not owned by user. */
|
||||
async markAsRead(id: string, userId: string): Promise<boolean> {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`UPDATE notifications
|
||||
SET gelesen = TRUE, gelesen_am = NOW()
|
||||
WHERE id = $1 AND user_id = $2 AND gelesen = FALSE
|
||||
RETURNING id`,
|
||||
[id, userId]
|
||||
);
|
||||
return (result.rowCount ?? 0) > 0;
|
||||
} catch (error) {
|
||||
logger.error('NotificationService.markAsRead failed', { error, id, userId });
|
||||
throw new Error('Notification konnte nicht als gelesen markiert werden');
|
||||
}
|
||||
}
|
||||
|
||||
/** Marks all notifications as read for a user. */
|
||||
async markAllRead(userId: string): Promise<void> {
|
||||
try {
|
||||
await pool.query(
|
||||
`UPDATE notifications SET gelesen = TRUE, gelesen_am = NOW()
|
||||
WHERE user_id = $1 AND gelesen = FALSE`,
|
||||
[userId]
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('NotificationService.markAllRead failed', { error, userId });
|
||||
throw new Error('Notifications konnten nicht als gelesen markiert werden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a notification. If quell_typ + quell_id are provided the insert is
|
||||
* silently ignored when an identical unread notification already exists
|
||||
* (dedup via unique index).
|
||||
*/
|
||||
async createNotification(data: CreateNotificationData): Promise<void> {
|
||||
try {
|
||||
await pool.query(
|
||||
`INSERT INTO notifications
|
||||
(user_id, typ, titel, nachricht, schwere, link, quell_id, quell_typ)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (user_id, quell_typ, quell_id) WHERE NOT gelesen AND quell_typ IS NOT NULL AND quell_id IS NOT NULL
|
||||
DO NOTHING`,
|
||||
[
|
||||
data.user_id,
|
||||
data.typ,
|
||||
data.titel,
|
||||
data.nachricht,
|
||||
data.schwere ?? 'info',
|
||||
data.link ?? null,
|
||||
data.quell_id ?? null,
|
||||
data.quell_typ ?? null,
|
||||
]
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('NotificationService.createNotification failed', { error });
|
||||
// Non-fatal — don't propagate to callers
|
||||
}
|
||||
}
|
||||
|
||||
/** Deletes read notifications older than 90 days for all users. */
|
||||
async deleteOldRead(): Promise<void> {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`DELETE FROM notifications WHERE gelesen = TRUE AND gelesen_am < NOW() - INTERVAL '90 days'`
|
||||
);
|
||||
if ((result.rowCount ?? 0) > 0) {
|
||||
logger.info(`NotificationService.deleteOldRead: removed ${result.rowCount} old notifications`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('NotificationService.deleteOldRead failed', { error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new NotificationService();
|
||||
Reference in New Issue
Block a user