feat: library page sections, sorting, view options

fix: tmdb library items not being cached
This commit is contained in:
Aleksi Lassila
2025-02-15 05:03:20 +02:00
parent 8f83a34b35
commit ef4fa1d13a
21 changed files with 575 additions and 178 deletions

View File

@@ -37,7 +37,7 @@ export class LibraryController {
): Promise<PaginatedResponseDto<LibraryItemDto>> {
// const user = await this.userService.findOne(userId);
const items = await this.libraryService.getLibraryItemsWithMetadata(
const items = await this.libraryService.getLibraryItemDtos(
userId,
pagination,
);

View File

@@ -1,22 +1,20 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, PickType } from '@nestjs/swagger';
import { MovieDto } from 'src/metadata/metadata.dto';
import { PlayStateDto } from '../play-state/play-state.dto';
import { MediaType } from 'src/common/common.dto';
import { Series } from 'src/metadata/metadata.entity';
import { LibraryItem } from './library.entity';
export class LibraryItemDto {
@ApiProperty()
tmdbId: string;
@ApiProperty({ enum: MediaType })
mediaType: MediaType;
@ApiProperty({ type: [PlayStateDto], required: false })
playStates?: PlayStateDto[];
export class LibraryItemDto extends PickType(LibraryItem, [
'tmdbId',
'mediaType',
'playStates',
'createdAt',
]) {
@ApiProperty({ type: MovieDto, required: false })
movieMetadata?: MovieDto;
@ApiProperty({ type: Series, required: false })
seriesMetadata?: Series;
@ApiProperty({ required: false })
watched?: boolean;
}

View File

@@ -3,6 +3,7 @@ import { MediaType } from 'src/common/common.dto';
import { User } from 'src/users/user.entity';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
@@ -10,8 +11,10 @@ import {
PrimaryColumn,
PrimaryGeneratedColumn,
Unique,
UpdateDateColumn,
} from 'typeorm';
import { PlayState } from '../play-state/play-state.entity';
import { PlayStateDto } from '../play-state/play-state.dto';
@Entity()
@Unique(['tmdbId', 'userId'])
@@ -20,7 +23,7 @@ export class LibraryItem {
@PrimaryGeneratedColumn('uuid')
id: string;
@ApiProperty({ required: true, type: 'number' })
@ApiProperty({ required: true })
@Column({ unique: true })
tmdbId: string;
@@ -37,6 +40,16 @@ export class LibraryItem {
@JoinColumn({ name: 'userId' })
user: User;
@ApiProperty({ type: [PlayStateDto], required: false })
@OneToMany(() => PlayState, (playState) => playState.libraryItem)
playStates?: PlayState[];
/** @deprecated */
@ApiProperty({ type: 'string' })
@UpdateDateColumn({ default: () => 'CURRENT_TIMESTAMP' })
updatedAt: Date;
@ApiProperty({ type: 'string' })
@CreateDateColumn({ default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
}

View File

@@ -14,7 +14,7 @@ export class LibraryService {
private readonly metadataService: MetadataService,
) {}
async getLibraryItemsWithMetadata(
async getLibraryItemDtos(
userId: string,
pagination: PaginationParamsDto,
): Promise<LibraryItemDto[]> {
@@ -22,16 +22,37 @@ export class LibraryService {
return Promise.all(
items.map(async (item) => {
const seriesMetadata =
item.mediaType === MediaType.Series
? await this.metadataService.getSeriesByTmdbId(item.tmdbId)
: undefined;
let watched = false;
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,
seriesMetadata:
item.mediaType === MediaType.Series
? await this.metadataService.getSeriesByTmdbId(item.tmdbId)
: undefined,
seriesMetadata,
};
}),
);