# UpdateReadingProgressCommandHandler

- File ID: file:13c5eb4:updatereadingprogresscommandhandler-cs
- Path: NexusReader.Application/Commands/Sync/UpdateReadingProgressCommandHandler.cs
- Role: handler
- Layer: application

## Purpose

MediatR handler that persists a user's reading position (User and Ebook entities) and broadcasts the progress update to other connected devices.

## Key Details

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.

## Symbols

- UpdateReadingProgressCommandHandler: MediatR handler that persists a user's reading position (User and Ebook entities) and broadcasts the progress update to other connected devices.
- UpdateReadingProgressCommandHandler(): Constructor: injects the AppDbContext factory and an ISyncBroadcaster.
- Handle(): 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.
- UpdateReadingProgressCommandHandler: MediatR request handler that orchestrates persistence of reading progress and broadcasting updates.

## Dependencies

- No resolved dependencies.

## Calls

- No resolved calls.

## Unresolved References

- dependency-target: NexusReader.Data.Persistence
- dependency-target: NexusReader.Application.Abstractions.Messaging
- call-target: unknown
- call-target: NexusReader.Data.Persistence
- call-target: NexusReader.Data.Persistence
- call-target: NexusReader.Data.Persistence
- call-target: unknown
- interaction-target: var user = await context.Users.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
- interaction-target: var ebook = await context.Ebooks.FirstOrDefaultAsync(e => e.Id == request.EbookId, cancellationToken);
- interaction-target: await context.SaveChangesAsync(cancellationToken);
- interaction-target: await _broadcaster.BroadcastProgressAsync(request.UserId, request.PageId, now, request.ExcludedConnectionId, cancellationToken);

## Coverage

- Symbols: 4
- Executable symbols: 2
- Executable symbols with control flow: 2
- Grounded edges: 4
- Unresolved references: 11
- Control-flow fragments: 2
- Unresolved jumps: 5
- Unsupported constructs: 0
- Control-flow coverage: 100%
- Required diagrams: 2
- Generated diagrams: 3

## Control Flow Fragments

- UpdateReadingProgressCommandHandler: 3 nodes, 2 edges, 0 jumps, 0 unsupported constructs
- Handle: 37 nodes, 40 edges, 5 jumps, 0 unsupported constructs

## Diagrams

### UpdateReadingProgressCommandHandler Neighborhood

```mermaid
flowchart LR
    class_2zm0w9_updatereadingprogresscommandhandler("UpdateReadingProgressCommandHandler")
    file_13c5eb4_updatereadingprogresscommandhandler_cs("UpdateReadingProgressCommandHandler")
    method_1ed5bhs_updatereadingprogresscommandhandler[["UpdateReadingProgressCommandHandler()"]]
    method_1jqlijy_handle[["Handle()"]]
    type_1hf0y4h_updatereadingprogresscommandhandler[/"UpdateReadingProgressCommandHandler"/]
```

### UpdateReadingProgressCommandHandler Execution Overview

