diff --git a/API/APIEndpointRecords/GotifyRecord.cs b/API/APIEndpointRecords/GotifyRecord.cs index 59c5287..28e2314 100644 --- a/API/APIEndpointRecords/GotifyRecord.cs +++ b/API/APIEndpointRecords/GotifyRecord.cs @@ -1,14 +1,14 @@ 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() { - if (endpoint == string.Empty) + if (Endpoint == string.Empty) return false; - if (appToken == string.Empty) + if (AppToken == string.Empty) return false; - if (priority < 0 || priority > 10) + if (Priority < 0 || Priority > 10) return false; return true; diff --git a/API/APIEndpointRecords/ModifyJobRecord.cs b/API/APIEndpointRecords/ModifyJobRecord.cs deleted file mode 100644 index 4e110fa..0000000 --- a/API/APIEndpointRecords/ModifyJobRecord.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace API.APIEndpointRecords; - -public record ModifyJobRecord(ulong? RecurrenceMs, bool? Enabled); \ No newline at end of file diff --git a/API/APIEndpointRecords/ModifyWorkerRecord.cs b/API/APIEndpointRecords/ModifyWorkerRecord.cs new file mode 100644 index 0000000..91434aa --- /dev/null +++ b/API/APIEndpointRecords/ModifyWorkerRecord.cs @@ -0,0 +1,3 @@ +namespace API.APIEndpointRecords; + +public record ModifyWorkerRecord(ulong? IntervalMs); \ No newline at end of file diff --git a/API/APIEndpointRecords/NewLibraryRecord.cs b/API/APIEndpointRecords/NewLibraryRecord.cs deleted file mode 100644 index b30b7b5..0000000 --- a/API/APIEndpointRecords/NewLibraryRecord.cs +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/API/APIEndpointRecords/NtfyRecord.cs b/API/APIEndpointRecords/NtfyRecord.cs index a8c2c22..8aa3099 100644 --- a/API/APIEndpointRecords/NtfyRecord.cs +++ b/API/APIEndpointRecords/NtfyRecord.cs @@ -1,16 +1,16 @@ 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() { - if (endpoint == string.Empty) + if (Endpoint == string.Empty) return false; - if (username == string.Empty) + if (Username == string.Empty) return false; - if (password == string.Empty) + if (Password == string.Empty) return false; - if (priority < 1 || priority > 5) + if (Priority < 1 || Priority > 5) return false; return true; } diff --git a/API/APIEndpointRecords/PushoverRecord.cs b/API/APIEndpointRecords/PushoverRecord.cs index 0f79919..6086da6 100644 --- a/API/APIEndpointRecords/PushoverRecord.cs +++ b/API/APIEndpointRecords/PushoverRecord.cs @@ -1,12 +1,12 @@ namespace API.APIEndpointRecords; -public record PushoverRecord(string apptoken, string user) +public record PushoverRecord(string Name, string AppToken, string User) { public bool Validate() { - if (apptoken == string.Empty) + if (AppToken == string.Empty) return false; - if (user == string.Empty) + if (User == string.Empty) return false; return true; } diff --git a/API/Controllers/FileLibraryController.cs b/API/Controllers/FileLibraryController.cs new file mode 100644 index 0000000..4611e4f --- /dev/null +++ b/API/Controllers/FileLibraryController.cs @@ -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 +{ + /// + /// Returns all + /// + /// + [HttpGet] + [ProducesResponseType(Status200OK, "application/json")] + public IActionResult GetFileLibraries() + { + MangaContext context = scope.ServiceProvider.GetRequiredService(); + + return Ok(context.FileLibraries.ToArray()); + } + + /// + /// Returns with + /// + /// .Key + /// + /// with not found. + [HttpGet("{FileLibraryId}")] + [ProducesResponseType(Status200OK, "application/json")] + [ProducesResponseType(Status404NotFound)] + public IActionResult GetFileLibrary(string FileLibraryId) + { + MangaContext context = scope.ServiceProvider.GetRequiredService(); + if (context.FileLibraries.Find(FileLibraryId) is not { } library) + return NotFound(); + + return Ok(library); + } + + /// + /// Changes the .BasePath with + /// + /// .Key + /// New .BasePath + /// + /// with not found. + /// Error during Database Operation + [HttpPatch("{FileLibraryId}/ChangeBasePath")] + [ProducesResponseType(Status200OK)] + [ProducesResponseType(Status404NotFound)] + [ProducesResponseType(Status500InternalServerError, "text/plain")] + public IActionResult ChangeLibraryBasePath(string FileLibraryId, [FromBody]string newBasePath) + { + MangaContext context = scope.ServiceProvider.GetRequiredService(); + 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(); + } + + /// + /// Changes the .LibraryName with + /// + /// .Key + /// New .LibraryName + /// + /// with not found. + /// Error during Database Operation + [HttpPatch("{FileLibraryId}/ChangeName")] + [ProducesResponseType(Status200OK)] + [ProducesResponseType(Status404NotFound)] + [ProducesResponseType(Status400BadRequest)] + [ProducesResponseType(Status500InternalServerError, "text/plain")] + public IActionResult ChangeLibraryName(string FileLibraryId, [FromBody] string newName) + { + MangaContext context = scope.ServiceProvider.GetRequiredService(); + 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(); + } + + /// + /// Creates new + /// + /// New to add + /// + /// Error during Database Operation + [HttpPut] + [ProducesResponseType(Status201Created)] + [ProducesResponseType(Status500InternalServerError, "text/plain")] + public IActionResult CreateNewLibrary([FromBody]FileLibrary library) + { + MangaContext context = scope.ServiceProvider.GetRequiredService(); + + //TODO Parameter check + context.FileLibraries.Add(library); + + if(context.Sync().Result is { } errorMessage) + return StatusCode(Status500InternalServerError, errorMessage); + return Created(); + } + + /// + /// Deletes the .LibraryName with + /// + /// .Key + /// + /// Error during Database Operation + [HttpDelete("{FileLibraryId}")] + [ProducesResponseType(Status200OK)] + [ProducesResponseType(Status404NotFound)] + [ProducesResponseType(Status500InternalServerError, "text/plain")] + public IActionResult DeleteLocalLibrary(string FileLibraryId) + { + MangaContext context = scope.ServiceProvider.GetRequiredService(); + 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(); + } +} \ No newline at end of file diff --git a/API/Controllers/LibraryConnectorController.cs b/API/Controllers/LibraryConnectorController.cs index f69fdf8..f947ebc 100644 --- a/API/Controllers/LibraryConnectorController.cs +++ b/API/Controllers/LibraryConnectorController.cs @@ -1,52 +1,54 @@ using API.Schema.LibraryContext; using API.Schema.LibraryContext.LibraryConnectors; using Asp.Versioning; -using log4net; 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 LibraryConnectorController(LibraryContext context, ILog Log) : Controller +public class LibraryConnectorController(IServiceScope scope) : Controller { /// - /// Gets all configured ToFileLibrary-Connectors + /// Gets all configured /// /// [HttpGet] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetAllConnectors() { + LibraryContext context = scope.ServiceProvider.GetRequiredService(); + LibraryConnector[] connectors = context.LibraryConnectors.ToArray(); + return Ok(connectors); } /// - /// Returns ToFileLibrary-Connector with requested ID + /// Returns with /// - /// ToFileLibrary-Connector-ID + /// .Key /// - /// Connector with ID not found. - [HttpGet("{LibraryControllerId}")] + /// with not found. + [HttpGet("{LibraryConnectorId}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetConnector(string LibraryControllerId) + public IActionResult GetConnector(string LibraryConnectorId) { - LibraryConnector? ret = context.LibraryConnectors.Find(LibraryControllerId); - return (ret is not null) switch - { - true => Ok(ret), - false => NotFound() - }; + LibraryContext context = scope.ServiceProvider.GetRequiredService(); + if (context.LibraryConnectors.Find(LibraryConnectorId) is not { } connector) + return NotFound(); + + return Ok(connector); } /// - /// Creates a new ToFileLibrary-Connector + /// Creates a new /// - /// ToFileLibrary-Connector + /// /// /// Error during Database Operation [HttpPut] @@ -54,46 +56,36 @@ public class LibraryConnectorController(LibraryContext context, ILog Log) : Cont [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateConnector([FromBody]LibraryConnector libraryConnector) { - try - { - context.LibraryConnectors.Add(libraryConnector); - context.SaveChanges(); - return Created(); - } - catch (Exception e) - { - Log.Error(e); - return StatusCode(500, e.Message); - } + LibraryContext context = scope.ServiceProvider.GetRequiredService(); + + context.LibraryConnectors.Add(libraryConnector); + + if(context.Sync().Result is { } errorMessage) + return StatusCode(Status500InternalServerError, errorMessage); + return Created(); } /// - /// Deletes the ToFileLibrary-Connector with the requested ID + /// Deletes with /// - /// ToFileLibrary-Connector-ID + /// ToFileLibrary-Connector-ID /// - /// Connector with ID not found. + /// with < not found. /// Error during Database Operation - [HttpDelete("{LibraryControllerId}")] + [HttpDelete("{LibraryConnectorId}")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult DeleteConnector(string LibraryControllerId) + public IActionResult DeleteConnector(string LibraryConnectorId) { - try - { - LibraryConnector? ret = context.LibraryConnectors.Find(LibraryControllerId); - if (ret is null) - return NotFound(); - - context.Remove(ret); - context.SaveChanges(); - return Ok(); - } - catch (Exception e) - { - Log.Error(e); - return StatusCode(500, e.Message); - } + LibraryContext context = scope.ServiceProvider.GetRequiredService(); + if (context.LibraryConnectors.Find(LibraryConnectorId) is not { } connector) + return NotFound(); + + context.LibraryConnectors.Remove(connector); + + if(context.Sync().Result is { } errorMessage) + return StatusCode(Status500InternalServerError, errorMessage); + return Ok(); } } \ No newline at end of file diff --git a/API/Controllers/LocalLibrariesController.cs b/API/Controllers/LocalLibrariesController.cs deleted file mode 100644 index 8302662..0000000 --- a/API/Controllers/LocalLibrariesController.cs +++ /dev/null @@ -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(Status200OK, "application/json")] - public IActionResult GetLocalLibraries() - { - return Ok(context.LocalLibraries); - } - - [HttpGet("{LibraryId}")] - [ProducesResponseType(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(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(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(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(Status200OK, "application/json")] - [ProducesResponseType(Status400BadRequest)] - [ProducesResponseType(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(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); - } - } -} \ No newline at end of file diff --git a/API/Controllers/MangaConnectorController.cs b/API/Controllers/MangaConnectorController.cs index 60d8077..9e2ad72 100644 --- a/API/Controllers/MangaConnectorController.cs +++ b/API/Controllers/MangaConnectorController.cs @@ -1,107 +1,95 @@ using API.Schema.MangaContext; using API.Schema.MangaContext.MangaConnectors; using Asp.Versioning; -using log4net; 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 MangaConnectorController(MangaContext context, ILog Log) : Controller +public class MangaConnectorController(IServiceScope scope) : Controller { /// - /// Get all available Connectors (Scanlation-Sites) + /// Get all (Scanlation-Sites) /// - /// + /// Names of (Scanlation-Sites) [HttpGet] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetConnectors() { - MangaConnector[] connectors = context.MangaConnectors.ToArray(); - return Ok(connectors); + MangaContext context = scope.ServiceProvider.GetRequiredService(); + return Ok(context.MangaConnectors.Select(c => c.Name).ToArray()); } /// - /// Returns the MangaConnector with the requested Name + /// Returns the (Scanlation-Sites) with the requested Name /// - /// + /// .Name /// - /// Connector with ID not found. - /// Error during Database Operation + /// (Scanlation-Sites) with Name not found. [HttpGet("{MangaConnectorName}")] [ProducesResponseType(Status200OK, "application/json")] + [ProducesResponseType(Status404NotFound)] public IActionResult GetConnector(string MangaConnectorName) { - try - { - if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector) - return NotFound(); - - return Ok(connector); - } - catch (Exception e) - { - Log.Error(e); - return StatusCode(500, e.Message); - } + MangaContext context = scope.ServiceProvider.GetRequiredService(); + if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector) + return NotFound(); + + return Ok(connector); } /// - /// Get all enabled Connectors (Scanlation-Sites) + /// Get all enabled (Scanlation-Sites) /// /// - [HttpGet("enabled")] + [HttpGet("Enabled")] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetEnabledConnectors() { - MangaConnector[] connectors = context.MangaConnectors.Where(c => c.Enabled == true).ToArray(); - return Ok(connectors); + MangaContext context = scope.ServiceProvider.GetRequiredService(); + + return Ok(context.MangaConnectors.Where(c => c.Enabled).ToArray()); } /// - /// Get all disabled Connectors (Scanlation-Sites) + /// Get all disabled (Scanlation-Sites) /// /// - [HttpGet("disabled")] + [HttpGet("Disabled")] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetDisabledConnectors() { - MangaConnector[] connectors = context.MangaConnectors.Where(c => c.Enabled == false).ToArray(); - return Ok(connectors); + MangaContext context = scope.ServiceProvider.GetRequiredService(); + + return Ok(context.MangaConnectors.Where(c => c.Enabled == false).ToArray()); } /// - /// Enabled or disables a Connector + /// Enabled or disables (Scanlation-Sites) with Name /// - /// ID of the connector - /// Set true to enable - /// - /// Connector with ID not found. + /// .Name + /// Set true to enable, false to disable + /// + /// (Scanlation-Sites) with Name not found. /// Error during Database Operation - [HttpPatch("{MangaConnectorName}/SetEnabled/{enabled}")] - [ProducesResponseType(Status200OK)] + [HttpPatch("{MangaConnectorName}/SetEnabled/{Enabled}")] + [ProducesResponseType(Status202Accepted)] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult SetEnabled(string MangaConnectorName, bool enabled) + public IActionResult SetEnabled(string MangaConnectorName, bool Enabled) { - try - { - MangaConnector? connector = context.MangaConnectors.Find(MangaConnectorName); - if (connector is null) - return NotFound(); - - connector.Enabled = enabled; - context.SaveChanges(); - - return Ok(); - } - catch (Exception e) - { - Log.Error(e); - return StatusCode(500, e.Message); - } + MangaContext context = scope.ServiceProvider.GetRequiredService(); + if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector) + return NotFound(); + + connector.Enabled = Enabled; + + if(context.Sync().Result is { } errorMessage) + return StatusCode(Status500InternalServerError, errorMessage); + return Accepted(); } } \ No newline at end of file diff --git a/API/Controllers/MangaController.cs b/API/Controllers/MangaController.cs index 554c339..45b6bba 100644 --- a/API/Controllers/MangaController.cs +++ b/API/Controllers/MangaController.cs @@ -2,9 +2,7 @@ using API.Schema.MangaContext.MangaConnectors; using API.Workers; using Asp.Versioning; -using log4net; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; using Microsoft.Net.Http.Headers; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; @@ -352,7 +350,7 @@ public class MangaController(IServiceScope scope) : Controller MangaContext context = scope.ServiceProvider.GetRequiredService(); if (context.Mangas.Find(MangaId) is not { } manga) return NotFound(nameof(MangaId)); - if(context.LocalLibraries.Find(LibraryId) is not { } library) + if(context.FileLibraries.Find(LibraryId) is not { } library) return NotFound(nameof(LibraryId)); MoveMangaLibraryWorker moveLibrary = new(manga, library, scope); diff --git a/API/Controllers/MetadataFetcherController.cs b/API/Controllers/MetadataFetcherController.cs index 4653a2b..071bdb8 100644 --- a/API/Controllers/MetadataFetcherController.cs +++ b/API/Controllers/MetadataFetcherController.cs @@ -1,7 +1,6 @@ using API.Schema.MangaContext; using API.Schema.MangaContext.MetadataFetchers; using Asp.Versioning; -using log4net; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using static Microsoft.AspNetCore.Http.StatusCodes; @@ -12,47 +11,51 @@ namespace API.Controllers; [ApiVersion(2)] [ApiController] [Route("v{v:apiVersion}/[controller]")] -public class MetadataFetcherController(MangaContext context, ILog Log) : Controller +public class MetadataFetcherController(IServiceScope scope) : Controller { /// - /// Get all available Connectors (Metadata-Sites) + /// Get all (Metadata-Sites) /// - /// Names of Metadata-Fetchers + /// Names of (Metadata-Sites) [HttpGet] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetConnectors() { - string[] connectors = Tranga.MetadataFetchers.Select(f => f.MetadataFetcherName).ToArray(); - return Ok(connectors); + return Ok(Tranga.MetadataFetchers.Select(m => m.Name).ToArray()); } /// - /// Returns all Mangas which have a linked Metadata-Provider + /// Returns all /// /// [HttpGet("Links")] - [ProducesResponseType(Status200OK, "application/json")] + [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetLinkedEntries() { + MangaContext context = scope.ServiceProvider.GetRequiredService(); + return Ok(context.MetadataEntries.ToArray()); } /// - /// Searches Metadata-Provider for Manga-Metadata + /// Searches (Metadata-Sites) for Manga-Metadata /// - /// Instead of using the Manga for search, use a specific term + /// .Key + /// .Name + /// Instead of using the for search on Website, use a specific term /// - /// Metadata-fetcher with Name does not exist - /// Manga with ID not found + /// (Metadata-Sites) with does not exist + /// with not found [HttpPost("{MetadataFetcherName}/SearchManga/{MangaId}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status400BadRequest)] [ProducesResponseType(Status404NotFound)] public IActionResult SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null) { + MangaContext context = scope.ServiceProvider.GetRequiredService(); if(context.Mangas.Find(MangaId) is not { } manga) 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(); MetadataSearchResult[] searchResults = searchTerm is null ? fetcher.SearchMetadataEntry(manga) : fetcher.SearchMetadataEntry(searchTerm); @@ -60,45 +63,43 @@ public class MetadataFetcherController(MangaContext context, ILog Log) : Control } /// - /// Links Metadata-Provider using Provider-Specific Identifier to Manga + /// Links (Metadata-Sites) using Provider-Specific Identifier to /// + /// .Key + /// .Name + /// -Specific ID /// - /// Metadata-fetcher with Name does not exist - /// Manga with ID not found + /// (Metadata-Sites) with does not exist + /// with not found /// Error during Database Operation [HttpPost("{MetadataFetcherName}/Link/{MangaId}")] - [ProducesResponseType(Status200OK)] + [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status400BadRequest)] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult LinkMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody]string Identifier) { + MangaContext context = scope.ServiceProvider.GetRequiredService(); if(context.Mangas.Find(MangaId) is not { } manga) 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(); - MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier); - try - { - context.MetadataEntries.Add(entry); - context.SaveChanges(); - } - catch (Exception e) - { - Log.Error(e); - return StatusCode(500, e.Message); - } - return Ok(); + MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier); + context.MetadataEntries.Add(entry); + + if(context.Sync().Result is { } errorMessage) + return StatusCode(Status500InternalServerError, errorMessage); + return Ok(entry); } /// - /// Un-Links Metadata-Provider using Provider-Specific Identifier to Manga + /// Un-Links (Metadata-Sites) from /// /// - /// Metadata-fetcher with Name does not exist - /// Manga with ID not found - /// No Entry linking Manga and Metadata-Provider found + /// (Metadata-Sites) with does not exist + /// with not found + /// No linking and found /// Error during Database Operation [HttpPost("{MetadataFetcherName}/Unlink/{MangaId}")] [ProducesResponseType(Status200OK)] @@ -108,58 +109,18 @@ public class MetadataFetcherController(MangaContext context, ILog Log) : Control [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult UnlinkMangaMetadata(string MangaId, string MetadataFetcherName) { - if(context.Mangas.Find(MangaId) is not { } manga) + MangaContext context = scope.ServiceProvider.GetRequiredService(); + if(context.Mangas.Find(MangaId) is null) 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(); - MetadataEntry? entry = context.MetadataEntries.FirstOrDefault(e => e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName); - if (entry is null) + if(context.MetadataEntries.FirstOrDefault(e => e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName) is not { } entry) 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(); - } - /// - /// Tries linking a Manga to a Metadata-Provider-Site - /// - /// - /// MetadataFetcher Name is invalid - /// Manga has no linked entry with MetadataFetcher - /// Error during Database Operation - [HttpPost("{MetadataFetcherName}/{MangaId}/UpdateMetadata")] - [ProducesResponseType(Status200OK)] - [ProducesResponseType(Status400BadRequest)] - [ProducesResponseType(Status404NotFound)] - [ProducesResponseType(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); - } + context.Remove(entry); + + if(context.Sync().Result is { } errorMessage) + return StatusCode(Status500InternalServerError, errorMessage); return Ok(); } } \ No newline at end of file diff --git a/API/Controllers/NotificationConnectorController.cs b/API/Controllers/NotificationConnectorController.cs index e521ad5..1da22be 100644 --- a/API/Controllers/NotificationConnectorController.cs +++ b/API/Controllers/NotificationConnectorController.cs @@ -3,9 +3,9 @@ using API.APIEndpointRecords; using API.Schema.NotificationsContext; using API.Schema.NotificationsContext.NotificationConnectors; using Asp.Versioning; -using log4net; using Microsoft.AspNetCore.Mvc; using static Microsoft.AspNetCore.Http.StatusCodes; +// ReSharper disable InconsistentNaming namespace API.Controllers; @@ -13,121 +13,103 @@ namespace API.Controllers; [ApiController] [Produces("application/json")] [Route("v{v:apiVersion}/[controller]")] -public class NotificationConnectorController(NotificationsContext context, ILog Log) : Controller +public class NotificationConnectorController(IServiceScope scope) : Controller { /// - /// Gets all configured Notification-Connectors + /// Gets all configured /// /// [HttpGet] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetAllConnectors() { - NotificationConnector[] ret = context.NotificationConnectors.ToArray(); - return Ok(ret); + NotificationsContext context = scope.ServiceProvider.GetRequiredService(); + + return Ok(context.NotificationConnectors.ToArray()); } /// - /// Returns Notification-Connector with requested ID + /// Returns with requested Name /// - /// Notification-Connector-ID + /// .Name /// - /// NotificationConnector with ID not found - [HttpGet("{NotificationConnectorId}")] + /// with not found + [HttpGet("{Name}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetConnector(string NotificationConnectorId) + public IActionResult GetConnector(string Name) { - NotificationConnector? ret = context.NotificationConnectors.Find(NotificationConnectorId); - return (ret is not null) switch - { - true => Ok(ret), - false => NotFound() - }; + NotificationsContext context = scope.ServiceProvider.GetRequiredService(); + if(context.NotificationConnectors.Find(Name) is not { } connector) + return NotFound(); + + return Ok(connector); } /// - /// Creates a new REST-Notification-Connector + /// Creates a new /// /// Formatting placeholders: "%title" and "%text" can be placed in url, header-values and body and will be replaced when notifications are sent - /// Notification-Connector - /// ID of new connector - /// A NotificationConnector with name already exists + /// /// Error during Database Operation [HttpPut] - [ProducesResponseType(Status201Created, "application/json")] + [ProducesResponseType(Status201Created)] [ProducesResponseType(Status409Conflict)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateConnector([FromBody]NotificationConnector notificationConnector) { - if (context.NotificationConnectors.Find(notificationConnector.Name) is not null) - return Conflict(); - try - { - context.NotificationConnectors.Add(notificationConnector); - context.SaveChanges(); - return Created(notificationConnector.Name, notificationConnector); - } - catch (Exception e) - { - Log.Error(e); - return StatusCode(500, e.Message); - } + NotificationsContext context = scope.ServiceProvider.GetRequiredService(); + + context.NotificationConnectors.Add(notificationConnector); + + if(context.Sync().Result is { } errorMessage) + return StatusCode(Status500InternalServerError, errorMessage); + return Created(); } /// - /// Creates a new Gotify-Notification-Connector + /// Creates a new Gotify- /// /// Priority needs to be between 0 and 10 - /// ID of new connector - /// - /// A NotificationConnector with name already exists + /// /// Error during Database Operation [HttpPut("Gotify")] [ProducesResponseType(Status201Created, "application/json")] - [ProducesResponseType(Status400BadRequest)] - [ProducesResponseType(Status409Conflict)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateGotifyConnector([FromBody]GotifyRecord gotifyData) { - if(!gotifyData.Validate()) - return BadRequest(); + //TODO Validate Data - NotificationConnector gotifyConnector = new NotificationConnector(TokenGen.CreateToken("Gotify"), - gotifyData.endpoint, - new Dictionary() { { "X-Gotify-IDOnConnector", gotifyData.appToken } }, + NotificationConnector gotifyConnector = new (gotifyData.Name, + gotifyData.Endpoint, + new Dictionary() { { "X-Gotify-IDOnConnector", gotifyData.AppToken } }, "POST", - $"{{\"message\": \"%text\", \"title\": \"%title\", \"priority\": {gotifyData.priority}}}"); + $"{{\"message\": \"%text\", \"title\": \"%title\", \"Priority\": {gotifyData.Priority}}}"); return CreateConnector(gotifyConnector); } /// - /// Creates a new Ntfy-Notification-Connector + /// Creates a new Ntfy- /// /// Priority needs to be between 1 and 5 - /// ID of new connector - /// - /// A NotificationConnector with name already exists + /// /// Error during Database Operation [HttpPut("Ntfy")] [ProducesResponseType(Status201Created, "application/json")] - [ProducesResponseType(Status400BadRequest)] - [ProducesResponseType(Status409Conflict)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreateNtfyConnector([FromBody]NtfyRecord ntfyRecord) { - if(!ntfyRecord.Validate()) - return BadRequest(); + //TODO Validate Data - 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("=",""); - NotificationConnector ntfyConnector = new (TokenGen.CreateToken("Ntfy"), - $"{ntfyRecord.endpoint}?auth={auth}", + NotificationConnector ntfyConnector = new (ntfyRecord.Name, + $"{ntfyRecord.Endpoint}/{ntfyRecord.Topic}?auth={auth}", new Dictionary() { {"Title", "%title"}, - {"Priority", ntfyRecord.priority.ToString()}, + {"Priority", ntfyRecord.Priority.ToString()}, }, "POST", "%text"); @@ -135,58 +117,47 @@ public class NotificationConnectorController(NotificationsContext context, ILog } /// - /// Creates a new Pushover-Notification-Connector + /// Creates a new Pushover- /// /// https://pushover.net/api /// ID of new connector - /// - /// A NotificationConnector with name already exists /// Error during Database Operation [HttpPut("Pushover")] [ProducesResponseType(Status201Created, "application/json")] - [ProducesResponseType(Status400BadRequest)] - [ProducesResponseType(Status409Conflict)] [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult CreatePushoverConnector([FromBody]PushoverRecord pushoverRecord) { - if(!pushoverRecord.Validate()) - return BadRequest(); + //TODO Validate Data - NotificationConnector pushoverConnector = new (TokenGen.CreateToken("Pushover"), + NotificationConnector pushoverConnector = new (pushoverRecord.Name, $"https://api.pushover.net/1/messages.json", new Dictionary(), "POST", - $"{{\"token\": \"{pushoverRecord.apptoken}\", \"user\": \"{pushoverRecord.user}\", \"message:\":\"%text\", \"%title\" }}"); + $"{{\"token\": \"{pushoverRecord.AppToken}\", \"user\": \"{pushoverRecord.User}\", \"message:\":\"%text\", \"%title\" }}"); return CreateConnector(pushoverConnector); } /// - /// Deletes the Notification-Connector with the requested ID + /// Deletes the with the requested Name /// - /// Notification-Connector-ID + /// .Name /// - /// NotificationConnector with ID not found + /// with Name not found /// Error during Database Operation - [HttpDelete("{NotificationConnectorId}")] + [HttpDelete("{Name}")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult DeleteConnector(string NotificationConnectorId) + public IActionResult DeleteConnector(string Name) { - try - { - NotificationConnector? ret = context.NotificationConnectors.Find(NotificationConnectorId); - if(ret is null) - return NotFound(); - - context.Remove(ret); - context.SaveChanges(); - return Ok(); - } - catch (Exception e) - { - Log.Error(e); - return StatusCode(500, e.Message); - } + NotificationsContext context = scope.ServiceProvider.GetRequiredService(); + if(context.NotificationConnectors.Find(Name) is not { } connector) + return NotFound(); + + context.NotificationConnectors.Remove(connector); + + if(context.Sync().Result is { } errorMessage) + return StatusCode(Status500InternalServerError, errorMessage); + return Created(); } } \ No newline at end of file diff --git a/API/Controllers/QueryController.cs b/API/Controllers/QueryController.cs index d258260..3341162 100644 --- a/API/Controllers/QueryController.cs +++ b/API/Controllers/QueryController.cs @@ -1,6 +1,5 @@ using API.Schema.MangaContext; using Asp.Versioning; -using log4net; using Microsoft.AspNetCore.Mvc; using static Microsoft.AspNetCore.Http.StatusCodes; // ReSharper disable InconsistentNaming @@ -10,102 +9,74 @@ namespace API.Controllers; [ApiVersion(2)] [ApiController] [Route("v{v:apiVersion}/[controller]")] -public class QueryController(MangaContext context, ILog Log) : Controller +public class QueryController(IServiceScope scope) : Controller { /// - /// Returns the Author-Information for Author-ID + /// Returns the with /// - /// Author-Id + /// .Key /// - /// Author with ID not found + /// with not found [HttpGet("Author/{AuthorId}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] public IActionResult GetAuthor(string AuthorId) { - Author? ret = context.Authors.Find(AuthorId); - if (ret is null) + MangaContext context = scope.ServiceProvider.GetRequiredService(); + if (context.Authors.Find(AuthorId) is not { } author) return NotFound(); - return Ok(ret); + + return Ok(author); } /// - /// Returns all Mangas which where Authored by Author with AuthorId + /// Returns all which where Authored by with /// - /// Author-ID + /// .Key /// - /// Author not found + /// with [HttpGet("Mangas/WithAuthorId/{AuthorId}")] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetMangaWithAuthorIds(string AuthorId) { - if(context.Authors.Find(AuthorId) is not { } a) + MangaContext context = scope.ServiceProvider.GetRequiredService(); + if (context.Authors.Find(AuthorId) is not { } author) return NotFound(); - return Ok(context.Mangas.Where(m => m.Authors.Contains(a))); - } - /* - /// - /// Returns Link-Information for Link-Id - /// - /// - /// - /// Link with ID not found - [HttpGet("Link/{LinkId}")] - [ProducesResponseType(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); + + return Ok(context.Mangas.Where(m => m.Authors.Contains(author))); } /// - /// Returns AltTitle-Information for AltTitle-Id + /// Returns all with /// - /// + /// .Tag /// - /// AltTitle with ID not found - [HttpGet("AltTitle/{AltTitleId}")] - [ProducesResponseType(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); - }*/ - - /// - /// Returns all Obj with Tag - /// - /// - /// - /// Tag not found + /// not found [HttpGet("Mangas/WithTag/{Tag}")] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetMangasWithTag(string Tag) { - if(context.Tags.Find(Tag) is not { } t) + MangaContext context = scope.ServiceProvider.GetRequiredService(); + if (context.Tags.Find(Tag) is not { } tag) return NotFound(); - return Ok(context.Mangas.Where(m => m.MangaTags.Contains(t))); + + return Ok(context.Mangas.Where(m => m.MangaTags.Contains(tag))); } /// - /// Returns Chapter-Information for Chapter-Id + /// Returns with /// - /// + /// .Key /// - /// Chapter with ID not found + /// with not found [HttpGet("Chapter/{ChapterId}")] [ProducesResponseType(Status200OK, "application/json")] public IActionResult GetChapter(string ChapterId) { - Chapter? ret = context.Chapters.Find(ChapterId); - if (ret is null) + MangaContext context = scope.ServiceProvider.GetRequiredService(); + if (context.Chapters.Find(ChapterId) is not { } chapter) return NotFound(); - return Ok(ret); + + return Ok(chapter); } } \ No newline at end of file diff --git a/API/Controllers/SearchController.cs b/API/Controllers/SearchController.cs index 9edd200..258c72b 100644 --- a/API/Controllers/SearchController.cs +++ b/API/Controllers/SearchController.cs @@ -1,9 +1,7 @@ using API.Schema.MangaContext; +using API.Schema.MangaContext.MangaConnectors; using Asp.Versioning; -using log4net; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Soenneker.Utils.String.NeedlemanWunsch; using static Microsoft.AspNetCore.Http.StatusCodes; // ReSharper disable InconsistentNaming @@ -12,98 +10,69 @@ namespace API.Controllers; [ApiVersion(2)] [ApiController] [Route("v{v:apiVersion}/[controller]")] -public class SearchController(MangaContext context, ILog Log) : Controller +public class SearchController(IServiceScope scope) : Controller { /// - /// Initiate a search for a Obj on a specific Connector + /// Initiate a search for a on with searchTerm /// - /// - /// + /// .Name + /// searchTerm /// - /// MangaConnector with ID not found - /// MangaConnector with ID is disabled - /// Error during Database Operation + /// with Name not found + /// with Name is disabled [HttpGet("{MangaConnectorName}/{Query}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status406NotAcceptable)] - [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult SearchManga(string MangaConnectorName, string Query) { + MangaContext context = scope.ServiceProvider.GetRequiredService(); if(context.MangaConnectors.Find(MangaConnectorName) is not { } connector) return NotFound(); - else if (connector.Enabled is false) - return StatusCode(Status406NotAcceptable); + if (connector.Enabled is false) + return StatusCode(Status412PreconditionFailed); (Manga, MangaConnectorId)[] mangas = connector.SearchManga(Query); List retMangas = new(); foreach ((Manga manga, MangaConnectorId mcId) manga in mangas) { - try - { - if(AddMangaToContext(manga) is { } add) - retMangas.Add(add); - } - catch (DbUpdateException e) - { - Log.Error(e); - return StatusCode(Status500InternalServerError, e.Message); - } + if(AddMangaToContext(manga, context) is { } add) + retMangas.Add(add); } return Ok(retMangas.ToArray()); } - - /// - /// Search for a known Obj - /// - /// - /// - [HttpGet("Local/{Query}")] - [ProducesResponseType(Status200OK, "application/json")] - public IActionResult SearchMangaLocally(string Query) - { - Dictionary 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()); - } /// - /// Returns Obj from MangaConnector associated with URL + /// Returns from the associated with /// - /// Obj-Page URL + /// /// - /// Multiple connectors found for URL - /// Obj not found + /// Multiple found for URL + /// not found /// Error during Database Operation [HttpPost("Url")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - [ProducesResponseType(Status500InternalServerError, "text/plain")] + [ProducesResponseType(Status500InternalServerError)] public IActionResult GetMangaFromUrl([FromBody]string url) { + MangaContext context = scope.ServiceProvider.GetRequiredService(); if (context.MangaConnectors.Find("Global") is not { } connector) return StatusCode(Status500InternalServerError, "Could not find Global Connector."); if(connector.GetMangaFromUrl(url) is not { } manga) return NotFound(); - try - { - if(AddMangaToContext(manga) is { } add) - return Ok(add); + + if(AddMangaToContext(manga, context) is not { } add) return StatusCode(Status500InternalServerError); - } - catch (DbUpdateException e) - { - Log.Error(e); - return StatusCode(Status500InternalServerError, e.Message); - } + + return Ok(add); } - private Manga? AddMangaToContext((Manga, MangaConnectorId) manga) => AddMangaToContext(manga.Item1, manga.Item2, context); - - internal static Manga? AddMangaToContext(Manga addManga, MangaConnectorId addMcId, MangaContext context) + private Manga? AddMangaToContext((Manga, MangaConnectorId) manga, MangaContext context) => AddMangaToContext(manga.Item1, manga.Item2, context); + + private static Manga? AddMangaToContext(Manga addManga, MangaConnectorId addMcId, MangaContext context) { Manga manga = context.Mangas.Find(addManga.Key) ?? addManga; MangaConnectorId mcId = context.MangaConnectorToManga.Find(addMcId.Key) ?? addMcId; @@ -123,16 +92,12 @@ public class SearchController(MangaContext context, ILog Log) : Controller }); manga.Authors = mergedAuthors.ToList(); - try - { - if(context.MangaConnectorToManga.Find(addMcId.Key) is null) - context.MangaConnectorToManga.Add(mcId); - context.SaveChanges(); - } - catch (DbUpdateException e) - { + + if(context.MangaConnectorToManga.Find(addMcId.Key) is null) + context.MangaConnectorToManga.Add(mcId); + + if (context.Sync().Result is not null) return null; - } return manga; } } \ No newline at end of file diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index 09a2a48..597f92c 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -1,18 +1,18 @@ using API.MangaDownloadClients; -using API.Schema.JobsContext.Jobs; using API.Schema.MangaContext; +using API.Workers; using Asp.Versioning; -using log4net; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; using static Microsoft.AspNetCore.Http.StatusCodes; +// ReSharper disable InconsistentNaming namespace API.Controllers; [ApiVersion(2)] [ApiController] [Route("v{v:apiVersion}/[controller]")] -public class SettingsController(MangaContext context, ILog Log) : Controller +public class SettingsController(IServiceScope scope) : Controller { /// /// Get all Settings @@ -237,58 +237,25 @@ public class SettingsController(MangaContext context, ILog Log) : Controller /// %C Chapter /// %T Title /// %A Author (first in list) - /// %I Chapter Internal ID - /// %i Obj Internal ID /// %Y Year (Obj) /// /// ?_(...) replace _ with a value from above: /// Everything inside the braces will only be added if the value of %_ is not null /// /// - /// Error during Database Operation [HttpPatch("ChapterNamingScheme")] [ProducesResponseType(Status200OK)] - [ProducesResponseType(Status500InternalServerError, "text/plain")] public IActionResult SetCustomNamingScheme([FromBody]string namingScheme) { - try - { - Dictionary oldPaths = context.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath); - TrangaSettings.UpdateChapterNamingScheme(namingScheme); - MoveFileOrFolderJob[] newJobs = oldPaths - .Select(kv => new MoveFileOrFolderJob(kv.Value, kv.Key.FullArchiveFilePath)).ToArray(); - context.Jobs.AddRange(newJobs); - return Ok(); - } - catch (Exception e) - { - Log.Error(e); - return StatusCode(500, e); - } - } - - /// - /// Creates a UpdateCoverJob for all Obj - /// - /// Array of JobIds - /// Error during Database Operation - [HttpPost("CleanupCovers")] - [ProducesResponseType(Status200OK)] - [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult CleanupCovers() - { - try - { - Tranga.RemoveStaleFiles(context); - List 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); - } + MangaContext context = scope.ServiceProvider.GetRequiredService(); + + Dictionary oldPaths = context.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath); + TrangaSettings.UpdateChapterNamingScheme(namingScheme); + MoveFileOrFolderWorker[] newJobs = oldPaths + .Select(kv => new MoveFileOrFolderWorker(kv.Value, kv.Key.FullArchiveFilePath)).ToArray(); + Tranga.AddWorkers(newJobs); + + return Ok(); } /// diff --git a/API/Controllers/WorkerController.cs b/API/Controllers/WorkerController.cs index 4349433..0b3816e 100644 --- a/API/Controllers/WorkerController.cs +++ b/API/Controllers/WorkerController.cs @@ -84,110 +84,73 @@ public class WorkerController(ILog Log) : Controller } /// - /// Modify Job with ID + /// Modify with /// - /// Job-ID - /// Fields to modify, set to null to keep previous value - /// Job modified - /// Malformed request - /// Job with ID not found - /// Error during Database Operation - [HttpPatch("{JobId}")] - [ProducesResponseType(Status202Accepted, "application/json")] + /// .Key + /// Fields to modify, set to null to keep previous value + /// + /// + /// with could not be found + /// is not , can not modify + [HttpPatch("{WorkerId}")] + [ProducesResponseType(Status202Accepted, "application/json")] [ProducesResponseType(Status400BadRequest)] [ProducesResponseType(Status404NotFound)] - [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult ModifyJob(string JobId, [FromBody]ModifyJobRecord modifyJobRecord) + [ProducesResponseType(Status409Conflict, "text/plain")] + public IActionResult ModifyJob(string WorkerId, [FromBody]ModifyWorkerRecord modifyWorkerRecord) { - try - { - Job? ret = context.Jobs.Find(JobId); - if(ret is null) - return NotFound(); - - ret.RecurrenceMs = modifyJobRecord.RecurrenceMs ?? ret.RecurrenceMs; - ret.Enabled = modifyJobRecord.Enabled ?? ret.Enabled; - - context.SaveChanges(); - return new AcceptedResult(ret.Key, ret); - } - catch (Exception e) - { - Log.Error(e); - return StatusCode(500, e.Message); - } + if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker) + return NotFound(nameof(WorkerId)); + + if(modifyWorkerRecord.IntervalMs is not null && worker is not IPeriodic) + 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); + + return Accepted(worker); } /// - /// Starts the Job with the requested ID + /// Starts with /// - /// Job-ID - /// Start Jobs necessary for execution - /// Job started - /// Job with ID not found - /// Job was already running - /// Error during Database Operation - [HttpPost("{JobId}/Start")] + /// .Key + /// + /// with could not be found + /// was already running + [HttpPost("{WorkerId}/Start")] [ProducesResponseType(Status202Accepted)] [ProducesResponseType(Status404NotFound)] - [ProducesResponseType(Status409Conflict)] - [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult StartJob(string JobId, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]bool startDependencies = false) + [ProducesResponseType(Status412PreconditionFailed, "text/plain")] + public IActionResult StartJob(string WorkerId) { - Job? ret = context.Jobs.Find(JobId); - if (ret is null) - return NotFound(); - List dependencies = startDependencies ? ret.GetDependenciesAndSelf() : [ret]; + if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker) + return NotFound(nameof(WorkerId)); - try - { - 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); - } + if (worker.State >= WorkerExecutionState.Waiting) + return StatusCode(Status412PreconditionFailed, "Already running"); + + Tranga.StartWorker(worker); + return Ok(); } /// - /// Stops the Job with the requested ID + /// Stops with /// - /// Job-ID - ///

NOT IMPLEMENTED

- [HttpPost("{JobId}/Stop")] + /// .Key + /// + /// with could not be found + /// was not running + [HttpPost("{WorkerId}/Stop")] [ProducesResponseType(Status501NotImplemented)] - public IActionResult StopJob(string JobId) + public IActionResult StopJob(string WorkerId) { - return StatusCode(Status501NotImplemented); - } - - /// - /// Removes failed and completed Jobs (that are not recurring) - /// - /// Job started - /// Error during Database Operation - [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(); - } - catch (Exception e) - { - Log.Error(e); - return StatusCode(500, e.Message); - } + if(Tranga.Workers.FirstOrDefault(w => w.Key == WorkerId) is not { } worker) + return NotFound(nameof(WorkerId)); + + if(worker.State is < WorkerExecutionState.Running or >= WorkerExecutionState.Completed) + return StatusCode(Status208AlreadyReported, "Not running"); + + Tranga.StopWorker(worker); + return Ok(); } } \ No newline at end of file diff --git a/API/Program.cs b/API/Program.cs index 1b5cc90..9e013d8 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -118,8 +118,8 @@ using (IServiceScope scope = app.Services.CreateScope()) ]; MangaConnector[] newConnectors = connectors.Where(c => !context.MangaConnectors.Contains(c)).ToArray(); context.MangaConnectors.AddRange(newConnectors); - if (!context.LocalLibraries.Any()) - context.LocalLibraries.Add(new FileLibrary(TrangaSettings.downloadLocation, "Default FileLibrary")); + if (!context.FileLibraries.Any()) + context.FileLibraries.Add(new FileLibrary(TrangaSettings.downloadLocation, "Default FileLibrary")); context.Sync(); } diff --git a/API/Schema/LibraryContext/LibraryConnectors/Kavita.cs b/API/Schema/LibraryContext/LibraryConnectors/Kavita.cs index 4543c40..04bcdf8 100644 --- a/API/Schema/LibraryContext/LibraryConnectors/Kavita.cs +++ b/API/Schema/LibraryContext/LibraryConnectors/Kavita.cs @@ -6,7 +6,7 @@ namespace API.Schema.LibraryContext.LibraryConnectors; 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) { } diff --git a/API/Schema/LibraryContext/LibraryConnectors/Komga.cs b/API/Schema/LibraryContext/LibraryConnectors/Komga.cs index 2e94e84..601f4ab 100644 --- a/API/Schema/LibraryContext/LibraryConnectors/Komga.cs +++ b/API/Schema/LibraryContext/LibraryConnectors/Komga.cs @@ -5,8 +5,7 @@ namespace API.Schema.LibraryContext.LibraryConnectors; public class Komga : LibraryConnector { - public Komga(string baseUrl, string auth) : base(TokenGen.CreateToken(typeof(Komga), baseUrl), LibraryType.Komga, - baseUrl, auth) + public Komga(string baseUrl, string auth) : base(LibraryType.Komga, baseUrl, auth) { } diff --git a/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs b/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs index 750fd16..e58bba2 100644 --- a/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs +++ b/API/Schema/LibraryContext/LibraryConnectors/LibraryConnector.cs @@ -7,26 +7,51 @@ using Newtonsoft.Json; namespace API.Schema.LibraryContext.LibraryConnectors; [PrimaryKey("LibraryConnectorId")] -public abstract class LibraryConnector(string libraryConnectorId, LibraryType libraryType, string baseUrl, string auth) +public abstract class LibraryConnector : Identifiable { - [StringLength(64)] [Required] - public string LibraryConnectorId { get; } = libraryConnectorId; - - [Required] - public LibraryType LibraryType { get; init; } = libraryType; + public LibraryType LibraryType { get; init; } [StringLength(256)] [Required] [Url] - public string BaseUrl { get; init; } = baseUrl; + public string BaseUrl { get; init; } [StringLength(256)] [Required] - public string Auth { get; init; } = auth; + public string Auth { get; init; } [JsonIgnore] [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()); + } + + /// + /// EF CORE ONLY!!!! + /// + 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(); internal abstract bool Test(); +} + +public enum LibraryType : byte +{ + Komga = 0, + Kavita = 1 } \ No newline at end of file diff --git a/API/Schema/LibraryContext/LibraryConnectors/LibraryType.cs b/API/Schema/LibraryContext/LibraryConnectors/LibraryType.cs deleted file mode 100644 index b2454a8..0000000 --- a/API/Schema/LibraryContext/LibraryConnectors/LibraryType.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace API.Schema.LibraryContext.LibraryConnectors; - -public enum LibraryType : byte -{ - Komga = 0, - Kavita = 1 -} \ No newline at end of file diff --git a/API/Schema/MangaContext/MangaContext.cs b/API/Schema/MangaContext/MangaContext.cs index 2371690..3e8e9e6 100644 --- a/API/Schema/MangaContext/MangaContext.cs +++ b/API/Schema/MangaContext/MangaContext.cs @@ -8,7 +8,7 @@ public class MangaContext(DbContextOptions options) : TrangaBaseCo { public DbSet MangaConnectors { get; set; } public DbSet Mangas { get; set; } - public DbSet LocalLibraries { get; set; } + public DbSet FileLibraries { get; set; } public DbSet Chapters { get; set; } public DbSet Authors { get; set; } public DbSet Tags { get; set; } diff --git a/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs b/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs index e8550fa..a239a08 100644 --- a/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs +++ b/API/Schema/MangaContext/MetadataFetchers/MetadataEntry.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace API.Schema.MangaContext.MetadataFetchers; -[PrimaryKey("MetadataFetcherName", "Identifier")] +[PrimaryKey("Name", "Identifier")] public class MetadataEntry { [JsonIgnore] @@ -19,7 +19,7 @@ public class MetadataEntry this.Manga = manga; this.MangaId = manga.Key; this.MetadataFetcher = fetcher; - this.MetadataFetcherName = fetcher.MetadataFetcherName; + this.MetadataFetcherName = fetcher.Name; this.Identifier = identifier; } diff --git a/API/Schema/MangaContext/MetadataFetchers/MetadataFetcher.cs b/API/Schema/MangaContext/MetadataFetchers/MetadataFetcher.cs index 48f8104..37c36b8 100644 --- a/API/Schema/MangaContext/MetadataFetchers/MetadataFetcher.cs +++ b/API/Schema/MangaContext/MetadataFetchers/MetadataFetcher.cs @@ -2,23 +2,23 @@ using Microsoft.EntityFrameworkCore; namespace API.Schema.MangaContext.MetadataFetchers; -[PrimaryKey("MetadataFetcherName")] +[PrimaryKey("Name")] public abstract class MetadataFetcher { // ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength - public string MetadataFetcherName { get; init; } + public string Name { get; init; } protected MetadataFetcher() { - this.MetadataFetcherName = this.GetType().Name; + this.Name = this.GetType().Name; } /// /// EFCORE ONLY!!! /// - internal MetadataFetcher(string metadataFetcherName) + internal MetadataFetcher(string name) { - this.MetadataFetcherName = metadataFetcherName; + this.Name = name; } internal MetadataEntry CreateMetadataEntry(Manga manga, string identifier) => diff --git a/API/Tranga.cs b/API/Tranga.cs index ac6eb83..7a8c6c1 100644 --- a/API/Tranga.cs +++ b/API/Tranga.cs @@ -71,4 +71,14 @@ public static class Tranga Thread.Sleep(TrangaSettings.workCycleTimeout); } } + + internal static void StartWorker(BaseWorker worker) + { + throw new NotImplementedException(); + } + + internal static void StopWorker(BaseWorker worker) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/API/Workers/BaseWorker.cs b/API/Workers/BaseWorker.cs index 497affa..49b6dca 100644 --- a/API/Workers/BaseWorker.cs +++ b/API/Workers/BaseWorker.cs @@ -13,7 +13,12 @@ public abstract class BaseWorker : Identifiable internal WorkerExecutionState State { get; set; } private static readonly CancellationTokenSource CancellationTokenSource = new(TimeSpan.FromMinutes(10)); 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; public BaseWorker(IEnumerable? dependsOn = null) @@ -58,5 +63,6 @@ public enum WorkerExecutionState Created = 64, Waiting = 96, Running = 128, - Completed = 192 + Completed = 192, + Cancelled = 193 } \ No newline at end of file diff --git a/API/Workers/IPeriodic.cs b/API/Workers/IPeriodic.cs index 4260f9a..14c0fc3 100644 --- a/API/Workers/IPeriodic.cs +++ b/API/Workers/IPeriodic.cs @@ -1,9 +1,9 @@ namespace API.Workers; -public interface IPeriodic where T : BaseWorker +public interface IPeriodic { protected DateTime LastExecution { get; set; } - protected TimeSpan Interval { get; set; } - + public TimeSpan Interval { get; set; } public DateTime NextExecution => LastExecution.Add(Interval); + public bool IsDue => NextExecution <= DateTime.UtcNow; } \ No newline at end of file