mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-03-27 17:54:15 -04:00
Compare commits
6 Commits
api-docs
...
quality-si
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
974c4a601b | ||
|
|
9549038121 | ||
|
|
2f193ac58a | ||
|
|
e893ca4f1c | ||
|
|
039d7775ed | ||
|
|
87bd5e62f2 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -162,3 +162,6 @@ src/.idea/
|
|||||||
|
|
||||||
# API doc generation
|
# API doc generation
|
||||||
.config/
|
.config/
|
||||||
|
|
||||||
|
# Ignore Jetbrains IntelliJ Workspace Directories
|
||||||
|
.idea/
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
.saveError {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ interface CssExports {
|
|||||||
'header': string;
|
'header': string;
|
||||||
'megabytesPerMinute': string;
|
'megabytesPerMinute': string;
|
||||||
'quality': string;
|
'quality': string;
|
||||||
|
'saveError': string;
|
||||||
'sizeLimit': string;
|
'sizeLimit': string;
|
||||||
'sizeLimitHelpText': string;
|
'sizeLimitHelpText': string;
|
||||||
'sizeLimitHelpTextContainer': string;
|
'sizeLimitHelpTextContainer': string;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import FieldSet from 'Components/FieldSet';
|
import FieldSet from 'Components/FieldSet';
|
||||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||||
|
import { kinds } from 'Helpers/Props';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import QualityDefinitionConnector from './QualityDefinitionConnector';
|
import QualityDefinitionConnector from './QualityDefinitionConnector';
|
||||||
import styles from './QualityDefinitions.css';
|
import styles from './QualityDefinitions.css';
|
||||||
@@ -15,15 +17,44 @@ class QualityDefinitions extends Component {
|
|||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
|
saveError,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
console.log(saveError);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldSet legend={translate('QualityDefinitions')}>
|
<FieldSet legend={translate('QualityDefinitions')}>
|
||||||
<PageSectionContent
|
<PageSectionContent
|
||||||
errorMessage={translate('QualityDefinitionsLoadError')}
|
errorMessage={translate('QualityDefinitionsLoadError')}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
|
{
|
||||||
|
saveError ?
|
||||||
|
<div className={styles.saveError}>
|
||||||
|
<Alert kind={kinds.DANGER}>
|
||||||
|
{translate('QualityDefinitionsSaveError')}
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
Array.isArray(saveError.responseJSON) ?
|
||||||
|
saveError.responseJSON.map((error, index) => {
|
||||||
|
return (
|
||||||
|
<li key={index}>
|
||||||
|
{error.errorMessage}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}) :
|
||||||
|
<li>
|
||||||
|
{
|
||||||
|
JSON.stringify(saveError.responseJSON)
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</Alert>
|
||||||
|
</div> : null
|
||||||
|
}
|
||||||
|
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.quality}>
|
<div className={styles.quality}>
|
||||||
{translate('Quality')}
|
{translate('Quality')}
|
||||||
@@ -72,6 +103,7 @@ class QualityDefinitions extends Component {
|
|||||||
QualityDefinitions.propTypes = {
|
QualityDefinitions.propTypes = {
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
|
saveError: PropTypes.object,
|
||||||
defaultProfile: PropTypes.object,
|
defaultProfile: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
advancedSettings: PropTypes.bool.isRequired
|
advancedSettings: PropTypes.bool.isRequired
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
using FluentValidation.TestHelper;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
using Sonarr.Api.V3.Qualities;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Test.v3.Qualities;
|
||||||
|
|
||||||
|
[Parallelizable(ParallelScope.All)]
|
||||||
|
public class QualityDefinitionResourceValidatorTests
|
||||||
|
{
|
||||||
|
private readonly QualityDefinitionResourceValidator _validator = new ();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Validate_fails_when_min_size_is_below_min_limit()
|
||||||
|
{
|
||||||
|
var resource = new QualityDefinitionResource { MinSize = QualityDefinitionLimits.Min - 1 };
|
||||||
|
|
||||||
|
var result = _validator.TestValidate(resource);
|
||||||
|
|
||||||
|
result.ShouldHaveValidationErrorFor(r => r.MinSize)
|
||||||
|
.WithErrorCode("GreaterThanOrEqualTo");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Validate_fails_when_min_size_is_above_preferred_size()
|
||||||
|
{
|
||||||
|
var resource = new QualityDefinitionResource
|
||||||
|
{
|
||||||
|
MinSize = 10,
|
||||||
|
PreferredSize = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _validator.TestValidate(resource);
|
||||||
|
|
||||||
|
result.ShouldHaveValidationErrorFor(r => r.MinSize)
|
||||||
|
.WithErrorCode("LessThanOrEqualTo");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Validate_passes_when_min_size_is_within_limits()
|
||||||
|
{
|
||||||
|
var resource = new QualityDefinitionResource
|
||||||
|
{
|
||||||
|
MinSize = QualityDefinitionLimits.Min,
|
||||||
|
PreferredSize = QualityDefinitionLimits.Max
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _validator.TestValidate(resource);
|
||||||
|
|
||||||
|
result.ShouldNotHaveAnyValidationErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Validate_fails_when_max_size_is_below_preferred_size()
|
||||||
|
{
|
||||||
|
var resource = new QualityDefinitionResource
|
||||||
|
{
|
||||||
|
MaxSize = 5,
|
||||||
|
PreferredSize = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _validator.TestValidate(resource);
|
||||||
|
|
||||||
|
result.ShouldHaveValidationErrorFor(r => r.MaxSize)
|
||||||
|
.WithErrorCode("GreaterThanOrEqualTo");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Validate_fails_when_max_size_exceeds_max_limit()
|
||||||
|
{
|
||||||
|
var resource = new QualityDefinitionResource { MaxSize = QualityDefinitionLimits.Max + 1 };
|
||||||
|
|
||||||
|
var result = _validator.TestValidate(resource);
|
||||||
|
|
||||||
|
result.ShouldHaveValidationErrorFor(r => r.MaxSize)
|
||||||
|
.WithErrorCode("LessThanOrEqualTo");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Validate_passes_when_max_size_is_within_limits()
|
||||||
|
{
|
||||||
|
var resource = new QualityDefinitionResource
|
||||||
|
{
|
||||||
|
MaxSize = QualityDefinitionLimits.Max,
|
||||||
|
PreferredSize = QualityDefinitionLimits.Min
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _validator.TestValidate(resource);
|
||||||
|
|
||||||
|
result.ShouldNotHaveAnyValidationErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1591,6 +1591,7 @@
|
|||||||
"QualityCutoffNotMet": "Quality cutoff has not been met",
|
"QualityCutoffNotMet": "Quality cutoff has not been met",
|
||||||
"QualityDefinitions": "Quality Definitions",
|
"QualityDefinitions": "Quality Definitions",
|
||||||
"QualityDefinitionsLoadError": "Unable to load Quality Definitions",
|
"QualityDefinitionsLoadError": "Unable to load Quality Definitions",
|
||||||
|
"QualityDefinitionsSaveError": "Unable to save Quality Definitions",
|
||||||
"QualityLimitsSeriesRuntimeHelpText": "Limits are automatically adjusted for the series runtime and number of episodes in the file.",
|
"QualityLimitsSeriesRuntimeHelpText": "Limits are automatically adjusted for the series runtime and number of episodes in the file.",
|
||||||
"QualityProfile": "Quality Profile",
|
"QualityProfile": "Quality Profile",
|
||||||
"QualityProfileInUseSeriesListCollection": "Can't delete a quality profile that is attached to a series, list, or collection",
|
"QualityProfileInUseSeriesListCollection": "Can't delete a quality profile that is attached to a series, list, or collection",
|
||||||
|
|||||||
7
src/NzbDrone.Core/Qualities/QualityDefinitionLimits.cs
Normal file
7
src/NzbDrone.Core/Qualities/QualityDefinitionLimits.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace NzbDrone.Core.Qualities;
|
||||||
|
|
||||||
|
public static class QualityDefinitionLimits
|
||||||
|
{
|
||||||
|
public const int Min = 0;
|
||||||
|
public const int Max = 1000;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NzbDrone.Api.V3.Qualities;
|
||||||
using NzbDrone.Core.Datastore.Events;
|
using NzbDrone.Core.Datastore.Events;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
@@ -20,6 +21,9 @@ namespace Sonarr.Api.V3.Qualities
|
|||||||
: base(signalRBroadcaster)
|
: base(signalRBroadcaster)
|
||||||
{
|
{
|
||||||
_qualityDefinitionService = qualityDefinitionService;
|
_qualityDefinitionService = qualityDefinitionService;
|
||||||
|
|
||||||
|
SharedValidator.RuleFor(c => c)
|
||||||
|
.SetValidator(new QualityDefinitionResourceValidator());
|
||||||
}
|
}
|
||||||
|
|
||||||
[RestPutById]
|
[RestPutById]
|
||||||
@@ -55,6 +59,12 @@ namespace Sonarr.Api.V3.Qualities
|
|||||||
.ToResource());
|
.ToResource());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("limits")]
|
||||||
|
public ActionResult<QualityDefinitionLimitsResource> GetLimits()
|
||||||
|
{
|
||||||
|
return Ok(new QualityDefinitionLimitsResource());
|
||||||
|
}
|
||||||
|
|
||||||
[NonAction]
|
[NonAction]
|
||||||
public void Handle(CommandExecutedEvent message)
|
public void Handle(CommandExecutedEvent message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.V3.Qualities
|
||||||
|
{
|
||||||
|
public class QualityDefinitionLimitsResource
|
||||||
|
{
|
||||||
|
public int Min { get; set; } = QualityDefinitionLimits.Min;
|
||||||
|
public int Max { get; set; } = QualityDefinitionLimits.Max;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
|
|
||||||
|
namespace Sonarr.Api.V3.Qualities;
|
||||||
|
|
||||||
|
public class QualityDefinitionResourceValidator : AbstractValidator<QualityDefinitionResource>
|
||||||
|
{
|
||||||
|
public QualityDefinitionResourceValidator()
|
||||||
|
{
|
||||||
|
When(c => c.MinSize is not null, () =>
|
||||||
|
{
|
||||||
|
RuleFor(c => c.MinSize)
|
||||||
|
.GreaterThanOrEqualTo(QualityDefinitionLimits.Min)
|
||||||
|
.WithErrorCode("GreaterThanOrEqualTo")
|
||||||
|
.LessThanOrEqualTo(c => c.PreferredSize ?? QualityDefinitionLimits.Max)
|
||||||
|
.WithErrorCode("LessThanOrEqualTo");
|
||||||
|
});
|
||||||
|
|
||||||
|
When(c => c.MaxSize is not null, () =>
|
||||||
|
{
|
||||||
|
RuleFor(c => c.MaxSize)
|
||||||
|
.GreaterThanOrEqualTo(c => c.PreferredSize ?? QualityDefinitionLimits.Min)
|
||||||
|
.WithErrorCode("GreaterThanOrEqualTo")
|
||||||
|
.LessThanOrEqualTo(QualityDefinitionLimits.Max)
|
||||||
|
.WithErrorCode("LessThanOrEqualTo");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -70,11 +70,15 @@ namespace Sonarr.Http.REST
|
|||||||
var skipValidate = skipAttribute?.Skip ?? false;
|
var skipValidate = skipAttribute?.Skip ?? false;
|
||||||
var skipShared = skipAttribute?.SkipShared ?? false;
|
var skipShared = skipAttribute?.SkipShared ?? false;
|
||||||
|
|
||||||
if (Request.Method == "POST" || Request.Method == "PUT")
|
if (Request.Method is "POST" or "PUT")
|
||||||
{
|
{
|
||||||
var resourceArgs = context.ActionArguments.Values.Where(x => x.GetType() == typeof(TResource))
|
var resourceArgs = context.ActionArguments.Values
|
||||||
.Select(x => x as TResource)
|
.SelectMany(x => x switch
|
||||||
.ToList();
|
{
|
||||||
|
TResource single => new[] { single },
|
||||||
|
IEnumerable<TResource> multiple => multiple,
|
||||||
|
_ => Enumerable.Empty<TResource>()
|
||||||
|
});
|
||||||
|
|
||||||
foreach (var resource in resourceArgs)
|
foreach (var resource in resourceArgs)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user