Intial Setup
This commit is contained in:
571
storycove-spec.md
Normal file
571
storycove-spec.md
Normal file
@@ -0,0 +1,571 @@
|
||||
# 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,
|
||||
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),
|
||||
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),
|
||||
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": "author_name", "type": "string"},
|
||||
{"name": "content", "type": "string"},
|
||||
{"name": "tags", "type": "string[]"},
|
||||
{"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
|
||||
|
||||
#### 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
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
#### 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
|
||||
- Tag cloud for filtering
|
||||
- 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
|
||||
- Progress indicator
|
||||
- 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
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
### 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
|
||||
// Thumbnail generation for list views
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
### 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
|
||||
- Configurable scrapers for specific sites
|
||||
- Site configuration stored in database
|
||||
- Content extraction rules per site
|
||||
- Image download and storage
|
||||
|
||||
### 9.2 Image Support
|
||||
- Image storage in filesystem or S3-compatible storage
|
||||
- Image optimization pipeline
|
||||
- Inline image display in stories
|
||||
|
||||
### 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
|
||||
- Settings management
|
||||
- Rating system
|
||||
|
||||
### Milestone 5: Polish & Testing (Week 7-8)
|
||||
- UI refinements
|
||||
- Comprehensive testing
|
||||
- Documentation
|
||||
- Deployment scripts
|
||||
Reference in New Issue
Block a user