mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-26 18:55:12 +02:00
feat: add metadata to libraryItems, server side library sorting and filtering
This commit is contained in:
@@ -21,7 +21,12 @@ import {
|
||||
SuccessResponseDto,
|
||||
} from 'src/common/common.dto';
|
||||
import { LibraryItemDto } from './library.dto';
|
||||
import { LibraryService } from './library.service';
|
||||
import {
|
||||
LibraryService,
|
||||
LibrarySortBy,
|
||||
MyListFilter as MyListFilter,
|
||||
SortByDirection,
|
||||
} from './library.service';
|
||||
|
||||
@ApiTags('users')
|
||||
@Controller('users/:userId/library')
|
||||
@@ -29,25 +34,32 @@ import { LibraryService } from './library.service';
|
||||
export class LibraryController {
|
||||
constructor(private libraryService: LibraryService) {}
|
||||
|
||||
@Get()
|
||||
@Get('my-list')
|
||||
@ApiQuery({ name: 'filter', enum: MyListFilter, required: false })
|
||||
@ApiQuery({ name: 'sortBy', enum: LibrarySortBy, required: false })
|
||||
@ApiQuery({ name: 'direction', enum: SortByDirection, required: false })
|
||||
@PaginatedApiOkResponse(LibraryItemDto)
|
||||
async getLibraryItems(
|
||||
@GetPaginationParams() pagination: PaginationParamsDto,
|
||||
@Param('userId') userId: string,
|
||||
@Query('filter', new ParseEnumPipe(MyListFilter, { optional: true }))
|
||||
filter?: MyListFilter,
|
||||
@Query('sortBy', new ParseEnumPipe(LibrarySortBy, { optional: true }))
|
||||
sortBy?: LibrarySortBy,
|
||||
@Query('direction', new ParseEnumPipe(SortByDirection, { optional: true }))
|
||||
direction?: SortByDirection,
|
||||
): Promise<PaginatedResponseDto<LibraryItemDto>> {
|
||||
// const user = await this.userService.findOne(userId);
|
||||
|
||||
const items = await this.libraryService.getLibraryItemDtos(
|
||||
const items = await this.libraryService.getMyListDtos({
|
||||
userId,
|
||||
pagination,
|
||||
);
|
||||
filter,
|
||||
sortBy,
|
||||
direction,
|
||||
});
|
||||
|
||||
return {
|
||||
items,
|
||||
itemsPerPage: pagination.itemsPerPage,
|
||||
page: pagination.page,
|
||||
total: items.length,
|
||||
};
|
||||
return items;
|
||||
}
|
||||
|
||||
@Put('tmdb/:tmdbId')
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from 'typeorm';
|
||||
import { PlayState } from '../play-state/play-state.entity';
|
||||
import { PlayStateDto } from '../play-state/play-state.dto';
|
||||
import { MovieMetadata, SeriesMetadata } from 'src/metadata/metadata.entity';
|
||||
|
||||
@Entity()
|
||||
@Unique(['tmdbId', 'userId'])
|
||||
@@ -31,6 +32,22 @@ export class LibraryItem {
|
||||
@Column()
|
||||
mediaType: MediaType;
|
||||
|
||||
@ApiProperty({ required: false, type: MovieMetadata })
|
||||
@ManyToOne(() => MovieMetadata, {
|
||||
createForeignKeyConstraints: false,
|
||||
nullable: true,
|
||||
})
|
||||
@JoinColumn({ name: 'tmdbId', referencedColumnName: 'tmdbId' })
|
||||
movieMetadata?: MovieMetadata;
|
||||
|
||||
@ApiProperty({ required: false, type: SeriesMetadata })
|
||||
@ManyToOne(() => SeriesMetadata, {
|
||||
createForeignKeyConstraints: false,
|
||||
nullable: true,
|
||||
})
|
||||
@JoinColumn({ name: 'tmdbId', referencedColumnName: 'tmdbId' })
|
||||
seriesMetadata?: SeriesMetadata;
|
||||
|
||||
@ApiProperty({ required: true, type: 'string' })
|
||||
@PrimaryColumn()
|
||||
userId: string;
|
||||
|
||||
@@ -1,11 +1,39 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { MediaType, PaginationParamsDto } from 'src/common/common.dto';
|
||||
import {
|
||||
MediaType,
|
||||
PaginatedResponseDto,
|
||||
PaginationParamsDto,
|
||||
} from 'src/common/common.dto';
|
||||
import { MetadataService } from 'src/metadata/metadata.service';
|
||||
import { Repository } from 'typeorm';
|
||||
import { IsNull, Not, Repository } from 'typeorm';
|
||||
import { LibraryItemDto } from './library.dto';
|
||||
import { LibraryItem } from './library.entity';
|
||||
import { USER_LIBRARY_REPOSITORY } from './library.providers';
|
||||
|
||||
export enum SortByDirection {
|
||||
Asc = 'asc',
|
||||
Desc = 'desc',
|
||||
}
|
||||
|
||||
export enum LibrarySortBy {
|
||||
DateAdded = 'dateAdded',
|
||||
Name = 'name',
|
||||
FirstReleaseDate = 'firstReleaseDate',
|
||||
LastReleaseDate = 'lastReleaseDate',
|
||||
}
|
||||
|
||||
export enum MyListFilter {
|
||||
Movie = 'movie',
|
||||
Series = 'series',
|
||||
}
|
||||
|
||||
export enum CatalogueFilter {
|
||||
All = 'all',
|
||||
Movies = 'movies',
|
||||
Series = 'series',
|
||||
Unavailable = 'unavailable',
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class LibraryService {
|
||||
constructor(
|
||||
@@ -14,14 +42,13 @@ export class LibraryService {
|
||||
private readonly metadataService: MetadataService,
|
||||
) {}
|
||||
|
||||
async getLibraryItemDtos(
|
||||
userId: string,
|
||||
pagination: PaginationParamsDto,
|
||||
): Promise<LibraryItemDto[]> {
|
||||
const items = await this.getLibraryItems(userId, pagination);
|
||||
async getMyListDtos(
|
||||
...args: Parameters<LibraryService['getMyList']>
|
||||
): Promise<PaginatedResponseDto<LibraryItemDto>> {
|
||||
const paginatedItems = await this.getMyList(...args);
|
||||
|
||||
return Promise.all(
|
||||
items.map(async (item) => {
|
||||
const items = await Promise.all(
|
||||
paginatedItems.items.map(async (item) => {
|
||||
const seriesMetadata =
|
||||
item.mediaType === MediaType.Series
|
||||
? await this.metadataService.getSeriesByTmdbId(item.tmdbId)
|
||||
@@ -38,21 +65,95 @@ export class LibraryService {
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return { ...paginatedItems, items };
|
||||
}
|
||||
|
||||
private async getLibraryItems(
|
||||
userId: string,
|
||||
pagination: PaginationParamsDto,
|
||||
): Promise<LibraryItem[]> {
|
||||
return this.libraryRepository.find({
|
||||
where: { userId },
|
||||
/** TODO: decouple librayItem and movie/seriesItem */
|
||||
private async getMyList(options: {
|
||||
userId: string;
|
||||
pagination: PaginationParamsDto;
|
||||
filter?: MyListFilter;
|
||||
sortBy?: LibrarySortBy;
|
||||
direction?: SortByDirection;
|
||||
}): Promise<PaginatedResponseDto<LibraryItem>> {
|
||||
const {
|
||||
userId,
|
||||
pagination,
|
||||
filter,
|
||||
sortBy,
|
||||
direction = SortByDirection.Desc,
|
||||
} = options;
|
||||
|
||||
const directon = direction === SortByDirection.Asc ? 'ASC' : 'DESC';
|
||||
|
||||
const order = {
|
||||
[LibrarySortBy.DateAdded]: { createdAt: directon } as const,
|
||||
[LibrarySortBy.Name]: {
|
||||
seriesMetadata: {
|
||||
name: directon,
|
||||
},
|
||||
movieMetadata: {
|
||||
name: directon,
|
||||
},
|
||||
} as const,
|
||||
[LibrarySortBy.FirstReleaseDate]: {
|
||||
movieMetadata: {
|
||||
releaseDate: directon,
|
||||
},
|
||||
seriesMetadata: {
|
||||
firstReleaseDate: directon,
|
||||
},
|
||||
} as const,
|
||||
[LibrarySortBy.LastReleaseDate]: {
|
||||
movieMetadata: {
|
||||
releaseDate: directon,
|
||||
},
|
||||
seriesMetadata: {
|
||||
lastReleaseDate: directon,
|
||||
},
|
||||
} as const,
|
||||
}[sortBy];
|
||||
|
||||
const mediaType = filter
|
||||
? filter === MyListFilter.Movie
|
||||
? MediaType.Movie
|
||||
: MediaType.Series
|
||||
: undefined;
|
||||
|
||||
const [items, total] = await this.libraryRepository.findAndCount({
|
||||
relations: {
|
||||
playStates: true,
|
||||
seriesMetadata: true,
|
||||
movieMetadata: true,
|
||||
},
|
||||
// TODO: Implement pagination
|
||||
// take: pagination.itemsPerPage,
|
||||
// skip: pagination.itemsPerPage * (pagination.page - 1),
|
||||
select: {
|
||||
seriesMetadata: {
|
||||
firstReleaseDate: true,
|
||||
lastReleaseDate: true,
|
||||
name: true,
|
||||
},
|
||||
movieMetadata: {
|
||||
releaseDate: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
|
||||
where: {
|
||||
userId,
|
||||
...(mediaType ? { mediaType } : {}),
|
||||
},
|
||||
order,
|
||||
take: pagination.itemsPerPage,
|
||||
skip: pagination.itemsPerPage * (pagination.page - 1),
|
||||
});
|
||||
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
itemsPerPage: pagination.itemsPerPage,
|
||||
page: pagination.page,
|
||||
};
|
||||
}
|
||||
|
||||
async findByTmdbId(
|
||||
|
||||
Reference in New Issue
Block a user