mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-19 11:53:24 +02:00
refactor: pretty much the whole backend module hierarchy
This commit is contained in:
93
backend/src/user-data/library/library.controller.ts
Normal file
93
backend/src/user-data/library/library.controller.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
ParseEnumPipe,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOkResponse, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||
import { UserAccessControl } from 'src/auth/auth.guard';
|
||||
import {
|
||||
GetPaginationParams,
|
||||
PaginatedApiOkResponse,
|
||||
} from 'src/common/common.decorator';
|
||||
import {
|
||||
MediaType,
|
||||
PaginatedResponseDto,
|
||||
PaginationParamsDto,
|
||||
SuccessResponseDto,
|
||||
} from 'src/common/common.dto';
|
||||
import { LibraryItemDto } from './library.dto';
|
||||
import { LibraryService } from './library.service';
|
||||
|
||||
@ApiTags('users')
|
||||
@Controller('users/:userId/library')
|
||||
@UseGuards(UserAccessControl)
|
||||
export class LibraryController {
|
||||
constructor(private libraryService: LibraryService) {}
|
||||
|
||||
@Get()
|
||||
@PaginatedApiOkResponse(LibraryItemDto)
|
||||
async getLibraryItems(
|
||||
@GetPaginationParams() pagination: PaginationParamsDto,
|
||||
@Param('userId') userId: string,
|
||||
): Promise<PaginatedResponseDto<LibraryItemDto>> {
|
||||
// const user = await this.userService.findOne(userId);
|
||||
|
||||
const items = await this.libraryService.getLibraryItemsWithMetadata(
|
||||
userId,
|
||||
pagination,
|
||||
);
|
||||
|
||||
return {
|
||||
items,
|
||||
itemsPerPage: pagination.itemsPerPage,
|
||||
page: pagination.page,
|
||||
total: items.length,
|
||||
};
|
||||
}
|
||||
|
||||
@Put('tmdb/:tmdbId')
|
||||
@ApiQuery({ name: 'mediaType', enum: MediaType })
|
||||
@ApiOkResponse({
|
||||
description: 'Library item added',
|
||||
type: SuccessResponseDto,
|
||||
})
|
||||
async addLibraryItem(
|
||||
@Param('userId') userId: string,
|
||||
@Param('tmdbId') tmdbId: string,
|
||||
@Query('mediaType', new ParseEnumPipe(MediaType)) mediaType: MediaType,
|
||||
): Promise<SuccessResponseDto> {
|
||||
const item = await this.libraryService.findOrCreateByTmdbId(
|
||||
userId,
|
||||
tmdbId,
|
||||
mediaType,
|
||||
);
|
||||
|
||||
return {
|
||||
success: !!item,
|
||||
};
|
||||
}
|
||||
|
||||
@Delete('tmdb/:tmdbId')
|
||||
@ApiOkResponse({
|
||||
description: 'Library item removed',
|
||||
type: SuccessResponseDto,
|
||||
})
|
||||
async removeLibraryItem(
|
||||
@Param('userId') userId: string,
|
||||
@Param('tmdbId') tmdbId: string,
|
||||
): Promise<SuccessResponseDto> {
|
||||
const deleteAction = await this.libraryService.deleteByTmdbId(
|
||||
userId,
|
||||
tmdbId,
|
||||
);
|
||||
|
||||
return {
|
||||
success: deleteAction.affected > 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
22
backend/src/user-data/library/library.dto.ts
Normal file
22
backend/src/user-data/library/library.dto.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ApiProperty } 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';
|
||||
|
||||
export class LibraryItemDto {
|
||||
@ApiProperty()
|
||||
tmdbId: string;
|
||||
|
||||
@ApiProperty({ enum: MediaType })
|
||||
mediaType: MediaType;
|
||||
|
||||
@ApiProperty({ type: [PlayStateDto], required: false })
|
||||
playStates?: PlayStateDto[];
|
||||
|
||||
@ApiProperty({ type: MovieDto, required: false })
|
||||
movieMetadata?: MovieDto;
|
||||
|
||||
@ApiProperty({ type: Series, required: false })
|
||||
seriesMetadata?: Series;
|
||||
}
|
||||
42
backend/src/user-data/library/library.entity.ts
Normal file
42
backend/src/user-data/library/library.entity.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { MediaType } from 'src/common/common.dto';
|
||||
import { User } from 'src/users/user.entity';
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryColumn,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import { PlayState } from '../play-state/play-state.entity';
|
||||
|
||||
@Entity()
|
||||
@Unique(['tmdbId', 'userId'])
|
||||
export class LibraryItem {
|
||||
@ApiProperty({ required: false, type: 'string' })
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@ApiProperty({ required: true, type: 'number' })
|
||||
@Column({ unique: true })
|
||||
tmdbId: string;
|
||||
|
||||
@ApiProperty({ required: true, enum: MediaType })
|
||||
@Column()
|
||||
mediaType: MediaType;
|
||||
|
||||
@ApiProperty({ required: true, type: 'string' })
|
||||
@PrimaryColumn()
|
||||
userId: string;
|
||||
|
||||
@ApiProperty({ required: false, type: 'string' })
|
||||
@ManyToOne(() => User, (user) => user.libraryItems, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'userId' })
|
||||
user: User;
|
||||
|
||||
@OneToMany(() => PlayState, (playState) => playState.libraryItem)
|
||||
playStates?: PlayState[];
|
||||
}
|
||||
14
backend/src/user-data/library/library.module.ts
Normal file
14
backend/src/user-data/library/library.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MetadataModule } from 'src/metadata/metadata.module';
|
||||
import { LibraryController } from './library.controller';
|
||||
import { libraryProviders } from './library.providers';
|
||||
import { LibraryService } from './library.service';
|
||||
import { UsersModule } from 'src/users/users.module';
|
||||
|
||||
@Module({
|
||||
providers: [...libraryProviders, LibraryService],
|
||||
controllers: [LibraryController],
|
||||
imports: [MetadataModule, UsersModule],
|
||||
exports: [LibraryService],
|
||||
})
|
||||
export class LibraryModule {}
|
||||
14
backend/src/user-data/library/library.providers.ts
Normal file
14
backend/src/user-data/library/library.providers.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { LibraryItem } from './library.entity';
|
||||
import { DATA_SOURCE } from 'src/database/database.providers';
|
||||
|
||||
export const USER_LIBRARY_REPOSITORY = 'USER_LIBRARY_REPOSITORY';
|
||||
|
||||
export const libraryProviders = [
|
||||
{
|
||||
provide: USER_LIBRARY_REPOSITORY,
|
||||
useFactory: (dataSource: DataSource) =>
|
||||
dataSource.getRepository(LibraryItem),
|
||||
inject: [DATA_SOURCE],
|
||||
},
|
||||
];
|
||||
83
backend/src/user-data/library/library.service.ts
Normal file
83
backend/src/user-data/library/library.service.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Repository } from 'typeorm';
|
||||
import { LibraryItem } from './library.entity';
|
||||
import { MediaType, PaginationParamsDto } from 'src/common/common.dto';
|
||||
import { LibraryItemDto } from './library.dto';
|
||||
import { MetadataService } from 'src/metadata/metadata.service';
|
||||
import { USER_LIBRARY_REPOSITORY } from './library.providers';
|
||||
|
||||
@Injectable()
|
||||
export class LibraryService {
|
||||
constructor(
|
||||
@Inject(USER_LIBRARY_REPOSITORY)
|
||||
private readonly libraryRepository: Repository<LibraryItem>,
|
||||
private readonly metadataService: MetadataService,
|
||||
) {}
|
||||
|
||||
async getLibraryItemsWithMetadata(
|
||||
userId: string,
|
||||
pagination: PaginationParamsDto,
|
||||
): Promise<LibraryItemDto[]> {
|
||||
const items = await this.getLibraryItems(userId, pagination);
|
||||
|
||||
return Promise.all(
|
||||
items.map(async (item) => {
|
||||
return {
|
||||
...item,
|
||||
movieMetadata:
|
||||
item.mediaType === MediaType.Movie
|
||||
? await this.metadataService.getMovieByTmdbId(item.tmdbId)
|
||||
: undefined,
|
||||
seriesMetadata:
|
||||
item.mediaType === MediaType.Series
|
||||
? await this.metadataService.getSeriesByTmdbId(item.tmdbId)
|
||||
: undefined,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async getLibraryItems(
|
||||
userId: string,
|
||||
pagination: PaginationParamsDto,
|
||||
): Promise<LibraryItem[]> {
|
||||
return this.libraryRepository.find({
|
||||
where: { userId },
|
||||
relations: {
|
||||
playStates: true,
|
||||
},
|
||||
take: pagination.itemsPerPage,
|
||||
skip: pagination.itemsPerPage * (pagination.page - 1),
|
||||
});
|
||||
}
|
||||
|
||||
async findByTmdbId(
|
||||
userId: string,
|
||||
tmdbId: string,
|
||||
): Promise<LibraryItem | null> {
|
||||
return this.libraryRepository.findOne({ where: { userId, tmdbId } });
|
||||
}
|
||||
|
||||
async findOrCreateByTmdbId(
|
||||
userId: string,
|
||||
tmdbId: string,
|
||||
mediaType: MediaType,
|
||||
): Promise<LibraryItem> {
|
||||
let libraryItem = await this.findByTmdbId(userId, tmdbId);
|
||||
|
||||
if (!libraryItem) {
|
||||
libraryItem = this.libraryRepository.create({
|
||||
userId,
|
||||
tmdbId,
|
||||
mediaType,
|
||||
});
|
||||
await this.libraryRepository.save(libraryItem);
|
||||
}
|
||||
|
||||
return libraryItem;
|
||||
}
|
||||
|
||||
async deleteByTmdbId(userId: string, tmdbId: string) {
|
||||
return await this.libraryRepository.delete({ userId, tmdbId });
|
||||
}
|
||||
}
|
||||
48
backend/src/user-data/play-state/play-state.dto.ts
Normal file
48
backend/src/user-data/play-state/play-state.dto.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { ApiProperty, OmitType, PartialType } from '@nestjs/swagger';
|
||||
import { PlayState } from './play-state.entity';
|
||||
|
||||
export class PlayStateDto extends PlayState {}
|
||||
|
||||
// export class PlayStateDto extends IntersectionType(
|
||||
// OmitType(PlayState, ['id']),
|
||||
// PartialType(PickType(PlayState, ['id'])),
|
||||
// ) {
|
||||
// constructor(
|
||||
// tmdbId: string,
|
||||
// userId: string,
|
||||
// season?: number,
|
||||
// episode?: number,
|
||||
// ) {
|
||||
// super();
|
||||
// this.tmdbId = tmdbId;
|
||||
// this.userId = userId;
|
||||
// if (season !== undefined) this.season = season;
|
||||
// if (episode !== undefined) this.episode = episode;
|
||||
// }
|
||||
// }
|
||||
|
||||
export class UpdatePlayStateDto extends PartialType(
|
||||
OmitType(PlayStateDto, ['userId']),
|
||||
) {}
|
||||
|
||||
export class MovieUserDataDto {
|
||||
@ApiProperty()
|
||||
tmdbId: string;
|
||||
|
||||
@ApiProperty()
|
||||
inLibrary: boolean;
|
||||
|
||||
@ApiProperty({ type: PlayStateDto, required: false })
|
||||
playState?: PlayStateDto;
|
||||
}
|
||||
|
||||
export class SeriesUserDataDto {
|
||||
@ApiProperty()
|
||||
tmdbId: string;
|
||||
|
||||
@ApiProperty()
|
||||
inLibrary: boolean;
|
||||
|
||||
@ApiProperty({ type: [PlayStateDto] })
|
||||
playStates: PlayStateDto[];
|
||||
}
|
||||
80
backend/src/user-data/play-state/play-state.entity.ts
Normal file
80
backend/src/user-data/play-state/play-state.entity.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryColumn,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { LibraryItem } from '../library/library.entity';
|
||||
import { MediaType } from 'src/common/common.dto';
|
||||
import { User } from 'src/users/user.entity';
|
||||
|
||||
@Entity()
|
||||
@Unique(['tmdbId', 'userId', 'season', 'episode'])
|
||||
export class PlayState {
|
||||
@ApiProperty({ type: 'string' })
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@ApiProperty({ required: true, type: 'number' })
|
||||
@Column()
|
||||
tmdbId: string;
|
||||
|
||||
@ApiProperty({ enum: MediaType })
|
||||
@Column()
|
||||
mediaType: MediaType;
|
||||
|
||||
@ApiProperty({ required: true, type: 'string' })
|
||||
@Column()
|
||||
userId: string;
|
||||
|
||||
// @ApiProperty({ required: false, type: UserDto })
|
||||
@ManyToOne(() => User, (user) => user.playStates, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'userId' })
|
||||
user?: User;
|
||||
|
||||
@ApiProperty({ required: false, type: 'number' })
|
||||
@PrimaryColumn({ default: 0 })
|
||||
season: number = 0;
|
||||
|
||||
@ApiProperty({ required: false, type: 'number' })
|
||||
@PrimaryColumn({ default: 0 })
|
||||
episode: number = 0;
|
||||
|
||||
@ApiProperty({
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether the user has watched this media',
|
||||
})
|
||||
@Column({ default: false })
|
||||
watched: boolean = false;
|
||||
|
||||
@ApiProperty({
|
||||
default: false,
|
||||
example: 0.5,
|
||||
description: 'A number between 0 and 1',
|
||||
})
|
||||
@Column('double', { default: 0 })
|
||||
progress: number = 0;
|
||||
|
||||
@ApiProperty({
|
||||
type: 'string',
|
||||
description: 'Last time the user played this media',
|
||||
})
|
||||
@UpdateDateColumn()
|
||||
lastPlayedAt: Date;
|
||||
|
||||
@ManyToOne(() => LibraryItem, (libraryItem) => libraryItem.playStates, {
|
||||
nullable: true,
|
||||
createForeignKeyConstraints: false,
|
||||
})
|
||||
@JoinColumn([
|
||||
{ name: 'tmdbId', referencedColumnName: 'tmdbId' },
|
||||
{ name: 'userId', referencedColumnName: 'userId' },
|
||||
])
|
||||
libraryItem?: LibraryItem;
|
||||
}
|
||||
13
backend/src/user-data/play-state/play-state.providers.ts
Normal file
13
backend/src/user-data/play-state/play-state.providers.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { PlayState } from './play-state.entity';
|
||||
import { DATA_SOURCE } from 'src/database/database.providers';
|
||||
|
||||
export const USER_PLAY_STATE_REPOSITORY = 'USER_PLAY_STATE_REPOSITORY';
|
||||
|
||||
export const playStateProviders = [
|
||||
{
|
||||
provide: USER_PLAY_STATE_REPOSITORY,
|
||||
useFactory: (dataSource: DataSource) => dataSource.getRepository(PlayState),
|
||||
inject: [DATA_SOURCE],
|
||||
},
|
||||
];
|
||||
76
backend/src/user-data/play-state/play-states.controller.ts
Normal file
76
backend/src/user-data/play-state/play-states.controller.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Put,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { UserAccessControl } from 'src/auth/auth.guard';
|
||||
import { UpdatePlayStateDto } from './play-state.dto';
|
||||
import { PlayStatesService } from './play-states.service';
|
||||
|
||||
@ApiTags('users')
|
||||
@Controller('users/:userId/play-state')
|
||||
@UseGuards(UserAccessControl)
|
||||
export class PlayStatesController {
|
||||
constructor(private playStateService: PlayStatesService) {}
|
||||
|
||||
@Put('movie/tmdb/:tmdbId')
|
||||
// @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,
|
||||
) {
|
||||
return this.playStateService.updateOrCreateMoviePlayState(
|
||||
userId,
|
||||
tmdbId,
|
||||
playState,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('movie/tmdb/:tmdbId')
|
||||
async deleteMoviePlayStateByTmdbId(
|
||||
@Param('userId') userId: string,
|
||||
@Param('tmdbId') tmdbId: string,
|
||||
) {
|
||||
return this.playStateService.deleteMoviePlayState(userId, tmdbId);
|
||||
}
|
||||
|
||||
@Put('show/tmdb/:tmdbId/season/:season/episode/:episode')
|
||||
async updateEpisodePlayStateByTmdbId(
|
||||
@Param('userId') userId: string,
|
||||
@Param('tmdbId') tmdbId: string,
|
||||
@Param('season', ParseIntPipe) season: number,
|
||||
@Param('episode', ParseIntPipe) episode: number,
|
||||
@Body() playState: UpdatePlayStateDto,
|
||||
) {
|
||||
return this.playStateService.updateOrCreateEpisodePlayState(
|
||||
userId,
|
||||
tmdbId,
|
||||
season,
|
||||
episode,
|
||||
playState,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('show/tmdb/:tmdbId/season/:season/episode/:episode')
|
||||
async deleteEpisodePlayStateByTmdbId(
|
||||
@Param('userId') userId: string,
|
||||
@Param('tmdbId') tmdbId: string,
|
||||
@Param('season', ParseIntPipe) season: number,
|
||||
@Param('episode', ParseIntPipe) episode: number,
|
||||
) {
|
||||
return this.playStateService.deleteEpisodePlayState(
|
||||
userId,
|
||||
tmdbId,
|
||||
season,
|
||||
episode,
|
||||
);
|
||||
}
|
||||
}
|
||||
13
backend/src/user-data/play-state/play-states.module.ts
Normal file
13
backend/src/user-data/play-state/play-states.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PlayStatesService } from './play-states.service';
|
||||
import { PlayStatesController as PlayStatesController } from './play-states.controller';
|
||||
import { playStateProviders } from './play-state.providers';
|
||||
import { UsersModule } from 'src/users/users.module';
|
||||
|
||||
@Module({
|
||||
providers: [...playStateProviders, PlayStatesService],
|
||||
controllers: [PlayStatesController],
|
||||
imports: [UsersModule],
|
||||
exports: [PlayStatesService],
|
||||
})
|
||||
export class PlayStatesModule {}
|
||||
120
backend/src/user-data/play-state/play-states.service.ts
Normal file
120
backend/src/user-data/play-state/play-states.service.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { MediaType } from 'src/common/common.dto';
|
||||
import { Repository } from 'typeorm';
|
||||
import { UpdatePlayStateDto } from './play-state.dto';
|
||||
import { PlayState } from './play-state.entity';
|
||||
import { USER_PLAY_STATE_REPOSITORY } from './play-state.providers';
|
||||
|
||||
@Injectable()
|
||||
export class PlayStatesService {
|
||||
constructor(
|
||||
@Inject(USER_PLAY_STATE_REPOSITORY)
|
||||
private readonly playStateRepository: Repository<PlayState>,
|
||||
) {}
|
||||
|
||||
async findMoviePlayState(userId: string, tmdbId: string) {
|
||||
return this.playStateRepository.findOne({
|
||||
where: { userId, tmdbId },
|
||||
});
|
||||
}
|
||||
|
||||
async findSeriesPlayStates(
|
||||
userId: string,
|
||||
tmdbId: string,
|
||||
season?: number,
|
||||
episode?: number,
|
||||
): Promise<PlayState[]> {
|
||||
const playStates =
|
||||
(await this.playStateRepository.find({
|
||||
where: {
|
||||
userId,
|
||||
tmdbId,
|
||||
...(season ? { season } : {}),
|
||||
...(episode ? { episode } : {}),
|
||||
},
|
||||
})) ?? [];
|
||||
|
||||
playStates.sort((a, b) => {
|
||||
if (a.season !== b.season) {
|
||||
return a.season - b.season;
|
||||
}
|
||||
|
||||
return a.episode - b.episode;
|
||||
});
|
||||
|
||||
return playStates;
|
||||
}
|
||||
|
||||
async updateOrCreateMoviePlayState(
|
||||
userId: string,
|
||||
tmdbId: string,
|
||||
playState: UpdatePlayStateDto,
|
||||
) {
|
||||
let state = await this.findMoviePlayState(userId, tmdbId);
|
||||
|
||||
if (!state) {
|
||||
state = this.playStateRepository.create();
|
||||
state.userId = userId;
|
||||
state.tmdbId = tmdbId;
|
||||
state.mediaType = MediaType.Movie;
|
||||
}
|
||||
|
||||
state.progress = playState.progress;
|
||||
state.watched = playState.watched;
|
||||
|
||||
return this.playStateRepository.save(state);
|
||||
}
|
||||
|
||||
async updateOrCreateEpisodePlayState(
|
||||
userId: string,
|
||||
tmdbId: string,
|
||||
season: number,
|
||||
episode: number,
|
||||
playState: UpdatePlayStateDto,
|
||||
) {
|
||||
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();
|
||||
state.userId = userId;
|
||||
state.tmdbId = tmdbId;
|
||||
state.season = season;
|
||||
state.episode = episode;
|
||||
state.mediaType = MediaType.Episode;
|
||||
}
|
||||
|
||||
state.progress = playState.progress;
|
||||
state.watched = playState.watched;
|
||||
|
||||
return this.playStateRepository.save(state);
|
||||
}
|
||||
|
||||
async deleteMoviePlayState(userId: string, tmdbId: string) {
|
||||
const state = await this.findMoviePlayState(userId, tmdbId);
|
||||
return await this.playStateRepository.remove(state);
|
||||
}
|
||||
|
||||
async deleteEpisodePlayState(
|
||||
userId: string,
|
||||
tmdbId: string,
|
||||
season: number,
|
||||
episode: number,
|
||||
) {
|
||||
const state = await this.findSeriesPlayStates(
|
||||
userId,
|
||||
tmdbId,
|
||||
season,
|
||||
episode,
|
||||
);
|
||||
return await this.playStateRepository.remove(state);
|
||||
}
|
||||
}
|
||||
96
backend/src/user-data/user-data.controller.ts
Normal file
96
backend/src/user-data/user-data.controller.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { UserAccessControl } from 'src/auth/auth.guard';
|
||||
import { LibraryService } from './library/library.service';
|
||||
import {
|
||||
MovieUserDataDto,
|
||||
SeriesUserDataDto,
|
||||
} from './play-state/play-state.dto';
|
||||
import { PlayStatesService } from './play-state/play-states.service';
|
||||
|
||||
@ApiTags('users')
|
||||
@Controller('users')
|
||||
export class UserDataController {
|
||||
constructor(
|
||||
private libraryService: LibraryService,
|
||||
private playStateService: PlayStatesService,
|
||||
) {}
|
||||
|
||||
@UseGuards(UserAccessControl)
|
||||
@Get(':userId/user-data/movie/tmdb/:tmdbId')
|
||||
@ApiOkResponse({
|
||||
description: 'User movie data found',
|
||||
type: MovieUserDataDto,
|
||||
})
|
||||
async getMovieUserData(
|
||||
@Param('userId') userId: string,
|
||||
@Param('tmdbId') tmdbId: string,
|
||||
): Promise<MovieUserDataDto> {
|
||||
const libraryItem = await this.libraryService.findByTmdbId(userId, tmdbId);
|
||||
const playState = await this.playStateService.findMoviePlayState(
|
||||
userId,
|
||||
tmdbId,
|
||||
);
|
||||
|
||||
return {
|
||||
tmdbId,
|
||||
inLibrary: !!libraryItem,
|
||||
playState: playState,
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(UserAccessControl)
|
||||
@Get(':userId/user-data/series/tmdb/:tmdbId')
|
||||
@ApiOkResponse({
|
||||
description: 'User series data found',
|
||||
type: SeriesUserDataDto,
|
||||
})
|
||||
async getSeriesUserData(
|
||||
@Param('userId') userId: string,
|
||||
@Param('tmdbId') tmdbId: string,
|
||||
): Promise<SeriesUserDataDto> {
|
||||
const libraryItem = await this.libraryService.findByTmdbId(userId, tmdbId);
|
||||
const playState = await this.playStateService.findSeriesPlayStates(
|
||||
userId,
|
||||
tmdbId,
|
||||
);
|
||||
|
||||
return {
|
||||
tmdbId,
|
||||
inLibrary: !!libraryItem,
|
||||
playStates: playState,
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(UserAccessControl)
|
||||
@Get(':userId/user-data/series/tmdb/:tmdbId/season/:season/episode/:episode')
|
||||
@ApiOkResponse({
|
||||
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<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),
|
||||
);
|
||||
|
||||
return {
|
||||
tmdbId,
|
||||
inLibrary: !!libraryItem,
|
||||
playState: playState,
|
||||
};
|
||||
}
|
||||
}
|
||||
12
backend/src/user-data/user-data.module.ts
Normal file
12
backend/src/user-data/user-data.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { LibraryModule } from './library/library.module';
|
||||
import { PlayStatesModule } from './play-state/play-states.module';
|
||||
import { UserDataController } from './user-data.controller';
|
||||
import { UsersModule } from 'src/users/users.module';
|
||||
|
||||
@Module({
|
||||
providers: [],
|
||||
controllers: [UserDataController],
|
||||
imports: [PlayStatesModule, LibraryModule, UsersModule],
|
||||
})
|
||||
export class UserDataModule {}
|
||||
Reference in New Issue
Block a user