```mermaid
flowchart TD
    class_2zm0w9_updatereadingprogresscommandhandler("UpdateReadingProgressCommandHandler")
    method_1ed5bhs_updatereadingprogresscommandhandler[["UpdateReadingProgressCommandHandler()"]]
    method_1jqlijy_handle[["Handle()"]]
    unresolved_unresolved_167ab0v_call_target{"Unresolved: AppDbContext.Users.FirstOrDefaultAsync"}
    unresolved_unresolved_197jdm1_call_target{"Unresolved: IDbContextFactory<AppDbContext>.CreateDbContextAsync"}
    unresolved_unresolved_1mfbj3f_interaction_target{"Unresolved: var ebook = await context.Ebooks.FirstOrDefaultAsync(e => e.Id == request.EbookId, cancellationToken);"}
    unresolved_unresolved_1n2eft4_interaction_target{"Unresolved: await _broadcaster.BroadcastProgressAsync(request.UserId, request.PageId, now, request.ExcludedConnectionId, cancellationToken);"}
    unresolved_unresolved_1rw33m5_call_target{"Unresolved: AppDbContext.SaveChangesAsync"}
    unresolved_unresolved_35czhy_interaction_target{"Unresolved: var user = await context.Users.FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);"}
    unresolved_unresolved_eld0cu_call_target{"Unresolved: AppDbContext.Ebooks.FirstOrDefaultAsync"}
    unresolved_unresolved_jg0l72_call_target{"Unresolved: ISyncBroadcaster.BroadcastProgressAsync"}
    unresolved_unresolved_k7dskm_interaction_target{"Unresolved: await context.SaveChangesAsync(cancellationToken);"}
    class_2zm0w9_updatereadingprogresscommandhandler -->|contains| method_1ed5bhs_updatereadingprogresscommandhandler
    class_2zm0w9_updatereadingprogresscommandhandler -->|contains| method_1jqlijy_handle
    class_2zm0w9_updatereadingprogresscommandhandler -->|unresolved interaction| unresolved_unresolved_1mfbj3f_interaction_target
    class_2zm0w9_updatereadingprogresscommandhandler -->|unresolved interaction| unresolved_unresolved_1n2eft4_interaction_target
    class_2zm0w9_updatereadingprogresscommandhandler -->|unresolved interaction| unresolved_unresolved_35czhy_interaction_target
    class_2zm0w9_updatereadingprogresscommandhandler -->|unresolved interaction| unresolved_unresolved_k7dskm_interaction_target
    method_1jqlijy_handle -->|unresolved call| unresolved_unresolved_167ab0v_call_target
    method_1jqlijy_handle -->|unresolved call| unresolved_unresolved_197jdm1_call_target
    method_1jqlijy_handle -->|unresolved call| unresolved_unresolved_1rw33m5_call_target
    method_1jqlijy_handle -->|unresolved call| unresolved_unresolved_eld0cu_call_target
    method_1jqlijy_handle -->|unresolved call| unresolved_unresolved_jg0l72_call_target
```

### Handle() Control Flow

_Warnings: diagram method_1jqlijy_handle__fragment_diagram exceeds preferred complexity thresholds (37 nodes, 40 edges)._

