From ad5fb5038d99ef42e9b8a833ce2e41b3afff8f2c Mon Sep 17 00:00:00 2001 From: ditiqi Date: Wed, 22 Jan 2025 15:40:22 +0800 Subject: [PATCH 1/2] add 2025-0122-0340 --- packages/common/package.json | 67 ++++++++++++++++++------------------ packages/common/src/types.ts | 11 ------ 2 files changed, 34 insertions(+), 44 deletions(-) diff --git a/packages/common/package.json b/packages/common/package.json index eb5c31c..07f2965 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,34 +1,35 @@ { - "name": "@nice/common", - "version": "1.0.0", - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "private": true, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "generate": "pnpm prisma generate", - "build": "pnpm generate && tsup", - "dev": "pnpm generate && tsup --watch ", - "studio": "pnpm prisma studio", - "db:clear": "rm -rf prisma/migrations && pnpm prisma migrate dev --name init", - "postinstall": "pnpm generate" - }, - "dependencies": { - "@prisma/client": "5.17.0", - "prisma": "5.17.0" - }, - "peerDependencies": { - "zod": "^3.23.8", - "yjs": "^13.6.20", - "lib0": "^0.2.98" - }, - "devDependencies": { - "@types/node": "^20.3.1", - "ts-node": "^10.9.1", - "typescript": "^5.5.4", - "concurrently": "^8.0.0", - "tsup": "^8.3.5", - "rimraf": "^6.0.1" - } -} \ No newline at end of file + "name": "@nice/common", + "version": "1.0.0", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "generate": "pnpm prisma generate", + "build": "pnpm generate && tsup", + "dev": "pnpm generate && tsup --watch ", + "dev-static": "pnpm generate && tsup --no-watch ", + "studio": "pnpm prisma studio", + "db:clear": "rm -rf prisma/migrations && pnpm prisma migrate dev --name init", + "postinstall": "pnpm generate" + }, + "dependencies": { + "@prisma/client": "5.17.0", + "prisma": "5.17.0" + }, + "peerDependencies": { + "zod": "^3.23.8", + "yjs": "^13.6.20", + "lib0": "^0.2.98" + }, + "devDependencies": { + "@types/node": "^20.3.1", + "ts-node": "^10.9.1", + "typescript": "^5.5.4", + "concurrently": "^8.0.0", + "tsup": "^8.3.5", + "rimraf": "^6.0.1" + } +} diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 41c1783..1b247cd 100755 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -5,10 +5,6 @@ import type { Message, Post, RoleMap, - Section, - Lecture, - Course, - Enrollment, } from "@prisma/client"; import { SocketMsgType, RolePerms } from "./enum"; import { RowRequestSchema } from "./schema"; @@ -156,13 +152,6 @@ export type DepartmentDto = Department & { export type RoleMapDto = RoleMap & { staff: StaffDto; }; -export type SectionDto = Section & { - lectures: Lecture[]; -}; -export type CourseDto = Course & { - enrollments: Enrollment[]; - sections: SectionDto[]; -}; export interface BaseSetting { appConfig?: { splashScreen?: string; From 0da8a60bd6dbec9d1ffe1ecdedb4f9523ee3d848 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Wed, 22 Jan 2025 18:56:27 +0800 Subject: [PATCH 2/2] add 202501221855 --- apps/server/src/models/base/base.service.ts | 81 ++++--- .../server/src/models/course/course.module.ts | 10 - .../server/src/models/course/course.router.ts | 86 ------- .../server/src/models/course/course.schema.ts | 0 .../src/models/course/course.service.ts | 78 ------ apps/server/src/models/course/utils.ts | 46 ---- .../src/models/enrollment/enroll.schema.ts | 11 - .../models/enrollment/enrollment.module.ts | 9 - .../models/enrollment/enrollment.router.ts | 54 ----- .../models/enrollment/enrollment.service.ts | 74 ------ .../src/models/lecture/lecture.module.ts | 10 - .../src/models/lecture/lecture.router.ts | 70 ------ .../src/models/lecture/lecture.service.ts | 35 --- apps/server/src/models/lecture/utils.ts | 39 --- .../server/src/models/post/post.controller.ts | 5 +- apps/server/src/models/post/post.router.ts | 16 +- apps/server/src/models/post/post.service.ts | 55 ++--- apps/server/src/models/post/utils.ts | 100 ++++---- .../src/models/section/section.module.ts | 10 - .../src/models/section/section.router.ts | 70 ------ .../src/models/section/section.service.ts | 24 -- apps/server/src/models/visit/visit.service.ts | 18 +- apps/server/src/queue/worker/processor.ts | 79 +++--- apps/server/src/tasks/init/init.service.ts | 15 +- apps/server/src/trpc/trpc.module.ts | 11 +- apps/server/src/trpc/trpc.router.ts | 19 +- apps/server/src/trpc/trpc.service.ts | 14 +- apps/web/src/app/main/home/page.tsx | 169 +++++++------ .../editor/context/CourseEditorContext.tsx | 151 ++++++------ apps/web/src/providers/auth-provider.tsx | 3 + packages/client/package.json | 81 +++---- packages/client/src/api/hooks/index.ts | 1 - packages/client/src/api/hooks/useCourse.ts | 56 ----- packages/client/src/api/hooks/usePost.ts | 12 +- packages/client/src/index.ts | 2 +- packages/client/src/upload/index.ts | 3 - packages/client/src/upload/types.ts | 13 - packages/client/src/upload/uploadManager.ts | 225 ------------------ packages/client/src/upload/useUpload.ts | 56 ----- packages/common/prisma/schema.prisma | 5 +- packages/common/src/types.ts | 4 + packages/tus/package.json | 1 + web-app/error.html | 1 + web-app/index.html | 1 + 44 files changed, 438 insertions(+), 1385 deletions(-) delete mode 100644 apps/server/src/models/course/course.module.ts delete mode 100644 apps/server/src/models/course/course.router.ts delete mode 100644 apps/server/src/models/course/course.schema.ts delete mode 100644 apps/server/src/models/course/course.service.ts delete mode 100644 apps/server/src/models/course/utils.ts delete mode 100644 apps/server/src/models/enrollment/enroll.schema.ts delete mode 100644 apps/server/src/models/enrollment/enrollment.module.ts delete mode 100644 apps/server/src/models/enrollment/enrollment.router.ts delete mode 100644 apps/server/src/models/enrollment/enrollment.service.ts delete mode 100644 apps/server/src/models/lecture/lecture.module.ts delete mode 100644 apps/server/src/models/lecture/lecture.router.ts delete mode 100644 apps/server/src/models/lecture/lecture.service.ts delete mode 100644 apps/server/src/models/lecture/utils.ts delete mode 100644 apps/server/src/models/section/section.module.ts delete mode 100644 apps/server/src/models/section/section.router.ts delete mode 100644 apps/server/src/models/section/section.service.ts delete mode 100644 packages/client/src/api/hooks/useCourse.ts delete mode 100644 packages/client/src/upload/index.ts delete mode 100644 packages/client/src/upload/types.ts delete mode 100644 packages/client/src/upload/uploadManager.ts delete mode 100644 packages/client/src/upload/useUpload.ts create mode 100644 web-app/error.html create mode 100644 web-app/index.html diff --git a/apps/server/src/models/base/base.service.ts b/apps/server/src/models/base/base.service.ts index ffbcfc3..d1b8b16 100644 --- a/apps/server/src/models/base/base.service.ts +++ b/apps/server/src/models/base/base.service.ts @@ -37,10 +37,8 @@ export class BaseService< constructor( protected prisma: PrismaClient, protected objectType: string, - protected enableOrder: boolean = false - ) { - - } + protected enableOrder: boolean = false, + ) {} /** * Retrieves the name of the model dynamically. @@ -51,7 +49,7 @@ export class BaseService< return modelName; } private getModel(tx?: TransactionType): D { - return tx?.[this.objectType] || this.prisma[this.objectType] as D; + return tx?.[this.objectType] || (this.prisma[this.objectType] as D); } /** * Error handling helper function @@ -85,7 +83,9 @@ export class BaseService< */ async findUnique(args: A['findUnique']): Promise { try { - return this.getModel().findUnique(args as any) as Promise; + return this.getModel().findUnique(args as any) as Promise< + R['findUnique'] + >; } catch (error) { this.handleError(error, 'read'); } @@ -152,26 +152,27 @@ export class BaseService< * const newUser = await service.create({ data: { name: 'John Doe' } }); */ async create(args: A['create'], params?: any): Promise { - try { - if (this.enableOrder && !(args as any).data.order) { // 查找当前最大的 order 值 - const maxOrderItem = await this.getModel(params?.tx).findFirst({ - orderBy: { order: 'desc' } - }) as any; + const maxOrderItem = (await this.getModel(params?.tx).findFirst({ + orderBy: { order: 'desc' }, + })) as any; // 设置新记录的 order 值 - const newOrder = maxOrderItem ? maxOrderItem.order + this.ORDER_INTERVAL : 1; + const newOrder = maxOrderItem + ? maxOrderItem.order + this.ORDER_INTERVAL + : 1; // 将 order 添加到创建参数中 (args as any).data.order = newOrder; } - return this.getModel(params?.tx).create(args as any) as Promise; + return this.getModel(params?.tx).create(args as any) as Promise< + R['create'] + >; } catch (error) { this.handleError(error, 'create'); } } - /** * Creates multiple new records with the given data. * @param args - Arguments to create multiple records. @@ -179,9 +180,14 @@ export class BaseService< * @example * const newUsers = await service.createMany({ data: [{ name: 'John' }, { name: 'Jane' }] }); */ - async createMany(args: A['createMany'], params?: any): Promise { + async createMany( + args: A['createMany'], + params?: any, + ): Promise { try { - return this.getModel(params?.tx).createMany(args as any) as Promise; + return this.getModel(params?.tx).createMany(args as any) as Promise< + R['createMany'] + >; } catch (error) { this.handleError(error, 'create'); } @@ -196,8 +202,9 @@ export class BaseService< */ async update(args: A['update'], params?: any): Promise { try { - - return this.getModel(params?.tx).update(args as any) as Promise; + return this.getModel(params?.tx).update(args as any) as Promise< + R['update'] + >; } catch (error) { this.handleError(error, 'update'); } @@ -251,7 +258,9 @@ export class BaseService< */ async delete(args: A['delete'], params?: any): Promise { try { - return this.getModel(params?.tx).delete(args as any) as Promise; + return this.getModel(params?.tx).delete(args as any) as Promise< + R['delete'] + >; } catch (error) { this.handleError(error, 'delete'); } @@ -309,10 +318,14 @@ export class BaseService< * @example * const deleteResult = await service.deleteMany({ where: { isActive: false } }); */ - async deleteMany(args: A['deleteMany'], params?: any): Promise { + async deleteMany( + args: A['deleteMany'], + params?: any, + ): Promise { try { - - return this.getModel(params?.tx).deleteMany(args as any) as Promise; + return this.getModel(params?.tx).deleteMany(args as any) as Promise< + R['deleteMany'] + >; } catch (error) { this.handleError(error, 'delete'); } @@ -327,7 +340,9 @@ export class BaseService< */ async updateMany(args: A['updateMany']): Promise { try { - return this.getModel().updateMany(args as any) as Promise; + return this.getModel().updateMany(args as any) as Promise< + R['updateMany'] + >; } catch (error) { this.handleError(error, 'update'); } @@ -420,8 +435,7 @@ export class BaseService< data: { ...data, deletedAt: null } as any, }) as Promise; } catch (error) { - this.handleError(error, "update"); - + this.handleError(error, 'update'); } } @@ -436,25 +450,25 @@ export class BaseService< page?: number; pageSize?: number; where?: WhereArgs; - select?: SelectArgs + select?: SelectArgs; }): Promise<{ items: R['findMany']; totalPages: number }> { const { page = 1, pageSize = 10, where, select } = args; try { // 获取总记录数 - const total = await this.getModel().count({ where }) as number; + const total = (await this.getModel().count({ where })) as number; // 获取分页数据 - const items = await this.getModel().findMany({ + const items = (await this.getModel().findMany({ where, select, skip: (page - 1) * pageSize, take: pageSize, - } as any) as R['findMany']; + } as any)) as R['findMany']; // 计算总页数 const totalPages = Math.ceil(total / pageSize); return { items, - totalPages + totalPages, }; } catch (error) { this.handleError(error, 'read'); @@ -483,10 +497,9 @@ export class BaseService< : undefined, } as any)) as any[]; - /** * 处理下一页游标 - * @description + * @description * 1. 如果查到的记录数超过take,说明还有下一页 * 2. 将最后一条记录弹出,用其updatedAt和id构造下一页游标 * 3. 游标格式为: updatedAt_id @@ -502,7 +515,7 @@ export class BaseService< /** * 返回查询结果 - * @returns {Object} + * @returns {Object} * - items: 当前页记录 * - totalCount: 总记录数 * - nextCursor: 下一页游标 @@ -530,7 +543,7 @@ export class BaseService< order: { gt: targetObject.order }, deletedAt: null, }, - orderBy: { order: 'asc' } + orderBy: { order: 'asc' }, } as any)) as any; const newOrder = nextObject diff --git a/apps/server/src/models/course/course.module.ts b/apps/server/src/models/course/course.module.ts deleted file mode 100644 index c776075..0000000 --- a/apps/server/src/models/course/course.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { CourseRouter } from './course.router'; -import { CourseService } from './course.service'; -import { TrpcService } from '@server/trpc/trpc.service'; - -@Module({ - providers: [CourseRouter, CourseService, TrpcService], - exports: [CourseRouter, CourseService] -}) -export class CourseModule { } diff --git a/apps/server/src/models/course/course.router.ts b/apps/server/src/models/course/course.router.ts deleted file mode 100644 index f08d35c..0000000 --- a/apps/server/src/models/course/course.router.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { TrpcService } from '@server/trpc/trpc.service'; -import { Prisma, UpdateOrderSchema } from '@nice/common'; -import { CourseService } from './course.service'; -import { z, ZodType } from 'zod'; -const CourseCreateArgsSchema: ZodType = z.any() -const CourseUpdateArgsSchema: ZodType = z.any() -const CourseCreateManyInputSchema: ZodType = z.any() -const CourseDeleteManyArgsSchema: ZodType = z.any() -const CourseFindManyArgsSchema: ZodType = z.any() -const CourseFindFirstArgsSchema: ZodType = z.any() -const CourseWhereInputSchema: ZodType = z.any() -const CourseSelectSchema: ZodType = z.any() - -@Injectable() -export class CourseRouter { - constructor( - private readonly trpc: TrpcService, - private readonly courseService: CourseService, - ) { } - router = this.trpc.router({ - create: this.trpc.protectProcedure - .input(CourseCreateArgsSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.courseService.create(input, { staff }); - }), - update: this.trpc.protectProcedure - .input(CourseUpdateArgsSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.courseService.update(input, { staff }); - }), - createMany: this.trpc.protectProcedure.input(z.array(CourseCreateManyInputSchema)) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - - return await this.courseService.createMany({ data: input }, staff); - }), - deleteMany: this.trpc.procedure - .input(CourseDeleteManyArgsSchema) - .mutation(async ({ input }) => { - return await this.courseService.deleteMany(input); - }), - findFirst: this.trpc.procedure - .input(CourseFindFirstArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.courseService.findFirst(input); - }), - softDeleteByIds: this.trpc.protectProcedure - .input(z.object({ ids: z.array(z.string()) })) // expect input according to the schema - .mutation(async ({ input }) => { - return this.courseService.softDeleteByIds(input.ids); - }), - updateOrder: this.trpc.protectProcedure - .input(UpdateOrderSchema) - .mutation(async ({ input }) => { - return this.courseService.updateOrder(input); - }), - findMany: this.trpc.procedure - .input(CourseFindManyArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.courseService.findMany(input); - }), - findManyWithCursor: this.trpc.protectProcedure - .input(z.object({ - cursor: z.any().nullish(), - take: z.number().optional(), - where: CourseWhereInputSchema.optional(), - select: CourseSelectSchema.optional() - })) - .query(async ({ ctx, input }) => { - return await this.courseService.findManyWithCursor(input); - }), - findManyWithPagination: this.trpc.procedure - .input(z.object({ - page: z.number().optional(), - pageSize: z.number().optional(), - where: CourseWhereInputSchema.optional(), - select: CourseSelectSchema.optional() - })) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.courseService.findManyWithPagination(input); - }), - }); -} diff --git a/apps/server/src/models/course/course.schema.ts b/apps/server/src/models/course/course.schema.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/server/src/models/course/course.service.ts b/apps/server/src/models/course/course.service.ts deleted file mode 100644 index f21a249..0000000 --- a/apps/server/src/models/course/course.service.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { BaseService } from '../base/base.service'; -import { - UserProfile, - db, - ObjectType, - Prisma, - InstructorRole, -} from '@nice/common'; -@Injectable() -export class CourseService extends BaseService { - constructor() { - super(db, ObjectType.COURSE); - } - async create( - args: Prisma.CourseCreateArgs, - params?: { staff?: UserProfile } - ) { - return await db.$transaction(async tx => { - const result = await super.create(args, { tx }); - if (params?.staff?.id) { - await tx.courseInstructor.create({ - data: { - instructorId: params.staff.id, - courseId: result.id, - role: InstructorRole.MAIN, - } - }); - } - return result; - }, { - timeout: 10000 // 10 seconds - }); - } - async update( - args: Prisma.CourseUpdateArgs, - params?: { staff?: UserProfile } - ) { - return await db.$transaction(async tx => { - const result = await super.update(args, { tx }); - return result; - }, { - timeout: 10000 // 10 seconds - }); - } - async removeInstructor(courseId: string, instructorId: string) { - return await db.courseInstructor.delete({ - where: { - courseId_instructorId: { - courseId, - instructorId, - }, - }, - }); - } - async addInstructor(params: { - courseId: string; - instructorId: string; - role?: string; - order?: number; - }) { - return await db.courseInstructor.create({ - data: { - courseId: params.courseId, - instructorId: params.instructorId, - role: params.role || InstructorRole.ASSISTANT, - order: params.order, - }, - }); - } - async getInstructors(courseId: string) { - return await db.courseInstructor.findMany({ - where: { courseId }, - include: { instructor: true }, - orderBy: { order: 'asc' }, - }); - } -} diff --git a/apps/server/src/models/course/utils.ts b/apps/server/src/models/course/utils.ts deleted file mode 100644 index 9d2092c..0000000 --- a/apps/server/src/models/course/utils.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { db, EnrollmentStatus, PostType } from "@nice/common"; - -// 更新课程评价统计 -export async function updateCourseReviewStats(courseId: string) { - const reviews = await db.post.findMany({ - where: { - courseId, - type: PostType.COURSE_REVIEW, - deletedAt: null - }, - select: { rating: true } - }); - const numberOfReviews = reviews.length; - const averageRating = numberOfReviews > 0 - ? reviews.reduce((sum, review) => sum + review.rating, 0) / numberOfReviews - : 0; - - return db.course.update({ - where: { id: courseId }, - data: { numberOfReviews, averageRating } - }); -} - -// 更新课程注册统计 -export async function updateCourseEnrollmentStats(courseId: string) { - const completedEnrollments = await db.enrollment.count({ - where: { - courseId, - status: EnrollmentStatus.COMPLETED - } - }); - const totalEnrollments = await db.enrollment.count({ - where: { courseId } - }); - const completionRate = totalEnrollments > 0 - ? (completedEnrollments / totalEnrollments) * 100 - : 0; - return db.course.update({ - where: { id: courseId }, - data: { - numberOfStudents: totalEnrollments, - completionRate - } - }); -} - diff --git a/apps/server/src/models/enrollment/enroll.schema.ts b/apps/server/src/models/enrollment/enroll.schema.ts deleted file mode 100644 index 41a9968..0000000 --- a/apps/server/src/models/enrollment/enroll.schema.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { z } from "zod"; - -export const EnrollSchema = z.object({ - studentId: z.string(), - courseId: z.string(), -}); - -export const UnenrollSchema = z.object({ - studentId: z.string(), - courseId: z.string(), -}); \ No newline at end of file diff --git a/apps/server/src/models/enrollment/enrollment.module.ts b/apps/server/src/models/enrollment/enrollment.module.ts deleted file mode 100644 index 71a7e35..0000000 --- a/apps/server/src/models/enrollment/enrollment.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from '@nestjs/common'; -import { EnrollmentRouter } from './enrollment.router'; -import { EnrollmentService } from './enrollment.service'; - -@Module({ - exports: [EnrollmentRouter, EnrollmentService], - providers: [EnrollmentRouter, EnrollmentService] -}) -export class EnrollmentModule { } diff --git a/apps/server/src/models/enrollment/enrollment.router.ts b/apps/server/src/models/enrollment/enrollment.router.ts deleted file mode 100644 index b14e2f2..0000000 --- a/apps/server/src/models/enrollment/enrollment.router.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { TrpcService } from '@server/trpc/trpc.service'; -import { Prisma, UpdateOrderSchema } from '@nice/common'; -import { EnrollmentService } from './enrollment.service'; -import { z, ZodType } from 'zod'; -import { EnrollSchema, UnenrollSchema } from './enroll.schema'; -const EnrollmentCreateArgsSchema: ZodType = z.any() -const EnrollmentCreateManyInputSchema: ZodType = z.any() -const EnrollmentDeleteManyArgsSchema: ZodType = z.any() -const EnrollmentFindManyArgsSchema: ZodType = z.any() -const EnrollmentFindFirstArgsSchema: ZodType = z.any() -const EnrollmentWhereInputSchema: ZodType = z.any() -const EnrollmentSelectSchema: ZodType = z.any() - -@Injectable() -export class EnrollmentRouter { - constructor( - private readonly trpc: TrpcService, - private readonly enrollmentService: EnrollmentService, - ) { } - router = this.trpc.router({ - findFirst: this.trpc.procedure - .input(EnrollmentFindFirstArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.enrollmentService.findFirst(input); - }), - findMany: this.trpc.procedure - .input(EnrollmentFindManyArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.enrollmentService.findMany(input); - }), - findManyWithCursor: this.trpc.protectProcedure - .input(z.object({ - cursor: z.any().nullish(), - take: z.number().nullish(), - where: EnrollmentWhereInputSchema.nullish(), - select: EnrollmentSelectSchema.nullish() - })) - .query(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.enrollmentService.findManyWithCursor(input); - }), - enroll: this.trpc.protectProcedure - .input(EnrollSchema) - .mutation(async ({ input }) => { - return await this.enrollmentService.enroll(input); - }), - unenroll: this.trpc.protectProcedure - .input(UnenrollSchema) - .mutation(async ({ input }) => { - return await this.enrollmentService.unenroll(input); - }), - }); -} diff --git a/apps/server/src/models/enrollment/enrollment.service.ts b/apps/server/src/models/enrollment/enrollment.service.ts deleted file mode 100644 index 63f87ef..0000000 --- a/apps/server/src/models/enrollment/enrollment.service.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { ConflictException, Injectable, NotFoundException } from '@nestjs/common'; -import { BaseService } from '../base/base.service'; -import { - UserProfile, - db, - ObjectType, - Prisma, - EnrollmentStatus -} from '@nice/common'; -import { z } from 'zod'; -import { EnrollSchema, UnenrollSchema } from './enroll.schema'; -import EventBus, { CrudOperation } from '@server/utils/event-bus'; - -@Injectable() -export class EnrollmentService extends BaseService { - constructor() { - super(db, ObjectType.COURSE); - } - async enroll(params: z.infer) { - const { studentId, courseId } = params - const result = await db.$transaction(async tx => { - // 检查是否已经报名 - const existing = await tx.enrollment.findUnique({ - where: { - studentId_courseId: { - studentId: studentId, - courseId: courseId, - }, - }, - }); - if (existing) { - throw new ConflictException('Already enrolled in this course'); - } - // 创建报名记录 - const enrollment = await tx.enrollment.create({ - data: { - studentId: studentId, - courseId: courseId, - status: EnrollmentStatus.ACTIVE, - }, - }); - - return enrollment; - }); - - EventBus.emit('dataChanged', { - type: ObjectType.ENROLLMENT, - operation: CrudOperation.CREATED, - data: result, - }); - return result - } - async unenroll(params: z.infer) { - const { studentId, courseId } = params - const result = await db.enrollment.update({ - where: { - studentId_courseId: { - studentId, - courseId, - }, - }, - data: { - status: EnrollmentStatus.CANCELLED - } - }); - - EventBus.emit('dataChanged', { - type: ObjectType.ENROLLMENT, - operation: CrudOperation.UPDATED, - data: result, - }); - return result - } -} diff --git a/apps/server/src/models/lecture/lecture.module.ts b/apps/server/src/models/lecture/lecture.module.ts deleted file mode 100644 index a7d7561..0000000 --- a/apps/server/src/models/lecture/lecture.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { LectureRouter } from './lecture.router'; -import { LectureService } from './lecture.service'; -import { TrpcService } from '@server/trpc/trpc.service'; - -@Module({ - providers: [LectureRouter, LectureService, TrpcService], - exports: [LectureRouter, LectureService] -}) -export class LectureModule { } diff --git a/apps/server/src/models/lecture/lecture.router.ts b/apps/server/src/models/lecture/lecture.router.ts deleted file mode 100644 index 39d609e..0000000 --- a/apps/server/src/models/lecture/lecture.router.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { TrpcService } from '@server/trpc/trpc.service'; -import { Prisma, UpdateOrderSchema } from '@nice/common'; -import { LectureService } from './lecture.service'; -import { z, ZodType } from 'zod'; -const LectureCreateArgsSchema: ZodType = z.any() -const LectureCreateManyInputSchema: ZodType = z.any() -const LectureDeleteManyArgsSchema: ZodType = z.any() -const LectureFindManyArgsSchema: ZodType = z.any() -const LectureFindFirstArgsSchema: ZodType = z.any() -const LectureWhereInputSchema: ZodType = z.any() -const LectureSelectSchema: ZodType = z.any() - -@Injectable() -export class LectureRouter { - constructor( - private readonly trpc: TrpcService, - private readonly lectureService: LectureService, - ) { } - router = this.trpc.router({ - create: this.trpc.protectProcedure - .input(LectureCreateArgsSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.lectureService.create(input, {staff}); - }), - createMany: this.trpc.protectProcedure.input(z.array(LectureCreateManyInputSchema)) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - - return await this.lectureService.createMany({ data: input }, staff); - }), - deleteMany: this.trpc.procedure - .input(LectureDeleteManyArgsSchema) - .mutation(async ({ input }) => { - return await this.lectureService.deleteMany(input); - }), - findFirst: this.trpc.procedure - .input(LectureFindFirstArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.lectureService.findFirst(input); - }), - softDeleteByIds: this.trpc.protectProcedure - .input(z.object({ ids: z.array(z.string()) })) // expect input according to the schema - .mutation(async ({ input }) => { - return this.lectureService.softDeleteByIds(input.ids); - }), - updateOrder: this.trpc.protectProcedure - .input(UpdateOrderSchema) - .mutation(async ({ input }) => { - return this.lectureService.updateOrder(input); - }), - findMany: this.trpc.procedure - .input(LectureFindManyArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.lectureService.findMany(input); - }), - findManyWithCursor: this.trpc.protectProcedure - .input(z.object({ - cursor: z.any().nullish(), - take: z.number().nullish(), - where: LectureWhereInputSchema.nullish(), - select: LectureSelectSchema.nullish() - })) - .query(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.lectureService.findManyWithCursor(input); - }), - }); -} diff --git a/apps/server/src/models/lecture/lecture.service.ts b/apps/server/src/models/lecture/lecture.service.ts deleted file mode 100644 index ea02d72..0000000 --- a/apps/server/src/models/lecture/lecture.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { BaseService } from '../base/base.service'; -import { - UserProfile, - db, - ObjectType, - Prisma -} from '@nice/common'; -import EventBus, { CrudOperation } from '@server/utils/event-bus'; - -@Injectable() -export class LectureService extends BaseService { - constructor() { - super(db, ObjectType.COURSE); - } - async create(args: Prisma.LectureCreateArgs, params?: { staff?: UserProfile }) { - const result = await super.create(args) - EventBus.emit('dataChanged', { - type: ObjectType.LECTURE, - operation: CrudOperation.CREATED, - data: result, - }); - return result; - } - async update(args: Prisma.LectureUpdateArgs) { - const result = await super.update(args); - EventBus.emit('dataChanged', { - type: ObjectType.LECTURE, - operation: CrudOperation.UPDATED, - data: result, - }); - return result; - } - -} diff --git a/apps/server/src/models/lecture/utils.ts b/apps/server/src/models/lecture/utils.ts deleted file mode 100644 index 008bc87..0000000 --- a/apps/server/src/models/lecture/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { db, Lecture } from "@nice/common" - -export async function updateSectionLectureStats(sectionId: string) { - const sectionStats = await db.lecture.aggregate({ - where: { - sectionId, - deletedAt: null - }, - _count: { _all: true }, - _sum: { duration: true } - }); - - await db.section.update({ - where: { id: sectionId }, - data: { - totalLectures: sectionStats._count._all, - totalDuration: sectionStats._sum.duration || 0 - } - }); -} - -export async function updateCourseLectureStats(courseId: string) { - const courseStats = await db.lecture.aggregate({ - where: { - courseId, - deletedAt: null - }, - _count: { _all: true }, - _sum: { duration: true } - }); - - await db.course.update({ - where: { id: courseId }, - data: { - totalLectures: courseStats._count._all, - totalDuration: courseStats._sum.duration || 0 - } - }); -} \ 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 index e5731af..32e0a03 100755 --- a/apps/server/src/models/post/post.controller.ts +++ b/apps/server/src/models/post/post.controller.ts @@ -1,11 +1,8 @@ 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) { } - + constructor(private readonly postService: PostService) {} } diff --git a/apps/server/src/models/post/post.router.ts b/apps/server/src/models/post/post.router.ts index b4ca364..40398bb 100755 --- a/apps/server/src/models/post/post.router.ts +++ b/apps/server/src/models/post/post.router.ts @@ -3,6 +3,8 @@ import { TrpcService } from '@server/trpc/trpc.service'; import { Prisma } from '@nice/common'; import { PostService } from './post.service'; import { z, ZodType } from 'zod'; +import { PostMeta } from '@nice/common'; +import { getClientIp } from './utils'; const PostCreateArgsSchema: ZodType = z.any(); const PostUpdateArgsSchema: ZodType = z.any(); const PostFindFirstArgsSchema: ZodType = z.any(); @@ -15,12 +17,22 @@ 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; + const { staff, req } = ctx; + // 从请求中获取 IP + const ip = getClientIp(req); + const currentMeta = + typeof input.data.meta === 'object' && input.data.meta !== null + ? input.data.meta + : {}; + input.data.meta = { + ...currentMeta, + ip: ip || '', + } as Prisma.InputJsonObject; // 明确指定类型 return await this.postService.create(input, { staff }); }), softDeleteByIds: this.trpc.protectProcedure diff --git a/apps/server/src/models/post/post.service.ts b/apps/server/src/models/post/post.service.ts index a391ba6..0f3f6e0 100755 --- a/apps/server/src/models/post/post.service.ts +++ b/apps/server/src/models/post/post.service.ts @@ -3,9 +3,7 @@ import { db, Prisma, UserProfile, - VisitType, Post, - PostType, RolePerms, ResPerm, ObjectType, @@ -45,13 +43,13 @@ export class PostService extends BaseService { operation: CrudOperation.UPDATED, data: result, }); - return result + return result; } async findManyWithCursor(args: Prisma.PostFindManyArgs, staff?: UserProfile) { - if (!args.where) args.where = {} + if (!args.where) args.where = {}; args.where.OR = await this.preFilter(args.where.OR, staff); return this.wrapResult(super.findManyWithCursor(args), async (result) => { - let { items } = result; + const { items } = result; await Promise.all( items.map(async (item) => { await setPostRelation({ data: item, staff }); @@ -68,7 +66,7 @@ export class PostService extends BaseService { delete: false, }; const isMySelf = data?.authorId === staff?.id; - const isDomain = staff.domainId === data.domainId; + // const isDomain = staff.domainId === data.domainId; const setManagePermissions = (perms: ResPerm) => { Object.assign(perms, { delete: true, @@ -84,11 +82,11 @@ export class PostService extends BaseService { case RolePerms.MANAGE_ANY_POST: setManagePermissions(perms); break; - case RolePerms.MANAGE_DOM_POST: - if (isDomain) { - setManagePermissions(perms); - } - break; + // case RolePerms.MANAGE_DOM_POST: + // if (isDomain) { + // setManagePermissions(perms); + // } + // break; } }); Object.assign(data, { perms }); @@ -99,51 +97,28 @@ export class PostService extends BaseService { return outOR?.length > 0 ? outOR : undefined; } async getPostPreFilter(staff?: UserProfile) { - if (!staff) return - const { deptId, domainId } = staff; + 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, }, + { + isPublic: true, + }, staff?.id && { - watchableStaffs: { + receivers: { 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; diff --git a/apps/server/src/models/post/utils.ts b/apps/server/src/models/post/utils.ts index 6029f81..eb51237 100644 --- a/apps/server/src/models/post/utils.ts +++ b/apps/server/src/models/post/utils.ts @@ -1,44 +1,62 @@ -import { db, Post, PostType, UserProfile, VisitType } from "@nice/common"; +import { db, Post, PostType, 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, - }, - }); +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 - }) + Object.assign(data, { + readed, + readedCount, + limitedComments, + commentsCount, + // trouble + }); +} -} \ No newline at end of file +export function getClientIp(req: any): string { + let ip = + req.ip || + (Array.isArray(req.headers['x-forwarded-for']) + ? req.headers['x-forwarded-for'][0] + : req.headers['x-forwarded-for']) || + req.socket.remoteAddress; + + // 如果是 IPv4-mapped IPv6 地址,转换为 IPv4 + if (typeof ip === 'string' && ip.startsWith('::ffff:')) { + ip = ip.substring(7); + } + + return ip || ''; +} diff --git a/apps/server/src/models/section/section.module.ts b/apps/server/src/models/section/section.module.ts deleted file mode 100644 index 0a44529..0000000 --- a/apps/server/src/models/section/section.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { SectionRouter } from './section.router'; -import { SectionService } from './section.service'; -import { TrpcService } from '@server/trpc/trpc.service'; - -@Module({ - exports: [SectionRouter, SectionService], - providers: [SectionRouter, SectionService, TrpcService] -}) -export class SectionModule { } diff --git a/apps/server/src/models/section/section.router.ts b/apps/server/src/models/section/section.router.ts deleted file mode 100644 index fcdd6ba..0000000 --- a/apps/server/src/models/section/section.router.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { TrpcService } from '@server/trpc/trpc.service'; -import { Prisma, UpdateOrderSchema } from '@nice/common'; -import { SectionService } from './section.service'; -import { z, ZodType } from 'zod'; -const SectionCreateArgsSchema: ZodType = z.any() -const SectionCreateManyInputSchema: ZodType = z.any() -const SectionDeleteManyArgsSchema: ZodType = z.any() -const SectionFindManyArgsSchema: ZodType = z.any() -const SectionFindFirstArgsSchema: ZodType = z.any() -const SectionWhereInputSchema: ZodType = z.any() -const SectionSelectSchema: ZodType = z.any() - -@Injectable() -export class SectionRouter { - constructor( - private readonly trpc: TrpcService, - private readonly sectionService: SectionService, - ) { } - router = this.trpc.router({ - create: this.trpc.protectProcedure - .input(SectionCreateArgsSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.sectionService.create(input, { staff }); - }), - createMany: this.trpc.protectProcedure.input(z.array(SectionCreateManyInputSchema)) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - - return await this.sectionService.createMany({ data: input }, staff); - }), - deleteMany: this.trpc.procedure - .input(SectionDeleteManyArgsSchema) - .mutation(async ({ input }) => { - return await this.sectionService.deleteMany(input); - }), - findFirst: this.trpc.procedure - .input(SectionFindFirstArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.sectionService.findFirst(input); - }), - softDeleteByIds: this.trpc.protectProcedure - .input(z.object({ ids: z.array(z.string()) })) // expect input according to the schema - .mutation(async ({ input }) => { - return this.sectionService.softDeleteByIds(input.ids); - }), - updateOrder: this.trpc.protectProcedure - .input(UpdateOrderSchema) - .mutation(async ({ input }) => { - return this.sectionService.updateOrder(input); - }), - findMany: this.trpc.procedure - .input(SectionFindManyArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.sectionService.findMany(input); - }), - findManyWithCursor: this.trpc.protectProcedure - .input(z.object({ - cursor: z.any().nullish(), - take: z.number().nullish(), - where: SectionWhereInputSchema.nullish(), - select: SectionSelectSchema.nullish() - })) - .query(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.sectionService.findManyWithCursor(input); - }), - }); -} diff --git a/apps/server/src/models/section/section.service.ts b/apps/server/src/models/section/section.service.ts deleted file mode 100644 index 229e26b..0000000 --- a/apps/server/src/models/section/section.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { BaseService } from '../base/base.service'; -import { - UserProfile, - db, - ObjectType, - Prisma, - -} from '@nice/common'; -@Injectable() -export class SectionService extends BaseService { - constructor() { - super(db, ObjectType.SECTION); - } - - create(args: Prisma.SectionCreateArgs, params?: { staff?: UserProfile }) { - return super.create(args) - } - async update(args: Prisma.SectionUpdateArgs) { - return super.update(args); - } - - -} diff --git a/apps/server/src/models/visit/visit.service.ts b/apps/server/src/models/visit/visit.service.ts index 69ac47a..9e8faad 100644 --- a/apps/server/src/models/visit/visit.service.ts +++ b/apps/server/src/models/visit/visit.service.ts @@ -1,12 +1,6 @@ import { Injectable } from '@nestjs/common'; import { BaseService } from '../base/base.service'; -import { - UserProfile, - db, - ObjectType, - Prisma, - VisitType, -} from '@nice/common'; +import { UserProfile, db, ObjectType, Prisma, VisitType } from '@nice/common'; import EventBus from '@server/utils/event-bus'; @Injectable() export class VisitService extends BaseService { @@ -14,14 +8,14 @@ export class VisitService extends BaseService { super(db, ObjectType.VISIT); } async create(args: Prisma.VisitCreateArgs, staff?: UserProfile) { - const { postId, lectureId, messageId } = args.data; + const { postId, messageId } = args.data; const visitorId = args.data.visitorId || staff?.id; let result; const existingVisit = await db.visit.findFirst({ where: { type: args.data.type, visitorId, - OR: [{ postId }, { lectureId }, { messageId }], + OR: [{ postId }, { messageId }], }, }); if (!existingVisit) { @@ -50,12 +44,12 @@ export class VisitService extends BaseService { const createData: Prisma.VisitCreateManyInput[] = []; await Promise.all( data.map(async (item) => { - if (staff && !item.visitorId) item.visitorId = staff.id - const { postId, lectureId, messageId, visitorId } = item; + if (staff && !item.visitorId) item.visitorId = staff.id; + const { postId, messageId, visitorId } = item; const existingVisit = await db.visit.findFirst({ where: { visitorId, - OR: [{ postId }, { lectureId }, { messageId }], + OR: [{ postId }, { messageId }], }, }); diff --git a/apps/server/src/queue/worker/processor.ts b/apps/server/src/queue/worker/processor.ts index 6574912..93717af 100755 --- a/apps/server/src/queue/worker/processor.ts +++ b/apps/server/src/queue/worker/processor.ts @@ -1,49 +1,46 @@ import { Job } from 'bullmq'; import { Logger } from '@nestjs/common'; -import { - updateCourseLectureStats, - updateSectionLectureStats -} from '@server/models/lecture/utils'; + import { ObjectType } from '@nice/common'; -import { - updateCourseEnrollmentStats, - updateCourseReviewStats -} from '@server/models/course/utils'; + import { QueueJobType } from '../types'; 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 updateSectionLectureStats(sectionId); - logger.debug(`Updated section stats for sectionId: ${sectionId}`); - } - // 如果没有 courseId,提前返回 - if (!courseId) { - return; - } - // 处理 course 相关统计 - switch (type) { - case ObjectType.LECTURE: - await updateCourseLectureStats(courseId); - break; - case ObjectType.ENROLLMENT: - await updateCourseEnrollmentStats(courseId); - break; - case ObjectType.POST: - await updateCourseReviewStats(courseId); - break; - default: - logger.warn(`Unknown update stats type: ${type}`); - } + try { + if (job.name === QueueJobType.UPDATE_STATS) { + const { sectionId, courseId, type } = job.data; + // 处理 section 统计 + // if (sectionId) { + // await updateSectionLectureStats(sectionId); + // logger.debug(`Updated section stats for sectionId: ${sectionId}`); + // } + // // 如果没有 courseId,提前返回 + // if (!courseId) { + // return; + // } + // 处理 course 相关统计 + switch (type) { + // case ObjectType.LECTURE: + // await updateCourseLectureStats(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}`); - } - - - } catch (error: any) { - logger.error(`Error processing stats update job: ${error.message}`, error.stack); + logger.debug( + `Updated course stats for courseId: ${courseId}, type: ${type}`, + ); } -} \ No newline at end of file + } catch (error: any) { + logger.error( + `Error processing stats update job: ${error.message}`, + error.stack, + ); + } +} diff --git a/apps/server/src/tasks/init/init.service.ts b/apps/server/src/tasks/init/init.service.ts index 4f68e8e..dfc033f 100755 --- a/apps/server/src/tasks/init/init.service.ts +++ b/apps/server/src/tasks/init/init.service.ts @@ -19,7 +19,7 @@ export class InitService { private readonly minioService: MinioService, private readonly authService: AuthService, private readonly genDevService: GenDevService, - ) { } + ) {} private async createRoles() { this.logger.log('Checking existing system roles'); for (const role of InitRoles) { @@ -142,12 +142,13 @@ export class InitService { await this.createRoot(); await this.createOrUpdateTaxonomy(); await this.initAppConfigs(); - try { - this.logger.log('Initialize minio'); - await this.createBucket(); - } catch (error) { - this.logger.error('Minio initialization failed:', error); - } + //不需要minio + // try { + // this.logger.log('Initialize minio'); + // await this.createBucket(); + // } catch (error) { + // this.logger.error('Minio initialization failed:', error); + // } if (process.env.NODE_ENV === 'development') { try { diff --git a/apps/server/src/trpc/trpc.module.ts b/apps/server/src/trpc/trpc.module.ts index fff1aa9..978c0af 100755 --- a/apps/server/src/trpc/trpc.module.ts +++ b/apps/server/src/trpc/trpc.module.ts @@ -14,9 +14,7 @@ import { VisitModule } from '@server/models/visit/visit.module'; import { WebSocketModule } from '@server/socket/websocket.module'; import { RoleMapModule } from '@server/models/rbac/rbac.module'; import { TransformModule } from '@server/models/transform/transform.module'; -import { CourseModule } from '@server/models/course/course.module'; -import { LectureModule } from '@server/models/lecture/lecture.module'; -import { SectionModule } from '@server/models/section/section.module'; + @Module({ imports: [ AuthModule, @@ -31,12 +29,9 @@ import { SectionModule } from '@server/models/section/section.module'; AppConfigModule, PostModule, VisitModule, - CourseModule, - LectureModule, - SectionModule, - WebSocketModule + WebSocketModule, ], controllers: [], providers: [TrpcService, TrpcRouter, Logger], }) -export class TrpcModule { } +export class TrpcModule {} diff --git a/apps/server/src/trpc/trpc.router.ts b/apps/server/src/trpc/trpc.router.ts index ac97d20..6073f21 100755 --- a/apps/server/src/trpc/trpc.router.ts +++ b/apps/server/src/trpc/trpc.router.ts @@ -13,12 +13,10 @@ import { VisitRouter } from '@server/models/visit/visit.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'; -import { CourseRouter } from '@server/models/course/course.router'; -import { LectureRouter } from '@server/models/lecture/lecture.router'; -import { SectionRouter } from '@server/models/section/section.router'; + @Injectable() export class TrpcRouter { - logger = new Logger(TrpcRouter.name) + logger = new Logger(TrpcRouter.name); constructor( private readonly trpc: TrpcService, private readonly post: PostRouter, @@ -32,13 +30,9 @@ export class TrpcRouter { private readonly app_config: AppConfigRouter, private readonly message: MessageRouter, private readonly visitor: VisitRouter, - private readonly course: CourseRouter, - private readonly lecture: LectureRouter, - private readonly section: SectionRouter // private readonly websocketService: WebSocketService - ) { } + ) {} appRouter = this.trpc.router({ - transform: this.transform.router, post: this.post.router, department: this.department.router, @@ -50,11 +44,8 @@ export class TrpcRouter { message: this.message.router, app_config: this.app_config.router, visitor: this.visitor.router, - course: this.course.router, - lecture: this.lecture.router, - section: this.section.router }); - wss: WebSocketServer = undefined + wss: WebSocketServer = undefined; async applyMiddleware(app: INestApplication) { app.use( @@ -65,7 +56,7 @@ export class TrpcRouter { onError(opts) { const { error, type, path, input, ctx, req } = opts; // console.error('TRPC Error:', error); - } + }, }), ); // applyWSSHandler({ diff --git a/apps/server/src/trpc/trpc.service.ts b/apps/server/src/trpc/trpc.service.ts index 31f3072..81ff772 100755 --- a/apps/server/src/trpc/trpc.service.ts +++ b/apps/server/src/trpc/trpc.service.ts @@ -1,3 +1,4 @@ +//trpc.service.ts import { Injectable, Logger } from '@nestjs/common'; import { initTRPC, TRPCError } from '@trpc/server'; import superjson from 'superjson-cjs'; @@ -12,9 +13,17 @@ export class TrpcService { async createExpressContext( opts: trpcExpress.CreateExpressContextOptions, - ): Promise<{ staff: UserProfile | undefined }> { + ): Promise<{ + staff: UserProfile | undefined; + req: trpcExpress.CreateExpressContextOptions['req']; + }> { const token = opts.req.headers.authorization?.split(' ')[1]; - return await UserProfileService.instance.getUserProfileByToken(token); + const staff = + await UserProfileService.instance.getUserProfileByToken(token); + return { + staff: staff.staff, + req: opts.req, + }; } async createWSSContext( opts: CreateWSSContextFnOptions, @@ -45,6 +54,7 @@ export class TrpcService { ctx: { // User value is confirmed to be non-null at this point staff: ctx.staff, + req: ctx.req, }, }); }); diff --git a/apps/web/src/app/main/home/page.tsx b/apps/web/src/app/main/home/page.tsx index 8633dd4..de52085 100644 --- a/apps/web/src/app/main/home/page.tsx +++ b/apps/web/src/app/main/home/page.tsx @@ -1,83 +1,100 @@ -import GraphEditor from '@web/src/components/common/editor/graph/GraphEditor'; -import React, { useState, useCallback } from 'react'; -import * as tus from 'tus-js-client'; +import GraphEditor from "@web/src/components/common/editor/graph/GraphEditor"; +import { usePost } from "@nice/client"; +import React, { useState, useCallback } from "react"; +import * as tus from "tus-js-client"; +import toast from "react-hot-toast"; interface TusUploadProps { - onSuccess?: (response: any) => void; - onError?: (error: Error) => void; + onSuccess?: (response: any) => void; + onError?: (error: Error) => void; } -const TusUploader: React.FC = ({ - onSuccess, - onError -}) => { - const [progress, setProgress] = useState(0); - const [isUploading, setIsUploading] = useState(false); - const [uploadError, setUploadError] = useState(null); - const handleFileUpload = useCallback((file: File) => { - if (!file) return; - setIsUploading(true); - setProgress(0); - setUploadError(null); - // Extract file extension - const extension = file.name.split('.').pop() || ''; - const upload = new tus.Upload(file, { - endpoint: "http://localhost:3000/upload", - retryDelays: [0, 1000, 3000, 5000], - metadata: { - filename: file.name, - size: file.size.toString(), - mimeType: file.type, - extension: extension, - modifiedAt: new Date(file.lastModified).toISOString(), - }, - onProgress: (bytesUploaded, bytesTotal) => { - const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2); - setProgress(Number(percentage)); - }, - onSuccess: () => { - setIsUploading(false); - setProgress(100); - onSuccess && onSuccess(upload); - }, - onError: (error) => { - setIsUploading(false); - setUploadError(error.message); - onError && onError(error); - } - }); +const TusUploader: React.FC = ({ onSuccess, onError }) => { + const { create } = usePost(); + const [progress, setProgress] = useState(0); + const [isUploading, setIsUploading] = useState(false); + const [uploadError, setUploadError] = useState(null); + const handleFileUpload = useCallback( + (file: File) => { + if (!file) return; + setIsUploading(true); + setProgress(0); + setUploadError(null); + // Extract file extension + const extension = file.name.split(".").pop() || ""; + const upload = new tus.Upload(file, { + endpoint: "http://localhost:3000/upload", + retryDelays: [0, 1000, 3000, 5000], + metadata: { + filename: file.name, + size: file.size.toString(), + mimeType: file.type, + extension: extension, + modifiedAt: new Date(file.lastModified).toISOString(), + }, + onProgress: (bytesUploaded, bytesTotal) => { + const percentage = ( + (bytesUploaded / bytesTotal) * + 100 + ).toFixed(2); + setProgress(Number(percentage)); + }, + onSuccess: () => { + setIsUploading(false); + setProgress(100); + onSuccess?.(upload); + }, + onError: (error) => { + setIsUploading(false); + setUploadError(error.message); + onError?.(error); + }, + }); - upload.start(); - }, [onSuccess, onError]); + upload.start(); + }, + [onSuccess, onError] + ); - return ( -
-
- -
- {/*
- -
*/} - {/* */} + return ( +
+ +
+ +
- { - const file = e.target.files?.[0]; - if (file) handleFileUpload(file); - }} - /> - {isUploading && ( -
- - {progress}% -
- )} - {uploadError && ( -
- 上传错误: {uploadError} -
- )} -
- ); + { + const file = e.target.files?.[0]; + if (file) handleFileUpload(file); + }} + /> + {isUploading && ( +
+ + {progress}% +
+ )} + {uploadError && ( +
上传错误: {uploadError}
+ )} +
+ ); }; -export default TusUploader; \ No newline at end of file +export default TusUploader; diff --git a/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx b/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx index 55c8104..aa3233f 100644 --- a/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx +++ b/apps/web/src/components/models/course/editor/context/CourseEditorContext.tsx @@ -2,26 +2,26 @@ import { createContext, useContext, ReactNode, useEffect } from "react"; import { useForm, FormProvider, SubmitHandler } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; -import { CourseDto, CourseLevel, CourseStatus } from "@nice/common"; -import { api, useCourse } from "@nice/client"; +// import { CourseDto, CourseLevel, CourseStatus } from "@nice/common"; +// import { api, useCourse } from "@nice/client"; import toast from "react-hot-toast"; import { useNavigate } from "react-router-dom"; // 定义课程表单验证 Schema const courseSchema = z.object({ - title: z.string().min(1, '课程标题不能为空'), - subTitle: z.string().nullish(), - description: z.string().nullish(), - thumbnail: z.string().url().nullish(), - level: z.nativeEnum(CourseLevel), - requirements: z.array(z.string()).nullish(), - objectives: z.array(z.string()).nullish() + title: z.string().min(1, "课程标题不能为空"), + subTitle: z.string().nullish(), + description: z.string().nullish(), + thumbnail: z.string().url().nullish(), + // level: z.nativeEnum(CourseLevel), + requirements: z.array(z.string()).nullish(), + objectives: z.array(z.string()).nullish(), }); export type CourseFormData = z.infer; interface CourseEditorContextType { onSubmit: SubmitHandler; editId?: string; // 添加 editId part?: string; - course?: CourseDto; + // course?: CourseDto; } interface CourseFormProviderProps { children: ReactNode; @@ -29,69 +29,74 @@ interface CourseFormProviderProps { part?: string; } const CourseEditorContext = createContext(null); -export function CourseFormProvider({ children, editId }: CourseFormProviderProps) { - const { create, update } = useCourse() - const { data: course }: { data: CourseDto } = api.course.findFirst.useQuery({ where: { id: editId } }, { enabled: Boolean(editId) }) - const navigate = useNavigate() - const methods = useForm({ - resolver: zodResolver(courseSchema), - defaultValues: { - level: CourseLevel.BEGINNER, - requirements: [], - objectives: [] - }, - }); - useEffect(() => { - if (course) { - const formData = { - title: course.title, - subTitle: course.subTitle, - description: course.description, - thumbnail: course.thumbnail, - level: course.level, - requirements: course.requirements, - objectives: course.objectives, - status: course.status, - }; - methods.reset(formData as any); - } - }, [course, methods]); - const onSubmit: SubmitHandler = async (data: CourseFormData) => { - try { - - if (editId) { - await update.mutateAsync({ - where: { id: editId }, - data: { - ...data - } - }) - toast.success('课程更新成功!'); - } else { - const result = await create.mutateAsync({ - data: { - status: CourseStatus.DRAFT, - ...data - } - }) - navigate(`/course/${result.id}/editor`, { replace: true }) - toast.success('课程创建成功!'); - } - methods.reset(data); - - - } catch (error) { - console.error('Error submitting form:', error); - toast.error('操作失败,请重试!'); - } - }; - return ( - - - {children} - - - ); +export function CourseFormProvider({ + children, + editId, +}: CourseFormProviderProps) { + // const { create, update } = useCourse() + // const { data: course }: { data: CourseDto } = api.course.findFirst.useQuery({ where: { id: editId } }, { enabled: Boolean(editId) }) + const navigate = useNavigate(); + const methods = useForm({ + resolver: zodResolver(courseSchema), + defaultValues: { + // level: CourseLevel.BEGINNER, + requirements: [], + objectives: [], + }, + }); + // useEffect(() => { + // if (course) { + // const formData = { + // title: course.title, + // subTitle: course.subTitle, + // description: course.description, + // thumbnail: course.thumbnail, + // level: course.level, + // requirements: course.requirements, + // objectives: course.objectives, + // status: course.status, + // }; + // methods.reset(formData as any); + // } + // }, [course, methods]); + const onSubmit: SubmitHandler = async ( + data: CourseFormData + ) => { + try { + if (editId) { + // await update.mutateAsync({ + // where: { id: editId }, + // data: { + // ...data + // } + // }) + toast.success("课程更新成功!"); + } else { + // const result = await create.mutateAsync({ + // data: { + // status: CourseStatus.DRAFT, + // ...data + // } + // }) + // navigate(`/course/${result.id}/editor`, { replace: true }); + toast.success("课程创建成功!"); + } + methods.reset(data); + } catch (error) { + console.error("Error submitting form:", error); + toast.error("操作失败,请重试!"); + } + }; + return ( + + {children} + + ); } export const useCourseEditor = () => { diff --git a/apps/web/src/providers/auth-provider.tsx b/apps/web/src/providers/auth-provider.tsx index b9a0397..c835c29 100644 --- a/apps/web/src/providers/auth-provider.tsx +++ b/apps/web/src/providers/auth-provider.tsx @@ -9,6 +9,7 @@ import { import apiClient from "../utils/axios-client"; import { AuthSchema, RolePerms, UserProfile } from "@nice/common"; import { z } from "zod"; + interface AuthContextProps { accessToken: string | null; refreshToken: string | null; @@ -144,6 +145,7 @@ export function AuthProvider({ children }: AuthProviderProps) { startTokenRefreshInterval(); fetchUserProfile(); } catch (err: any) { + console.log(err); throw err; } finally { setIsLoading(false); @@ -157,6 +159,7 @@ export function AuthProvider({ children }: AuthProviderProps) { setIsLoading(true); await apiClient.post(`/auth/signup`, data); } catch (err: any) { + console.log(err); throw err; } finally { setIsLoading(false); diff --git a/packages/client/package.json b/packages/client/package.json index 5514a71..c4713d8 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,41 +1,42 @@ { - "name": "@nice/client", - "version": "1.0.0", - "main": "./dist/index.js", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "sideEffects": false, - "files": [ - "dist", - "src" - ], - "scripts": { - "build": "tsup", - "dev": "tsup --watch", - "clean": "rimraf dist", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "tinycolor2": "^1.6.0" - }, - "peerDependencies": { - "@nice/common": "workspace:^", - "@tanstack/query-async-storage-persister": "^5.51.9", - "@tanstack/react-query": "^5.51.21", - "@tanstack/react-query-persist-client": "^5.51.9", - "@trpc/client": "11.0.0-rc.456", - "@trpc/react-query": "11.0.0-rc.456", - "@trpc/server": "11.0.0-rc.456", - "dayjs": "^1.11.12", - "lib0": "^0.2.98", - "mitt": "^3.0.1", - "react": "18.2.0", - "yjs": "^13.6.20", - "axios": "^1.7.2" - }, - "devDependencies": { - "rimraf": "^6.0.1", - "tsup": "^8.3.5", - "typescript": "^5.5.4" - } -} \ No newline at end of file + "name": "@nice/client", + "version": "1.0.0", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "sideEffects": false, + "files": [ + "dist", + "src" + ], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "dev-static": "tsup --no-watch", + "clean": "rimraf dist", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "tinycolor2": "^1.6.0" + }, + "peerDependencies": { + "@nice/common": "workspace:^", + "@tanstack/query-async-storage-persister": "^5.51.9", + "@tanstack/react-query": "^5.51.21", + "@tanstack/react-query-persist-client": "^5.51.9", + "@trpc/client": "11.0.0-rc.456", + "@trpc/react-query": "11.0.0-rc.456", + "@trpc/server": "11.0.0-rc.456", + "dayjs": "^1.11.12", + "lib0": "^0.2.98", + "mitt": "^3.0.1", + "react": "18.2.0", + "yjs": "^13.6.20", + "axios": "^1.7.2" + }, + "devDependencies": { + "rimraf": "^6.0.1", + "tsup": "^8.3.5", + "typescript": "^5.5.4" + } +} diff --git a/packages/client/src/api/hooks/index.ts b/packages/client/src/api/hooks/index.ts index d6f1d1d..9f4f998 100644 --- a/packages/client/src/api/hooks/index.ts +++ b/packages/client/src/api/hooks/index.ts @@ -9,4 +9,3 @@ export * from "./useTaxonomy" export * from "./useVisitor" export * from "./useMessage" export * from "./usePost" -export * from "./useCourse" \ No newline at end of file diff --git a/packages/client/src/api/hooks/useCourse.ts b/packages/client/src/api/hooks/useCourse.ts deleted file mode 100644 index 5061072..0000000 --- a/packages/client/src/api/hooks/useCourse.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { api } from "../trpc"; - -export function useCourse() { - const utils = api.useUtils(); - return { - // Queries - findMany: api.course.findMany.useQuery, - findFirst: api.course.findFirst.useQuery, - findManyWithCursor: api.course.findManyWithCursor.useQuery, - - // Mutations - create: api.course.create.useMutation({ - onSuccess: () => { - utils.course.invalidate() - utils.course.findMany.invalidate(); - utils.course.findManyWithCursor.invalidate(); - utils.course.findManyWithPagination.invalidate() - }, - }), - update: api.course.update.useMutation({ - onSuccess: () => { - utils.course.findMany.invalidate(); - utils.course.findManyWithCursor.invalidate(); - utils.course.findManyWithPagination.invalidate() - }, - }), - createMany: api.course.createMany.useMutation({ - onSuccess: () => { - utils.course.findMany.invalidate(); - utils.course.findManyWithCursor.invalidate(); - utils.course.findManyWithPagination.invalidate() - }, - }), - deleteMany: api.course.deleteMany.useMutation({ - onSuccess: () => { - utils.course.findMany.invalidate(); - utils.course.findManyWithCursor.invalidate(); - utils.course.findManyWithPagination.invalidate() - }, - }), - softDeleteByIds: api.course.softDeleteByIds.useMutation({ - onSuccess: () => { - utils.course.findMany.invalidate(); - utils.course.findManyWithCursor.invalidate(); - utils.course.findManyWithPagination.invalidate() - }, - }), - updateOrder: api.course.updateOrder.useMutation({ - onSuccess: () => { - utils.course.findMany.invalidate(); - utils.course.findManyWithCursor.invalidate(); - utils.course.findManyWithPagination.invalidate() - }, - }) - }; -} \ No newline at end of file diff --git a/packages/client/src/api/hooks/usePost.ts b/packages/client/src/api/hooks/usePost.ts index 259b62c..5006731 100644 --- a/packages/client/src/api/hooks/usePost.ts +++ b/packages/client/src/api/hooks/usePost.ts @@ -2,12 +2,12 @@ import { api } from "../trpc"; export function usePost() { const utils = api.useUtils(); - const create = api.post.create.useMutation({ + const create: any = api.post.create.useMutation({ onSuccess: () => { utils.post.invalidate(); }, }); - const update = api.post.update.useMutation({ + const update: any = api.post.update.useMutation({ onSuccess: () => { utils.post.invalidate(); }, @@ -17,21 +17,21 @@ export function usePost() { utils.post.invalidate(); }, }); - const softDeleteByIds = api.post.softDeleteByIds.useMutation({ + const softDeleteByIds: any = api.post.softDeleteByIds.useMutation({ onSuccess: () => { utils.post.invalidate(); }, }); - const restoreByIds = api.post.restoreByIds.useMutation({ + const restoreByIds: any = api.post.restoreByIds.useMutation({ onSuccess: () => { utils.post.invalidate(); }, - }) + }); return { create, update, deleteMany, softDeleteByIds, - restoreByIds + restoreByIds, }; } diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index e079f98..104ddfe 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -7,4 +7,4 @@ export * from "./hooks" export * from "./websocket" export * from "./event" export * from "./types" -export * from "./upload" \ No newline at end of file +// export * from "./upload" \ No newline at end of file diff --git a/packages/client/src/upload/index.ts b/packages/client/src/upload/index.ts deleted file mode 100644 index 6e6ccb0..0000000 --- a/packages/client/src/upload/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./types" -export * from "./uploadManager" -export * from "./useUpload" diff --git a/packages/client/src/upload/types.ts b/packages/client/src/upload/types.ts deleted file mode 100644 index f843021..0000000 --- a/packages/client/src/upload/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { UploadProgress } from "@nice/common"; - - -export interface UploadOptions { - baseUrl?: string; - chunkSize?: number; - concurrency?: number; - retries?: number; - clientId?: string; - onProgress?: (progress: UploadProgress) => void; - onError?: (error: Error) => void; - onSuccess?: (response: any) => void; -} \ No newline at end of file diff --git a/packages/client/src/upload/uploadManager.ts b/packages/client/src/upload/uploadManager.ts deleted file mode 100644 index 46ff833..0000000 --- a/packages/client/src/upload/uploadManager.ts +++ /dev/null @@ -1,225 +0,0 @@ -import axios, { AxiosInstance } from 'axios'; -import { ChunkDto, UploadProgress, UploadStatusInfoDto, UUIDGenerator } from '@nice/common'; -import { UploadOptions } from './types'; -import { calculateFileIdentifier } from '../tools'; - -export class UploadManager { - private readonly axios: AxiosInstance; - private readonly chunkSize: number; - private readonly concurrency: number; - private readonly retries: number; - private readonly clientId: string; - private activeUploads: Map = new Map(); - private abortControllers: Map = new Map(); - - constructor(options: UploadOptions = {}) { - const { - baseUrl = '/upload', - chunkSize = 10 * 1024 * 1024, - concurrency = 3, - retries = 3, - } = options; - - this.axios = axios.create({ baseURL: baseUrl }); - this.chunkSize = chunkSize; - this.concurrency = concurrency; - this.retries = retries; - this.clientId = options.clientId || UUIDGenerator.generate(); - } - async uploadFile(file: File, options: UploadOptions = {}): Promise { - const identifier = await calculateFileIdentifier(file); - const controller = new AbortController(); - this.abortControllers.set(identifier, controller); - try { - // Check if file is already uploaded - const statusInfo = await this.checkUploadStatusInfo(identifier); - if (statusInfo?.status === "completed") { - options.onSuccess?.({ identifier, filename: file.name }); - return; - } - const chunks = await this.prepareChunks(file, identifier); - const uploadedChunks = statusInfo?.chunks || new Set(); - // Filter out already uploaded chunks - const remainingChunks = chunks.filter(chunk => !uploadedChunks.has(chunk.chunkNumber)); - await this.uploadChunks(remainingChunks, file, options, controller.signal); - - } catch (error) { - if (axios.isCancel(error)) { - return; - } - options.onError?.(error as Error); - throw error; - } finally { - this.abortControllers.delete(identifier); - this.activeUploads.delete(identifier); - } - } - - private async prepareChunks(file: File, identifier: string): Promise { - const chunks: ChunkDto[] = []; - const totalChunks = Math.ceil(file.size / this.chunkSize); - for (let i = 0; i < totalChunks; i++) { - chunks.push({ - identifier, - filename: file.name, - chunkNumber: i + 1, - totalChunks, - currentChunkSize: Math.min(this.chunkSize, file.size - i * this.chunkSize), - totalSize: file.size, - }); - } - - return chunks; - } - - private async uploadChunks( - chunks: ChunkDto[], - file: File, - options: UploadOptions, - signal: AbortSignal - ): Promise { - const chunkQueue = [...chunks]; // Create a copy of chunks array - - let activeUploads = 0; - let completedChunks = 0; - let uploadedBytes = 0; - - // 记录最近几次chunk的上传速度 - const speedBuffer: number[] = []; - const SPEED_BUFFER_SIZE = 5; // 保留最近5个chunk的速度 - - return new Promise((resolve, reject) => { - const uploadNextChunk = async () => { - if (completedChunks === chunks.length) { - if (activeUploads === 0) { // Only resolve when all active uploads are done - resolve(); - } - return; - } - while (activeUploads < this.concurrency && chunkQueue.length > 0) { - - const chunk = chunkQueue.shift(); - if (!chunk) break; - - const chunkStartTime = Date.now(); - activeUploads++; - - this.uploadChunk(chunk, file, signal) - .then(() => { - - const chunkEndTime = Date.now(); - const chunkUploadTime = (chunkEndTime - chunkStartTime) / 1000; // 秒 - - completedChunks++; - uploadedBytes += chunk.currentChunkSize; - activeUploads--; - - // 计算当前chunk的上传速度 - const currentSpeed = chunkUploadTime > 0 - ? chunk.currentChunkSize / chunkUploadTime - : 0; - - // 维护速度缓冲区 - speedBuffer.push(currentSpeed); - if (speedBuffer.length > SPEED_BUFFER_SIZE) { - speedBuffer.shift(); - } - - // 计算平均速度 - const averageSpeed = speedBuffer.length > 0 - ? speedBuffer.reduce((a, b) => a + b, 0) / speedBuffer.length - : 0; - - - const totalUploadedBytes = uploadedBytes; - const remainingBytes = file.size - totalUploadedBytes; - - // 使用平均速度计算剩余时间 - const remainingTime = averageSpeed > 0 - ? remainingBytes / averageSpeed - : 0; - - const progress: UploadProgress = { - identifier: chunk.identifier, - percentage: (completedChunks / (chunks.length + completedChunks)) * 100, - uploadedSize: totalUploadedBytes, - totalSize: file.size, - speed: averageSpeed, // 字节/秒 - remainingTime: remainingTime // 秒 - }; - - options.onProgress?.(progress); - uploadNextChunk(); - }) - .catch(reject); - } - }; - - uploadNextChunk(); - }); - } - private async uploadChunk( - chunk: ChunkDto, - file: File, - signal: AbortSignal - ): Promise { - const start = (chunk.chunkNumber - 1) * this.chunkSize; - const end = Math.min(start + this.chunkSize, file.size); - const chunkBlob = file.slice(start, end); - - const formData = new FormData(); - formData.append('chunk', JSON.stringify(chunk)); - formData.append('clientId', this.clientId) - formData.append('file', chunkBlob); - - let attempts = 0; - while (attempts < this.retries) { - try { - await this.axios.post('/chunk', formData, { signal }); - return; - } catch (error) { - attempts++; - if (attempts === this.retries) throw error; - await new Promise(resolve => setTimeout(resolve, 1000 * attempts)); - } - } - } - - async pauseUpload(identifier: string): Promise { - const controller = this.abortControllers.get(identifier); - if (controller) { - controller.abort(); - this.abortControllers.delete(identifier); - } - try { - // Call the pause API endpoint - await this.axios.post(`/pause/${identifier}`); - } catch (error) { - console.error('Error pausing upload:', error); - throw error; - } - } - async resumeUpload(file: File, options: UploadOptions = {}): Promise { - const identifier = await calculateFileIdentifier(file); - - try { - // Call the resume API endpoint - await this.axios.post(`/resume/${identifier}`); - - // Then continue with the upload process - return this.uploadFile(file, options); - } catch (error) { - console.error('Error resuming upload:', error); - throw error; - } - } - - private async checkUploadStatusInfo(identifier: string): Promise { - try { - const response = await this.axios.get(`/status/${identifier}`); - return response.data; - } catch { - return null; - } - } -} \ No newline at end of file diff --git a/packages/client/src/upload/useUpload.ts b/packages/client/src/upload/useUpload.ts deleted file mode 100644 index 3e02c3f..0000000 --- a/packages/client/src/upload/useUpload.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { useCallback, useRef, useState } from 'react'; -import { UploadManager } from './uploadManager'; -import { UploadProgress } from '@nice/common'; -import { UploadOptions } from './types'; - -export function useUpload(options: UploadOptions = {}) { - const [progress, setProgress] = useState>({}); - const [errors, setErrors] = useState>({}); - const uploadManagerRef = useRef(); - const getUploadManager = useCallback(() => { - if (!uploadManagerRef.current) { - uploadManagerRef.current = new UploadManager(options); - } - return uploadManagerRef.current; - }, [options]); - const upload = useCallback(async (files: File | File[]) => { - const fileList = Array.isArray(files) ? files : [files]; - const manager = getUploadManager(); - const uploads = fileList.map(file => { - return manager.uploadFile(file, { - ...options, - onProgress: (progress) => { - setProgress(prev => ({ - ...prev, - [progress.identifier]: progress - })); - options.onProgress?.(progress); - }, - onError: (error) => { - setErrors(prev => ({ - ...prev, - [file.name]: error - })); - options.onError?.(error); - } - }); - }); - return Promise.all(uploads); - }, [options, getUploadManager]); - - const pauseUpload = useCallback((identifier: string) => { - getUploadManager().pauseUpload(identifier); - }, [getUploadManager]); - - const resumeUpload = useCallback((file: File) => { - return getUploadManager().resumeUpload(file, options); - }, [getUploadManager, options]); - - return { - upload, - pauseUpload, - resumeUpload, - progress, - errors - }; -} \ No newline at end of file diff --git a/packages/common/prisma/schema.prisma b/packages/common/prisma/schema.prisma index 450aac0..041f28a 100644 --- a/packages/common/prisma/schema.prisma +++ b/packages/common/prisma/schema.prisma @@ -195,17 +195,14 @@ model Post { // 关系类型字段 authorId String? @map("author_id") author Staff? @relation("post_author", fields: [authorId], references: [id]) // 帖子作者,关联 Staff 模型 - visits Visit[] // 访问记录,关联 Visit 模型 receivers Staff[] @relation("post_receiver") - parentId String? @map("parent_id") parent Post? @relation("PostChildren", fields: [parentId], references: [id]) // 父级帖子,关联 Post 模型 children Post[] @relation("PostChildren") // 子级帖子列表,关联 Post 模型 - resources Resource[] // 附件列表 isPublic Boolean? @default(false) @map("is_public") - meta Json? + meta Json? // 签名 和 IP // 复合索引 @@index([type, domainId]) // 类型和域组合查询 diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 1b247cd..b20b146 100755 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -158,6 +158,10 @@ export interface BaseSetting { devDept?: string; }; } +export interface PostMeta { + signature?: string; + ip?: string; +} export type RowModelResult = { rowData: any[]; rowCount: number; diff --git a/packages/tus/package.json b/packages/tus/package.json index 3600307..4363026 100644 --- a/packages/tus/package.json +++ b/packages/tus/package.json @@ -8,6 +8,7 @@ "scripts": { "build": "tsup", "dev": "tsup --watch", + "dev-static": "tsup --no-watch", "clean": "rimraf dist", "typecheck": "tsc --noEmit" }, diff --git a/web-app/error.html b/web-app/error.html new file mode 100644 index 0000000..ecfd539 --- /dev/null +++ b/web-app/error.html @@ -0,0 +1 @@ +

error

\ No newline at end of file diff --git a/web-app/index.html b/web-app/index.html new file mode 100644 index 0000000..48b59bb --- /dev/null +++ b/web-app/index.html @@ -0,0 +1 @@ +

test1

\ No newline at end of file