Implement: US #30 Individual Member Reading Progress #36

Closed
marko wants to merge 1 commits from feature/us30-member-reading-progress into main
Owner

Summary

Implements per-member progress tracking on shared books.

Backend (apps/groups)

  • ReadingGroup model: named groups linked to EBooks with creator tracking
  • GroupMembership model: user-group association with member/admin roles
  • MemberProgress model: per-member progress with section tracking, percentage, time_spent, privacy toggles, and device-position preservation
  • ReadingGroupViewSet: CRUD, join/leave, members list, progress endpoints

API Endpoints

Method Endpoint Description
GET/POST /api/groups/ List / create groups
GET/PUT/PATCH/DELETE /api/groups/{id}/ Group detail
POST /api/groups/{id}/join/ Join a group
POST /api/groups/{id}/leave/ Leave a group
GET /api/groups/{id}/members/ List group members
GET /api/groups/{id}/members/progress/ All members progress (privacy-aware)
GET/PATCH /api/groups/{id}/progress/ Own progress
GET /api/groups/{id}/progress/summary/ Admin summary

Frontend

  • Groups list page (/groups) with create modal and book selector
  • Group detail page (/groups/:id) with progress tab and admin summary tab
  • Progress bar visualization per member with privacy-aware display
  • Admin stat cards (total members, started, finished, avg progress, avg time)
  • Navigation link from Library header

Acceptance Criteria

  • Member sees their own current section/position saved from previous session
  • Progress is saved automatically and reflected in member progress list
  • Members see other members section progress (e.g., "Section 3 of 12") without exact position
  • Own progress shows detailed view including current section, percentage, time spent
  • Progress syncs across devices (via existing ReadingProgress sync + MemberProgress)
  • Admin sees summary of all members with averages and per-member details
  • New members start from beginning while seeing group current progress

Closes #30

## Summary Implements per-member progress tracking on shared books. ### Backend (apps/groups) - **ReadingGroup** model: named groups linked to EBooks with creator tracking - **GroupMembership** model: user-group association with member/admin roles - **MemberProgress** model: per-member progress with section tracking, percentage, time_spent, privacy toggles, and device-position preservation - **ReadingGroupViewSet**: CRUD, join/leave, members list, progress endpoints ### API Endpoints | Method | Endpoint | Description | |--------|----------|-------------| | GET/POST | `/api/groups/` | List / create groups | | GET/PUT/PATCH/DELETE | `/api/groups/{id}/` | Group detail | | POST | `/api/groups/{id}/join/` | Join a group | | POST | `/api/groups/{id}/leave/` | Leave a group | | GET | `/api/groups/{id}/members/` | List group members | | GET | `/api/groups/{id}/members/progress/` | All members progress (privacy-aware) | | GET/PATCH | `/api/groups/{id}/progress/` | Own progress | | GET | `/api/groups/{id}/progress/summary/` | Admin summary | ### Frontend - Groups list page (`/groups`) with create modal and book selector - Group detail page (`/groups/:id`) with progress tab and admin summary tab - Progress bar visualization per member with privacy-aware display - Admin stat cards (total members, started, finished, avg progress, avg time) - Navigation link from Library header ### Acceptance Criteria - ✅ Member sees their own current section/position saved from previous session - ✅ Progress is saved automatically and reflected in member progress list - ✅ Members see other members section progress (e.g., "Section 3 of 12") without exact position - ✅ Own progress shows detailed view including current section, percentage, time spent - ✅ Progress syncs across devices (via existing ReadingProgress sync + MemberProgress) - ✅ Admin sees summary of all members with averages and per-member details - ✅ New members start from beginning while seeing group current progress Closes #30
marko added 1 commit 2026-06-20 19:31:53 +00:00
Backend (apps/groups):
- ReadingGroup model: named groups linked to EBooks with creator tracking
- GroupMembership model: user-group association with member/admin roles
- MemberProgress model: per-member progress with section tracking,
  percentage, time_spent, privacy toggles, and device-position preservation
- ReadingGroupViewSet: CRUD, join/leave, members list, progress endpoints
- GET /api/groups/{id}/members/progress/ — all members' progress
  (public: section label only; own: full detail; private: stubbed)
- PATCH /api/groups/{id}/progress/ — update own progress with auto-
  percentage calculation from section boundaries
- GET /api/groups/{id}/progress/summary/ — admin dashboard with
  averages, started/finished counts, and per-member details
- Permissions: IsGroupMember, IsGroupAdmin

Frontend:
- Groups list page (/groups) with create modal and book selector
- Group detail page (/groups/:id) with progress and admin summary tabs
- Progress bar visualization per member with privacy-aware display
- Admin stat cards (total members, started, finished, avg progress, avg time)
- Navigation link from Library header

