< Summary

Information
Class: Chronicis.Api.Controllers.HandwrittenNoteController
Assembly: Chronicis.Api
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Api/Controllers/HandwrittenNoteController.cs
Line coverage
100%
Covered lines: 8
Uncovered lines: 0
Coverable lines: 8
Total lines: 214
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Api/Controllers/HandwrittenNoteController.cs

#LineLine coverage
 1using Chronicis.Api.Infrastructure;
 2using Chronicis.Api.Services;
 3using Chronicis.Shared.DTOs;
 4using Microsoft.AspNetCore.Authorization;
 5using Microsoft.AspNetCore.Mvc;
 6
 7namespace Chronicis.Api.Controllers;
 8
 9/// <summary>
 10/// Request DTO for handwritten note upload endpoints.
 11/// </summary>
 12public class HandwrittenNoteUploadRequest
 13{
 14    public byte[] ImageBytes { get; set; } = [];
 15}
 16
 17/// <summary>
 18/// API endpoints for handwritten note operations on session note articles.
 19/// </summary>
 20[ApiController]
 21[Route("articles/{articleId:guid}/handwritten-note")]
 22[Authorize]
 23public class HandwrittenNoteController : ControllerBase
 24{
 25    private readonly IHandwrittenNoteService _handwrittenNoteService;
 26    private readonly ICurrentUserService _currentUserService;
 27    private readonly ILogger<HandwrittenNoteController> _logger;
 28
 2229    public HandwrittenNoteController(
 2230        IHandwrittenNoteService handwrittenNoteService,
 2231        ICurrentUserService currentUserService,
 2232        ILogger<HandwrittenNoteController> logger)
 33    {
 2234        _handwrittenNoteService = handwrittenNoteService;
 2235        _currentUserService = currentUserService;
 2236        _logger = logger;
 2237    }
 38
 39    /// <summary>
 40    /// POST articles/{articleId}/handwritten-note — Upload or replace a handwritten note PNG.
 41    /// </summary>
 42    [HttpPost]
 43    public async Task<ActionResult<HandwrittenNoteSaveResultDto>> SaveHandwrittenNote(
 44        Guid articleId,
 45        [FromBody] HandwrittenNoteUploadRequest request)
 46    {
 47        if (request?.ImageBytes == null || request.ImageBytes.Length == 0)
 48        {
 49            return BadRequest(new { error = "Image data is required and cannot be empty" });
 50        }
 51
 52        var user = await _currentUserService.GetRequiredUserAsync();
 53        _logger.LogTraceSanitized("User {UserId} saving handwritten note for article {ArticleId}", user.Id, articleId);
 54
 55        try
 56        {
 57            var result = await _handwrittenNoteService.SaveAsync(articleId, user.Id, request.ImageBytes);
 58            return Ok(result);
 59        }
 60        catch (InvalidOperationException ex)
 61        {
 62            _logger.LogWarningSanitized(ex, "Article not found for handwritten note save");
 63            return NotFound(new { error = ex.Message });
 64        }
 65        catch (UnauthorizedAccessException ex)
 66        {
 67            _logger.LogWarningSanitized(ex, "Unauthorized handwritten note save");
 68            return StatusCode(403, new { error = ex.Message });
 69        }
 70        catch (Exception ex)
 71        {
 72            _logger.LogErrorSanitized(ex, "Failed to save handwritten note for article {ArticleId}", articleId);
 73            return StatusCode(500, new { error = "Failed to save handwritten note" });
 74        }
 75    }
 76
 77    /// <summary>
 78    /// POST articles/{articleId}/handwritten-note/transcribe — Save and transcribe a handwritten note.
 79    /// </summary>
 80    [HttpPost("transcribe")]
 81    public async Task<ActionResult<HandwrittenNoteTranscribeResultDto>> TranscribeHandwrittenNote(
 82        Guid articleId,
 83        [FromBody] HandwrittenNoteUploadRequest request,
 84        [FromQuery] bool confirmOverwrite = false)
 85    {
 86        if (request?.ImageBytes == null || request.ImageBytes.Length == 0)
 87        {
 88            return BadRequest(new { error = "Image data is required and cannot be empty" });
 89        }
 90
 91        var user = await _currentUserService.GetRequiredUserAsync();
 92        _logger.LogTraceSanitized("User {UserId} transcribing handwritten note for article {ArticleId}", user.Id, articl
 93
 94        try
 95        {
 96            var result = await _handwrittenNoteService.TranscribeAsync(articleId, user.Id, request.ImageBytes);
 97            return Ok(result);
 98        }
 99        catch (InvalidOperationException ex)
 100        {
 101            _logger.LogWarningSanitized(ex, "Transcription failed for article {ArticleId}", articleId);
 102            return NotFound(new { error = ex.Message });
 103        }
 104        catch (UnauthorizedAccessException ex)
 105        {
 106            _logger.LogWarningSanitized(ex, "Unauthorized transcription request");
 107            return StatusCode(403, new { error = ex.Message });
 108        }
 109        catch (Exception ex)
 110        {
 111            _logger.LogErrorSanitized(ex, "Failed to transcribe handwritten note for article {ArticleId}", articleId);
 112            return StatusCode(500, new { error = "Failed to transcribe handwritten note" });
 113        }
 114    }
 115
 116    /// <summary>
 117    /// POST articles/{articleId}/handwritten-note/transcribe-existing — Transcribe an already-saved handwritten note.
 118    /// </summary>
 119    [HttpPost("transcribe-existing")]
 120    public async Task<ActionResult<HandwrittenNoteTranscribeResultDto>> TranscribeExistingHandwrittenNote(Guid articleId
 121    {
 122        var user = await _currentUserService.GetRequiredUserAsync();
 123        _logger.LogTraceSanitized("User {UserId} transcribing existing handwritten note for article {ArticleId}", user.I
 124
 125        try
 126        {
 127            var result = await _handwrittenNoteService.TranscribeExistingAsync(articleId, user.Id);
 128            return Ok(result);
 129        }
 130        catch (InvalidOperationException ex)
 131        {
 132            _logger.LogWarningSanitized(ex, "Transcription of existing note failed for article {ArticleId}", articleId);
 133            return NotFound(new { error = ex.Message });
 134        }
 135        catch (UnauthorizedAccessException ex)
 136        {
 137            _logger.LogWarningSanitized(ex, "Unauthorized transcription request");
 138            return StatusCode(403, new { error = ex.Message });
 139        }
 140        catch (Exception ex)
 141        {
 142            _logger.LogErrorSanitized(ex, "Failed to transcribe existing handwritten note for article {ArticleId}", arti
 143            return StatusCode(500, new { error = "Failed to transcribe handwritten note" });
 144        }
 145    }
 146
 147    /// <summary>
 148    /// GET articles/{articleId}/handwritten-note — Get download URL for the handwritten note image.
 149    /// </summary>
 150    [HttpGet]
 151    public async Task<ActionResult<object>> GetHandwrittenNoteUrl(Guid articleId)
 152    {
 153        var user = await _currentUserService.GetRequiredUserAsync();
 154        _logger.LogTraceSanitized("User {UserId} getting handwritten note URL for article {ArticleId}", user.Id, article
 155
 156        try
 157        {
 158            var url = await _handwrittenNoteService.GetImageDownloadUrlAsync(articleId, user.Id);
 159
 160            if (url == null)
 161            {
 162                return NotFound(new { error = "No handwritten note exists for this article" });
 163            }
 164
 165            return Ok(new { downloadUrl = url });
 166        }
 167        catch (InvalidOperationException ex)
 168        {
 169            _logger.LogWarningSanitized(ex, "Article not found for handwritten note URL");
 170            return NotFound(new { error = ex.Message });
 171        }
 172        catch (UnauthorizedAccessException ex)
 173        {
 174            _logger.LogWarningSanitized(ex, "Unauthorized handwritten note URL request");
 175            return StatusCode(403, new { error = ex.Message });
 176        }
 177        catch (Exception ex)
 178        {
 179            _logger.LogErrorSanitized(ex, "Failed to get handwritten note URL for article {ArticleId}", articleId);
 180            return StatusCode(500, new { error = "Failed to get handwritten note URL" });
 181        }
 182    }
 183
 184    /// <summary>
 185    /// DELETE api/articles/{articleId}/handwritten-note — Delete the handwritten note image.
 186    /// </summary>
 187    [HttpDelete]
 188    public async Task<IActionResult> DeleteHandwrittenNote(Guid articleId)
 189    {
 190        var user = await _currentUserService.GetRequiredUserAsync();
 191        _logger.LogTraceSanitized("User {UserId} deleting handwritten note for article {ArticleId}", user.Id, articleId)
 192
 193        try
 194        {
 195            await _handwrittenNoteService.DeleteAsync(articleId, user.Id);
 196            return NoContent();
 197        }
 198        catch (InvalidOperationException ex)
 199        {
 200            _logger.LogWarningSanitized(ex, "Article not found for handwritten note deletion");
 201            return NotFound(new { error = ex.Message });
 202        }
 203        catch (UnauthorizedAccessException ex)
 204        {
 205            _logger.LogWarningSanitized(ex, "Unauthorized handwritten note deletion");
 206            return StatusCode(403, new { error = ex.Message });
 207        }
 208        catch (Exception ex)
 209        {
 210            _logger.LogErrorSanitized(ex, "Failed to delete handwritten note for article {ArticleId}", articleId);
 211            return StatusCode(500, new { error = "Failed to delete handwritten note" });
 212        }
 213    }
 214}