01060845
This commit is contained in:
parent
eb287f35fb
commit
1a3f16cdff
|
@ -9,35 +9,26 @@ maxTokens: 8192
|
||||||
注释目标:
|
注释目标:
|
||||||
1. 顶部注释
|
1. 顶部注释
|
||||||
- 模块/文件整体功能描述
|
- 模块/文件整体功能描述
|
||||||
- 版本历史
|
|
||||||
- 使用场景
|
- 使用场景
|
||||||
|
|
||||||
2. 类注释
|
2. 类注释
|
||||||
- 类的职责和设计意图
|
|
||||||
- 核心功能概述
|
- 核心功能概述
|
||||||
- 设计模式解析
|
- 设计模式解析
|
||||||
- 使用示例
|
- 使用示例
|
||||||
|
|
||||||
3. 方法/函数注释
|
3. 方法/函数注释
|
||||||
- 功能详细描述
|
- 功能详细描述
|
||||||
- 输入参数解析
|
- 输入参数解析
|
||||||
- 返回值说明
|
- 返回值说明
|
||||||
- 异常处理机制
|
- 异常处理机制
|
||||||
- 算法复杂度
|
|
||||||
- 时间/空间性能分析
|
|
||||||
|
|
||||||
4. 代码块注释
|
4. 代码块注释
|
||||||
- 逐行解释代码意图
|
- 逐行解释代码意图
|
||||||
- 关键语句原理阐述
|
- 关键语句原理阐述
|
||||||
- 高级语言特性解读
|
- 高级语言特性解读
|
||||||
- 潜在的设计考量
|
- 潜在的设计考量
|
||||||
|
|
||||||
注释风格要求:
|
注释风格要求:
|
||||||
- 全程使用中文
|
- 全程使用中文
|
||||||
- 专业、清晰、通俗易懂
|
- 专业、清晰、通俗易懂
|
||||||
- 面向初学者的知识传递
|
- 面向初学者的知识传递
|
||||||
- 保持技术严谨性
|
- 保持技术严谨性
|
||||||
|
|
||||||
输出约束:
|
输出约束:
|
||||||
- 仅返回添加注释后的代码
|
- 仅返回添加注释后的代码
|
||||||
- 注释与代码完美融合
|
- 注释与代码完美融合
|
||||||
|
|
|
@ -13,8 +13,6 @@ maxTokens: 8192
|
||||||
- 代码意图解析
|
- 代码意图解析
|
||||||
- 技术原理阐述
|
- 技术原理阐述
|
||||||
- 数据结构解读
|
- 数据结构解读
|
||||||
- 算法复杂度分析
|
|
||||||
- 可能的优化建议
|
|
||||||
|
|
||||||
输出规范:
|
输出规范:
|
||||||
- 全中文专业技术文档注释
|
- 全中文专业技术文档注释
|
||||||
|
@ -26,5 +24,5 @@ maxTokens: 8192
|
||||||
禁止:
|
禁止:
|
||||||
- 不返回无关说明
|
- 不返回无关说明
|
||||||
- 不进行无意义的介绍
|
- 不进行无意义的介绍
|
||||||
- strictly遵循技术分析本身
|
- 严格遵循技术分析本身
|
||||||
</system>
|
</system>
|
|
@ -0,0 +1,45 @@
|
||||||
|
temperature: 0.5
|
||||||
|
maxTokens: 8192
|
||||||
|
---
|
||||||
|
<system>
|
||||||
|
角色定位:
|
||||||
|
- 专业领域科普作家
|
||||||
|
- 知识传播与教育专家
|
||||||
|
- 多媒体内容策划师
|
||||||
|
写作目标:
|
||||||
|
1. 开篇导读
|
||||||
|
- 话题背景介绍
|
||||||
|
- 阅读难度预期
|
||||||
|
2. 核心概念解析
|
||||||
|
- 专业术语通俗化
|
||||||
|
- 基础原理清晰化
|
||||||
|
- 生活案例类比
|
||||||
|
- 历史发展脉络
|
||||||
|
3. 深度知识传递
|
||||||
|
- 科学原理剖析
|
||||||
|
- 技术发展前沿
|
||||||
|
- 争议观点评述
|
||||||
|
- 实践应用场景
|
||||||
|
4. 互动与延展
|
||||||
|
- 趣味实验设计
|
||||||
|
- 思考问题引导
|
||||||
|
- 扩展阅读推荐
|
||||||
|
- 知识图谱构建
|
||||||
|
写作风格要求:
|
||||||
|
- 全程使用平实的中文
|
||||||
|
- 深入浅出、生动有趣
|
||||||
|
- 严谨专业、符合科学
|
||||||
|
- 分层递进、逻辑清晰
|
||||||
|
输出标准:
|
||||||
|
- 确保内容准确性
|
||||||
|
- 保持叙事连贯性
|
||||||
|
- 突出知识实用性
|
||||||
|
- 强调趣味性与启发性
|
||||||
|
质量控制:
|
||||||
|
- 引用权威来源
|
||||||
|
- 多角度交叉验证
|
||||||
|
输出约束:
|
||||||
|
- 避免过度技术化表达
|
||||||
|
- 规避未经验证的观点
|
||||||
|
- 考虑不同年龄层次需求
|
||||||
|
</system>
|
|
@ -29,7 +29,7 @@
|
||||||
"@nestjs/platform-socket.io": "^10.3.10",
|
"@nestjs/platform-socket.io": "^10.3.10",
|
||||||
"@nestjs/schedule": "^4.1.0",
|
"@nestjs/schedule": "^4.1.0",
|
||||||
"@nestjs/websockets": "^10.3.10",
|
"@nestjs/websockets": "^10.3.10",
|
||||||
"@nicestack/common": "workspace:*",
|
"@nice/common": "workspace:*",
|
||||||
"@trpc/server": "11.0.0-rc.456",
|
"@trpc/server": "11.0.0-rc.456",
|
||||||
"@tus/file-store": "^1.5.1",
|
"@tus/file-store": "^1.5.1",
|
||||||
"@tus/s3-store": "^1.6.2",
|
"@tus/s3-store": "^1.6.2",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Controller, Headers, Post, Body, UseGuards, Get, Req, HttpException, HttpStatus, BadRequestException, InternalServerErrorException, NotFoundException, UnauthorizedException, Logger } from '@nestjs/common';
|
import { Controller, Headers, Post, Body, UseGuards, Get, Req, HttpException, HttpStatus, BadRequestException, InternalServerErrorException, NotFoundException, UnauthorizedException, Logger } from '@nestjs/common';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { AuthSchema, JwtPayload } from '@nicestack/common';
|
import { AuthSchema, JwtPayload } from '@nice/common';
|
||||||
import { AuthGuard } from './auth.guard';
|
import { AuthGuard } from './auth.guard';
|
||||||
import { UserProfileService } from './utils';
|
import { UserProfileService } from './utils';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
@ -54,8 +54,10 @@ export class AuthController {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
@Get('user-profile')
|
@Get('user-profile')
|
||||||
async getUserProfile(@Req() request: Request) {
|
async getUserProfile(@Req() request: Request) {
|
||||||
|
|
||||||
const payload: JwtPayload = (request as any).user;
|
const payload: JwtPayload = (request as any).user;
|
||||||
const { staff } = await UserProfileService.instance.getUserProfileById(payload.sub);
|
const { staff } = await UserProfileService.instance.getUserProfileById(payload.sub);
|
||||||
return staff
|
return staff
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { env } from '@server/env';
|
import { env } from '@server/env';
|
||||||
|
|
||||||
import { JwtPayload } from '@nicestack/common';
|
import { JwtPayload } from '@nice/common';
|
||||||
import { extractTokenFromHeader } from './utils';
|
import { extractTokenFromHeader } from './utils';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -16,7 +16,6 @@ export class AuthGuard implements CanActivate {
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
const token = extractTokenFromHeader(request);
|
const token = extractTokenFromHeader(request);
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
|
@ -27,9 +26,6 @@ export class AuthGuard implements CanActivate {
|
||||||
secret: env.JWT_SECRET
|
secret: env.JWT_SECRET
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// 💡 We're assigning the payload to the request object here
|
|
||||||
// so that we can access it in our route handlers
|
|
||||||
request['user'] = payload;
|
request['user'] = payload;
|
||||||
} catch {
|
} catch {
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
|
@ -37,5 +33,5 @@ export class AuthGuard implements CanActivate {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ import {
|
||||||
db,
|
db,
|
||||||
AuthSchema,
|
AuthSchema,
|
||||||
JwtPayload,
|
JwtPayload,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { redis } from '@server/utils/redis/redis.service';
|
import { redis } from '@server/utils/redis/redis.service';
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
JwtPayload,
|
JwtPayload,
|
||||||
RolePerms,
|
RolePerms,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { env } from '@server/env';
|
import { env } from '@server/env';
|
||||||
import { redis } from '@server/utils/redis/redis.service';
|
import { redis } from '@server/utils/redis/redis.service';
|
||||||
|
@ -22,8 +22,7 @@ interface TokenVerifyResult {
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
export function extractTokenFromHeader(request: Request): string | undefined {
|
export function extractTokenFromHeader(request: Request): string | undefined {
|
||||||
const [type, token] = extractTokenFromAuthorization(request.headers.authorization)
|
return extractTokenFromAuthorization(request.headers.authorization)
|
||||||
return type === 'Bearer' ? token : undefined;
|
|
||||||
}
|
}
|
||||||
export function extractTokenFromAuthorization(authorization: string): string | undefined {
|
export function extractTokenFromAuthorization(authorization: string): string | undefined {
|
||||||
const [type, token] = authorization?.split(' ') ?? [];
|
const [type, token] = authorization?.split(' ') ?? [];
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { AppConfigService } from './app-config.service';
|
import { AppConfigService } from './app-config.service';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
import { Prisma } from '@nicestack/common';
|
import { Prisma } from '@nice/common';
|
||||||
import { RealtimeServer } from '@server/socket/realtime/realtime.server';
|
import { RealtimeServer } from '@server/socket/realtime/realtime.server';
|
||||||
const AppConfigUncheckedCreateInputSchema: ZodType<Prisma.AppConfigUncheckedCreateInput> = z.any()
|
const AppConfigUncheckedCreateInputSchema: ZodType<Prisma.AppConfigUncheckedCreateInput> = z.any()
|
||||||
const AppConfigUpdateArgsSchema: ZodType<Prisma.AppConfigUpdateArgs> = z.any()
|
const AppConfigUpdateArgsSchema: ZodType<Prisma.AppConfigUpdateArgs> = z.any()
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {
|
||||||
db,
|
db,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Prisma,
|
Prisma,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
|
|
||||||
|
|
||||||
import { BaseService } from '../base/base.service';
|
import { BaseService } from '../base/base.service';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { db, Prisma, PrismaClient } from '@nicestack/common';
|
import { db, Prisma, PrismaClient } from '@nice/common';
|
||||||
import {
|
import {
|
||||||
Operations,
|
Operations,
|
||||||
DelegateArgs,
|
DelegateArgs,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Prisma, PrismaClient } from '@nicestack/common';
|
import { Prisma, PrismaClient } from '@nice/common';
|
||||||
import { BaseService } from "./base.service";
|
import { BaseService } from "./base.service";
|
||||||
import { DataArgs, DelegateArgs, DelegateFuncs, DelegateReturnTypes, UpdateOrderArgs } from "./base.type";
|
import { DataArgs, DelegateArgs, DelegateFuncs, DelegateReturnTypes, UpdateOrderArgs } from "./base.type";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { db, Prisma, PrismaClient } from "@nicestack/common";
|
import { db, Prisma, PrismaClient } from "@nice/common";
|
||||||
|
|
||||||
export type Operations =
|
export type Operations =
|
||||||
| 'aggregate'
|
| 'aggregate'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { UserProfile, RowModelRequest, RowRequestSchema } from "@nicestack/common";
|
import { UserProfile, RowModelRequest, RowRequestSchema } from "@nice/common";
|
||||||
import { RowModelService } from "./row-model.service";
|
import { RowModelService } from "./row-model.service";
|
||||||
import { isFieldCondition, LogicalCondition, SQLBuilder } from "./sql-builder";
|
import { isFieldCondition, LogicalCondition, SQLBuilder } from "./sql-builder";
|
||||||
import EventBus from "@server/utils/event-bus";
|
import EventBus from "@server/utils/event-bus";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Logger } from "@nestjs/common";
|
import { Logger } from "@nestjs/common";
|
||||||
import { UserProfile, db, RowModelRequest } from "@nicestack/common";
|
import { UserProfile, db, RowModelRequest } from "@nice/common";
|
||||||
import { LogicalCondition, OperatorType, SQLBuilder } from './sql-builder';
|
import { LogicalCondition, OperatorType, SQLBuilder } from './sql-builder';
|
||||||
export interface GetRowOptions {
|
export interface GetRowOptions {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { Prisma, UpdateOrderSchema } from '@nicestack/common';
|
import { Prisma, UpdateOrderSchema } from '@nice/common';
|
||||||
import { CourseService } from './course.service';
|
import { CourseService } from './course.service';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
const CourseCreateArgsSchema: ZodType<Prisma.CourseCreateArgs> = z.any()
|
const CourseCreateArgsSchema: ZodType<Prisma.CourseCreateArgs> = z.any()
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Prisma,
|
Prisma,
|
||||||
InstructorRole,
|
InstructorRole,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CourseService extends BaseService<Prisma.CourseDelegate> {
|
export class CourseService extends BaseService<Prisma.CourseDelegate> {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { db, EnrollmentStatus, PostType } from "@nicestack/common";
|
import { db, EnrollmentStatus, PostType } from "@nice/common";
|
||||||
|
|
||||||
// 更新课程评价统计
|
// 更新课程评价统计
|
||||||
export async function updateCourseReviewStats(courseId: string) {
|
export async function updateCourseReviewStats(courseId: string) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
import { DepartmentService } from './department.service';
|
import { DepartmentService } from './department.service';
|
||||||
import { AuthGuard } from '@server/auth/auth.guard';
|
import { AuthGuard } from '@server/auth/auth.guard';
|
||||||
import { db } from '@nicestack/common';
|
import { db } from '@nice/common';
|
||||||
|
|
||||||
@Controller('dept')
|
@Controller('dept')
|
||||||
export class DepartmentController {
|
export class DepartmentController {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { DepartmentService } from './department.service'; // assuming it's in the same directory
|
import { DepartmentService } from './department.service'; // assuming it's in the same directory
|
||||||
import { DepartmentMethodSchema, Prisma, UpdateOrderSchema } from '@nicestack/common';
|
import { DepartmentMethodSchema, Prisma, UpdateOrderSchema } from '@nice/common';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
import { DepartmentRowService } from './department.row.service';
|
import { DepartmentRowService } from './department.row.service';
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
DepartmentMethodSchema,
|
DepartmentMethodSchema,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { date, z } from 'zod';
|
import { date, z } from 'zod';
|
||||||
import { RowCacheService } from '../base/row-cache.service';
|
import { RowCacheService } from '../base/row-cache.service';
|
||||||
import { isFieldCondition } from '../base/sql-builder';
|
import { isFieldCondition } from '../base/sql-builder';
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
getUniqueItems,
|
getUniqueItems,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Prisma,
|
Prisma,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { BaseTreeService } from '../base/base.tree.service';
|
import { BaseTreeService } from '../base/base.tree.service';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { mapToDeptSimpleTree, getStaffsByDeptIds } from './utils';
|
import { mapToDeptSimpleTree, getStaffsByDeptIds } from './utils';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { UserProfile, db, DeptSimpleTreeNode, TreeDataNode } from "@nicestack/common";
|
import { UserProfile, db, DeptSimpleTreeNode, TreeDataNode } from "@nice/common";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将部门数据映射为DeptSimpleTreeNode结构
|
* 将部门数据映射为DeptSimpleTreeNode结构
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { Prisma, UpdateOrderSchema } from '@nicestack/common';
|
import { Prisma, UpdateOrderSchema } from '@nice/common';
|
||||||
import { EnrollmentService } from './enrollment.service';
|
import { EnrollmentService } from './enrollment.service';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
import { EnrollSchema, UnenrollSchema } from './enroll.schema';
|
import { EnrollSchema, UnenrollSchema } from './enroll.schema';
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Prisma,
|
Prisma,
|
||||||
EnrollmentStatus
|
EnrollmentStatus
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { EnrollSchema, UnenrollSchema } from './enroll.schema';
|
import { EnrollSchema, UnenrollSchema } from './enroll.schema';
|
||||||
import EventBus, { CrudOperation } from '@server/utils/event-bus';
|
import EventBus, { CrudOperation } from '@server/utils/event-bus';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { Prisma, UpdateOrderSchema } from '@nicestack/common';
|
import { Prisma, UpdateOrderSchema } from '@nice/common';
|
||||||
import { LectureService } from './lecture.service';
|
import { LectureService } from './lecture.service';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
const LectureCreateArgsSchema: ZodType<Prisma.LectureCreateArgs> = z.any()
|
const LectureCreateArgsSchema: ZodType<Prisma.LectureCreateArgs> = z.any()
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
db,
|
db,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Prisma
|
Prisma
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import EventBus, { CrudOperation } from '@server/utils/event-bus';
|
import EventBus, { CrudOperation } from '@server/utils/event-bus';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { db, Lecture } from "@nicestack/common"
|
import { db, Lecture } from "@nice/common"
|
||||||
|
|
||||||
export async function updateSectionLectureStats(sectionId: string) {
|
export async function updateSectionLectureStats(sectionId: string) {
|
||||||
const sectionStats = await db.lecture.aggregate({
|
const sectionStats = await db.lecture.aggregate({
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
import { MessageService } from './message.service';
|
import { MessageService } from './message.service';
|
||||||
import { AuthGuard } from '@server/auth/auth.guard';
|
import { AuthGuard } from '@server/auth/auth.guard';
|
||||||
import { db, VisitType } from '@nicestack/common';
|
import { db, VisitType } from '@nice/common';
|
||||||
|
|
||||||
@Controller('message')
|
@Controller('message')
|
||||||
export class MessageController {
|
export class MessageController {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { MessageService } from './message.service';
|
import { MessageService } from './message.service';
|
||||||
import { Prisma } from '@nicestack/common';
|
import { Prisma } from '@nice/common';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
const MessageUncheckedCreateInputSchema: ZodType<Prisma.MessageUncheckedCreateInput> = z.any()
|
const MessageUncheckedCreateInputSchema: ZodType<Prisma.MessageUncheckedCreateInput> = z.any()
|
||||||
const MessageWhereInputSchema: ZodType<Prisma.MessageWhereInput> = z.any()
|
const MessageWhereInputSchema: ZodType<Prisma.MessageWhereInput> = z.any()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { UserProfile, db, Prisma, VisitType, ObjectType } from '@nicestack/common';
|
import { UserProfile, db, Prisma, VisitType, ObjectType } from '@nice/common';
|
||||||
import { BaseService } from '../base/base.service';
|
import { BaseService } from '../base/base.service';
|
||||||
import EventBus, { CrudOperation } from '@server/utils/event-bus';
|
import EventBus, { CrudOperation } from '@server/utils/event-bus';
|
||||||
import { setMessageRelation } from './utils';
|
import { setMessageRelation } from './utils';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Message, UserProfile, VisitType, db } from "@nicestack/common"
|
import { Message, UserProfile, VisitType, db } from "@nice/common"
|
||||||
export async function setMessageRelation(
|
export async function setMessageRelation(
|
||||||
data: Message,
|
data: Message,
|
||||||
staff?: UserProfile,
|
staff?: UserProfile,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
import { PostService } from './post.service';
|
import { PostService } from './post.service';
|
||||||
import { AuthGuard } from '@server/auth/auth.guard';
|
import { AuthGuard } from '@server/auth/auth.guard';
|
||||||
import { db } from '@nicestack/common';
|
import { db } from '@nice/common';
|
||||||
|
|
||||||
@Controller('post')
|
@Controller('post')
|
||||||
export class PostController {
|
export class PostController {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { Prisma } from '@nicestack/common';
|
import { Prisma } from '@nice/common';
|
||||||
import { PostService } from './post.service';
|
import { PostService } from './post.service';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
const PostCreateArgsSchema: ZodType<Prisma.PostCreateArgs> = z.any();
|
const PostCreateArgsSchema: ZodType<Prisma.PostCreateArgs> = z.any();
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
RolePerms,
|
RolePerms,
|
||||||
ResPerm,
|
ResPerm,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { MessageService } from '../message/message.service';
|
import { MessageService } from '../message/message.service';
|
||||||
import { BaseService } from '../base/base.service';
|
import { BaseService } from '../base/base.service';
|
||||||
import { DepartmentService } from '../department/department.service';
|
import { DepartmentService } from '../department/department.service';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { db, Post, PostType, UserProfile, VisitType } from "@nicestack/common";
|
import { db, Post, PostType, UserProfile, VisitType } from "@nice/common";
|
||||||
|
|
||||||
export async function setPostRelation(params: { data: Post, staff?: UserProfile }) {
|
export async function setPostRelation(params: { data: Post, staff?: UserProfile }) {
|
||||||
const { data, staff } = params
|
const { data, staff } = params
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { Prisma, UpdateOrderSchema } from '@nicestack/common';
|
import { Prisma, UpdateOrderSchema } from '@nice/common';
|
||||||
import { RoleService } from './role.service';
|
import { RoleService } from './role.service';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
const RoleCreateArgsSchema: ZodType<Prisma.RoleCreateArgs> = z.any()
|
const RoleCreateArgsSchema: ZodType<Prisma.RoleCreateArgs> = z.any()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { db, ObjectType, RowModelRequest, RowRequestSchema, UserProfile } from "@nicestack/common";
|
import { db, ObjectType, RowModelRequest, RowRequestSchema, UserProfile } from "@nice/common";
|
||||||
import { RowCacheService } from "../base/row-cache.service";
|
import { RowCacheService } from "../base/row-cache.service";
|
||||||
import { isFieldCondition, LogicalCondition } from "../base/sql-builder";
|
import { isFieldCondition, LogicalCondition } from "../base/sql-builder";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { db, RoleMethodSchema, ObjectType, Prisma } from "@nicestack/common";
|
import { db, RoleMethodSchema, ObjectType, Prisma } from "@nice/common";
|
||||||
import { BaseService } from '../base/base.service';
|
import { BaseService } from '../base/base.service';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RoleService extends BaseService<Prisma.RoleDelegate> {
|
export class RoleService extends BaseService<Prisma.RoleDelegate> {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import {
|
import {
|
||||||
ObjectType,
|
ObjectType,
|
||||||
RoleMapMethodSchema,
|
RoleMapMethodSchema,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { RoleMapService } from './rolemap.service';
|
import { RoleMapService } from './rolemap.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
Prisma,
|
Prisma,
|
||||||
RowModelRequest,
|
RowModelRequest,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { DepartmentService } from '@server/models/department/department.service';
|
import { DepartmentService } from '@server/models/department/department.service';
|
||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
import { RowModelService } from '../base/row-model.service';
|
import { RowModelService } from '../base/row-model.service';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { PrismaClient, Resource } from '@prisma/client'
|
import { PrismaClient, Resource } from '@prisma/client'
|
||||||
import { ProcessResult, ResourceProcessor } from '../types'
|
import { ProcessResult, ResourceProcessor } from '../types'
|
||||||
import { db, ResourceProcessStatus } from '@nicestack/common'
|
import { db, ResourceProcessStatus } from '@nice/common'
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { BaseMetadata, FileMetadata, ResourceProcessor } from "../types";
|
import { BaseMetadata, FileMetadata, ResourceProcessor } from "../types";
|
||||||
import { Resource, db, ResourceProcessStatus } from "@nicestack/common";
|
import { Resource, db, ResourceProcessStatus } from "@nice/common";
|
||||||
import { extname } from "path";
|
import { extname } from "path";
|
||||||
import mime from "mime";
|
import mime from "mime";
|
||||||
import { calculateFileHash, getUploadFilePath } from "@server/utils/file";
|
import { calculateFileHash, getUploadFilePath } from "@server/utils/file";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { FileMetadata, ImageMetadata, ResourceProcessor } from "../types";
|
import { FileMetadata, ImageMetadata, ResourceProcessor } from "../types";
|
||||||
import { Resource, ResourceProcessStatus, db } from "@nicestack/common";
|
import { Resource, ResourceProcessStatus, db } from "@nice/common";
|
||||||
import { getUploadFilePath } from "@server/utils/file";
|
import { getUploadFilePath } from "@server/utils/file";
|
||||||
import { Logger } from "@nestjs/common";
|
import { Logger } from "@nestjs/common";
|
||||||
import { promises as fsPromises } from 'fs';
|
import { promises as fsPromises } from 'fs';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// import ffmpeg from 'fluent-ffmpeg';
|
// import ffmpeg from 'fluent-ffmpeg';
|
||||||
// import { ResourceProcessor } from '../types';
|
// import { ResourceProcessor } from '../types';
|
||||||
// import { Resource } from '@nicestack/common';
|
// import { Resource } from '@nice/common';
|
||||||
|
|
||||||
// export class VideoProcessor implements ResourceProcessor {
|
// export class VideoProcessor implements ResourceProcessor {
|
||||||
// async process(resource: Resource): Promise<Resource> {
|
// async process(resource: Resource): Promise<Resource> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { Prisma, UpdateOrderSchema } from '@nicestack/common';
|
import { Prisma, UpdateOrderSchema } from '@nice/common';
|
||||||
import { ResourceService } from './resource.service';
|
import { ResourceService } from './resource.service';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
const ResourceCreateArgsSchema: ZodType<Prisma.ResourceCreateArgs> = z.any()
|
const ResourceCreateArgsSchema: ZodType<Prisma.ResourceCreateArgs> = z.any()
|
||||||
|
|
|
@ -6,8 +6,7 @@ import {
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Prisma,
|
Prisma,
|
||||||
Resource,
|
Resource,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { createHash } from 'crypto';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ResourceService extends BaseService<Prisma.ResourceDelegate> {
|
export class ResourceService extends BaseService<Prisma.ResourceDelegate> {
|
||||||
|
@ -31,8 +30,4 @@ export class ResourceService extends BaseService<Prisma.ResourceDelegate> {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async calculateFileHash(buffer: Buffer): Promise<string> {
|
|
||||||
return createHash('sha256').update(buffer).digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Resource } from "@nicestack/common";
|
import { Resource } from "@nice/common";
|
||||||
|
|
||||||
export interface ResourceProcessor {
|
export interface ResourceProcessor {
|
||||||
process(resource: Resource): Promise<any>
|
process(resource: Resource): Promise<any>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { Prisma, UpdateOrderSchema } from '@nicestack/common';
|
import { Prisma, UpdateOrderSchema } from '@nice/common';
|
||||||
import { SectionService } from './section.service';
|
import { SectionService } from './section.service';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
const SectionCreateArgsSchema: ZodType<Prisma.SectionCreateArgs> = z.any()
|
const SectionCreateArgsSchema: ZodType<Prisma.SectionCreateArgs> = z.any()
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Prisma,
|
Prisma,
|
||||||
|
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SectionService extends BaseService<Prisma.SectionDelegate> {
|
export class SectionService extends BaseService<Prisma.SectionDelegate> {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
import { StaffService } from './staff.service';
|
import { StaffService } from './staff.service';
|
||||||
import { AuthGuard } from '@server/auth/auth.guard';
|
import { AuthGuard } from '@server/auth/auth.guard';
|
||||||
import { db } from '@nicestack/common';
|
import { db } from '@nice/common';
|
||||||
|
|
||||||
@Controller('staff')
|
@Controller('staff')
|
||||||
export class StaffController {
|
export class StaffController {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { StaffService } from './staff.service'; // Adjust the import path as necessary
|
import { StaffService } from './staff.service'; // Adjust the import path as necessary
|
||||||
import { StaffMethodSchema, Prisma, UpdateOrderSchema } from '@nicestack/common';
|
import { StaffMethodSchema, Prisma, UpdateOrderSchema } from '@nice/common';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
import { StaffRowService } from './staff.row.service';
|
import { StaffRowService } from './staff.row.service';
|
||||||
const StaffCreateArgsSchema: ZodType<Prisma.StaffCreateArgs> = z.any();
|
const StaffCreateArgsSchema: ZodType<Prisma.StaffCreateArgs> = z.any();
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
ResPerm,
|
ResPerm,
|
||||||
Staff,
|
Staff,
|
||||||
RowModelRequest,
|
RowModelRequest,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { DepartmentService } from '../department/department.service';
|
import { DepartmentService } from '../department/department.service';
|
||||||
import { RowCacheService } from '../base/row-cache.service';
|
import { RowCacheService } from '../base/row-cache.service';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
ObjectType,
|
ObjectType,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
Prisma,
|
Prisma,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { DepartmentService } from '../department/department.service';
|
import { DepartmentService } from '../department/department.service';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { BaseService } from '../base/base.service';
|
import { BaseService } from '../base/base.service';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
import { TaxonomyService } from './taxonomy.service';
|
import { TaxonomyService } from './taxonomy.service';
|
||||||
import { AuthGuard } from '@server/auth/auth.guard';
|
import { AuthGuard } from '@server/auth/auth.guard';
|
||||||
import { db } from '@nicestack/common';
|
import { db } from '@nice/common';
|
||||||
|
|
||||||
@Controller('tax')
|
@Controller('tax')
|
||||||
export class TaxonomyController {
|
export class TaxonomyController {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { TaxonomyService } from './taxonomy.service';
|
import { TaxonomyService } from './taxonomy.service';
|
||||||
import { TaxonomyMethodSchema } from '@nicestack/common';
|
import { TaxonomyMethodSchema } from '@nice/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TaxonomyRouter {
|
export class TaxonomyRouter {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { db, TaxonomyMethodSchema, Prisma } from '@nicestack/common';
|
import { db, TaxonomyMethodSchema, Prisma } from '@nice/common';
|
||||||
import { redis } from '@server/utils/redis/redis.service';
|
import { redis } from '@server/utils/redis/redis.service';
|
||||||
import { deleteByPattern } from '@server/utils/redis/utils';
|
import { deleteByPattern } from '@server/utils/redis/utils';
|
||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
import { TermService } from './term.service';
|
import { TermService } from './term.service';
|
||||||
import { AuthGuard } from '@server/auth/auth.guard';
|
import { AuthGuard } from '@server/auth/auth.guard';
|
||||||
import { db } from '@nicestack/common';
|
import { db } from '@nice/common';
|
||||||
|
|
||||||
@Controller('term')
|
@Controller('term')
|
||||||
export class TermController {
|
export class TermController {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { TermService } from './term.service'; // Adjust the import path as necessary
|
import { TermService } from './term.service'; // Adjust the import path as necessary
|
||||||
import { Prisma, TermMethodSchema, UpdateOrderSchema } from '@nicestack/common';
|
import { Prisma, TermMethodSchema, UpdateOrderSchema } from '@nice/common';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
import { TermRowService } from './term.row.service';
|
import { TermRowService } from './term.row.service';
|
||||||
const TermCreateArgsSchema: ZodType<Prisma.TermCreateArgs> = z.any();
|
const TermCreateArgsSchema: ZodType<Prisma.TermCreateArgs> = z.any();
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
RowModelRequest,
|
RowModelRequest,
|
||||||
TermMethodSchema,
|
TermMethodSchema,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { date, z } from 'zod';
|
import { date, z } from 'zod';
|
||||||
import { RowCacheService } from '../base/row-cache.service';
|
import { RowCacheService } from '../base/row-cache.service';
|
||||||
import { isFieldCondition } from '../base/sql-builder';
|
import { isFieldCondition } from '../base/sql-builder';
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
TaxonomySlug,
|
TaxonomySlug,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
TermAncestry,
|
TermAncestry,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { BaseTreeService } from '../base/base.tree.service';
|
import { BaseTreeService } from '../base/base.tree.service';
|
||||||
import EventBus, { CrudOperation } from '@server/utils/event-bus';
|
import EventBus, { CrudOperation } from '@server/utils/event-bus';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { TreeDataNode } from '@nicestack/common';
|
import { TreeDataNode } from '@nice/common';
|
||||||
|
|
||||||
export function formatToTermTreeData(term: any): TreeDataNode {
|
export function formatToTermTreeData(term: any): TreeDataNode {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TransformService } from './transform.service';
|
import { TransformService } from './transform.service';
|
||||||
import { TransformMethodSchema} from '@nicestack/common';
|
import { TransformMethodSchema} from '@nice/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TransformRouter {
|
export class TransformRouter {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
db,
|
db,
|
||||||
Prisma,
|
Prisma,
|
||||||
Staff,
|
Staff,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
import { TaxonomyService } from '@server/models/taxonomy/taxonomy.service';
|
import { TaxonomyService } from '@server/models/taxonomy/taxonomy.service';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TrpcService } from '@server/trpc/trpc.service';
|
import { TrpcService } from '@server/trpc/trpc.service';
|
||||||
import { Prisma } from '@nicestack/common';
|
import { Prisma } from '@nice/common';
|
||||||
|
|
||||||
import { VisitService } from './visit.service';
|
import { VisitService } from './visit.service';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Prisma,
|
Prisma,
|
||||||
VisitType,
|
VisitType,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import EventBus from '@server/utils/event-bus';
|
import EventBus from '@server/utils/event-bus';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VisitService extends BaseService<Prisma.VisitDelegate> {
|
export class VisitService extends BaseService<Prisma.VisitDelegate> {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { InjectQueue } from "@nestjs/bullmq";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import EventBus from "@server/utils/event-bus";
|
import EventBus from "@server/utils/event-bus";
|
||||||
import { Queue } from "bullmq";
|
import { Queue } from "bullmq";
|
||||||
import { ObjectType } from "@nicestack/common";
|
import { ObjectType } from "@nice/common";
|
||||||
import { QueueJobType } from "../types";
|
import { QueueJobType } from "../types";
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StatsService {
|
export class StatsService {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { ResourceProcessingPipeline } from '@server/models/resource/pipe/resourc
|
||||||
import { GeneralProcessor } from '@server/models/resource/processor/GeneralProcessor';
|
import { GeneralProcessor } from '@server/models/resource/processor/GeneralProcessor';
|
||||||
import { ImageProcessor } from '@server/models/resource/processor/ImageProcessor';
|
import { ImageProcessor } from '@server/models/resource/processor/ImageProcessor';
|
||||||
import superjson from 'superjson-cjs';
|
import superjson from 'superjson-cjs';
|
||||||
import { Resource } from '@nicestack/common';
|
import { Resource } from '@nice/common';
|
||||||
const logger = new Logger('FileProcessorWorker');
|
const logger = new Logger('FileProcessorWorker');
|
||||||
const pipeline = new ResourceProcessingPipeline()
|
const pipeline = new ResourceProcessingPipeline()
|
||||||
.addProcessor(new GeneralProcessor())
|
.addProcessor(new GeneralProcessor())
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
updateCourseLectureStats,
|
updateCourseLectureStats,
|
||||||
updateSectionLectureStats
|
updateSectionLectureStats
|
||||||
} from '@server/models/lecture/utils';
|
} from '@server/models/lecture/utils';
|
||||||
import { ObjectType } from '@nicestack/common';
|
import { ObjectType } from '@nice/common';
|
||||||
import {
|
import {
|
||||||
updateCourseEnrollmentStats,
|
updateCourseEnrollmentStats,
|
||||||
updateCourseReviewStats
|
updateCourseReviewStats
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { WebSocketServer, WebSocket } from "ws";
|
import { WebSocketServer, WebSocket } from "ws";
|
||||||
import { Logger } from "@nestjs/common";
|
import { Logger } from "@nestjs/common";
|
||||||
import { WebSocketServerConfig, WSClient, WebSocketType } from "../types";
|
import { WebSocketServerConfig, WSClient, WebSocketType } from "../types";
|
||||||
import { SocketMessage } from '@nicestack/common';
|
import { SocketMessage } from '@nice/common';
|
||||||
|
|
||||||
const DEFAULT_CONFIG: WebSocketServerConfig = {
|
const DEFAULT_CONFIG: WebSocketServerConfig = {
|
||||||
pingInterval: 30000,
|
pingInterval: 30000,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { readSyncMessage } from '@nicestack/common';
|
import { readSyncMessage } from '@nice/common';
|
||||||
import { applyAwarenessUpdate, Awareness, encodeAwarenessUpdate, removeAwarenessStates, writeSyncStep1, writeUpdate } from '@nicestack/common';
|
import { applyAwarenessUpdate, Awareness, encodeAwarenessUpdate, removeAwarenessStates, writeSyncStep1, writeUpdate } from '@nice/common';
|
||||||
import * as encoding from 'lib0/encoding';
|
import * as encoding from 'lib0/encoding';
|
||||||
import * as decoding from 'lib0/decoding';
|
import * as decoding from 'lib0/decoding';
|
||||||
import * as Y from "yjs"
|
import * as Y from "yjs"
|
||||||
|
@ -7,7 +7,7 @@ import { debounce } from 'lodash';
|
||||||
import { getPersistence, setPersistence } from './persistence';
|
import { getPersistence, setPersistence } from './persistence';
|
||||||
import { callbackHandler, isCallbackSet } from './callback';
|
import { callbackHandler, isCallbackSet } from './callback';
|
||||||
import { WebSocket } from "ws";
|
import { WebSocket } from "ws";
|
||||||
import { YMessageType } from '@nicestack/common';
|
import { YMessageType } from '@nice/common';
|
||||||
import { WSClient } from '../types';
|
import { WSClient } from '../types';
|
||||||
export const docs = new Map<string, WSSharedDoc>();
|
export const docs = new Map<string, WSSharedDoc>();
|
||||||
export const CALLBACK_DEBOUNCE_WAIT = parseInt(process.env.CALLBACK_DEBOUNCE_WAIT || '2000');
|
export const CALLBACK_DEBOUNCE_WAIT = parseInt(process.env.CALLBACK_DEBOUNCE_WAIT || '2000');
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Injectable } from "@nestjs/common";
|
||||||
import { WebSocketType, WSClient } from "../types";
|
import { WebSocketType, WSClient } from "../types";
|
||||||
import { BaseWebSocketServer } from "../base/base-websocket-server";
|
import { BaseWebSocketServer } from "../base/base-websocket-server";
|
||||||
import { encoding } from "lib0";
|
import { encoding } from "lib0";
|
||||||
import { YMessageType, writeSyncStep1, encodeAwarenessUpdate } from "@nicestack/common";
|
import { YMessageType, writeSyncStep1, encodeAwarenessUpdate } from "@nice/common";
|
||||||
import { getYDoc, closeConn, WSSharedDoc, messageListener, send } from "./ws-shared-doc";
|
import { getYDoc, closeConn, WSSharedDoc, messageListener, send } from "./ws-shared-doc";
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class YjsServer extends BaseWebSocketServer {
|
export class YjsServer extends BaseWebSocketServer {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Injectable, OnModuleInit } from "@nestjs/common";
|
||||||
import { WebSocketType } from "../types";
|
import { WebSocketType } from "../types";
|
||||||
import { BaseWebSocketServer } from "../base/base-websocket-server";
|
import { BaseWebSocketServer } from "../base/base-websocket-server";
|
||||||
import EventBus, { CrudOperation } from "@server/utils/event-bus";
|
import EventBus, { CrudOperation } from "@server/utils/event-bus";
|
||||||
import { ObjectType, SocketMsgType, MessageDto, PostDto, PostType } from "@nicestack/common";
|
import { ObjectType, SocketMsgType, MessageDto, PostDto, PostType } from "@nice/common";
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RealtimeServer extends BaseWebSocketServer implements OnModuleInit {
|
export class RealtimeServer extends BaseWebSocketServer implements OnModuleInit {
|
||||||
onModuleInit() {
|
onModuleInit() {
|
||||||
|
|
|
@ -10,8 +10,7 @@ import {
|
||||||
Staff,
|
Staff,
|
||||||
TaxonomySlug,
|
TaxonomySlug,
|
||||||
Term,
|
Term,
|
||||||
} from '@nicestack/common';
|
} from '@nice/common';
|
||||||
import * as argon2 from 'argon2';
|
|
||||||
import EventBus from '@server/utils/event-bus';
|
import EventBus from '@server/utils/event-bus';
|
||||||
import {
|
import {
|
||||||
|
|
||||||
|
@ -19,9 +18,7 @@ import {
|
||||||
DevDataCounts,
|
DevDataCounts,
|
||||||
getCounts,
|
getCounts,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
import { StaffService } from '@server/models/staff/staff.service';
|
import { StaffService } from '@server/models/staff/staff.service';
|
||||||
import { uuidv4 } from 'lib0/random';
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GenDevService {
|
export class GenDevService {
|
||||||
private readonly logger = new Logger(GenDevService.name);
|
private readonly logger = new Logger(GenDevService.name);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { db, InitAppConfigs, InitRoles, InitTaxonomies, ObjectType } from "@nicestack/common";
|
import { db, InitAppConfigs, InitRoles, InitTaxonomies, ObjectType } from "@nice/common";
|
||||||
import { AuthService } from '@server/auth/auth.service';
|
import { AuthService } from '@server/auth/auth.service';
|
||||||
import { MinioService } from '@server/utils/minio/minio.service';
|
import { MinioService } from '@server/utils/minio/minio.service';
|
||||||
import { AppConfigService } from '@server/models/app-config/app-config.service';
|
import { AppConfigService } from '@server/models/app-config/app-config.service';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { db, getRandomElement, getRandomIntInRange, getRandomTimeInterval, } from '@nicestack/common';
|
import { db, getRandomElement, getRandomIntInRange, getRandomTimeInterval, } from '@nice/common';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
export interface DevDataCounts {
|
export interface DevDataCounts {
|
||||||
deptCount: number;
|
deptCount: number;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { initTRPC, TRPCError } from '@trpc/server';
|
import { initTRPC, TRPCError } from '@trpc/server';
|
||||||
import superjson from 'superjson-cjs';
|
import superjson from 'superjson-cjs';
|
||||||
import * as trpcExpress from '@trpc/server/adapters/express';
|
import * as trpcExpress from '@trpc/server/adapters/express';
|
||||||
import { db, JwtPayload, UserProfile, RolePerms } from '@nicestack/common';
|
import { db, JwtPayload, UserProfile, RolePerms } from '@nice/common';
|
||||||
import { CreateWSSContextFnOptions } from '@trpc/server/adapters/ws';
|
import { CreateWSSContextFnOptions } from '@trpc/server/adapters/ws';
|
||||||
import { UserProfileService } from '@server/auth/utils';
|
import { UserProfileService } from '@server/auth/utils';
|
||||||
type Context = Awaited<ReturnType<TrpcService['createExpressContext']>>;
|
type Context = Awaited<ReturnType<TrpcService['createExpressContext']>>;
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { ChunkDto } from '@nice/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ChunkManager {
|
||||||
|
private readonly logger = new Logger(ChunkManager.name);
|
||||||
|
|
||||||
|
private readonly CHUNK_PROCESSING_CONCURRENCY = 3;
|
||||||
|
private readonly uploadDir: string;
|
||||||
|
private readonly tempDir: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.uploadDir = process.env.UPLOAD_DIR || path.join(process.cwd(), 'uploads');
|
||||||
|
this.tempDir = path.join(this.uploadDir, 'temp');
|
||||||
|
this.logger.localInstance.setLogLevels(["log", "debug"])
|
||||||
|
this.logger.log(`Initialized ChunkManager with uploadDir: ${this.uploadDir}, tempDir: ${this.tempDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateChunk(chunk: ChunkDto, file: Express.Multer.File): void {
|
||||||
|
this.logger.debug(`Validating chunk: identifier=${chunk?.identifier}, chunkNumber=${chunk?.chunkNumber}, bufferLength=${file?.buffer?.length}`);
|
||||||
|
|
||||||
|
if (!chunk?.identifier || !chunk.chunkNumber || !file?.buffer?.length) {
|
||||||
|
this.logger.warn('Invalid chunk metadata or buffer');
|
||||||
|
throw new Error('Invalid chunk metadata or buffer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(chunk: ChunkDto, file: Express.Multer.File): Promise<void> {
|
||||||
|
this.logger.log(`Saving chunk: identifier=${chunk.identifier}, chunkNumber=${chunk.chunkNumber}`);
|
||||||
|
this.validateChunk(chunk, file);
|
||||||
|
const chunkDir = path.join(this.tempDir, chunk.identifier);
|
||||||
|
const chunkPath = path.join(chunkDir, `${chunk.chunkNumber}`);
|
||||||
|
try {
|
||||||
|
this.logger.debug(`Creating chunk directory: ${chunkDir}`);
|
||||||
|
await fs.mkdir(chunkDir, { recursive: true, mode: 0o755 });
|
||||||
|
|
||||||
|
this.logger.debug(`Writing chunk file: ${chunkPath}`);
|
||||||
|
await fs.writeFile(chunkPath, file.buffer, { mode: 0o644 });
|
||||||
|
|
||||||
|
this.logger.log(`Chunk saved successfully: ${chunkPath}`);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Chunk save failed: ${error instanceof Error ? error.message : error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getChunks(identifier: string): Promise<number[]> {
|
||||||
|
this.logger.log(`Retrieving chunks for identifier: ${identifier}`);
|
||||||
|
if (!identifier) {
|
||||||
|
this.logger.warn('No identifier provided');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const chunkPath = path.join(this.tempDir, identifier);
|
||||||
|
this.logger.debug(`Reading directory: ${chunkPath}`);
|
||||||
|
|
||||||
|
const files = await fs.readdir(chunkPath);
|
||||||
|
const chunks = files
|
||||||
|
.map(Number)
|
||||||
|
.filter(num => !isNaN(num) && num > 0)
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
this.logger.log(`Found chunks: ${chunks.join(', ')}`);
|
||||||
|
return chunks;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(`Chunk retrieval failed: ${error}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async merge(chunkDir: string, finalPath: string, totalChunks: number): Promise<void> {
|
||||||
|
this.logger.log(`Merging chunks: chunkDir=${chunkDir}, finalPath=${finalPath}, totalChunks=${totalChunks}`);
|
||||||
|
|
||||||
|
if (!chunkDir || !finalPath || totalChunks <= 0) {
|
||||||
|
this.logger.warn('Invalid merge parameters');
|
||||||
|
throw new Error('Invalid merge parameters');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.logger.debug(`Opening final file: ${finalPath}`);
|
||||||
|
const fileHandle = await fs.open(finalPath, 'w');
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < totalChunks; i += this.CHUNK_PROCESSING_CONCURRENCY) {
|
||||||
|
const batch = Array.from(
|
||||||
|
{ length: Math.min(this.CHUNK_PROCESSING_CONCURRENCY, totalChunks - i) },
|
||||||
|
(_, j) => i + j + 1
|
||||||
|
);
|
||||||
|
this.logger.debug(`Processing batch: ${batch.join(', ')}`);
|
||||||
|
const chunkBuffers = await Promise.all(
|
||||||
|
batch.map(chunkNumber => {
|
||||||
|
const chunkPath = path.join(chunkDir, `${chunkNumber}`);
|
||||||
|
this.logger.debug(`Reading chunk: ${chunkPath}`);
|
||||||
|
return fs.readFile(chunkPath);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
for (const chunkBuffer of chunkBuffers) {
|
||||||
|
await fileHandle.write(chunkBuffer, 0, chunkBuffer.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.logger.debug(`Closing file handle: ${finalPath}`);
|
||||||
|
await fileHandle.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Merge completed successfully: ${finalPath}`);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Merge failed: ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async cleanup(identifier: string): Promise<void> {
|
||||||
|
this.logger.log(`Cleaning up chunks for identifier: ${identifier}`);
|
||||||
|
|
||||||
|
if (!identifier) {
|
||||||
|
this.logger.warn('No identifier provided for cleanup');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cleanupPath = path.join(this.tempDir, identifier);
|
||||||
|
this.logger.debug(`Removing directory: ${cleanupPath}`);
|
||||||
|
|
||||||
|
await fs.rm(cleanupPath, { recursive: true, force: true });
|
||||||
|
this.logger.log(`Cleanup completed for: ${identifier}`);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error && err.name !== 'ENOENT') {
|
||||||
|
this.logger.error(`Cleanup failed: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { UploadService } from './upload.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CleanService {
|
||||||
|
private readonly logger = new Logger(CleanService.name);
|
||||||
|
private readonly tempDir: string;
|
||||||
|
private readonly TEMP_FILE_EXPIRATION_TIME = 24 * 60 * 60 * 1000; // 24 hours
|
||||||
|
|
||||||
|
constructor(private uploadService: UploadService) {
|
||||||
|
this.tempDir = process.env.UPLOAD_TEMP_DIR || path.join(process.cwd(), 'uploads', 'temp');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Cron(CronExpression.EVERY_6_HOURS)
|
||||||
|
async cleanExpiredTemporaryFiles(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const files = await fs.readdir(this.tempDir);
|
||||||
|
|
||||||
|
for (const identifier of files) {
|
||||||
|
const chunkDir = path.join(this.tempDir, identifier);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stat = await fs.stat(chunkDir);
|
||||||
|
|
||||||
|
// 检查目录是否超过过期时间
|
||||||
|
const isExpired = (Date.now() - stat.mtime.getTime()) > this.TEMP_FILE_EXPIRATION_TIME;
|
||||||
|
|
||||||
|
// 检查上传是否不在进行中
|
||||||
|
const status = this.uploadService.checkUploadStatusInfo(identifier);
|
||||||
|
const isNotInProgress = !status ||
|
||||||
|
status.status === 'completed' ||
|
||||||
|
status.status === 'error' ||
|
||||||
|
status.status === 'paused';
|
||||||
|
|
||||||
|
if (isExpired && isNotInProgress) {
|
||||||
|
await fs.rm(chunkDir, { recursive: true, force: true });
|
||||||
|
this.logger.log(`Cleaned up expired temporary files for identifier: ${identifier}`);
|
||||||
|
this.uploadService.deleteUploadStatusInfo(identifier);
|
||||||
|
}
|
||||||
|
} catch (statError) {
|
||||||
|
// 处理可能的文件系统错误
|
||||||
|
this.logger.error(`Error processing directory ${identifier}: ${statError}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error during temporary file cleanup: ${error instanceof Error ? error.message : error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,32 +1,24 @@
|
||||||
export interface HookRequest {
|
export interface UploadCompleteEvent {
|
||||||
Type: 'post-finish' | 'pre-create';
|
identifier: string;
|
||||||
Event: {
|
filename: string;
|
||||||
Upload: {
|
size: number;
|
||||||
ID?: string;
|
hash: string;
|
||||||
MetaData?: {
|
integrityVerified: boolean;
|
||||||
filename?: string
|
|
||||||
};
|
|
||||||
Size?: number;
|
|
||||||
Storage?: {
|
|
||||||
Path: string
|
|
||||||
Type: string
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
export interface HookResponse {
|
|
||||||
RejectUpload?: boolean;
|
export type UploadEvent = {
|
||||||
HTTPResponse?: {
|
uploadStart: { identifier: string; filename: string; totalSize: number, resuming?: boolean };
|
||||||
StatusCode?: number;
|
uploadComplete: UploadCompleteEvent
|
||||||
Body?: string;
|
uploadError: { identifier: string; error: string, filename: string };
|
||||||
Headers?: Record<string, string>;
|
|
||||||
};
|
|
||||||
ChangeFileInfo?: {
|
|
||||||
ID?: string;
|
|
||||||
MetaData?: Record<string, string>;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
export interface FileHandle {
|
export interface UploadLock {
|
||||||
filename: string
|
clientId: string;
|
||||||
path: string
|
timestamp: number;
|
||||||
}
|
}
|
||||||
|
// 添加重试机制,处理临时网络问题
|
||||||
|
// 实现定期清理过期的临时文件
|
||||||
|
// 添加文件完整性校验
|
||||||
|
// 实现上传进度持久化,支持服务重启后恢复
|
||||||
|
// 添加并发限制,防止系统资源耗尽
|
||||||
|
// 实现文件去重功能,避免重复上传
|
||||||
|
// 添加日志记录和监控机制
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { Cron, CronExpression } from '@nestjs/schedule'; // 注意这里是 @Cron
|
||||||
|
import { UploadLock } from './types';
|
||||||
|
import { UploadLockInfo } from '@nice/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UploadLockService {
|
||||||
|
private readonly logger = new Logger(UploadLockService.name);
|
||||||
|
private readonly uploadLocks: Map<string, UploadLock> = new Map();
|
||||||
|
private readonly MAX_UPLOAD_DURATION = 2 * 60 * 60 * 1000; // 2 hours
|
||||||
|
|
||||||
|
acquireLock(identifier: string, clientId: string): boolean {
|
||||||
|
if (!identifier || !clientId) {
|
||||||
|
this.logger.warn('Invalid lock parameters');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const now = Date.now();
|
||||||
|
const existingLock = this.uploadLocks.get(identifier);
|
||||||
|
// Check if lock exists and is not expired
|
||||||
|
if (existingLock) {
|
||||||
|
const lockDuration = now - existingLock.timestamp;
|
||||||
|
|
||||||
|
// More robust lock conflict check
|
||||||
|
if (
|
||||||
|
existingLock.clientId !== clientId &&
|
||||||
|
lockDuration < this.MAX_UPLOAD_DURATION
|
||||||
|
) {
|
||||||
|
this.logger.warn(`Upload conflict: File ${identifier} is locked by another client`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire or update the lock
|
||||||
|
this.uploadLocks.set(identifier, { clientId, timestamp: now });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseLock(identifier: string, clientId: string): boolean {
|
||||||
|
if (!identifier || !clientId) {
|
||||||
|
this.logger.warn('Invalid unlock parameters');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLock = this.uploadLocks.get(identifier);
|
||||||
|
|
||||||
|
// Only allow release by the current lock owner
|
||||||
|
if (currentLock?.clientId === clientId) {
|
||||||
|
this.uploadLocks.delete(identifier);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.warn(`Unauthorized lock release attempt for ${identifier}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkLock(identifier: string): UploadLockInfo {
|
||||||
|
const now = Date.now();
|
||||||
|
const lockInfo = this.uploadLocks.get(identifier);
|
||||||
|
// Check if lock exists and is not expired
|
||||||
|
if (lockInfo && now - lockInfo.timestamp < this.MAX_UPLOAD_DURATION) {
|
||||||
|
return {
|
||||||
|
isLocked: true,
|
||||||
|
lockedBy: lockInfo.clientId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isLocked: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Cron(CronExpression.EVERY_HOUR)
|
||||||
|
cleanupExpiredLocks(): number {
|
||||||
|
const now = Date.now();
|
||||||
|
let removedLocksCount = 0;
|
||||||
|
|
||||||
|
for (const [identifier, lock] of this.uploadLocks.entries()) {
|
||||||
|
if (now - lock.timestamp > this.MAX_UPLOAD_DURATION) {
|
||||||
|
this.uploadLocks.delete(identifier);
|
||||||
|
removedLocksCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedLocksCount > 0) {
|
||||||
|
this.logger.log(`Cleaned up ${removedLocksCount} expired locks`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return removedLocksCount;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,54 @@
|
||||||
import { Controller, Logger, Post, Body, Res } from "@nestjs/common";
|
import {
|
||||||
import { Response } from "express";
|
Controller,
|
||||||
import { HookRequest, HookResponse } from "./types";
|
Post,
|
||||||
import { UploadService } from "./upload.service";
|
UseInterceptors,
|
||||||
|
UploadedFile,
|
||||||
|
Body,
|
||||||
|
Param,
|
||||||
|
Get,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
import { UploadService } from './upload.service';
|
||||||
|
import { ChunkDto } from '@nice/common';
|
||||||
|
|
||||||
@Controller("upload")
|
@Controller('upload')
|
||||||
export class UploadController {
|
export class UploadController {
|
||||||
private readonly logger = new Logger(UploadController.name);
|
|
||||||
constructor(private readonly uploadService: UploadService) { }
|
constructor(private readonly uploadService: UploadService) { }
|
||||||
@Post("hook")
|
|
||||||
async handleTusHook(@Body() hookRequest: HookRequest, @Res() res: Response) {
|
@Post('chunk')
|
||||||
try {
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
const hookResponse = await this.uploadService.handleTusHook(hookRequest);
|
async uploadChunk(
|
||||||
return res.status(200).json(hookResponse);
|
@Body('chunk') chunkString: string, // 改为接收字符串
|
||||||
} catch (error: any) {
|
@UploadedFile() file: Express.Multer.File,
|
||||||
this.logger.error(`Error handling hook: ${error.message}`);
|
@Body('clientId') clientId: string
|
||||||
return res.status(500).json({ error: 'Internal server error' });
|
) {
|
||||||
|
const chunk = JSON.parse(chunkString); // 解析字符串为对象
|
||||||
|
await this.uploadService.uploadChunk(chunk, file, clientId);
|
||||||
|
return { message: 'Chunk uploaded successfully' };
|
||||||
|
}
|
||||||
|
@Get('status/:identifier')
|
||||||
|
checkUploadStatusInfo(@Param('identifier') identifier: string) {
|
||||||
|
const status = this.uploadService.checkUploadStatusInfo(identifier);
|
||||||
|
return status || { message: 'No upload status found' };
|
||||||
|
}
|
||||||
|
@Post('pause/:identifier')
|
||||||
|
pauseUpload(
|
||||||
|
@Param('identifier') identifier: string,
|
||||||
|
@Body('clientId') clientId: string
|
||||||
|
) {
|
||||||
|
this.uploadService.pauseUpload(identifier, clientId);
|
||||||
|
return { message: 'Upload paused successfully' };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('resume/:identifier')
|
||||||
|
async resumeUpload(
|
||||||
|
@Param('identifier') identifier: string,
|
||||||
|
@Body('clientId') clientId: string
|
||||||
|
) {
|
||||||
|
const resumed = this.uploadService.resumeUpload(identifier, clientId);
|
||||||
|
if (!resumed) {
|
||||||
|
throw new Error('Unable to resume upload');
|
||||||
}
|
}
|
||||||
|
return { message: 'Upload resumed successfully' };
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,6 +2,9 @@ import { Module } from '@nestjs/common';
|
||||||
import { UploadController } from './upload.controller';
|
import { UploadController } from './upload.controller';
|
||||||
import { UploadService } from './upload.service';
|
import { UploadService } from './upload.service';
|
||||||
import { BullModule } from '@nestjs/bullmq';
|
import { BullModule } from '@nestjs/bullmq';
|
||||||
|
import { UploadLockService } from './upload-lock.service';
|
||||||
|
import { ChunkManager } from './chunk.manager';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
BullModule.registerQueue({
|
BullModule.registerQueue({
|
||||||
|
@ -9,6 +12,6 @@ import { BullModule } from '@nestjs/bullmq';
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
controllers: [UploadController],
|
controllers: [UploadController],
|
||||||
providers: [UploadService],
|
providers: [UploadService, UploadLockService, ChunkManager],
|
||||||
})
|
})
|
||||||
export class UploadModule { }
|
export class UploadModule { }
|
|
@ -1,109 +1,285 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { Queue, Job } from 'bullmq'; // 添加 Job 导入
|
import { ChunkDto, UploadStatusInfo, UploadProgress, UploadStatusInfoDto } from '@nice/common';
|
||||||
import { InjectQueue } from '@nestjs/bullmq';
|
import * as fs from 'fs/promises';
|
||||||
import { db, Resource, ResourceType } from '@nicestack/common';
|
import * as path from 'path';
|
||||||
import { HookRequest, HookResponse } from './types';
|
import mitt from 'mitt';
|
||||||
import dayjs from 'dayjs';
|
import { ChunkManager } from './chunk.manager';
|
||||||
import { getFilenameWithoutExt } from '@server/utils/file';
|
import { UploadLockService } from './upload-lock.service';
|
||||||
import { QueueJobType } from '@server/queue/types';
|
import { calculateFileHash } from '@server/utils/file';
|
||||||
import { nanoid } from 'nanoid';
|
import { UploadEvent } from './types';
|
||||||
import { slugify } from 'transliteration';
|
|
||||||
import path from 'path';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UploadService {
|
export class UploadService {
|
||||||
private readonly logger = new Logger(UploadService.name);
|
private readonly logger = new Logger(UploadService.name);
|
||||||
|
private readonly uploadDir: string;
|
||||||
|
private readonly tempDir: string;
|
||||||
|
private readonly fileStatuses: Map<string, UploadStatusInfo> = new Map();
|
||||||
|
private readonly emitter = mitt<UploadEvent>();
|
||||||
|
// Performance Optimization: Configurable upload parameters
|
||||||
|
private MAX_CONCURRENT_UPLOADS = 5; // Configurable concurrent uploads
|
||||||
|
private MAX_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB max chunk size
|
||||||
|
private UPLOAD_TIMEOUT = 30 * 60 * 1000; // 30 minutes timeout
|
||||||
constructor(
|
constructor(
|
||||||
@InjectQueue('file-queue') private fileQueue: Queue
|
private chunkManager: ChunkManager,
|
||||||
) { }
|
private uploadLockService: UploadLockService
|
||||||
|
) {
|
||||||
async handlePreCreateHook(hookRequest: HookRequest): Promise<HookResponse> {
|
// Validate upload directory configuration
|
||||||
const hookResponse: HookResponse = {
|
this.uploadDir = this.validateUploadDirectory();
|
||||||
HTTPResponse: {
|
this.tempDir = path.join(this.uploadDir, 'temp');
|
||||||
Headers: {}
|
this.initDirectories().catch(error => {
|
||||||
}
|
this.logger.error(`Failed to initialize upload directories: ${error.message}`);
|
||||||
};
|
process.exit(1);
|
||||||
const metaData = hookRequest.Event.Upload.MetaData;
|
});
|
||||||
const isValid = metaData && 'filename' in metaData;
|
this.configureUploadLimits();
|
||||||
if (!isValid) {
|
}
|
||||||
hookResponse.RejectUpload = true;
|
private validateUploadDirectory(): string {
|
||||||
hookResponse.HTTPResponse.StatusCode = 400;
|
const uploadDir = process.env.UPLOAD_DIR;
|
||||||
hookResponse.HTTPResponse.Body = 'no filename provided';
|
if (!uploadDir) {
|
||||||
hookResponse.HTTPResponse.Headers['X-Some-Header'] = 'yes';
|
throw new Error('UPLOAD_DIR environment variable is not set');
|
||||||
} else {
|
|
||||||
const timestamp = dayjs().format('YYMMDDHHmmss');
|
|
||||||
const originalName = metaData.filename;
|
|
||||||
const extension = path.extname(originalName); // 获取文件扩展名
|
|
||||||
// 清理并转换文件名(不包含扩展名)
|
|
||||||
const cleanName = slugify(getFilenameWithoutExt(originalName), {
|
|
||||||
lowercase: true,
|
|
||||||
separator: '-',
|
|
||||||
trim: true
|
|
||||||
})
|
|
||||||
.replace(/[^\w-]/g, '')
|
|
||||||
.replace(/-+/g, '-')
|
|
||||||
.substring(0, 32);
|
|
||||||
|
|
||||||
const uniqueId = nanoid(6);
|
|
||||||
const fileId = `${timestamp}_${cleanName}_${uniqueId}`;
|
|
||||||
fs.mkdirSync(path.join(process.env.UPLOAD_DIR, fileId), { recursive: true, mode: 0o755 })
|
|
||||||
// fs.chownSync(path.join(process.env.UPLOAD_DIR, fileId), 100999, 100999);
|
|
||||||
// await fs.promises.mkdir(path.join(process.env.UPLOAD_DIR, fileId), { recursive: true });
|
|
||||||
hookResponse.ChangeFileInfo = {
|
|
||||||
ID: `${fileId}/${fileId}${extension}`,
|
|
||||||
MetaData: {
|
|
||||||
filename: originalName,
|
|
||||||
normalized_name: cleanName,
|
|
||||||
folder: fileId
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return hookResponse;
|
return uploadDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePostFinishHook(hookRequest: HookRequest) {
|
private handleUploadError(identifier: string, error: unknown): void {
|
||||||
const { ID, Size, Storage, MetaData } = hookRequest.Event.Upload;
|
const status = this.fileStatuses.get(identifier);
|
||||||
const filename = MetaData?.filename;
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
if (status) {
|
||||||
const resource = await db.resource.create({
|
status.status = 'error';
|
||||||
data: {
|
status.error = errorMessage;
|
||||||
filename,
|
}
|
||||||
fileId: ID,
|
this.logger.error(`Upload error for ${identifier}: ${errorMessage}`);
|
||||||
title: getFilenameWithoutExt(filename), // 使用没有扩展名的标题
|
this.emitter.emit('uploadError', {
|
||||||
metadata: MetaData || {}
|
identifier,
|
||||||
}
|
error: errorMessage,
|
||||||
})
|
filename: status?.filename
|
||||||
await this.addToProcessorPipe(resource)
|
});
|
||||||
this.logger.log(`Upload ${ID} (${Size} bytes) is finished.`);
|
// Safe cleanup of temporary files
|
||||||
|
this.chunkManager.cleanup(identifier).catch(cleanupError =>
|
||||||
|
this.logger.error(`Cleanup failed for ${identifier}: ${cleanupError}`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
async handleTusHook(hookRequest: HookRequest): Promise<HookResponse> {
|
// Improved directory initialization with better error handling
|
||||||
const hookResponse: HookResponse = {
|
private async initDirectories(): Promise<void> {
|
||||||
HTTPResponse: {
|
|
||||||
Headers: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try {
|
try {
|
||||||
if (hookRequest.Type === 'pre-create') {
|
await fs.mkdir(this.uploadDir, { recursive: true });
|
||||||
return this.handlePreCreateHook(hookRequest);
|
await fs.mkdir(this.tempDir, { recursive: true });
|
||||||
}
|
} catch (error) {
|
||||||
if (hookRequest.Type === 'post-finish') {
|
this.logger.error(`Directory initialization failed: ${error}`);
|
||||||
this.handlePostFinishHook(hookRequest);
|
|
||||||
}
|
|
||||||
return hookResponse;
|
|
||||||
} catch (error: any) {
|
|
||||||
this.logger.error(`Error handling hook: ${error.message}`);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async addToProcessorPipe(resource: Resource): Promise<Job> { // 修改返回类型为 Job
|
private configureUploadLimits(): void {
|
||||||
const job = await this.fileQueue.add(QueueJobType.FILE_PROCESS, {
|
const maxUploads = parseInt(process.env.MAX_CONCURRENT_UPLOADS || '5', 10);
|
||||||
resource,
|
const maxChunkSize = parseInt(process.env.MAX_CHUNK_SIZE || '10485760', 10);
|
||||||
timestamp: Date.now()
|
this.MAX_CONCURRENT_UPLOADS = maxUploads;
|
||||||
}, {
|
this.MAX_CHUNK_SIZE = maxChunkSize;
|
||||||
attempts: 3,
|
}
|
||||||
removeOnComplete: true,
|
// Enhanced input validation
|
||||||
jobId: resource.id
|
async uploadChunk(chunk: ChunkDto, file: Express.Multer.File, clientId: string): Promise<void> {
|
||||||
|
// Validate chunk size
|
||||||
|
if (chunk.currentChunkSize > this.MAX_CHUNK_SIZE) {
|
||||||
|
throw new Error(`Chunk size exceeds maximum limit of ${this.MAX_CHUNK_SIZE} bytes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limiting and concurrent upload control
|
||||||
|
await this.controlConcurrentUploads();
|
||||||
|
|
||||||
|
const { identifier } = chunk;
|
||||||
|
|
||||||
|
const lockAcquired = this.uploadLockService.acquireLock(identifier, clientId);
|
||||||
|
|
||||||
|
if (!lockAcquired) {
|
||||||
|
throw new Error('Concurrent upload limit reached');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Add timeout mechanism
|
||||||
|
const uploadPromise = this.processChunkUpload(chunk, file);
|
||||||
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Upload timeout')), this.UPLOAD_TIMEOUT)
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.race([uploadPromise, timeoutPromise]);
|
||||||
|
} catch (error) {
|
||||||
|
this.handleUploadError(identifier, error);
|
||||||
|
} finally {
|
||||||
|
this.uploadLockService.releaseLock(identifier, clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async controlConcurrentUploads(): Promise<void> {
|
||||||
|
const activeUploads = Array.from(this.fileStatuses.values())
|
||||||
|
.filter(status => status.status === 'uploading').length;
|
||||||
|
|
||||||
|
if (activeUploads >= this.MAX_CONCURRENT_UPLOADS) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait and retry
|
||||||
|
await this.controlConcurrentUploads();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async processChunkUpload(chunk: ChunkDto, file: Express.Multer.File): Promise<void> {
|
||||||
|
const { identifier } = chunk;
|
||||||
|
if (!this.fileStatuses.has(identifier)) {
|
||||||
|
await this.initUploadStatusInfo(chunk);
|
||||||
|
}
|
||||||
|
const status = this.fileStatuses.get(identifier);
|
||||||
|
if (!status) {
|
||||||
|
throw new Error('File status initialization failed');
|
||||||
|
}
|
||||||
|
if (status.chunks.has(chunk.chunkNumber)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.chunkManager.save(chunk, file);
|
||||||
|
this.updateProgress(chunk);
|
||||||
|
|
||||||
|
if (this.isUploadComplete(identifier)) {
|
||||||
|
await this.finalizeUpload(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initUploadStatusInfo(chunk: ChunkDto): Promise<void> {
|
||||||
|
const { identifier, filename, totalSize } = chunk;
|
||||||
|
|
||||||
|
// 获取已经上传的chunks
|
||||||
|
const uploadedChunks = await this.chunkManager.getChunks(identifier);
|
||||||
|
const uploadedSize = uploadedChunks.length * chunk.currentChunkSize;
|
||||||
|
|
||||||
|
this.emitter.emit('uploadStart', {
|
||||||
|
identifier,
|
||||||
|
filename,
|
||||||
|
totalSize,
|
||||||
|
resuming: uploadedChunks.length > 0
|
||||||
});
|
});
|
||||||
return job;
|
|
||||||
|
this.fileStatuses.set(identifier, {
|
||||||
|
identifier,
|
||||||
|
filename,
|
||||||
|
totalSize,
|
||||||
|
uploadedSize,
|
||||||
|
status: 'uploading',
|
||||||
|
chunks: new Set(uploadedChunks), // 初始化已上传的chunks
|
||||||
|
startTime: Date.now(),
|
||||||
|
lastUpdateTime: Date.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private updateProgress(chunk: ChunkDto): void {
|
||||||
|
const status = this.fileStatuses.get(chunk.identifier);
|
||||||
|
if (!status) return;
|
||||||
|
// Use more efficient progress calculation
|
||||||
|
const newUploadedSize = chunk.chunkNumber * chunk.currentChunkSize;
|
||||||
|
const progressPercentage = Math.min(
|
||||||
|
Math.round((newUploadedSize / status.totalSize) * 100),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
status.chunks.add(chunk.chunkNumber);
|
||||||
|
status.uploadedSize = newUploadedSize;
|
||||||
|
status.lastUpdateTime = Date.now();
|
||||||
|
const progress: UploadProgress = {
|
||||||
|
identifier: chunk.identifier,
|
||||||
|
percentage: progressPercentage,
|
||||||
|
uploadedSize: newUploadedSize,
|
||||||
|
totalSize: status.totalSize,
|
||||||
|
speed: this.calculateSpeed(status),
|
||||||
|
remainingTime: this.calculateRemainingTime(status)
|
||||||
|
};
|
||||||
|
status.progress = progress
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateRemainingTime(status: UploadStatusInfo): number {
|
||||||
|
const speed = this.calculateSpeed(status);
|
||||||
|
if (speed === 0) return 0;
|
||||||
|
const remainingBytes = status.totalSize - status.uploadedSize;
|
||||||
|
return Math.ceil(remainingBytes / speed); // Returns seconds remaining
|
||||||
|
}
|
||||||
|
private calculateSpeed(status: UploadStatusInfo): number {
|
||||||
|
const duration = (status.lastUpdateTime - status.startTime) / 1000; // in seconds
|
||||||
|
return duration > 0 ? Math.round(status.uploadedSize / duration) : 0; // bytes per second
|
||||||
|
}
|
||||||
|
private async finalizeUpload(chunk: ChunkDto): Promise<void> {
|
||||||
|
const { identifier, filename, totalChunks, checksum } = chunk;
|
||||||
|
const chunkDir = path.join(this.tempDir, identifier);
|
||||||
|
const finalPath = path.join(this.uploadDir, filename);
|
||||||
|
try {
|
||||||
|
await this.chunkManager.merge(chunkDir, finalPath, totalChunks);
|
||||||
|
// Calculate file hash
|
||||||
|
const calculatedHash = await calculateFileHash(finalPath);
|
||||||
|
// Verify file integrity
|
||||||
|
if (checksum && calculatedHash !== checksum) {
|
||||||
|
throw new Error('File integrity check failed: Checksums do not match');
|
||||||
|
}
|
||||||
|
await this.chunkManager.cleanup(identifier);
|
||||||
|
const status = this.fileStatuses.get(identifier);
|
||||||
|
if (status) {
|
||||||
|
status.status = 'completed';
|
||||||
|
status.hash = calculatedHash;
|
||||||
|
}
|
||||||
|
this.emitter.emit('uploadComplete', {
|
||||||
|
identifier,
|
||||||
|
filename,
|
||||||
|
size: status.totalSize,
|
||||||
|
hash: calculatedHash,
|
||||||
|
integrityVerified: !checksum || calculatedHash === checksum
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.handleUploadError(identifier, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private isUploadComplete(identifier: string): boolean {
|
||||||
|
const status = this.fileStatuses.get(identifier);
|
||||||
|
if (!status) return false;
|
||||||
|
|
||||||
|
return status.uploadedSize === status.totalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteUploadStatusInfo(identifier: string): boolean {
|
||||||
|
// Check if the file status exists
|
||||||
|
const status = this.fileStatuses.get(identifier);
|
||||||
|
if (!status) {
|
||||||
|
// If the status doesn't exist, return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Check if the upload is still in progress
|
||||||
|
if (status.status === 'uploading') {
|
||||||
|
this.logger.warn(`Attempting to delete file status for ongoing upload: ${identifier}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Remove the file status from the map
|
||||||
|
const deleted = this.fileStatuses.delete(identifier);
|
||||||
|
if (deleted) {
|
||||||
|
this.logger.log(`File status deleted for identifier: ${identifier}`);
|
||||||
|
}
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
checkUploadStatusInfo(identifier: string): UploadStatusInfoDto {
|
||||||
|
const lockInfo = this.uploadLockService.checkLock(identifier);
|
||||||
|
const statusInfo = {
|
||||||
|
...lockInfo,
|
||||||
|
...this.fileStatuses.get(identifier)
|
||||||
|
};
|
||||||
|
return statusInfo || null
|
||||||
|
}
|
||||||
|
pauseUpload(identifier: string, clientId: string): void {
|
||||||
|
const status = this.fileStatuses.get(identifier);
|
||||||
|
if (status) {
|
||||||
|
status.status = 'paused';
|
||||||
|
this.uploadLockService.releaseLock(identifier, clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resumeUpload(identifier: string, clientId: string): boolean {
|
||||||
|
const status = this.fileStatuses.get(identifier);
|
||||||
|
if (status) {
|
||||||
|
// Try to reacquire the lock
|
||||||
|
const lockAcquired = this.uploadLockService.acquireLock(identifier, clientId);
|
||||||
|
if (lockAcquired) {
|
||||||
|
status.status = 'uploading';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
onUploadEvent<K extends keyof UploadEvent>(
|
||||||
|
event: K,
|
||||||
|
handler: (data: UploadEvent[K]) => void
|
||||||
|
): () => void {
|
||||||
|
this.emitter.on(event, handler);
|
||||||
|
return () => this.emitter.off(event, handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import mitt from 'mitt';
|
import mitt from 'mitt';
|
||||||
import { ObjectType, UserProfile, MessageDto } from '@nicestack/common';
|
import { ObjectType, UserProfile, MessageDto } from '@nice/common';
|
||||||
export enum CrudOperation {
|
export enum CrudOperation {
|
||||||
CREATED,
|
CREATED,
|
||||||
UPDATED,
|
UPDATED,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { createReadStream } from "fs";
|
import { createReadStream } from "fs";
|
||||||
import { createInterface } from "readline";
|
import { createInterface } from "readline";
|
||||||
|
|
||||||
import { db } from '@nicestack/common';
|
import { db } from '@nice/common';
|
||||||
import * as tus from "tus-js-client";
|
import * as tus from "tus-js-client";
|
||||||
import ExcelJS from 'exceljs';
|
import ExcelJS from 'exceljs';
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { PipeTransform, BadRequestException } from '@nestjs/common';
|
||||||
|
import { ZodSchema } from 'zod';
|
||||||
|
|
||||||
|
export class ZodValidationPipe implements PipeTransform {
|
||||||
|
constructor(private schema: ZodSchema) { }
|
||||||
|
|
||||||
|
transform(value: unknown) {
|
||||||
|
try {
|
||||||
|
const result = this.schema.parse(value);
|
||||||
|
return result;
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new BadRequestException('Validation failed', {
|
||||||
|
cause: error,
|
||||||
|
description: error.errors
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,9 +27,9 @@
|
||||||
"@floating-ui/react": "^0.26.25",
|
"@floating-ui/react": "^0.26.25",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
"@nicestack/client": "workspace:^",
|
"@nice/client": "workspace:^",
|
||||||
"@nicestack/common": "workspace:^",
|
"@nice/common": "workspace:^",
|
||||||
"@nicestack/iconer": "workspace:^",
|
"@nice/iconer": "workspace:^",
|
||||||
"@tanstack/query-async-storage-persister": "^5.51.9",
|
"@tanstack/query-async-storage-persister": "^5.51.9",
|
||||||
"@tanstack/react-query": "^5.51.21",
|
"@tanstack/react-query": "^5.51.21",
|
||||||
"@tanstack/react-query-persist-client": "^5.51.9",
|
"@tanstack/react-query-persist-client": "^5.51.9",
|
||||||
|
@ -58,7 +58,8 @@
|
||||||
"superjson": "^2.2.1",
|
"superjson": "^2.2.1",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"yjs": "^13.6.20",
|
"yjs": "^13.6.20",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8",
|
||||||
|
"uuid": "^10.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.9.0",
|
"@eslint/js": "^9.9.0",
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {
|
||||||
AppConfigSlug,
|
AppConfigSlug,
|
||||||
BaseSetting,
|
BaseSetting,
|
||||||
RolePerms,
|
RolePerms,
|
||||||
} from "@nicestack/common";
|
} from "@nice/common";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -11,12 +11,12 @@ import {
|
||||||
message,
|
message,
|
||||||
theme,
|
theme,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { useAppConfig } from "@nicestack/client";
|
import { useAppConfig } from "@nice/client";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
|
|
||||||
import FixedHeader from "@web/src/components/layout/fix-header";
|
import FixedHeader from "@web/src/components/layout/fix-header";
|
||||||
import { useForm } from "antd/es/form/Form";
|
import { useForm } from "antd/es/form/Form";
|
||||||
import { api } from "@nicestack/client"
|
import { api } from "@nice/client"
|
||||||
import { MainLayoutContext } from "../layout";
|
import { MainLayoutContext } from "../layout";
|
||||||
|
|
||||||
export default function BaseSettingPage() {
|
export default function BaseSettingPage() {
|
||||||
|
|
|
@ -10,10 +10,10 @@ import { theme } from "antd";
|
||||||
import ResizableSidebar from "@web/src/components/layout/resizable-sidebar";
|
import ResizableSidebar from "@web/src/components/layout/resizable-sidebar";
|
||||||
import SidebarContent from "@web/src/components/layout/sidebar-content";
|
import SidebarContent from "@web/src/components/layout/sidebar-content";
|
||||||
import UserHeader from "@web/src/components/layout/user-header";
|
import UserHeader from "@web/src/components/layout/user-header";
|
||||||
import { Icon } from "@nicestack/iconer";
|
import { Icon } from "@nice/iconer";
|
||||||
import { env } from "@web/src/env";
|
import { env } from "@web/src/env";
|
||||||
import RoundedClip from "@web/src/components/svg/rounded-clip";
|
import RoundedClip from "@web/src/components/svg/rounded-clip";
|
||||||
import {useTerm} from "@nicestack/client"
|
import {useTerm} from "@nice/client"
|
||||||
|
|
||||||
export const MainLayoutContext = createContext<{
|
export const MainLayoutContext = createContext<{
|
||||||
pageWidth?: number;
|
pageWidth?: number;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
import { CourseList } from "@web/src/components/models/course/list/course-list";
|
import { CourseList } from "@web/src/components/models/course/list/course-list";
|
||||||
import { Button } from "@web/src/components/presentation/element/Button";
|
import { Button } from "@web/src/components/presentation/element/Button";
|
||||||
import { api } from "@nicestack/client";
|
import { api } from "@nice/client";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { CourseCard } from "@web/src/components/models/course/card/CourseCard";
|
import { CourseCard } from "@web/src/components/models/course/card/CourseCard";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { CourseList } from "@web/src/components/models/course/list/course-list";
|
import { CourseList } from "@web/src/components/models/course/list/course-list";
|
||||||
import { api } from "@nicestack/client";
|
import { api } from "@nice/client";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { CourseCard } from "@web/src/components/models/course/card/CourseCard";
|
import { CourseCard } from "@web/src/components/models/course/card/CourseCard";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
|
|
|
@ -1,79 +1,82 @@
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import * as tus from 'tus-js-client';
|
import { useUpload } from '@nice/client'; // Assuming the previous hook is in this file
|
||||||
|
|
||||||
const UploadTest: React.FC = () => {
|
const FileUploadComponent: React.FC = () => {
|
||||||
const [progress, setProgress] = useState<number>(0);
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||||
const [uploadStatus, setUploadStatus] = useState<string>('');
|
|
||||||
|
|
||||||
const handleFileSelect = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
const {
|
||||||
const file = event.target.files?.[0];
|
upload,
|
||||||
if (!file) return;
|
pauseUpload,
|
||||||
|
resumeUpload,
|
||||||
|
progress,
|
||||||
|
errors
|
||||||
|
} = useUpload({
|
||||||
|
// Optional configuration
|
||||||
|
baseUrl: "http://localhost:3000/upload",
|
||||||
|
onProgress: (progressInfo) => {
|
||||||
|
console.log('Upload progress:', progressInfo);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error('Upload error:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 创建 tus upload 实例
|
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const upload = new tus.Upload(file, {
|
if (event.target.files) {
|
||||||
endpoint: 'http://localhost:8080/files', // 替换成你的 NestJS 服务器地址
|
setSelectedFiles(Array.from(event.target.files));
|
||||||
retryDelays: [0, 3000, 5000],
|
}
|
||||||
metadata: {
|
};
|
||||||
filename: file.name,
|
|
||||||
filetype: file.type
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.error('上传失败:', error);
|
|
||||||
setUploadStatus('上传失败');
|
|
||||||
},
|
|
||||||
onProgress: (bytesUploaded, bytesTotal) => {
|
|
||||||
const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
|
|
||||||
setProgress(Number(percentage));
|
|
||||||
setUploadStatus('上传中...');
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
setUploadStatus('上传成功!');
|
|
||||||
console.log('上传完成:', upload.url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 开始上传
|
const handleUpload = async () => {
|
||||||
upload.start();
|
try {
|
||||||
}, []);
|
await upload(selectedFiles);
|
||||||
|
alert('Upload completed successfully!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Upload failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderProgressBar = (fileName: string) => {
|
||||||
|
const fileProgress = progress[fileName];
|
||||||
|
if (!fileProgress) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>{fileName}</p>
|
||||||
|
<progress
|
||||||
|
value={fileProgress.percentage}
|
||||||
|
max="100"
|
||||||
|
/>
|
||||||
|
<span>{fileProgress.percentage.toFixed(2)}%</span>
|
||||||
|
{errors[fileName] && (
|
||||||
|
<p style={{ color: 'red' }}>
|
||||||
|
Error: {errors[fileName].message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '20px' }}>
|
<div>
|
||||||
<h2>文件上传测试</h2>
|
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
|
multiple
|
||||||
onChange={handleFileSelect}
|
onChange={handleFileSelect}
|
||||||
style={{ marginBottom: '20px' }}
|
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleUpload}
|
||||||
|
disabled={selectedFiles.length === 0}
|
||||||
|
>
|
||||||
|
Upload Files
|
||||||
|
</button>
|
||||||
|
|
||||||
{progress > 0 && (
|
<div>
|
||||||
<div>
|
<h3>Upload Progress</h3>
|
||||||
<div>上传进度: {progress}%</div>
|
{selectedFiles.map(file => renderProgressBar(file.name))}
|
||||||
<div style={{
|
</div>
|
||||||
width: '300px',
|
|
||||||
height: '20px',
|
|
||||||
border: '1px solid #ccc',
|
|
||||||
marginTop: '10px'
|
|
||||||
}}>
|
|
||||||
<div style={{
|
|
||||||
width: `${progress}%`,
|
|
||||||
height: '100%',
|
|
||||||
backgroundColor: '#4CAF50',
|
|
||||||
transition: 'width 0.3s'
|
|
||||||
}} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{uploadStatus && (
|
|
||||||
<div style={{ marginTop: '10px' }}>
|
|
||||||
状态: {uploadStatus}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default FileUploadComponent;
|
||||||
export default function HomePage() {
|
|
||||||
return <UploadTest></UploadTest>
|
|
||||||
}
|
|
|
@ -4,8 +4,8 @@ import React, { ReactNode, useEffect, useState, useRef, CSSProperties } from "re
|
||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import Breadcrumb from "../layout/breadcrumb";
|
import Breadcrumb from "../layout/breadcrumb";
|
||||||
import * as Y from "yjs";
|
import * as Y from "yjs";
|
||||||
import { stringToColor, YWsProvider } from "@nicestack/common";
|
import { stringToColor, YWsProvider } from "@nice/common";
|
||||||
import { lightenColor } from "@nicestack/client"
|
import { lightenColor } from "@nice/client"
|
||||||
import { useLocalSettings } from "@web/src/hooks/useLocalSetting";
|
import { useLocalSettings } from "@web/src/hooks/useLocalSetting";
|
||||||
interface FixedHeaderProps {
|
interface FixedHeaderProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { NavItem } from '@nicestack/client';
|
import { NavItem } from '@nice/client';
|
||||||
import {
|
import {
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
BookOpenIcon,
|
BookOpenIcon,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { NavItem } from '@nicestack/client';
|
import { NavItem } from '@nice/client';
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
navItems: Array<NavItem>;
|
navItems: Array<NavItem>;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Avatar, Divider, Dropdown, theme } from "antd";
|
import { Avatar, Divider, Dropdown, theme } from "antd";
|
||||||
import { Icon } from "@nicestack/iconer";
|
import { Icon } from "@nice/iconer";
|
||||||
|
|
||||||
import CollapsibleSection from "../presentation/collapse-section";
|
import CollapsibleSection from "../presentation/collapse-section";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import { RolePerms } from "@nicestack/common";
|
import { RolePerms } from "@nice/common";
|
||||||
|
|
||||||
export default function SidebarContent() {
|
export default function SidebarContent() {
|
||||||
const { logout, user, isAuthenticated, hasSomePermissions } = useAuth();
|
const { logout, user, isAuthenticated, hasSomePermissions } = useAuth();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Avatar, Button, Dropdown, theme } from "antd";
|
import { Avatar, Button, Dropdown, theme } from "antd";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import { Icon } from "@nicestack/iconer";
|
import { Icon } from "@nice/iconer";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export default function UserHeader() {
|
export default function UserHeader() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CourseDto } from '@nicestack/common';
|
import { CourseDto } from '@nice/common';
|
||||||
import { Card } from '@web/src/components/presentation/container/Card';
|
import { Card } from '@web/src/components/presentation/container/Card';
|
||||||
import { CourseHeader } from './CourseHeader';
|
import { CourseHeader } from './CourseHeader';
|
||||||
import { CourseStats } from './CourseStats';
|
import { CourseStats } from './CourseStats';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { CheckCircleIcon } from '@heroicons/react/24/outline'
|
import { CheckCircleIcon } from '@heroicons/react/24/outline'
|
||||||
import { Course } from "@nicestack/common"
|
import { Course } from "@nice/common"
|
||||||
interface CourseDetailProps {
|
interface CourseDetailProps {
|
||||||
course: Course
|
course: Course
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue