{"path":"NexusReader.Application/Commands/Library/IngestEbookCommandHandler.cs","purpose":"Handles ingestion of an uploaded ebook: saves files to storage, resolves/creates author, creates Ebook entity and persists it to the repository, returning the new Ebook Id or a failure result.","classification":{"role":"handler","layer":"application","confidence":0.9,"evidence":["Command pattern","Class implements MediatR IRequestHandler","Uses IEbookRepository and IBookStorageService to orchestrate storage + persistence"]},"className":"IngestEbookCommandHandler","methods":[{"name":"IngestEbookCommandHandler","line":15,"endLine":21,"signature":"(ebookRepository: IEbookRepository, storageService: IBookStorageService) -> IngestEbookCommandHandler","purpose":"Constructor; receives repository and storage service via DI and assigns to fields.","calls":[],"actions":[{"id":"assignment_19","kind":"mapping","label":"Assign DI dependencies","line":19,"detail":"_ebookRepository = ebookRepository; _storageService = storageService;","visibility":"detail-only","confidence":0.7}]},{"name":"Handle","line":23,"endLine":81,"signature":"(request: IngestEbookCommand, cancellationToken: CancellationToken) -> Task>","purpose":"Orchestrates ebook ingestion: saves epub/cover to storage, resolves or creates author, constructs Ebook entity, persists it and returns the created Ebook Id or failure.","calls":[{"targetFile":"NexusReader.Application.Abstractions.Services/IBookStorageService.cs","targetMethod":"SaveEbookAsync","callLine":31,"paramSummary":"request.EpubData, filename = \"{request.Title}.epub\""},{"targetFile":"NexusReader.Application.Abstractions.Services/IBookStorageService.cs","targetMethod":"SaveCoverAsync","callLine":33,"paramSummary":"request.CoverImage, filename = \"{request.Title}_cover.jpg\" (called only if CoverImage present)"},{"targetFile":"NexusReader.Application.Abstractions.Persistence/IEbookRepository.cs","targetMethod":"FindAuthorByNameAsync","callLine":52,"paramSummary":"authorName (trimmed or 'Unknown Author'), cancellationToken"},{"targetFile":"NexusReader.Application.Abstractions.Persistence/IEbookRepository.cs","targetMethod":"AddAuthor","callLine":56,"paramSummary":"new Author { Name = authorName } (when author not found)"},{"targetFile":"NexusReader.Application.Abstractions.Persistence/IEbookRepository.cs","targetMethod":"AddEbook","callLine":72,"paramSummary":"ebook (Ebook entity constructed from request + paths + metadata)"},{"targetFile":"NexusReader.Application.Abstractions.Persistence/IEbookRepository.cs","targetMethod":"SaveChangesAsync","callLine":73,"paramSummary":"cancellationToken"}],"actions":[{"id":"handle_try_28_0","kind":"try","label":"Begins protected execution","line":28,"detail":"try","visibility":"primary-visible","confidence":0.84},{"id":"try-catch_28","kind":"mapping","label":"Storage save with I/O and generic exception handling","line":28,"detail":"Try saving epub and optional cover; catch IOException -> return storage I/O failure Result.Fail; catch Exception -> return storage failure Result.Fail","visibility":"detail-only","confidence":0.7},{"id":"handle_await_31_1","kind":"await","label":"Waits for async work","line":31,"detail":"epubPath = await _storageService.SaveEbookAsync(request.EpubData, $\"{request.Title}.epub\");","visibility":"secondary-visible","confidence":0.81},{"id":"branch_32","kind":"branch","label":"Optional cover save","line":32,"detail":"Conditional call to SaveCoverAsync when image present","conditionSummary":"request.CoverImage != null && request.CoverImage.Length > 0","outcomeLabels":["save cover and set coverUrl","no cover (coverUrl = null)"],"visibility":"detail-only","confidence":0.7},{"id":"handle_await_33_2","kind":"await","label":"Waits for async work","line":33,"detail":"? await _storageService.SaveCoverAsync(request.CoverImage, $\"{request.Title}_cover.jpg\")","visibility":"secondary-visible","confidence":0.81},{"id":"handle_catch_36_3","kind":"catch","label":"Handles exception path","line":36,"detail":"catch (IOException ex)","conditionSummary":"IOException ex","outcomeLabels":["handled exception"],"visibility":"primary-visible","confidence":0.86},{"id":"handle_return_38_4","kind":"return","label":"Returns result","line":38,"detail":"return Result.Fail(new Error($\"Storage I/O failure: {ex.Message}\").CausedBy(ex));","visibility":"detail-only","confidence":0.7},{"id":"handle_catch_40_5","kind":"catch","label":"Handles exception path","line":40,"detail":"catch (Exception ex)","conditionSummary":"Exception ex","outcomeLabels":["handled exception"],"visibility":"primary-visible","confidence":0.86},{"id":"handle_return_42_6","kind":"return","label":"Returns result","line":42,"detail":"return Result.Fail(new Error($\"Storage failure: {ex.Message}\").CausedBy(ex));","visibility":"detail-only","confidence":0.7},{"id":"handle_try_45_7","kind":"try","label":"Begins protected execution","line":45,"detail":"try","visibility":"primary-visible","confidence":0.84},{"id":"guard-clause_48","kind":"guard-clause","label":"Normalize author name","line":48,"detail":"Sets authorName to 'Unknown Author' if missing, else trimmed value","conditionSummary":"string.IsNullOrWhiteSpace(request.AuthorName)","outcomeLabels":["use 'Unknown Author'","use trimmed request.AuthorName"],"visibility":"detail-only","confidence":0.7},{"id":"external-repository-read_52","kind":"mapping","label":"Find author case-insensitively (via repository)","line":52,"detail":"Calls FindAuthorByNameAsync and branches on null","visibility":"detail-only","confidence":0.7},{"id":"handle_repository-read_52_8","kind":"repository-read","label":"Reads repository or persistence state","line":52,"detail":"var author = await _ebookRepository.FindAuthorByNameAsync(authorName, cancellationToken);","visibility":"secondary-visible","confidence":0.86},{"id":"handle_await_52_9","kind":"await","label":"Waits for async work","line":52,"detail":"var author = await _ebookRepository.FindAuthorByNameAsync(authorName, cancellationToken);","visibility":"secondary-visible","confidence":0.81},{"id":"branch-fallback_53","kind":"mapping","label":"Create missing author","line":53,"detail":"Adds new Author to repository when not found","conditionSummary":"author == null","outcomeLabels":["create new Author and AddAuthor","reuse existing author"],"visibility":"detail-only","confidence":0.7},{"id":"handle_branch_53_10","kind":"branch","label":"Evaluates branch condition","line":53,"detail":"if (author == null)","conditionSummary":"author == null","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"entity-construction_60","kind":"mapping","label":"Construct Ebook entity","line":60,"detail":"Populate Title, Author, FilePath, CoverUrl, Description, UserId, TenantId, AddedDate = DateTime.UtcNow","visibility":"detail-only","confidence":0.7},{"id":"repository-write_72","kind":"repository-write","label":"Add ebook and persist","line":72,"detail":"Calls AddEbook and SaveChangesAsync to persist the new entity","visibility":"detail-only","confidence":0.7},{"id":"handle_await_73_11","kind":"await","label":"Waits for async work","line":73,"detail":"await _ebookRepository.SaveChangesAsync(cancellationToken);","visibility":"secondary-visible","confidence":0.81},{"id":"return_75","kind":"return","label":"Return success with Ebook Id","line":75,"detail":"Result.Ok(ebook.Id) returned on successful save","visibility":"detail-only","confidence":0.7},{"id":"handle_return_75_12","kind":"return","label":"Returns result","line":75,"detail":"return Result.Ok(ebook.Id);","visibility":"detail-only","confidence":0.7},{"id":"try-catch_77","kind":"mapping","label":"Database error handling during persistence","line":77,"detail":"Wraps persistence in try/catch; on Exception returns Result.Fail with database error","visibility":"detail-only","confidence":0.7},{"id":"handle_catch_77_13","kind":"catch","label":"Handles exception path","line":77,"detail":"catch (Exception ex)","conditionSummary":"Exception ex","outcomeLabels":["handled exception"],"visibility":"primary-visible","confidence":0.86},{"id":"handle_return_79_14","kind":"return","label":"Returns result","line":79,"detail":"return Result.Fail(new Error($\"Database error during ingestion: {ex.Message}\").CausedBy(ex));","visibility":"detail-only","confidence":0.7}]}],"types":[],"serviceRegistrations":[],"startupActions":[],"dependencies":["NexusReader.Application.Abstractions.Persistence/IEbookRepository.cs","NexusReader.Application.Abstractions.Services/IBookStorageService.cs","NexusReader.Domain.Entities/Ebook.cs","NexusReader.Domain.Entities/Author.cs"],"patterns":["Repository","Command Handler","Dependency Injection"],"domainConcepts":["Ebook","Author","File storage","Tenant","User"],"keyDetails":"Two-phase ingestion: first persist files to storage (with I/O error mapping), then create/resolve author and persist Ebook in DB (with DB error mapping). Uses MediatR IRequestHandler and repository/service abstractions to keep side effects encapsulated.","orchestrationMethods":[{"name":"Handle","line":23,"confidence":0.98,"reason":"Coordinates 6 downstream calls with 4 architectural actions.","actionKinds":["try","mapping","await","branch","catch","return","guard-clause","repository-read","repository-write"],"evidencePaths":["NexusReader.Application/Commands/Library/IngestEbookCommandHandler.cs","NexusReader.Application.Abstractions.Services/IBookStorageService.cs","NexusReader.Application.Abstractions.Services/IBookStorageService.cs","NexusReader.Application.Abstractions.Persistence/IEbookRepository.cs","NexusReader.Application.Abstractions.Persistence/IEbookRepository.cs","NexusReader.Application.Abstractions.Persistence/IEbookRepository.cs"]}],"typedContracts":[],"persistenceInteractions":[{"methodName":"Handle","line":52,"kind":"persistence-read","detail":"var author = await _ebookRepository.FindAuthorByNameAsync(authorName, cancellationToken);","evidencePaths":["NexusReader.Application/Commands/Library/IngestEbookCommandHandler.cs"]},{"methodName":"Handle","line":72,"kind":"persistence-write","detail":"Calls AddEbook and SaveChangesAsync to persist the new entity","evidencePaths":["NexusReader.Application/Commands/Library/IngestEbookCommandHandler.cs"]}],"externalInteractions":[],"evidenceAnchors":[{"kind":"orchestration-method","label":"Handle","line":23,"summary":"Coordinates 6 downstream calls with 4 architectural actions.","confidence":0.98,"evidencePaths":["NexusReader.Application/Commands/Library/IngestEbookCommandHandler.cs","NexusReader.Application.Abstractions.Services/IBookStorageService.cs","NexusReader.Application.Abstractions.Services/IBookStorageService.cs","NexusReader.Application.Abstractions.Persistence/IEbookRepository.cs","NexusReader.Application.Abstractions.Persistence/IEbookRepository.cs","NexusReader.Application.Abstractions.Persistence/IEbookRepository.cs"]},{"kind":"persistence","label":"Handle","line":52,"summary":"var author = await _ebookRepository.FindAuthorByNameAsync(authorName, cancellationToken);","confidence":0.82,"evidencePaths":["NexusReader.Application/Commands/Library/IngestEbookCommandHandler.cs"]},{"kind":"persistence","label":"Handle","line":72,"summary":"Calls AddEbook and SaveChangesAsync to persist the new entity","confidence":0.82,"evidencePaths":["NexusReader.Application/Commands/Library/IngestEbookCommandHandler.cs"]}],"cacheMetadata":{"schemaVersion":2,"analysisVersion":"2026-05-23.cache-v1","contentChecksum":"04a1cbc9766360062a46f0f77000e58c7cfaa5d32e94d4fd8dfae2f855cb3ba3","sourceByteSize":2902,"analyzedAt":"2026-05-23T16:17:06.532Z","technology":"dotnet"}}