feat: implement group EPUB upload and section splitting (US #29)

Backend:
- Create hermes Django app with models: ReadingGroup, GroupBook, Section, ReadingSchedule, MemberProgress
- EPUB section splitting service with automatic detection and reading time estimation
- Section recommendation engine for 4-week meeting schedule
- REST API endpoints for groups, books, sections, schedule, and member progress
- Manual section adjustment (merge/split) support

Frontend:
- GroupsPage: list/create reading groups
- GroupDetailPage: manage members, upload EPUB to group, view group books
- GroupBookPage: section breakdown with merge/split controls, reading schedule, member progress
- API client and TypeScript types for all group operations
- i18n keys for English and Spanish

Shared:
- Group-related types and API endpoint constants in packages/shared
This commit is contained in:
Marko
2026-06-20 19:30:11 +00:00
parent 26c5f6f06b
commit 1b3b709ba5
23 changed files with 2492 additions and 0 deletions
+44
View File
@@ -215,4 +215,48 @@ export interface PaginatedResponse<T> {
export interface ApiError {
detail?: string;
[key: string]: unknown;
}
// ---- Reading Groups ----
export interface ReadingGroup {
id: number;
name: string;
description: string;
admin: number;
admin_email: string;
member_count: number;
created_at: string;
updated_at: string;
}
export interface GroupMembership {
id: number;
user: number;
user_email: string;
user_username: string;
role: "admin" | "member";
joined_at: string;
}
export interface GroupBookSummary {
id: number;
title: string;
status: "active" | "replaced";
section_count: number;
created_at: string;
}
export interface SectionSummary {
id: number;
title: string;
order: number;
estimated_reading_minutes: number;
}
export interface ReadingScheduleEntry {
id: number;
meeting_number: number;
week_date: string;
section_ids: number[];
}
+10
View File
@@ -114,4 +114,14 @@ export const API_ENDPOINTS = {
bookmarkDetail: (id: string) => `/api/annotations/bookmarks/${id}/`,
noteDetail: (id: string) => `/api/annotations/notes/${id}/`,
},
groups: {
list: "/api/groups/",
detail: (id: number) => `/api/groups/${id}/`,
books: (groupId: number) => `/api/groups/${groupId}/books/`,
bookDetail: (groupId: number, bookId: number) => `/api/groups/${groupId}/books/${bookId}/`,
detectSections: (groupId: number, bookId: number) => `/api/groups/${groupId}/books/${bookId}/detect-sections/`,
adjustSections: (groupId: number, bookId: number) => `/api/groups/${groupId}/books/${bookId}/adjust-sections/`,
schedule: (groupId: number, bookId: number) => `/api/groups/${groupId}/books/${bookId}/schedule/`,
progress: (groupId: number, bookId: number) => `/api/groups/${groupId}/books/${bookId}/progress/`,
},
} as const;