chore: 初始化TikTok短剧后端项目基础架构
创建了完整的NestJS项目结构,包含配置文件、核心模块、工具类、拦截器、过滤器等基础代码,实现了健康检查、用户认证、管理员RBAC、剧集管理、订单支付、媒体管理等核心功能的基础框架,配置了ESLint、TypeScript、Jest等开发工具链。
This commit is contained in:
12
src/modules/media/dto/create-cover.dto.ts
Normal file
12
src/modules/media/dto/create-cover.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
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;
|
||||
}
|
||||
22
src/modules/media/dto/create-upload-job.dto.ts
Normal file
22
src/modules/media/dto/create-upload-job.dto.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsObject, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateUploadJobDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
jobId: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
byteplusVid?: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
status: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
rawResponse?: Record<string, unknown>;
|
||||
}
|
||||
34
src/modules/media/entities/media-upload-job.entity.ts
Normal file
34
src/modules/media/entities/media-upload-job.entity.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
@Entity('media_upload_jobs')
|
||||
export class MediaUploadJob {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column({ type: 'varchar', length: 128 })
|
||||
jobId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ type: 'varchar', length: 128, nullable: true })
|
||||
byteplusVid?: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 64 })
|
||||
status: string;
|
||||
|
||||
@Column({ type: 'json', nullable: true })
|
||||
rawResponse?: Record<string, unknown>;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
16
src/modules/media/interfaces/media-records.interface.ts
Normal file
16
src/modules/media/interfaces/media-records.interface.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface CoverRecord {
|
||||
id: number;
|
||||
fileName: string;
|
||||
url: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface MediaUploadJobRecord {
|
||||
id: number;
|
||||
jobId: string;
|
||||
byteplusVid?: string;
|
||||
status: string;
|
||||
rawResponse?: Record<string, unknown>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
40
src/modules/media/media.controller.ts
Normal file
40
src/modules/media/media.controller.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Body, Controller, Get, Post, Query, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
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 { CreateCoverDto } from './dto/create-cover.dto';
|
||||
import { CreateUploadJobDto } from './dto/create-upload-job.dto';
|
||||
import { MediaService } from './media.service';
|
||||
|
||||
@ApiTags('Media')
|
||||
@Controller('/api/admin/v1/media')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
|
||||
export class MediaController {
|
||||
constructor(private readonly mediaService: MediaService) {}
|
||||
|
||||
@Post('/covers')
|
||||
@RequirePermissions('media:create')
|
||||
createCover(@Body() dto: CreateCoverDto) {
|
||||
return this.mediaService.createCover(dto);
|
||||
}
|
||||
|
||||
@Post('/upload-jobs')
|
||||
@RequirePermissions('media:create')
|
||||
createUploadJob(@Body() dto: CreateUploadJobDto) {
|
||||
return this.mediaService.createUploadJob(dto);
|
||||
}
|
||||
|
||||
@Get('/upload-jobs')
|
||||
@RequirePermissions('media:read')
|
||||
listUploadJobs(
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
) {
|
||||
return this.mediaService.listUploadJobs({
|
||||
page: Number(page) || 1,
|
||||
pageSize: Number(pageSize) || 20,
|
||||
});
|
||||
}
|
||||
}
|
||||
12
src/modules/media/media.module.ts
Normal file
12
src/modules/media/media.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AdminModule } from '../admin/admin.module';
|
||||
import { MediaController } from './media.controller';
|
||||
import { MediaService } from './media.service';
|
||||
|
||||
@Module({
|
||||
imports: [AdminModule],
|
||||
controllers: [MediaController],
|
||||
providers: [MediaService],
|
||||
exports: [MediaService],
|
||||
})
|
||||
export class MediaModule {}
|
||||
65
src/modules/media/media.service.ts
Normal file
65
src/modules/media/media.service.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PaginatedResponse } from '../../common/dto/paginated-response.dto';
|
||||
import { CreateCoverDto } from './dto/create-cover.dto';
|
||||
import { CreateUploadJobDto } from './dto/create-upload-job.dto';
|
||||
import {
|
||||
CoverRecord,
|
||||
MediaUploadJobRecord,
|
||||
} from './interfaces/media-records.interface';
|
||||
|
||||
interface PaginationInput {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MediaService {
|
||||
private coverSequence = 1;
|
||||
private uploadJobSequence = 1;
|
||||
private readonly covers: CoverRecord[] = [];
|
||||
private readonly uploadJobs: MediaUploadJobRecord[] = [];
|
||||
|
||||
createCover(dto: CreateCoverDto): CoverRecord {
|
||||
const cover: CoverRecord = {
|
||||
id: this.coverSequence++,
|
||||
fileName: dto.fileName,
|
||||
url: dto.url,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
this.covers.push(cover);
|
||||
return cover;
|
||||
}
|
||||
|
||||
createUploadJob(dto: CreateUploadJobDto): MediaUploadJobRecord {
|
||||
const now = new Date().toISOString();
|
||||
const job: MediaUploadJobRecord = {
|
||||
id: this.uploadJobSequence++,
|
||||
jobId: dto.jobId,
|
||||
byteplusVid: dto.byteplusVid,
|
||||
status: dto.status,
|
||||
rawResponse: dto.rawResponse,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
this.uploadJobs.push(job);
|
||||
return job;
|
||||
}
|
||||
|
||||
listUploadJobs(
|
||||
input: PaginationInput,
|
||||
): PaginatedResponse<MediaUploadJobRecord> {
|
||||
const page = Math.max(input.page, 1);
|
||||
const pageSize = Math.min(Math.max(input.pageSize, 1), 100);
|
||||
const start = (page - 1) * pageSize;
|
||||
const records = [...this.uploadJobs].reverse();
|
||||
|
||||
return {
|
||||
list: records.slice(start, start + pageSize),
|
||||
pagination: {
|
||||
page,
|
||||
pageSize,
|
||||
total: records.length,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user