74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
import {
|
|
Controller,
|
|
Get,
|
|
Post,
|
|
Body,
|
|
Query,
|
|
Headers,
|
|
UnauthorizedException,
|
|
ParseIntPipe,
|
|
DefaultValuePipe,
|
|
} from '@nestjs/common';
|
|
import { NotificationRepository } from '../../../infrastructure/repositories/notification.repository';
|
|
import * as jwt from 'jsonwebtoken';
|
|
|
|
/**
|
|
* User-facing notification endpoints.
|
|
* Kong enforces JWT — we extract tenantId/userId from Authorization header.
|
|
*/
|
|
@Controller('api/v1/notifications')
|
|
export class NotificationUserController {
|
|
constructor(private readonly repo: NotificationRepository) {}
|
|
|
|
/** GET /api/v1/notifications/me — notifications visible to this user */
|
|
@Get('me')
|
|
async getMyNotifications(
|
|
@Headers('authorization') auth: string,
|
|
@Query('limit', new DefaultValuePipe(50), ParseIntPipe) limit: number,
|
|
@Query('offset', new DefaultValuePipe(0), ParseIntPipe) offset: number,
|
|
) {
|
|
const { tenantId, userId } = this.extractJwt(auth);
|
|
return this.repo.findForUser({ tenantId, userId, limit: Math.min(limit, 100), offset });
|
|
}
|
|
|
|
/** GET /api/v1/notifications/me/unread-count */
|
|
@Get('me/unread-count')
|
|
async getUnreadCount(@Headers('authorization') auth: string) {
|
|
const { tenantId, userId } = this.extractJwt(auth);
|
|
const count = await this.repo.getUnreadCount(tenantId, userId);
|
|
return { count };
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/notifications/me/mark-read
|
|
* Body: { notificationId?: string } — omit to mark ALL as read
|
|
*/
|
|
@Post('me/mark-read')
|
|
async markRead(
|
|
@Headers('authorization') auth: string,
|
|
@Body() body: { notificationId?: string },
|
|
) {
|
|
const { tenantId, userId } = this.extractJwt(auth);
|
|
if (body?.notificationId) {
|
|
await this.repo.markRead(body.notificationId, userId, tenantId);
|
|
} else {
|
|
await this.repo.markAllRead(tenantId, userId);
|
|
}
|
|
return { ok: true };
|
|
}
|
|
|
|
private extractJwt(auth: string): { tenantId: string; userId: string } {
|
|
if (!auth?.startsWith('Bearer ')) {
|
|
throw new UnauthorizedException('Missing authorization header');
|
|
}
|
|
const token = auth.slice(7);
|
|
const secret = process.env.JWT_SECRET || 'dev-secret';
|
|
try {
|
|
const payload = jwt.verify(token, secret) as any;
|
|
return { tenantId: payload.tenantId, userId: payload.sub };
|
|
} catch {
|
|
throw new UnauthorizedException('Invalid JWT');
|
|
}
|
|
}
|
|
}
|