307 lines
8.6 KiB
TypeScript
Executable File
307 lines
8.6 KiB
TypeScript
Executable File
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';
|
|
|
|
@Injectable()
|
|
export class PostService extends BaseTreeService<Prisma.PostDelegate> {
|
|
constructor(
|
|
private readonly messageService: MessageService,
|
|
private readonly departmentService: DepartmentService,
|
|
) {
|
|
super(db, ObjectType.POST, 'postAncestry', true);
|
|
}
|
|
async createLecture(
|
|
lecture: z.infer<typeof CourseMethodSchema.createLecture>,
|
|
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,
|
|
resources: {
|
|
connect: resourceIds.map((fileId) => ({ fileId })),
|
|
},
|
|
meta: {
|
|
type: type,
|
|
},
|
|
},
|
|
},
|
|
{ tx },
|
|
);
|
|
}
|
|
async createSection(
|
|
section: z.infer<typeof CourseMethodSchema.createSection>,
|
|
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,
|
|
},
|
|
},
|
|
{ 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;
|
|
sections?: z.infer<typeof CourseMethodSchema.createSection>[];
|
|
},
|
|
params: { staff?: UserProfile; tx?: Prisma.TransactionClient },
|
|
) {
|
|
const { courseDetail, sections } = 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
|
|
console.log(courseParams?.staff?.id);
|
|
console.log('courseDetail', courseDetail);
|
|
const createdCourse = await this.create(courseDetail, courseParams);
|
|
// If sections are provided, create them
|
|
if (sections && sections.length > 0) {
|
|
const sectionPromises = sections.map((section) =>
|
|
this.createSection(
|
|
{
|
|
courseId: createdCourse.id,
|
|
title: section.title,
|
|
lectures: section.lectures,
|
|
},
|
|
courseParams,
|
|
),
|
|
);
|
|
// Create all sections (and their lectures) in parallel
|
|
await Promise.all(sectionPromises);
|
|
}
|
|
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
|
|
if (sections && sections.length > 0) {
|
|
const sectionPromises = sections.map((section) =>
|
|
this.createSection(
|
|
{
|
|
courseId: createdCourse.id,
|
|
title: section.title,
|
|
lectures: section.lectures,
|
|
},
|
|
params,
|
|
),
|
|
);
|
|
// Create all sections (and their lectures) in parallel
|
|
await Promise.all(sectionPromises);
|
|
}
|
|
|
|
return createdCourse;
|
|
}
|
|
async create(
|
|
args: Prisma.PostCreateArgs,
|
|
params?: { staff?: UserProfile; tx?: Prisma.TransactionClient },
|
|
) {
|
|
args.data.authorId = params?.staff?.id;
|
|
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;
|
|
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 });
|
|
}
|
|
|
|
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 };
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|