using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System; using System.Threading.Tasks; using API.Data; using API.Data.Repositories; using API.DTOs.Koreader; using API.Entities; using API.Services; using Kavita.Common; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using static System.Net.WebRequestMethods; namespace API.Controllers; #nullable enable /// /// The endpoint to interface with Koreader's Progress Sync plugin. /// /// /// Koreader uses a different form of authentication. It stores the username and password in headers. /// https://github.com/koreader/koreader/blob/master/plugins/kosync.koplugin/KOSyncClient.lua /// [AllowAnonymous] public class KoreaderController : BaseApiController { private readonly IUnitOfWork _unitOfWork; private readonly ILocalizationService _localizationService; private readonly IKoreaderService _koreaderService; private readonly ILogger _logger; public KoreaderController(IUnitOfWork unitOfWork, ILocalizationService localizationService, IKoreaderService koreaderService, ILogger logger) { _unitOfWork = unitOfWork; _localizationService = localizationService; _koreaderService = koreaderService; _logger = logger; } // We won't allow users to be created from Koreader. Rather, they // must already have an account. /* [HttpPost("/users/create")] public IActionResult CreateUser(CreateUserRequest request) { } */ [HttpGet("{apiKey}/users/auth")] public async Task Authenticate(string apiKey) { var userId = await GetUserId(apiKey); var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId); if (user == null) return Unauthorized(); return Ok(new { username = user.UserName }); } /// /// Syncs book progress with Kavita. Will attempt to save the underlying reader position if possible. /// /// /// /// [HttpPut("{apiKey}/syncs/progress")] public async Task> UpdateProgress(string apiKey, KoreaderBookDto request) { try { var userId = await GetUserId(apiKey); await _koreaderService.SaveProgress(request, userId); return Ok(new KoreaderProgressUpdateDto{ Document = request.Document, Timestamp = DateTime.UtcNow }); } catch (KavitaException ex) { return BadRequest(ex.Message); } } /// /// Gets book progress from Kavita, if not found will return a 400 /// /// /// /// [HttpGet("{apiKey}/syncs/progress/{ebookHash}")] public async Task> GetProgress(string apiKey, string ebookHash) { try { var userId = await GetUserId(apiKey); var response = await _koreaderService.GetProgress(ebookHash, userId); _logger.LogDebug("Koreader response progress: {Progress}", response.Progress); return Ok(response); } catch (KavitaException ex) { return BadRequest(ex.Message); } } private async Task GetUserId(string apiKey) { try { return await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey); } catch { throw new KavitaException(await _localizationService.Get("en", "user-doesnt-exist")); } } }