rwadurian/backend/services/admin-service/src/api/controllers/notification.controller.ts

239 lines
6.3 KiB
TypeScript

import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
Query,
HttpCode,
HttpStatus,
Inject,
NotFoundException,
} from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
import {
NOTIFICATION_REPOSITORY,
NotificationRepository,
} from '../../domain/repositories/notification.repository';
import {
NotificationEntity,
NotificationTarget,
TargetType,
} from '../../domain/entities/notification.entity';
import {
CreateNotificationDto,
UpdateNotificationDto,
ListNotificationsDto,
UserNotificationsDto,
MarkReadDto,
} from '../dto/request/notification.dto';
import {
NotificationResponseDto,
UserNotificationResponseDto,
UnreadCountResponseDto,
NotificationListResponseDto,
} from '../dto/response/notification.dto';
/**
* 管理端通知控制器
*/
@Controller('admin/notifications')
export class AdminNotificationController {
constructor(
@Inject(NOTIFICATION_REPOSITORY)
private readonly notificationRepo: NotificationRepository,
) {}
/**
* 创建通知
*/
@Post()
async create(@Body() dto: CreateNotificationDto): Promise<NotificationResponseDto> {
// 构建目标配置
let targetConfig: NotificationTarget | null = null;
const targetType = dto.targetType ?? TargetType.ALL;
if (dto.targetConfig || targetType !== TargetType.ALL) {
targetConfig = {
type: targetType,
tagIds: dto.targetConfig?.tagIds,
segmentId: dto.targetConfig?.segmentId,
accountSequences: dto.targetConfig?.accountSequences,
};
}
const notification = NotificationEntity.create({
id: uuidv4(),
title: dto.title,
content: dto.content,
type: dto.type,
priority: dto.priority,
targetType,
targetConfig,
imageUrl: dto.imageUrl,
linkUrl: dto.linkUrl,
requiresForceRead: dto.requiresForceRead,
publishedAt: dto.publishedAt ? new Date(dto.publishedAt) : null,
expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : null,
createdBy: 'admin', // TODO: 从认证信息获取
});
const saved = await this.notificationRepo.save(notification);
return NotificationResponseDto.fromEntity(saved);
}
/**
* 获取通知详情
*/
@Get(':id')
async findOne(@Param('id') id: string): Promise<NotificationResponseDto> {
const notification = await this.notificationRepo.findById(id);
if (!notification) {
throw new NotFoundException('Notification not found');
}
return NotificationResponseDto.fromEntity(notification);
}
/**
* 获取通知列表(管理端)
*/
@Get()
async findAll(@Query() dto: ListNotificationsDto): Promise<NotificationResponseDto[]> {
const notifications = await this.notificationRepo.findAll({
type: dto.type,
limit: dto.limit,
offset: dto.offset,
});
return notifications.map(NotificationResponseDto.fromEntity);
}
/**
* 更新通知
*/
@Put(':id')
async update(
@Param('id') id: string,
@Body() dto: UpdateNotificationDto,
): Promise<NotificationResponseDto> {
const existing = await this.notificationRepo.findById(id);
if (!existing) {
throw new NotFoundException('Notification not found');
}
// 构建目标配置
let targetConfig: NotificationTarget | null | undefined = undefined;
if (dto.targetConfig !== undefined || dto.targetType !== undefined) {
const targetType = dto.targetType ?? existing.targetType;
if (dto.targetConfig || targetType !== TargetType.ALL) {
targetConfig = {
type: targetType,
tagIds: dto.targetConfig?.tagIds ?? existing.targetConfig?.tagIds,
segmentId: dto.targetConfig?.segmentId ?? existing.targetConfig?.segmentId,
accountSequences:
dto.targetConfig?.accountSequences ??
existing.targetConfig?.accountSequences,
};
} else {
targetConfig = null;
}
}
const updated = existing.update({
title: dto.title,
content: dto.content,
type: dto.type,
priority: dto.priority,
targetType: dto.targetType,
targetConfig,
imageUrl: dto.imageUrl,
linkUrl: dto.linkUrl,
isEnabled: dto.isEnabled,
requiresForceRead: dto.requiresForceRead,
publishedAt: dto.publishedAt !== undefined
? dto.publishedAt
? new Date(dto.publishedAt)
: null
: undefined,
expiresAt: dto.expiresAt !== undefined
? dto.expiresAt
? new Date(dto.expiresAt)
: null
: undefined,
});
const saved = await this.notificationRepo.save(updated);
return NotificationResponseDto.fromEntity(saved);
}
/**
* 删除通知
*/
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
async delete(@Param('id') id: string): Promise<void> {
await this.notificationRepo.delete(id);
}
}
/**
* 移动端通知控制器
*/
@Controller('mobile/notifications')
export class MobileNotificationController {
constructor(
@Inject(NOTIFICATION_REPOSITORY)
private readonly notificationRepo: NotificationRepository,
) {}
/**
* 获取用户的通知列表
*/
@Get()
async getNotifications(
@Query() dto: UserNotificationsDto,
): Promise<NotificationListResponseDto> {
const [notifications, unreadCount] = await Promise.all([
this.notificationRepo.findNotificationsForUser({
userSerialNum: dto.userSerialNum,
type: dto.type,
limit: dto.limit ?? 50,
offset: dto.offset ?? 0,
}),
this.notificationRepo.countUnreadForUser(dto.userSerialNum),
]);
return {
notifications: notifications.map(UserNotificationResponseDto.fromEntity),
total: notifications.length,
unreadCount,
};
}
/**
* 获取未读通知数量
*/
@Get('unread-count')
async getUnreadCount(
@Query('userSerialNum') userSerialNum: string,
): Promise<UnreadCountResponseDto> {
const unreadCount = await this.notificationRepo.countUnreadForUser(userSerialNum);
return { unreadCount };
}
/**
* 标记通知为已读
*/
@Post('mark-read')
@HttpCode(HttpStatus.OK)
async markRead(@Body() dto: MarkReadDto): Promise<{ success: boolean }> {
if (dto.notificationId) {
await this.notificationRepo.markAsRead(dto.notificationId, dto.userSerialNum);
} else {
await this.notificationRepo.markAllAsRead(dto.userSerialNum);
}
return { success: true };
}
}