Files
goodgo-platform/apps/api/src/modules/notifications/presentation/controllers/notifications.controller.ts
Ho Ngoc Hai db7147a95d feat: add pricing checkout flow, MFA type fixes, and Wave 13 audit docs
- Pricing page: enhanced with checkout modal integration, plan
  comparison table, and subscription funnel
- Payment return page: new VNPay/MoMo callback handler
- Subscription components: new checkout-modal with payment method
  selection (VNPay, MoMo, ZaloPay)
- API modules: type-safe PII encryption, improved error handling in
  MFA/auth/payments/analytics/search/notifications modules
- Audit docs: comprehensive Wave 13 platform assessment, pricing
  audit, production readiness checklist
- Updated PROJECT_TRACKER with Wave 13 status

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-12 20:17:11 +07:00

128 lines
4.4 KiB
TypeScript

import {
Controller,
Get,
Put,
Patch,
Body,
Param,
Query,
UseGuards,
Inject,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery, ApiProperty } from '@nestjs/swagger';
import { NotificationChannel as PrismaChannel } from '@prisma/client';
import { IsBoolean, IsEnum, IsString } from 'class-validator';
import { CurrentUser, type JwtPayload } from '@modules/auth';
import {
NOTIFICATION_REPOSITORY,
type INotificationRepository,
NOTIFICATION_PREFERENCE_REPOSITORY,
type INotificationPreferenceRepository,
} from '../../domain';
import { TemplateService } from '../../infrastructure/services/template.service';
class UpdatePreferenceDto {
@ApiProperty({ enum: PrismaChannel, description: 'Notification channel' })
@IsEnum(PrismaChannel)
channel!: PrismaChannel;
@ApiProperty({ description: 'Event type identifier' })
@IsString()
eventType!: string;
@ApiProperty({ description: 'Whether the preference is enabled' })
@IsBoolean()
enabled!: boolean;
}
@ApiTags('notifications')
@ApiBearerAuth('JWT')
@Controller('notifications')
@UseGuards(AuthGuard('jwt'))
export class NotificationsController {
constructor(
@Inject(NOTIFICATION_REPOSITORY)
private readonly notificationRepo: INotificationRepository,
@Inject(NOTIFICATION_PREFERENCE_REPOSITORY)
private readonly preferenceRepo: INotificationPreferenceRepository,
private readonly templateService: TemplateService,
) {}
@Get('history')
@ApiOperation({ summary: 'Get notification history' })
@ApiResponse({ status: 200, description: 'Notification history retrieved' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
@ApiQuery({ name: 'limit', required: false, type: Number })
async getHistory(
@CurrentUser() user: JwtPayload,
@Query('limit') limit?: number,
) {
return this.notificationRepo.findByUserId(user.sub, limit ?? 50);
}
@Get('preferences')
@ApiOperation({ summary: 'Get notification preferences' })
@ApiResponse({ status: 200, description: 'Preferences retrieved' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async getPreferences(@CurrentUser() user: JwtPayload) {
return this.preferenceRepo.findByUserId(user.sub);
}
@Put('preferences')
@ApiOperation({ summary: 'Update a notification preference' })
@ApiResponse({ status: 200, description: 'Preference updated' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async updatePreference(
@CurrentUser() user: JwtPayload,
@Body() dto: UpdatePreferenceDto,
) {
return this.preferenceRepo.upsert(user.sub, dto.channel, dto.eventType, dto.enabled);
}
@Get('unread')
@ApiOperation({ summary: 'Get unread notifications' })
@ApiResponse({ status: 200, description: 'Unread notifications retrieved' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
@ApiQuery({ name: 'limit', required: false, type: Number })
async getUnread(
@CurrentUser() user: JwtPayload,
@Query('limit') limit?: number,
) {
const [notifications, count] = await Promise.all([
this.notificationRepo.findUnreadByUserId(user.sub, limit ?? 50),
this.notificationRepo.countUnreadByUserId(user.sub),
]);
return { notifications, unreadCount: count };
}
@Patch(':id/read')
@ApiOperation({ summary: 'Mark a notification as read' })
@ApiResponse({ status: 200, description: 'Notification marked as read' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async markAsRead(
@CurrentUser() user: JwtPayload,
@Param('id') id: string,
) {
await this.notificationRepo.markAsRead(id, user.sub);
return { success: true };
}
@Patch('read-all')
@ApiOperation({ summary: 'Mark all notifications as read' })
@ApiResponse({ status: 200, description: 'All notifications marked as read' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async markAllAsRead(@CurrentUser() user: JwtPayload) {
const count = await this.notificationRepo.markAllAsRead(user.sub);
return { markedCount: count };
}
@Get('templates')
@ApiOperation({ summary: 'Get available notification templates' })
@ApiResponse({ status: 200, description: 'Templates retrieved' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
async getTemplates() {
return { templates: this.templateService.getTemplateKeys() };
}
}