Archived
feat: full book management system with backend API, frontend UI, and spec docs
- Backend: Book model with reading progress, DRF ViewSet with full CRUD, search, sort, filter, pagination, mark-as-finished, stats endpoint - Frontend: Library grid, BookCard, BookDetail, BookForm components with React 19 + TypeScript + Vite - Tests: 29 passing tests covering models, API, serializers, permissions - Spec: backend api-spec.md and frontend component-spec.md in docs/ Closes crisleo-hermes/cloud-reader#3
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# Backend API Specification — Cloud Reader
|
||||
|
||||
## Overview
|
||||
|
||||
The Cloud Reader backend provides a RESTful API for managing a user's personal book library. Built with Django 5 + Django REST Framework.
|
||||
|
||||
## Models
|
||||
|
||||
### Book
|
||||
|
||||
| Field | Type | Constraints |
|
||||
|------------------|--------------------|---------------------------------|
|
||||
| `title` | `CharField(500)` | Required |
|
||||
| `author` | `CharField(500)` | Required |
|
||||
| `genre` | `CharField(200)` | Optional, blank allowed |
|
||||
| `description` | `TextField` | Optional, blank allowed |
|
||||
| `cover_image_url`| `URLField` | Optional, blank allowed |
|
||||
| `isbn` | `CharField(20)` | Optional, blank allowed |
|
||||
| `total_pages` | `PositiveIntegerField` | Default 0 |
|
||||
| `current_page` | `PositiveIntegerField` | Default 0, validated ≤ total |
|
||||
| `reading_status` | `CharField(20)` | Choices: `not_started`, `reading`, `finished`, `dnf` |
|
||||
| `owner` | `ForeignKey(User)` | Set automatically on create |
|
||||
| `created_at` | `DateTimeField` | Auto-set on create |
|
||||
| `updated_at` | `DateTimeField` | Auto-set on update |
|
||||
|
||||
**Properties:**
|
||||
- `reading_progress` — computed `(current_page / total_pages) * 100`, returns `0.0` when `total_pages` is 0.
|
||||
|
||||
**Indexes:** Composite indexes on `(owner, reading_status)`, `(owner, title)`, `(owner, author)`.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
Base URL: `/api/`
|
||||
|
||||
Authentication: SessionAuthentication + BasicAuthentication (DRF defaults).
|
||||
|
||||
Permissions: All book endpoints require `IsAuthenticated`. Users can only access their own books.
|
||||
|
||||
### Books
|
||||
|
||||
| Method | URL | Action | Serializer |
|
||||
|----------|------------------------------------|---------------|--------------------|
|
||||
| `GET` | `/api/books/` | List books | `BookListSerializer` |
|
||||
| `POST` | `/api/books/` | Create book | `BookDetailSerializer` |
|
||||
| `GET` | `/api/books/{id}/` | Retrieve book | `BookDetailSerializer` |
|
||||
| `PUT` | `/api/books/{id}/` | Full update | `BookDetailSerializer` |
|
||||
| `PATCH` | `/api/books/{id}/` | Partial update| `BookDetailSerializer` |
|
||||
| `DELETE` | `/api/books/{id}/` | Delete book | — |
|
||||
| `POST` | `/api/books/{id}/mark_finished/` | Mark finished | `BookDetailSerializer` |
|
||||
| `GET` | `/api/books/stats/` | Library stats | — (custom) |
|
||||
|
||||
### Query Parameters (List)
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|------------------|----------|--------------------------------------------------|
|
||||
| `page` | int | Page number for pagination (20 items/page) |
|
||||
| `sort_by` | string | `title`, `-title`, `author`, `-author`, `-created_at`, `-updated_at`, `-reading_progress`, `reading_progress` |
|
||||
| `reading_status` | string | Filter by status value |
|
||||
| `search` | string | Search in title and author fields (icontains) |
|
||||
|
||||
### Stats Response
|
||||
|
||||
```json
|
||||
{
|
||||
"total_books": 10,
|
||||
"finished": 3,
|
||||
"reading": 4,
|
||||
"not_started": 3
|
||||
}
|
||||
```
|
||||
|
||||
### Mark Finished
|
||||
|
||||
`POST /api/books/{id}/mark_finished/` sets `reading_status` to `finished` and `current_page` to `total_pages`.
|
||||
|
||||
## Validation Rules
|
||||
|
||||
- Title and author cannot be empty or whitespace-only
|
||||
- `current_page` cannot exceed `total_pages` (when `total_pages > 0`)
|
||||
- Setting `reading_status` to `finished` automatically sets `current_page = total_pages`
|
||||
|
||||
## Admin
|
||||
|
||||
Books are registered in Django admin with list display, filters by `reading_status` and `genre`, and search by `title`/`author`.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Frontend Component Specification — Cloud Reader
|
||||
|
||||
## Overview
|
||||
|
||||
React 19 + TypeScript SPA using Vite for development and production builds. State managed via React hooks and Context API. Routing via `react-router-dom` v7 with `lazy`/`Suspense` for code splitting.
|
||||
|
||||
## Components
|
||||
|
||||
### App (root)
|
||||
|
||||
- **Path:** `/`
|
||||
- **Layout:** Header with logo + `<Routes>` wrapper
|
||||
- **Routing:** `/` → `Library`, `*` → redirect to `/`
|
||||
- **Code splitting:** `Library` loaded via `React.lazy` + `<Suspense>`
|
||||
|
||||
### Library
|
||||
|
||||
- **State:** `books[]`, `stats`, `search`, `sortBy`, `statusFilter`, `currentPage`, `view`
|
||||
- **Views:** `library` (grid), `detail` (selected book), `add` (modal form)
|
||||
- **Sub-components:** `BookCard`, `BookDetail`, `BookForm`
|
||||
- **Data flow:** Calls `fetchBooks()` on mount and when filters/page change
|
||||
|
||||
### BookCard
|
||||
|
||||
- **Props:** `{ book: Book, onClick: (book: Book) => void }`
|
||||
- **Display:** Cover image (or first-letter placeholder), title, author, genre badge, reading progress bar with status color
|
||||
- **Interaction:** Click/keyboard-accessible (Enter/Space)
|
||||
|
||||
### BookDetail
|
||||
|
||||
- **Props:** `{ bookId: number, onBack: () => void, onUpdated: () => void }`
|
||||
- **Sections:** Cover, metadata (title, author, genre, ISBN), progress bar with page count, action buttons
|
||||
- **Actions:** Mark as Finished, Edit Details (switches to BookForm), Delete (with confirmation)
|
||||
- **States:** Loading, error, editing mode
|
||||
|
||||
### BookForm
|
||||
|
||||
- **Props:** `{ initialData?: Book, onSubmit: (data: BookFormData) => Promise<void>, onCancel: () => void }`
|
||||
- **Fields:** Title*, Author*, Genre, Description, Cover URL, ISBN, Total Pages, Current Page, Status
|
||||
- **Client validation:** Title/author required, page ≤ total pages
|
||||
- **Loading state:** Submit button shows "Saving..." when `isLoading`
|
||||
|
||||
## Types
|
||||
|
||||
```typescript
|
||||
interface Book {
|
||||
id: number; title: string; author: string; genre: string;
|
||||
description: string; cover_image_url: string; isbn: string;
|
||||
total_pages: number; current_page: number;
|
||||
reading_status: ReadingStatus; reading_progress: number;
|
||||
owner: string; created_at: string; updated_at: string;
|
||||
}
|
||||
|
||||
type ReadingStatus = 'not_started' | 'reading' | 'finished' | 'dnf';
|
||||
|
||||
interface BookFormData {
|
||||
title: string; author: string; genre: string;
|
||||
description: string; cover_image_url: string; isbn: string;
|
||||
total_pages: number; current_page: number; reading_status: ReadingStatus;
|
||||
}
|
||||
```
|
||||
|
||||
## API Client (`api/books.ts`)
|
||||
|
||||
| Function | HTTP Call |
|
||||
|----------------------|------------------------------------|
|
||||
| `fetchBooks(params)` | `GET /api/books/` |
|
||||
| `fetchBook(id)` | `GET /api/books/{id}/` |
|
||||
| `createBook(data)` | `POST /api/books/` |
|
||||
| `updateBook(id, data)` | `PATCH /api/books/{id}/` |
|
||||
| `deleteBook(id)` | `DELETE /api/books/{id}/` |
|
||||
| `markAsFinished(id)` | `POST /api/books/{id}/mark_finished/` |
|
||||
| `fetchBookStats()` | `GET /api/books/stats/` |
|
||||
|
||||
## Styling
|
||||
|
||||
Dark theme with CSS custom properties. All styles in `App.css`. Responsive grid layout with breakpoint at 768px.
|
||||
|
||||
## Build & Dev
|
||||
|
||||
- `yarn workspace frontend dev` — Vite dev server on port 5173 with API proxy to 127.0.0.1:8000
|
||||
- `yarn workspace frontend build` — TypeScript check + Vite production build
|
||||
Reference in New Issue
Block a user