feat: mark all series episodes as watched

This commit is contained in:
Aleksi Lassila
2025-02-15 18:47:37 +02:00
parent ef4fa1d13a
commit 8f1d4c3270
18 changed files with 256 additions and 100 deletions

View File

@@ -1,4 +1,4 @@
import { ApiProperty, OmitType, PartialType } from '@nestjs/swagger';
import { ApiProperty, OmitType, PartialType, PickType } from '@nestjs/swagger';
import { PlayState } from './play-state.entity';
export class PlayStateDto extends PlayState {}
@@ -22,9 +22,14 @@ export class PlayStateDto extends PlayState {}
// }
export class UpdatePlayStateDto extends PartialType(
OmitType(PlayStateDto, ['userId']),
PickType(PlayStateDto, ['season', 'episode', 'watched', 'progress']),
) {}
export class BulkUpdatePlayStateDto {
@ApiProperty({ type: UpdatePlayStateDto, isArray: true })
playStates: UpdatePlayStateDto[];
}
export class MovieUserDataDto {
@ApiProperty()
tmdbId: string;

View File

@@ -9,7 +9,7 @@ import {
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { UserAccessControl } from 'src/auth/auth.guard';
import { UpdatePlayStateDto } from './play-state.dto';
import { BulkUpdatePlayStateDto, UpdatePlayStateDto } from './play-state.dto';
import { PlayStatesService } from './play-states.service';
@ApiTags('users')
@@ -42,7 +42,7 @@ export class PlayStatesController {
return this.playStateService.deleteMoviePlayState(userId, tmdbId);
}
@Put('show/tmdb/:tmdbId/season/:season/episode/:episode')
@Put('series/tmdb/:tmdbId/season/:season/episode/:episode')
async updateEpisodePlayStateByTmdbId(
@Param('userId') userId: string,
@Param('tmdbId') tmdbId: string,
@@ -59,7 +59,7 @@ export class PlayStatesController {
);
}
@Delete('show/tmdb/:tmdbId/season/:season/episode/:episode')
@Delete('series/tmdb/:tmdbId/season/:season/episode/:episode')
async deleteEpisodePlayStateByTmdbId(
@Param('userId') userId: string,
@Param('tmdbId') tmdbId: string,
@@ -73,4 +73,17 @@ export class PlayStatesController {
episode,
);
}
@Put('series/tmdb/:tmdbId')
async updateSeriesPlayStatesByTmdbId(
@Param('userId') userId: string,
@Param('tmdbId') tmdbId: string,
@Body() body: BulkUpdatePlayStateDto,
) {
return this.playStateService.updateOrCreateSeriesPlayStates(
userId,
tmdbId,
body.playStates,
);
}
}

View File

@@ -1,7 +1,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { MediaType } from 'src/common/common.dto';
import { Repository } from 'typeorm';
import { UpdatePlayStateDto } from './play-state.dto';
import { BulkUpdatePlayStateDto, UpdatePlayStateDto } from './play-state.dto';
import { PlayState } from './play-state.entity';
import { USER_PLAY_STATE_REPOSITORY } from './play-state.providers';
@@ -45,20 +45,64 @@ export class PlayStatesService {
return playStates;
}
async updateOrCreateMoviePlayState(
async getPlayState(
userId: string,
tmdbId: string,
playState: UpdatePlayStateDto,
season?: number,
episode?: number,
): Promise<PlayState | undefined> {
return this.playStateRepository.findOne({
where: {
userId,
tmdbId,
...(season ? { season } : {}),
...(episode ? { episode } : {}),
},
});
}
private async getOrCreatePlayState(
userId: string,
tmdbId: string,
options: {
season?: number;
episode?: number;
mediaType: MediaType;
save?: boolean;
},
) {
let state = await this.findMoviePlayState(userId, tmdbId);
let state = await this.getPlayState(
userId,
tmdbId,
options.season,
options.episode,
);
if (!state) {
state = this.playStateRepository.create();
state.userId = userId;
state.tmdbId = tmdbId;
state.mediaType = MediaType.Movie;
state.season = options.season;
state.episode = options.episode;
state.mediaType = options.mediaType;
}
if (options.save) {
return this.playStateRepository.save(state);
}
return state;
}
async updateOrCreateMoviePlayState(
userId: string,
tmdbId: string,
playState: UpdatePlayStateDto,
) {
const state = await this.getOrCreatePlayState(userId, tmdbId, {
mediaType: MediaType.Movie,
});
state.progress = playState.progress;
state.watched = playState.watched;
@@ -72,28 +116,14 @@ export class PlayStatesService {
episode: number,
playState: UpdatePlayStateDto,
) {
let state = await this.findSeriesPlayStates(
userId,
tmdbId,
const state = await this.getOrCreatePlayState(userId, tmdbId, {
season,
episode,
).then((states) =>
states.find(
(state) => state.season === season && state.episode === episode,
),
);
mediaType: MediaType.Episode,
});
if (!state) {
state = this.playStateRepository.create();
state.userId = userId;
state.tmdbId = tmdbId;
state.season = season;
state.episode = episode;
state.mediaType = MediaType.Episode;
}
state.progress = playState.progress;
state.watched = playState.watched;
state.progress = playState.progress ?? state.progress;
state.watched = playState.watched ?? state.watched;
return this.playStateRepository.save(state);
}
@@ -109,12 +139,33 @@ export class PlayStatesService {
season: number,
episode: number,
) {
const state = await this.findSeriesPlayStates(
userId,
tmdbId,
season,
episode,
const state = await this.getPlayState(userId, tmdbId, season, episode);
if (state) {
return await this.playStateRepository.remove(state);
}
}
async updateOrCreateSeriesPlayStates(
userId: string,
tmdbId: string,
playStates: UpdatePlayStateDto[],
) {
const states = await Promise.all(
playStates.map(async (updatedState) => {
const state = await this.getOrCreatePlayState(userId, tmdbId, {
season: updatedState.season,
episode: updatedState.episode,
mediaType: MediaType.Episode,
});
state.progress = updatedState.progress ?? state.progress;
state.watched = updatedState.watched ?? state.watched;
return this.playStateRepository.save(state);
}),
);
return await this.playStateRepository.remove(state);
return states;
}
}

View File

@@ -81,11 +81,12 @@ export class UserDataController {
@Param('episode', ParseIntPipe) episode: number,
): Promise<MovieUserDataDto> {
const libraryItem = await this.libraryService.findByTmdbId(userId, tmdbId);
const playState = await this.playStateService
.findSeriesPlayStates(userId, tmdbId, season, episode)
.then((states) =>
states.find((s) => s.season === season && s.episode === episode),
);
const playState = await this.playStateService.getPlayState(
userId,
tmdbId,
season,
episode,
);
return {
tmdbId,