feat(dramas): add play count field for drama records

1. 新增plays字段到DramaRecord接口和swagger/openapi文档
2. 在剧集创建时默认设置plays为0
3. 新增getPlayCountsByDramaIds方法聚合剧集播放量
4. 在后台和前台剧集列表中添加播放量数据
5. 更新测试用例适配新增的plays字段
This commit is contained in:
2026-07-02 18:25:58 +08:00
parent 03d3c2f9cc
commit 55e20f9f16
6 changed files with 142 additions and 44 deletions

View File

@@ -8,6 +8,15 @@ import { UsersService } from '../src/modules/users/users.service';
function createRepository<T extends { id?: number }>() {
const records: T[] = [];
const matchesWhere = (item: T, where?: Partial<T>) => {
if (!where) {
return true;
}
return Object.entries(where).every(
([key, value]) => item[key as keyof T] === value,
);
};
return {
records,
create: jest.fn((input: Partial<T>) => input as T),
@@ -24,14 +33,18 @@ function createRepository<T extends { id?: number }>() {
return input;
}),
findOne: jest.fn(async ({ where }: { where: Partial<T> }) =>
records.find((item) =>
Object.entries(where).every(
([key, value]) => item[key as keyof T] === value,
),
) ?? null,
records.find((item) => matchesWhere(item, where)) ?? null,
),
find: jest.fn(async () => [...records]),
count: jest.fn(async () => records.length),
findAndCount: jest.fn(
async ({ where, skip = 0, take = records.length }: { where?: Partial<T>; skip?: number; take?: number }) => {
const filtered = records.filter((item) => matchesWhere(item, where));
return [filtered.slice(skip, skip + take), filtered.length];
},
),
count: jest.fn(async ({ where }: { where?: Partial<T> } = {}) =>
records.filter((item) => matchesWhere(item, where)).length,
),
};
}
@@ -182,4 +195,72 @@ describe('database-backed persistence', () => {
expect(episodeMetricRepository.save).toHaveBeenCalled();
expect(syncJobRepository.save).toHaveBeenCalled();
});
it('aggregates episode metric plays for database-backed drama lists', async () => {
const now = new Date('2026-07-02T00:00:00.000Z');
const dramaRepository = createRepository<any>();
const episodeRepository = createRepository<any>();
const queryBuilder = {
select: jest.fn().mockReturnThis(),
addSelect: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
groupBy: jest.fn().mockReturnThis(),
getRawMany: jest.fn(async () => [{ dramaId: '1', plays: '321' }]),
};
const episodeMetricRepository = {
createQueryBuilder: jest.fn(() => queryBuilder),
};
const dataSource = createDataSource({
Drama: dramaRepository,
Episode: episodeRepository,
EpisodeMetric: episodeMetricRepository,
});
const service = new DramasService(dataSource as never);
await dramaRepository.save({
id: 1,
title: 'DB Plays Drama',
coverUrl: 'https://cdn.example.com/db-plays.jpg',
type: 'action',
tags: [],
status: PublishStatus.Published,
sortOrder: 0,
isRecommended: false,
onlineVersion: 1,
createdAt: now,
updatedAt: now,
});
await episodeRepository.save({
id: 1,
dramaId: 1,
episodeNo: 1,
title: 'Episode 1',
coverUrl: 'https://cdn.example.com/ep1.jpg',
durationSeconds: 180,
isPaid: false,
freeType: 'free',
price: 0,
status: PublishStatus.Published,
publishStatus: PublishStatus.Published,
reviewStatus: 'approved',
createdAt: now,
updatedAt: now,
});
const adminList = await service.listAdminDramas({ page: 1, pageSize: 10 });
const appList = await service.listPublishedDramas({ page: 1, pageSize: 10 });
expect(adminList.list[0]).toMatchObject({
title: 'DB Plays Drama',
plays: 321,
});
expect(appList.list[0]).toMatchObject({
title: 'DB Plays Drama',
plays: 321,
});
expect(queryBuilder.where).toHaveBeenCalledWith(
'metric.dramaId IN (:...dramaIds)',
{ dramaIds: [1] },
);
});
});