feat: 新增剧集封面、集数管理与上传优化功能

1. 新增Cover实体类并注册到数据库模块
2. 为剧集添加总集数字段及自动创建集数槽功能
3. 新增管理员更新剧集集数接口
4. 优化发布剧集列表:支持标题过滤与默认分页大小10
5. 扩展MediaUploadJob实体字段,完善媒体上传流程
6. 重构订单与媒体服务,新增数据库持久化支持
7. 新增剧集标题过滤的端到端测试用例
This commit is contained in:
2026-07-02 15:46:29 +08:00
parent f342312b92
commit 0eeeea0c4d
9 changed files with 669 additions and 60 deletions

View File

@@ -6,7 +6,7 @@ import {
UnprocessableEntityException,
} from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { DataSource, Like } from 'typeorm';
import { PaginatedResponse } from '../../common/dto/paginated-response.dto';
import { ConfigureEpisodesDto } from './dto/configure-episodes.dto';
import { CreateDramaDto } from './dto/create-drama.dto';
@@ -26,6 +26,10 @@ interface PaginationInput {
pageSize: number;
}
interface PublishedDramaListInput extends PaginationInput {
title?: string;
}
@Injectable()
export class DramasService {
private dramaSequence = 1;
@@ -40,6 +44,7 @@ export class DramasService {
) {}
async createDrama(dto: CreateDramaDto): Promise<DramaRecord> {
const { totalEpisodes } = dto;
if (this.hasDatabase()) {
const repository = this.dataSource.getRepository(Drama);
const entity = repository.create({
@@ -61,6 +66,12 @@ export class DramasService {
onlineVersion: dto.onlineVersion ?? 1,
});
const saved = await repository.save(entity);
if (totalEpisodes) {
await this.configureEpisodes(Number(saved.id), {
episodeCount: totalEpisodes,
});
return this.getDramaOrThrow(Number(saved.id));
}
return this.toDramaRecord(saved, 0, 0);
}
@@ -90,6 +101,10 @@ export class DramasService {
};
this.dramas.push(drama);
if (totalEpisodes) {
await this.configureEpisodes(drama.id, { episodeCount: totalEpisodes });
return this.getDramaOrThrow(drama.id);
}
return drama;
}
@@ -137,14 +152,23 @@ export class DramasService {
}
async updateDrama(id: number, dto: UpdateDramaDto): Promise<DramaRecord> {
const { totalEpisodes, ...dramaUpdates } = dto;
if (this.hasDatabase()) {
const repository = this.dataSource.getRepository(Drama);
const drama = await repository.findOne({ where: { id } });
if (!drama) {
throw new NotFoundException('Drama not found');
}
repository.merge(drama, { ...dto, tags: dto.tags ?? drama.tags });
repository.merge(drama, {
...dramaUpdates,
tags: dramaUpdates.tags ?? drama.tags,
});
const saved = await repository.save(drama);
if (totalEpisodes) {
await this.configureEpisodes(Number(saved.id), {
episodeCount: totalEpisodes,
});
}
return this.toDramaRecordWithCounts(saved);
}
@@ -154,10 +178,13 @@ export class DramasService {
}
Object.assign(drama, {
...dto,
tags: dto.tags ?? drama.tags,
...dramaUpdates,
tags: dramaUpdates.tags ?? drama.tags,
updatedAt: new Date().toISOString(),
});
if (totalEpisodes) {
await this.configureEpisodes(drama.id, { episodeCount: totalEpisodes });
}
return this.withEpisodeCounts(drama);
}
@@ -474,6 +501,28 @@ export class DramasService {
return episode;
}
async updateDramaEpisode(
dramaId: number,
episodeId: number,
dto: UpdateEpisodeDto,
): Promise<EpisodeRecord> {
if (this.hasDatabase()) {
const episode = await this.dataSource.getRepository(Episode).findOne({
where: { id: episodeId, dramaId },
});
if (!episode) {
throw new NotFoundException('Episode not found');
}
} else {
const episode = await this.getMemEpisodeOrThrow(episodeId);
if (episode.dramaId !== dramaId) {
throw new NotFoundException('Episode not found');
}
}
return this.updateEpisode(episodeId, { ...dto, dramaId });
}
async attachEpisodeMedia(input: {
episodeId: number;
byteplusVid: string;
@@ -546,14 +595,18 @@ export class DramasService {
}
async listPublishedDramas(
pagination: PaginationInput,
input: PublishedDramaListInput,
): Promise<PaginatedResponse<DramaRecord>> {
const title = input.title?.trim();
if (this.hasDatabase()) {
const { page, pageSize, skip, take } = this.resolvePagination(pagination);
const { page, pageSize, skip, take } = this.resolvePagination(input);
const [records, total] = await this.dataSource
.getRepository(Drama)
.findAndCount({
where: { status: PublishStatus.Published },
where: {
status: PublishStatus.Published,
...(title ? { title: Like(`%${title}%`) } : {}),
},
order: { sortOrder: 'DESC', id: 'DESC' },
skip,
take,
@@ -568,9 +621,10 @@ export class DramasService {
this.sortDramas(
this.dramas
.filter((item) => item.status === PublishStatus.Published)
.filter((item) => !title || item.title.includes(title))
.map((drama) => this.withEpisodeCounts(drama)),
),
pagination,
input,
);
}