658 lines
18 KiB
Markdown
658 lines
18 KiB
Markdown
# StoryCove - Software Requirements Specification
|
|
|
|
## 1. Executive Summary
|
|
|
|
StoryCove is a self-hosted web application designed to store, organize, and read short stories collected from various internet sources. The application provides a clean, responsive interface for managing a personal library of stories with advanced search capabilities, author management, and rating systems.
|
|
|
|
### 1.1 Key Features (MVP)
|
|
- Story management with HTML content support
|
|
- Author profiles with ratings and metadata
|
|
- Tag-based categorization
|
|
- Full-text search capabilities
|
|
- Responsive reading interface
|
|
- JWT-based authentication
|
|
- Docker-based deployment
|
|
|
|
### 1.2 Technology Stack
|
|
- **Frontend**: Next.js
|
|
- **Backend**: Spring Boot (Java)
|
|
- **Database**: PostgreSQL
|
|
- **Search Engine**: Typesense
|
|
- **Reverse Proxy**: Nginx
|
|
- **Containerization**: Docker & Docker Compose
|
|
|
|
## 2. System Architecture
|
|
|
|
### 2.1 Container Architecture
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Nginx (Port 80/443) │
|
|
│ Reverse Proxy │
|
|
└─────────────┬───────────────────────────┬───────────────────┘
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────────────────┐ ┌─────────────────────────────────┐
|
|
│ Next.js Frontend │ │ Spring Boot Backend │
|
|
│ (Port 3000) │ │ (Port 8080) │
|
|
└─────────────────────────┘ └──────────┬──────────┬──────────┘
|
|
│ │
|
|
▼ ▼
|
|
┌────────────────┐ ┌─────────────────┐
|
|
│ PostgreSQL │ │ Typesense │
|
|
│ (Port 5432) │ │ (Port 8108) │
|
|
└────────────────┘ └─────────────────┘
|
|
```
|
|
|
|
### 2.2 Data Flow
|
|
1. User accesses application through Nginx
|
|
2. Nginx routes requests to appropriate service
|
|
3. Frontend communicates with Backend API
|
|
4. Backend manages data in PostgreSQL and syncs search index with Typesense
|
|
5. Authentication handled via JWT tokens
|
|
|
|
## 3. Data Models
|
|
|
|
### 3.1 Database Schema
|
|
|
|
#### Story Table
|
|
```sql
|
|
CREATE TABLE stories (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
title VARCHAR(500) NOT NULL,
|
|
summary TEXT,
|
|
author_id UUID NOT NULL,
|
|
content_html TEXT NOT NULL,
|
|
content_plain TEXT NOT NULL,
|
|
source_url VARCHAR(1000),
|
|
word_count INTEGER NOT NULL,
|
|
series_id UUID,
|
|
volume INTEGER,
|
|
rating INTEGER CHECK (rating >= 1 AND rating <= 5),
|
|
cover_image_path VARCHAR(500), -- Phase 2: Consider storing base filename without size suffix
|
|
reading_position INTEGER DEFAULT 0, -- Character position for reading progress
|
|
last_read_at TIMESTAMP, -- Last time story was accessed
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (author_id) REFERENCES authors(id),
|
|
FOREIGN KEY (series_id) REFERENCES series(id)
|
|
);
|
|
```
|
|
|
|
#### Author Table
|
|
```sql
|
|
CREATE TABLE authors (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(255) NOT NULL UNIQUE,
|
|
notes TEXT,
|
|
author_rating INTEGER CHECK (author_rating >= 1 AND author_rating <= 5),
|
|
avatar_image_path VARCHAR(500), -- Phase 2: Consider storing base filename without size suffix
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
```
|
|
|
|
#### Author URLs Table
|
|
```sql
|
|
CREATE TABLE author_urls (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
author_id UUID NOT NULL,
|
|
url VARCHAR(1000) NOT NULL,
|
|
description VARCHAR(255),
|
|
FOREIGN KEY (author_id) REFERENCES authors(id) ON DELETE CASCADE
|
|
);
|
|
```
|
|
|
|
#### Series Table
|
|
```sql
|
|
CREATE TABLE series (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(500) NOT NULL UNIQUE,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
```
|
|
|
|
#### Tags Table
|
|
```sql
|
|
CREATE TABLE tags (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(100) NOT NULL UNIQUE,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
```
|
|
|
|
#### Story Tags Junction Table
|
|
```sql
|
|
CREATE TABLE story_tags (
|
|
story_id UUID NOT NULL,
|
|
tag_id UUID NOT NULL,
|
|
PRIMARY KEY (story_id, tag_id),
|
|
FOREIGN KEY (story_id) REFERENCES stories(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
|
);
|
|
```
|
|
|
|
### 3.2 Typesense Schema
|
|
|
|
```json
|
|
{
|
|
"name": "stories",
|
|
"fields": [
|
|
{"name": "id", "type": "string"},
|
|
{"name": "title", "type": "string"},
|
|
{"name": "summary", "type": "string", "optional": true},
|
|
{"name": "author_name", "type": "string"},
|
|
{"name": "content", "type": "string"},
|
|
{"name": "tagNames", "type": "string[]", "facet": true},
|
|
{"name": "series_name", "type": "string", "optional": true},
|
|
{"name": "word_count", "type": "int32"},
|
|
{"name": "rating", "type": "int32", "optional": true},
|
|
{"name": "created_at", "type": "int64"}
|
|
],
|
|
"default_sorting_field": "created_at"
|
|
}
|
|
```
|
|
|
|
## 4. API Specification
|
|
|
|
### 4.1 Authentication Endpoints
|
|
|
|
#### POST /api/auth/login
|
|
```json
|
|
Request:
|
|
{
|
|
"password": "string"
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"token": "jwt_token_string",
|
|
"expiresIn": 86400
|
|
}
|
|
```
|
|
|
|
### 4.2 Story Endpoints
|
|
|
|
#### GET /api/stories
|
|
Query parameters:
|
|
- `page` (integer): Page number
|
|
- `limit` (integer): Items per page
|
|
- `search` (string): Search query
|
|
- `tags` (string[]): Filter by tags
|
|
- `authorId` (uuid): Filter by author
|
|
- `seriesId` (uuid): Filter by series
|
|
- `facetBy` (string[]): Enable faceting for specified fields (e.g., ['tagNames'])
|
|
|
|
#### POST /api/stories
|
|
```json
|
|
Request (multipart/form-data):
|
|
{
|
|
"title": "string",
|
|
"authorName": "string",
|
|
"contentHtml": "string",
|
|
"sourceUrl": "string",
|
|
"tags": ["string"],
|
|
"seriesName": "string",
|
|
"volume": "integer",
|
|
"coverImage": "file (optional)"
|
|
}
|
|
```
|
|
|
|
#### POST /api/stories/{id}/cover
|
|
Upload/update cover image
|
|
```
|
|
Request: multipart/form-data
|
|
- coverImage: file
|
|
|
|
Response:
|
|
{
|
|
"imagePath": "string"
|
|
}
|
|
```
|
|
|
|
#### GET /api/stories/{id}
|
|
Returns full story details including HTML content
|
|
|
|
#### PUT /api/stories/{id}
|
|
Update story (same structure as POST)
|
|
|
|
#### DELETE /api/stories/{id}
|
|
Delete a story
|
|
|
|
#### PUT /api/stories/{id}/rating
|
|
```json
|
|
Request:
|
|
{
|
|
"rating": 1-5
|
|
}
|
|
```
|
|
|
|
#### POST /api/stories/{id}/reading-progress
|
|
```json
|
|
Request:
|
|
{
|
|
"position": 1250
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"id": "uuid",
|
|
"readingPosition": 1250,
|
|
"lastReadAt": "2024-01-01T12:00:00Z"
|
|
}
|
|
```
|
|
|
|
### 4.3 Author Endpoints
|
|
|
|
#### GET /api/authors
|
|
List all authors with story count and average rating
|
|
|
|
#### GET /api/authors/{id}
|
|
Get author details with all stories
|
|
|
|
#### PUT /api/authors/{id}
|
|
```json
|
|
Request (multipart/form-data):
|
|
{
|
|
"notes": "string",
|
|
"authorRating": 1-5,
|
|
"urls": [
|
|
{
|
|
"url": "string",
|
|
"description": "string"
|
|
}
|
|
],
|
|
"avatarImage": "file (optional)"
|
|
}
|
|
```
|
|
|
|
#### POST /api/authors/{id}/avatar
|
|
Upload/update author avatar
|
|
```
|
|
Request: multipart/form-data
|
|
- avatarImage: file
|
|
|
|
Response:
|
|
{
|
|
"imagePath": "string"
|
|
}
|
|
```
|
|
|
|
### 4.4 Image Endpoints
|
|
|
|
#### GET /api/images/{type}/{filename}
|
|
Serve images (covers or avatars)
|
|
- type: "covers" or "avatars"
|
|
- filename: stored filename
|
|
|
|
**Phase 2 Enhancement**: Support for size variants
|
|
- `GET /api/images/{type}/{filename}?size=thumb|medium|full`
|
|
- Default to 'full' if no size specified
|
|
- Return appropriate resized version
|
|
|
|
#### DELETE /api/stories/{id}/cover
|
|
Remove cover image from story
|
|
|
|
#### DELETE /api/authors/{id}/avatar
|
|
Remove avatar from author
|
|
|
|
### 4.4 Tag Endpoints
|
|
|
|
#### GET /api/tags
|
|
Returns tag cloud data with usage counts
|
|
|
|
#### GET /api/tags/autocomplete?q={query}
|
|
Autocomplete for tag input
|
|
|
|
### 4.5 Series Endpoints
|
|
|
|
#### GET /api/series
|
|
List all series with story counts
|
|
|
|
#### GET /api/series/{id}/stories
|
|
Get all stories in a series ordered by volume
|
|
|
|
## 5. UI/UX Specifications
|
|
|
|
### 5.1 Main Views
|
|
|
|
#### Story List View
|
|
- Grid/List toggle
|
|
- Search bar with real-time results
|
|
- Dynamic tag filtering with live story counts (faceted search)
|
|
- Sort options: Date added, Title, Author, Rating
|
|
- Pagination
|
|
- Cover image thumbnails in grid view
|
|
- Quick actions: Edit, Delete, Read
|
|
|
|
#### Story Create/Edit Form
|
|
- Title input (required)
|
|
- Author input with autocomplete
|
|
- Cover image upload with preview
|
|
- Rich text editor for content (with HTML source view)
|
|
- Tag input with autocomplete and chip display
|
|
- Series dropdown with "Add new" option
|
|
- Volume number input (shown if series selected)
|
|
- Source URL input
|
|
- Save/Cancel buttons
|
|
|
|
#### Reading View
|
|
- Clean, distraction-free interface
|
|
- Cover image display at the top (if available)
|
|
- Responsive typography
|
|
- Real-time reading progress indicator with visual progress bar
|
|
- Character-based position tracking with automatic saving
|
|
- Automatic scroll-to-position restoration on story reopen
|
|
- Navigation: Previous/Next in series
|
|
- Quick access to rate story
|
|
- Back to library button
|
|
|
|
#### Author View
|
|
- Author avatar display
|
|
- Author name and rating display
|
|
- Editable notes section
|
|
- URL links management
|
|
- Story list with cover thumbnails and individual ratings
|
|
- Average story rating calculation
|
|
- Edit author details button with avatar upload
|
|
|
|
### 5.2 Global Settings
|
|
- Font family selection
|
|
- Font size adjustment
|
|
- Theme selection (Light/Dark)
|
|
- Reading width preference
|
|
|
|
### 5.3 Color Specifications
|
|
|
|
#### Light Mode
|
|
- **Background**: Off-White (#FAFAF8)
|
|
- **Primary Text**: Charcoal (#2C3E50)
|
|
- **Headers**: Deep Navy (#0A1628)
|
|
- **Accents**: Teal Blue (#2A4D5C)
|
|
|
|
#### Dark Mode
|
|
- **Background**: Deep Navy (#0A1628)
|
|
- **Primary Text**: Warm Cream (#F5E6D3)
|
|
- **Headers**: Warm Cream (#F5E6D3)
|
|
- **Accents**: Golden Amber (#D4A574)
|
|
|
|
## 6. Technical Implementation Details
|
|
|
|
### 6.1 Frontend (Next.js)
|
|
|
|
#### Key Libraries
|
|
- **UI Framework**: Tailwind CSS or Material-UI
|
|
- **State Management**: React Context or Zustand
|
|
- **HTTP Client**: Axios or Fetch API
|
|
- **HTML Sanitization**: DOMPurify
|
|
- **Rich Text Editor**: TinyMCE or Quill
|
|
- **Image Handling**: react-dropzone for uploads
|
|
|
|
#### Authentication Flow
|
|
```typescript
|
|
// JWT stored in httpOnly cookie
|
|
// Auth context provides user state
|
|
// Protected routes using middleware
|
|
```
|
|
|
|
#### Image Upload Handling
|
|
```typescript
|
|
// Use multipart/form-data for story and author forms
|
|
// Image preview before upload
|
|
// Supported formats: JPG, PNG, WebP
|
|
// Max file size: 5MB
|
|
// Automatic image optimization on backend
|
|
```
|
|
|
|
#### Theme Implementation
|
|
```typescript
|
|
// CSS Variables approach for theme switching
|
|
// Light Mode:
|
|
--color-background: #FAFAF8;
|
|
--color-text-primary: #2C3E50;
|
|
--color-text-header: #0A1628;
|
|
--color-accent: #2A4D5C;
|
|
|
|
// Dark Mode:
|
|
--color-background: #0A1628;
|
|
--color-text-primary: #F5E6D3;
|
|
--color-text-header: #F5E6D3;
|
|
--color-accent: #D4A574;
|
|
|
|
// Theme preference stored in localStorage
|
|
// Respects system preference on first visit
|
|
```
|
|
|
|
### 6.2 Backend (Spring Boot)
|
|
|
|
#### Key Dependencies
|
|
```xml
|
|
<dependencies>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-web</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-starter-security</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>io.jsonwebtoken</groupId>
|
|
<artifactId>jjwt</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.jsoup</groupId>
|
|
<artifactId>jsoup</artifactId>
|
|
</dependency>
|
|
<dependency>
|
|
<groupId>org.postgresql</groupId>
|
|
<artifactId>postgresql</artifactId>
|
|
</dependency>
|
|
</dependencies>
|
|
```
|
|
|
|
#### HTML Processing
|
|
```java
|
|
// Use Jsoup for HTML sanitization
|
|
// Whitelist allowed tags: p, br, strong, em, ul, ol, li, h1-h6, blockquote
|
|
// Strip all JavaScript and unsafe attributes
|
|
// Generate plain text version for search indexing
|
|
```
|
|
|
|
#### Image Processing
|
|
```java
|
|
// Image upload handling
|
|
// Supported formats: JPG, PNG, WebP
|
|
// Max size: 5MB
|
|
// Automatic resizing: covers to 800x1200 max, avatars to 400x400
|
|
// Store in filesystem: /app/images/covers/ and /app/images/avatars/
|
|
// Generate unique filenames using UUID
|
|
// Current: Single size per image type
|
|
|
|
// PHASE 2 ENHANCEMENT: Multi-size generation during upload
|
|
// Generate multiple sizes for optimal performance:
|
|
// - Cover images: thumbnail (200x300), medium (400x600), full (800x1200)
|
|
// - Avatar images: small (64x64), medium (200x200), full (400x400)
|
|
// Store with naming convention: {uuid}_thumb.jpg, {uuid}_medium.jpg, {uuid}_full.jpg
|
|
// Frontend selects appropriate size based on usage context
|
|
// Significant bandwidth and loading time improvements
|
|
```
|
|
|
|
### 6.3 Search Integration
|
|
|
|
#### Typesense Sync Strategy
|
|
1. On story create/update: Index immediately
|
|
2. On story delete: Remove from index
|
|
3. Batch reindex endpoint for maintenance
|
|
4. Search includes: title, author, content, tags
|
|
5. Faceted search support for dynamic filtering
|
|
6. Tag facets provide real-time counts for library filtering
|
|
|
|
### 6.4 Security Considerations
|
|
|
|
1. **Authentication**: JWT with secure httpOnly cookies
|
|
2. **Input Validation**: All inputs validated and sanitized
|
|
3. **HTML Sanitization**: Strict whitelist of allowed tags
|
|
4. **SQL Injection**: Use parameterized queries
|
|
5. **XSS Prevention**: Content Security Policy headers
|
|
6. **CORS**: Configured for frontend origin only
|
|
|
|
## 7. Deployment Configuration
|
|
|
|
### 7.1 Docker Compose Configuration
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
nginx:
|
|
image: nginx:alpine
|
|
ports:
|
|
- "80:80"
|
|
volumes:
|
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
|
- images_data:/app/images:ro
|
|
depends_on:
|
|
- frontend
|
|
- backend
|
|
|
|
frontend:
|
|
build: ./frontend
|
|
environment:
|
|
- NEXT_PUBLIC_API_URL=http://backend:8080
|
|
depends_on:
|
|
- backend
|
|
|
|
backend:
|
|
build: ./backend
|
|
environment:
|
|
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/storycove
|
|
- SPRING_DATASOURCE_USERNAME=storycove
|
|
- SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD}
|
|
- JWT_SECRET=${JWT_SECRET}
|
|
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
|
|
- TYPESENSE_HOST=typesense
|
|
- TYPESENSE_PORT=8108
|
|
- IMAGE_STORAGE_PATH=/app/images
|
|
volumes:
|
|
- images_data:/app/images
|
|
depends_on:
|
|
- postgres
|
|
- typesense
|
|
|
|
postgres:
|
|
image: postgres:15-alpine
|
|
environment:
|
|
- POSTGRES_DB=storycove
|
|
- POSTGRES_USER=storycove
|
|
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
|
|
typesense:
|
|
image: typesense/typesense:0.25.0
|
|
environment:
|
|
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
|
|
- TYPESENSE_DATA_DIR=/data
|
|
volumes:
|
|
- typesense_data:/data
|
|
|
|
volumes:
|
|
postgres_data:
|
|
typesense_data:
|
|
images_data:
|
|
```
|
|
|
|
### 7.2 Environment Variables
|
|
```env
|
|
DB_PASSWORD=secure_password_here
|
|
JWT_SECRET=secure_jwt_secret_here
|
|
TYPESENSE_API_KEY=secure_api_key_here
|
|
APP_PASSWORD=application_password_here
|
|
```
|
|
|
|
## 8. Testing Strategy
|
|
|
|
### 8.1 Unit Tests
|
|
- Backend: JUnit 5 for service and controller tests
|
|
- Frontend: Jest and React Testing Library
|
|
|
|
### 8.2 Integration Tests
|
|
- API endpoint testing with MockMvc
|
|
- Database integration tests with Testcontainers
|
|
|
|
### 8.3 E2E Tests
|
|
- Cypress or Playwright for critical user flows
|
|
|
|
## 9. Phase 2 Roadmap
|
|
|
|
### 9.1 URL Content Grabbing ✅ IMPLEMENTED
|
|
- Configurable scrapers for specific sites
|
|
- Site configuration stored in JSON files
|
|
- Content extraction rules per site (DeviantArt support added)
|
|
- Adaptive content extraction for varying HTML structures
|
|
|
|
### 9.2 Enhanced Image Processing & Optimization
|
|
- **Multi-size generation during upload**
|
|
- Cover images: thumbnail (200x300), medium (400x600), full (800x1200)
|
|
- Avatar images: small (64x64), medium (200x200), full (400x400)
|
|
- Automatic format optimization (WebP when supported)
|
|
- Progressive JPEG for faster loading
|
|
- **Smart image serving**
|
|
- Context-aware size selection in frontend
|
|
- Responsive images with srcset support
|
|
- Lazy loading implementation
|
|
- **Storage optimization**
|
|
- Image compression with quality settings
|
|
- Optional cloud storage integration (S3-compatible)
|
|
- Automatic cleanup of unused images
|
|
- **Advanced features**
|
|
- Image metadata extraction (dimensions, EXIF)
|
|
- Batch image processing tools
|
|
- Image quality assessment and warnings
|
|
- Inline image display in stories (future)
|
|
|
|
### 9.3 Story Collections
|
|
- Collection management interface
|
|
- Ordered story lists
|
|
- Collection sharing (future)
|
|
|
|
### 9.4 Export Functionality
|
|
- PDF generation with formatting
|
|
- EPUB export with metadata
|
|
- Batch export for collections
|
|
|
|
## 10. Development Milestones
|
|
|
|
### Milestone 1: Infrastructure Setup (Week 1)
|
|
- Docker environment configuration
|
|
- Database schema implementation
|
|
- Basic Spring Boot setup with security
|
|
|
|
### Milestone 2: Core Backend (Week 2-3)
|
|
- Story CRUD operations
|
|
- Author management
|
|
- Tag system
|
|
- Typesense integration
|
|
|
|
### Milestone 3: Frontend Foundation (Week 4-5)
|
|
- Authentication flow
|
|
- Story list and create forms
|
|
- Author views
|
|
- Search interface
|
|
|
|
### Milestone 4: Reading Experience (Week 6)
|
|
- Reading view implementation with progress tracking
|
|
- Character-based reading position persistence
|
|
- Automatic position restoration
|
|
- Settings management
|
|
- Rating system
|
|
|
|
### Milestone 5: Polish & Testing (Week 7-8)
|
|
- UI refinements
|
|
- Comprehensive testing
|
|
- Documentation
|
|
- Deployment scripts |