sysmgr-nest/src/notices/notices.service.ts
liuyun 477f35293e fix(security): 若干越权/健壮性修复
- assignOrgs 校验 orgCodes 存在性(对齐 assignResources)
- lock/kick 保护保留角色账号(super_admin/tenant_super_admin)
- BatchDeleteDto 加 @ArrayMaxSize(100)
- 待改密门禁显式放行 /auth/logout
- 分配部门改用独立权限码 role:assign-org

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-11 22:56:12 +08:00

85 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Injectable } from '@nestjs/common';
import { BaseCrudService } from '../common/base-crud.service.js';
import { PrismaService } from '../prisma/prisma.service.js';
import { getRequestContext } from '../common/context/request-context.js';
import { CreateNoticeDto, ListNoticeQuery, UpdateNoticeDto } from './dto.js';
@Injectable()
export class NoticesService extends BaseCrudService {
constructor(prisma: PrismaService) {
super(prisma, 'notice');
}
listNotices(q: ListNoticeQuery) {
const where: Record<string, unknown> = {};
if (q.title) where.title = { contains: q.title };
if (q.status) where.status = q.status;
return this.paginate(q.page, q.pageSize, where);
}
createNotice(dto: CreateNoticeDto) {
return this.create({ ...dto });
}
updateNotice(id: string, dto: UpdateNoticeDto) {
return this.update(id, { ...dto });
}
/** 当前用户可见通知(enabled)+ 已读状态。 */
async mine(q: ListNoticeQuery) {
const ctx = getRequestContext()!;
const [rows, total] = await Promise.all([
this.prisma.client.notice.findMany({
where: { status: 'enabled' },
skip: (q.page - 1) * q.pageSize,
take: q.pageSize,
orderBy: { createdAt: 'desc' },
select: { id: true, title: true, content: true, status: true, createdAt: true },
}),
this.prisma.client.notice.count({ where: { status: 'enabled' } }),
]);
const ids = rows.map((r) => r.id);
const readRows = ids.length
? await this.prisma.client.noticeRead.findMany({
where: { userId: ctx.userId, noticeId: { in: ids } },
select: { noticeId: true, readAt: true },
})
: [];
const readMap = new Map(readRows.map((r) => [r.noticeId, r.readAt]));
const list = rows.map((r) => ({ ...r, readAt: readMap.get(r.id) ?? null }));
return { list, total, page: q.page, pageSize: q.pageSize };
}
/** 未读数 = enabled 通知数 − 当前用户已读数(O(enabled 通知数),公告量小可接受)。 */
async unreadCount(): Promise<number> {
const ctx = getRequestContext()!;
const enabled = await this.prisma.client.notice.findMany({ where: { status: 'enabled' }, select: { id: true } });
const enabledIds = enabled.map((n) => n.id);
const readCount = enabledIds.length
? await this.prisma.client.noticeRead.count({ where: { userId: ctx.userId, noticeId: { in: enabledIds } } })
: 0;
return Math.max(0, enabled.length - readCount);
}
async read(id: string): Promise<null> {
const ctx = getRequestContext()!;
await this.prisma.client.noticeRead.upsert({
where: { tenantCode_noticeId_userId: { tenantCode: ctx.tenantCode, noticeId: id, userId: ctx.userId } },
update: {},
create: { tenantCode: ctx.tenantCode, noticeId: id, userId: ctx.userId },
});
return null;
}
async readAll(): Promise<null> {
const ctx = getRequestContext()!;
const enabled = await this.prisma.client.notice.findMany({ where: { status: 'enabled' }, select: { id: true } });
if (enabled.length === 0) return null;
await this.prisma.client.noticeRead.createMany({
data: enabled.map((n) => ({ tenantCode: ctx.tenantCode, noticeId: n.id, userId: ctx.userId })),
skipDuplicates: true,
});
return null;
}
}