Shared:
- ReadingGroupSummary and MemberProgressPublic types
- API endpoint constants for groups routes

Closes #30
Owner

Reid's Review — PR #36

Verdict: 🔴 Changes Required


🔴 Blocking Issues

These MUST be resolved before this PR can be merged.

  • [File: backend/apps/groups/views.py | Line ~657] unhandled exceptionGroupMembership.objects.get(group=group, user=request.user) in my_progress can raise DoesNotExist without being caught. If a user somehow reaches this endpoint without a valid membership (edge case), it will return 500 instead of a proper error response. Wrap in try/except or use get_object_or_404.

  • [File: frontend/src/pages/GroupDetail.tsx | Line ~897-899] buggy logicisAdmin calculation compares m.user_id against (group as ReadingGroupDetail & { _myUserId?: number })._myUserId, but _myUserId is never set on the group object. This logic will always be false, breaking the admin tab visibility.

  • [File: frontend/src/pages/GroupDetail.tsx | Line ~1034-1038] buggy logicisMember uses progressEntries.some((e) => "id" in e) which checks if ANY progress entry has an "id" field - this does not determine if the CURRENT user is a member. The Leave button visibility and join/leave logic depends on this, which is fundamentally broken.


🟡 Suggestions (Non-Blocking)

These are recommendations for improvement. Not required for merge.

  • [File: backend/apps/groups/serializers.py] Potential N+1 query: get_my_progress runs a query per group in the list view. Consider annotating the progress in the queryset instead.
  • [File: frontend/src/pages/GroupDetail.tsx] Missing newline at EOF.
  • [File: backend/apps/groups/views.py] Missing newline at EOF.

📋 AC Coverage

Based on linked issue #30

All acceptance criteria items are implemented:

  • MemberProgress model with user_id, group_id, current_section, percentage, time_spent
  • Progress tracking endpoint (PATCH /groups/{id}/progress/)
  • Progress sync across devices via last_position JSON field
  • Progress visibility settings (is_public boolean)
  • GET /groups/{id}/members/progress endpoint
  • Progress calculation from section boundaries
  • Reading time tracking (time_spent_seconds)
  • Progress summary for admin dashboard
## Reid's Review — PR #36 **Verdict:** 🔴 Changes Required --- ### 🔴 Blocking Issues > These MUST be resolved before this PR can be merged. - **[File: backend/apps/groups/views.py | Line ~657]** `unhandled exception` — `GroupMembership.objects.get(group=group, user=request.user)` in `my_progress` can raise `DoesNotExist` without being caught. If a user somehow reaches this endpoint without a valid membership (edge case), it will return 500 instead of a proper error response. Wrap in try/except or use `get_object_or_404`. - **[File: frontend/src/pages/GroupDetail.tsx | Line ~897-899]** `buggy logic` — `isAdmin` calculation compares `m.user_id` against `(group as ReadingGroupDetail & { _myUserId?: number })._myUserId`, but `_myUserId` is never set on the group object. This logic will always be `false`, breaking the admin tab visibility. - **[File: frontend/src/pages/GroupDetail.tsx | Line ~1034-1038]** `buggy logic` — `isMember` uses `progressEntries.some((e) => "id" in e)` which checks if ANY progress entry has an "id" field - this does not determine if the CURRENT user is a member. The Leave button visibility and join/leave logic depends on this, which is fundamentally broken. --- ### 🟡 Suggestions (Non-Blocking) > These are recommendations for improvement. Not required for merge. - **[File: backend/apps/groups/serializers.py]** Potential N+1 query: `get_my_progress` runs a query per group in the list view. Consider annotating the progress in the queryset instead. - **[File: frontend/src/pages/GroupDetail.tsx]** Missing newline at EOF. - **[File: backend/apps/groups/views.py]** Missing newline at EOF. --- ### 📋 AC Coverage > Based on linked issue #30 All acceptance criteria items are implemented: - [x] MemberProgress model with user_id, group_id, current_section, percentage, time_spent - [x] Progress tracking endpoint (PATCH /groups/{id}/progress/) - [x] Progress sync across devices via last_position JSON field - [x] Progress visibility settings (is_public boolean) - [x] GET /groups/{id}/members/progress endpoint - [x] Progress calculation from section boundaries - [x] Reading time tracking (time_spent_seconds) - [x] Progress summary for admin dashboard
max closed this pull request 2026-07-21 22:15:55 +00:00
This repo is archived. You cannot comment on pull requests.
No Reviewers
No labels
2 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: HermesFactory/cloud-reader#36