feat: mark as watched, episode page uses userdata, progress etc

This commit is contained in:
Aleksi Lassila
2025-02-02 16:15:35 +02:00
parent 27467a28b2
commit 1478d507dc
24 changed files with 959 additions and 562 deletions

View File

@@ -22,19 +22,18 @@ export class PlayStateController {
constructor(private playStateService: PlayStateService) {}
@Put('movie/tmdb/:tmdbId')
@ApiQuery({ name: 'mediaType', enum: MediaType, required: false })
// @ApiQuery({ name: 'mediaType', enum: MediaType, required: false })
async updateMoviePlayStateByTmdbId(
@Param('userId') userId: string,
@Param('tmdbId') tmdbId: string,
@Body() playState: UpdatePlayStateDto,
@Query('mediaType', new ParseEnumPipe(MediaType, { optional: true }))
mediaType?: MediaType,
// @Query('mediaType', new ParseEnumPipe(MediaType, { optional: true }))
// mediaType?: MediaType,
) {
return this.playStateService.updateOrCreateMoviePlayState(
userId,
tmdbId,
playState,
mediaType,
);
}

View File

@@ -1,4 +1,4 @@
import { OmitType } from '@nestjs/swagger';
import { OmitType, PartialType } from '@nestjs/swagger';
import { PlayState } from './play-state.entity';
export class PlayStateDto extends PlayState {}
@@ -21,12 +21,6 @@ export class PlayStateDto extends PlayState {}
// }
// }
export class UpdatePlayStateDto extends OmitType(PlayState, [
'id',
'tmdbId',
'episode',
'season',
'user',
'userId',
'lastPlayedAt',
]) {}
export class UpdatePlayStateDto extends PartialType(
OmitType(PlayStateDto, ['userId']),
) {}

View File

@@ -17,16 +17,16 @@ import { MediaType } from 'src/common/common.dto';
@Entity()
@Unique(['tmdbId', 'userId', 'season', 'episode'])
export class PlayState {
@ApiProperty({ required: false, type: 'string' })
@ApiProperty({ type: 'string' })
@PrimaryGeneratedColumn('uuid')
id: string;
@ApiProperty({ required: true, type: 'number' })
@Column({ unique: true })
@Column()
tmdbId: string;
@ApiProperty({ required: false, enum: MediaType })
@Column({ nullable: true })
@ApiProperty({ enum: MediaType })
@Column()
mediaType: MediaType;
@ApiProperty({ required: true, type: 'string' })
@@ -47,7 +47,6 @@ export class PlayState {
episode: number = 0;
@ApiProperty({
required: false,
type: 'boolean',
default: false,
description: 'Whether the user has watched this media',
@@ -56,7 +55,6 @@ export class PlayState {
watched: boolean = false;
@ApiProperty({
required: false,
default: false,
example: 0.5,
description: 'A number between 0 and 1',
@@ -67,7 +65,6 @@ export class PlayState {
@ApiProperty({
type: 'string',
description: 'Last time the user played this media',
required: false,
})
@UpdateDateColumn()
lastPlayedAt: Date;

View File

@@ -18,12 +18,12 @@ export class PlayStateService {
});
}
async findShowPlayState(
async findSeriesPlayStates(
userId: string,
tmdbId: string,
season?: number,
episode?: number,
): Promise<PlayState | undefined> {
): Promise<PlayState[]> {
const playStates =
(await this.playStateRepository.find({
where: {
@@ -42,14 +42,13 @@ export class PlayStateService {
return a.episode - b.episode;
});
return playStates[0];
return playStates;
}
async updateOrCreateMoviePlayState(
userId: string,
tmdbId: string,
playState: UpdatePlayStateDto,
mediaType?: MediaType,
) {
let state = await this.findMoviePlayState(userId, tmdbId);
@@ -57,9 +56,7 @@ export class PlayStateService {
state = this.playStateRepository.create();
state.userId = userId;
state.tmdbId = tmdbId;
if (mediaType) {
state.mediaType = mediaType;
}
state.mediaType = MediaType.Movie;
}
state.progress = playState.progress;
@@ -75,7 +72,16 @@ export class PlayStateService {
episode: number,
playState: UpdatePlayStateDto,
) {
let state = await this.findShowPlayState(userId, tmdbId, season, episode);
let state = await this.findSeriesPlayStates(
userId,
tmdbId,
season,
episode,
).then((states) =>
states.find(
(state) => state.season === season && state.episode === episode,
),
);
if (!state) {
state = this.playStateRepository.create();
@@ -83,6 +89,7 @@ export class PlayStateService {
state.tmdbId = tmdbId;
state.season = season;
state.episode = episode;
state.mediaType = MediaType.Episode;
}
state.progress = playState.progress;
@@ -102,7 +109,12 @@ export class PlayStateService {
season: number,
episode: number,
) {
const state = await this.findShowPlayState(userId, tmdbId, season, episode);
const state = await this.findSeriesPlayStates(
userId,
tmdbId,
season,
episode,
);
return await this.playStateRepository.remove(state);
}
}

View File

@@ -58,7 +58,7 @@ export class UpdateUserDto extends PartialType(
export class SignInDto extends PickType(User, ['name', 'password'] as const) {}
export class MediaUserDataDto {
export class MovieUserDataDto {
@ApiProperty()
tmdbId: string;
@@ -68,3 +68,14 @@ export class MediaUserDataDto {
@ApiProperty({ type: PlayStateDto, required: false })
playState?: PlayStateDto;
}
export class SeriesUserDataDto {
@ApiProperty()
tmdbId: string;
@ApiProperty()
inLibrary: boolean;
@ApiProperty({ type: [PlayStateDto] })
playStates: PlayStateDto[];
}

View File

@@ -22,7 +22,8 @@ import {
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import {
CreateUserDto,
MediaUserDataDto,
MovieUserDataDto,
SeriesUserDataDto,
UpdateUserDto,
UserDto,
} from './user.dto';
@@ -167,12 +168,12 @@ export class UsersController {
@Get(':userId/user-data/movie/tmdb/:tmdbId')
@ApiOkResponse({
description: 'User movie data found',
type: MediaUserDataDto,
type: MovieUserDataDto,
})
async getUserMovieData(
@Param('userId') userId: string,
@Param('tmdbId') tmdbId: string,
): Promise<MediaUserDataDto> {
): Promise<MovieUserDataDto> {
const libraryItem = await this.libraryService.findByTmdbId(userId, tmdbId);
const playState = await this.playStateService.findMoviePlayState(
userId,
@@ -187,17 +188,17 @@ export class UsersController {
}
@UseGuards(UserAccessControl)
@Get(':userId/user-data/show/tmdb/:tmdbId')
@Get(':userId/user-data/series/tmdb/:tmdbId')
@ApiOkResponse({
description: 'User show data found',
type: MediaUserDataDto,
description: 'User series data found',
type: SeriesUserDataDto,
})
async getShowUserData(
async getSeriesUserData(
@Param('userId') userId: string,
@Param('tmdbId') tmdbId: string,
): Promise<MediaUserDataDto> {
): Promise<SeriesUserDataDto> {
const libraryItem = await this.libraryService.findByTmdbId(userId, tmdbId);
const playState = await this.playStateService.findShowPlayState(
const playState = await this.playStateService.findSeriesPlayStates(
userId,
tmdbId,
);
@@ -205,29 +206,28 @@ export class UsersController {
return {
tmdbId,
inLibrary: !!libraryItem,
playState: playState,
playStates: playState,
};
}
@UseGuards(UserAccessControl)
@Get(':userId/user-data/show/tmdb/:tmdbId/season/:season/episode/:episode')
@Get(':userId/user-data/series/tmdb/:tmdbId/season/:season/episode/:episode')
@ApiOkResponse({
description: 'User show data found',
type: MediaUserDataDto,
description: 'User series data found',
type: MovieUserDataDto,
})
async getEpisodeUserData(
@Param('userId') userId: string,
@Param('tmdbId') tmdbId: string,
@Param('season', ParseIntPipe) season: number,
@Param('episode', ParseIntPipe) episode: number,
): Promise<MediaUserDataDto> {
): Promise<MovieUserDataDto> {
const libraryItem = await this.libraryService.findByTmdbId(userId, tmdbId);
const playState = await this.playStateService.findShowPlayState(
userId,
tmdbId,
season,
episode,
);
const playState = await this.playStateService
.findSeriesPlayStates(userId, tmdbId, season, episode)
.then((states) =>
states.find((s) => s.season === season && s.episode === episode),
);
return {
tmdbId,