mirror of
https://github.com/C9Glax/tranga.git
synced 2025-07-03 17:34:17 +02:00
Refactor Controllers
SettingsController.cs SearchController.cs QueryController.cs NotificationConnectorController.cs MetadataFetcherController.cs MangaConnectorController.cs FileLibraryController LibraryConnectors WorkerController
This commit is contained in:
@ -1,14 +1,14 @@
|
|||||||
namespace API.APIEndpointRecords;
|
namespace API.APIEndpointRecords;
|
||||||
|
|
||||||
public record GotifyRecord(string endpoint, string appToken, int priority)
|
public record GotifyRecord(string Name, string Endpoint, string AppToken, int Priority)
|
||||||
{
|
{
|
||||||
public bool Validate()
|
public bool Validate()
|
||||||
{
|
{
|
||||||
if (endpoint == string.Empty)
|
if (Endpoint == string.Empty)
|
||||||
return false;
|
return false;
|
||||||
if (appToken == string.Empty)
|
if (AppToken == string.Empty)
|
||||||
return false;
|
return false;
|
||||||
if (priority < 0 || priority > 10)
|
if (Priority < 0 || Priority > 10)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
namespace API.APIEndpointRecords;
|
|
||||||
|
|
||||||
public record ModifyJobRecord(ulong? RecurrenceMs, bool? Enabled);
|
|
3
API/APIEndpointRecords/ModifyWorkerRecord.cs
Normal file
3
API/APIEndpointRecords/ModifyWorkerRecord.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace API.APIEndpointRecords;
|
||||||
|
|
||||||
|
public record ModifyWorkerRecord(ulong? IntervalMs);
|
@ -1,13 +0,0 @@
|
|||||||
namespace API.APIEndpointRecords;
|
|
||||||
|
|
||||||
public record NewLibraryRecord(string path, string name)
|
|
||||||
{
|
|
||||||
public bool Validate()
|
|
||||||
{
|
|
||||||
if (path.Length < 1) //TODO Better Path validation
|
|
||||||
return false;
|
|
||||||
if (name.Length < 1)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +1,16 @@
|
|||||||
namespace API.APIEndpointRecords;
|
namespace API.APIEndpointRecords;
|
||||||
|
|
||||||
public record NtfyRecord(string endpoint, string username, string password, string topic, int priority)
|
public record NtfyRecord(string Name, string Endpoint, string Username, string Password, string Topic, int Priority)
|
||||||
{
|
{
|
||||||
public bool Validate()
|
public bool Validate()
|
||||||
{
|
{
|
||||||
if (endpoint == string.Empty)
|
if (Endpoint == string.Empty)
|
||||||
return false;
|
return false;
|
||||||
if (username == string.Empty)
|
if (Username == string.Empty)
|
||||||
return false;
|
return false;
|
||||||
if (password == string.Empty)
|
if (Password == string.Empty)
|
||||||
return false;
|
return false;
|
||||||
if (priority < 1 || priority > 5)
|
if (Priority < 1 || Priority > 5)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
namespace API.APIEndpointRecords;
|
namespace API.APIEndpointRecords;
|
||||||
|
|
||||||
public record PushoverRecord(string apptoken, string user)
|
public record PushoverRecord(string Name, string AppToken, string User)
|
||||||
{
|
{
|
||||||
public bool Validate()
|
public bool Validate()
|
||||||
{
|
{
|
||||||
if (apptoken == string.Empty)
|
if (AppToken == string.Empty)
|
||||||
return false;
|
return false;
|
||||||
if (user == string.Empty)
|
if (User == string.Empty)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
141
API/Controllers/FileLibraryController.cs
Normal file
141
API/Controllers/FileLibraryController.cs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
using API.Schema.MangaContext;
|
||||||
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
|
namespace API.Controllers;
|
||||||
|
|
||||||
|
[ApiVersion(2)]
|
||||||
|
[ApiController]
|
||||||
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
|
public class FileLibraryController(IServiceScope scope) : Controller
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all <see cref="FileLibrary"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <response code="200"></response>
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType<FileLibrary[]>(Status200OK, "application/json")]
|
||||||
|
public IActionResult GetFileLibraries()
|
||||||
|
{
|
||||||
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
|
|
||||||
|
return Ok(context.FileLibraries.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns <see cref="FileLibrary"/> with <paramref name="FileLibraryId"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="FileLibraryId"><see cref="FileLibrary"/>.Key</param>
|
||||||
|
/// <response code="200"></response>
|
||||||
|
/// <response code="404"><see cref="FileLibrary"/> with <paramref name="FileLibraryId"/> not found.</response>
|
||||||
|
[HttpGet("{FileLibraryId}")]
|
||||||
|
[ProducesResponseType<FileLibrary>(Status200OK, "application/json")]
|
||||||
|
[ProducesResponseType(Status404NotFound)]
|
||||||
|
public IActionResult GetFileLibrary(string FileLibraryId)
|
||||||
|
{
|
||||||
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
|
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return Ok(library);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the <see cref="FileLibraryId"/>.BasePath with <paramref name="FileLibraryId"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="FileLibraryId"><see cref="FileLibrary"/>.Key</param>
|
||||||
|
/// <param name="newBasePath">New <see cref="FileLibraryId"/>.BasePath</param>
|
||||||
|
/// <response code="200"></response>
|
||||||
|
/// <response code="404"><see cref="FileLibrary"/> with <paramref name="FileLibraryId"/> not found.</response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
|
[HttpPatch("{FileLibraryId}/ChangeBasePath")]
|
||||||
|
[ProducesResponseType(Status200OK)]
|
||||||
|
[ProducesResponseType(Status404NotFound)]
|
||||||
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
|
public IActionResult ChangeLibraryBasePath(string FileLibraryId, [FromBody]string newBasePath)
|
||||||
|
{
|
||||||
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
|
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
//TODO Path check
|
||||||
|
library.BasePath = newBasePath;
|
||||||
|
|
||||||
|
if(context.Sync().Result is { } errorMessage)
|
||||||
|
return StatusCode(Status500InternalServerError, errorMessage);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the <see cref="FileLibraryId"/>.LibraryName with <paramref name="FileLibraryId"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="FileLibraryId"><see cref="FileLibrary"/>.Key</param>
|
||||||
|
/// <param name="newName">New <see cref="FileLibraryId"/>.LibraryName</param>
|
||||||
|
/// <response code="200"></response>
|
||||||
|
/// <response code="404"><see cref="FileLibrary"/> with <paramref name="FileLibraryId"/> not found.</response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
|
[HttpPatch("{FileLibraryId}/ChangeName")]
|
||||||
|
[ProducesResponseType(Status200OK)]
|
||||||
|
[ProducesResponseType(Status404NotFound)]
|
||||||
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
|
public IActionResult ChangeLibraryName(string FileLibraryId, [FromBody] string newName)
|
||||||
|
{
|
||||||
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
|
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
//TODO Name check
|
||||||
|
library.LibraryName = newName;
|
||||||
|
|
||||||
|
if(context.Sync().Result is { } errorMessage)
|
||||||
|
return StatusCode(Status500InternalServerError, errorMessage);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates new <see cref="FileLibraryId"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="library">New <see cref="FileLibrary"/> to add</param>
|
||||||
|
/// <response code="200"></response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
|
[HttpPut]
|
||||||
|
[ProducesResponseType(Status201Created)]
|
||||||
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
|
public IActionResult CreateNewLibrary([FromBody]FileLibrary library)
|
||||||
|
{
|
||||||
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
|
|
||||||
|
//TODO Parameter check
|
||||||
|
context.FileLibraries.Add(library);
|
||||||
|
|
||||||
|
if(context.Sync().Result is { } errorMessage)
|
||||||
|
return StatusCode(Status500InternalServerError, errorMessage);
|
||||||
|
return Created();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes the <see cref="FileLibraryId"/>.LibraryName with <paramref name="FileLibraryId"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="FileLibraryId"><see cref="FileLibrary"/>.Key</param>
|
||||||
|
/// <response code="200"></response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
|
[HttpDelete("{FileLibraryId}")]
|
||||||
|
[ProducesResponseType(Status200OK)]
|
||||||
|
[ProducesResponseType(Status404NotFound)]
|
||||||
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
|
public IActionResult DeleteLocalLibrary(string FileLibraryId)
|
||||||
|
{
|
||||||
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
|
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
context.FileLibraries.Remove(library);
|
||||||
|
|
||||||
|
if(context.Sync().Result is { } errorMessage)
|
||||||
|
return StatusCode(Status500InternalServerError, errorMessage);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
@ -1,52 +1,54 @@
|
|||||||
using API.Schema.LibraryContext;
|
using API.Schema.LibraryContext;
|
||||||
using API.Schema.LibraryContext.LibraryConnectors;
|
using API.Schema.LibraryContext.LibraryConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using log4net;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class LibraryConnectorController(LibraryContext context, ILog Log) : Controller
|
public class LibraryConnectorController(IServiceScope scope) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all configured ToFileLibrary-Connectors
|
/// Gets all configured <see cref="LibraryConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<LibraryConnector[]>(Status200OK, "application/json")]
|
[ProducesResponseType<LibraryConnector[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetAllConnectors()
|
public IActionResult GetAllConnectors()
|
||||||
{
|
{
|
||||||
|
LibraryContext context = scope.ServiceProvider.GetRequiredService<LibraryContext>();
|
||||||
|
|
||||||
LibraryConnector[] connectors = context.LibraryConnectors.ToArray();
|
LibraryConnector[] connectors = context.LibraryConnectors.ToArray();
|
||||||
|
|
||||||
return Ok(connectors);
|
return Ok(connectors);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns ToFileLibrary-Connector with requested ID
|
/// Returns <see cref="LibraryConnector"/> with <paramref name="LibraryConnectorId"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="LibraryControllerId">ToFileLibrary-Connector-ID</param>
|
/// <param name="LibraryConnectorId"><see cref="LibraryConnector"/>.Key</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">Connector with ID not found.</response>
|
/// <response code="404"><see cref="LibraryConnector"/> with <paramref name="LibraryConnectorId"/> not found.</response>
|
||||||
[HttpGet("{LibraryControllerId}")]
|
[HttpGet("{LibraryConnectorId}")]
|
||||||
[ProducesResponseType<LibraryConnector>(Status200OK, "application/json")]
|
[ProducesResponseType<LibraryConnector>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public IActionResult GetConnector(string LibraryControllerId)
|
public IActionResult GetConnector(string LibraryConnectorId)
|
||||||
{
|
{
|
||||||
LibraryConnector? ret = context.LibraryConnectors.Find(LibraryControllerId);
|
LibraryContext context = scope.ServiceProvider.GetRequiredService<LibraryContext>();
|
||||||
return (ret is not null) switch
|
if (context.LibraryConnectors.Find(LibraryConnectorId) is not { } connector)
|
||||||
{
|
return NotFound();
|
||||||
true => Ok(ret),
|
|
||||||
false => NotFound()
|
return Ok(connector);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new ToFileLibrary-Connector
|
/// Creates a new <see cref="LibraryConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="libraryConnector">ToFileLibrary-Connector</param>
|
/// <param name="libraryConnector"></param>
|
||||||
/// <response code="201"></response>
|
/// <response code="201"></response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut]
|
[HttpPut]
|
||||||
@ -54,46 +56,36 @@ public class LibraryConnectorController(LibraryContext context, ILog Log) : Cont
|
|||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateConnector([FromBody]LibraryConnector libraryConnector)
|
public IActionResult CreateConnector([FromBody]LibraryConnector libraryConnector)
|
||||||
{
|
{
|
||||||
try
|
LibraryContext context = scope.ServiceProvider.GetRequiredService<LibraryContext>();
|
||||||
{
|
|
||||||
context.LibraryConnectors.Add(libraryConnector);
|
context.LibraryConnectors.Add(libraryConnector);
|
||||||
context.SaveChanges();
|
|
||||||
|
if(context.Sync().Result is { } errorMessage)
|
||||||
|
return StatusCode(Status500InternalServerError, errorMessage);
|
||||||
return Created();
|
return Created();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the ToFileLibrary-Connector with the requested ID
|
/// Deletes <see cref="LibraryConnector"/> with <paramref name="LibraryConnectorId"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="LibraryControllerId">ToFileLibrary-Connector-ID</param>
|
/// <param name="LibraryConnectorId">ToFileLibrary-Connector-ID</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">Connector with ID not found.</response>
|
/// <response code="404"><see cref="LibraryConnector"/> with <<paramref name="LibraryConnectorId"/> not found.</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpDelete("{LibraryControllerId}")]
|
[HttpDelete("{LibraryConnectorId}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult DeleteConnector(string LibraryControllerId)
|
public IActionResult DeleteConnector(string LibraryConnectorId)
|
||||||
{
|
{
|
||||||
try
|
LibraryContext context = scope.ServiceProvider.GetRequiredService<LibraryContext>();
|
||||||
{
|
if (context.LibraryConnectors.Find(LibraryConnectorId) is not { } connector)
|
||||||
LibraryConnector? ret = context.LibraryConnectors.Find(LibraryControllerId);
|
|
||||||
if (ret is null)
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
context.Remove(ret);
|
context.LibraryConnectors.Remove(connector);
|
||||||
context.SaveChanges();
|
|
||||||
|
if(context.Sync().Result is { } errorMessage)
|
||||||
|
return StatusCode(Status500InternalServerError, errorMessage);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,163 +0,0 @@
|
|||||||
using API.APIEndpointRecords;
|
|
||||||
using API.Schema.MangaContext;
|
|
||||||
using Asp.Versioning;
|
|
||||||
using log4net;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
|
||||||
|
|
||||||
namespace API.Controllers;
|
|
||||||
|
|
||||||
[ApiVersion(2)]
|
|
||||||
[ApiController]
|
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
|
||||||
public class LocalLibrariesController(MangaContext context, ILog Log) : Controller
|
|
||||||
{
|
|
||||||
[HttpGet]
|
|
||||||
[ProducesResponseType<FileLibrary[]>(Status200OK, "application/json")]
|
|
||||||
public IActionResult GetLocalLibraries()
|
|
||||||
{
|
|
||||||
return Ok(context.LocalLibraries);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{LibraryId}")]
|
|
||||||
[ProducesResponseType<FileLibrary>(Status200OK, "application/json")]
|
|
||||||
[ProducesResponseType(Status404NotFound)]
|
|
||||||
public IActionResult GetLocalLibrary(string LibraryId)
|
|
||||||
{
|
|
||||||
FileLibrary? library = context.LocalLibraries.Find(LibraryId);
|
|
||||||
if (library is null)
|
|
||||||
return NotFound();
|
|
||||||
return Ok(library);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPatch("{LibraryId}")]
|
|
||||||
[ProducesResponseType(Status200OK)]
|
|
||||||
[ProducesResponseType(Status404NotFound)]
|
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
|
||||||
public IActionResult UpdateLocalLibrary(string LibraryId, [FromBody]NewLibraryRecord record)
|
|
||||||
{
|
|
||||||
FileLibrary? library = context.LocalLibraries.Find(LibraryId);
|
|
||||||
if (library is null)
|
|
||||||
return NotFound();
|
|
||||||
if (record.Validate() == false)
|
|
||||||
return BadRequest();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
library.LibraryName = record.name;
|
|
||||||
library.BasePath = record.path;
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPatch("{LibraryId}/ChangeBasePath")]
|
|
||||||
[ProducesResponseType(Status200OK)]
|
|
||||||
[ProducesResponseType(Status404NotFound)]
|
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
|
||||||
public IActionResult ChangeLibraryBasePath(string LibraryId, [FromBody] string newBasePath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
FileLibrary? library = context.LocalLibraries.Find(LibraryId);
|
|
||||||
if (library is null)
|
|
||||||
return NotFound();
|
|
||||||
|
|
||||||
if (false) //TODO implement path check
|
|
||||||
return BadRequest();
|
|
||||||
|
|
||||||
library.BasePath = newBasePath;
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPatch("{LibraryId}/ChangeName")]
|
|
||||||
[ProducesResponseType(Status200OK)]
|
|
||||||
[ProducesResponseType(Status404NotFound)]
|
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
|
||||||
public IActionResult ChangeLibraryName(string LibraryId, [FromBody] string newName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
FileLibrary? library = context.LocalLibraries.Find(LibraryId);
|
|
||||||
if (library is null)
|
|
||||||
return NotFound();
|
|
||||||
|
|
||||||
if(newName.Length < 1)
|
|
||||||
return BadRequest();
|
|
||||||
|
|
||||||
library.LibraryName = newName;
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut]
|
|
||||||
[ProducesResponseType<FileLibrary>(Status200OK, "application/json")]
|
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
|
||||||
public IActionResult CreateNewLibrary([FromBody]NewLibraryRecord library)
|
|
||||||
{
|
|
||||||
if (library.Validate() == false)
|
|
||||||
return BadRequest();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
FileLibrary newFileLibrary = new (library.path, library.name);
|
|
||||||
context.LocalLibraries.Add(newFileLibrary);
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
return Ok(newFileLibrary);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("{LibraryId}")]
|
|
||||||
[ProducesResponseType(Status200OK)]
|
|
||||||
[ProducesResponseType(Status404NotFound)]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
|
||||||
public IActionResult DeleteLocalLibrary(string LibraryId)
|
|
||||||
{
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
FileLibrary? library = context.LocalLibraries.Find(LibraryId);
|
|
||||||
if (library is null)
|
|
||||||
return NotFound();
|
|
||||||
context.Remove(library);
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,107 +1,95 @@
|
|||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using API.Schema.MangaContext.MangaConnectors;
|
using API.Schema.MangaContext.MangaConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using log4net;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class MangaConnectorController(MangaContext context, ILog Log) : Controller
|
public class MangaConnectorController(IServiceScope scope) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all available Connectors (Scanlation-Sites)
|
/// Get all <see cref="MangaConnector"/> (Scanlation-Sites)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200">Names of <see cref="MangaConnector"/> (Scanlation-Sites)</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetConnectors()
|
public IActionResult GetConnectors()
|
||||||
{
|
{
|
||||||
MangaConnector[] connectors = context.MangaConnectors.ToArray();
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
return Ok(connectors);
|
return Ok(context.MangaConnectors.Select(c => c.Name).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the MangaConnector with the requested Name
|
/// Returns the <see cref="MangaConnector"/> (Scanlation-Sites) with the requested Name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaConnectorName"></param>
|
/// <param name="MangaConnectorName"><see cref="MangaConnector"/>.Name</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">Connector with ID not found.</response>
|
/// <response code="404"><see cref="MangaConnector"/> (Scanlation-Sites) with Name not found.</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
|
||||||
[HttpGet("{MangaConnectorName}")]
|
[HttpGet("{MangaConnectorName}")]
|
||||||
[ProducesResponseType<MangaConnector>(Status200OK, "application/json")]
|
[ProducesResponseType<MangaConnector>(Status200OK, "application/json")]
|
||||||
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public IActionResult GetConnector(string MangaConnectorName)
|
public IActionResult GetConnector(string MangaConnectorName)
|
||||||
{
|
{
|
||||||
try
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
{
|
|
||||||
if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector)
|
if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
return Ok(connector);
|
return Ok(connector);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all enabled Connectors (Scanlation-Sites)
|
/// Get all enabled <see cref="MangaConnector"/> (Scanlation-Sites)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("enabled")]
|
[HttpGet("Enabled")]
|
||||||
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetEnabledConnectors()
|
public IActionResult GetEnabledConnectors()
|
||||||
{
|
{
|
||||||
MangaConnector[] connectors = context.MangaConnectors.Where(c => c.Enabled == true).ToArray();
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
return Ok(connectors);
|
|
||||||
|
return Ok(context.MangaConnectors.Where(c => c.Enabled).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all disabled Connectors (Scanlation-Sites)
|
/// Get all disabled <see cref="MangaConnector"/> (Scanlation-Sites)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("disabled")]
|
[HttpGet("Disabled")]
|
||||||
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
[ProducesResponseType<MangaConnector[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetDisabledConnectors()
|
public IActionResult GetDisabledConnectors()
|
||||||
{
|
{
|
||||||
MangaConnector[] connectors = context.MangaConnectors.Where(c => c.Enabled == false).ToArray();
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
return Ok(connectors);
|
|
||||||
|
return Ok(context.MangaConnectors.Where(c => c.Enabled == false).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enabled or disables a Connector
|
/// Enabled or disables <see cref="MangaConnector"/> (Scanlation-Sites) with Name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaConnectorName">ID of the connector</param>
|
/// <param name="MangaConnectorName"><see cref="MangaConnector"/>.Name</param>
|
||||||
/// <param name="enabled">Set true to enable</param>
|
/// <param name="Enabled">Set true to enable, false to disable</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="202"></response>
|
||||||
/// <response code="404">Connector with ID not found.</response>
|
/// <response code="404"><see cref="MangaConnector"/> (Scanlation-Sites) with Name not found.</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPatch("{MangaConnectorName}/SetEnabled/{enabled}")]
|
[HttpPatch("{MangaConnectorName}/SetEnabled/{Enabled}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status202Accepted)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult SetEnabled(string MangaConnectorName, bool enabled)
|
public IActionResult SetEnabled(string MangaConnectorName, bool Enabled)
|
||||||
{
|
{
|
||||||
try
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
{
|
if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector)
|
||||||
MangaConnector? connector = context.MangaConnectors.Find(MangaConnectorName);
|
|
||||||
if (connector is null)
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
connector.Enabled = enabled;
|
connector.Enabled = Enabled;
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
return Ok();
|
if(context.Sync().Result is { } errorMessage)
|
||||||
}
|
return StatusCode(Status500InternalServerError, errorMessage);
|
||||||
catch (Exception e)
|
return Accepted();
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,9 +2,7 @@
|
|||||||
using API.Schema.MangaContext.MangaConnectors;
|
using API.Schema.MangaContext.MangaConnectors;
|
||||||
using API.Workers;
|
using API.Workers;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using log4net;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||||
@ -352,7 +350,7 @@ public class MangaController(IServiceScope scope) : Controller
|
|||||||
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
if (context.Mangas.Find(MangaId) is not { } manga)
|
if (context.Mangas.Find(MangaId) is not { } manga)
|
||||||
return NotFound(nameof(MangaId));
|
return NotFound(nameof(MangaId));
|
||||||
if(context.LocalLibraries.Find(LibraryId) is not { } library)
|
if(context.FileLibraries.Find(LibraryId) is not { } library)
|
||||||
return NotFound(nameof(LibraryId));
|
return NotFound(nameof(LibraryId));
|
||||||
|
|
||||||
MoveMangaLibraryWorker moveLibrary = new(manga, library, scope);
|
MoveMangaLibraryWorker moveLibrary = new(manga, library, scope);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using API.Schema.MangaContext.MetadataFetchers;
|
using API.Schema.MangaContext.MetadataFetchers;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using log4net;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
@ -12,47 +11,51 @@ namespace API.Controllers;
|
|||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class MetadataFetcherController(MangaContext context, ILog Log) : Controller
|
public class MetadataFetcherController(IServiceScope scope) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all available Connectors (Metadata-Sites)
|
/// Get all <see cref="MetadataFetcher"/> (Metadata-Sites)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200">Names of Metadata-Fetchers</response>
|
/// <response code="200">Names of <see cref="MetadataFetcher"/> (Metadata-Sites)</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetConnectors()
|
public IActionResult GetConnectors()
|
||||||
{
|
{
|
||||||
string[] connectors = Tranga.MetadataFetchers.Select(f => f.MetadataFetcherName).ToArray();
|
return Ok(Tranga.MetadataFetchers.Select(m => m.Name).ToArray());
|
||||||
return Ok(connectors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all Mangas which have a linked Metadata-Provider
|
/// Returns all <see cref="MetadataEntry"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("Links")]
|
[HttpGet("Links")]
|
||||||
[ProducesResponseType<MetadataEntry>(Status200OK, "application/json")]
|
[ProducesResponseType<MetadataEntry[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetLinkedEntries()
|
public IActionResult GetLinkedEntries()
|
||||||
{
|
{
|
||||||
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
|
|
||||||
return Ok(context.MetadataEntries.ToArray());
|
return Ok(context.MetadataEntries.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Searches Metadata-Provider for Manga-Metadata
|
/// Searches <see cref="MetadataFetcher"/> (Metadata-Sites) for Manga-Metadata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="searchTerm">Instead of using the Manga for search, use a specific term</param>
|
/// <param name="MangaId"><see cref="Manga"/>.Key</param>
|
||||||
|
/// <param name="MetadataFetcherName"><see cref="MetadataFetcher"/>.Name</param>
|
||||||
|
/// <param name="searchTerm">Instead of using the <paramref name="MangaId"/> for search on Website, use a specific term</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
/// <response code="400"><see cref="MetadataFetcher"/> (Metadata-Sites) with <paramref name="MetadataFetcherName"/> does not exist</response>
|
||||||
/// <response code="404">Manga with ID not found</response>
|
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found</response>
|
||||||
[HttpPost("{MetadataFetcherName}/SearchManga/{MangaId}")]
|
[HttpPost("{MetadataFetcherName}/SearchManga/{MangaId}")]
|
||||||
[ProducesResponseType<MetadataSearchResult[]>(Status200OK, "application/json")]
|
[ProducesResponseType<MetadataSearchResult[]>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public IActionResult SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null)
|
public IActionResult SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null)
|
||||||
{
|
{
|
||||||
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
|
|
||||||
MetadataSearchResult[] searchResults = searchTerm is null ? fetcher.SearchMetadataEntry(manga) : fetcher.SearchMetadataEntry(searchTerm);
|
MetadataSearchResult[] searchResults = searchTerm is null ? fetcher.SearchMetadataEntry(manga) : fetcher.SearchMetadataEntry(searchTerm);
|
||||||
@ -60,45 +63,43 @@ public class MetadataFetcherController(MangaContext context, ILog Log) : Control
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Links Metadata-Provider using Provider-Specific Identifier to Manga
|
/// Links <see cref="MetadataFetcher"/> (Metadata-Sites) using Provider-Specific Identifier to <see cref="Manga"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="MangaId"><see cref="Manga"/>.Key</param>
|
||||||
|
/// <param name="MetadataFetcherName"><see cref="MetadataFetcher"/>.Name</param>
|
||||||
|
/// <param name="Identifier"><see cref="MetadataFetcherName"/>-Specific ID</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
/// <response code="400"><see cref="MetadataFetcher"/> (Metadata-Sites) with <paramref name="MetadataFetcherName"/> does not exist</response>
|
||||||
/// <response code="404">Manga with ID not found</response>
|
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPost("{MetadataFetcherName}/Link/{MangaId}")]
|
[HttpPost("{MetadataFetcherName}/Link/{MangaId}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType<MetadataEntry>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult LinkMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody]string Identifier)
|
public IActionResult LinkMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody]string Identifier)
|
||||||
{
|
{
|
||||||
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
if(context.Mangas.Find(MangaId) is not { } manga)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier);
|
|
||||||
|
|
||||||
try
|
MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier);
|
||||||
{
|
|
||||||
context.MetadataEntries.Add(entry);
|
context.MetadataEntries.Add(entry);
|
||||||
context.SaveChanges();
|
|
||||||
}
|
if(context.Sync().Result is { } errorMessage)
|
||||||
catch (Exception e)
|
return StatusCode(Status500InternalServerError, errorMessage);
|
||||||
{
|
return Ok(entry);
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
return Ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Un-Links Metadata-Provider using Provider-Specific Identifier to Manga
|
/// Un-Links <see cref="MetadataFetcher"/> (Metadata-Sites) from <see cref="Manga"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="400">Metadata-fetcher with Name does not exist</response>
|
/// <response code="400"><see cref="MetadataFetcher"/> (Metadata-Sites) with <paramref name="MetadataFetcherName"/> does not exist</response>
|
||||||
/// <response code="404">Manga with ID not found</response>
|
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found</response>
|
||||||
/// <response code="412">No Entry linking Manga and Metadata-Provider found</response>
|
/// <response code="412">No <see cref="MetadataEntry"/> linking <see cref="Manga"/> and <see cref="MetadataFetcher"/> found</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPost("{MetadataFetcherName}/Unlink/{MangaId}")]
|
[HttpPost("{MetadataFetcherName}/Unlink/{MangaId}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
@ -108,58 +109,18 @@ public class MetadataFetcherController(MangaContext context, ILog Log) : Control
|
|||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult UnlinkMangaMetadata(string MangaId, string MetadataFetcherName)
|
public IActionResult UnlinkMangaMetadata(string MangaId, string MetadataFetcherName)
|
||||||
{
|
{
|
||||||
if(context.Mangas.Find(MangaId) is not { } manga)
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
|
if(context.Mangas.Find(MangaId) is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.MetadataFetcherName == MetadataFetcherName) is not { } fetcher)
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is null)
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
MetadataEntry? entry = context.MetadataEntries.FirstOrDefault(e => e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName);
|
if(context.MetadataEntries.FirstOrDefault(e => e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName) is not { } entry)
|
||||||
if (entry is null)
|
|
||||||
return StatusCode(Status412PreconditionFailed, "No entry found");
|
return StatusCode(Status412PreconditionFailed, "No entry found");
|
||||||
try
|
|
||||||
{
|
|
||||||
context.MetadataEntries.Remove(entry);
|
|
||||||
context.SaveChanges();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
context.Remove(entry);
|
||||||
/// Tries linking a Manga to a Metadata-Provider-Site
|
|
||||||
/// </summary>
|
if(context.Sync().Result is { } errorMessage)
|
||||||
/// <response code="200"></response>
|
return StatusCode(Status500InternalServerError, errorMessage);
|
||||||
/// <response code="400">MetadataFetcher Name is invalid</response>
|
|
||||||
/// <response code="404">Manga has no linked entry with MetadataFetcher</response>
|
|
||||||
/// <response code="500">Error during Database Operation</response>
|
|
||||||
[HttpPost("{MetadataFetcherName}/{MangaId}/UpdateMetadata")]
|
|
||||||
[ProducesResponseType(Status200OK)]
|
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
|
||||||
[ProducesResponseType(Status404NotFound)]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
|
||||||
public IActionResult UpdateMetadata(string MangaId, string MetadataFetcherName)
|
|
||||||
{
|
|
||||||
if(Tranga.MetadataFetchers
|
|
||||||
.FirstOrDefault(f =>
|
|
||||||
f.MetadataFetcherName.Equals(MetadataFetcherName, StringComparison.InvariantCultureIgnoreCase)) is not { } fetcher)
|
|
||||||
return BadRequest();
|
|
||||||
MetadataEntry? entry = context.MetadataEntries
|
|
||||||
.FirstOrDefault(e =>
|
|
||||||
e.MangaId == MangaId && e.MetadataFetcherName.Equals(MetadataFetcherName, StringComparison.InvariantCultureIgnoreCase));
|
|
||||||
if (entry is null)
|
|
||||||
return NotFound();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
fetcher.UpdateMetadata(entry, context);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,9 +3,9 @@ using API.APIEndpointRecords;
|
|||||||
using API.Schema.NotificationsContext;
|
using API.Schema.NotificationsContext;
|
||||||
using API.Schema.NotificationsContext.NotificationConnectors;
|
using API.Schema.NotificationsContext.NotificationConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using log4net;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
@ -13,121 +13,103 @@ namespace API.Controllers;
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class NotificationConnectorController(NotificationsContext context, ILog Log) : Controller
|
public class NotificationConnectorController(IServiceScope scope) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all configured Notification-Connectors
|
/// Gets all configured <see cref="NotificationConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<NotificationConnector[]>(Status200OK, "application/json")]
|
[ProducesResponseType<NotificationConnector[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetAllConnectors()
|
public IActionResult GetAllConnectors()
|
||||||
{
|
{
|
||||||
NotificationConnector[] ret = context.NotificationConnectors.ToArray();
|
NotificationsContext context = scope.ServiceProvider.GetRequiredService<NotificationsContext>();
|
||||||
return Ok(ret);
|
|
||||||
|
return Ok(context.NotificationConnectors.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns Notification-Connector with requested ID
|
/// Returns <see cref="NotificationConnector"/> with requested Name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="NotificationConnectorId">Notification-Connector-ID</param>
|
/// <param name="Name"><see cref="NotificationConnector"/>.Name</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">NotificationConnector with ID not found</response>
|
/// <response code="404"><see cref="NotificationConnector"/> with <paramref name="Name"/> not found</response>
|
||||||
[HttpGet("{NotificationConnectorId}")]
|
[HttpGet("{Name}")]
|
||||||
[ProducesResponseType<NotificationConnector>(Status200OK, "application/json")]
|
[ProducesResponseType<NotificationConnector>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public IActionResult GetConnector(string NotificationConnectorId)
|
public IActionResult GetConnector(string Name)
|
||||||
{
|
{
|
||||||
NotificationConnector? ret = context.NotificationConnectors.Find(NotificationConnectorId);
|
NotificationsContext context = scope.ServiceProvider.GetRequiredService<NotificationsContext>();
|
||||||
return (ret is not null) switch
|
if(context.NotificationConnectors.Find(Name) is not { } connector)
|
||||||
{
|
return NotFound();
|
||||||
true => Ok(ret),
|
|
||||||
false => NotFound()
|
return Ok(connector);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new REST-Notification-Connector
|
/// Creates a new <see cref="NotificationConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Formatting placeholders: "%title" and "%text" can be placed in url, header-values and body and will be replaced when notifications are sent</remarks>
|
/// <remarks>Formatting placeholders: "%title" and "%text" can be placed in url, header-values and body and will be replaced when notifications are sent</remarks>
|
||||||
/// <param name="notificationConnector">Notification-Connector</param>
|
/// <response code="201"></response>
|
||||||
/// <response code="201">ID of new connector</response>
|
|
||||||
/// <response code="409">A NotificationConnector with name already exists</response>
|
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut]
|
[HttpPut]
|
||||||
[ProducesResponseType<string>(Status201Created, "application/json")]
|
[ProducesResponseType(Status201Created)]
|
||||||
[ProducesResponseType(Status409Conflict)]
|
[ProducesResponseType(Status409Conflict)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateConnector([FromBody]NotificationConnector notificationConnector)
|
public IActionResult CreateConnector([FromBody]NotificationConnector notificationConnector)
|
||||||
{
|
{
|
||||||
if (context.NotificationConnectors.Find(notificationConnector.Name) is not null)
|
NotificationsContext context = scope.ServiceProvider.GetRequiredService<NotificationsContext>();
|
||||||
return Conflict();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
context.NotificationConnectors.Add(notificationConnector);
|
context.NotificationConnectors.Add(notificationConnector);
|
||||||
context.SaveChanges();
|
|
||||||
return Created(notificationConnector.Name, notificationConnector);
|
if(context.Sync().Result is { } errorMessage)
|
||||||
}
|
return StatusCode(Status500InternalServerError, errorMessage);
|
||||||
catch (Exception e)
|
return Created();
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new Gotify-Notification-Connector
|
/// Creates a new Gotify-<see cref="NotificationConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Priority needs to be between 0 and 10</remarks>
|
/// <remarks>Priority needs to be between 0 and 10</remarks>
|
||||||
/// <response code="201">ID of new connector</response>
|
/// <response code="201"></response>
|
||||||
/// <response code="400"></response>
|
|
||||||
/// <response code="409">A NotificationConnector with name already exists</response>
|
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut("Gotify")]
|
[HttpPut("Gotify")]
|
||||||
[ProducesResponseType<string>(Status201Created, "application/json")]
|
[ProducesResponseType<string>(Status201Created, "application/json")]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
|
||||||
[ProducesResponseType(Status409Conflict)]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateGotifyConnector([FromBody]GotifyRecord gotifyData)
|
public IActionResult CreateGotifyConnector([FromBody]GotifyRecord gotifyData)
|
||||||
{
|
{
|
||||||
if(!gotifyData.Validate())
|
//TODO Validate Data
|
||||||
return BadRequest();
|
|
||||||
|
|
||||||
NotificationConnector gotifyConnector = new NotificationConnector(TokenGen.CreateToken("Gotify"),
|
NotificationConnector gotifyConnector = new (gotifyData.Name,
|
||||||
gotifyData.endpoint,
|
gotifyData.Endpoint,
|
||||||
new Dictionary<string, string>() { { "X-Gotify-IDOnConnector", gotifyData.appToken } },
|
new Dictionary<string, string>() { { "X-Gotify-IDOnConnector", gotifyData.AppToken } },
|
||||||
"POST",
|
"POST",
|
||||||
$"{{\"message\": \"%text\", \"title\": \"%title\", \"priority\": {gotifyData.priority}}}");
|
$"{{\"message\": \"%text\", \"title\": \"%title\", \"Priority\": {gotifyData.Priority}}}");
|
||||||
return CreateConnector(gotifyConnector);
|
return CreateConnector(gotifyConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new Ntfy-Notification-Connector
|
/// Creates a new Ntfy-<see cref="NotificationConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Priority needs to be between 1 and 5</remarks>
|
/// <remarks>Priority needs to be between 1 and 5</remarks>
|
||||||
/// <response code="201">ID of new connector</response>
|
/// <response code="201"></response>
|
||||||
/// <response code="400"></response>
|
|
||||||
/// <response code="409">A NotificationConnector with name already exists</response>
|
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut("Ntfy")]
|
[HttpPut("Ntfy")]
|
||||||
[ProducesResponseType<string>(Status201Created, "application/json")]
|
[ProducesResponseType<string>(Status201Created, "application/json")]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
|
||||||
[ProducesResponseType(Status409Conflict)]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateNtfyConnector([FromBody]NtfyRecord ntfyRecord)
|
public IActionResult CreateNtfyConnector([FromBody]NtfyRecord ntfyRecord)
|
||||||
{
|
{
|
||||||
if(!ntfyRecord.Validate())
|
//TODO Validate Data
|
||||||
return BadRequest();
|
|
||||||
|
|
||||||
string authHeader = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ntfyRecord.username}:{ntfyRecord.password}"));
|
string authHeader = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ntfyRecord.Username}:{ntfyRecord.Password}"));
|
||||||
string auth = Convert.ToBase64String(Encoding.UTF8.GetBytes(authHeader)).Replace("=","");
|
string auth = Convert.ToBase64String(Encoding.UTF8.GetBytes(authHeader)).Replace("=","");
|
||||||
|
|
||||||
NotificationConnector ntfyConnector = new (TokenGen.CreateToken("Ntfy"),
|
NotificationConnector ntfyConnector = new (ntfyRecord.Name,
|
||||||
$"{ntfyRecord.endpoint}?auth={auth}",
|
$"{ntfyRecord.Endpoint}/{ntfyRecord.Topic}?auth={auth}",
|
||||||
new Dictionary<string, string>()
|
new Dictionary<string, string>()
|
||||||
{
|
{
|
||||||
{"Title", "%title"},
|
{"Title", "%title"},
|
||||||
{"Priority", ntfyRecord.priority.ToString()},
|
{"Priority", ntfyRecord.Priority.ToString()},
|
||||||
},
|
},
|
||||||
"POST",
|
"POST",
|
||||||
"%text");
|
"%text");
|
||||||
@ -135,58 +117,47 @@ public class NotificationConnectorController(NotificationsContext context, ILog
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new Pushover-Notification-Connector
|
/// Creates a new Pushover-<see cref="NotificationConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>https://pushover.net/api</remarks>
|
/// <remarks>https://pushover.net/api</remarks>
|
||||||
/// <response code="201">ID of new connector</response>
|
/// <response code="201">ID of new connector</response>
|
||||||
/// <response code="400"></response>
|
|
||||||
/// <response code="409">A NotificationConnector with name already exists</response>
|
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPut("Pushover")]
|
[HttpPut("Pushover")]
|
||||||
[ProducesResponseType<string>(Status201Created, "application/json")]
|
[ProducesResponseType<string>(Status201Created, "application/json")]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
|
||||||
[ProducesResponseType(Status409Conflict)]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreatePushoverConnector([FromBody]PushoverRecord pushoverRecord)
|
public IActionResult CreatePushoverConnector([FromBody]PushoverRecord pushoverRecord)
|
||||||
{
|
{
|
||||||
if(!pushoverRecord.Validate())
|
//TODO Validate Data
|
||||||
return BadRequest();
|
|
||||||
|
|
||||||
NotificationConnector pushoverConnector = new (TokenGen.CreateToken("Pushover"),
|
NotificationConnector pushoverConnector = new (pushoverRecord.Name,
|
||||||
$"https://api.pushover.net/1/messages.json",
|
$"https://api.pushover.net/1/messages.json",
|
||||||
new Dictionary<string, string>(),
|
new Dictionary<string, string>(),
|
||||||
"POST",
|
"POST",
|
||||||
$"{{\"token\": \"{pushoverRecord.apptoken}\", \"user\": \"{pushoverRecord.user}\", \"message:\":\"%text\", \"%title\" }}");
|
$"{{\"token\": \"{pushoverRecord.AppToken}\", \"user\": \"{pushoverRecord.User}\", \"message:\":\"%text\", \"%title\" }}");
|
||||||
return CreateConnector(pushoverConnector);
|
return CreateConnector(pushoverConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the Notification-Connector with the requested ID
|
/// Deletes the <see cref="NotificationConnector"/> with the requested Name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="NotificationConnectorId">Notification-Connector-ID</param>
|
/// <param name="Name"><see cref="NotificationConnector"/>.Name</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">NotificationConnector with ID not found</response>
|
/// <response code="404"><see cref="NotificationConnector"/> with Name not found</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpDelete("{NotificationConnectorId}")]
|
[HttpDelete("{Name}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult DeleteConnector(string NotificationConnectorId)
|
public IActionResult DeleteConnector(string Name)
|
||||||
{
|
{
|
||||||
try
|
NotificationsContext context = scope.ServiceProvider.GetRequiredService<NotificationsContext>();
|
||||||
{
|
if(context.NotificationConnectors.Find(Name) is not { } connector)
|
||||||
NotificationConnector? ret = context.NotificationConnectors.Find(NotificationConnectorId);
|
|
||||||
if(ret is null)
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
context.Remove(ret);
|
context.NotificationConnectors.Remove(connector);
|
||||||
context.SaveChanges();
|
|
||||||
return Ok();
|
if(context.Sync().Result is { } errorMessage)
|
||||||
}
|
return StatusCode(Status500InternalServerError, errorMessage);
|
||||||
catch (Exception e)
|
return Created();
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,5 @@
|
|||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using log4net;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
@ -10,102 +9,74 @@ namespace API.Controllers;
|
|||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class QueryController(MangaContext context, ILog Log) : Controller
|
public class QueryController(IServiceScope scope) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the Author-Information for Author-ID
|
/// Returns the <see cref="Author"/> with <paramref name="AuthorId"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="AuthorId">Author-Id</param>
|
/// <param name="AuthorId"><see cref="Author"/>.Key</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">Author with ID not found</response>
|
/// <response code="404"><see cref="Author"/> with <paramref name="AuthorId"/> not found</response>
|
||||||
[HttpGet("Author/{AuthorId}")]
|
[HttpGet("Author/{AuthorId}")]
|
||||||
[ProducesResponseType<Author>(Status200OK, "application/json")]
|
[ProducesResponseType<Author>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public IActionResult GetAuthor(string AuthorId)
|
public IActionResult GetAuthor(string AuthorId)
|
||||||
{
|
{
|
||||||
Author? ret = context.Authors.Find(AuthorId);
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
if (ret is null)
|
if (context.Authors.Find(AuthorId) is not { } author)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return Ok(ret);
|
|
||||||
|
return Ok(author);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all Mangas which where Authored by Author with AuthorId
|
/// Returns all <see cref="Manga"/> which where Authored by <see cref="Author"/> with <paramref name="AuthorId"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="AuthorId">Author-ID</param>
|
/// <param name="AuthorId"><see cref="Author"/>.Key</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">Author not found</response>
|
/// <response code="404"><see cref="Author"/> with <paramref name="AuthorId"/></response>
|
||||||
[HttpGet("Mangas/WithAuthorId/{AuthorId}")]
|
[HttpGet("Mangas/WithAuthorId/{AuthorId}")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetMangaWithAuthorIds(string AuthorId)
|
public IActionResult GetMangaWithAuthorIds(string AuthorId)
|
||||||
{
|
{
|
||||||
if(context.Authors.Find(AuthorId) is not { } a)
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
|
if (context.Authors.Find(AuthorId) is not { } author)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return Ok(context.Mangas.Where(m => m.Authors.Contains(a)));
|
|
||||||
}
|
return Ok(context.Mangas.Where(m => m.Authors.Contains(author)));
|
||||||
/*
|
|
||||||
/// <summary>
|
|
||||||
/// Returns Link-Information for Link-Id
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="LinkId"></param>
|
|
||||||
/// <response code="200"></response>
|
|
||||||
/// <response code="404">Link with ID not found</response>
|
|
||||||
[HttpGet("Link/{LinkId}")]
|
|
||||||
[ProducesResponseType<Link>(Status200OK, "application/json")]
|
|
||||||
[ProducesResponseType(Status404NotFound)]
|
|
||||||
public IActionResult GetLink(string LinkId)
|
|
||||||
{
|
|
||||||
Link? ret = context.Links.Find(LinkId);
|
|
||||||
if (ret is null)
|
|
||||||
return NotFound();
|
|
||||||
return Ok(ret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns AltTitle-Information for AltTitle-Id
|
/// Returns all <see cref="Manga"/> with <see cref="Tag"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="AltTitleId"></param>
|
/// <param name="Tag"><see cref="Tag"/>.Tag</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">AltTitle with ID not found</response>
|
/// <response code="404"><see cref="Tag"/> not found</response>
|
||||||
[HttpGet("AltTitle/{AltTitleId}")]
|
|
||||||
[ProducesResponseType<AltTitle>(Status200OK, "application/json")]
|
|
||||||
[ProducesResponseType(Status404NotFound)]
|
|
||||||
public IActionResult GetAltTitle(string AltTitleId)
|
|
||||||
{
|
|
||||||
AltTitle? ret = context.AltTitles.Find(AltTitleId);
|
|
||||||
if (ret is null)
|
|
||||||
return NotFound();
|
|
||||||
return Ok(ret);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns all Obj with Tag
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Tag"></param>
|
|
||||||
/// <response code="200"></response>
|
|
||||||
/// <response code="404">Tag not found</response>
|
|
||||||
[HttpGet("Mangas/WithTag/{Tag}")]
|
[HttpGet("Mangas/WithTag/{Tag}")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetMangasWithTag(string Tag)
|
public IActionResult GetMangasWithTag(string Tag)
|
||||||
{
|
{
|
||||||
if(context.Tags.Find(Tag) is not { } t)
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
|
if (context.Tags.Find(Tag) is not { } tag)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return Ok(context.Mangas.Where(m => m.MangaTags.Contains(t)));
|
|
||||||
|
return Ok(context.Mangas.Where(m => m.MangaTags.Contains(tag)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns Chapter-Information for Chapter-Id
|
/// Returns <see cref="Chapter"/> with <paramref name="ChapterId"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ChapterId"></param>
|
/// <param name="ChapterId"><see cref="Chapter"/>.Key</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">Chapter with ID not found</response>
|
/// <response code="404"><see cref="Chapter"/> with <paramref name="ChapterId"/> not found</response>
|
||||||
[HttpGet("Chapter/{ChapterId}")]
|
[HttpGet("Chapter/{ChapterId}")]
|
||||||
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
||||||
public IActionResult GetChapter(string ChapterId)
|
public IActionResult GetChapter(string ChapterId)
|
||||||
{
|
{
|
||||||
Chapter? ret = context.Chapters.Find(ChapterId);
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
if (ret is null)
|
if (context.Chapters.Find(ChapterId) is not { } chapter)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return Ok(ret);
|
|
||||||
|
return Ok(chapter);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,7 @@
|
|||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
|
using API.Schema.MangaContext.MangaConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using log4net;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Soenneker.Utils.String.NeedlemanWunsch;
|
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
@ -12,98 +10,69 @@ namespace API.Controllers;
|
|||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class SearchController(MangaContext context, ILog Log) : Controller
|
public class SearchController(IServiceScope scope) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initiate a search for a Obj on a specific Connector
|
/// Initiate a search for a <see cref="Manga"/> on <see cref="MangaConnector"/> with searchTerm
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="MangaConnectorName"></param>
|
/// <param name="MangaConnectorName"><see cref="MangaConnector"/>.Name</param>
|
||||||
/// <param name="Query"></param>
|
/// <param name="Query">searchTerm</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404">MangaConnector with ID not found</response>
|
/// <response code="404"><see cref="MangaConnector"/> with Name not found</response>
|
||||||
/// <response code="406">MangaConnector with ID is disabled</response>
|
/// <response code="412"><see cref="MangaConnector"/> with Name is disabled</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
|
||||||
[HttpGet("{MangaConnectorName}/{Query}")]
|
[HttpGet("{MangaConnectorName}/{Query}")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType(Status406NotAcceptable)]
|
[ProducesResponseType(Status406NotAcceptable)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
|
||||||
public IActionResult SearchManga(string MangaConnectorName, string Query)
|
public IActionResult SearchManga(string MangaConnectorName, string Query)
|
||||||
{
|
{
|
||||||
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector)
|
if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
else if (connector.Enabled is false)
|
if (connector.Enabled is false)
|
||||||
return StatusCode(Status406NotAcceptable);
|
return StatusCode(Status412PreconditionFailed);
|
||||||
|
|
||||||
(Manga, MangaConnectorId<Manga>)[] mangas = connector.SearchManga(Query);
|
(Manga, MangaConnectorId<Manga>)[] mangas = connector.SearchManga(Query);
|
||||||
List<Manga> retMangas = new();
|
List<Manga> retMangas = new();
|
||||||
foreach ((Manga manga, MangaConnectorId<Manga> mcId) manga in mangas)
|
foreach ((Manga manga, MangaConnectorId<Manga> mcId) manga in mangas)
|
||||||
{
|
{
|
||||||
try
|
if(AddMangaToContext(manga, context) is { } add)
|
||||||
{
|
|
||||||
if(AddMangaToContext(manga) is { } add)
|
|
||||||
retMangas.Add(add);
|
retMangas.Add(add);
|
||||||
}
|
}
|
||||||
catch (DbUpdateException e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(Status500InternalServerError, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(retMangas.ToArray());
|
return Ok(retMangas.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Search for a known Obj
|
/// Returns <see cref="Manga"/> from the <see cref="MangaConnector"/> associated with <paramref name="url"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Query"></param>
|
/// <param name="url"></param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
[HttpGet("Local/{Query}")]
|
/// <response code="300">Multiple <see cref="MangaConnector"/> found for URL</response>
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
/// <response code="404"><see cref="Manga"/> not found</response>
|
||||||
public IActionResult SearchMangaLocally(string Query)
|
|
||||||
{
|
|
||||||
Dictionary<Manga, double> distance = context.Mangas
|
|
||||||
.ToArray()
|
|
||||||
.ToDictionary(m => m, m => NeedlemanWunschStringUtil.CalculateSimilarityPercentage(Query, m.Name));
|
|
||||||
return Ok(distance.Where(kv => kv.Value > 50).OrderByDescending(kv => kv.Value).Select(kv => kv.Key).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns Obj from MangaConnector associated with URL
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="url">Obj-Page URL</param>
|
|
||||||
/// <response code="200"></response>
|
|
||||||
/// <response code="300">Multiple connectors found for URL</response>
|
|
||||||
/// <response code="404">Obj not found</response>
|
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPost("Url")]
|
[HttpPost("Url")]
|
||||||
[ProducesResponseType<Manga>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType(Status500InternalServerError)]
|
||||||
public IActionResult GetMangaFromUrl([FromBody]string url)
|
public IActionResult GetMangaFromUrl([FromBody]string url)
|
||||||
{
|
{
|
||||||
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
if (context.MangaConnectors.Find("Global") is not { } connector)
|
if (context.MangaConnectors.Find("Global") is not { } connector)
|
||||||
return StatusCode(Status500InternalServerError, "Could not find Global Connector.");
|
return StatusCode(Status500InternalServerError, "Could not find Global Connector.");
|
||||||
|
|
||||||
if(connector.GetMangaFromUrl(url) is not { } manga)
|
if(connector.GetMangaFromUrl(url) is not { } manga)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
try
|
|
||||||
{
|
if(AddMangaToContext(manga, context) is not { } add)
|
||||||
if(AddMangaToContext(manga) is { } add)
|
|
||||||
return Ok(add);
|
|
||||||
return StatusCode(Status500InternalServerError);
|
return StatusCode(Status500InternalServerError);
|
||||||
}
|
|
||||||
catch (DbUpdateException e)
|
return Ok(add);
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(Status500InternalServerError, e.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Manga? AddMangaToContext((Manga, MangaConnectorId<Manga>) manga) => AddMangaToContext(manga.Item1, manga.Item2, context);
|
private Manga? AddMangaToContext((Manga, MangaConnectorId<Manga>) manga, MangaContext context) => AddMangaToContext(manga.Item1, manga.Item2, context);
|
||||||
|
|
||||||
internal static Manga? AddMangaToContext(Manga addManga, MangaConnectorId<Manga> addMcId, MangaContext context)
|
private static Manga? AddMangaToContext(Manga addManga, MangaConnectorId<Manga> addMcId, MangaContext context)
|
||||||
{
|
{
|
||||||
Manga manga = context.Mangas.Find(addManga.Key) ?? addManga;
|
Manga manga = context.Mangas.Find(addManga.Key) ?? addManga;
|
||||||
MangaConnectorId<Manga> mcId = context.MangaConnectorToManga.Find(addMcId.Key) ?? addMcId;
|
MangaConnectorId<Manga> mcId = context.MangaConnectorToManga.Find(addMcId.Key) ?? addMcId;
|
||||||
@ -123,16 +92,12 @@ public class SearchController(MangaContext context, ILog Log) : Controller
|
|||||||
});
|
});
|
||||||
manga.Authors = mergedAuthors.ToList();
|
manga.Authors = mergedAuthors.ToList();
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(context.MangaConnectorToManga.Find(addMcId.Key) is null)
|
if(context.MangaConnectorToManga.Find(addMcId.Key) is null)
|
||||||
context.MangaConnectorToManga.Add(mcId);
|
context.MangaConnectorToManga.Add(mcId);
|
||||||
context.SaveChanges();
|
|
||||||
}
|
if (context.Sync().Result is not null)
|
||||||
catch (DbUpdateException e)
|
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
return manga;
|
return manga;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,18 +1,18 @@
|
|||||||
using API.MangaDownloadClients;
|
using API.MangaDownloadClients;
|
||||||
using API.Schema.JobsContext.Jobs;
|
|
||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
|
using API.Workers;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using log4net;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
|
|
||||||
[ApiVersion(2)]
|
[ApiVersion(2)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("v{v:apiVersion}/[controller]")]
|
[Route("v{v:apiVersion}/[controller]")]
|
||||||
public class SettingsController(MangaContext context, ILog Log) : Controller
|
public class SettingsController(IServiceScope scope) : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all Settings
|
/// Get all Settings
|
||||||
@ -237,58 +237,25 @@ public class SettingsController(MangaContext context, ILog Log) : Controller
|
|||||||
/// %C Chapter
|
/// %C Chapter
|
||||||
/// %T Title
|
/// %T Title
|
||||||
/// %A Author (first in list)
|
/// %A Author (first in list)
|
||||||
/// %I Chapter Internal ID
|
|
||||||
/// %i Obj Internal ID
|
|
||||||
/// %Y Year (Obj)
|
/// %Y Year (Obj)
|
||||||
///
|
///
|
||||||
/// ?_(...) replace _ with a value from above:
|
/// ?_(...) replace _ with a value from above:
|
||||||
/// Everything inside the braces will only be added if the value of %_ is not null
|
/// Everything inside the braces will only be added if the value of %_ is not null
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
|
||||||
[HttpPatch("ChapterNamingScheme")]
|
[HttpPatch("ChapterNamingScheme")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
|
||||||
public IActionResult SetCustomNamingScheme([FromBody]string namingScheme)
|
public IActionResult SetCustomNamingScheme([FromBody]string namingScheme)
|
||||||
{
|
{
|
||||||
try
|
MangaContext context = scope.ServiceProvider.GetRequiredService<MangaContext>();
|
||||||
{
|
|
||||||
Dictionary<Chapter, string> oldPaths = context.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
|
Dictionary<Chapter, string> oldPaths = context.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
|
||||||
TrangaSettings.UpdateChapterNamingScheme(namingScheme);
|
TrangaSettings.UpdateChapterNamingScheme(namingScheme);
|
||||||
MoveFileOrFolderJob[] newJobs = oldPaths
|
MoveFileOrFolderWorker[] newJobs = oldPaths
|
||||||
.Select(kv => new MoveFileOrFolderJob(kv.Value, kv.Key.FullArchiveFilePath)).ToArray();
|
.Select(kv => new MoveFileOrFolderWorker(kv.Value, kv.Key.FullArchiveFilePath)).ToArray();
|
||||||
context.Jobs.AddRange(newJobs);
|
Tranga.AddWorkers(newJobs);
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
return Ok();
|
||||||
/// Creates a UpdateCoverJob for all Obj
|
|
||||||
/// </summary>
|
|
||||||
/// <response code="200">Array of JobIds</response>
|
|
||||||
/// <response code="500">Error during Database Operation</response>
|
|
||||||
[HttpPost("CleanupCovers")]
|
|
||||||
[ProducesResponseType<string[]>(Status200OK)]
|
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
|
||||||
public IActionResult CleanupCovers()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Tranga.RemoveStaleFiles(context);
|
|
||||||
List<UpdateCoverJob> newJobs = context.Mangas.ToList().Select(m => new UpdateCoverJob(m, 0)).ToList();
|
|
||||||
context.Jobs.AddRange(newJobs);
|
|
||||||
return Ok(newJobs.Select(j => j.Key));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -84,110 +84,73 @@ public class WorkerController(ILog Log) : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Modify Job with ID
|
/// Modify <see cref="BaseWorker"/> with <paramref name="WorkerId"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="JobId">Job-ID</param>
|
/// <param name="WorkerId"><see cref="BaseWorker"/>.Key</param>
|
||||||
/// <param name="modifyJobRecord">Fields to modify, set to null to keep previous value</param>
|
/// <param name="modifyWorkerRecord">Fields to modify, set to null to keep previous value</param>
|
||||||
/// <response code="202">Job modified</response>
|
/// <response code="202"></response>
|
||||||
/// <response code="400">Malformed request</response>
|
/// <response code="400"></response>
|
||||||
/// <response code="404">Job with ID not found</response>
|
/// <response code="404"><see cref="BaseWorker"/> with <paramref name="WorkerId"/> could not be found</response>
|
||||||
/// <response code="500">Error during Database Operation</response>
|
/// <response code="409"><see cref="BaseWorker"/> is not <see cref="IPeriodic"/>, can not modify <paramref name="modifyWorkerRecord.IntervalMs"/></response>
|
||||||
[HttpPatch("{JobId}")]
|
[HttpPatch("{WorkerId}")]
|
||||||
[ProducesResponseType<Job>(Status202Accepted, "application/json")]
|
[ProducesResponseType<BaseWorker>(Status202Accepted, "application/json")]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status409Conflict, "text/plain")]
|
||||||
public IActionResult ModifyJob(string JobId, [FromBody]ModifyJobRecord modifyJobRecord)
|
public IActionResult ModifyJob(string WorkerId, [FromBody]ModifyWorkerRecord modifyWorkerRecord)
|
||||||
{
|
{
|
||||||
try
|
if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||||
{
|
return NotFound(nameof(WorkerId));
|
||||||
Job? ret = context.Jobs.Find(JobId);
|
|
||||||
if(ret is null)
|
|
||||||
return NotFound();
|
|
||||||
|
|
||||||
ret.RecurrenceMs = modifyJobRecord.RecurrenceMs ?? ret.RecurrenceMs;
|
if(modifyWorkerRecord.IntervalMs is not null && worker is not IPeriodic)
|
||||||
ret.Enabled = modifyJobRecord.Enabled ?? ret.Enabled;
|
return Conflict("Can not modify Interval of non-Periodic worker");
|
||||||
|
else if(modifyWorkerRecord.IntervalMs is not null && worker is IPeriodic periodic)
|
||||||
|
periodic.Interval = TimeSpan.FromMilliseconds((long)modifyWorkerRecord.IntervalMs);
|
||||||
|
|
||||||
context.SaveChanges();
|
return Accepted(worker);
|
||||||
return new AcceptedResult(ret.Key, ret);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts the Job with the requested ID
|
/// Starts <see cref="BaseWorker"/> with <paramref name="WorkerId"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="JobId">Job-ID</param>
|
/// <param name="WorkerId"><see cref="BaseWorker"/>.Key</param>
|
||||||
/// <param name="startDependencies">Start Jobs necessary for execution</param>
|
/// <response code="200"></response>
|
||||||
/// <response code="202">Job started</response>
|
/// <response code="404"><see cref="BaseWorker"/> with <paramref name="WorkerId"/> could not be found</response>
|
||||||
/// <response code="404">Job with ID not found</response>
|
/// <response code="412"><see cref="BaseWorker"/> was already running</response>
|
||||||
/// <response code="409">Job was already running</response>
|
[HttpPost("{WorkerId}/Start")]
|
||||||
/// <response code="500">Error during Database Operation</response>
|
|
||||||
[HttpPost("{JobId}/Start")]
|
|
||||||
[ProducesResponseType(Status202Accepted)]
|
[ProducesResponseType(Status202Accepted)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType(Status409Conflict)]
|
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
public IActionResult StartJob(string WorkerId)
|
||||||
public IActionResult StartJob(string JobId, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]bool startDependencies = false)
|
|
||||||
{
|
{
|
||||||
Job? ret = context.Jobs.Find(JobId);
|
if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||||
if (ret is null)
|
return NotFound(nameof(WorkerId));
|
||||||
return NotFound();
|
|
||||||
List<Job> dependencies = startDependencies ? ret.GetDependenciesAndSelf() : [ret];
|
|
||||||
|
|
||||||
try
|
if (worker.State >= WorkerExecutionState.Waiting)
|
||||||
{
|
return StatusCode(Status412PreconditionFailed, "Already running");
|
||||||
if(dependencies.Any(d => d.state >= JobState.Running && d.state < JobState.Completed))
|
|
||||||
return new ConflictResult();
|
|
||||||
dependencies.ForEach(d =>
|
|
||||||
{
|
|
||||||
d.LastExecution = DateTime.UnixEpoch;
|
|
||||||
d.state = JobState.CompletedWaiting;
|
|
||||||
});
|
|
||||||
context.SaveChanges();
|
|
||||||
return Accepted();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e);
|
|
||||||
return StatusCode(500, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
Tranga.StartWorker(worker);
|
||||||
/// Stops the Job with the requested ID
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="JobId">Job-ID</param>
|
|
||||||
/// <remarks><h1>NOT IMPLEMENTED</h1></remarks>
|
|
||||||
[HttpPost("{JobId}/Stop")]
|
|
||||||
[ProducesResponseType(Status501NotImplemented)]
|
|
||||||
public IActionResult StopJob(string JobId)
|
|
||||||
{
|
|
||||||
return StatusCode(Status501NotImplemented);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes failed and completed Jobs (that are not recurring)
|
|
||||||
/// </summary>
|
|
||||||
/// <response code="202">Job started</response>
|
|
||||||
/// <response code="500">Error during Database Operation</response>
|
|
||||||
[HttpPost("Cleanup")]
|
|
||||||
public IActionResult CleanupJobs()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
context.Jobs.RemoveRange(context.Jobs.Where(j => j.state == JobState.Failed || j.state == JobState.Completed));
|
|
||||||
context.SaveChanges();
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops <see cref="BaseWorker"/> with <paramref name="WorkerId"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="WorkerId"><see cref="BaseWorker"/>.Key</param>
|
||||||
|
/// <response code="200"></response>
|
||||||
|
/// <response code="404"><see cref="BaseWorker"/> with <paramref name="WorkerId"/> could not be found</response>
|
||||||
|
/// <response code="208"><see cref="BaseWorker"/> was not running</response>
|
||||||
|
[HttpPost("{WorkerId}/Stop")]
|
||||||
|
[ProducesResponseType(Status501NotImplemented)]
|
||||||
|
public IActionResult StopJob(string WorkerId)
|
||||||
{
|
{
|
||||||
Log.Error(e);
|
if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker)
|
||||||
return StatusCode(500, e.Message);
|
return NotFound(nameof(WorkerId));
|
||||||
}
|
|
||||||
|
if(worker.State is < WorkerExecutionState.Running or >= WorkerExecutionState.Completed)
|
||||||
|
return StatusCode(Status208AlreadyReported, "Not running");
|
||||||
|
|
||||||
|
Tranga.StopWorker(worker);
|
||||||
|
return Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -118,8 +118,8 @@ using (IServiceScope scope = app.Services.CreateScope())
|
|||||||
];
|
];
|
||||||
MangaConnector[] newConnectors = connectors.Where(c => !context.MangaConnectors.Contains(c)).ToArray();
|
MangaConnector[] newConnectors = connectors.Where(c => !context.MangaConnectors.Contains(c)).ToArray();
|
||||||
context.MangaConnectors.AddRange(newConnectors);
|
context.MangaConnectors.AddRange(newConnectors);
|
||||||
if (!context.LocalLibraries.Any())
|
if (!context.FileLibraries.Any())
|
||||||
context.LocalLibraries.Add(new FileLibrary(TrangaSettings.downloadLocation, "Default FileLibrary"));
|
context.FileLibraries.Add(new FileLibrary(TrangaSettings.downloadLocation, "Default FileLibrary"));
|
||||||
|
|
||||||
context.Sync();
|
context.Sync();
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ namespace API.Schema.LibraryContext.LibraryConnectors;
|
|||||||
public class Kavita : LibraryConnector
|
public class Kavita : LibraryConnector
|
||||||
{
|
{
|
||||||
|
|
||||||
public Kavita(string baseUrl, string auth) : base(TokenGen.CreateToken(typeof(Kavita), baseUrl), LibraryType.Kavita, baseUrl, auth)
|
public Kavita(string baseUrl, string auth) : base(LibraryType.Kavita, baseUrl, auth)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,8 +5,7 @@ namespace API.Schema.LibraryContext.LibraryConnectors;
|
|||||||
|
|
||||||
public class Komga : LibraryConnector
|
public class Komga : LibraryConnector
|
||||||
{
|
{
|
||||||
public Komga(string baseUrl, string auth) : base(TokenGen.CreateToken(typeof(Komga), baseUrl), LibraryType.Komga,
|
public Komga(string baseUrl, string auth) : base(LibraryType.Komga, baseUrl, auth)
|
||||||
baseUrl, auth)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,26 +7,51 @@ using Newtonsoft.Json;
|
|||||||
namespace API.Schema.LibraryContext.LibraryConnectors;
|
namespace API.Schema.LibraryContext.LibraryConnectors;
|
||||||
|
|
||||||
[PrimaryKey("LibraryConnectorId")]
|
[PrimaryKey("LibraryConnectorId")]
|
||||||
public abstract class LibraryConnector(string libraryConnectorId, LibraryType libraryType, string baseUrl, string auth)
|
public abstract class LibraryConnector : Identifiable
|
||||||
{
|
{
|
||||||
[StringLength(64)]
|
|
||||||
[Required]
|
[Required]
|
||||||
public string LibraryConnectorId { get; } = libraryConnectorId;
|
public LibraryType LibraryType { get; init; }
|
||||||
|
|
||||||
[Required]
|
|
||||||
public LibraryType LibraryType { get; init; } = libraryType;
|
|
||||||
[StringLength(256)]
|
[StringLength(256)]
|
||||||
[Required]
|
[Required]
|
||||||
[Url]
|
[Url]
|
||||||
public string BaseUrl { get; init; } = baseUrl;
|
public string BaseUrl { get; init; }
|
||||||
[StringLength(256)]
|
[StringLength(256)]
|
||||||
[Required]
|
[Required]
|
||||||
public string Auth { get; init; } = auth;
|
public string Auth { get; init; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
protected ILog Log { get; init; } = LogManager.GetLogger($"{libraryType.ToString()} {baseUrl}");
|
protected ILog Log { get; init; }
|
||||||
|
|
||||||
|
protected LibraryConnector(LibraryType libraryType, string baseUrl, string auth)
|
||||||
|
: base()
|
||||||
|
{
|
||||||
|
this.LibraryType = libraryType;
|
||||||
|
this.BaseUrl = baseUrl;
|
||||||
|
this.Auth = auth;
|
||||||
|
this.Log = LogManager.GetLogger(GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// EF CORE ONLY!!!!
|
||||||
|
/// </summary>
|
||||||
|
internal LibraryConnector(string key, LibraryType libraryType, string baseUrl, string auth)
|
||||||
|
: base(key)
|
||||||
|
{
|
||||||
|
this.LibraryType = libraryType;
|
||||||
|
this.BaseUrl = baseUrl;
|
||||||
|
this.Auth = auth;
|
||||||
|
this.Log = LogManager.GetLogger(GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"{base.ToString()} {this.LibraryType} {this.BaseUrl}";
|
||||||
|
|
||||||
protected abstract void UpdateLibraryInternal();
|
protected abstract void UpdateLibraryInternal();
|
||||||
internal abstract bool Test();
|
internal abstract bool Test();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum LibraryType : byte
|
||||||
|
{
|
||||||
|
Komga = 0,
|
||||||
|
Kavita = 1
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
namespace API.Schema.LibraryContext.LibraryConnectors;
|
|
||||||
|
|
||||||
public enum LibraryType : byte
|
|
||||||
{
|
|
||||||
Komga = 0,
|
|
||||||
Kavita = 1
|
|
||||||
}
|
|
@ -8,7 +8,7 @@ public class MangaContext(DbContextOptions<MangaContext> options) : TrangaBaseCo
|
|||||||
{
|
{
|
||||||
public DbSet<MangaConnector> MangaConnectors { get; set; }
|
public DbSet<MangaConnector> MangaConnectors { get; set; }
|
||||||
public DbSet<Manga> Mangas { get; set; }
|
public DbSet<Manga> Mangas { get; set; }
|
||||||
public DbSet<FileLibrary> LocalLibraries { get; set; }
|
public DbSet<FileLibrary> FileLibraries { get; set; }
|
||||||
public DbSet<Chapter> Chapters { get; set; }
|
public DbSet<Chapter> Chapters { get; set; }
|
||||||
public DbSet<Author> Authors { get; set; }
|
public DbSet<Author> Authors { get; set; }
|
||||||
public DbSet<MangaTag> Tags { get; set; }
|
public DbSet<MangaTag> Tags { get; set; }
|
||||||
|
@ -3,7 +3,7 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
namespace API.Schema.MangaContext.MetadataFetchers;
|
namespace API.Schema.MangaContext.MetadataFetchers;
|
||||||
|
|
||||||
[PrimaryKey("MetadataFetcherName", "Identifier")]
|
[PrimaryKey("Name", "Identifier")]
|
||||||
public class MetadataEntry
|
public class MetadataEntry
|
||||||
{
|
{
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
@ -19,7 +19,7 @@ public class MetadataEntry
|
|||||||
this.Manga = manga;
|
this.Manga = manga;
|
||||||
this.MangaId = manga.Key;
|
this.MangaId = manga.Key;
|
||||||
this.MetadataFetcher = fetcher;
|
this.MetadataFetcher = fetcher;
|
||||||
this.MetadataFetcherName = fetcher.MetadataFetcherName;
|
this.MetadataFetcherName = fetcher.Name;
|
||||||
this.Identifier = identifier;
|
this.Identifier = identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,23 +2,23 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
namespace API.Schema.MangaContext.MetadataFetchers;
|
namespace API.Schema.MangaContext.MetadataFetchers;
|
||||||
|
|
||||||
[PrimaryKey("MetadataFetcherName")]
|
[PrimaryKey("Name")]
|
||||||
public abstract class MetadataFetcher
|
public abstract class MetadataFetcher
|
||||||
{
|
{
|
||||||
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
|
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
|
||||||
public string MetadataFetcherName { get; init; }
|
public string Name { get; init; }
|
||||||
|
|
||||||
protected MetadataFetcher()
|
protected MetadataFetcher()
|
||||||
{
|
{
|
||||||
this.MetadataFetcherName = this.GetType().Name;
|
this.Name = this.GetType().Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EFCORE ONLY!!!
|
/// EFCORE ONLY!!!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal MetadataFetcher(string metadataFetcherName)
|
internal MetadataFetcher(string name)
|
||||||
{
|
{
|
||||||
this.MetadataFetcherName = metadataFetcherName;
|
this.Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal MetadataEntry CreateMetadataEntry(Manga manga, string identifier) =>
|
internal MetadataEntry CreateMetadataEntry(Manga manga, string identifier) =>
|
||||||
|
@ -71,4 +71,14 @@ public static class Tranga
|
|||||||
Thread.Sleep(TrangaSettings.workCycleTimeout);
|
Thread.Sleep(TrangaSettings.workCycleTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void StartWorker(BaseWorker worker)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void StopWorker(BaseWorker worker)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
@ -13,7 +13,12 @@ public abstract class BaseWorker : Identifiable
|
|||||||
internal WorkerExecutionState State { get; set; }
|
internal WorkerExecutionState State { get; set; }
|
||||||
private static readonly CancellationTokenSource CancellationTokenSource = new(TimeSpan.FromMinutes(10));
|
private static readonly CancellationTokenSource CancellationTokenSource = new(TimeSpan.FromMinutes(10));
|
||||||
protected ILog Log { get; init; }
|
protected ILog Log { get; init; }
|
||||||
public void Cancel() => CancellationTokenSource.Cancel();
|
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
this.State = WorkerExecutionState.Cancelled;
|
||||||
|
CancellationTokenSource.Cancel();
|
||||||
|
}
|
||||||
protected void Fail() => this.State = WorkerExecutionState.Failed;
|
protected void Fail() => this.State = WorkerExecutionState.Failed;
|
||||||
|
|
||||||
public BaseWorker(IEnumerable<BaseWorker>? dependsOn = null)
|
public BaseWorker(IEnumerable<BaseWorker>? dependsOn = null)
|
||||||
@ -58,5 +63,6 @@ public enum WorkerExecutionState
|
|||||||
Created = 64,
|
Created = 64,
|
||||||
Waiting = 96,
|
Waiting = 96,
|
||||||
Running = 128,
|
Running = 128,
|
||||||
Completed = 192
|
Completed = 192,
|
||||||
|
Cancelled = 193
|
||||||
}
|
}
|
@ -1,9 +1,9 @@
|
|||||||
namespace API.Workers;
|
namespace API.Workers;
|
||||||
|
|
||||||
public interface IPeriodic<T> where T : BaseWorker
|
public interface IPeriodic
|
||||||
{
|
{
|
||||||
protected DateTime LastExecution { get; set; }
|
protected DateTime LastExecution { get; set; }
|
||||||
protected TimeSpan Interval { get; set; }
|
public TimeSpan Interval { get; set; }
|
||||||
|
|
||||||
public DateTime NextExecution => LastExecution.Add(Interval);
|
public DateTime NextExecution => LastExecution.Add(Interval);
|
||||||
|
public bool IsDue => NextExecution <= DateTime.UtcNow;
|
||||||
}
|
}
|
Reference in New Issue
Block a user