import { Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; @Injectable() export class ContentReviewService { constructor(private prisma: PrismaService) {} /** 审核统计 */ async getWorkStats() { const today = new Date(); today.setHours(0, 0, 0, 0); const [pending, todayReviewed, todayApproved, todayRejected] = await Promise.all([ this.prisma.userWork.count({ where: { status: 'pending_review', isDeleted: 0 } }), this.prisma.contentReviewLog.count({ where: { targetType: 'work', createTime: { gte: today } } }), this.prisma.contentReviewLog.count({ where: { targetType: 'work', action: 'approve', createTime: { gte: today } } }), this.prisma.contentReviewLog.count({ where: { targetType: 'work', action: 'reject', createTime: { gte: today } } }), ]); return { pending, todayReviewed, todayApproved, todayRejected }; } /** 审核队列列表 */ async getWorkQueue(params: { page?: number; pageSize?: number; status?: string; keyword?: string; startTime?: string; endTime?: string; }) { const { page = 1, pageSize = 10, status, keyword, startTime, endTime } = params; const skip = (page - 1) * pageSize; const where: any = { isDeleted: 0 }; if (status) { where.status = status; } else { where.status = { in: ['pending_review', 'published', 'rejected', 'taken_down'] }; } if (keyword) { where.OR = [ { title: { contains: keyword } }, { creator: { nickname: { contains: keyword } } }, ]; } if (startTime) where.createTime = { ...where.createTime, gte: new Date(startTime) }; if (endTime) where.createTime = { ...where.createTime, lte: new Date(endTime + ' 23:59:59') }; const [list, total] = await Promise.all([ this.prisma.userWork.findMany({ where, skip, take: pageSize, orderBy: { createTime: 'desc' }, include: { creator: { select: { id: true, nickname: true, avatar: true, username: true, userType: true } }, tags: { include: { tag: { select: { id: true, name: true } } } }, _count: { select: { pages: true } }, }, }), this.prisma.userWork.count({ where }), ]); return { list, total, page, pageSize }; } /** 审核详情(含绘本分页) */ async getWorkDetail(workId: number) { const work = await this.prisma.userWork.findUnique({ where: { id: workId }, include: { pages: { orderBy: { pageNo: 'asc' } }, creator: { select: { id: true, nickname: true, avatar: true, username: true, userType: true } }, tags: { include: { tag: true } }, _count: { select: { pages: true, likes: true, favorites: true, comments: true } }, }, }); if (!work) throw new NotFoundException('作品不存在'); return work; } /** 通过 */ async approve(workId: number, operatorId: number, note?: string) { const work = await this.prisma.userWork.findUnique({ where: { id: workId } }); if (!work) throw new NotFoundException('作品不存在'); return this.prisma.$transaction(async (tx) => { await tx.userWork.update({ where: { id: workId }, data: { status: 'published', reviewTime: new Date(), reviewerId: operatorId, reviewNote: note || null, publishTime: new Date(), }, }); await tx.contentReviewLog.create({ data: { targetType: 'work', targetId: workId, workId, action: 'approve', note, operatorId, }, }); return { success: true }; }); } /** 拒绝 */ async reject(workId: number, operatorId: number, reason: string, note?: string) { const work = await this.prisma.userWork.findUnique({ where: { id: workId } }); if (!work) throw new NotFoundException('作品不存在'); return this.prisma.$transaction(async (tx) => { await tx.userWork.update({ where: { id: workId }, data: { status: 'rejected', reviewTime: new Date(), reviewerId: operatorId, reviewNote: reason, }, }); await tx.contentReviewLog.create({ data: { targetType: 'work', targetId: workId, workId, action: 'reject', reason, note, operatorId, }, }); return { success: true }; }); } /** 下架 */ async takedown(workId: number, operatorId: number, reason: string) { const work = await this.prisma.userWork.findUnique({ where: { id: workId } }); if (!work) throw new NotFoundException('作品不存在'); return this.prisma.$transaction(async (tx) => { await tx.userWork.update({ where: { id: workId }, data: { status: 'taken_down', reviewNote: reason }, }); await tx.contentReviewLog.create({ data: { targetType: 'work', targetId: workId, workId, action: 'takedown', reason, operatorId, }, }); return { success: true }; }); } /** 恢复 */ async restore(workId: number, operatorId: number) { const work = await this.prisma.userWork.findUnique({ where: { id: workId } }); if (!work) throw new NotFoundException('作品不存在'); return this.prisma.$transaction(async (tx) => { await tx.userWork.update({ where: { id: workId }, data: { status: 'published' }, }); await tx.contentReviewLog.create({ data: { targetType: 'work', targetId: workId, workId, action: 'restore', operatorId, }, }); return { success: true }; }); } /** 推荐/取消推荐 */ async toggleRecommend(workId: number) { const work = await this.prisma.userWork.findUnique({ where: { id: workId } }); if (!work) throw new NotFoundException('作品不存在'); return this.prisma.userWork.update({ where: { id: workId }, data: { isRecommended: !work.isRecommended }, }); } /** 作品管理统计 */ async getManagementStats() { const today = new Date(); today.setHours(0, 0, 0, 0); const [total, todayNew, totalViews, takenDown] = await Promise.all([ this.prisma.userWork.count({ where: { status: 'published', isDeleted: 0 } }), this.prisma.userWork.count({ where: { status: 'published', publishTime: { gte: today }, isDeleted: 0 } }), this.prisma.userWork.aggregate({ where: { status: 'published', isDeleted: 0 }, _sum: { viewCount: true } }), this.prisma.userWork.count({ where: { status: 'taken_down', isDeleted: 0 } }), ]); return { total, todayNew, totalViews: totalViews._sum.viewCount || 0, takenDown }; } /** 审核日志 */ async getLogs(params: { page?: number; pageSize?: number; workId?: number }) { const { page = 1, pageSize = 20, workId } = params; const skip = (page - 1) * pageSize; const where: any = {}; if (workId) where.workId = workId; const [list, total] = await Promise.all([ this.prisma.contentReviewLog.findMany({ where, skip, take: pageSize, orderBy: { createTime: 'desc' }, include: { operator: { select: { id: true, nickname: true } }, }, }), this.prisma.contentReviewLog.count({ where }), ]); return { list, total, page, pageSize }; } }