347 lines
9.7 KiB
TypeScript
Executable File
347 lines
9.7 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';
|
|
import { DefaultArgs } from '@prisma/client/runtime/library';
|
|
import dayjs from 'dayjs';
|
|
|
|
@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,
|
|
updatedAt: dayjs().toDate(),
|
|
resources: {
|
|
connect: resourceIds.map((fileId) => ({ fileId })),
|
|
},
|
|
meta: {
|
|
type: type,
|
|
},
|
|
} as any,
|
|
},
|
|
{ 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,
|
|
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<DefaultArgs>;
|
|
}): 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;
|
|
}
|
|
}
|