jRay had no requirement IDs, so nothing in this repo could be traced to and the CI gate had no denominator to read. The other two components had already moved to registers; this brings the plugin level with them. Adds docs/requirements.md with 46 permanent JR-nnn IDs, each carrying a parent requirement, priority, status and verification tier, plus a per-requirement verification plan. JR is flat rather than split by theme: the plugin is one deployable with one audience, and JRay-public-server already ships UR/DR, so a second repo using those prefixes would make UR-007 ambiguous across registers. Rewrites SPEC.md as requirements prose with Current:/Gap: on every one. It had drifted into a format-plus-API reference that documented schema_version 1 while owning a format whose v2 shape was specified only in the other two repos, said nothing about SR-002's scene-scoped semantics, and carried the manifest exchange as a "planned" aside while its configuration classes were already implemented. Plugin-side exchange obligations move here from the server's spec, where they were an ownership inversion. Adds JR-038..041 for PR-005, which had no software row in any repo -- it was held structurally by SR-004 and GR-005 both being prohibitions, and a goal preserved only by prohibitions is the kind that erodes unnoticed. jRay is the component that actually opens a socket. Tags 18 units with the requirements they satisfy. Tags name what the code satisfies, so FileTransformationRegistration is not tagged JR-021: that requirement is a prohibition and was still violated elsewhere when this was written. Vendors jray-project as a submodule for the system spec and shared gate. TRACES: JR-001, JR-004, JR-005, JR-007, JR-008, JR-009, JR-010, JR-011 TRACES: JR-012, JR-013, JR-014, JR-015, JR-016, JR-017, JR-018, JR-019 TRACES: JR-020, JR-024, JR-025, JR-036, JR-038 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
83 lines
2.9 KiB
C#
83 lines
2.9 KiB
C#
using System.Collections.Generic;
|
|
using Jellyfin.Plugin.JRay.Models;
|
|
using Jellyfin.Plugin.JRay.Services.Interfaces;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Jellyfin.Plugin.JRay.Controllers;
|
|
|
|
/// <summary>
|
|
/// Manages the prioritise/ignore rules that shape the work-discovery API
|
|
/// (<c>GET /Plugins/JRay/Tasks/Pending</c>). Rules can target a genre, a
|
|
/// series, or a single item; setting a rule for a target that already has one
|
|
/// replaces it, so a target can never be both prioritised and ignored.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Rules steer work discovery only. They never reach the read endpoints or the
|
|
/// overlay, because a rule says "don't spend compute here", not "pretend this
|
|
/// item does not exist".
|
|
/// </remarks>
|
|
[ApiController]
|
|
[Route("Plugins/JRay/Policy")]
|
|
[Authorize(Roles = "Administrator")]
|
|
// TRACES: JR-016, JR-014 | PR-003
|
|
public class PolicyController : ControllerBase
|
|
{
|
|
private readonly IMediaPolicyStore _policyStore;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PolicyController"/> class.
|
|
/// </summary>
|
|
/// <param name="policyStore">The media policy store.</param>
|
|
public PolicyController(IMediaPolicyStore policyStore)
|
|
{
|
|
_policyStore = policyStore;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all configured prioritise/ignore rules.
|
|
/// </summary>
|
|
/// <returns>The current rules.</returns>
|
|
[HttpGet("Rules")]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public ActionResult<IReadOnlyList<MediaPolicyRule>> GetRules()
|
|
{
|
|
return Ok(_policyStore.GetRules());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds or replaces a rule. If a rule already exists for the same scope
|
|
/// and value, its action is updated.
|
|
/// </summary>
|
|
/// <param name="rule">The rule to set.</param>
|
|
/// <returns>204 on success, or 400 if the rule has no value.</returns>
|
|
[HttpPut("Rules")]
|
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
public IActionResult SetRule([FromBody] MediaPolicyRule rule)
|
|
{
|
|
if (rule is null || string.IsNullOrWhiteSpace(rule.Value))
|
|
{
|
|
return BadRequest("A rule must have a non-empty value.");
|
|
}
|
|
|
|
_policyStore.SetRule(rule);
|
|
return NoContent();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the rule matching the given scope and value.
|
|
/// </summary>
|
|
/// <param name="scope">The scope of the rule to remove.</param>
|
|
/// <param name="value">The value of the rule to remove.</param>
|
|
/// <returns>204 whether or not a matching rule existed.</returns>
|
|
[HttpDelete("Rules")]
|
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
|
public IActionResult RemoveRule([FromQuery] PolicyScope scope, [FromQuery] string value)
|
|
{
|
|
_policyStore.RemoveRule(scope, value ?? string.Empty);
|
|
return NoContent();
|
|
}
|
|
}
|