diff --git a/apps/server/src/models/message/message.controller.ts b/apps/server/src/models/message/message.controller.ts deleted file mode 100755 index 0738c08..0000000 --- a/apps/server/src/models/message/message.controller.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Controller, Get, Query, UseGuards } from '@nestjs/common'; - -import { MessageService } from './message.service'; -import { AuthGuard } from '@server/auth/auth.guard'; -import { db, VisitType } from '@nice/common'; - -@Controller('message') -export class MessageController { - constructor(private readonly messageService: MessageService) { } - @UseGuards(AuthGuard) - @Get('find-last-one') - async findLastOne(@Query('staff-id') staffId: string) { - try { - const result = await db.message.findFirst({ - where: { - OR: [ - { - receivers: { - some: { - id: staffId, - }, - }, - }, - ], - }, - orderBy: { createdAt: 'desc' }, - select: { - title: true, - content: true, - url: true - }, - }); - - return { - data: result, - errmsg: 'success', - errno: 0, - }; - } catch (e) { - return { - data: {}, - errmsg: (e as any)?.message || 'error', - errno: 1, - }; - } - } - @UseGuards(AuthGuard) - @Get('find-unreaded') - async findUnreaded(@Query('staff-id') staffId: string) { - try { - const result = await db.message.findMany({ - where: { - visits: { - none: { - id: staffId, - type: VisitType.READED - }, - }, - receivers: { - some: { - id: staffId, - }, - }, - }, - orderBy: { createdAt: 'desc' }, - select: { - title: true, - content: true, - url: true, - }, - }); - - return { - data: result, - errmsg: 'success', - errno: 0, - }; - } catch (e) { - return { - data: {}, - errmsg: (e as any)?.message || 'error', - errno: 1, - }; - } - } - @UseGuards(AuthGuard) - @Get('count-unreaded') - async countUnreaded(@Query('staff-id') staffId: string) { - try { - const result = await db.message.findMany({ - where: { - visits: { - none: { - id: staffId, - type: VisitType.READED - }, - }, - receivers: { - some: { - id: staffId, - }, - }, - }, - orderBy: { createdAt: 'desc' }, - select: { - title: true, - content: true, - url: true, - }, - }); - - return { - data: result, - errmsg: 'success', - errno: 0, - }; - } catch (e) { - return { - data: {}, - errmsg: (e as any)?.message || 'error', - errno: 1, - }; - } - } -} diff --git a/apps/server/src/models/message/message.module.ts b/apps/server/src/models/message/message.module.ts deleted file mode 100755 index ce83a6e..0000000 --- a/apps/server/src/models/message/message.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Module } from '@nestjs/common'; -import { MessageService } from './message.service'; -import { MessageRouter } from './message.router'; -import { TrpcService } from '@server/trpc/trpc.service'; -import { DepartmentModule } from '../department/department.module'; -import { MessageController } from './message.controller'; - -@Module({ - imports: [DepartmentModule], - providers: [MessageService, MessageRouter, TrpcService], - exports: [MessageService, MessageRouter], - controllers: [MessageController], -}) -export class MessageModule { } diff --git a/apps/server/src/models/message/message.router.ts b/apps/server/src/models/message/message.router.ts deleted file mode 100755 index 44d56b9..0000000 --- a/apps/server/src/models/message/message.router.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { TrpcService } from '@server/trpc/trpc.service'; -import { MessageService } from './message.service'; -import { Prisma } from '@nice/common'; -import { z, ZodType } from 'zod'; -const MessageUncheckedCreateInputSchema: ZodType = z.any() -const MessageWhereInputSchema: ZodType = z.any() -const MessageSelectSchema: ZodType = z.any() -@Injectable() -export class MessageRouter { - constructor( - private readonly trpc: TrpcService, - private readonly messageService: MessageService, - ) { } - router = this.trpc.router({ - create: this.trpc.procedure - .input(MessageUncheckedCreateInputSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.messageService.create({ data: input }, { staff }); - }), - findManyWithCursor: this.trpc.protectProcedure - .input(z.object({ - cursor: z.any().nullish(), - take: z.number().nullish(), - where: MessageWhereInputSchema.nullish(), - select: MessageSelectSchema.nullish() - })) - .query(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.messageService.findManyWithCursor(input, staff); - }), - getUnreadCount: this.trpc.protectProcedure - .query(async ({ ctx }) => { - const { staff } = ctx; - return await this.messageService.getUnreadCount(staff); - }) - }) -} diff --git a/apps/server/src/models/message/message.service.ts b/apps/server/src/models/message/message.service.ts deleted file mode 100755 index 8b85635..0000000 --- a/apps/server/src/models/message/message.service.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { UserProfile, db, Prisma, VisitType, ObjectType } from '@nice/common'; -import { BaseService } from '../base/base.service'; -import EventBus, { CrudOperation } from '@server/utils/event-bus'; -import { setMessageRelation } from './utils'; -@Injectable() -export class MessageService extends BaseService { - constructor() { - super(db, ObjectType.MESSAGE); - } - async create(args: Prisma.MessageCreateArgs, params?: { tx?: Prisma.MessageDelegate, staff?: UserProfile }) { - args.data!.senderId = params?.staff?.id; - args.include = { - receivers: { - select: { id: true, registerToken: true, username: true } - } - } - const result = await super.create(args); - EventBus.emit("dataChanged", { - type: ObjectType.MESSAGE, - operation: CrudOperation.CREATED, - data: result - }) - return result - } - async findManyWithCursor( - args: Prisma.MessageFindManyArgs, - staff?: UserProfile, - ) { - - return this.wrapResult(super.findManyWithCursor(args), async (result) => { - let { items } = result; - await Promise.all( - items.map(async (item) => { - await setMessageRelation(item, staff); - }), - ); - - return { ...result, items }; - }); - } - async getUnreadCount(staff?: UserProfile) { - const count = await db.message.count({ - where: { - receivers: { some: { id: staff?.id } }, - visits: { - none: { - visitorId: staff?.id, - type: VisitType.READED - } - } - } - }) - - return count - } -} diff --git a/apps/server/src/models/message/utils.ts b/apps/server/src/models/message/utils.ts deleted file mode 100755 index 7c2bf35..0000000 --- a/apps/server/src/models/message/utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Message, UserProfile, VisitType, db } from "@nice/common" -export async function setMessageRelation( - data: Message, - staff?: UserProfile, -): Promise { - - const readed = - (await db.visit.count({ - where: { - messageId: data.id, - type: VisitType.READED, - visitorId: staff?.id, - }, - })) > 0; - - - Object.assign(data, { - readed - }) -} \ No newline at end of file diff --git a/apps/server/src/models/post/post.controller.ts b/apps/server/src/models/post/post.controller.ts deleted file mode 100755 index 1e9eb77..0000000 --- a/apps/server/src/models/post/post.controller.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Controller, Get, Query, UseGuards } from '@nestjs/common'; - -import { PostService } from './post.service'; -import { AuthGuard } from '@server/auth/auth.guard'; -import { db } from '@nice/common'; - -@Controller('post') -export class PostController { - constructor(private readonly postService: PostService) {} -} diff --git a/apps/server/src/models/post/post.module.ts b/apps/server/src/models/post/post.module.ts deleted file mode 100755 index 836d47b..0000000 --- a/apps/server/src/models/post/post.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TrpcService } from '@server/trpc/trpc.service'; -import { DepartmentService } from '@server/models/department/department.service'; - -import { QueueModule } from '@server/queue/queue.module'; -import { MessageModule } from '../message/message.module'; -import { PostRouter } from './post.router'; -import { PostController } from './post.controller'; -import { PostService } from './post.service'; -import { RoleMapModule } from '../rbac/rbac.module'; - -@Module({ - imports: [QueueModule, RoleMapModule, MessageModule], - providers: [PostService, PostRouter, TrpcService, DepartmentService], - exports: [PostRouter, PostService], - controllers: [PostController], -}) -export class PostModule {} diff --git a/apps/server/src/models/post/post.router.ts b/apps/server/src/models/post/post.router.ts deleted file mode 100755 index 9ee4f9b..0000000 --- a/apps/server/src/models/post/post.router.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { TrpcService } from '@server/trpc/trpc.service'; -import { CourseMethodSchema, Prisma } from '@nice/common'; -import { PostService } from './post.service'; -import { z, ZodType } from 'zod'; -import { UpdateOrderArgs } from '../base/base.type'; -const PostCreateArgsSchema: ZodType = z.any(); -const PostUpdateArgsSchema: ZodType = z.any(); -const PostUpdateOrderArgsSchema: ZodType = z.any(); -const PostFindFirstArgsSchema: ZodType = z.any(); -const PostFindManyArgsSchema: ZodType = z.any(); -const PostDeleteManyArgsSchema: ZodType = z.any(); -const PostWhereInputSchema: ZodType = z.any(); -const PostSelectSchema: ZodType = z.any(); -const PostUpdateInputSchema: ZodType = z.any(); -@Injectable() -export class PostRouter { - constructor( - private readonly trpc: TrpcService, - private readonly postService: PostService, - ) {} - router = this.trpc.router({ - create: this.trpc.protectProcedure - .input(PostCreateArgsSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.postService.create(input, { staff }); - }), - createCourse: this.trpc.protectProcedure - .input(CourseMethodSchema.createCourse) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.postService.createCourse(input, { staff }); - }), - softDeleteByIds: this.trpc.protectProcedure - .input( - z.object({ - ids: z.array(z.string()), - data: PostUpdateInputSchema.nullish(), - }), - ) - .mutation(async ({ input }) => { - return await this.postService.softDeleteByIds(input.ids, input.data); - }), - restoreByIds: this.trpc.protectProcedure - .input( - z.object({ - ids: z.array(z.string()), - args: PostUpdateInputSchema.nullish(), - }), - ) - .mutation(async ({ input }) => { - return await this.postService.restoreByIds(input.ids, input.args); - }), - update: this.trpc.protectProcedure - .input(PostUpdateArgsSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.postService.update(input, staff); - }), - findById: this.trpc.procedure - .input(z.object({ id: z.string(), args: PostFindFirstArgsSchema })) - .query(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.postService.findById(input.id, input.args); - }), - findMany: this.trpc.procedure - .input(PostFindManyArgsSchema) - .query(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.postService.findMany(input); - }), - - findFirst: this.trpc.procedure - .input(PostFindFirstArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input, ctx }) => { - const { staff, ip } = ctx; - // 从请求中获取 IP - - return await this.postService.findFirst(input, staff, ip); - }), - deleteMany: this.trpc.protectProcedure - .input(PostDeleteManyArgsSchema) - .mutation(async ({ input }) => { - return await this.postService.deleteMany(input); - }), - findManyWithCursor: this.trpc.procedure - .input( - z.object({ - cursor: z.any().nullish(), - take: z.number().nullish(), - where: PostWhereInputSchema.nullish(), - select: PostSelectSchema.nullish(), - }), - ) - .query(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.postService.findManyWithCursor(input, staff); - }), - findManyWithPagination: this.trpc.procedure - .input( - z.object({ - page: z.number().optional(), - pageSize: z.number().optional(), - where: PostWhereInputSchema.optional(), - select: PostSelectSchema.optional(), - }), - ) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.postService.findManyWithPagination(input); - }), - updateOrder: this.trpc.protectProcedure - .input(PostUpdateOrderArgsSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.postService.updateOrder(input); - }), - updateOrderByIds: this.trpc.protectProcedure - .input( - z.object({ - ids: z.array(z.string()), - }), - ) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.postService.updateOrderByIds(input.ids); - }), - softDeletePostDescendant:this.trpc.protectProcedure - .input( - z.object({ - ancestorId:z.string() - }) - ) - .mutation(async ({ input })=>{ - return await this.postService.softDeletePostDescendant(input) - }) - }); -} diff --git a/apps/server/src/models/post/post.service.ts b/apps/server/src/models/post/post.service.ts deleted file mode 100755 index c3a465d..0000000 --- a/apps/server/src/models/post/post.service.ts +++ /dev/null @@ -1,328 +0,0 @@ -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 { setPostInfo, setPostRelation } from './utils'; -import EventBus, { CrudOperation } from '@server/utils/event-bus'; -import { BaseTreeService } from '../base/base.tree.service'; -import { z } from 'zod'; -import dayjs from 'dayjs'; -import { OrderByArgs } from '../base/base.type'; - -@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 { 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 - 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 setPostInfo({ data: result }); - } - // console.log(result); - return result; - }, - ); - return transDto; - } - - 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; - orderBy?: OrderByArgs<(typeof db.post)['findMany']>; - 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; - views: number; - hates: number; - likes: number; - 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, - }, - ].filter(Boolean); - - if (orCondition?.length > 0) return orCondition; - return undefined; - } - async softDeletePostDescendant(args:{ancestorId?:string}){ - const { ancestorId } = args - const descendantIds = [] - await db.postAncestry.findMany({ - where:{ - ancestorId, - }, - select:{ - descendantId:true - } - }).then(res=>{ - res.forEach(item=>{ - descendantIds.push(item.descendantId) - }) - }) - console.log(descendantIds) - const result = super.softDeleteByIds([...descendantIds,ancestorId]) - EventBus.emit('dataChanged', { - type: ObjectType.POST, - operation: CrudOperation.DELETED, - data: result, - }); - return result - } -} diff --git a/apps/server/src/models/post/utils.ts b/apps/server/src/models/post/utils.ts deleted file mode 100755 index 4dea80c..0000000 --- a/apps/server/src/models/post/utils.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { - db, - EnrollmentStatus, - Lecture, - Post, - PostType, - SectionDto, - UserProfile, - VisitType, -} from '@nice/common'; - -export async function setPostRelation(params: { - data: Post; - staff?: UserProfile; -}) { - const { data, staff } = params; - const limitedComments = await db.post.findMany({ - where: { - parentId: data.id, - type: PostType.POST_COMMENT, - }, - include: { - author: true, - }, - take: 5, - }); - const commentsCount = await db.post.count({ - where: { - parentId: data.id, - type: PostType.POST_COMMENT, - }, - }); - const readed = - (await db.visit.count({ - where: { - postId: data.id, - type: VisitType.READED, - visitorId: staff?.id, - }, - })) > 0; - const readedCount = await db.visit.count({ - where: { - postId: data.id, - type: VisitType.READED, - }, - }); - - Object.assign(data, { - readed, - readedCount, - limitedComments, - commentsCount, - // trouble - }); -} -export async function updateParentLectureStats(parentId: string) { - const ParentStats = await db.post.aggregate({ - where: { - ancestors: { - some: { - ancestorId: parentId, - descendant: { - type: PostType.LECTURE, - deletedAt: null, - }, - }, - }, - }, - _count: { _all: true }, - _sum: { - duration: true, - }, - }); - await db.post.update({ - where: { id: parentId }, - data: { - //totalLectures: courseStats._count._all, - //totalDuration: courseStats._sum.duration || 0, - }, - }); -} - -// 更新课程评价统计 -export async function updateCourseReviewStats(courseId: string) { - const reviews = await db.visit.findMany({ - where: { - postId: courseId, - type: PostType.COURSE_REVIEW, - deletedAt: null, - }, - select: { views: true }, - }); - const numberOfReviews = reviews.length; - const averageRating = - numberOfReviews > 0 - ? reviews.reduce((sum, review) => sum + review.views, 0) / numberOfReviews - : 0; - - return db.post.update({ - where: { id: courseId }, - data: { - // numberOfReviews, - //averageRating, - }, - }); -} -// 更新课程注册统计 -export async function updateCourseEnrollmentStats(courseId: string) { - const completedEnrollments = await db.enrollment.count({ - where: { - postId: courseId, - status: EnrollmentStatus.COMPLETED, - }, - }); - const totalEnrollments = await db.enrollment.count({ - where: { postId: courseId }, - }); - const completionRate = - totalEnrollments > 0 ? (completedEnrollments / totalEnrollments) * 100 : 0; - return db.post.update({ - where: { id: courseId }, - data: { - // numberOfStudents: totalEnrollments, - // completionRate, - }, - }); -} - -export async function setPostInfo({ data }: { data: Post }) { - // await db.term - if (data?.type === PostType.COURSE) { - const ancestries = await db.postAncestry.findMany({ - where: { - ancestorId: data.id, - }, - select: { - id: true, - descendant: true, - }, - orderBy: { - descendant: { - order: 'asc', - }, - }, - }); - const descendants = ancestries.map((ancestry) => ancestry.descendant); - const sections: SectionDto[] = ( - descendants.filter((descendant) => { - return ( - descendant.type === PostType.SECTION && - descendant.parentId === data.id - ); - }) as any - ).map((section) => ({ - ...section, - lectures: [], - })); - const lectures = descendants.filter((descendant) => { - return ( - descendant.type === PostType.LECTURE && - sections.map((section) => section.id).includes(descendant.parentId) - ); - }); - - const lectureCount = lectures?.length || 0; - sections.forEach((section) => { - section.lectures = lectures.filter( - (lecture) => lecture.parentId === section.id, - ) as any as Lecture[]; - }); - - Object.assign(data, { sections, lectureCount }); - } - if (data?.type === PostType.LECTURE || data?.type === PostType.SECTION) { - const ancestry = await db.postAncestry.findFirst({ - where: { - descendantId: data?.id, - ancestor: { - type: PostType.COURSE, - }, - }, - select: { - ancestor: { select: { id: true } }, - }, - }); - const courseId = ancestry.ancestor.id; - Object.assign(data, { courseId }); - } - const students = await db.staff.findMany({ - where: { - learningPosts: { - some: { - id: data.id, - }, - }, - }, - select: { - id: true, - }, - }); - - const studentIds = (students || []).map((student) => student?.id); - Object.assign(data, { studentIds }); -} diff --git a/apps/server/src/models/resource/resource.service.ts b/apps/server/src/models/resource/resource.service.ts index 86b69aa..1f2f9a9 100755 --- a/apps/server/src/models/resource/resource.service.ts +++ b/apps/server/src/models/resource/resource.service.ts @@ -35,53 +35,4 @@ export class ResourceService extends BaseService { }, }); } - // 添加保存文件名的方法 - async saveFileName(fileId: string, fileName: string): Promise { - try { - this.logger.log(`尝试保存文件名 "${fileName}" 到文件 ${fileId}`); - - // 首先检查是否已存在 ShareCode 记录 - const existingShareCode = await db.shareCode.findUnique({ - where: { fileId }, - }); - - if (existingShareCode) { - // 如果记录存在,更新文件名 - await db.shareCode.update({ - where: { fileId }, - data: { fileName }, - }); - this.logger.log(`更新了现有记录的文件名 "${fileName}" 到文件 ${fileId}`); - } else { - // 如果记录不存在,创建新记录 - await db.shareCode.create({ - data: { - fileId, - fileName, - code: null, // 这里可以设置为 null 或生成一个临时码 - expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24小时后过期 - isUsed: false, - }, - }); - this.logger.log(`创建了新记录并保存文件名 "${fileName}" 到文件 ${fileId}`); - } - } catch (error) { - this.logger.error(`保存文件名失败,文件ID: ${fileId}`, error); - throw error; - } - } - - // 添加获取文件名的方法 - async getFileName(fileId: string): Promise { - try { - const shareCode = await db.shareCode.findUnique({ - where: { fileId }, - select: { fileName: true }, - }); - return shareCode?.fileName || null; - } catch (error) { - this.logger.error(`Failed to get filename for ${fileId}`, error); - return null; - } - } } diff --git a/apps/server/src/queue/models/post/utils.ts b/apps/server/src/queue/models/post/utils.ts index 932d4b3..18b60c0 100755 --- a/apps/server/src/queue/models/post/utils.ts +++ b/apps/server/src/queue/models/post/utils.ts @@ -5,132 +5,132 @@ import { PostType, VisitType, } from '@nice/common'; -export async function updateTotalCourseViewCount(type: VisitType) { - const posts = await db.post.findMany({ - where: { - // type: { in: [PostType.COURSE, PostType.LECTURE,] }, - deletedAt: null, - }, - select: { id: true, type: true }, - }); +// export async function updateTotalCourseViewCount(type: VisitType) { +// const posts = await db.post.findMany({ +// where: { +// // type: { in: [PostType.COURSE, PostType.LECTURE,] }, +// deletedAt: null, +// }, +// select: { id: true, type: true }, +// }); - const courseIds = posts - .filter((post) => post.type === PostType.COURSE) - .map((course) => course.id); - const lectures = posts.filter((post) => post.type === PostType.LECTURE); - const totalViews = await db.visit.aggregate({ - _sum: { - views: true, - }, - where: { - postId: { in: posts.map((post) => post.id) }, - type: type, - }, - }); - const appConfig = await db.appConfig.findFirst({ - where: { - slug: AppConfigSlug.BASE_SETTING, - }, - select: { - id: true, - meta: true, - }, - }); - const staffs = await db.staff.count({ - where: { deletedAt: null }, - }); +// const courseIds = posts +// .filter((post) => post.type === PostType.COURSE) +// .map((course) => course.id); +// const lectures = posts.filter((post) => post.type === PostType.LECTURE); +// const totalViews = await db.visit.aggregate({ +// _sum: { +// views: true, +// }, +// where: { +// postId: { in: posts.map((post) => post.id) }, +// type: type, +// }, +// }); +// const appConfig = await db.appConfig.findFirst({ +// where: { +// slug: AppConfigSlug.BASE_SETTING, +// }, +// select: { +// id: true, +// meta: true, +// }, +// }); +// const staffs = await db.staff.count({ +// where: { deletedAt: null }, +// }); - const baseSeting = appConfig.meta as BaseSetting; - await db.appConfig.update({ - where: { - slug: AppConfigSlug.BASE_SETTING, - }, - data: { - meta: { - ...baseSeting, - appConfig: { - ...(baseSeting?.appConfig || {}), - statistics: { - reads: totalViews._sum.views || 0, - courses: courseIds?.length || 0, - staffs: staffs || 0, - lectures: lectures?.length || 0, - }, - }, - }, - }, - }); -} -export async function updatePostViewCount(id: string, type: VisitType) { - const post = await db.post.findFirst({ - where: { id }, - select: { id: true, meta: true, type: true }, - }); - const metaFieldMap = { - [VisitType.READED]: 'views', - [VisitType.LIKE]: 'likes', - [VisitType.HATE]: 'hates', - }; - if (post?.type === PostType.LECTURE) { - const courseAncestry = await db.postAncestry.findFirst({ - where: { - descendantId: post?.id, - ancestor: { - type: PostType.COURSE, - }, - }, - select: { id: true, ancestorId: true }, - }); - const course = { id: courseAncestry.ancestorId }; - const lecturesAncestry = await db.postAncestry.findMany({ - where: { ancestorId: course.id, descendant: { type: PostType.LECTURE } }, - select: { - id: true, - descendantId: true, - }, - }); - const lectures = lecturesAncestry.map((ancestry) => ({ - id: ancestry.descendantId, - })); - const courseViews = await db.visit.aggregate({ - _sum: { - views: true, - }, - where: { - postId: { - in: [course.id, ...lectures.map((lecture) => lecture.id)], - }, - type: type, - }, - }); - await db.post.update({ - where: { id: course.id }, - data: { - [metaFieldMap[type]]: courseViews._sum.views || 0, - // meta: { - // ...((post?.meta as any) || {}), - // [metaFieldMap[type]]: courseViews._sum.views || 0, - // }, - }, - }); - } - const totalViews = await db.visit.aggregate({ - _sum: { - views: true, - }, - where: { - postId: id, - type: type, - }, - }); - await db.post.update({ - where: { id }, - data: { - [metaFieldMap[type]]: totalViews._sum.views || 0, - // meta: { - // ...((post?.meta as any) || {}), - // [metaFieldMap[type]]: totalViews._sum.views || 0, - // }, - }, - }); -} +// const baseSeting = appConfig.meta as BaseSetting; +// await db.appConfig.update({ +// where: { +// slug: AppConfigSlug.BASE_SETTING, +// }, +// data: { +// meta: { +// ...baseSeting, +// appConfig: { +// ...(baseSeting?.appConfig || {}), +// statistics: { +// reads: totalViews._sum.views || 0, +// courses: courseIds?.length || 0, +// staffs: staffs || 0, +// lectures: lectures?.length || 0, +// }, +// }, +// }, +// }, +// }); +// } +// export async function updatePostViewCount(id: string, type: VisitType) { +// const post = await db.post.findFirst({ +// where: { id }, +// select: { id: true, meta: true, type: true }, +// }); +// const metaFieldMap = { +// [VisitType.READED]: 'views', +// [VisitType.LIKE]: 'likes', +// [VisitType.HATE]: 'hates', +// }; +// if (post?.type === PostType.LECTURE) { +// const courseAncestry = await db.postAncestry.findFirst({ +// where: { +// descendantId: post?.id, +// ancestor: { +// type: PostType.COURSE, +// }, +// }, +// select: { id: true, ancestorId: true }, +// }); +// const course = { id: courseAncestry.ancestorId }; +// const lecturesAncestry = await db.postAncestry.findMany({ +// where: { ancestorId: course.id, descendant: { type: PostType.LECTURE } }, +// select: { +// id: true, +// descendantId: true, +// }, +// }); +// const lectures = lecturesAncestry.map((ancestry) => ({ +// id: ancestry.descendantId, +// })); +// const courseViews = await db.visit.aggregate({ +// _sum: { +// views: true, +// }, +// where: { +// postId: { +// in: [course.id, ...lectures.map((lecture) => lecture.id)], +// }, +// type: type, +// }, +// }); +// await db.post.update({ +// where: { id: course.id }, +// data: { +// [metaFieldMap[type]]: courseViews._sum.views || 0, +// // meta: { +// // ...((post?.meta as any) || {}), +// // [metaFieldMap[type]]: courseViews._sum.views || 0, +// // }, +// }, +// }); +// } +// const totalViews = await db.visit.aggregate({ +// _sum: { +// views: true, +// }, +// where: { +// postId: id, +// type: type, +// }, +// }); +// await db.post.update({ +// where: { id }, +// data: { +// [metaFieldMap[type]]: totalViews._sum.views || 0, +// // meta: { +// // ...((post?.meta as any) || {}), +// // [metaFieldMap[type]]: totalViews._sum.views || 0, +// // }, +// }, +// }); +// } diff --git a/apps/server/src/queue/worker/processor.ts b/apps/server/src/queue/worker/processor.ts index 86d428b..a1721be 100755 --- a/apps/server/src/queue/worker/processor.ts +++ b/apps/server/src/queue/worker/processor.ts @@ -6,61 +6,61 @@ import { ObjectType } from '@nice/common'; // updateCourseReviewStats, // } from '@server/models/course/utils'; import { QueueJobType } from '../types'; -import { - updateCourseEnrollmentStats, - updateCourseReviewStats, - updateParentLectureStats, -} from '@server/models/post/utils'; -import { - updatePostViewCount, - updateTotalCourseViewCount, -} from '../models/post/utils'; -const logger = new Logger('QueueWorker'); -export default async function processJob(job: Job) { - try { - if (job.name === QueueJobType.UPDATE_STATS) { - const { sectionId, courseId, type } = job.data; - // 处理 section 统计 - if (sectionId) { - await updateParentLectureStats(sectionId); - logger.debug(`Updated section stats for sectionId: ${sectionId}`); - } - // 如果没有 courseId,提前返回 - if (!courseId) { - return; - } - // 处理 course 相关统计 - switch (type) { - case ObjectType.LECTURE: - await updateParentLectureStats(courseId); - break; - case ObjectType.ENROLLMENT: - await updateCourseEnrollmentStats(courseId); - break; - case ObjectType.POST: - await updateCourseReviewStats(courseId); - break; - default: - logger.warn(`Unknown update stats type: ${type}`); - } +// import { +// updateCourseEnrollmentStats, +// updateCourseReviewStats, +// updateParentLectureStats, +// } from '@server/models/post/utils'; +// import { +// updatePostViewCount, +// updateTotalCourseViewCount, +// } from '../models/post/utils'; +// const logger = new Logger('QueueWorker'); +// export default async function processJob(job: Job) { +// try { +// if (job.name === QueueJobType.UPDATE_STATS) { +// const { sectionId, courseId, type } = job.data; +// // 处理 section 统计 +// if (sectionId) { +// await updateParentLectureStats(sectionId); +// logger.debug(`Updated section stats for sectionId: ${sectionId}`); +// } +// // 如果没有 courseId,提前返回 +// if (!courseId) { +// return; +// } +// // 处理 course 相关统计 +// switch (type) { +// case ObjectType.LECTURE: +// await updateParentLectureStats(courseId); +// break; +// case ObjectType.ENROLLMENT: +// await updateCourseEnrollmentStats(courseId); +// break; +// case ObjectType.POST: +// await updateCourseReviewStats(courseId); +// break; +// default: +// logger.warn(`Unknown update stats type: ${type}`); +// } - logger.debug( - `Updated course stats for courseId: ${courseId}, type: ${type}`, - ); - } - if (job.name === QueueJobType.UPDATE_POST_VISIT_COUNT) { - await updatePostViewCount(job.data.id, job.data.type); - } - if (job.name === QueueJobType.UPDATE_POST_STATE) { - await updatePostViewCount(job.data.id, job.data.type); - } - if (job.name === QueueJobType.UPDATE_TOTAL_COURSE_VIEW_COUNT) { - await updateTotalCourseViewCount(job.data.type); - } - } catch (error: any) { - logger.error( - `Error processing stats update job: ${error.message}`, - error.stack, - ); - } -} +// logger.debug( +// `Updated course stats for courseId: ${courseId}, type: ${type}`, +// ); +// } +// if (job.name === QueueJobType.UPDATE_POST_VISIT_COUNT) { +// await updatePostViewCount(job.data.id, job.data.type); +// } +// if (job.name === QueueJobType.UPDATE_POST_STATE) { +// await updatePostViewCount(job.data.id, job.data.type); +// } +// if (job.name === QueueJobType.UPDATE_TOTAL_COURSE_VIEW_COUNT) { +// await updateTotalCourseViewCount(job.data.type); +// } +// } catch (error: any) { +// logger.error( +// `Error processing stats update job: ${error.message}`, +// error.stack, +// ); +// } +// } diff --git a/apps/server/src/socket/realtime/realtime.server.ts b/apps/server/src/socket/realtime/realtime.server.ts index 18d7d99..e9dd560 100755 --- a/apps/server/src/socket/realtime/realtime.server.ts +++ b/apps/server/src/socket/realtime/realtime.server.ts @@ -2,20 +2,20 @@ import { Injectable, OnModuleInit } from "@nestjs/common"; import { WebSocketType } from "../types"; import { BaseWebSocketServer } from "../base/base-websocket-server"; import EventBus, { CrudOperation } from "@server/utils/event-bus"; -import { ObjectType, SocketMsgType, MessageDto, PostDto, PostType } from "@nice/common"; +import { ObjectType, SocketMsgType } from "@nice/common"; @Injectable() export class RealtimeServer extends BaseWebSocketServer implements OnModuleInit { onModuleInit() { EventBus.on("dataChanged", ({ data, type, operation }) => { - if (type === ObjectType.MESSAGE && operation === CrudOperation.CREATED) { - const receiverIds = (data as Partial).receivers.map(receiver => receiver.id) - this.sendToUsers(receiverIds, { type: SocketMsgType.NOTIFY, payload: { objectType: ObjectType.MESSAGE } }) - } + // if (type === ObjectType.MESSAGE && operation === CrudOperation.CREATED) { + // const receiverIds = (data as Partial).receivers.map(receiver => receiver.id) + // this.sendToUsers(receiverIds, { type: SocketMsgType.NOTIFY, payload: { objectType: ObjectType.MESSAGE } }) + // } - if (type === ObjectType.POST) { - const post = data as Partial + // if (type === ObjectType.POST) { + // const post = data as Partial - } + // } }) } diff --git a/apps/server/src/tasks/init/gendev.service.ts b/apps/server/src/tasks/init/gendev.service.ts index e5c3f9f..2e8da13 100755 --- a/apps/server/src/tasks/init/gendev.service.ts +++ b/apps/server/src/tasks/init/gendev.service.ts @@ -46,7 +46,6 @@ export class GenDevService { await this.generateDepartments(3, 6); await this.generateTerms(2, 6); await this.generateStaffs(4); - await this.generateCourses(8); } catch (err) { this.logger.error(err); } @@ -144,65 +143,7 @@ export class GenDevService { collectChildren(domainId); return children; } - private async generateCourses(countPerCate: number = 3) { - const titleList = [ - '计算机科学导论', - '数据结构与算法', - '网络安全', - '机器学习', - '数据库管理系统', - 'Web开发', - '移动应用开发', - '人工智能', - '计算机网络', - '操作系统', - '数字信号处理', - '无线通信', - '信息论', - '密码学', - '计算机图形学', - ]; - - if (!this.counts.courseCount) { - this.logger.log('Generating courses...'); - const depts = await db.department.findMany({ - select: { id: true, name: true }, - }); - const cates = await db.term.findMany({ - where: { - taxonomy: { slug: TaxonomySlug.CATEGORY }, - }, - select: { id: true, name: true }, - }); - const total = cates.length * countPerCate; - const levels = await db.term.findMany({ - where: { - taxonomy: { slug: TaxonomySlug.LEVEL }, - }, - select: { id: true, name: true }, - }); - for (const cate of cates) { - for (let i = 0; i < countPerCate; i++) { - const randomTitle = `${titleList[Math.floor(Math.random() * titleList.length)]} ${Math.random().toString(36).substring(7)}`; - const randomLevelId = - levels[Math.floor(Math.random() * levels.length)].id; - const randomDeptId = - depts[Math.floor(Math.random() * depts.length)].id; - - await this.createCourse( - randomTitle, - randomDeptId, - cate.id, - randomLevelId, - ); - this.courseGeneratedCount++; - this.logger.log( - `Generated ${this.courseGeneratedCount}/${total} course`, - ); - } - } - } - } + private async generateStaffs(countPerDept: number = 3) { if (this.counts.staffCount === 1) { this.logger.log('Generating staffs...'); @@ -266,31 +207,6 @@ export class GenDevService { throw error; // 向上抛出错误供上层处理 } } - private async createCourse( - title: string, - deptId: string, - cateId: string, - levelId: string, - ) { - const course = await db.post.create({ - data: { - type: PostType.COURSE, - title: title, - updatedAt: dayjs().toDate(), - depts: { - connect: { - id: deptId, - }, - }, - terms: { - connect: [cateId, levelId].map((id) => ({ - id: id, - })), - }, - }, - }); - return course; - } private async createDepartment( name: string, parentId?: string | null, diff --git a/apps/server/src/tasks/init/utils.ts b/apps/server/src/tasks/init/utils.ts index d55dcec..ea6e0d0 100755 --- a/apps/server/src/tasks/init/utils.ts +++ b/apps/server/src/tasks/init/utils.ts @@ -8,10 +8,8 @@ import { import dayjs from 'dayjs'; export interface DevDataCounts { deptCount: number; - staffCount: number; termCount: number; - courseCount: number; } export async function getCounts(): Promise { const counts = { @@ -19,11 +17,6 @@ export async function getCounts(): Promise { staffCount: await db.staff.count(), termCount: await db.term.count(), - courseCount: await db.post.count({ - where: { - type: PostType.COURSE, - }, - }), }; return counts; } diff --git a/apps/server/src/tasks/reminder/reminder.module.ts b/apps/server/src/tasks/reminder/reminder.module.ts index ed85068..e9e7d91 100755 --- a/apps/server/src/tasks/reminder/reminder.module.ts +++ b/apps/server/src/tasks/reminder/reminder.module.ts @@ -1,9 +1,8 @@ import { Module } from '@nestjs/common'; import { ReminderService } from './reminder.service'; -import { MessageModule } from '@server/models/message/message.module'; @Module({ - imports: [ MessageModule], + imports: [], providers: [ReminderService], exports: [ReminderService] }) diff --git a/apps/server/src/tasks/reminder/reminder.service.ts b/apps/server/src/tasks/reminder/reminder.service.ts index 41a0f8e..e8e939d 100755 --- a/apps/server/src/tasks/reminder/reminder.service.ts +++ b/apps/server/src/tasks/reminder/reminder.service.ts @@ -8,7 +8,6 @@ import { Injectable, Logger } from '@nestjs/common'; import dayjs from 'dayjs'; -import { MessageService } from '@server/models/message/message.service'; /** * 提醒服务类 @@ -25,7 +24,7 @@ export class ReminderService { * 构造函数 * @param messageService 消息服务实例 */ - constructor(private readonly messageService: MessageService) { } + constructor() { } /** * 生成提醒时间点 diff --git a/apps/server/src/trpc/trpc.module.ts b/apps/server/src/trpc/trpc.module.ts index 0eee4c9..55da50f 100755 --- a/apps/server/src/trpc/trpc.module.ts +++ b/apps/server/src/trpc/trpc.module.ts @@ -8,8 +8,6 @@ import { TermModule } from '@server/models/term/term.module'; import { TaxonomyModule } from '@server/models/taxonomy/taxonomy.module'; import { AuthModule } from '@server/auth/auth.module'; import { AppConfigModule } from '@server/models/app-config/app-config.module'; -import { MessageModule } from '@server/models/message/message.module'; -import { PostModule } from '@server/models/post/post.module'; import { WebSocketModule } from '@server/socket/websocket.module'; import { RoleMapModule } from '@server/models/rbac/rbac.module'; import { TransformModule } from '@server/models/transform/transform.module'; @@ -25,9 +23,7 @@ import { ResourceModule } from '@server/models/resource/resource.module'; TaxonomyModule, RoleMapModule, TransformModule, - MessageModule, AppConfigModule, - PostModule, WebSocketModule, ResourceModule, ], diff --git a/apps/server/src/trpc/trpc.router.ts b/apps/server/src/trpc/trpc.router.ts index c898b6b..81a1569 100755 --- a/apps/server/src/trpc/trpc.router.ts +++ b/apps/server/src/trpc/trpc.router.ts @@ -7,8 +7,6 @@ import { TrpcService } from '@server/trpc/trpc.service'; import * as trpcExpress from '@trpc/server/adapters/express'; import ws, { WebSocketServer } from 'ws'; import { AppConfigRouter } from '@server/models/app-config/app-config.router'; -import { MessageRouter } from '@server/models/message/message.router'; -import { PostRouter } from '@server/models/post/post.router'; import { RoleMapRouter } from '@server/models/rbac/rolemap.router'; import { TransformRouter } from '@server/models/transform/transform.router'; import { RoleRouter } from '@server/models/rbac/role.router'; @@ -18,7 +16,6 @@ export class TrpcRouter { logger = new Logger(TrpcRouter.name); constructor( private readonly trpc: TrpcService, - private readonly post: PostRouter, private readonly department: DepartmentRouter, private readonly staff: StaffRouter, private readonly term: TermRouter, @@ -27,7 +24,6 @@ export class TrpcRouter { private readonly rolemap: RoleMapRouter, private readonly transform: TransformRouter, private readonly app_config: AppConfigRouter, - private readonly message: MessageRouter, private readonly resource: ResourceRouter, ) {} getRouter() { @@ -35,14 +31,12 @@ export class TrpcRouter { } appRouter = this.trpc.router({ transform: this.transform.router, - post: this.post.router, department: this.department.router, staff: this.staff.router, term: this.term.router, taxonomy: this.taxonomy.router, role: this.role.router, rolemap: this.rolemap.router, - message: this.message.router, app_config: this.app_config.router, resource: this.resource.router, }); diff --git a/apps/server/src/upload/share-code.service.ts b/apps/server/src/upload/share-code.service.ts deleted file mode 100755 index f92cb50..0000000 --- a/apps/server/src/upload/share-code.service.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { Injectable, Logger, NotFoundException } from '@nestjs/common'; -import { customAlphabet } from 'nanoid-cjs'; -import { db } from '@nice/common'; -import { ShareCode, GenerateShareCodeResponse } from './types'; -import { Cron, CronExpression } from '@nestjs/schedule'; -import { ResourceService } from '@server/models/resource/resource.service'; - -@Injectable() -export class ShareCodeService { - private readonly logger = new Logger(ShareCodeService.name); - // 生成8位分享码,使用易读的字符 - private readonly generateCode = customAlphabet( - '23456789ABCDEFGHJKLMNPQRSTUVWXYZ', - 8, - ); - - constructor(private readonly resourceService: ResourceService) {} - - async generateShareCode( - fileId: string, - fileName?: string, - ): Promise { - try { - // 检查文件是否存在 - const resource = await this.resourceService.findUnique({ - where: { fileId }, - }); - console.log('完整 fileId:', fileId); // 确保与前端一致 - - if (!resource) { - throw new NotFoundException('文件不存在'); - } - - // 生成分享码 - const code = this.generateCode(); - const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24小时后过期 - - // 查找是否已有分享码记录 - const existingShareCode = await db.shareCode.findUnique({ - where: { fileId }, - }); - - if (existingShareCode) { - // 更新现有记录,但保留原有文件名 - await db.shareCode.update({ - where: { fileId }, - data: { - code, - expiresAt, - isUsed: false, - // 只在没有现有文件名且提供了新文件名时才更新文件名 - ...(fileName && !existingShareCode.fileName ? { fileName } : {}), - }, - }); - } else { - // 创建新记录 - await db.shareCode.create({ - data: { - code, - fileId, - expiresAt, - isUsed: false, - fileName: fileName || null, - }, - }); - } - this.logger.log(`Generated share code ${code} for file ${fileId}`); - return { - code, - expiresAt, - }; - } catch (error) { - this.logger.error('Failed to generate share code', error); - throw error; - } - } - - async validateAndUseCode(code: string): Promise { - try { - console.log(`尝试验证分享码: ${code}`); - - // 查找有效的分享码 - const shareCode = await db.shareCode.findFirst({ - where: { - code, - isUsed: false, - expiresAt: { gt: new Date() }, - }, - }); - - console.log('查询结果:', shareCode); - - if (!shareCode) { - console.log('分享码无效或已过期'); - return null; - } - - // 标记分享码为已使用 - // await db.shareCode.update({ - // where: { id: shareCode.id }, - // data: { isUsed: true }, - // }); - - // 记录使用日志 - this.logger.log(`Share code ${code} used for file ${shareCode.fileId}`); - - // 返回完整的分享码信息,包括文件名 - return shareCode; - } catch (error) { - this.logger.error('Failed to validate share code', error); - return null; - } - } - - // 每天清理过期的分享码 - @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) - async cleanupExpiredShareCodes() { - try { - const result = await db.shareCode.deleteMany({ - where: { - OR: [{ expiresAt: { lt: new Date() } }, { isUsed: true }], - }, - }); - - this.logger.log(`Cleaned up ${result.count} expired share codes`); - } catch (error) { - this.logger.error('Failed to cleanup expired share codes', error); - } - } - - // 获取分享码信息 - async getShareCodeInfo(code: string): Promise { - try { - return await db.shareCode.findFirst({ - where: { code }, - }); - } catch (error) { - this.logger.error('Failed to get share code info', error); - return null; - } - } - - // 检查文件是否已经生成过分享码 - async hasActiveShareCode(fileId: string): Promise { - try { - const activeCode = await db.shareCode.findFirst({ - where: { - fileId, - isUsed: false, - expiresAt: { gt: new Date() }, - }, - }); - - return !!activeCode; - } catch (error) { - this.logger.error('Failed to check active share code', error); - return false; - } - } - - // 获取文件的所有分享记录 - async getFileShareHistory(fileId: string) { - try { - return await db.shareCode.findMany({ - where: { fileId }, - orderBy: { createdAt: 'desc' }, - }); - } catch (error) { - this.logger.error('Failed to get file share history', error); - return []; - } - } -} diff --git a/apps/server/src/upload/upload.controller.ts b/apps/server/src/upload/upload.controller.ts index fe765e9..4251037 100755 --- a/apps/server/src/upload/upload.controller.ts +++ b/apps/server/src/upload/upload.controller.ts @@ -17,7 +17,6 @@ import { } from '@nestjs/common'; import { Request, Response } from 'express'; import { TusService } from './tus.service'; -import { ShareCodeService } from './share-code.service'; import { ResourceService } from '@server/models/resource/resource.service'; interface ResourceMeta { @@ -30,7 +29,6 @@ interface ResourceMeta { export class UploadController { constructor( private readonly tusService: TusService, - private readonly shareCodeService: ShareCodeService, private readonly resourceService: ResourceService, ) {} // @Post() @@ -52,52 +50,7 @@ export class UploadController { async handlePost(@Req() req: Request, @Res() res: Response) { return this.tusService.handleTus(req, res); } - @Get('share/:code') - async validateShareCode(@Param('code') code: string) { - console.log('收到验证分享码请求,code:', code); - - const shareCode = await this.shareCodeService.validateAndUseCode(code); - console.log('验证分享码结果:', shareCode); - - if (!shareCode) { - throw new NotFoundException('分享码无效或已过期'); - } - - // 获取文件信息 - const resource = await this.resourceService.findUnique({ - where: { fileId: shareCode.fileId }, - }); - console.log('获取到的资源信息:', resource); - const {filename} = resource.meta as any as ResourceMeta - if (!resource) { - throw new NotFoundException('文件不存在'); - } - - // 直接返回正确的数据结构 - const response = { - fileId: shareCode.fileId, - fileName:filename || 'downloaded_file', - code: shareCode.code, - expiresAt: shareCode.expiresAt - }; - - console.log('返回给前端的数据:', response); // 添加日志 - return response; - } - - @Get('share/info/:code') - async getShareCodeInfo(@Param('code') code: string) { - const info = await this.shareCodeService.getShareCodeInfo(code); - if (!info) { - throw new NotFoundException('分享码不存在'); - } - return info; - } - - @Get('share/history/:fileId') - async getFileShareHistory(@Param('fileId') fileId: string) { - return this.shareCodeService.getFileShareHistory(fileId); - } + @Get('/*') async handleGet(@Req() req: Request, @Res() res: Response) { return this.tusService.handleTus(req, res); @@ -113,80 +66,5 @@ export class UploadController { async handleUpload(@Req() req: Request, @Res() res: Response) { return this.tusService.handleTus(req, res); } - @Post('share/:fileId(*)') - async generateShareCode(@Param('fileId') fileId: string) { - try { - console.log('收到生成分享码请求,fileId:', fileId); - const result = await this.shareCodeService.generateShareCode(fileId); - console.log('生成分享码结果:', result); - return result; - } catch (error) { - console.error('生成分享码错误:', error); - throw new HttpException( - { - message: (error as Error).message || '生成分享码失败', - error: 'SHARE_CODE_GENERATION_FAILED' - }, - HttpStatus.INTERNAL_SERVER_ERROR - ); - } - } - - @Post('filename') - async saveFileName(@Body() data: { fileId: string; fileName: string }) { - try { - console.log('收到保存文件名请求:', data); - - // 检查参数 - if (!data.fileId || !data.fileName) { - throw new HttpException( - { message: '缺少必要参数' }, - HttpStatus.BAD_REQUEST - ); - } - // 保存文件名 - await this.resourceService.saveFileName(data.fileId, data.fileName); - console.log('文件名保存成功:', data.fileName, '对应文件ID:', data.fileId); - - return { success: true }; - } catch (error) { - console.error('保存文件名失败:', error); - throw new HttpException( - { - message: '保存文件名失败', - error: (error instanceof Error) ? error.message : String(error) - }, - HttpStatus.INTERNAL_SERVER_ERROR - ); - } - } - - @Get('download/:fileId') - async downloadFile(@Param('fileId') fileId: string, @Res() res: Response) { - try { - // 获取文件信息 - const resource = await this.resourceService.findUnique({ - where: { fileId }, - }); - - if (!resource) { - throw new NotFoundException('文件不存在'); - } - - // 获取原始文件名 - const fileName = await this.resourceService.getFileName(fileId) || 'downloaded-file'; - - // 设置响应头,包含原始文件名 - res.setHeader( - 'Content-Disposition', - `attachment; filename="${encodeURIComponent(fileName)}"` - ); - - // 其他下载逻辑... - - } catch (error) { - // 错误处理... - } - } } diff --git a/apps/server/src/upload/upload.module.ts b/apps/server/src/upload/upload.module.ts index e1a6a56..7cddd56 100755 --- a/apps/server/src/upload/upload.module.ts +++ b/apps/server/src/upload/upload.module.ts @@ -3,7 +3,6 @@ import { UploadController } from './upload.controller'; import { BullModule } from '@nestjs/bullmq'; import { TusService } from './tus.service'; import { ResourceModule } from '@server/models/resource/resource.module'; -import { ShareCodeService } from './share-code.service'; @Module({ imports: [ BullModule.registerQueue({ @@ -12,6 +11,6 @@ import { ShareCodeService } from './share-code.service'; ResourceModule, ], controllers: [UploadController], - providers: [TusService, ShareCodeService], + providers: [TusService], }) export class UploadModule {} diff --git a/apps/server/src/utils/event-bus.ts b/apps/server/src/utils/event-bus.ts index 3f96688..8121655 100755 --- a/apps/server/src/utils/event-bus.ts +++ b/apps/server/src/utils/event-bus.ts @@ -1,5 +1,5 @@ import mitt from 'mitt'; -import { ObjectType, UserProfile, MessageDto, VisitType } from '@nice/common'; +import { ObjectType, UserProfile, VisitType } from '@nice/common'; export enum CrudOperation { CREATED, UPDATED, @@ -24,7 +24,6 @@ type Events = { updateTotalCourseViewCount: { visitType: VisitType | string; }; - onMessageCreated: { data: Partial }; dataChanged: { type: string; operation: CrudOperation; data: any }; }; const EventBus = mitt(); diff --git a/packages/client/src/api/hooks/index.ts b/packages/client/src/api/hooks/index.ts index ffe1cf5..978f71e 100755 --- a/packages/client/src/api/hooks/index.ts +++ b/packages/client/src/api/hooks/index.ts @@ -6,6 +6,4 @@ export * from "./useRole" export * from "./useRoleMap" export * from "./useTransform" export * from "./useTaxonomy" -export * from "./useMessage" -export * from "./usePost" export * from "./useEntity" diff --git a/packages/client/src/api/hooks/useMessage.ts b/packages/client/src/api/hooks/useMessage.ts deleted file mode 100755 index 97d2d97..0000000 --- a/packages/client/src/api/hooks/useMessage.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { useEntity } from "./useEntity"; - -export function useMessage() { - return useEntity("message"); -} diff --git a/packages/client/src/api/hooks/usePost.ts b/packages/client/src/api/hooks/usePost.ts old mode 100755 new mode 100644 index 88c6326..bb0a5f3 --- a/packages/client/src/api/hooks/usePost.ts +++ b/packages/client/src/api/hooks/usePost.ts @@ -1,5 +1,5 @@ import { MutationResult, useEntity } from "./useEntity"; -export function usePost() { - return useEntity("post"); -} +// export function usePost() { +// return useEntity("post"); +// } diff --git a/packages/client/src/api/hooks/useStaff.ts b/packages/client/src/api/hooks/useStaff.ts index aed3aa7..d45e527 100755 --- a/packages/client/src/api/hooks/useStaff.ts +++ b/packages/client/src/api/hooks/useStaff.ts @@ -26,7 +26,6 @@ export function useStaff() { const update = api.staff.update.useMutation({ onSuccess: (result) => { queryClient.invalidateQueries({ queryKey }); - queryClient.invalidateQueries({ queryKey: getQueryKey(api.post) }); emitDataChange( ObjectType.STAFF, result as any, diff --git a/packages/common/prisma/schema.prisma b/packages/common/prisma/schema.prisma index 0732b25..9948e66 100755 --- a/packages/common/prisma/schema.prisma +++ b/packages/common/prisma/schema.prisma @@ -44,7 +44,6 @@ model Term { createdBy String? @map("created_by") depts Department[] @relation("department_term") hasChildren Boolean? @default(false) @map("has_children") - posts Post[] @relation("post_term") @@index([name]) // 对name字段建立索引,以加快基于name的查找速度 @@index([parentId]) // 对parentId字段建立索引,以加快基于parentId的查找速度 @@ -88,12 +87,7 @@ model Staff { deletedAt DateTime? @map("deleted_at") officerId String? @map("officer_id") - // watchedPost Post[] @relation("post_watch_staff") - posts Post[] - - learningPosts Post[] @relation("post_student") registerToken String? - teachedPosts PostInstructor[] ownedResources Resource[] @@index([officerId]) @@ -108,7 +102,6 @@ model Department { id String @id @default(cuid()) name String order Float? - posts Post[] @relation("post_dept") ancestors DeptAncestry[] @relation("DescendantToAncestor") descendants DeptAncestry[] @relation("AncestorToDescendant") parentId String? @map("parent_id") @@ -184,91 +177,7 @@ model AppConfig { @@map("app_config") } -model Post { - // 字符串类型字段 - id String @id @default(cuid()) // 帖子唯一标识,使用 cuid() 生成默认值 - type String? // Post类型,课程、章节、小节、讨论都用Post实现 - level String? - state String? - title String? // 帖子标题,可为空 - subTitle String? - content String? // 帖子内容,可为空 - important Boolean? //是否重要/精选/突出 - domainId String? @map("domain_id") - terms Term[] @relation("post_term") - order Float? @default(0) @map("order") - duration Int? - rating Int? @default(0) - students Staff[] @relation("post_student") - depts Department[] @relation("post_dept") - views Int @default(0) @map("views") - hates Int @default(0) @map("hates") - likes Int @default(0) @map("likes") - // 索引 - // 日期时间类型字段 - createdAt DateTime @default(now()) @map("created_at") - publishedAt DateTime? @map("published_at") // 发布时间 - updatedAt DateTime @map("updated_at") - deletedAt DateTime? @map("deleted_at") // 删除时间,可为空 - instructors PostInstructor[] - // 关系类型字段 - authorId String? @map("author_id") - author Staff? @relation(fields: [authorId], references: [id]) // 帖子作者,关联 Staff 模型 - parentId String? @map("parent_id") - parent Post? @relation("PostChildren", fields: [parentId], references: [id]) // 父级帖子,关联 Post 模型 - children Post[] @relation("PostChildren") // 子级帖子列表,关联 Post 模型 - hasChildren Boolean? @default(false) @map("has_children") - // 闭包表关系 - ancestors PostAncestry[] @relation("DescendantPosts") - descendants PostAncestry[] @relation("AncestorPosts") - resources Resource[] // 附件列表 - meta Json? // 封面url 视频url objectives具体的学习目标 rating评分Int - // 索引 - @@index([type, domainId]) - @@index([authorId, type]) - @@index([parentId, type]) - @@index([parentId, order]) - @@index([createdAt]) - @@index([updatedAt]) - @@index([type, publishedAt]) - @@index([state]) - @@index([level]) - @@index([views]) - @@index([important]) - @@map("post") -} - -model PostAncestry { - id String @id @default(cuid()) - ancestorId String? @map("ancestor_id") - descendantId String @map("descendant_id") - relDepth Int @map("rel_depth") - ancestor Post? @relation("AncestorPosts", fields: [ancestorId], references: [id]) - descendant Post @relation("DescendantPosts", fields: [descendantId], references: [id]) - - // 复合索引优化 - // 索引建议 - @@index([ancestorId]) // 针对祖先的查询 - @@index([descendantId]) // 针对后代的查询 - @@index([ancestorId, descendantId]) // 组合索引,用于查询特定的祖先-后代关系 - @@index([relDepth]) // 根据关系深度的查询 - @@map("post_ancestry") -} - -model PostInstructor { - postId String @map("post_id") - instructorId String @map("instructor_id") - role String @map("role") - createdAt DateTime @default(now()) @map("created_at") - order Float? @default(0) @map("order") - - post Post @relation(fields: [postId], references: [id]) - instructor Staff @relation(fields: [instructorId], references: [id]) - - @@id([postId, instructorId]) - @@map("post_instructor") -} model Resource { id String @id @default(cuid()) @map("id") @@ -289,8 +198,7 @@ model Resource { isPublic Boolean? @default(true) @map("is_public") owner Staff? @relation(fields: [ownerId], references: [id]) ownerId String? @map("owner_id") - post Post? @relation(fields: [postId], references: [id]) - postId String? @map("post_id") + // 索引 @@index([type]) @@index([createdAt]) diff --git a/packages/common/src/models/index.ts b/packages/common/src/models/index.ts index 6273433..fc7d6e7 100755 --- a/packages/common/src/models/index.ts +++ b/packages/common/src/models/index.ts @@ -1,8 +1,6 @@ export * from "./department"; -export * from "./message"; export * from "./staff"; export * from "./term"; -export * from "./post"; export * from "./rbac"; export * from "./select"; export * from "./resource"; diff --git a/packages/common/src/models/message.ts b/packages/common/src/models/message.ts deleted file mode 100755 index 6e9d0be..0000000 --- a/packages/common/src/models/message.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Message, Staff } from "@prisma/client"; - -export type MessageDto = Message & { - readed: boolean; - receivers: Staff[]; - sender: Staff; -}; diff --git a/packages/common/src/models/post.ts b/packages/common/src/models/post.ts deleted file mode 100755 index fa76dfb..0000000 --- a/packages/common/src/models/post.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { - Post, - Department, - Staff, - Enrollment, - Taxonomy, - Term, -} from "@prisma/client"; -import { StaffDto } from "./staff"; -import { TermDto } from "./term"; -import { ResourceDto } from "./resource"; -import { DepartmentDto } from "./department"; - -export type PostComment = { - id: string; - type: string; - title: string; - content: string; - authorId: string; - domainId: string; - referenceId: string; - resources: string[]; - createdAt: Date; - updatedAt: Date; - parentId: string; - author: { - id: string; - showname: string; - username: string; - avatar: string; - }; -}; -export type PostDto = Post & { - readed: boolean; - readedCount: number; - author: StaffDto; - limitedComments: PostComment[]; - commentsCount: number; - perms?: { - delete: boolean; - // edit: boolean; - }; - meta?: PostMeta; - watchableDepts: Department[]; - watchableStaffs: Staff[]; - terms: TermDto[]; - depts: DepartmentDto[]; - studentIds?: string[]; -}; -export type PostMeta = { - thumbnail?: string; - views?: number; - likes?: number; - hates?: number; -}; -export type LectureMeta = PostMeta & { - type?: string; - - videoUrl?: string; - videoThumbnail?: string; - videoIds?: string[]; - videoThumbnailIds?: string[]; -}; - -export type Lecture = Post & { - courseId?: string; - resources?: ResourceDto[]; - meta?: LectureMeta; -}; - -export type SectionMeta = PostMeta & { - objectives?: string[]; -}; -export type Section = Post & { - courseId?: string; - meta?: SectionMeta; -}; -export type SectionDto = Section & { - lectures: Lecture[]; -}; -export type CourseMeta = PostMeta & { - objectives?: string[]; -}; -export type Course = PostDto & { - meta?: CourseMeta; -}; -export type CourseDto = Course & { - enrollments?: Enrollment[]; - sections?: SectionDto[]; - terms: TermDto[]; - lectureCount?: number; - depts: Department[]; - studentIds: string[]; -}; - -export type Summary = { - id: string; - text: string; - parent: string; - start: number; - end: number; -}; -export type NodeObj = { - topic: string; - id: string; - style?: { - fontSize?: string; - color?: string; - background?: string; - fontWeight?: string; - }; - children?: NodeObj[]; -}; -export type Arrow = { - id: string; - label: string; - from: string; - to: string; - delta1: { - x: number; - y: number; - }; - delta2: { - x: number; - y: number; - }; -}; -export type PathMeta = PostMeta & { - nodeData: NodeObj; - arrows?: Arrow[]; - summaries?: Summary[]; - direction?: number; -}; -export type PathDto = PostDto & { - meta: PathMeta; -}; diff --git a/packages/common/src/models/select.ts b/packages/common/src/models/select.ts index ba35ec2..4f91ed1 100755 --- a/packages/common/src/models/select.ts +++ b/packages/common/src/models/select.ts @@ -1,138 +1,2 @@ import { Prisma } from "@prisma/client"; -export const postDetailSelect: Prisma.PostSelect = { - id: true, - type: true, - title: true, - content: true, - resources: true, - parent: true, - parentId: true, - // watchableDepts: true, - // watchableStaffs: true, - updatedAt: true, - terms: { - select: { - id: true, - name: true, - taxonomyId: true, - taxonomy: { - select: { - id: true, - slug: true, - }, - }, - }, - }, - depts: true, - author: { - select: { - id: true, - showname: true, - avatar: true, - department: { - select: { - id: true, - name: true, - }, - }, - domain: { - select: { - id: true, - name: true, - }, - }, - }, - }, - meta: true, - views: true, -}; -export const postUnDetailSelect: Prisma.PostSelect = { - id: true, - type: true, - title: true, - views: true, - parent: true, - parentId: true, - content: true, - resources: true, - updatedAt: true, - author: { - select: { - id: true, - showname: true, - avatar: true, - department: { - select: { - id: true, - name: true, - }, - }, - domain: { - select: { - id: true, - name: true, - }, - }, - }, - }, -}; -export const messageDetailSelect: Prisma.MessageSelect = { - id: true, - sender: true, - content: true, - - title: true, - url: true, - option: true, - intent: true, -}; -export const courseDetailSelect: Prisma.PostSelect = { - id: true, - title: true, - subTitle: true, - views: true, - type: true, - author: true, - authorId: true, - content: true, - depts: true, - // isFeatured: true, - createdAt: true, - updatedAt: true, - deletedAt: true, - // 关联表选择 - terms: { - select: { - id: true, - name: true, - taxonomyId: true, - taxonomy: { - select: { - id: true, - slug: true, - }, - }, - }, - }, - enrollments: { - select: { - id: true, - }, - }, - meta: true, - rating: true, -}; -export const lectureDetailSelect: Prisma.PostSelect = { - id: true, - title: true, - subTitle: true, - content: true, - resources: true, - views: true, - createdAt: true, - updatedAt: true, - // 关联表选择 - - meta: true, -}; diff --git a/packages/common/src/schema.ts b/packages/common/src/schema.ts index 95b3af3..431e0b2 100755 --- a/packages/common/src/schema.ts +++ b/packages/common/src/schema.ts @@ -1,7 +1,6 @@ import { z, ZodType } from "zod"; import { ObjectType } from "./enum"; import { Prisma } from "."; -const PostCreateArgsSchema: ZodType = z.any(); export const AuthSchema = { signUpRequest: z.object({ username: z.string(), @@ -390,46 +389,4 @@ export const BaseCursorSchema = z.object({ createEndDate: z.date().nullish(), deptId: z.string().nullish(), }); -export const CourseMethodSchema = { - createLecture: z.object({ - sectionId: z.string().nullish(), - type: z.string().nullish(), // 视频还是文章 - title: z.string().nullish(), //单课标题 - content: z.string().nullish(), //文章内容 - resourceIds: z.array(z.string()).nullish(), - }), - createSection: z.object({ - courseId: z.string().nullish(), - title: z.string().nullish(), //单课标题 - lectures: z - .array( - z.object({ - type: z.string().nullish(), - title: z.string().nullish(), - content: z.string().nullish(), - resourceIds: z.array(z.string()).nullish(), - }) - ) - .nullish(), - }), - createCourse: z.object({ - courseDetail: PostCreateArgsSchema, - sections: z - .array( - z.object({ - title: z.string(), - lectures: z - .array( - z.object({ - type: z.string(), - title: z.string(), - content: z.string().optional(), - resourceIds: z.array(z.string()).optional(), - }) - ) - .optional(), - }) - ) - .optional(), - }), -}; + diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index bdd007d..4c53d0f 100755 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -2,13 +2,10 @@ import type { Staff, Department, Term, - Message, - Post, RoleMap, // Section, // Lecture, // Course, - Enrollment, } from "@prisma/client"; import { SocketMsgType, RolePerms } from "./enum"; import { RowRequestSchema } from "./schema";