feat(api): add price history, Stringee SMS, Zalo OA, WebSocket notifications, and feature-listing command
- Add PriceHistory model + migration, price-changed domain event, and event handler - Add GetPriceHistory query handler and controller endpoint - Implement StringeeSmsService and ZaloOaService with unit tests - Add Zalo ZNS templates for Vietnamese notification messages - Add WebSocket notification gateway for real-time push - Add FeatureListingCommand for promoted listings - Apply remaining consistent-type-imports lint fixes across API modules Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -13,14 +13,15 @@ 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, JwtPayload } from '@modules/auth';
|
||||
import { CurrentUser, type JwtPayload } from '@modules/auth';
|
||||
import {
|
||||
NOTIFICATION_REPOSITORY,
|
||||
INotificationRepository,
|
||||
type INotificationRepository,
|
||||
NOTIFICATION_PREFERENCE_REPOSITORY,
|
||||
INotificationPreferenceRepository,
|
||||
type INotificationPreferenceRepository,
|
||||
} from '../../domain';
|
||||
import { TemplateService } from '../../infrastructure/services/template.service';
|
||||
import { type TemplateService } from '../../infrastructure/services/template.service';
|
||||
import { type NotificationsGateway } from '../gateways/notifications.gateway';
|
||||
|
||||
class UpdatePreferenceDto {
|
||||
@ApiProperty({ enum: PrismaChannel, description: 'Notification channel' })
|
||||
@@ -47,6 +48,7 @@ export class NotificationsController {
|
||||
@Inject(NOTIFICATION_PREFERENCE_REPOSITORY)
|
||||
private readonly preferenceRepo: INotificationPreferenceRepository,
|
||||
private readonly templateService: TemplateService,
|
||||
private readonly notificationsGateway: NotificationsGateway,
|
||||
) {}
|
||||
|
||||
@Get('history')
|
||||
@@ -80,6 +82,15 @@ export class NotificationsController {
|
||||
return this.preferenceRepo.upsert(user.sub, dto.channel, dto.eventType, dto.enabled);
|
||||
}
|
||||
|
||||
@Get('unread-count')
|
||||
@ApiOperation({ summary: 'Get unread notification count (Redis-cached)' })
|
||||
@ApiResponse({ status: 200, description: 'Unread count retrieved' })
|
||||
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||
async getUnreadCount(@CurrentUser() user: JwtPayload) {
|
||||
const count = await this.notificationRepo.countUnreadByUserId(user.sub);
|
||||
return { unreadCount: count };
|
||||
}
|
||||
|
||||
@Get('unread')
|
||||
@ApiOperation({ summary: 'Get unread notifications' })
|
||||
@ApiResponse({ status: 200, description: 'Unread notifications retrieved' })
|
||||
@@ -105,6 +116,9 @@ export class NotificationsController {
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
await this.notificationRepo.markAsRead(id, user.sub);
|
||||
// Invalidate cached count and push updated count via WebSocket
|
||||
await this.notificationsGateway.invalidateUnreadCount(user.sub);
|
||||
await this.notificationsGateway.emitUnreadCount(user.sub);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@@ -114,6 +128,9 @@ export class NotificationsController {
|
||||
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||
async markAllAsRead(@CurrentUser() user: JwtPayload) {
|
||||
const count = await this.notificationRepo.markAllAsRead(user.sub);
|
||||
// Invalidate cached count and push updated count via WebSocket
|
||||
await this.notificationsGateway.invalidateUnreadCount(user.sub);
|
||||
await this.notificationsGateway.emitUnreadCount(user.sub);
|
||||
return { markedCount: count };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user