database.ts 导出db常量替代getDb()函数 env.ts JWT_SECRET/ES_URL改optional加DEV_MODE/PUSH_GATEWAY_URL elasticsearch.ts ES降级: ES_URL未设置时esClient=null notifications.service.ts 加createBatch+分页查询+Push Gateway推送调用 新建msg-init.sql创建2张表
103 lines
2.8 KiB
TypeScript
103 lines
2.8 KiB
TypeScript
import {
|
||
Body,
|
||
Controller,
|
||
Get,
|
||
HttpException,
|
||
HttpStatus,
|
||
Param,
|
||
Post,
|
||
Put,
|
||
Query,
|
||
Req,
|
||
} from "@nestjs/common";
|
||
import { NotificationsService } from "./notifications.service.js";
|
||
import type { SendNotificationDto } from "./notifications.service.js";
|
||
|
||
interface AuthedRequest {
|
||
headers: Record<string, string | string[] | undefined>;
|
||
}
|
||
|
||
/**
|
||
* 从请求头读取用户身份。
|
||
* Gateway 在通过鉴权后会注入 x-user-id;如未注入(开发模式或匿名请求)返回 null。
|
||
*/
|
||
function getUserIdFromRequest(req: AuthedRequest): string | null {
|
||
const raw = req.headers["x-user-id"];
|
||
if (typeof raw === "string" && raw.length > 0) return raw;
|
||
return null;
|
||
}
|
||
|
||
@Controller("notifications")
|
||
export class NotificationsController {
|
||
constructor(private readonly service: NotificationsService) {}
|
||
|
||
@Post()
|
||
async send(@Body() body: unknown): Promise<{ success: true; data: unknown }> {
|
||
const result = await this.service.send(body as SendNotificationDto);
|
||
return { success: true, data: result };
|
||
}
|
||
|
||
@Post("batch")
|
||
async createBatch(
|
||
@Body() body: unknown,
|
||
): Promise<{ success: true; data: unknown }> {
|
||
const dtos = (body as SendNotificationDto[]) ?? [];
|
||
const result = await this.service.createBatch(dtos);
|
||
return { success: true, data: result };
|
||
}
|
||
|
||
@Get("user/:userId")
|
||
async listByUser(
|
||
@Param("userId") userId: string,
|
||
@Query("unread") unread: string,
|
||
): Promise<{ success: true; data: unknown }> {
|
||
const result = await this.service.listByUser(userId, unread === "true");
|
||
return { success: true, data: result };
|
||
}
|
||
|
||
@Get("user/:userId/page")
|
||
async listByUserPaginated(
|
||
@Param("userId") userId: string,
|
||
@Query("page") page: string,
|
||
@Query("pageSize") pageSize: string,
|
||
): Promise<{ success: true; data: unknown }> {
|
||
const pageNum = Number(page) > 0 ? Number(page) : 1;
|
||
const pageSizeNum = Number(pageSize) > 0 ? Number(pageSize) : 20;
|
||
const result = await this.service.listByUserWithPagination(
|
||
userId,
|
||
pageNum,
|
||
pageSizeNum,
|
||
);
|
||
return { success: true, data: result };
|
||
}
|
||
|
||
@Put(":id/read")
|
||
async markAsRead(@Param("id") id: string): Promise<{ success: true }> {
|
||
await this.service.markAsRead(id);
|
||
return { success: true };
|
||
}
|
||
|
||
@Get("search")
|
||
async search(
|
||
@Req() req: AuthedRequest,
|
||
@Query("q") q: string,
|
||
@Query("userId") userIdParam: string,
|
||
): Promise<{ success: true; data: unknown }> {
|
||
const userId = getUserIdFromRequest(req) ?? userIdParam;
|
||
if (!userId) {
|
||
throw new HttpException(
|
||
{
|
||
success: false,
|
||
error: {
|
||
code: "MSG_PERMISSION_DENIED",
|
||
message: "Missing user identity",
|
||
},
|
||
},
|
||
HttpStatus.FORBIDDEN,
|
||
);
|
||
}
|
||
const result = await this.service.search(userId, q);
|
||
return { success: true, data: result };
|
||
}
|
||
}
|