import { Injectable } from '@nestjs/common'; import { db, Prisma, UserProfile, VisitType, Post, PostType, RolePerms, ResPerm, ObjectType, LectureType, CourseMethodSchema, } from '@nice/common'; import { MessageService } from '../message/message.service'; import { BaseService } from '../base/base.service'; import { DepartmentService } from '../department/department.service'; import { setCourseInfo, setPostRelation } from './utils'; import EventBus, { CrudOperation } from '@server/utils/event-bus'; import { BaseTreeService } from '../base/base.tree.service'; import { z } from 'zod'; import { DefaultArgs } from '@prisma/client/runtime/library'; import dayjs from 'dayjs'; @Injectable() export class PostService extends BaseTreeService { constructor( private readonly messageService: MessageService, private readonly departmentService: DepartmentService, ) { super(db, ObjectType.POST, 'postAncestry', true); } async createLecture( lecture: z.infer, params: { staff?: UserProfile; tx?: Prisma.TransactionClient }, ) { const { sectionId, type, title, content, resourceIds = [] } = lecture; const { staff, tx } = params; return await this.create( { data: { type: PostType.LECTURE, parentId: sectionId, content: content, title: title, authorId: params?.staff?.id, updatedAt: dayjs().toDate(), resources: { connect: resourceIds.map((fileId) => ({ fileId })), }, meta: { type: type, }, } as any, }, { tx }, ); } async createSection( section: z.infer, params: { staff?: UserProfile; tx?: Prisma.TransactionClient; }, ) { const { title, courseId, lectures } = section; const { staff, tx } = params; // Create section first const createdSection = await this.create( { data: { type: PostType.SECTION, parentId: courseId, title: title, authorId: staff?.id, updatedAt: dayjs().toDate(), } as any, }, { tx }, ); // If lectures are provided, create them if (lectures && lectures.length > 0) { const lecturePromises = lectures.map((lecture) => this.createLecture( { sectionId: createdSection.id, ...lecture, }, params, ), ); // Create all lectures in parallel await Promise.all(lecturePromises); } return createdSection; } async createCourse( args: { courseDetail: Prisma.PostCreateArgs; }, params: { staff?: UserProfile; tx?: Prisma.TransactionClient }, ) { // const await db.post.findMany({ // where: { // type: PostType.COURSE, // }, // }); const { courseDetail } = args; // If no transaction is provided, create a new one if (!params.tx) { return await db.$transaction(async (tx) => { const courseParams = { ...params, tx }; // Create the course first const createdCourse = await this.create(courseDetail, courseParams); // If sections are provided, create them return createdCourse; }); } // If transaction is provided, use it directly console.log('courseDetail', courseDetail); const createdCourse = await this.create(courseDetail, params); // If sections are provided, create them return createdCourse; } async create( args: Prisma.PostCreateArgs, params?: { staff?: UserProfile; tx?: Prisma.TransactionClient }, ) { args.data.authorId = params?.staff?.id; args.data.updatedAt = dayjs().toDate(); const result = await super.create(args); EventBus.emit('dataChanged', { type: ObjectType.POST, operation: CrudOperation.CREATED, data: result, }); return result; } async update(args: Prisma.PostUpdateArgs, staff?: UserProfile) { args.data.authorId = staff?.id; args.data.updatedAt = dayjs().toDate(); const result = await super.update(args); EventBus.emit('dataChanged', { type: ObjectType.POST, operation: CrudOperation.UPDATED, data: result, }); return result; } async findFirst( args?: Prisma.PostFindFirstArgs, staff?: UserProfile, clientIp?: string, ) { const transDto = await this.wrapResult( super.findFirst(args), async (result) => { if (result) { await setPostRelation({ data: result, staff }); await this.setPerms(result, staff); await setCourseInfo({ data: result }); } // console.log(result); return result; }, ); return transDto; } // async findMany(args: Prisma.PostFindManyArgs, staff?: UserProfile) { // if (!args.where) args.where = {}; // args.where.OR = await this.preFilter(args.where.OR, staff); // return this.wrapResult(super.findMany(args), async (result) => { // await Promise.all( // result.map(async (item) => { // await setPostRelation({ data: item, staff }); // await this.setPerms(item, staff); // }), // ); // return { ...result }; // }); // } async findManyWithCursor(args: Prisma.PostFindManyArgs, staff?: UserProfile) { if (!args.where) args.where = {}; args.where.OR = await this.preFilter(args.where.OR, staff); return this.wrapResult(super.findManyWithCursor(args), async (result) => { const { items } = result; await Promise.all( items.map(async (item) => { await setPostRelation({ data: item, staff }); await this.setPerms(item, staff); }), ); return { ...result, items }; }); } async findManyWithPagination(args: { page?: number; pageSize?: number; where?: Prisma.PostWhereInput; select?: Prisma.PostSelect; }): Promise<{ items: { id: string; type: string | null; level: string | null; state: string | null; title: string | null; subTitle: string | null; content: string | null; important: boolean | null; domainId: string | null; order: number | null; duration: number | null; rating: number | null; createdAt: Date; publishedAt: Date | null; updatedAt: Date; deletedAt: Date | null; authorId: string | null; parentId: string | null; hasChildren: boolean | null; meta: Prisma.JsonValue | null; }[]; totalPages: number; }> { // super.updateOrder; return super.findManyWithPagination(args); } async updateOrderByIds(ids: string[]) { const posts = await db.post.findMany({ where: { id: { in: ids } }, select: { id: true, order: true }, }); const postMap = new Map(posts.map((post) => [post.id, post])); const orderedPosts = ids .map((id) => postMap.get(id)) .filter((post): post is { id: string; order: number } => !!post); // 生成仅需更新的操作 const updates = orderedPosts .map((post, index) => ({ id: post.id, newOrder: index, // 按数组索引设置新顺序 currentOrder: post.order, })) .filter(({ newOrder, currentOrder }) => newOrder !== currentOrder) .map(({ id, newOrder }) => db.post.update({ where: { id }, data: { order: newOrder }, }), ); // 批量执行更新 return updates.length > 0 ? await db.$transaction(updates) : []; } protected async setPerms(data: Post, staff?: UserProfile) { if (!staff) return; const perms: ResPerm = { delete: false, }; const isMySelf = data?.authorId === staff?.id; const isDomain = staff.domainId === data.domainId; const setManagePermissions = (perms: ResPerm) => { Object.assign(perms, { delete: true, // edit: true, }); }; if (isMySelf) { perms.delete = true; // perms.edit = true; } staff.permissions.forEach((permission) => { switch (permission) { case RolePerms.MANAGE_ANY_POST: setManagePermissions(perms); break; case RolePerms.MANAGE_DOM_POST: if (isDomain) { setManagePermissions(perms); } break; } }); Object.assign(data, { perms }); } async preFilter(OR?: Prisma.PostWhereInput[], staff?: UserProfile) { const preFilter = (await this.getPostPreFilter(staff)) || []; const outOR = OR ? [...OR, ...preFilter].filter(Boolean) : preFilter; return outOR?.length > 0 ? outOR : undefined; } async getPostPreFilter(staff?: UserProfile) { if (!staff) return; const { deptId, domainId } = staff; if ( staff.permissions.includes(RolePerms.READ_ANY_POST) || staff.permissions.includes(RolePerms.MANAGE_ANY_POST) ) { return undefined; } const parentDeptIds = (await this.departmentService.getAncestorIds(staff.deptId)) || []; const orCondition: Prisma.PostWhereInput[] = [ staff?.id && { authorId: staff.id, }, staff?.id && { watchableStaffs: { some: { id: staff.id, }, }, }, deptId && { watchableDepts: { some: { id: { in: parentDeptIds, }, }, }, }, { AND: [ { watchableStaffs: { none: {}, // 匹配 watchableStaffs 为空 }, }, { watchableDepts: { none: {}, // 匹配 watchableDepts 为空 }, }, ], }, ].filter(Boolean); if (orCondition?.length > 0) return orCondition; return undefined; } }