```mermaid
---
config:
  flowchart:
    defaultRenderer: "elk"
---
flowchart TD
    method_1jqlijy_handle__call_28_IDbContextFactory_AppDbContext_CreateDbContextAsync[["IDbContextFactory<AppDbContext>.CreateDbContextAsync"]]
    method_1jqlijy_handle__call_30_AppDbContext_Users_FirstOrDefaultAsync[["AppDbContext.Users.FirstOrDefaultAsync"]]
    method_1jqlijy_handle__call_40_AppDbContext_Ebooks_FirstOrDefaultAsync[["AppDbContext.Ebooks.FirstOrDefaultAsync"]]
    method_1jqlijy_handle__call_49_AppDbContext_SaveChangesAsync[["AppDbContext.SaveChangesAsync"]]
    method_1jqlijy_handle__call_52_ISyncBroadcaster_BroadcastProgressAsync[["ISyncBroadcaster.BroadcastProgressAsync"]]
    method_1jqlijy_handle__action_28_handle_await_28_0__await[["Waits for async work"]]
    method_1jqlijy_handle__action_28_resource_acquire_28__linear("Create DbContext scope")
    method_1jqlijy_handle__action_30_handle_await_30_2__await[["Waits for async work"]]
    method_1jqlijy_handle__action_31_guard_clause_31__condition{"user == null"}
    method_1jqlijy_handle__action_31_guard_clause_31__false("continue")
    method_1jqlijy_handle__action_31_guard_clause_31__merge("merge")
    method_1jqlijy_handle__action_31_guard_clause_31__true("return Fail")
    method_1jqlijy_handle__action_31_handle_branch_31_3__condition{"user == null"}
    method_1jqlijy_handle__action_31_handle_branch_31_3__false("false")
    method_1jqlijy_handle__action_31_handle_branch_31_3__merge("merge")
    method_1jqlijy_handle__action_31_handle_branch_31_3__true("true")
    method_1jqlijy_handle__action_37_field_update_37__linear("Update user reading pointers/timestamps")
    method_1jqlijy_handle__action_40_handle_await_40_6__await[["Waits for async work"]]
    method_1jqlijy_handle__action_41_branch_41__condition{"ebook != null"}
    method_1jqlijy_handle__action_41_branch_41__false("skip")
    method_1jqlijy_handle__action_41_branch_41__merge("merge")
    method_1jqlijy_handle__action_41_branch_41__true("update ebook fields")
    method_1jqlijy_handle__action_41_handle_branch_41_7__condition{"ebook != null"}
    method_1jqlijy_handle__action_41_handle_branch_41_7__false("false")
    method_1jqlijy_handle__action_41_handle_branch_41_7__merge("merge")
    method_1jqlijy_handle__action_41_handle_branch_41_7__true("true")
    method_1jqlijy_handle__action_49_handle_await_49_9__await[["Waits for async work"]]
    method_1jqlijy_handle__action_49_persist_49__linear("Persist entity changes")
    method_1jqlijy_handle__action_52_handle_await_52_10__await[["Waits for async work"]]
    method_1jqlijy_handle__entry(["Handle entry"])
    method_1jqlijy_handle__action_30_handle_repository_read_30_1__linear[("Reads repository or persistence state")]
    method_1jqlijy_handle__action_40_handle_repository_read_40_5__linear[("Reads repository or persistence state")]
    method_1jqlijy_handle__action_49_handle_repository_read_49_8__linear[("Reads repository or persistence state")]
    method_1jqlijy_handle__action_52_external_call_52__linear("Broadcast progress to other devices")
    method_1jqlijy_handle__action_33_handle_return_33_4__return(["Returns result"])
    method_1jqlijy_handle__action_59_handle_return_59_11__return(["Returns result"])
    method_1jqlijy_handle__action_59_return_59__return(["Return success result"])
    method_1jqlijy_handle__action_28_handle_await_28_0__await -->|next| method_1jqlijy_handle__action_28_resource_acquire_28__linear
    method_1jqlijy_handle__action_28_resource_acquire_28__linear -->|cancellationToken| method_1jqlijy_handle__call_28_IDbContextFactory_AppDbContext_CreateDbContextAsync
    method_1jqlijy_handle__action_30_handle_await_30_2__await -->|next| method_1jqlijy_handle__action_30_handle_repository_read_30_1__linear
    method_1jqlijy_handle__action_30_handle_repository_read_30_1__linear -->|predicate u => u.Id == request.UserId, cancellationToken| method_1jqlijy_handle__call_30_AppDbContext_Users_FirstOrDefaultAsync
    method_1jqlijy_handle__action_31_guard_clause_31__condition -->|continue| method_1jqlijy_handle__action_31_guard_clause_31__false
    method_1jqlijy_handle__action_31_guard_clause_31__condition -->|return Fail| method_1jqlijy_handle__action_31_guard_clause_31__true
    method_1jqlijy_handle__action_31_guard_clause_31__false -->|next| method_1jqlijy_handle__action_31_guard_clause_31__merge
    method_1jqlijy_handle__action_31_guard_clause_31__merge -->|next| method_1jqlijy_handle__action_31_handle_branch_31_3__condition
    method_1jqlijy_handle__action_31_guard_clause_31__true -->|next| method_1jqlijy_handle__action_31_guard_clause_31__merge
    method_1jqlijy_handle__action_31_handle_branch_31_3__condition -->|false| method_1jqlijy_handle__action_31_handle_branch_31_3__false
    method_1jqlijy_handle__action_31_handle_branch_31_3__condition -->|true| method_1jqlijy_handle__action_31_handle_branch_31_3__true
    method_1jqlijy_handle__action_31_handle_branch_31_3__false -->|next| method_1jqlijy_handle__action_31_handle_branch_31_3__merge
    method_1jqlijy_handle__action_31_handle_branch_31_3__merge -->|return| method_1jqlijy_handle__action_33_handle_return_33_4__return
    method_1jqlijy_handle__action_31_handle_branch_31_3__true -->|next| method_1jqlijy_handle__action_31_handle_branch_31_3__merge
    method_1jqlijy_handle__action_33_handle_return_33_4__return -->|next| method_1jqlijy_handle__action_37_field_update_37__linear
    method_1jqlijy_handle__action_37_field_update_37__linear -->|next| method_1jqlijy_handle__action_40_handle_await_40_6__await
    method_1jqlijy_handle__action_40_handle_await_40_6__await -->|next| method_1jqlijy_handle__action_40_handle_repository_read_40_5__linear
    method_1jqlijy_handle__action_40_handle_repository_read_40_5__linear -->|predicate e => e.Id == request.EbookId, cancellationToken| method_1jqlijy_handle__call_40_AppDbContext_Ebooks_FirstOrDefaultAsync
    method_1jqlijy_handle__action_41_branch_41__condition -->|skip| method_1jqlijy_handle__action_41_branch_41__false
    method_1jqlijy_handle__action_41_branch_41__condition -->|update ebook fields| method_1jqlijy_handle__action_41_branch_41__true
    method_1jqlijy_handle__action_41_branch_41__false -->|next| method_1jqlijy_handle__action_41_branch_41__merge
    method_1jqlijy_handle__action_41_branch_41__merge -->|next| method_1jqlijy_handle__action_41_handle_branch_41_7__condition
    method_1jqlijy_handle__action_41_branch_41__true -->|next| method_1jqlijy_handle__action_41_branch_41__merge
    method_1jqlijy_handle__action_41_handle_branch_41_7__condition -->|false| method_1jqlijy_handle__action_41_handle_branch_41_7__false
    method_1jqlijy_handle__action_41_handle_branch_41_7__condition -->|true| method_1jqlijy_handle__action_41_handle_branch_41_7__true
    method_1jqlijy_handle__action_41_handle_branch_41_7__false -->|next| method_1jqlijy_handle__action_41_handle_branch_41_7__merge
    method_1jqlijy_handle__action_41_handle_branch_41_7__merge -->|next| method_1jqlijy_handle__action_49_handle_await_49_9__await
    method_1jqlijy_handle__action_41_handle_branch_41_7__true -->|next| method_1jqlijy_handle__action_41_handle_branch_41_7__merge
    method_1jqlijy_handle__action_49_handle_await_49_9__await -->|next| method_1jqlijy_handle__action_49_handle_repository_read_49_8__linear
    method_1jqlijy_handle__action_49_handle_repository_read_49_8__linear -->|next| method_1jqlijy_handle__action_49_persist_49__linear
    method_1jqlijy_handle__action_49_persist_49__linear -->|cancellationToken| method_1jqlijy_handle__call_49_AppDbContext_SaveChangesAsync
    method_1jqlijy_handle__action_52_external_call_52__linear -->|request.UserId, request.PageId, now DateTime.UtcNow , request.ExcludedConnectionId, cancellationToken| method_1jqlijy_handle__call_52_ISyncBroadcaster_BroadcastProgressAsync
    method_1jqlijy_handle__action_52_handle_await_52_10__await -->|next| method_1jqlijy_handle__action_52_external_call_52__linear
    method_1jqlijy_handle__action_59_handle_return_59_11__return -->|return| method_1jqlijy_handle__action_59_return_59__return
    method_1jqlijy_handle__call_28_IDbContextFactory_AppDbContext_CreateDbContextAsync -->|next| method_1jqlijy_handle__action_30_handle_await_30_2__await
    method_1jqlijy_handle__call_30_AppDbContext_Users_FirstOrDefaultAsync -->|next| method_1jqlijy_handle__action_31_guard_clause_31__condition
    method_1jqlijy_handle__call_40_AppDbContext_Ebooks_FirstOrDefaultAsync -->|next| method_1jqlijy_handle__action_41_branch_41__condition
    method_1jqlijy_handle__call_49_AppDbContext_SaveChangesAsync -->|next| method_1jqlijy_handle__action_52_handle_await_52_10__await
    method_1jqlijy_handle__call_52_ISyncBroadcaster_BroadcastProgressAsync -->|return| method_1jqlijy_handle__action_59_handle_return_59_11__return
    method_1jqlijy_handle__entry -->|next| method_1jqlijy_handle__action_28_handle_await_28_0__await
```

## Diagram Validation

- Status: warn
- Warning: Control-flow extraction left 5 unresolved jumps.
- Warning: control-flow: diagram method_1jqlijy_handle__fragment_diagram exceeds preferred complexity thresholds (37 nodes, 40 edges).
