feat: server side continue watching before refactoring

This commit is contained in:
Aleksi Lassila
2025-03-30 13:25:44 +03:00
parent 97223a1d40
commit be27a0ca90
11 changed files with 91 additions and 165 deletions

View File

@@ -12,6 +12,7 @@ export enum MyListOrder {
Name = 'name',
FirstReleaseDate = 'first-release-date',
LastReleaseDate = 'last-release-date',
LastPlayed = 'last-played',
}
export enum MyListStatusFilter {
@@ -19,7 +20,7 @@ export enum MyListStatusFilter {
Upcoming = 'upcoming',
Unwatched = 'unwatched',
Watched = 'watched',
ContinueWatching = 'continueWatching',
ContinueWatching = 'continue-watching',
}
export enum MyListTypeFilter {

View File

@@ -25,13 +25,17 @@ export class LibraryItem {
id: string;
@ApiProperty({ required: true })
@Column({ unique: true })
@Column()
tmdbId: string;
@ApiProperty({ required: true, enum: MediaType })
@Column()
mediaType: MediaType;
@ApiProperty({ type: 'string', required: false })
@Column({ nullable: true })
lastPlayedAt?: Date;
@ApiProperty({ required: false, type: MovieMetadata })
@ManyToOne(() => MovieMetadata, {
createForeignKeyConstraints: false,

View File

@@ -57,66 +57,12 @@ export class LibraryService {
direction = OrderDirection.Desc,
} = options;
// const order = {
// [MyListOrder.DateAdded]: { createdAt: directon } as const,
// [MyListOrder.Name]: {
// seriesMetadata: {
// name: directon,
// },
// movieMetadata: {
// name: directon,
// },
// } as const,
// [MyListOrder.FirstReleaseDate]: {
// movieMetadata: {
// releaseDate: directon,
// },
// seriesMetadata: {
// firstReleaseDate: directon,
// },
// } as const,
// [MyListOrder.LastReleaseDate]: {
// movieMetadata: {
// releaseDate: directon,
// },
// seriesMetadata: {
// lastReleaseDate: directon,
// },
// } as const,
// }[sortBy];
const mediaType = type
? type === MyListTypeFilter.Movies
? MediaType.Movie
: MediaType.Series
: undefined;
// const [items, total] = await this.libraryRepository.findAndCount({
// relations: {
// playStates: true,
// seriesMetadata: true,
// movieMetadata: true,
// },
// 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),
// });
let builder = this.libraryRepository
.createQueryBuilder('libraryItem')
.leftJoinAndSelect('libraryItem.playStates', 'playStates')
@@ -176,7 +122,10 @@ export class LibraryService {
builder = builder.andWhere(upcoming);
} else if (status === MyListStatusFilter.Watched) {
builder = builder.andWhere(watchedAndNotUpcoming);
} else if (status === MyListStatusFilter.Unwatched) {
} else if (
status === MyListStatusFilter.Unwatched ||
status === MyListStatusFilter.ContinueWatching
) {
builder = builder.andWhere(
new Brackets((qb) =>
qb
@@ -210,6 +159,12 @@ export class LibraryService {
),
),
);
if (status === MyListStatusFilter.ContinueWatching) {
builder = builder.andWhere(
"libraryItem.lastPlayedAt IS NOT NULL AND libraryItem.lastPlayedAt > date('now', '-1 month')",
);
}
}
const DIRECTION = direction === OrderDirection.Asc ? 'ASC' : 'DESC';
@@ -224,6 +179,8 @@ export class LibraryService {
} else if (order === MyListOrder.LastReleaseDate) {
builder.addOrderBy('movieMetadata.releaseDate', DIRECTION);
builder.addOrderBy('seriesMetadata.lastReleaseDate', DIRECTION);
} else if (order === MyListOrder.LastPlayed) {
builder.addOrderBy('libraryItem.lastPlayedAt', DIRECTION);
}
const [items, total] = await builder

View File

@@ -2,10 +2,11 @@ import { Module } from '@nestjs/common';
import { playStateProviders } from './play-state.providers';
import { PlayStatesController } from './play-states.controller';
import { PlayStatesService } from './play-states.service';
import { libraryProviders } from '../library/library.providers';
@Module({
imports: [],
providers: [...playStateProviders, PlayStatesService],
providers: [...playStateProviders, ...libraryProviders, PlayStatesService],
controllers: [PlayStatesController],
exports: [PlayStatesService],
})

View File

@@ -1,15 +1,19 @@
import { Inject, Injectable } from '@nestjs/common';
import { MediaTypeFull } from 'src/common/common.dto';
import { Repository } from 'typeorm';
import { BulkUpdatePlayStateDto, UpdatePlayStateDto } from './play-state.dto';
import { UpdatePlayStateDto } from './play-state.dto';
import { PlayState } from './play-state.entity';
import { USER_PLAY_STATE_REPOSITORY } from './play-state.providers';
import { USER_LIBRARY_REPOSITORY } from '../library/library.providers';
import { LibraryItem } from '../library/library.entity';
@Injectable()
export class PlayStatesService {
constructor(
@Inject(USER_PLAY_STATE_REPOSITORY)
private readonly playStateRepository: Repository<PlayState>,
@Inject(USER_LIBRARY_REPOSITORY)
private readonly libraryRepository: Repository<LibraryItem>,
) {}
async findMoviePlayState(userId: string, tmdbId: string) {
@@ -106,7 +110,14 @@ export class PlayStatesService {
if (playState.progress !== undefined) state.progress = playState.progress;
if (playState.watched !== undefined) state.watched = playState.watched;
return this.playStateRepository.save(state);
return this.playStateRepository.save(state).then(async (state) => {
await this.libraryRepository.update(
{ tmdbId, userId },
{ lastPlayedAt: new Date() },
);
return state;
});
}
async updateOrCreateEpisodePlayState(
@@ -125,7 +136,14 @@ export class PlayStatesService {
if (playState.progress !== undefined) state.progress = playState.progress;
if (playState.watched !== undefined) state.watched = playState.watched;
return this.playStateRepository.save(state);
return this.playStateRepository.save(state).then(async (state) => {
await this.libraryRepository.update(
{ tmdbId, userId },
{ lastPlayedAt: new Date() },
);
return state;
});
}
async deleteMoviePlayState(userId: string, tmdbId: string) {