mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-26 18:55:12 +02:00
feat!: Improved plugin settings api and medias source settings error messages for jackett and jellyfin
This commit is contained in:
@@ -1,10 +1,19 @@
|
||||
import { OmitType, PartialType } from '@nestjs/swagger';
|
||||
import { ApiProperty, OmitType, PartialType } from '@nestjs/swagger';
|
||||
import { PickAndPartial } from 'src/common/common.dto';
|
||||
import { MediaSource } from './media-source.entity';
|
||||
import { ValidationResponseDto } from 'src/source-providers/source-provider.dto';
|
||||
|
||||
export class MediaSourceDto extends PickAndPartial(
|
||||
MediaSource,
|
||||
['id', 'pluginId', 'name', 'userId', 'adminControlled', 'enabled'],
|
||||
[
|
||||
'id',
|
||||
'pluginId',
|
||||
'name',
|
||||
'userId',
|
||||
'adminControlled',
|
||||
'enabled',
|
||||
'priority',
|
||||
],
|
||||
['pluginSettings'],
|
||||
) {}
|
||||
|
||||
@@ -23,3 +32,11 @@ export class CreateMediaSourceDto extends OmitType(MediaSourceDto, [
|
||||
'id',
|
||||
'userId',
|
||||
]) {}
|
||||
|
||||
export class UpdateMediaSourceResponse {
|
||||
@ApiProperty({ type: MediaSourceDto })
|
||||
mediaSource: MediaSourceDto;
|
||||
|
||||
@ApiProperty({ type: ValidationResponseDto, required: false })
|
||||
validationResponse: ValidationResponseDto | undefined;
|
||||
}
|
||||
|
||||
@@ -69,9 +69,8 @@ export class ServiceOwnershipValidator implements CanActivate {
|
||||
|
||||
if (!sourceId) return true;
|
||||
|
||||
const mediaSource = await this.mediaSourcesService.findMediaSource(
|
||||
sourceId,
|
||||
);
|
||||
const mediaSource =
|
||||
await this.mediaSourcesService.findMediaSource(sourceId);
|
||||
|
||||
if (!mediaSource) throw new NotFoundException('Source not found');
|
||||
|
||||
@@ -303,9 +302,8 @@ export class MediaSourcesController {
|
||||
@GetAuthToken() token: string,
|
||||
) {
|
||||
const sourceId = params.sourceId;
|
||||
const mediaSource = await this.mediaSourcesService.findMediaSource(
|
||||
sourceId,
|
||||
);
|
||||
const mediaSource =
|
||||
await this.mediaSourcesService.findMediaSource(sourceId);
|
||||
|
||||
if (!mediaSource) throw new NotFoundException('Source not found');
|
||||
|
||||
@@ -370,9 +368,8 @@ export class MediaSourcesController {
|
||||
}
|
||||
|
||||
async getConnection(sourceId: string) {
|
||||
const mediaSource = await this.mediaSourcesService.findMediaSource(
|
||||
sourceId,
|
||||
);
|
||||
const mediaSource =
|
||||
await this.mediaSourcesService.findMediaSource(sourceId);
|
||||
|
||||
if (!mediaSource.pluginId || !mediaSource.enabled) {
|
||||
throw new BadRequestException('Source not configured');
|
||||
|
||||
@@ -6,6 +6,7 @@ import { UpdateOrCreateMediaSourceDto } from './media-source.dto';
|
||||
import { MediaSource } from './media-source.entity';
|
||||
import { MEIDA_SOURCE_REPOSITORY } from './media-source.providers';
|
||||
import { SourceProvidersService } from 'src/source-providers/source-providers.service';
|
||||
import { ValidationResponse } from '@aleksilassila/reiverr-plugin';
|
||||
|
||||
export enum MediaSourcesServiceError {
|
||||
SourceNotFound = 'SourceNotFound',
|
||||
@@ -58,7 +59,7 @@ export class MediaSourcesService {
|
||||
user: User,
|
||||
sourceDto: UpdateOrCreateMediaSourceDto,
|
||||
callerUser: User = user,
|
||||
): Promise<User> {
|
||||
) {
|
||||
if (!callerUser.isAdmin || callerUser.id !== user.id) {
|
||||
throw MediaSourcesServiceError.Unauthorized;
|
||||
}
|
||||
@@ -88,18 +89,21 @@ export class MediaSourcesService {
|
||||
|
||||
source.adminControlled =
|
||||
sourceDto.adminControlled ?? source.adminControlled;
|
||||
let validationResponse: ValidationResponse | undefined;
|
||||
if (sourceDto.pluginSettings !== undefined) {
|
||||
let valid = false;
|
||||
const provider = this.sourceProvidersService.getProvider(source.pluginId);
|
||||
|
||||
if (provider) {
|
||||
const validationRes = await provider.settingsManager.validateSettings(
|
||||
validationResponse = await provider.settingsManager.validateSettings(
|
||||
sourceDto.pluginSettings,
|
||||
);
|
||||
valid = validationRes.isValid;
|
||||
valid = validationResponse.isValid;
|
||||
source.pluginSettings = validationResponse.settings;
|
||||
} else {
|
||||
source.pluginSettings = sourceDto.pluginSettings;
|
||||
}
|
||||
|
||||
source.pluginSettings = sourceDto.pluginSettings;
|
||||
source.enabled = !!valid;
|
||||
}
|
||||
source.name = sourceDto.name ?? source.name;
|
||||
@@ -120,8 +124,10 @@ export class MediaSourcesService {
|
||||
priority++;
|
||||
}
|
||||
|
||||
await this.mediaSourceRepository.save(source);
|
||||
return this.usersService.findOne(user.id);
|
||||
return {
|
||||
mediaSource: await this.mediaSourceRepository.save(source),
|
||||
validationResponse,
|
||||
};
|
||||
}
|
||||
|
||||
getMediaSourceSettings(user: User, sourceId: string) {
|
||||
|
||||
@@ -14,11 +14,15 @@ import { GetAuthUser, UserAccessControl } from 'src/auth/auth.guard';
|
||||
import { UserDto } from 'src/users/user.dto';
|
||||
import { User } from 'src/users/user.entity';
|
||||
import { UsersService } from 'src/users/users.service';
|
||||
import { UpdateOrCreateMediaSourceDto } from './media-source.dto';
|
||||
import {
|
||||
UpdateMediaSourceResponse,
|
||||
UpdateOrCreateMediaSourceDto,
|
||||
} from './media-source.dto';
|
||||
import {
|
||||
MediaSourcesService,
|
||||
MediaSourcesServiceError,
|
||||
} from './media-sources.service';
|
||||
import { MediaSource } from './media-source.entity';
|
||||
|
||||
@ApiTags('users')
|
||||
@Controller('users/:userId/sources')
|
||||
@@ -30,33 +34,40 @@ export class MediaSourcesSettingsController {
|
||||
) {}
|
||||
|
||||
@Put()
|
||||
@ApiOkResponse({ description: 'Source updated', type: UserDto })
|
||||
@ApiOkResponse({
|
||||
description: 'Source updated',
|
||||
type: UpdateMediaSourceResponse,
|
||||
})
|
||||
async updateSource(
|
||||
@GetAuthUser() callerUser: User,
|
||||
@Param('userId') userId: string,
|
||||
@Body() sourceDto: UpdateOrCreateMediaSourceDto,
|
||||
): Promise<UserDto> {
|
||||
): Promise<UpdateMediaSourceResponse> {
|
||||
const user = await this.usersService.findOne(userId);
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('User not found');
|
||||
}
|
||||
|
||||
const updatedUser = await this.mediaSourcesService
|
||||
.updateOrCreateMediaSource(user, sourceDto, callerUser)
|
||||
.catch((e) => {
|
||||
if (e === MediaSourcesServiceError.Unauthorized) {
|
||||
throw new UnauthorizedException();
|
||||
} else {
|
||||
throw new InternalServerErrorException('Failed to update source');
|
||||
}
|
||||
});
|
||||
const { mediaSource: updatedSource, validationResponse } =
|
||||
await this.mediaSourcesService
|
||||
.updateOrCreateMediaSource(user, sourceDto, callerUser)
|
||||
.catch((e) => {
|
||||
if (e === MediaSourcesServiceError.Unauthorized) {
|
||||
throw new UnauthorizedException();
|
||||
} else {
|
||||
throw new InternalServerErrorException('Failed to update source');
|
||||
}
|
||||
});
|
||||
|
||||
if (!updatedUser) {
|
||||
if (!updatedSource) {
|
||||
throw new InternalServerErrorException('Failed to update source');
|
||||
}
|
||||
|
||||
return UserDto.fromEntity(updatedUser);
|
||||
return {
|
||||
mediaSource: updatedSource,
|
||||
validationResponse,
|
||||
};
|
||||
}
|
||||
|
||||
@Delete(':sourceId')
|
||||
|
||||
@@ -123,7 +123,7 @@ export class ValidationResponseDto implements ValidationResponse {
|
||||
type: 'object',
|
||||
additionalProperties: true,
|
||||
})
|
||||
replace: Record<string, any>;
|
||||
settings: Record<string, any>;
|
||||
}
|
||||
|
||||
export class AudioStreamDto implements AudioStream {
|
||||
|
||||
Reference in New Issue
Block a user