refactor: library items & metadata for performance

This commit is contained in:
Aleksi Lassila
2025-02-21 18:02:18 +02:00
parent b598245cb0
commit 80b4f8c01a
15 changed files with 363 additions and 133 deletions

View File

@@ -20,7 +20,7 @@ import {
PaginationParamsDto,
SuccessResponseDto,
} from 'src/common/common.dto';
import { LibraryItemDto } from './library.dto';
import { LibraryItemDto, LibraryItemDto2 } from './library.dto';
import { LibraryService } from './library.service';
@ApiTags('users')
@@ -30,11 +30,11 @@ export class LibraryController {
constructor(private libraryService: LibraryService) {}
@Get()
@PaginatedApiOkResponse(LibraryItemDto)
@PaginatedApiOkResponse(LibraryItemDto2)
async getLibraryItems(
@GetPaginationParams() pagination: PaginationParamsDto,
@Param('userId') userId: string,
): Promise<PaginatedResponseDto<LibraryItemDto>> {
): Promise<PaginatedResponseDto<LibraryItemDto2>> {
// const user = await this.userService.findOne(userId);
const items = await this.libraryService.getLibraryItemDtos(

View File

@@ -1,7 +1,13 @@
import { ApiProperty, PickType } from '@nestjs/swagger';
import { MovieDto } from 'src/metadata/metadata.dto';
import { Series } from 'src/metadata/metadata.entity';
import { MovieMetadata, SeriesMetadata } from 'src/metadata/metadata.entity';
import { LibraryItem } from './library.entity';
import {
TmdbMovie,
TmdbMovieFull,
TmdbSeries,
} from 'src/metadata/tmdb/tmdb.dto';
import { MediaType } from 'src/common/common.dto';
export class LibraryItemDto extends PickType(LibraryItem, [
'tmdbId',
@@ -12,9 +18,147 @@ export class LibraryItemDto extends PickType(LibraryItem, [
@ApiProperty({ type: MovieDto, required: false })
movieMetadata?: MovieDto;
@ApiProperty({ type: Series, required: false })
seriesMetadata?: Series;
@ApiProperty({ type: SeriesMetadata, required: false })
seriesMetadata?: SeriesMetadata;
@ApiProperty({ required: false })
watched?: boolean;
}
class NextEpisodeToAir {
@ApiProperty({ required: false })
air_date?: string;
}
class Season {
@ApiProperty({ required: false })
air_date?: string;
@ApiProperty({ required: false })
episode_count?: number;
@ApiProperty({ required: false })
id?: number;
@ApiProperty({ required: false })
name?: string;
@ApiProperty({ required: false })
overview?: string;
@ApiProperty({ required: false })
poster_path?: string;
@ApiProperty({ required: false })
season_number?: number;
@ApiProperty({ required: false })
vote_average?: number;
}
export class LibraryItemDto2
extends PickType(LibraryItem, [
'tmdbId',
'mediaType',
'playStates',
'createdAt',
])
implements TmdbMovie, TmdbSeries
{
// TmdbMovie & TmdbSeries
@ApiProperty({ required: false })
id?: number;
@ApiProperty({ required: false })
poster_path?: string;
@ApiProperty({ required: false })
vote_average?: number;
// TmdbMovie only
@ApiProperty({ required: false })
title?: string;
@ApiProperty({ required: false })
release_date?: string;
@ApiProperty({ required: false })
runtime?: number;
// TmdbSeries only
@ApiProperty({ required: false })
name?: string;
@ApiProperty({ required: false })
first_air_date?: string;
@ApiProperty({ required: false })
last_air_date?: string;
@ApiProperty({ required: false, type: NextEpisodeToAir })
next_episode_to_air?: NextEpisodeToAir;
@ApiProperty({ required: false, isArray: true, type: Season })
seasons?: Season[];
// Library Item
@ApiProperty({ required: false })
watched?: boolean;
static create(options: {
libraryItem: LibraryItem;
movieMetadata?: MovieMetadata;
seriesMetadata?: SeriesMetadata;
}): LibraryItemDto2 {
const { libraryItem, movieMetadata, seriesMetadata } = options;
if (!movieMetadata && !seriesMetadata) {
throw new Error(
'At least one of movieMetadata or seriesMetadata must be provided',
);
}
let watched = false;
if (libraryItem.mediaType === MediaType.Movie) {
watched = libraryItem.playStates?.some((state) => state.watched) ?? false;
} else if (
libraryItem.mediaType === MediaType.Series &&
seriesMetadata?.tmdbSeries?.last_episode_to_air
) {
const { season_number: season, episode_number: episode } =
seriesMetadata?.tmdbSeries.last_episode_to_air;
watched =
libraryItem.playStates?.some(
(state) =>
state.season === season &&
state.episode === episode &&
state.watched,
) ?? false;
}
return {
...libraryItem,
watched,
id: movieMetadata?.tmdbMovie.id ?? seriesMetadata?.tmdbSeries.id,
poster_path:
movieMetadata?.tmdbMovie.poster_path ??
seriesMetadata?.tmdbSeries.poster_path,
vote_average:
movieMetadata?.tmdbMovie.vote_average ??
seriesMetadata?.tmdbSeries.vote_average,
title: movieMetadata?.tmdbMovie.title,
release_date: movieMetadata?.tmdbMovie.release_date,
runtime: movieMetadata?.tmdbMovie.runtime,
name: seriesMetadata?.tmdbSeries.name,
first_air_date: seriesMetadata?.tmdbSeries.first_air_date,
last_air_date: seriesMetadata?.tmdbSeries.last_air_date,
next_episode_to_air: seriesMetadata?.tmdbSeries.next_episode_to_air,
seasons: seriesMetadata?.tmdbSeries.seasons,
};
}
}

View File

@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { LibraryItem } from './library.entity';
import { MediaType, PaginationParamsDto } from 'src/common/common.dto';
import { LibraryItemDto } from './library.dto';
import { LibraryItemDto, LibraryItemDto2 } from './library.dto';
import { MetadataService } from 'src/metadata/metadata.service';
import { USER_LIBRARY_REPOSITORY } from './library.providers';
@@ -17,7 +17,7 @@ export class LibraryService {
async getLibraryItemDtos(
userId: string,
pagination: PaginationParamsDto,
): Promise<LibraryItemDto[]> {
): Promise<LibraryItemDto2[]> {
const items = await this.getLibraryItems(userId, pagination);
return Promise.all(
@@ -26,34 +26,16 @@ export class LibraryService {
item.mediaType === MediaType.Series
? await this.metadataService.getSeriesByTmdbId(item.tmdbId)
: undefined;
let watched = false;
const movieMetadata =
item.mediaType === MediaType.Movie
? await this.metadataService.getMovieByTmdbId(item.tmdbId)
: undefined;
if (item.mediaType === MediaType.Movie) {
watched = item.playStates?.some((state) => state.watched) ?? false;
} else if (
item.mediaType === MediaType.Series &&
seriesMetadata.tmdbSeries?.last_episode_to_air
) {
const { season_number: season, episode_number: episode } =
seriesMetadata.tmdbSeries.last_episode_to_air;
watched =
item.playStates?.some(
(state) =>
state.season === season &&
state.episode === episode &&
state.watched,
) ?? false;
}
return {
...item,
watched,
movieMetadata:
item.mediaType === MediaType.Movie
? await this.metadataService.getMovieByTmdbId(item.tmdbId)
: undefined,
return LibraryItemDto2.create({
libraryItem: item,
seriesMetadata,
};
movieMetadata,
});
}),
);
}