refactor: 统一代码缩进与引号风格,新增CORS配置
1. 修正所有文件的缩进为4空格,将单引号统一改为双引号 2. 新增CORS_ORIGIN环境变量配置并从env读取CORS允许源 3. 新增prettier配置文件统一代码格式 4. 为main.ts添加启动日志输出
This commit is contained in:
3
.env
3
.env
@@ -12,4 +12,7 @@ DB_USERNAME=root
|
|||||||
DB_PASSWORD=zhxiao1124..
|
DB_PASSWORD=zhxiao1124..
|
||||||
DB_DATABASE=onion_tiktok
|
DB_DATABASE=onion_tiktok
|
||||||
|
|
||||||
|
# CORS settings
|
||||||
|
CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173
|
||||||
|
|
||||||
DB_SYNCHRONIZE = true
|
DB_SYNCHRONIZE = true
|
||||||
|
|||||||
@@ -12,4 +12,7 @@ DB_USERNAME=root
|
|||||||
DB_PASSWORD=
|
DB_PASSWORD=
|
||||||
DB_DATABASE=cth_tk_backend
|
DB_DATABASE=cth_tk_backend
|
||||||
|
|
||||||
|
# CORS settings
|
||||||
|
CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173
|
||||||
|
|
||||||
DB_SYNCHRONIZE = true
|
DB_SYNCHRONIZE = true
|
||||||
|
|||||||
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 390,
|
||||||
|
"eslintIntegration": true,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"stylelintIntegration": true
|
||||||
|
}
|
||||||
@@ -1,31 +1,31 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from "@nestjs/common";
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from "@nestjs/config";
|
||||||
import { AdminModule } from './modules/admin/admin.module';
|
import { AdminModule } from "./modules/admin/admin.module";
|
||||||
import { AuthModule } from './modules/auth/auth.module';
|
import { AuthModule } from "./modules/auth/auth.module";
|
||||||
import { DataModule } from './modules/data/data.module';
|
import { DataModule } from "./modules/data/data.module";
|
||||||
import { DatabaseModule } from './modules/database/database.module';
|
import { DatabaseModule } from "./modules/database/database.module";
|
||||||
import { DramasModule } from './modules/dramas/dramas.module';
|
import { DramasModule } from "./modules/dramas/dramas.module";
|
||||||
import { HealthModule } from './modules/health/health.module';
|
import { HealthModule } from "./modules/health/health.module";
|
||||||
import { LogsModule } from './modules/logs/logs.module';
|
import { LogsModule } from "./modules/logs/logs.module";
|
||||||
import { MediaModule } from './modules/media/media.module';
|
import { MediaModule } from "./modules/media/media.module";
|
||||||
import { OrdersModule } from './modules/orders/orders.module';
|
import { OrdersModule } from "./modules/orders/orders.module";
|
||||||
import { PlayerModule } from './modules/player/player.module';
|
import { PlayerModule } from "./modules/player/player.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
}),
|
}),
|
||||||
DatabaseModule,
|
DatabaseModule,
|
||||||
AdminModule,
|
AdminModule,
|
||||||
HealthModule,
|
HealthModule,
|
||||||
LogsModule,
|
LogsModule,
|
||||||
DramasModule,
|
DramasModule,
|
||||||
DataModule,
|
DataModule,
|
||||||
MediaModule,
|
MediaModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
OrdersModule,
|
OrdersModule,
|
||||||
PlayerModule,
|
PlayerModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -8,35 +8,27 @@ import { RequestIdMiddleware } from "./common/middleware/request-id.middleware";
|
|||||||
import { LogsService } from "./modules/logs/logs.service";
|
import { LogsService } from "./modules/logs/logs.service";
|
||||||
|
|
||||||
export function bootstrapApp(app: INestApplication) {
|
export function bootstrapApp(app: INestApplication) {
|
||||||
app.enableCors({
|
app.enableCors({
|
||||||
origin: process.env.CORS_ORIGIN?.split(',') ?? [
|
origin: process.env.CORS_ORIGIN?.split(","),
|
||||||
'http://localhost:5173',
|
credentials: true,
|
||||||
'http://127.0.0.1:5173',
|
});
|
||||||
],
|
app.use(RequestIdMiddleware);
|
||||||
credentials: true,
|
app.useGlobalPipes(
|
||||||
});
|
new ValidationPipe({
|
||||||
app.use(RequestIdMiddleware);
|
whitelist: true,
|
||||||
app.useGlobalPipes(
|
transform: true,
|
||||||
new ValidationPipe({
|
forbidNonWhitelisted: true,
|
||||||
whitelist: true,
|
}),
|
||||||
transform: true,
|
);
|
||||||
forbidNonWhitelisted: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const logsService = app.get(LogsService);
|
const logsService = app.get(LogsService);
|
||||||
app.useGlobalFilters(new HttpExceptionFilter(logsService));
|
|
||||||
app.useGlobalInterceptors(
|
|
||||||
new RequestLoggingInterceptor(logsService, app.get(Reflector)),
|
|
||||||
new ResponseInterceptor(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
app.useGlobalFilters(new HttpExceptionFilter(logsService));
|
||||||
.setTitle("TikTok Short Drama Backend")
|
app.useGlobalInterceptors(new RequestLoggingInterceptor(logsService, app.get(Reflector)), new ResponseInterceptor());
|
||||||
.setDescription("RESTful API for TikTok short drama backend services.")
|
|
||||||
.setVersion("1.0")
|
const config = new DocumentBuilder().setTitle("TikTok Short Drama Backend").setDescription("RESTful API for TikTok short drama backend services.").setVersion("1.0").addBearerAuth().build();
|
||||||
.addBearerAuth()
|
|
||||||
.build();
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
|
||||||
SwaggerModule.setup("/api/docs", app, document);
|
SwaggerModule.setup("/api/docs", app, document);
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/main.ts
15
src/main.ts
@@ -1,12 +1,13 @@
|
|||||||
import 'dotenv/config';
|
import "dotenv/config";
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from "@nestjs/core";
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from "./app.module";
|
||||||
import { bootstrapApp } from './bootstrap';
|
import { bootstrapApp } from "./bootstrap";
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
bootstrapApp(app);
|
bootstrapApp(app);
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
|
console.log(`Application is running on: ${await app.getUrl()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
void bootstrap();
|
void bootstrap();
|
||||||
|
|||||||
@@ -1,172 +1,134 @@
|
|||||||
import {
|
import { Body, Controller, Get, Param, Patch, Post, Query, UseGuards } from "@nestjs/common";
|
||||||
Body,
|
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
||||||
Controller,
|
import { RequirePermissions } from "../admin/decorators/require-permissions.decorator";
|
||||||
Get,
|
import { AdminJwtAuthGuard } from "../admin/guards/admin-jwt-auth.guard";
|
||||||
Param,
|
import { PermissionsGuard } from "../admin/guards/permissions.guard";
|
||||||
Patch,
|
import { ConfigureEpisodesDto } from "./dto/configure-episodes.dto";
|
||||||
Post,
|
import { CreateDramaDto } from "./dto/create-drama.dto";
|
||||||
Query,
|
import { CreateEpisodeDto } from "./dto/create-episode.dto";
|
||||||
UseGuards,
|
import { UpdateDramaDto } from "./dto/update-drama.dto";
|
||||||
} from '@nestjs/common';
|
import { UpdateEpisodeDto } from "./dto/update-episode.dto";
|
||||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
import { DramasService } from "./dramas.service";
|
||||||
import { RequirePermissions } from '../admin/decorators/require-permissions.decorator';
|
import { PublishStatus } from "./drama-status.enum";
|
||||||
import { AdminJwtAuthGuard } from '../admin/guards/admin-jwt-auth.guard';
|
|
||||||
import { PermissionsGuard } from '../admin/guards/permissions.guard';
|
|
||||||
import { ConfigureEpisodesDto } from './dto/configure-episodes.dto';
|
|
||||||
import { CreateDramaDto } from './dto/create-drama.dto';
|
|
||||||
import { CreateEpisodeDto } from './dto/create-episode.dto';
|
|
||||||
import { UpdateDramaDto } from './dto/update-drama.dto';
|
|
||||||
import { UpdateEpisodeDto } from './dto/update-episode.dto';
|
|
||||||
import { DramasService } from './dramas.service';
|
|
||||||
import { PublishStatus } from './drama-status.enum';
|
|
||||||
|
|
||||||
@ApiTags('Dramas')
|
@ApiTags("Dramas")
|
||||||
@Controller()
|
@Controller()
|
||||||
export class DramasController {
|
export class DramasController {
|
||||||
constructor(private readonly dramasService: DramasService) {}
|
constructor(private readonly dramasService: DramasService) {}
|
||||||
|
|
||||||
@Post('/api/admin/v1/dramas')
|
@Post("/api/admin/v1/dramas")
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
@RequirePermissions('drama:create')
|
@RequirePermissions("drama:create")
|
||||||
createDrama(@Body() dto: CreateDramaDto) {
|
createDrama(@Body() dto: CreateDramaDto) {
|
||||||
return this.dramasService.createDrama(dto);
|
return this.dramasService.createDrama(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/api/admin/v1/dramas')
|
@Get("/api/admin/v1/dramas")
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
@RequirePermissions('drama:read')
|
@RequirePermissions("drama:read")
|
||||||
listAdminDramas(@Query('page') page?: string, @Query('pageSize') pageSize?: string) {
|
listAdminDramas(@Query("page") page?: string, @Query("pageSize") pageSize?: string) {
|
||||||
return this.dramasService.listAdminDramas({
|
return this.dramasService.listAdminDramas({
|
||||||
page: Number(page) || 1,
|
page: Number(page) || 1,
|
||||||
pageSize: Number(pageSize) || 20,
|
pageSize: Number(pageSize) || 20,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/api/admin/v1/dramas/:id')
|
@Get("/api/admin/v1/dramas/:id")
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
@RequirePermissions('drama:read')
|
@RequirePermissions("drama:read")
|
||||||
getDrama(@Param('id') id: string) {
|
getDrama(@Param("id") id: string) {
|
||||||
return this.dramasService.getDramaOrThrow(Number(id));
|
return this.dramasService.getDramaOrThrow(Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch('/api/admin/v1/dramas/:id')
|
@Patch("/api/admin/v1/dramas/:id")
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
@RequirePermissions('drama:update')
|
@RequirePermissions("drama:update")
|
||||||
updateDrama(@Param('id') id: string, @Body() dto: UpdateDramaDto) {
|
updateDrama(@Param("id") id: string, @Body() dto: UpdateDramaDto) {
|
||||||
return this.dramasService.updateDrama(Number(id), dto);
|
return this.dramasService.updateDrama(Number(id), dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/api/admin/v1/dramas/:id/episodes/batch')
|
@Post("/api/admin/v1/dramas/:id/episodes/batch")
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
@RequirePermissions('episode:create')
|
@RequirePermissions("episode:create")
|
||||||
configureEpisodes(
|
configureEpisodes(@Param("id") id: string, @Body() dto: ConfigureEpisodesDto) {
|
||||||
@Param('id') id: string,
|
return this.dramasService.configureEpisodes(Number(id), dto);
|
||||||
@Body() dto: ConfigureEpisodesDto,
|
}
|
||||||
) {
|
|
||||||
return this.dramasService.configureEpisodes(Number(id), dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/api/admin/v1/dramas/:id/episodes')
|
@Get("/api/admin/v1/dramas/:id/episodes")
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
@RequirePermissions('episode:update')
|
@RequirePermissions("episode:update")
|
||||||
listAdminEpisodes(
|
listAdminEpisodes(@Param("id") id: string, @Query("page") page?: string, @Query("pageSize") pageSize?: string) {
|
||||||
@Param('id') id: string,
|
return this.dramasService.listAdminEpisodes(Number(id), {
|
||||||
@Query('page') page?: string,
|
page: Number(page) || 1,
|
||||||
@Query('pageSize') pageSize?: string,
|
pageSize: Number(pageSize) || 20,
|
||||||
) {
|
});
|
||||||
return this.dramasService.listAdminEpisodes(Number(id), {
|
}
|
||||||
page: Number(page) || 1,
|
|
||||||
pageSize: Number(pageSize) || 20,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/api/admin/v1/episodes')
|
@Post("/api/admin/v1/episodes")
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
@RequirePermissions('episode:create')
|
@RequirePermissions("episode:create")
|
||||||
createEpisode(@Body() dto: CreateEpisodeDto) {
|
createEpisode(@Body() dto: CreateEpisodeDto) {
|
||||||
return this.dramasService.createEpisode(dto);
|
return this.dramasService.createEpisode(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch('/api/admin/v1/episodes/:id')
|
@Patch("/api/admin/v1/episodes/:id")
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
@RequirePermissions('episode:update')
|
@RequirePermissions("episode:update")
|
||||||
updateEpisode(@Param('id') id: string, @Body() dto: UpdateEpisodeDto) {
|
updateEpisode(@Param("id") id: string, @Body() dto: UpdateEpisodeDto) {
|
||||||
return this.dramasService.updateEpisode(Number(id), dto);
|
return this.dramasService.updateEpisode(Number(id), dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch('/api/admin/v1/dramas/:dramaId/episodes/:episodeId')
|
@Patch("/api/admin/v1/dramas/:dramaId/episodes/:episodeId")
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
@RequirePermissions('episode:update')
|
@RequirePermissions("episode:update")
|
||||||
updateDramaEpisode(
|
updateDramaEpisode(@Param("dramaId") dramaId: string, @Param("episodeId") episodeId: string, @Body() dto: UpdateEpisodeDto) {
|
||||||
@Param('dramaId') dramaId: string,
|
return this.dramasService.updateDramaEpisode(Number(dramaId), Number(episodeId), dto);
|
||||||
@Param('episodeId') episodeId: string,
|
}
|
||||||
@Body() dto: UpdateEpisodeDto,
|
|
||||||
) {
|
|
||||||
return this.dramasService.updateDramaEpisode(
|
|
||||||
Number(dramaId),
|
|
||||||
Number(episodeId),
|
|
||||||
dto,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/api/admin/v1/dramas/:id/submit-review')
|
@Post("/api/admin/v1/dramas/:id/submit-review")
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
@RequirePermissions('drama:update')
|
@RequirePermissions("drama:update")
|
||||||
submitReview(@Param('id') id: string) {
|
submitReview(@Param("id") id: string) {
|
||||||
return this.dramasService.submitReview(Number(id));
|
return this.dramasService.submitReview(Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch('/api/admin/v1/episodes/:id/publish-status')
|
@Patch("/api/admin/v1/episodes/:id/publish-status")
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
@RequirePermissions('episode:update')
|
@RequirePermissions("episode:update")
|
||||||
updatePublishStatus(
|
updatePublishStatus(@Param("id") id: string, @Body() dto: { publishStatus: PublishStatus }) {
|
||||||
@Param('id') id: string,
|
return this.dramasService.updateEpisodePublishStatus(Number(id), dto.publishStatus);
|
||||||
@Body() dto: { publishStatus: PublishStatus },
|
}
|
||||||
) {
|
|
||||||
return this.dramasService.updateEpisodePublishStatus(
|
|
||||||
Number(id),
|
|
||||||
dto.publishStatus,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/api/app/v1/dramas')
|
@Get("/api/app/v1/dramas")
|
||||||
listPublishedDramas(
|
listPublishedDramas(@Query("page") page?: string, @Query("pageSize") pageSize?: string, @Query("title") title?: string) {
|
||||||
@Query('page') page?: string,
|
return this.dramasService.listPublishedDramas({
|
||||||
@Query('pageSize') pageSize?: string,
|
page: Number(page) || 1,
|
||||||
@Query('title') title?: string,
|
pageSize: Number(pageSize) || 10,
|
||||||
) {
|
title,
|
||||||
return this.dramasService.listPublishedDramas({
|
});
|
||||||
page: Number(page) || 1,
|
}
|
||||||
pageSize: Number(pageSize) || 10,
|
|
||||||
title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/api/app/v1/dramas/:id/episodes')
|
@Get("/api/app/v1/dramas/:id/episodes")
|
||||||
listPublishedEpisodes(
|
listPublishedEpisodes(@Param("id") id: string, @Query("page") page?: string, @Query("pageSize") pageSize?: string) {
|
||||||
@Param('id') id: string,
|
return this.dramasService.listPublishedEpisodes(Number(id), {
|
||||||
@Query('page') page?: string,
|
page: Number(page) || 1,
|
||||||
@Query('pageSize') pageSize?: string,
|
pageSize: Number(pageSize) || 20,
|
||||||
) {
|
});
|
||||||
return this.dramasService.listPublishedEpisodes(Number(id), {
|
}
|
||||||
page: Number(page) || 1,
|
|
||||||
pageSize: Number(pageSize) || 20,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/api/app/v1/dramas/:id')
|
@Get("/api/app/v1/dramas/:id")
|
||||||
getPublishedDrama(@Param('id') id: string) {
|
getPublishedDrama(@Param("id") id: string) {
|
||||||
return this.dramasService.getPublishedDramaOrThrow(Number(id));
|
return this.dramasService.getPublishedDramaOrThrow(Number(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
import {
|
import { Body, Controller, Get, Param, Patch, Post, Query, UseGuards } from "@nestjs/common";
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
Param,
|
|
||||||
Patch,
|
|
||||||
Post,
|
|
||||||
Query,
|
|
||||||
UseGuards,
|
|
||||||
} from "@nestjs/common";
|
|
||||||
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
||||||
import { RequirePermissions } from "../admin/decorators/require-permissions.decorator";
|
import { RequirePermissions } from "../admin/decorators/require-permissions.decorator";
|
||||||
import { AdminJwtAuthGuard } from "../admin/guards/admin-jwt-auth.guard";
|
import { AdminJwtAuthGuard } from "../admin/guards/admin-jwt-auth.guard";
|
||||||
@@ -22,59 +13,56 @@ import { MediaService } from "./media.service";
|
|||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||||
export class MediaController {
|
export class MediaController {
|
||||||
constructor(private readonly mediaService: MediaService) {}
|
constructor(private readonly mediaService: MediaService) {}
|
||||||
|
|
||||||
@Post("/covers")
|
@Post("/covers")
|
||||||
@RequirePermissions("media:create")
|
@RequirePermissions("media:create")
|
||||||
createCover(@Body() dto: CreateCoverDto) {
|
createCover(@Body() dto: CreateCoverDto) {
|
||||||
return this.mediaService.createCover(dto);
|
return this.mediaService.createCover(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("/upload-jobs")
|
@Post("/upload-jobs")
|
||||||
@RequirePermissions("media:create")
|
@RequirePermissions("media:create")
|
||||||
createUploadJob(@Body() dto: CreateUploadJobDto) {
|
createUploadJob(@Body() dto: CreateUploadJobDto) {
|
||||||
return this.mediaService.createUploadJob(dto);
|
return this.mediaService.createUploadJob(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("/upload-tasks")
|
@Post("/upload-tasks")
|
||||||
@RequirePermissions("media:create")
|
@RequirePermissions("media:create")
|
||||||
createDirectUploadTask(@Body() dto: CreateDirectUploadTaskDto) {
|
createDirectUploadTask(@Body() dto: CreateDirectUploadTaskDto) {
|
||||||
return this.mediaService.createDirectUploadTask(dto);
|
return this.mediaService.createDirectUploadTask(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("/upload-tasks/:jobId")
|
@Get("/upload-tasks/:jobId")
|
||||||
@RequirePermissions("media:read")
|
@RequirePermissions("media:read")
|
||||||
getUploadTask(@Param("jobId") jobId: string) {
|
getUploadTask(@Param("jobId") jobId: string) {
|
||||||
return this.mediaService.getUploadTask(jobId);
|
return this.mediaService.getUploadTask(jobId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch("/upload-tasks/:jobId/complete")
|
@Patch("/upload-tasks/:jobId/complete")
|
||||||
@RequirePermissions("media:create")
|
@RequirePermissions("media:create")
|
||||||
completeDirectUploadTask(@Param("jobId") jobId: string) {
|
completeDirectUploadTask(@Param("jobId") jobId: string) {
|
||||||
return this.mediaService.completeDirectUploadTask(jobId);
|
return this.mediaService.completeDirectUploadTask(jobId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("/upload-tasks/:jobId/review/retry")
|
@Post("/upload-tasks/:jobId/review/retry")
|
||||||
@RequirePermissions("media:create")
|
@RequirePermissions("media:create")
|
||||||
retryReview(@Param("jobId") jobId: string) {
|
retryReview(@Param("jobId") jobId: string) {
|
||||||
return this.mediaService.retryReview(jobId);
|
return this.mediaService.retryReview(jobId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("/review-status/sync")
|
@Post("/review-status/sync")
|
||||||
@RequirePermissions("media:create")
|
@RequirePermissions("media:create")
|
||||||
syncReviewStatuses() {
|
syncReviewStatuses() {
|
||||||
return this.mediaService.syncReviewStatuses();
|
return this.mediaService.syncReviewStatuses();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("/upload-jobs")
|
@Get("/upload-jobs")
|
||||||
@RequirePermissions("media:read")
|
@RequirePermissions("media:read")
|
||||||
listUploadJobs(
|
listUploadJobs(@Query("page") page?: string, @Query("pageSize") pageSize?: string) {
|
||||||
@Query("page") page?: string,
|
return this.mediaService.listUploadJobs({
|
||||||
@Query("pageSize") pageSize?: string,
|
page: Number(page) || 1,
|
||||||
) {
|
pageSize: Number(pageSize) || 20,
|
||||||
return this.mediaService.listUploadJobs({
|
});
|
||||||
page: Number(page) || 1,
|
}
|
||||||
pageSize: Number(pageSize) || 20,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,177 +1,172 @@
|
|||||||
import { Injectable, NotFoundException, Optional } from '@nestjs/common';
|
import { Injectable, NotFoundException, Optional } from "@nestjs/common";
|
||||||
import { InjectDataSource } from '@nestjs/typeorm';
|
import { InjectDataSource } from "@nestjs/typeorm";
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from "typeorm";
|
||||||
import { PaginatedResponse } from '../../common/dto/paginated-response.dto';
|
import { PaginatedResponse } from "../../common/dto/paginated-response.dto";
|
||||||
import { User } from './entities/user.entity';
|
import { User } from "./entities/user.entity";
|
||||||
import { UserRecord } from './interfaces/user-record.interface';
|
import { UserRecord } from "./interfaces/user-record.interface";
|
||||||
|
|
||||||
interface UpsertTikTokUserInput {
|
interface UpsertTikTokUserInput {
|
||||||
tiktokOpenId: string;
|
tiktokOpenId: string;
|
||||||
tiktokUnionId?: string;
|
tiktokUnionId?: string;
|
||||||
nickname?: string;
|
nickname?: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PaginationInput {
|
interface PaginationInput {
|
||||||
page: number;
|
page: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
private userSequence = 1;
|
private userSequence = 1;
|
||||||
private readonly users: UserRecord[] = [];
|
private readonly users: UserRecord[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Optional()
|
@Optional()
|
||||||
@InjectDataSource()
|
@InjectDataSource()
|
||||||
readonly dataSource?: DataSource,
|
readonly dataSource?: DataSource,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async upsertTikTokUser(input: UpsertTikTokUserInput) {
|
async upsertTikTokUser(input: UpsertTikTokUserInput) {
|
||||||
if (this.hasDatabase()) {
|
if (this.hasDatabase()) {
|
||||||
const repository = this.dataSource.getRepository(User);
|
const repository = this.dataSource.getRepository(User);
|
||||||
const existing = await repository.findOne({
|
const existing = await repository.findOne({
|
||||||
where: { tiktokOpenId: input.tiktokOpenId },
|
where: { tiktokOpenId: input.tiktokOpenId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.tiktokUnionId = input.tiktokUnionId ?? existing.tiktokUnionId;
|
existing.tiktokUnionId = input.tiktokUnionId ?? existing.tiktokUnionId;
|
||||||
existing.nickname = input.nickname ?? existing.nickname;
|
existing.nickname = input.nickname ?? existing.nickname;
|
||||||
existing.avatarUrl = input.avatarUrl ?? existing.avatarUrl;
|
existing.avatarUrl = input.avatarUrl ?? existing.avatarUrl;
|
||||||
const saved = await repository.save(existing);
|
const saved = await repository.save(existing);
|
||||||
return { user: this.toRecord(saved), isNewUser: false };
|
return { user: this.toRecord(saved), isNewUser: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = repository.create({
|
const user = repository.create({
|
||||||
tiktokOpenId: input.tiktokOpenId,
|
tiktokOpenId: input.tiktokOpenId,
|
||||||
tiktokUnionId: input.tiktokUnionId,
|
tiktokUnionId: input.tiktokUnionId,
|
||||||
nickname: input.nickname,
|
nickname: input.nickname,
|
||||||
avatarUrl: input.avatarUrl,
|
avatarUrl: input.avatarUrl,
|
||||||
status: 'active',
|
status: "active",
|
||||||
});
|
});
|
||||||
const saved = await repository.save(user);
|
const saved = await repository.save(user);
|
||||||
return { user: this.toRecord(saved), isNewUser: true };
|
return { user: this.toRecord(saved), isNewUser: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = this.users.find((user) => user.tiktokOpenId === input.tiktokOpenId);
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
existing.tiktokUnionId = input.tiktokUnionId ?? existing.tiktokUnionId;
|
||||||
|
existing.nickname = input.nickname ?? existing.nickname;
|
||||||
|
existing.avatarUrl = input.avatarUrl ?? existing.avatarUrl;
|
||||||
|
existing.updatedAt = now;
|
||||||
|
return { user: existing, isNewUser: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const user: UserRecord = {
|
||||||
|
id: this.userSequence++,
|
||||||
|
tiktokOpenId: input.tiktokOpenId,
|
||||||
|
tiktokUnionId: input.tiktokUnionId,
|
||||||
|
nickname: input.nickname,
|
||||||
|
avatarUrl: input.avatarUrl,
|
||||||
|
status: "active",
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.users.push(user);
|
||||||
|
return { user, isNewUser: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing = this.users.find(
|
async findById(id: number) {
|
||||||
(user) => user.tiktokOpenId === input.tiktokOpenId,
|
if (this.hasDatabase()) {
|
||||||
);
|
const user = await this.dataSource.getRepository(User).findOne({
|
||||||
const now = new Date().toISOString();
|
where: { id },
|
||||||
|
});
|
||||||
|
return user ? this.toRecord(user) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (existing) {
|
return this.users.find((user) => user.id === id);
|
||||||
existing.tiktokUnionId = input.tiktokUnionId ?? existing.tiktokUnionId;
|
|
||||||
existing.nickname = input.nickname ?? existing.nickname;
|
|
||||||
existing.avatarUrl = input.avatarUrl ?? existing.avatarUrl;
|
|
||||||
existing.updatedAt = now;
|
|
||||||
return { user: existing, isNewUser: false };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const user: UserRecord = {
|
async listUsers(pagination: PaginationInput): Promise<PaginatedResponse<UserRecord>> {
|
||||||
id: this.userSequence++,
|
if (this.hasDatabase()) {
|
||||||
tiktokOpenId: input.tiktokOpenId,
|
const page = Math.max(pagination.page, 1);
|
||||||
tiktokUnionId: input.tiktokUnionId,
|
const pageSize = Math.min(Math.max(pagination.pageSize, 1), 100);
|
||||||
nickname: input.nickname,
|
const repository = this.dataSource.getRepository(User);
|
||||||
avatarUrl: input.avatarUrl,
|
const [records, total] = await repository.findAndCount({
|
||||||
status: 'active',
|
order: { id: "DESC" },
|
||||||
createdAt: now,
|
skip: (page - 1) * pageSize,
|
||||||
updatedAt: now,
|
take: pageSize,
|
||||||
};
|
});
|
||||||
|
|
||||||
this.users.push(user);
|
return {
|
||||||
return { user, isNewUser: true };
|
list: records.map((user) => this.toRecord(user)),
|
||||||
}
|
pagination: { page, pageSize, total },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async findById(id: number) {
|
return this.paginate([...this.users].reverse(), pagination);
|
||||||
if (this.hasDatabase()) {
|
|
||||||
const user = await this.dataSource.getRepository(User).findOne({
|
|
||||||
where: { id },
|
|
||||||
});
|
|
||||||
return user ? this.toRecord(user) : undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.users.find((user) => user.id === id);
|
async getUserOrThrow(id: number) {
|
||||||
}
|
const user = await this.findById(id);
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundException("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
async listUsers(pagination: PaginationInput): Promise<PaginatedResponse<UserRecord>> {
|
return user;
|
||||||
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(User);
|
|
||||||
const [records, total] = await repository.findAndCount({
|
|
||||||
order: { id: 'DESC' },
|
|
||||||
skip: (page - 1) * pageSize,
|
|
||||||
take: pageSize,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
list: records.map((user) => this.toRecord(user)),
|
|
||||||
pagination: { page, pageSize, total },
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.paginate([...this.users].reverse(), pagination);
|
async updateStatus(id: number, status: "active" | "disabled") {
|
||||||
}
|
if (this.hasDatabase()) {
|
||||||
|
const repository = this.dataSource.getRepository(User);
|
||||||
|
const user = await repository.findOne({ where: { id } });
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundException("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
async getUserOrThrow(id: number) {
|
user.status = status;
|
||||||
const user = await this.findById(id);
|
return this.toRecord(await repository.save(user));
|
||||||
if (!user) {
|
}
|
||||||
throw new NotFoundException('User not found');
|
|
||||||
|
const user = await this.getUserOrThrow(id);
|
||||||
|
user.status = status;
|
||||||
|
user.updatedAt = new Date().toISOString();
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
private hasDatabase(): this is this & { dataSource: DataSource } {
|
||||||
}
|
return Boolean(this.dataSource?.isInitialized);
|
||||||
|
|
||||||
async updateStatus(id: number, status: 'active' | 'disabled') {
|
|
||||||
if (this.hasDatabase()) {
|
|
||||||
const repository = this.dataSource.getRepository(User);
|
|
||||||
const user = await repository.findOne({ where: { id } });
|
|
||||||
if (!user) {
|
|
||||||
throw new NotFoundException('User not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
user.status = status;
|
|
||||||
return this.toRecord(await repository.save(user));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.getUserOrThrow(id);
|
private toRecord(user: User): UserRecord {
|
||||||
user.status = status;
|
return {
|
||||||
user.updatedAt = new Date().toISOString();
|
id: Number(user.id),
|
||||||
return user;
|
tiktokOpenId: user.tiktokOpenId,
|
||||||
}
|
tiktokUnionId: user.tiktokUnionId,
|
||||||
|
nickname: user.nickname,
|
||||||
|
avatarUrl: user.avatarUrl,
|
||||||
|
status: user.status as "active" | "disabled",
|
||||||
|
createdAt: user.createdAt?.toISOString?.() ?? new Date().toISOString(),
|
||||||
|
updatedAt: user.updatedAt?.toISOString?.() ?? new Date().toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private hasDatabase(): this is this & { dataSource: DataSource } {
|
private paginate<T>(records: T[], input: PaginationInput): PaginatedResponse<T> {
|
||||||
return Boolean(this.dataSource?.isInitialized);
|
const page = Math.max(input.page, 1);
|
||||||
}
|
const pageSize = Math.min(Math.max(input.pageSize, 1), 100);
|
||||||
|
const start = (page - 1) * pageSize;
|
||||||
|
|
||||||
private toRecord(user: User): UserRecord {
|
return {
|
||||||
return {
|
list: records.slice(start, start + pageSize),
|
||||||
id: Number(user.id),
|
pagination: {
|
||||||
tiktokOpenId: user.tiktokOpenId,
|
page,
|
||||||
tiktokUnionId: user.tiktokUnionId,
|
pageSize,
|
||||||
nickname: user.nickname,
|
total: records.length,
|
||||||
avatarUrl: user.avatarUrl,
|
},
|
||||||
status: user.status as 'active' | 'disabled',
|
};
|
||||||
createdAt: user.createdAt?.toISOString?.() ?? new Date().toISOString(),
|
}
|
||||||
updatedAt: user.updatedAt?.toISOString?.() ?? new Date().toISOString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private paginate<T>(
|
|
||||||
records: T[],
|
|
||||||
input: PaginationInput,
|
|
||||||
): PaginatedResponse<T> {
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"target": "ES2023",
|
"target": "ES2023",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strictPropertyInitialization": false,
|
"strictPropertyInitialization": false,
|
||||||
"noImplicitAny": false
|
"noImplicitAny": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user