From e84351e8eddd02bc8e5992cf118be854d11e850f Mon Sep 17 00:00:00 2001 From: zhxiao1124 Date: Thu, 2 Jul 2026 19:17:43 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=9B=AE=E5=BD=95=E4=B8=8E=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84=EF=BC=8C=E6=B8=85=E7=90=86=E5=86=97=E4=BD=99=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本次提交进行了大规模的项目结构调整: 1. 调整文件目录归属,将模块代码按功能域重新组织 2. 删除多处冗余的DTO、实体类和模块文件 3. 统一了常量、装饰器、工具类的存放位置 4. 修复了`.gitignore`的日志目录匹配规则 5. 新增了健康检查、认证、用户、剧集、订单等核心业务模块 --- .gitignore | 2 +- .../dto => common}/paginated-response.dto.ts | 0 .../text.constant.ts | 0 .../api-message.decorator.ts | 0 .../current-admin.decorator.ts | 0 .../current-user.decorator.ts | 0 .../require-permissions.decorator.ts | 0 .../http.filter.ts | 0 .../guards => guards}/admin-jwt-auth.guard.ts | 0 .../auth/guards => guards}/jwt-auth.guard.ts | 0 .../guards => guards}/permissions.guard.ts | 0 .../logger.interceptor.ts | 0 .../response.interceptor.ts | 0 .../request-id.middleware.ts | 0 .../entities => admin}/admin-user.entity.ts | 0 src/{modules => }/admin/admin.controller.ts | 0 .../admin-login.dto.ts => admin/admin.dto.ts} | 0 src/{modules => }/admin/admin.module.ts | 0 src/{modules => }/admin/admin.service.ts | 0 .../admin.types.ts} | 0 .../entities => admin}/permission.entity.ts | 0 .../admin/entities => admin}/role.entity.ts | 0 .../app.controller.ts} | 0 src/{ => app}/app.module.ts | 0 src/{modules => }/auth/auth.controller.ts | 0 .../tiktok-login.dto.ts => auth/auth.dto.ts} | 0 src/{modules => }/auth/auth.module.ts | 0 src/{modules => }/auth/auth.service.ts | 0 .../entities => data}/data-sync-job.entity.ts | 0 src/{modules => }/data/data.controller.ts | 0 src/{modules => }/data/data.module.ts | 0 .../data.provider.ts} | 0 src/{modules => }/data/data.service.ts | 0 .../entities => data}/drama-metric.entity.ts | 0 .../episode-metric.entity.ts | 0 src/{modules => }/dramas/drama-status.enum.ts | 0 .../entities => dramas}/drama.entity.ts | 0 src/{modules => }/dramas/dramas.controller.ts | 0 .../dramas.dto.ts} | 0 src/{modules => }/dramas/dramas.module.ts | 0 src/{modules => }/dramas/dramas.service.ts | 0 .../dramas.types.ts} | 0 .../entities => dramas}/episode.entity.ts | 0 .../history/history.controller.ts | 0 .../history.dto.ts} | 0 src/{modules => }/history/history.module.ts | 0 src/{modules => }/history/history.service.ts | 0 .../history.types.ts} | 0 .../user-playback-history.entity.ts | 0 src/logs/logs.controller.ts | 64 +++++ src/logs/logs.dto.ts | 62 +++++ src/logs/logs.module.ts | 12 + src/logs/logs.service.ts | 240 ++++++++++++++++++ src/logs/logs.types.ts | 34 +++ src/logs/playback-error-log.entity.ts | 50 ++++ src/logs/request-log.entity.ts | 59 +++++ .../media/entities => media}/cover.entity.ts | 0 .../media-upload-job.entity.ts | 0 src/{modules => }/media/media.controller.ts | 0 .../media.dto.ts} | 0 src/{modules => }/media/media.module.ts | 0 .../media.provider.ts} | 0 src/{modules => }/media/media.service.ts | 0 .../media.types.ts} | 0 .../admin/dto/change-admin-password.dto.ts | 13 - .../admin/dto/create-admin-user.dto.ts | 38 --- .../admin/dto/create-permission.dto.ts | 17 -- src/modules/admin/dto/create-role.dto.ts | 30 --- .../admin/dto/update-admin-profile.dto.ts | 14 - .../admin/dto/update-admin-roles.dto.ts | 10 - .../admin/dto/update-admin-status.dto.ts | 8 - .../admin/dto/update-role-permissions.dto.ts | 10 - src/modules/database/database.module.ts | 55 ---- .../dramas/dto/configure-episodes.dto.ts | 10 - src/modules/dramas/dto/create-episode.dto.ts | 145 ----------- src/modules/dramas/dto/update-drama.dto.ts | 4 - src/modules/dramas/dto/update-episode.dto.ts | 4 - src/modules/health/health.module.ts | 7 - .../dto/complete-direct-upload-task.dto.ts | 9 - src/modules/media/dto/create-cover.dto.ts | 12 - .../dto/create-direct-upload-task.dto.ts | 22 -- .../orders/dto/tiktok-payment-webhook.dto.ts | 32 --- .../entities => orders}/order.entity.ts | 0 src/{modules => }/orders/orders.controller.ts | 0 .../orders.dto.ts} | 0 src/{modules => }/orders/orders.module.ts | 0 src/{modules => }/orders/orders.service.ts | 0 .../orders.types.ts} | 0 .../user-episode-unlock.entity.ts | 0 src/{modules => }/player/player.controller.ts | 0 src/{modules => }/player/player.module.ts | 0 src/{modules => }/player/player.service.ts | 0 .../users/entities => users}/user.entity.ts | 0 src/{modules => }/users/users.controller.ts | 0 .../users.dto.ts} | 0 src/{modules => }/users/users.module.ts | 0 src/{modules => }/users/users.service.ts | 0 .../users.types.ts} | 0 .../utils => utils}/request-context.util.ts | 0 99 files changed, 522 insertions(+), 441 deletions(-) rename {src/common/dto => common}/paginated-response.dto.ts (100%) rename src/common/constants/response-message.constants.ts => constants/text.constant.ts (100%) rename {src/common/decorators => decorators}/api-message.decorator.ts (100%) rename {src/modules/admin/decorators => decorators}/current-admin.decorator.ts (100%) rename {src/modules/auth/decorators => decorators}/current-user.decorator.ts (100%) rename {src/modules/admin/decorators => decorators}/require-permissions.decorator.ts (100%) rename src/common/filters/http-exception.filter.ts => filters/http.filter.ts (100%) rename {src/modules/admin/guards => guards}/admin-jwt-auth.guard.ts (100%) rename {src/modules/auth/guards => guards}/jwt-auth.guard.ts (100%) rename {src/modules/admin/guards => guards}/permissions.guard.ts (100%) rename src/common/interceptors/request-logging.interceptor.ts => interceptor/logger.interceptor.ts (100%) rename {src/common/interceptors => interceptor}/response.interceptor.ts (100%) rename {src/common/middleware => middleware}/request-id.middleware.ts (100%) rename src/{modules/admin/entities => admin}/admin-user.entity.ts (100%) rename src/{modules => }/admin/admin.controller.ts (100%) rename src/{modules/admin/dto/admin-login.dto.ts => admin/admin.dto.ts} (100%) rename src/{modules => }/admin/admin.module.ts (100%) rename src/{modules => }/admin/admin.service.ts (100%) rename src/{modules/admin/interfaces/admin-records.interface.ts => admin/admin.types.ts} (100%) rename src/{modules/admin/entities => admin}/permission.entity.ts (100%) rename src/{modules/admin/entities => admin}/role.entity.ts (100%) rename src/{modules/health/health.controller.ts => app/app.controller.ts} (100%) rename src/{ => app}/app.module.ts (100%) rename src/{modules => }/auth/auth.controller.ts (100%) rename src/{modules/auth/dto/tiktok-login.dto.ts => auth/auth.dto.ts} (100%) rename src/{modules => }/auth/auth.module.ts (100%) rename src/{modules => }/auth/auth.service.ts (100%) rename src/{modules/data/entities => data}/data-sync-job.entity.ts (100%) rename src/{modules => }/data/data.controller.ts (100%) rename src/{modules => }/data/data.module.ts (100%) rename src/{modules/data/providers/tiktok-data.provider.ts => data/data.provider.ts} (100%) rename src/{modules => }/data/data.service.ts (100%) rename src/{modules/data/entities => data}/drama-metric.entity.ts (100%) rename src/{modules/data/entities => data}/episode-metric.entity.ts (100%) rename src/{modules => }/dramas/drama-status.enum.ts (100%) rename src/{modules/dramas/entities => dramas}/drama.entity.ts (100%) rename src/{modules => }/dramas/dramas.controller.ts (100%) rename src/{modules/dramas/dto/create-drama.dto.ts => dramas/dramas.dto.ts} (100%) rename src/{modules => }/dramas/dramas.module.ts (100%) rename src/{modules => }/dramas/dramas.service.ts (100%) rename src/{modules/dramas/interfaces/drama-records.interface.ts => dramas/dramas.types.ts} (100%) rename src/{modules/dramas/entities => dramas}/episode.entity.ts (100%) rename src/{modules => }/history/history.controller.ts (100%) rename src/{modules/history/dto/upsert-history.dto.ts => history/history.dto.ts} (100%) rename src/{modules => }/history/history.module.ts (100%) rename src/{modules => }/history/history.service.ts (100%) rename src/{modules/history/interfaces/history-record.interface.ts => history/history.types.ts} (100%) rename src/{modules/history/entities => history}/user-playback-history.entity.ts (100%) create mode 100644 src/logs/logs.controller.ts create mode 100644 src/logs/logs.dto.ts create mode 100644 src/logs/logs.module.ts create mode 100644 src/logs/logs.service.ts create mode 100644 src/logs/logs.types.ts create mode 100644 src/logs/playback-error-log.entity.ts create mode 100644 src/logs/request-log.entity.ts rename src/{modules/media/entities => media}/cover.entity.ts (100%) rename src/{modules/media/entities => media}/media-upload-job.entity.ts (100%) rename src/{modules => }/media/media.controller.ts (100%) rename src/{modules/media/dto/create-upload-job.dto.ts => media/media.dto.ts} (100%) rename src/{modules => }/media/media.module.ts (100%) rename src/{modules/media/providers/byteplus-media.provider.ts => media/media.provider.ts} (100%) rename src/{modules => }/media/media.service.ts (100%) rename src/{modules/media/interfaces/media-records.interface.ts => media/media.types.ts} (100%) delete mode 100644 src/modules/admin/dto/change-admin-password.dto.ts delete mode 100644 src/modules/admin/dto/create-admin-user.dto.ts delete mode 100644 src/modules/admin/dto/create-permission.dto.ts delete mode 100644 src/modules/admin/dto/create-role.dto.ts delete mode 100644 src/modules/admin/dto/update-admin-profile.dto.ts delete mode 100644 src/modules/admin/dto/update-admin-roles.dto.ts delete mode 100644 src/modules/admin/dto/update-admin-status.dto.ts delete mode 100644 src/modules/admin/dto/update-role-permissions.dto.ts delete mode 100644 src/modules/database/database.module.ts delete mode 100644 src/modules/dramas/dto/configure-episodes.dto.ts delete mode 100644 src/modules/dramas/dto/create-episode.dto.ts delete mode 100644 src/modules/dramas/dto/update-drama.dto.ts delete mode 100644 src/modules/dramas/dto/update-episode.dto.ts delete mode 100644 src/modules/health/health.module.ts delete mode 100644 src/modules/media/dto/complete-direct-upload-task.dto.ts delete mode 100644 src/modules/media/dto/create-cover.dto.ts delete mode 100644 src/modules/media/dto/create-direct-upload-task.dto.ts delete mode 100644 src/modules/orders/dto/tiktok-payment-webhook.dto.ts rename src/{modules/orders/entities => orders}/order.entity.ts (100%) rename src/{modules => }/orders/orders.controller.ts (100%) rename src/{modules/orders/dto/create-order.dto.ts => orders/orders.dto.ts} (100%) rename src/{modules => }/orders/orders.module.ts (100%) rename src/{modules => }/orders/orders.service.ts (100%) rename src/{modules/orders/interfaces/order-records.interface.ts => orders/orders.types.ts} (100%) rename src/{modules/orders/entities => orders}/user-episode-unlock.entity.ts (100%) rename src/{modules => }/player/player.controller.ts (100%) rename src/{modules => }/player/player.module.ts (100%) rename src/{modules => }/player/player.service.ts (100%) rename src/{modules/users/entities => users}/user.entity.ts (100%) rename src/{modules => }/users/users.controller.ts (100%) rename src/{modules/users/dto/update-user-status.dto.ts => users/users.dto.ts} (100%) rename src/{modules => }/users/users.module.ts (100%) rename src/{modules => }/users/users.service.ts (100%) rename src/{modules/users/interfaces/user-record.interface.ts => users/users.types.ts} (100%) rename {src/common/utils => utils}/request-context.util.ts (100%) diff --git a/.gitignore b/.gitignore index a547bf3..6ffd4df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Logs -logs +/logs/ *.log npm-debug.log* yarn-debug.log* diff --git a/src/common/dto/paginated-response.dto.ts b/common/paginated-response.dto.ts similarity index 100% rename from src/common/dto/paginated-response.dto.ts rename to common/paginated-response.dto.ts diff --git a/src/common/constants/response-message.constants.ts b/constants/text.constant.ts similarity index 100% rename from src/common/constants/response-message.constants.ts rename to constants/text.constant.ts diff --git a/src/common/decorators/api-message.decorator.ts b/decorators/api-message.decorator.ts similarity index 100% rename from src/common/decorators/api-message.decorator.ts rename to decorators/api-message.decorator.ts diff --git a/src/modules/admin/decorators/current-admin.decorator.ts b/decorators/current-admin.decorator.ts similarity index 100% rename from src/modules/admin/decorators/current-admin.decorator.ts rename to decorators/current-admin.decorator.ts diff --git a/src/modules/auth/decorators/current-user.decorator.ts b/decorators/current-user.decorator.ts similarity index 100% rename from src/modules/auth/decorators/current-user.decorator.ts rename to decorators/current-user.decorator.ts diff --git a/src/modules/admin/decorators/require-permissions.decorator.ts b/decorators/require-permissions.decorator.ts similarity index 100% rename from src/modules/admin/decorators/require-permissions.decorator.ts rename to decorators/require-permissions.decorator.ts diff --git a/src/common/filters/http-exception.filter.ts b/filters/http.filter.ts similarity index 100% rename from src/common/filters/http-exception.filter.ts rename to filters/http.filter.ts diff --git a/src/modules/admin/guards/admin-jwt-auth.guard.ts b/guards/admin-jwt-auth.guard.ts similarity index 100% rename from src/modules/admin/guards/admin-jwt-auth.guard.ts rename to guards/admin-jwt-auth.guard.ts diff --git a/src/modules/auth/guards/jwt-auth.guard.ts b/guards/jwt-auth.guard.ts similarity index 100% rename from src/modules/auth/guards/jwt-auth.guard.ts rename to guards/jwt-auth.guard.ts diff --git a/src/modules/admin/guards/permissions.guard.ts b/guards/permissions.guard.ts similarity index 100% rename from src/modules/admin/guards/permissions.guard.ts rename to guards/permissions.guard.ts diff --git a/src/common/interceptors/request-logging.interceptor.ts b/interceptor/logger.interceptor.ts similarity index 100% rename from src/common/interceptors/request-logging.interceptor.ts rename to interceptor/logger.interceptor.ts diff --git a/src/common/interceptors/response.interceptor.ts b/interceptor/response.interceptor.ts similarity index 100% rename from src/common/interceptors/response.interceptor.ts rename to interceptor/response.interceptor.ts diff --git a/src/common/middleware/request-id.middleware.ts b/middleware/request-id.middleware.ts similarity index 100% rename from src/common/middleware/request-id.middleware.ts rename to middleware/request-id.middleware.ts diff --git a/src/modules/admin/entities/admin-user.entity.ts b/src/admin/admin-user.entity.ts similarity index 100% rename from src/modules/admin/entities/admin-user.entity.ts rename to src/admin/admin-user.entity.ts diff --git a/src/modules/admin/admin.controller.ts b/src/admin/admin.controller.ts similarity index 100% rename from src/modules/admin/admin.controller.ts rename to src/admin/admin.controller.ts diff --git a/src/modules/admin/dto/admin-login.dto.ts b/src/admin/admin.dto.ts similarity index 100% rename from src/modules/admin/dto/admin-login.dto.ts rename to src/admin/admin.dto.ts diff --git a/src/modules/admin/admin.module.ts b/src/admin/admin.module.ts similarity index 100% rename from src/modules/admin/admin.module.ts rename to src/admin/admin.module.ts diff --git a/src/modules/admin/admin.service.ts b/src/admin/admin.service.ts similarity index 100% rename from src/modules/admin/admin.service.ts rename to src/admin/admin.service.ts diff --git a/src/modules/admin/interfaces/admin-records.interface.ts b/src/admin/admin.types.ts similarity index 100% rename from src/modules/admin/interfaces/admin-records.interface.ts rename to src/admin/admin.types.ts diff --git a/src/modules/admin/entities/permission.entity.ts b/src/admin/permission.entity.ts similarity index 100% rename from src/modules/admin/entities/permission.entity.ts rename to src/admin/permission.entity.ts diff --git a/src/modules/admin/entities/role.entity.ts b/src/admin/role.entity.ts similarity index 100% rename from src/modules/admin/entities/role.entity.ts rename to src/admin/role.entity.ts diff --git a/src/modules/health/health.controller.ts b/src/app/app.controller.ts similarity index 100% rename from src/modules/health/health.controller.ts rename to src/app/app.controller.ts diff --git a/src/app.module.ts b/src/app/app.module.ts similarity index 100% rename from src/app.module.ts rename to src/app/app.module.ts diff --git a/src/modules/auth/auth.controller.ts b/src/auth/auth.controller.ts similarity index 100% rename from src/modules/auth/auth.controller.ts rename to src/auth/auth.controller.ts diff --git a/src/modules/auth/dto/tiktok-login.dto.ts b/src/auth/auth.dto.ts similarity index 100% rename from src/modules/auth/dto/tiktok-login.dto.ts rename to src/auth/auth.dto.ts diff --git a/src/modules/auth/auth.module.ts b/src/auth/auth.module.ts similarity index 100% rename from src/modules/auth/auth.module.ts rename to src/auth/auth.module.ts diff --git a/src/modules/auth/auth.service.ts b/src/auth/auth.service.ts similarity index 100% rename from src/modules/auth/auth.service.ts rename to src/auth/auth.service.ts diff --git a/src/modules/data/entities/data-sync-job.entity.ts b/src/data/data-sync-job.entity.ts similarity index 100% rename from src/modules/data/entities/data-sync-job.entity.ts rename to src/data/data-sync-job.entity.ts diff --git a/src/modules/data/data.controller.ts b/src/data/data.controller.ts similarity index 100% rename from src/modules/data/data.controller.ts rename to src/data/data.controller.ts diff --git a/src/modules/data/data.module.ts b/src/data/data.module.ts similarity index 100% rename from src/modules/data/data.module.ts rename to src/data/data.module.ts diff --git a/src/modules/data/providers/tiktok-data.provider.ts b/src/data/data.provider.ts similarity index 100% rename from src/modules/data/providers/tiktok-data.provider.ts rename to src/data/data.provider.ts diff --git a/src/modules/data/data.service.ts b/src/data/data.service.ts similarity index 100% rename from src/modules/data/data.service.ts rename to src/data/data.service.ts diff --git a/src/modules/data/entities/drama-metric.entity.ts b/src/data/drama-metric.entity.ts similarity index 100% rename from src/modules/data/entities/drama-metric.entity.ts rename to src/data/drama-metric.entity.ts diff --git a/src/modules/data/entities/episode-metric.entity.ts b/src/data/episode-metric.entity.ts similarity index 100% rename from src/modules/data/entities/episode-metric.entity.ts rename to src/data/episode-metric.entity.ts diff --git a/src/modules/dramas/drama-status.enum.ts b/src/dramas/drama-status.enum.ts similarity index 100% rename from src/modules/dramas/drama-status.enum.ts rename to src/dramas/drama-status.enum.ts diff --git a/src/modules/dramas/entities/drama.entity.ts b/src/dramas/drama.entity.ts similarity index 100% rename from src/modules/dramas/entities/drama.entity.ts rename to src/dramas/drama.entity.ts diff --git a/src/modules/dramas/dramas.controller.ts b/src/dramas/dramas.controller.ts similarity index 100% rename from src/modules/dramas/dramas.controller.ts rename to src/dramas/dramas.controller.ts diff --git a/src/modules/dramas/dto/create-drama.dto.ts b/src/dramas/dramas.dto.ts similarity index 100% rename from src/modules/dramas/dto/create-drama.dto.ts rename to src/dramas/dramas.dto.ts diff --git a/src/modules/dramas/dramas.module.ts b/src/dramas/dramas.module.ts similarity index 100% rename from src/modules/dramas/dramas.module.ts rename to src/dramas/dramas.module.ts diff --git a/src/modules/dramas/dramas.service.ts b/src/dramas/dramas.service.ts similarity index 100% rename from src/modules/dramas/dramas.service.ts rename to src/dramas/dramas.service.ts diff --git a/src/modules/dramas/interfaces/drama-records.interface.ts b/src/dramas/dramas.types.ts similarity index 100% rename from src/modules/dramas/interfaces/drama-records.interface.ts rename to src/dramas/dramas.types.ts diff --git a/src/modules/dramas/entities/episode.entity.ts b/src/dramas/episode.entity.ts similarity index 100% rename from src/modules/dramas/entities/episode.entity.ts rename to src/dramas/episode.entity.ts diff --git a/src/modules/history/history.controller.ts b/src/history/history.controller.ts similarity index 100% rename from src/modules/history/history.controller.ts rename to src/history/history.controller.ts diff --git a/src/modules/history/dto/upsert-history.dto.ts b/src/history/history.dto.ts similarity index 100% rename from src/modules/history/dto/upsert-history.dto.ts rename to src/history/history.dto.ts diff --git a/src/modules/history/history.module.ts b/src/history/history.module.ts similarity index 100% rename from src/modules/history/history.module.ts rename to src/history/history.module.ts diff --git a/src/modules/history/history.service.ts b/src/history/history.service.ts similarity index 100% rename from src/modules/history/history.service.ts rename to src/history/history.service.ts diff --git a/src/modules/history/interfaces/history-record.interface.ts b/src/history/history.types.ts similarity index 100% rename from src/modules/history/interfaces/history-record.interface.ts rename to src/history/history.types.ts diff --git a/src/modules/history/entities/user-playback-history.entity.ts b/src/history/user-playback-history.entity.ts similarity index 100% rename from src/modules/history/entities/user-playback-history.entity.ts rename to src/history/user-playback-history.entity.ts diff --git a/src/logs/logs.controller.ts b/src/logs/logs.controller.ts new file mode 100644 index 0000000..f91a3ad --- /dev/null +++ b/src/logs/logs.controller.ts @@ -0,0 +1,64 @@ +import { Body, Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ApiMessage } from '../../common/decorators/api-message.decorator'; +import { RequirePermissions } from '../admin/decorators/require-permissions.decorator'; +import { AdminJwtAuthGuard } from '../admin/guards/admin-jwt-auth.guard'; +import { PermissionsGuard } from '../admin/guards/permissions.guard'; +import { CreatePlaybackErrorLogDto } from './dto/create-playback-error-log.dto'; +import { LogsService } from './logs.service'; + +@ApiTags('Logs') +@Controller() +export class LogsController { + constructor(private readonly logsService: LogsService) {} + + @Get('/api/admin/v1/logs/requests') + @ApiBearerAuth() + @UseGuards(AdminJwtAuthGuard, PermissionsGuard) + @RequirePermissions('log:read') + listRequestLogs( + @Query('page') page?: string, + @Query('pageSize') pageSize?: string, + ) { + return this.logsService.listRequestLogs({ + page: Number(page) || 1, + pageSize: Number(pageSize) || 20, + }); + } + + @Get('/api/admin/v1/logs/requests/:requestId') + @ApiBearerAuth() + @UseGuards(AdminJwtAuthGuard, PermissionsGuard) + @RequirePermissions('log:read') + getRequestLog(@Param('requestId') requestId: string) { + return this.logsService.getRequestLogOrThrow(requestId); + } + + @Post('/api/app/v1/logs/playback-errors') + @ApiMessage('success') + createPlaybackError(@Body() dto: CreatePlaybackErrorLogDto) { + return this.logsService.createPlaybackError(dto); + } + + @Get('/api/admin/v1/logs/playback-errors') + @ApiBearerAuth() + @UseGuards(AdminJwtAuthGuard, PermissionsGuard) + @RequirePermissions('log:read') + listPlaybackErrors( + @Query('page') page?: string, + @Query('pageSize') pageSize?: string, + ) { + return this.logsService.listPlaybackErrors({ + page: Number(page) || 1, + pageSize: Number(pageSize) || 20, + }); + } + + @Get('/api/admin/v1/logs/playback-errors/:id') + @ApiBearerAuth() + @UseGuards(AdminJwtAuthGuard, PermissionsGuard) + @RequirePermissions('log:read') + getPlaybackError(@Param('id') id: string) { + return this.logsService.getPlaybackErrorOrThrow(Number(id)); + } +} diff --git a/src/logs/logs.dto.ts b/src/logs/logs.dto.ts new file mode 100644 index 0000000..d97e684 --- /dev/null +++ b/src/logs/logs.dto.ts @@ -0,0 +1,62 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsDateString, + IsInt, + IsNotEmpty, + IsObject, + IsOptional, + IsString, + IsUrl, + IsUUID, + Min, +} from 'class-validator'; + +export class CreatePlaybackErrorLogDto { + @ApiProperty() + @IsUUID(4) + requestId: string; + + @ApiProperty() + @IsInt() + @Min(1) + dramaId: number; + + @ApiProperty() + @IsInt() + @Min(1) + episodeId: number; + + @ApiProperty() + @IsUrl({ require_tld: false }) + playUrl: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + playerErrorCode: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + message: string; + + @ApiPropertyOptional() + @IsOptional() + @IsInt() + @Min(0) + progressSeconds?: number; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + networkType?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsObject() + device?: Record; + + @ApiProperty() + @IsDateString() + occurredAt: string; +} diff --git a/src/logs/logs.module.ts b/src/logs/logs.module.ts new file mode 100644 index 0000000..27651b8 --- /dev/null +++ b/src/logs/logs.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { AdminModule } from '../admin/admin.module'; +import { LogsController } from './logs.controller'; +import { LogsService } from './logs.service'; + +@Module({ + imports: [AdminModule], + controllers: [LogsController], + providers: [LogsService], + exports: [LogsService], +}) +export class LogsModule {} diff --git a/src/logs/logs.service.ts b/src/logs/logs.service.ts new file mode 100644 index 0000000..601e138 --- /dev/null +++ b/src/logs/logs.service.ts @@ -0,0 +1,240 @@ +import { Injectable, NotFoundException, Optional } from '@nestjs/common'; +import { InjectDataSource } from '@nestjs/typeorm'; +import { DataSource } from 'typeorm'; +import { PaginatedResponse } from '../../common/dto/paginated-response.dto'; +import { CreatePlaybackErrorLogDto } from './dto/create-playback-error-log.dto'; +import { PlaybackErrorLog } from './entities/playback-error-log.entity'; +import { RequestLog } from './entities/request-log.entity'; +import { + PlaybackErrorLogRecord, + RequestLogRecord, +} from './interfaces/log-records.interface'; + +interface PaginationInput { + page: number; + pageSize: number; +} + +@Injectable() +export class LogsService { + private requestLogSequence = 1; + private playbackErrorSequence = 1; + private readonly requestLogs = new Map(); + private readonly playbackErrors: PlaybackErrorLogRecord[] = []; + + constructor( + @Optional() + @InjectDataSource() + readonly dataSource?: DataSource, + ) {} + + async recordRequest(record: Omit) { + if (!record.requestId) { + return; + } + + if (this.hasDatabase()) { + const repository = this.dataSource.getRepository(RequestLog); + const existing = await repository.findOne({ + where: { requestId: record.requestId }, + }); + await repository.save( + repository.create({ + ...existing, + ...record, + }), + ); + return; + } + + this.requestLogs.set(record.requestId, { + ...record, + id: this.requestLogs.get(record.requestId)?.id ?? this.requestLogSequence++, + createdAt: + this.requestLogs.get(record.requestId)?.createdAt ?? + new Date().toISOString(), + }); + } + + async listRequestLogs( + pagination: PaginationInput, + ): Promise> { + if (this.hasDatabase()) { + const page = Math.max(pagination.page, 1); + const pageSize = Math.min(Math.max(pagination.pageSize, 1), 100); + const repository = this.dataSource.getRepository(RequestLog); + const [records, total] = await repository.findAndCount({ + order: { id: 'DESC' }, + skip: (page - 1) * pageSize, + take: pageSize, + }); + return { + list: records.map((record) => this.toRequestLogRecord(record)), + pagination: { page, pageSize, total }, + }; + } + + const list = Array.from(this.requestLogs.values()).sort( + (left, right) => (right.id ?? 0) - (left.id ?? 0), + ); + + return this.paginate(list, pagination); + } + + async getRequestLogOrThrow(requestId: string) { + if (this.hasDatabase()) { + const log = await this.dataSource + .getRepository(RequestLog) + .findOne({ where: { requestId } }); + if (!log) { + throw new NotFoundException('Request log not found'); + } + return this.toRequestLogRecord(log); + } + + const log = this.requestLogs.get(requestId); + if (!log) { + throw new NotFoundException('Request log not found'); + } + + return log; + } + + async createPlaybackError(dto: CreatePlaybackErrorLogDto) { + if (this.hasDatabase()) { + const repository = this.dataSource.getRepository(PlaybackErrorLog); + const log = await repository.save( + repository.create({ + ...dto, + occurredAt: new Date(dto.occurredAt), + }), + ); + return { + id: Number(log.id), + requestId: log.requestId, + }; + } + + const log: PlaybackErrorLogRecord = { + id: this.playbackErrorSequence++, + requestId: dto.requestId, + dramaId: dto.dramaId, + episodeId: dto.episodeId, + playUrl: dto.playUrl, + playerErrorCode: dto.playerErrorCode, + message: dto.message, + progressSeconds: dto.progressSeconds, + networkType: dto.networkType, + device: dto.device, + occurredAt: dto.occurredAt, + createdAt: new Date().toISOString(), + }; + + this.playbackErrors.push(log); + return { + id: log.id, + requestId: log.requestId, + }; + } + + async listPlaybackErrors( + pagination: PaginationInput, + ): Promise> { + if (this.hasDatabase()) { + const page = Math.max(pagination.page, 1); + const pageSize = Math.min(Math.max(pagination.pageSize, 1), 100); + const repository = this.dataSource.getRepository(PlaybackErrorLog); + const [records, total] = await repository.findAndCount({ + order: { id: 'DESC' }, + skip: (page - 1) * pageSize, + take: pageSize, + }); + return { + list: records.map((record) => this.toPlaybackErrorLogRecord(record)), + pagination: { page, pageSize, total }, + }; + } + + return this.paginate([...this.playbackErrors].reverse(), pagination); + } + + async getPlaybackErrorOrThrow(id: number) { + if (this.hasDatabase()) { + const log = await this.dataSource + .getRepository(PlaybackErrorLog) + .findOne({ where: { id } }); + if (!log) { + throw new NotFoundException('Playback error log not found'); + } + return this.toPlaybackErrorLogRecord(log); + } + + const log = this.playbackErrors.find((item) => item.id === id); + if (!log) { + throw new NotFoundException('Playback error log not found'); + } + + return log; + } + + private hasDatabase(): this is this & { dataSource: DataSource } { + return Boolean(this.dataSource?.isInitialized); + } + + private toRequestLogRecord(log: RequestLog): RequestLogRecord { + return { + id: Number(log.id), + requestId: log.requestId, + method: log.method, + path: log.path, + query: log.query, + body: log.body, + statusCode: log.statusCode, + responseCode: log.responseCode, + message: log.message, + costMs: log.costMs, + userId: log.userId ? Number(log.userId) : undefined, + adminId: log.adminId ? Number(log.adminId) : undefined, + ip: log.ip, + userAgent: log.userAgent, + errorStack: log.errorStack, + createdAt: log.createdAt?.toISOString?.(), + }; + } + + private toPlaybackErrorLogRecord(log: PlaybackErrorLog): PlaybackErrorLogRecord { + return { + id: Number(log.id), + requestId: log.requestId, + userId: log.userId ? Number(log.userId) : undefined, + dramaId: Number(log.dramaId), + episodeId: Number(log.episodeId), + playUrl: log.playUrl, + playerErrorCode: log.playerErrorCode, + message: log.message, + progressSeconds: log.progressSeconds, + networkType: log.networkType, + device: log.device, + occurredAt: log.occurredAt.toISOString(), + createdAt: log.createdAt?.toISOString?.() ?? new Date().toISOString(), + }; + } + + private paginate( + records: T[], + input: PaginationInput, + ): PaginatedResponse { + const page = Math.max(input.page, 1); + const pageSize = Math.min(Math.max(input.pageSize, 1), 100); + const start = (page - 1) * pageSize; + + return { + list: records.slice(start, start + pageSize), + pagination: { + page, + pageSize, + total: records.length, + }, + }; + } +} diff --git a/src/logs/logs.types.ts b/src/logs/logs.types.ts new file mode 100644 index 0000000..52a3630 --- /dev/null +++ b/src/logs/logs.types.ts @@ -0,0 +1,34 @@ +export interface RequestLogRecord { + id?: number; + requestId: string; + method: string; + path: string; + query?: unknown; + body?: unknown; + statusCode: number; + responseCode: number; + message: string; + costMs: number; + userId?: number; + adminId?: number; + ip?: string; + userAgent?: string; + errorStack?: string; + createdAt?: string; +} + +export interface PlaybackErrorLogRecord { + id: number; + requestId: string; + userId?: number; + dramaId: number; + episodeId: number; + playUrl: string; + playerErrorCode: string; + message: string; + progressSeconds?: number; + networkType?: string; + device?: Record; + occurredAt: string; + createdAt: string; +} diff --git a/src/logs/playback-error-log.entity.ts b/src/logs/playback-error-log.entity.ts new file mode 100644 index 0000000..0ea6060 --- /dev/null +++ b/src/logs/playback-error-log.entity.ts @@ -0,0 +1,50 @@ +import { + Column, + CreateDateColumn, + Entity, + Index, + PrimaryGeneratedColumn, +} from 'typeorm'; + +@Entity('playback_error_logs') +export class PlaybackErrorLog { + @PrimaryGeneratedColumn() + id: number; + + @Index() + @Column({ type: 'char', length: 36 }) + requestId: string; + + @Column({ type: 'bigint', nullable: true }) + userId?: number; + + @Column({ type: 'bigint' }) + dramaId: number; + + @Column({ type: 'bigint' }) + episodeId: number; + + @Column({ type: 'varchar', length: 1024 }) + playUrl: string; + + @Column({ type: 'varchar', length: 128 }) + playerErrorCode: string; + + @Column({ type: 'varchar', length: 1024 }) + message: string; + + @Column({ type: 'int', nullable: true }) + progressSeconds?: number; + + @Column({ type: 'varchar', length: 64, nullable: true }) + networkType?: string; + + @Column({ type: 'json', nullable: true }) + device?: Record; + + @Column({ type: 'datetime' }) + occurredAt: Date; + + @CreateDateColumn() + createdAt: Date; +} diff --git a/src/logs/request-log.entity.ts b/src/logs/request-log.entity.ts new file mode 100644 index 0000000..5bcfc06 --- /dev/null +++ b/src/logs/request-log.entity.ts @@ -0,0 +1,59 @@ +import { + Column, + CreateDateColumn, + Entity, + Index, + PrimaryGeneratedColumn, +} from 'typeorm'; + +@Entity('request_logs') +export class RequestLog { + @PrimaryGeneratedColumn() + id: number; + + @Index({ unique: true }) + @Column({ type: 'char', length: 36 }) + requestId: string; + + @Column({ type: 'varchar', length: 16 }) + method: string; + + @Column({ type: 'varchar', length: 1024 }) + path: string; + + @Column({ type: 'json', nullable: true }) + query?: unknown; + + @Column({ type: 'json', nullable: true }) + body?: unknown; + + @Column({ type: 'int' }) + statusCode: number; + + @Column({ type: 'int' }) + responseCode: number; + + @Column({ type: 'varchar', length: 512 }) + message: string; + + @Column({ type: 'int' }) + costMs: number; + + @Column({ type: 'bigint', nullable: true }) + userId?: number; + + @Column({ type: 'bigint', nullable: true }) + adminId?: number; + + @Column({ type: 'varchar', length: 128, nullable: true }) + ip?: string; + + @Column({ type: 'varchar', length: 512, nullable: true }) + userAgent?: string; + + @Column({ type: 'text', nullable: true }) + errorStack?: string; + + @CreateDateColumn() + createdAt: Date; +} diff --git a/src/modules/media/entities/cover.entity.ts b/src/media/cover.entity.ts similarity index 100% rename from src/modules/media/entities/cover.entity.ts rename to src/media/cover.entity.ts diff --git a/src/modules/media/entities/media-upload-job.entity.ts b/src/media/media-upload-job.entity.ts similarity index 100% rename from src/modules/media/entities/media-upload-job.entity.ts rename to src/media/media-upload-job.entity.ts diff --git a/src/modules/media/media.controller.ts b/src/media/media.controller.ts similarity index 100% rename from src/modules/media/media.controller.ts rename to src/media/media.controller.ts diff --git a/src/modules/media/dto/create-upload-job.dto.ts b/src/media/media.dto.ts similarity index 100% rename from src/modules/media/dto/create-upload-job.dto.ts rename to src/media/media.dto.ts diff --git a/src/modules/media/media.module.ts b/src/media/media.module.ts similarity index 100% rename from src/modules/media/media.module.ts rename to src/media/media.module.ts diff --git a/src/modules/media/providers/byteplus-media.provider.ts b/src/media/media.provider.ts similarity index 100% rename from src/modules/media/providers/byteplus-media.provider.ts rename to src/media/media.provider.ts diff --git a/src/modules/media/media.service.ts b/src/media/media.service.ts similarity index 100% rename from src/modules/media/media.service.ts rename to src/media/media.service.ts diff --git a/src/modules/media/interfaces/media-records.interface.ts b/src/media/media.types.ts similarity index 100% rename from src/modules/media/interfaces/media-records.interface.ts rename to src/media/media.types.ts diff --git a/src/modules/admin/dto/change-admin-password.dto.ts b/src/modules/admin/dto/change-admin-password.dto.ts deleted file mode 100644 index db61ba6..0000000 --- a/src/modules/admin/dto/change-admin-password.dto.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString, MinLength } from 'class-validator'; - -export class ChangeAdminPasswordDto { - @ApiProperty() - @IsString() - oldPassword: string; - - @ApiProperty() - @IsString() - @MinLength(6) - newPassword: string; -} diff --git a/src/modules/admin/dto/create-admin-user.dto.ts b/src/modules/admin/dto/create-admin-user.dto.ts deleted file mode 100644 index b308d31..0000000 --- a/src/modules/admin/dto/create-admin-user.dto.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { - IsArray, - IsEmail, - IsInt, - IsOptional, - IsString, - Min, - MinLength, -} from 'class-validator'; - -export class CreateAdminUserDto { - @ApiProperty() - @IsString() - username: string; - - @ApiProperty() - @IsString() - @MinLength(6) - password: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - nickname?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsEmail() - email?: string; - - @ApiPropertyOptional({ type: [Number] }) - @IsOptional() - @IsArray() - @IsInt({ each: true }) - @Min(1, { each: true }) - roleIds?: number[]; -} diff --git a/src/modules/admin/dto/create-permission.dto.ts b/src/modules/admin/dto/create-permission.dto.ts deleted file mode 100644 index 7805588..0000000 --- a/src/modules/admin/dto/create-permission.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; - -export class CreatePermissionDto { - @ApiProperty() - @IsString() - code: string; - - @ApiProperty() - @IsString() - name: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - description?: string; -} diff --git a/src/modules/admin/dto/create-role.dto.ts b/src/modules/admin/dto/create-role.dto.ts deleted file mode 100644 index b28cc32..0000000 --- a/src/modules/admin/dto/create-role.dto.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { - IsArray, - IsInt, - IsOptional, - IsString, - Min, -} from 'class-validator'; - -export class CreateRoleDto { - @ApiProperty() - @IsString() - code: string; - - @ApiProperty() - @IsString() - name: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - description?: string; - - @ApiPropertyOptional({ type: [Number] }) - @IsOptional() - @IsArray() - @IsInt({ each: true }) - @Min(1, { each: true }) - permissionIds?: number[]; -} diff --git a/src/modules/admin/dto/update-admin-profile.dto.ts b/src/modules/admin/dto/update-admin-profile.dto.ts deleted file mode 100644 index 20873e4..0000000 --- a/src/modules/admin/dto/update-admin-profile.dto.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ApiPropertyOptional } from '@nestjs/swagger'; -import { IsEmail, IsOptional, IsString } from 'class-validator'; - -export class UpdateAdminProfileDto { - @ApiPropertyOptional() - @IsOptional() - @IsString() - nickname?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsEmail() - email?: string; -} diff --git a/src/modules/admin/dto/update-admin-roles.dto.ts b/src/modules/admin/dto/update-admin-roles.dto.ts deleted file mode 100644 index 90025cf..0000000 --- a/src/modules/admin/dto/update-admin-roles.dto.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsInt, Min } from 'class-validator'; - -export class UpdateAdminRolesDto { - @ApiProperty({ type: [Number] }) - @IsArray() - @IsInt({ each: true }) - @Min(1, { each: true }) - roleIds: number[]; -} diff --git a/src/modules/admin/dto/update-admin-status.dto.ts b/src/modules/admin/dto/update-admin-status.dto.ts deleted file mode 100644 index be368c0..0000000 --- a/src/modules/admin/dto/update-admin-status.dto.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsIn } from 'class-validator'; - -export class UpdateAdminStatusDto { - @ApiProperty({ enum: ['active', 'disabled'] }) - @IsIn(['active', 'disabled']) - status: 'active' | 'disabled'; -} diff --git a/src/modules/admin/dto/update-role-permissions.dto.ts b/src/modules/admin/dto/update-role-permissions.dto.ts deleted file mode 100644 index 68a461c..0000000 --- a/src/modules/admin/dto/update-role-permissions.dto.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsInt, Min } from 'class-validator'; - -export class UpdateRolePermissionsDto { - @ApiProperty({ type: [Number] }) - @IsArray() - @IsInt({ each: true }) - @Min(1, { each: true }) - permissionIds: number[]; -} diff --git a/src/modules/database/database.module.ts b/src/modules/database/database.module.ts deleted file mode 100644 index 0985813..0000000 --- a/src/modules/database/database.module.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { AdminUser } from '../admin/entities/admin-user.entity'; -import { Permission } from '../admin/entities/permission.entity'; -import { Role } from '../admin/entities/role.entity'; -import { DataSyncJob } from '../data/entities/data-sync-job.entity'; -import { DramaMetric } from '../data/entities/drama-metric.entity'; -import { EpisodeMetric } from '../data/entities/episode-metric.entity'; -import { Drama } from '../dramas/entities/drama.entity'; -import { Episode } from '../dramas/entities/episode.entity'; -import { PlaybackErrorLog } from '../logs/entities/playback-error-log.entity'; -import { RequestLog } from '../logs/entities/request-log.entity'; -import { Cover } from '../media/entities/cover.entity'; -import { MediaUploadJob } from '../media/entities/media-upload-job.entity'; -import { Order } from '../orders/entities/order.entity'; -import { UserEpisodeUnlock } from '../orders/entities/user-episode-unlock.entity'; -import { UserPlaybackHistory } from '../history/entities/user-playback-history.entity'; -import { User } from '../users/entities/user.entity'; - -const databaseImports = process.env.DB_HOST - ? [ - TypeOrmModule.forRoot({ - type: 'mysql', - host: process.env.DB_HOST, - port: Number(process.env.DB_PORT) || 3306, - username: process.env.DB_USERNAME ?? 'root', - password: process.env.DB_PASSWORD ?? '', - database: process.env.DB_DATABASE ?? 'cth_tk', - entities: [ - AdminUser, - Permission, - Role, - DataSyncJob, - DramaMetric, - EpisodeMetric, - Drama, - Episode, - PlaybackErrorLog, - RequestLog, - Cover, - MediaUploadJob, - Order, - UserEpisodeUnlock, - UserPlaybackHistory, - User, - ], - synchronize: process.env.DB_SYNCHRONIZE === 'true', - }), - ] - : []; - -@Module({ - imports: databaseImports, -}) -export class DatabaseModule {} diff --git a/src/modules/dramas/dto/configure-episodes.dto.ts b/src/modules/dramas/dto/configure-episodes.dto.ts deleted file mode 100644 index b5d7df4..0000000 --- a/src/modules/dramas/dto/configure-episodes.dto.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsInt, Max, Min } from 'class-validator'; - -export class ConfigureEpisodesDto { - @ApiProperty() - @IsInt() - @Min(1) - @Max(500) - episodeCount: number; -} diff --git a/src/modules/dramas/dto/create-episode.dto.ts b/src/modules/dramas/dto/create-episode.dto.ts deleted file mode 100644 index 699fee6..0000000 --- a/src/modules/dramas/dto/create-episode.dto.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { - IsBoolean, - IsDateString, - IsEnum, - IsInt, - IsOptional, - IsString, - IsUrl, - Min, -} from 'class-validator'; -import { PublishStatus } from '../drama-status.enum'; - -export class CreateEpisodeDto { - @ApiProperty() - @IsOptional() - @IsInt() - @Min(1) - dramaId?: number; - - @ApiPropertyOptional() - @IsOptional() - @IsInt() - @Min(1) - albumId?: number; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - tiktokEpisodeId?: string; - - @ApiProperty() - @IsOptional() - @IsInt() - @Min(1) - episodeNo?: number; - - @ApiPropertyOptional() - @IsOptional() - @IsInt() - @Min(1) - seq?: number; - - @ApiProperty() - @IsString() - title: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - description?: string; - - @ApiProperty() - @IsOptional() - @IsUrl({ require_tld: false }) - coverUrl: string; - - @ApiPropertyOptional() - @IsOptional() - @IsInt() - @Min(1) - coverId?: number; - - @ApiProperty() - @IsOptional() - @IsUrl({ require_tld: false }) - videoUrl?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - byteplusVid?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsInt() - @Min(0) - trialDurationSeconds?: number; - - @ApiProperty() - @IsInt() - @Min(1) - durationSeconds: number; - - @ApiPropertyOptional() - @IsOptional() - @IsInt() - @Min(1) - width?: number; - - @ApiPropertyOptional() - @IsOptional() - @IsInt() - @Min(1) - height?: number; - - @ApiPropertyOptional() - @IsOptional() - @IsBoolean() - isPaid?: boolean; - - @ApiPropertyOptional({ enum: ['free', 'paid'] }) - @IsOptional() - @IsString() - freeType?: 'free' | 'paid'; - - @ApiPropertyOptional() - @IsOptional() - @IsInt() - @Min(0) - unlockPrice?: number; - - @ApiPropertyOptional() - @IsOptional() - @IsInt() - @Min(0) - price?: number; - - @ApiPropertyOptional() - @IsOptional() - @IsDateString() - publishAt?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsDateString() - nextReleaseAt?: string; - - @ApiPropertyOptional({ enum: PublishStatus }) - @IsOptional() - @IsEnum(PublishStatus) - status?: PublishStatus; - - @ApiPropertyOptional({ - enum: ['not_submitted', 'pending', 'reviewing', 'approved', 'rejected'], - }) - @IsOptional() - @IsString() - reviewStatus?: 'not_submitted' | 'pending' | 'reviewing' | 'approved' | 'rejected'; - - @ApiPropertyOptional({ enum: PublishStatus }) - @IsOptional() - @IsEnum(PublishStatus) - publishStatus?: PublishStatus; -} diff --git a/src/modules/dramas/dto/update-drama.dto.ts b/src/modules/dramas/dto/update-drama.dto.ts deleted file mode 100644 index 8a63aa1..0000000 --- a/src/modules/dramas/dto/update-drama.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { CreateDramaDto } from './create-drama.dto'; - -export class UpdateDramaDto extends PartialType(CreateDramaDto) {} diff --git a/src/modules/dramas/dto/update-episode.dto.ts b/src/modules/dramas/dto/update-episode.dto.ts deleted file mode 100644 index 3c5eadf..0000000 --- a/src/modules/dramas/dto/update-episode.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { CreateEpisodeDto } from './create-episode.dto'; - -export class UpdateEpisodeDto extends PartialType(CreateEpisodeDto) {} diff --git a/src/modules/health/health.module.ts b/src/modules/health/health.module.ts deleted file mode 100644 index 7476abe..0000000 --- a/src/modules/health/health.module.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Module } from '@nestjs/common'; -import { HealthController } from './health.controller'; - -@Module({ - controllers: [HealthController], -}) -export class HealthModule {} diff --git a/src/modules/media/dto/complete-direct-upload-task.dto.ts b/src/modules/media/dto/complete-direct-upload-task.dto.ts deleted file mode 100644 index 629003f..0000000 --- a/src/modules/media/dto/complete-direct-upload-task.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; - -export class CompleteDirectUploadTaskDto { - @ApiPropertyOptional() - @IsOptional() - @IsString() - providerUploadId?: string; -} diff --git a/src/modules/media/dto/create-cover.dto.ts b/src/modules/media/dto/create-cover.dto.ts deleted file mode 100644 index 62896f8..0000000 --- a/src/modules/media/dto/create-cover.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString, IsUrl } from 'class-validator'; - -export class CreateCoverDto { - @ApiProperty() - @IsString() - fileName: string; - - @ApiProperty() - @IsUrl({ require_tld: false }) - url: string; -} diff --git a/src/modules/media/dto/create-direct-upload-task.dto.ts b/src/modules/media/dto/create-direct-upload-task.dto.ts deleted file mode 100644 index d06cb84..0000000 --- a/src/modules/media/dto/create-direct-upload-task.dto.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsInt, IsString, Min } from 'class-validator'; - -export class CreateDirectUploadTaskDto { - @ApiProperty() - @IsInt() - @Min(1) - episodeId: number; - - @ApiProperty() - @IsString() - fileName: string; - - @ApiProperty() - @IsString() - contentType: string; - - @ApiProperty() - @IsInt() - @Min(1) - fileSize: number; -} diff --git a/src/modules/orders/dto/tiktok-payment-webhook.dto.ts b/src/modules/orders/dto/tiktok-payment-webhook.dto.ts deleted file mode 100644 index 73828de..0000000 --- a/src/modules/orders/dto/tiktok-payment-webhook.dto.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsIn, IsOptional, IsString } from 'class-validator'; - -export class TikTokPaymentWebhookDto { - @ApiPropertyOptional() - @IsOptional() - @IsString() - orderNo?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - tradeOrderId?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - tiktokOrderId?: string; - - @ApiProperty() - @IsString() - providerTransactionId: string; - - @ApiProperty({ enum: ['paid', 'failed'] }) - @IsIn(['paid', 'failed']) - status: 'paid' | 'failed'; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - signature?: string; -} diff --git a/src/modules/orders/entities/order.entity.ts b/src/orders/order.entity.ts similarity index 100% rename from src/modules/orders/entities/order.entity.ts rename to src/orders/order.entity.ts diff --git a/src/modules/orders/orders.controller.ts b/src/orders/orders.controller.ts similarity index 100% rename from src/modules/orders/orders.controller.ts rename to src/orders/orders.controller.ts diff --git a/src/modules/orders/dto/create-order.dto.ts b/src/orders/orders.dto.ts similarity index 100% rename from src/modules/orders/dto/create-order.dto.ts rename to src/orders/orders.dto.ts diff --git a/src/modules/orders/orders.module.ts b/src/orders/orders.module.ts similarity index 100% rename from src/modules/orders/orders.module.ts rename to src/orders/orders.module.ts diff --git a/src/modules/orders/orders.service.ts b/src/orders/orders.service.ts similarity index 100% rename from src/modules/orders/orders.service.ts rename to src/orders/orders.service.ts diff --git a/src/modules/orders/interfaces/order-records.interface.ts b/src/orders/orders.types.ts similarity index 100% rename from src/modules/orders/interfaces/order-records.interface.ts rename to src/orders/orders.types.ts diff --git a/src/modules/orders/entities/user-episode-unlock.entity.ts b/src/orders/user-episode-unlock.entity.ts similarity index 100% rename from src/modules/orders/entities/user-episode-unlock.entity.ts rename to src/orders/user-episode-unlock.entity.ts diff --git a/src/modules/player/player.controller.ts b/src/player/player.controller.ts similarity index 100% rename from src/modules/player/player.controller.ts rename to src/player/player.controller.ts diff --git a/src/modules/player/player.module.ts b/src/player/player.module.ts similarity index 100% rename from src/modules/player/player.module.ts rename to src/player/player.module.ts diff --git a/src/modules/player/player.service.ts b/src/player/player.service.ts similarity index 100% rename from src/modules/player/player.service.ts rename to src/player/player.service.ts diff --git a/src/modules/users/entities/user.entity.ts b/src/users/user.entity.ts similarity index 100% rename from src/modules/users/entities/user.entity.ts rename to src/users/user.entity.ts diff --git a/src/modules/users/users.controller.ts b/src/users/users.controller.ts similarity index 100% rename from src/modules/users/users.controller.ts rename to src/users/users.controller.ts diff --git a/src/modules/users/dto/update-user-status.dto.ts b/src/users/users.dto.ts similarity index 100% rename from src/modules/users/dto/update-user-status.dto.ts rename to src/users/users.dto.ts diff --git a/src/modules/users/users.module.ts b/src/users/users.module.ts similarity index 100% rename from src/modules/users/users.module.ts rename to src/users/users.module.ts diff --git a/src/modules/users/users.service.ts b/src/users/users.service.ts similarity index 100% rename from src/modules/users/users.service.ts rename to src/users/users.service.ts diff --git a/src/modules/users/interfaces/user-record.interface.ts b/src/users/users.types.ts similarity index 100% rename from src/modules/users/interfaces/user-record.interface.ts rename to src/users/users.types.ts diff --git a/src/common/utils/request-context.util.ts b/utils/request-context.util.ts similarity index 100% rename from src/common/utils/request-context.util.ts rename to utils/request-context.util.ts