From 88b66c50bf52f0dbeb851f58e1c7823e74079b86 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Mon, 27 Jan 2025 22:43:15 +0800 Subject: [PATCH] add --- apps/server/src/auth/auth.controller.ts | 42 +- .../src/models/base/row-model.service.ts | 497 ++++++++++-------- apps/server/src/models/course/utils.ts | 77 +-- 3 files changed, 354 insertions(+), 262 deletions(-) diff --git a/apps/server/src/auth/auth.controller.ts b/apps/server/src/auth/auth.controller.ts index e396a51..de67161 100755 --- a/apps/server/src/auth/auth.controller.ts +++ b/apps/server/src/auth/auth.controller.ts @@ -1,4 +1,19 @@ -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 { AuthSchema, JwtPayload } from '@nice/common'; import { AuthGuard } from './auth.guard'; @@ -7,8 +22,8 @@ import { z } from 'zod'; import { FileValidationErrorType } from './types'; @Controller('auth') export class AuthController { - private logger = new Logger(AuthController.name) - constructor(private readonly authService: AuthService) { } + private logger = new Logger(AuthController.name); + constructor(private readonly authService: AuthService) {} @Get('file') async authFileRequset( @Headers('x-original-uri') originalUri: string, @@ -18,7 +33,6 @@ export class AuthController { @Headers('host') host: string, @Headers('authorization') authorization: string, ) { - try { const fileRequest = { originalUri, @@ -26,10 +40,11 @@ export class AuthController { method, queryParams, host, - authorization + authorization, }; - const authResult = await this.authService.validateFileRequest(fileRequest); + const authResult = + await this.authService.validateFileRequest(fileRequest); if (!authResult.isValid) { // 使用枚举类型进行错误处理 switch (authResult.error) { @@ -41,7 +56,9 @@ export class AuthController { case FileValidationErrorType.INVALID_TOKEN: throw new UnauthorizedException(authResult.error); default: - throw new InternalServerErrorException(authResult.error || FileValidationErrorType.UNKNOWN_ERROR); + throw new InternalServerErrorException( + authResult.error || FileValidationErrorType.UNKNOWN_ERROR, + ); } } return { @@ -51,17 +68,20 @@ export class AuthController { }, }; } catch (error: any) { - this.logger.verbose(`File request auth failed from ${realIp} reason:${error.message}`) + this.logger.verbose( + `File request auth failed from ${realIp} reason:${error.message}`, + ); throw error; } } @UseGuards(AuthGuard) @Get('user-profile') async getUserProfile(@Req() request: Request) { - const payload: JwtPayload = (request as any).user; - const { staff } = await UserProfileService.instance.getUserProfileById(payload.sub); - return staff + const { staff } = await UserProfileService.instance.getUserProfileById( + payload.sub, + ); + return staff; } @Post('login') async login(@Body() body: z.infer) { diff --git a/apps/server/src/models/base/row-model.service.ts b/apps/server/src/models/base/row-model.service.ts index 406beab..a0cbb6f 100644 --- a/apps/server/src/models/base/row-model.service.ts +++ b/apps/server/src/models/base/row-model.service.ts @@ -1,238 +1,307 @@ -import { Logger } from "@nestjs/common"; -import { UserProfile, db, RowModelRequest } from "@nice/common"; +import { Logger } from '@nestjs/common'; +import { UserProfile, db, RowModelRequest } from '@nice/common'; import { LogicalCondition, OperatorType, SQLBuilder } from './sql-builder'; export interface GetRowOptions { - id?: string; - ids?: string[]; - extraCondition?: LogicalCondition; - staff?: UserProfile; + id?: string; + ids?: string[]; + extraCondition?: LogicalCondition; + staff?: UserProfile; } export abstract class RowModelService { - private keywords: Set = new Set([ - 'SELECT', 'FROM', 'WHERE', 'ORDER', 'BY', 'GROUP', 'JOIN', 'AND', 'OR' - // 添加更多需要引号的关键词 - ]); - protected logger = new Logger(this.tableName); - protected constructor(protected tableName: string) { } - protected async getRowDto(row: any, staff?: UserProfile): Promise { - return row; + private keywords: Set = new Set([ + 'SELECT', + 'FROM', + 'WHERE', + 'ORDER', + 'BY', + 'GROUP', + 'JOIN', + 'AND', + 'OR', + // 添加更多需要引号的关键词 + ]); + protected logger = new Logger(this.tableName); + protected constructor(protected tableName: string) {} + protected async getRowDto(row: any, staff?: UserProfile): Promise { + return row; + } + protected async getRowsSqlWrapper( + sql: string, + request?: RowModelRequest, + staff?: UserProfile, + ) { + if (request) return SQLBuilder.join([sql, this.getLimitSql(request)]); + return sql; + } + protected getLimitSql(request: RowModelRequest) { + return SQLBuilder.limit( + request.endRow - request.startRow, + request.startRow, + ); + } + abstract createJoinSql(request?: RowModelRequest): string[]; + async getRows(request: RowModelRequest, staff?: UserProfile) { + try { + let SQL = SQLBuilder.join([ + SQLBuilder.select(this.getRowSelectCols(request)), + SQLBuilder.from(this.tableName), + SQLBuilder.join(this.createJoinSql(request)), + SQLBuilder.where(this.createGetRowsFilters(request, staff)), + SQLBuilder.groupBy(this.getGroupByColumns(request)), + SQLBuilder.orderBy(this.getOrderByColumns(request)), + ]); + SQL = await this.getRowsSqlWrapper(SQL, request, staff); + + this.logger.debug('getrows', SQL); + + const results: any[] = (await db?.$queryRawUnsafe(SQL)) || []; + + const rowDataDto = await Promise.all( + results.map((row) => this.getRowDto(row, staff)), + ); + return { + rowCount: this.getRowCount(request, rowDataDto) || 0, + rowData: rowDataDto, + }; + } catch (error: any) { + this.logger.error('Error executing getRows:', error); } - protected async getRowsSqlWrapper(sql: string, request?: RowModelRequest, staff?: UserProfile) { - if (request) - return SQLBuilder.join([sql, this.getLimitSql(request)]) - return sql + } + getRowCount(request: RowModelRequest, results: any[]) { + if (results === null || results === undefined || results.length === 0) { + return null; } - protected getLimitSql(request: RowModelRequest) { - return SQLBuilder.limit(request.endRow - request.startRow, request.startRow) + const currentLastRow = request.startRow + results.length; + return currentLastRow <= request.endRow ? currentLastRow : -1; + } + + async getRowById(options: GetRowOptions): Promise { + const { + id, + extraCondition = { + field: `${this.tableName}.deleted_at`, + op: 'blank', + type: 'date', + }, + staff, + } = options; + return this.getSingleRow( + { AND: [this.createGetByIdFilter(id!), extraCondition] }, + staff, + ); + } + + async getRowByIds(options: GetRowOptions): Promise { + const { + ids, + extraCondition = { + field: `${this.tableName}.deleted_at`, + op: 'blank', + type: 'date', + }, + staff, + } = options; + return this.getMultipleRows( + { AND: [this.createGetByIdsFilter(ids!), extraCondition] }, + staff, + ); + } + + protected createGetRowsFilters( + request: RowModelRequest, + staff?: UserProfile, + ): LogicalCondition { + let groupConditions: LogicalCondition[] = []; + if (this.isDoingTreeGroup(request)) { + groupConditions = [ + { + field: 'parent_id', + op: 'equals' as OperatorType, + value: request.groupKeys[request.groupKeys.length - 1], + }, + ]; + } else { + groupConditions = request?.groupKeys?.map((key, index) => ({ + field: request.rowGroupCols[index].field, + op: 'equals' as OperatorType, + value: key, + })); } - abstract createJoinSql(request?: RowModelRequest): string[]; - async getRows(request: RowModelRequest, staff?: UserProfile) { - try { - let SQL = SQLBuilder.join([ - SQLBuilder.select(this.getRowSelectCols(request)), - SQLBuilder.from(this.tableName), - SQLBuilder.join(this.createJoinSql(request)), - SQLBuilder.where(this.createGetRowsFilters(request, staff)), - SQLBuilder.groupBy(this.getGroupByColumns(request)), - SQLBuilder.orderBy(this.getOrderByColumns(request)), - ]); - SQL = await this.getRowsSqlWrapper(SQL, request, staff) + const condition: LogicalCondition = { + AND: [ + ...groupConditions, + ...this.buildFilterConditions(request.filterModel), + ], + }; - this.logger.debug('getrows', SQL) + return condition; + } + private buildFilterConditions(filterModel: any): LogicalCondition[] { + return filterModel + ? Object.entries(filterModel)?.map(([key, item]) => + SQLBuilder.createFilterSql( + key === 'ag-Grid-AutoColumn' ? 'name' : key, + item, + ), + ) + : []; + } - const results: any[] = await db?.$queryRawUnsafe(SQL) || []; + getRowSelectCols(request: RowModelRequest): string[] { + return this.isDoingGroup(request) + ? this.createGroupingRowSelect(request) + : this.createUnGroupingRowSelect(request); + } + protected createUnGroupingRowSelect(request?: RowModelRequest): string[] { + return ['*']; + } + protected createAggSqlForWrapper(request: RowModelRequest) { + const { rowGroupCols, valueCols, groupKeys } = request; + return valueCols.map( + (valueCol) => + `${valueCol.aggFunc}(${valueCol.field.replace('.', '_')}) AS ${valueCol.field.split('.').join('_')}`, + ); + } + protected createGroupingRowSelect( + request: RowModelRequest, + wrapperSql: boolean = false, + ): string[] { + const { rowGroupCols, valueCols, groupKeys } = request; + const colsToSelect: string[] = []; - let rowDataDto = await Promise.all(results.map(row => this.getRowDto(row, staff))) - return { rowCount: this.getRowCount(request, rowDataDto) || 0, rowData: rowDataDto }; - } catch (error: any) { - this.logger.error('Error executing getRows:', error); + const rowGroupCol = rowGroupCols[groupKeys!.length]; + if (rowGroupCol) { + colsToSelect.push( + `${rowGroupCol.field} AS ${rowGroupCol.field.replace('.', '_')}`, + ); + } + colsToSelect.push( + ...valueCols.map( + (valueCol) => + `${wrapperSql ? '' : valueCol.aggFunc}(${valueCol.field}) AS ${valueCol.field.replace('.', '_')}`, + ), + ); + return colsToSelect; + } + + getGroupByColumns(request: RowModelRequest): string[] { + return this.isDoingGroup(request) + ? [request.rowGroupCols[request.groupKeys!.length]?.field] + : []; + } + + getOrderByColumns(request: RowModelRequest): string[] { + const { sortModel, rowGroupCols, groupKeys } = request; + const grouping = this.isDoingGroup(request); + const sortParts: string[] = []; + + if (sortModel) { + const groupColIds = rowGroupCols + .map((groupCol) => groupCol.id) + .slice(0, groupKeys.length + 1); + sortModel.forEach((item) => { + if ( + !grouping || + (groupColIds.indexOf(item.colId) >= 0 && + rowGroupCols[groupKeys.length].field === item.colId) + ) { + const colId = this.keywords.has(item.colId.toUpperCase()) + ? `"${item.colId}"` + : item.colId; + sortParts.push(`${colId} ${item.sort}`); } - } - getRowCount(request: RowModelRequest, results: any[]) { - if (results === null || results === undefined || results.length === 0) { - return null; - } - const currentLastRow = request.startRow + results.length; - return currentLastRow <= request.endRow ? currentLastRow : -1; + }); } - async getRowById(options: GetRowOptions): Promise { - const { id, extraCondition = { - field: `${this.tableName}.deleted_at`, - op: "blank", - type: "date" - }, staff } = options; - return this.getSingleRow({ AND: [this.createGetByIdFilter(id!), extraCondition] }, staff); - } + return sortParts; + } + isDoingGroup(requset: RowModelRequest): boolean { + return requset.rowGroupCols.length > requset.groupKeys.length; + } + isDoingTreeGroup(requset: RowModelRequest): boolean { + return requset.rowGroupCols.length === 0 && requset.groupKeys.length > 0; + } + private async getSingleRow( + condition: LogicalCondition, + staff?: UserProfile, + ): Promise { + const results = await this.getRowsWithFilters(condition, staff); + return results[0]; + } + private async getMultipleRows( + condition: LogicalCondition, + staff?: UserProfile, + ): Promise { + return this.getRowsWithFilters(condition, staff); + } - async getRowByIds(options: GetRowOptions): Promise { - const { ids, extraCondition = { - field: `${this.tableName}.deleted_at`, - op: "blank", - type: "date" - }, staff } = options; - return this.getMultipleRows({ AND: [this.createGetByIdsFilter(ids!), extraCondition] }, staff); - } + private async getRowsWithFilters( + condition: LogicalCondition, + staff?: UserProfile, + ): Promise { + try { + const SQL = SQLBuilder.join([ + SQLBuilder.select(this.createUnGroupingRowSelect()), + SQLBuilder.from(this.tableName), + SQLBuilder.join(this.createJoinSql()), + SQLBuilder.where(condition), + ]); - protected createGetRowsFilters(request: RowModelRequest, staff?: UserProfile): LogicalCondition { - let groupConditions: LogicalCondition[] = [] - if (this.isDoingTreeGroup(request)) { - groupConditions = [ - { - field: 'parent_id', - op: "equals" as OperatorType, - value: request.groupKeys[request.groupKeys.length - 1] - } - ] - } else { - groupConditions = request?.groupKeys?.map((key, index) => ({ - field: request.rowGroupCols[index].field, - op: "equals" as OperatorType, - value: key - })) - } + // this.logger.debug(SQL) + const results: any[] = await db.$queryRawUnsafe(SQL); - const condition: LogicalCondition = { - AND: [...groupConditions, ...this.buildFilterConditions(request.filterModel)] - } + const rowDataDto = await Promise.all( + results.map((item) => this.getRowDto(item, staff)), + ); - return condition; - } - private buildFilterConditions(filterModel: any): LogicalCondition[] { - return filterModel - ? Object.entries(filterModel)?.map(([key, item]) => SQLBuilder.createFilterSql(key === 'ag-Grid-AutoColumn' ? 'name' : key, item)) - : []; + // rowDataDto = getUniqueItems(rowDataDto, "id") + return rowDataDto; + } catch (error) { + this.logger.error('Error executing query:', error); + throw error; } + } - getRowSelectCols(request: RowModelRequest): string[] { - return this.isDoingGroup(request) - ? this.createGroupingRowSelect(request) - : this.createUnGroupingRowSelect(request); + async getAggValues(request: RowModelRequest) { + try { + const SQL = SQLBuilder.join([ + SQLBuilder.select(this.buildAggSelect(request.valueCols)), + SQLBuilder.from(this.tableName), + SQLBuilder.join(this.createJoinSql(request)), + SQLBuilder.where(this.createGetRowsFilters(request)), + SQLBuilder.groupBy(this.buildAggGroupBy()), + ]); + const result: any[] = await db.$queryRawUnsafe(SQL); + return result[0]; + } catch (error) { + this.logger.error('Error executing query:', error); + throw error; } - protected createUnGroupingRowSelect(request?: RowModelRequest): string[] { - return ['*']; - } - protected createAggSqlForWrapper(request: RowModelRequest) { - const { rowGroupCols, valueCols, groupKeys } = request; - return valueCols.map(valueCol => - `${valueCol.aggFunc}(${valueCol.field.replace('.', '_')}) AS ${valueCol.field.split('.').join('_')}` - ); - } - protected createGroupingRowSelect(request: RowModelRequest, wrapperSql: boolean = false): string[] { - const { rowGroupCols, valueCols, groupKeys } = request; - const colsToSelect: string[] = []; + } + protected buildAggGroupBy(): string[] { + return []; + } + protected buildAggSelect(valueCols: any[]): string[] { + return valueCols.map( + (valueCol) => + `${valueCol.aggFunc}(${valueCol.field}) AS ${valueCol.field.replace('.', '_')}`, + ); + } - const rowGroupCol = rowGroupCols[groupKeys!.length]; - if (rowGroupCol) { - colsToSelect.push(`${rowGroupCol.field} AS ${rowGroupCol.field.replace('.', '_')}`); - } - colsToSelect.push(...valueCols.map(valueCol => - `${wrapperSql ? "" : valueCol.aggFunc}(${valueCol.field}) AS ${valueCol.field.replace('.', '_')}` - )); - - return colsToSelect; - } - - getGroupByColumns(request: RowModelRequest): string[] { - return this.isDoingGroup(request) - ? [request.rowGroupCols[request.groupKeys!.length]?.field] - : []; - } - - - getOrderByColumns(request: RowModelRequest): string[] { - const { sortModel, rowGroupCols, groupKeys } = request; - const grouping = this.isDoingGroup(request); - const sortParts: string[] = []; - - if (sortModel) { - const groupColIds = rowGroupCols.map(groupCol => groupCol.id).slice(0, groupKeys.length + 1); - sortModel.forEach(item => { - if (!grouping || (groupColIds.indexOf(item.colId) >= 0 && rowGroupCols[groupKeys.length].field === item.colId)) { - const colId = this.keywords.has(item.colId.toUpperCase()) ? `"${item.colId}"` : item.colId; - sortParts.push(`${colId} ${item.sort}`); - } - }); - } - - return sortParts; - } - isDoingGroup(requset: RowModelRequest): boolean { - return requset.rowGroupCols.length > requset.groupKeys.length; - } - isDoingTreeGroup(requset: RowModelRequest): boolean { - return requset.rowGroupCols.length === 0 && requset.groupKeys.length > 0; - } - private async getSingleRow(condition: LogicalCondition, staff?: UserProfile): Promise { - const results = await this.getRowsWithFilters(condition, staff) - return results[0] - } - private async getMultipleRows(condition: LogicalCondition, staff?: UserProfile): Promise { - return this.getRowsWithFilters(condition, staff); - } - - private async getRowsWithFilters(condition: LogicalCondition, staff?: UserProfile): Promise { - try { - let SQL = SQLBuilder.join([ - SQLBuilder.select(this.createUnGroupingRowSelect()), - SQLBuilder.from(this.tableName), - SQLBuilder.join(this.createJoinSql()), - SQLBuilder.where(condition) - ]); - - // this.logger.debug(SQL) - const results: any[] = await db.$queryRawUnsafe(SQL); - - let rowDataDto = await Promise.all(results.map(item => this.getRowDto(item, staff))); - - // rowDataDto = getUniqueItems(rowDataDto, "id") - return rowDataDto - } catch (error) { - this.logger.error('Error executing query:', error); - throw error; - } - } - - async getAggValues(request: RowModelRequest) { - try { - const SQL = SQLBuilder.join([ - SQLBuilder.select(this.buildAggSelect(request.valueCols)), - SQLBuilder.from(this.tableName), - SQLBuilder.join(this.createJoinSql(request)), - SQLBuilder.where(this.createGetRowsFilters(request)), - SQLBuilder.groupBy(this.buildAggGroupBy()) - ]); - const result: any[] = await db.$queryRawUnsafe(SQL); - return result[0]; - } catch (error) { - this.logger.error('Error executing query:', error); - throw error; - } - } - protected buildAggGroupBy(): string[] { - return []; - } - protected buildAggSelect(valueCols: any[]): string[] { - return valueCols.map(valueCol => - `${valueCol.aggFunc}(${valueCol.field}) AS ${valueCol.field.replace('.', '_')}` - ); - } - - private createGetByIdFilter(id: string): LogicalCondition { - return { - field: `${this.tableName}.id`, - value: id, - op: "equals" - } - } - private createGetByIdsFilter(ids: string[]): LogicalCondition { - return { - field: `${this.tableName}.id`, - value: ids, - op: "in" - }; - } + private createGetByIdFilter(id: string): LogicalCondition { + return { + field: `${this.tableName}.id`, + value: id, + op: 'equals', + }; + } + private createGetByIdsFilter(ids: string[]): LogicalCondition { + return { + field: `${this.tableName}.id`, + value: ids, + op: 'in', + }; + } } - diff --git a/apps/server/src/models/course/utils.ts b/apps/server/src/models/course/utils.ts index 9d2092c..9cd98c7 100644 --- a/apps/server/src/models/course/utils.ts +++ b/apps/server/src/models/course/utils.ts @@ -1,46 +1,49 @@ -import { db, EnrollmentStatus, PostType } from "@nice/common"; +import { db, EnrollmentStatus, PostType } from '@nice/common'; // 更新课程评价统计 export async function updateCourseReviewStats(courseId: string) { - const reviews = await db.post.findMany({ - where: { - courseId, - type: PostType.COURSE_REVIEW, - deletedAt: null - }, - select: { rating: true } - }); - const numberOfReviews = reviews.length; - const averageRating = numberOfReviews > 0 - ? reviews.reduce((sum, review) => sum + review.rating, 0) / numberOfReviews - : 0; + const reviews = await db.post.findMany({ + where: { + courseId, + type: PostType.COURSE_REVIEW, + deletedAt: null, + }, + select: { rating: true }, + }); + const numberOfReviews = reviews.length; + const averageRating = + numberOfReviews > 0 + ? reviews.reduce((sum, review) => sum + review.rating, 0) / + numberOfReviews + : 0; - return db.course.update({ - where: { id: courseId }, - data: { numberOfReviews, averageRating } - }); + return db.course.update({ + where: { id: courseId }, + data: { + // numberOfReviews, + //averageRating, + }, + }); } // 更新课程注册统计 export async function updateCourseEnrollmentStats(courseId: string) { - const completedEnrollments = await db.enrollment.count({ - where: { - courseId, - status: EnrollmentStatus.COMPLETED - } - }); - const totalEnrollments = await db.enrollment.count({ - where: { courseId } - }); - const completionRate = totalEnrollments > 0 - ? (completedEnrollments / totalEnrollments) * 100 - : 0; - return db.course.update({ - where: { id: courseId }, - data: { - numberOfStudents: totalEnrollments, - completionRate - } - }); + const completedEnrollments = await db.enrollment.count({ + where: { + courseId, + status: EnrollmentStatus.COMPLETED, + }, + }); + const totalEnrollments = await db.enrollment.count({ + where: { courseId }, + }); + const completionRate = + totalEnrollments > 0 ? (completedEnrollments / totalEnrollments) * 100 : 0; + return db.course.update({ + where: { id: courseId }, + data: { + // numberOfStudents: totalEnrollments, + // completionRate, + }, + }); } -