15 KiB
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
- User accesses application through Nginx
- Nginx routes requests to appropriate service
- Frontend communicates with Backend API
- Backend manages data in PostgreSQL and syncs search index with Typesense
- Authentication handled via JWT tokens
3. Data Models
3.1 Database Schema
Story Table
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
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
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
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
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
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
{
"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
Request:
{
"password": "string"
}
Response:
{
"token": "jwt_token_string",
"expiresIn": 86400
}
4.2 Story Endpoints
GET /api/stories
Query parameters:
page(integer): Page numberlimit(integer): Items per pagesearch(string): Search querytags(string[]): Filter by tagsauthorId(uuid): Filter by authorseriesId(uuid): Filter by series
POST /api/stories
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
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}
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
// JWT stored in httpOnly cookie
// Auth context provides user state
// Protected routes using middleware
Image Upload Handling
// 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
<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
// 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
// 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
- On story create/update: Index immediately
- On story delete: Remove from index
- Batch reindex endpoint for maintenance
- Search includes: title, author, content, tags
6.4 Security Considerations
- Authentication: JWT with secure httpOnly cookies
- Input Validation: All inputs validated and sanitized
- HTML Sanitization: Strict whitelist of allowed tags
- SQL Injection: Use parameterized queries
- XSS Prevention: Content Security Policy headers
- CORS: Configured for frontend origin only
7. Deployment Configuration
7.1 Docker Compose Configuration
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
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