1 line
11 KiB
JSON
1 line
11 KiB
JSON
{"path":"NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs","purpose":"MediatR handler that persists a user's reading position (User and Ebook entities) and broadcasts the progress update to other connected devices.","classification":{"role":"handler","layer":"application","confidence":0.9,"evidence":["Command pattern","Implements IRequestHandler<UpdateReadingProgressCommand, Result> (line 13)","Persists using AppDbContext and broadcasts via ISyncBroadcaster (lines 15-16, 28, 52)"]},"className":"UpdateReadingProgressCommandHandler","methods":[{"name":"UpdateReadingProgressCommandHandler","line":18,"endLine":24,"signature":"(dbContextFactory: IDbContextFactory<AppDbContext>, broadcaster: ISyncBroadcaster) -> UpdateReadingProgressCommandHandler","purpose":"Constructor: injects the AppDbContext factory and an ISyncBroadcaster.","calls":[],"actions":[{"id":"assign_22","kind":"mapping","label":"Store injected dependencies","line":22,"detail":"_dbContextFactory = dbContextFactory; _broadcaster = broadcaster;","visibility":"detail-only","confidence":0.7}]},{"name":"Handle","line":26,"endLine":60,"signature":"(request: UpdateReadingProgressCommand, cancellationToken: CancellationToken) -> Task<Result>","purpose":"Load user and ebook, update reading progress and timestamps, persist changes, then broadcast the progress to other devices; returns success or failure if user missing.","calls":[{"targetFile":"unknown","targetMethod":"IDbContextFactory<AppDbContext>.CreateDbContextAsync","callLine":28,"paramSummary":"cancellationToken"},{"targetFile":"NexusReader.Data.Persistence","targetMethod":"AppDbContext.Users.FirstOrDefaultAsync","callLine":30,"paramSummary":"predicate u => u.Id == request.UserId, cancellationToken"},{"targetFile":"NexusReader.Data.Persistence","targetMethod":"AppDbContext.Ebooks.FirstOrDefaultAsync","callLine":40,"paramSummary":"predicate e => e.Id == request.EbookId, cancellationToken"},{"targetFile":"NexusReader.Data.Persistence","targetMethod":"AppDbContext.SaveChangesAsync","callLine":49,"paramSummary":"cancellationToken"},{"targetFile":"unknown","targetMethod":"ISyncBroadcaster.BroadcastProgressAsync","callLine":52,"paramSummary":"request.UserId, request.PageId, now (DateTime.UtcNow), request.ExcludedConnectionId, cancellationToken"}],"actions":[{"id":"resource-acquire_28","kind":"mapping","label":"Create DbContext scope","line":28,"detail":"using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);","visibility":"detail-only","confidence":0.7},{"id":"handle_await_28_0","kind":"await","label":"Waits for async work","line":28,"detail":"using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);","visibility":"secondary-visible","confidence":0.81},{"id":"handle_repository-read_30_1","kind":"repository-read","label":"Reads repository or persistence state","line":30,"detail":"var user = await context.Users.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);","visibility":"secondary-visible","confidence":0.86},{"id":"handle_await_30_2","kind":"await","label":"Waits for async work","line":30,"detail":"var user = await context.Users.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);","visibility":"secondary-visible","confidence":0.81},{"id":"handle_branch_31_3","kind":"branch","label":"Evaluates branch condition","line":31,"detail":"if (user == null)","conditionSummary":"user == null","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"guard-clause_31","kind":"guard-clause","label":"User existence check","line":31,"detail":"if (user == null) return Result.Fail(\"User not found.\");","conditionSummary":"user == null","outcomeLabels":["return Fail","continue"],"visibility":"detail-only","confidence":0.7},{"id":"handle_return_33_4","kind":"return","label":"Returns result","line":33,"detail":"return Result.Fail(\"User not found.\");","visibility":"detail-only","confidence":0.7},{"id":"field-update_37","kind":"mapping","label":"Update user reading pointers/timestamps","line":37,"detail":"user.LastReadPageId = request.PageId; user.LastReadAt = now;","visibility":"detail-only","confidence":0.7},{"id":"handle_repository-read_40_5","kind":"repository-read","label":"Reads repository or persistence state","line":40,"detail":"var ebook = await context.Ebooks.FirstOrDefaultAsync(e => e.Id == request.EbookId, cancellationToken);","visibility":"secondary-visible","confidence":0.86},{"id":"handle_await_40_6","kind":"await","label":"Waits for async work","line":40,"detail":"var ebook = await context.Ebooks.FirstOrDefaultAsync(e => e.Id == request.EbookId, cancellationToken);","visibility":"secondary-visible","confidence":0.81},{"id":"handle_branch_41_7","kind":"branch","label":"Evaluates branch condition","line":41,"detail":"if (ebook != null)","conditionSummary":"ebook != null","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"branch_41","kind":"branch","label":"Optional ebook update","line":41,"detail":"If ebook found, set Progress, LastChapter, LastChapterIndex, LastReadDate (lines 43-46).","conditionSummary":"ebook != null","outcomeLabels":["update ebook fields","skip"],"visibility":"detail-only","confidence":0.7},{"id":"persist_49","kind":"mapping","label":"Persist entity changes","line":49,"detail":"await context.SaveChangesAsync(cancellationToken);","visibility":"detail-only","confidence":0.7},{"id":"handle_repository-read_49_8","kind":"repository-read","label":"Reads repository or persistence state","line":49,"detail":"await context.SaveChangesAsync(cancellationToken);","visibility":"secondary-visible","confidence":0.86},{"id":"handle_await_49_9","kind":"await","label":"Waits for async work","line":49,"detail":"await context.SaveChangesAsync(cancellationToken);","visibility":"secondary-visible","confidence":0.81},{"id":"external-call_52","kind":"external-call","label":"Broadcast progress to other devices","line":52,"detail":"await _broadcaster.BroadcastProgressAsync(request.UserId, request.PageId, now, request.ExcludedConnectionId, cancellationToken);","visibility":"detail-only","confidence":0.7},{"id":"handle_await_52_10","kind":"await","label":"Waits for async work","line":52,"detail":"await _broadcaster.BroadcastProgressAsync(","visibility":"secondary-visible","confidence":0.81},{"id":"return_59","kind":"return","label":"Return success result","line":59,"detail":"return Result.Ok();","visibility":"detail-only","confidence":0.7},{"id":"handle_return_59_11","kind":"return","label":"Returns result","line":59,"detail":"return Result.Ok();","visibility":"detail-only","confidence":0.7}]}],"types":[{"name":"UpdateReadingProgressCommandHandler","kind":"model","line":13,"purpose":"MediatR request handler that orchestrates persistence of reading progress and broadcasting updates.","fields":[{"name":"_dbContextFactory","type":"IDbContextFactory<AppDbContext>","required":true,"line":15,"description":"Factory to create AppDbContext instances for database operations."},{"name":"_broadcaster","type":"ISyncBroadcaster","required":true,"line":16,"description":"Abstraction used to broadcast progress updates to other connected devices."}]}],"serviceRegistrations":[],"startupActions":[],"dependencies":["NexusReader.Data.Persistence","NexusReader.Application.Abstractions.Messaging"],"patterns":["Mediator (IRequestHandler)","Repository/DbContext usage"],"domainConcepts":["User","Ebook","ReadingProgress"],"keyDetails":"Checks for user presence and returns failure if missing; updates user and optionally ebook, saves changes and broadcasts update to other devices while honoring an excluded connection id; cancellationToken is propagated to async DB and broadcaster calls.","orchestrationMethods":[{"name":"Handle","line":26,"confidence":0.98,"reason":"Coordinates 5 downstream calls with 7 architectural actions.","actionKinds":["mapping","await","repository-read","branch","guard-clause","return","external-call"],"evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs","unknown","NexusReader.Data.Persistence","NexusReader.Data.Persistence","NexusReader.Data.Persistence","unknown"]}],"typedContracts":[{"name":"UpdateReadingProgressCommandHandler","kind":"model","line":13,"fieldCount":2,"evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs"]}],"persistenceInteractions":[{"methodName":"Handle","line":30,"kind":"persistence-read","detail":"var user = await context.Users.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);","evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs"]},{"methodName":"Handle","line":40,"kind":"persistence-read","detail":"var ebook = await context.Ebooks.FirstOrDefaultAsync(e => e.Id == request.EbookId, cancellationToken);","evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs"]},{"methodName":"Handle","line":49,"kind":"persistence-read","detail":"await context.SaveChangesAsync(cancellationToken);","evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs"]}],"externalInteractions":[{"methodName":"Handle","line":52,"kind":"external-call","detail":"await _broadcaster.BroadcastProgressAsync(request.UserId, request.PageId, now, request.ExcludedConnectionId, cancellationToken);","evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs"]}],"evidenceAnchors":[{"kind":"orchestration-method","label":"Handle","line":26,"summary":"Coordinates 5 downstream calls with 7 architectural actions.","confidence":0.98,"evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs","unknown","NexusReader.Data.Persistence","NexusReader.Data.Persistence","NexusReader.Data.Persistence","unknown"]},{"kind":"typed-contract","label":"UpdateReadingProgressCommandHandler","line":13,"summary":"model with 2 fields.","confidence":0.8,"evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs"]},{"kind":"persistence","label":"Handle","line":30,"summary":"var user = await context.Users.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);","confidence":0.82,"evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs"]},{"kind":"persistence","label":"Handle","line":40,"summary":"var ebook = await context.Ebooks.FirstOrDefaultAsync(e => e.Id == request.EbookId, cancellationToken);","confidence":0.82,"evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs"]},{"kind":"persistence","label":"Handle","line":49,"summary":"await context.SaveChangesAsync(cancellationToken);","confidence":0.82,"evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs"]},{"kind":"external-call","label":"Handle","line":52,"summary":"await _broadcaster.BroadcastProgressAsync(request.UserId, request.PageId, now, request.ExcludedConnectionId, cancellationToken);","confidence":0.8,"evidencePaths":["NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs"]}],"cacheMetadata":{"schemaVersion":2,"analysisVersion":"2026-05-23.cache-v1","contentChecksum":"0822c683f589712bee0533b15c5fbeb79014e044ee84881405ba44ac966e4625","sourceByteSize":2182,"analyzedAt":"2026-05-23T16:17:14.652Z","technology":"dotnet"}} |