Files
storycove/storycove-spec.md
Stefan Hardegger a660056003 Various improvements
2025-08-21 13:55:38 +02:00

34 KiB

StoryCove - Software Requirements Specification

Table of Contents

  1. Executive Summary
  2. System Architecture
  3. Data Models
  4. API Specification
  5. UI/UX Specifications
  6. Security Requirements
  7. Deployment
  8. Testing Strategy
  9. Advanced Features ( IMPLEMENTED)
  10. Future Roadmap

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 ( IMPLEMENTED)

  • Story Management: Complete CRUD operations with HTML content support
  • Author Profiles: Full author management with ratings, metadata, and URL collections
  • Enhanced Tag System: Color tags, aliases, merging, and AI-powered suggestions
  • Collections: Organized story lists with reading flow and EPUB export
  • EPUB Import/Export: Full EPUB support with metadata and reading position preservation
  • Advanced Search: Typesense-powered full-text search with faceting
  • Reading Position Tracking: Character-level position tracking with EPUB CFI support
  • Responsive Reading Interface: Distraction-free reading with progress tracking
  • JWT Authentication: Secure single-password authentication system
  • Docker Deployment: Complete containerized deployment with Docker Compose

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

CREATE TABLE stories (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    title VARCHAR(255) NOT NULL,
    summary TEXT,
    description VARCHAR(1000),
    author_id UUID NOT NULL,
    content_html TEXT NOT NULL,
    content_plain TEXT NOT NULL,
    source_url VARCHAR(1000),
    word_count INTEGER DEFAULT 0,
    series_id UUID,
    volume INTEGER,
    rating INTEGER CHECK (rating >= 1 AND rating <= 5),
    cover_path VARCHAR(500), -- Updated field name
    is_read BOOLEAN DEFAULT FALSE, -- Reading status
    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 NOT NULL,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
    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), -- Phase 2: Consider storing base filename without size suffix
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Author URLs Collection Table

-- Note: In the actual implementation, this is managed as an @ElementCollection
-- This table is created automatically by Hibernate
CREATE TABLE author_urls (
    author_id UUID NOT NULL,
    url VARCHAR(1000) NOT NULL,
    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,
    color VARCHAR(7), -- Hex color code like #3B82F6
    description VARCHAR(500), -- Tag description
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);

Collections Table

CREATE TABLE collections (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(500) NOT NULL,
    description TEXT,
    rating INTEGER CHECK (rating >= 1 AND rating <= 5),
    cover_image_path VARCHAR(500),
    is_archived BOOLEAN DEFAULT FALSE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);

CREATE INDEX idx_collections_archived ON collections(is_archived);

Collection Stories Junction Table

CREATE TABLE collection_stories (
    collection_id UUID NOT NULL,
    story_id UUID NOT NULL,
    position INTEGER NOT NULL,
    added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (collection_id, story_id),
    FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE,
    FOREIGN KEY (story_id) REFERENCES stories(id) ON DELETE CASCADE,
    UNIQUE(collection_id, position)
);

CREATE INDEX idx_collection_stories_position ON collection_stories(collection_id, position);

Collection Tags Junction Table

CREATE TABLE collection_tags (
    collection_id UUID NOT NULL,
    tag_id UUID NOT NULL,
    PRIMARY KEY (collection_id, tag_id),
    FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE,
    FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);

Tag Aliases Table

CREATE TABLE tag_aliases (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    alias_name VARCHAR(100) UNIQUE NOT NULL,
    canonical_tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
    created_from_merge BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);

CREATE INDEX idx_tag_aliases_name ON tag_aliases(alias_name);

Reading Positions Table

CREATE TABLE reading_positions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    story_id UUID NOT NULL,
    chapter_index INTEGER,
    chapter_title VARCHAR(255),
    word_position INTEGER,
    character_position INTEGER,
    percentage_complete DECIMAL(5,2),
    epub_cfi TEXT,
    context_before VARCHAR(500),
    context_after VARCHAR(500),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
    FOREIGN KEY (story_id) REFERENCES stories(id) ON DELETE CASCADE
);

CREATE INDEX idx_reading_position_story ON reading_positions(story_id);

Libraries Table

CREATE TABLE libraries (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255) NOT NULL,
    description TEXT,
    is_default BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);

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": "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

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

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
}

POST /api/stories/{id}/reading-progress

Request:
{
  "position": 1250
}

Response:
{
  "id": "uuid",
  "readingPosition": 1250,
  "lastReadAt": "2024-01-01T12:00:00Z"
}

POST /api/stories/{id}/reading-status

Request:
{
  "isRead": true
}

Response:
{
  "id": "uuid",
  "isRead": true,
  "readingPosition": 0,
  "lastReadAt": "2024-01-01T12:00:00Z"
}

GET /api/stories/{id}/read

Returns story content optimized for reading interface

GET /api/stories/random

Returns a random story with optional filtering Query parameters:

  • searchQuery (string): Filter search
  • tags (string[]): Filter by tags

GET /api/stories/check-duplicate

Check for potential duplicate stories Query parameters:

  • title (string): Story title to check
  • authorName (string): Author name to check

GET /api/stories/{id}/collections

Get all collections containing this story

4.2.1 EPUB Import/Export Endpoints

POST /api/stories/epub/import

Import story from EPUB file

Request (multipart/form-data):
{
  "file": "epub file",
  "authorId": "uuid (optional)",
  "authorName": "string (optional)",
  "seriesId": "uuid (optional)",
  "seriesName": "string (optional)",
  "seriesVolume": "integer (optional)",
  "tags": ["string"],
  "preserveReadingPosition": "boolean",
  "overwriteExisting": "boolean",
  "createMissingAuthor": "boolean",
  "createMissingSeries": "boolean"
}

Response:
{
  "success": true,
  "storyId": "uuid",
  "storyTitle": "string",
  "message": "EPUB imported successfully",
  "extractedData": {
    "chapterCount": 12,
    "wordCount": 45000,
    "hasCovers": true
  }
}

GET /api/stories/{id}/epub

Export story as EPUB (simple download)

POST /api/stories/epub/export

Export story as EPUB with custom options

Request:
{
  "storyId": "uuid",
  "includeReadingPosition": true,
  "includeCoverImage": true,
  "includeMetadata": true
}

Response: EPUB file download

POST /api/stories/epub/validate

Validate EPUB file before import

Request (multipart/form-data):
{
  "file": "epub file"
}

Response:
{
  "valid": true,
  "errors": [],
  "filename": "story.epub",
  "size": 1024000
}

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

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 paginated list of tags with enhanced features Query parameters:

  • page (integer): Page number
  • size (integer): Items per page
  • sortBy (string): Sort field (name, usage)
  • sortDir (string): Sort direction (asc, desc)

GET /api/tags/{id}

Get specific tag with full details including aliases

POST /api/tags

Create new tag with color and description

Request:
{
  "name": "string",
  "color": "#3B82F6",
  "description": "string"
}

PUT /api/tags/{id}

Update tag properties

Request:
{
  "name": "string",
  "color": "#3B82F6",
  "description": "string"
}

DELETE /api/tags/{id}

Delete tag with safety checks

GET /api/tags/autocomplete?query={query}

Enhanced autocomplete with alias resolution

GET /api/tags/popular

Most used tags

GET /api/tags/unused

Tags not assigned to any stories

GET /api/tags/stats

Tag usage statistics

GET /api/tags/collections

Tags used by collections

GET /api/tags/resolve/{name}

Resolve tag name through alias system

4.4.1 Tag Alias Management

POST /api/tags/{tagId}/aliases

Add alias to tag

Request:
{
  "aliasName": "string"
}

DELETE /api/tags/{tagId}/aliases/{aliasId}

Remove alias from tag

4.4.2 Tag Management Operations

POST /api/tags/merge

Merge multiple tags into one

Request:
{
  "sourceTagIds": ["uuid1", "uuid2"],
  "targetTagId": "uuid3"
}

Response:
{
  "resultTag": "TagDto",
  "aliasesCreated": ["alias1", "alias2"]
}

POST /api/tags/merge/preview

Preview tag merge operation without executing

POST /api/tags/suggest

AI-powered tag suggestions for content

Request:
{
  "title": "string",
  "content": "string",
  "summary": "string",
  "limit": 10
}

Response:
[
  {
    "tagName": "fantasy",
    "confidence": 0.85,
    "reason": "Content contains magical elements"
  }
]

4.5 Collections Endpoints

GET /api/collections

Search and list collections with pagination (Typesense-powered) Query parameters:

  • page (integer): Page number
  • limit (integer): Items per page
  • search (string): Search query
  • tags (string[]): Filter by tags
  • archived (boolean): Include archived collections

GET /api/collections/{id}

Get collection details with story list

POST /api/collections

Create new collection

Request (JSON or multipart/form-data):
{
  "name": "string",
  "description": "string",
  "tagNames": ["string"],
  "storyIds": ["uuid"],
  "coverImage": "file (optional for multipart)"
}

PUT /api/collections/{id}

Update collection metadata

Request:
{
  "name": "string",
  "description": "string",
  "tagNames": ["string"],
  "rating": 1-5
}

DELETE /api/collections/{id}

Delete collection (stories remain in system)

PUT /api/collections/{id}/archive

Archive or unarchive collection

Request:
{
  "archived": true
}

POST /api/collections/{id}/cover

Upload collection cover image

DELETE /api/collections/{id}/cover

Remove collection cover image

GET /api/collections/{id}/stats

Get detailed collection statistics

Response:
{
  "totalStories": 15,
  "totalWordCount": 125000,
  "estimatedReadingTime": 625,
  "averageStoryRating": 4.2,
  "tagFrequency": {
    "fantasy": 12,
    "adventure": 8
  },
  "authorDistribution": [
    {"authorName": "string", "storyCount": 5}
  ]
}

4.5.1 Collection Story Management

POST /api/collections/{id}/stories

Add stories to collection with optional positioning

Request:
{
  "storyIds": ["uuid"],
  "position": 3
}

Response:
{
  "added": 3,
  "skipped": 1,
  "totalStories": 15
}

DELETE /api/collections/{id}/stories/{storyId}

Remove story from collection

PUT /api/collections/{id}/stories/order

Reorder stories in collection

Request:
{
  "storyOrders": [
    {"storyId": "uuid", "position": 1},
    {"storyId": "uuid", "position": 2}
  ]
}

GET /api/collections/{id}/read/{storyId}

Get story content with collection navigation context

Response:
{
  "story": { /* full story data */ },
  "collection": {
    "id": "uuid",
    "name": "string",
    "currentPosition": 3,
    "totalStories": 10,
    "previousStoryId": "uuid",
    "nextStoryId": "uuid"
  }
}

4.5.2 Collection EPUB Export

GET /api/collections/{id}/epub

Export collection as EPUB file

POST /api/collections/{id}/epub

Export collection as EPUB with custom options

Request:
{
  "includeCoverImage": true,
  "includeMetadata": true,
  "includeTableOfContents": true
}

Response: EPUB file download

4.6 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

// 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

Theme Implementation

// 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

<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
// 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

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. Advanced Features ( IMPLEMENTED)

9.1 Collections Feature COMPLETED

Story Collections allow users to organize stories into ordered lists for better content management and reading workflows. Collections support custom ordering, metadata, and provide an enhanced reading experience for grouped content.

Core Capabilities

  • Create and manage ordered lists of stories
  • Stories can belong to multiple collections
  • Drag-and-drop reordering with gap-based positioning
  • Collection-level metadata and ratings
  • Dedicated collection reading flow with navigation
  • Batch operations on collection contents
  • EPUB export for entire collections
  • Archive functionality for collection management

Collection Data Model

Collections use a junction table approach with position-based ordering:

  • Collections Table: Core collection metadata with archiving support
  • collection_stories: Junction table with gap-based positioning (1000, 2000, 3000...)
  • collection_tags: Many-to-many relationship with tags for categorization

Collection Reading Flow

Enhanced reading experience with collection context:

  • Navigate between stories in collection order
  • Show position within collection (Story 3 of 10)
  • Previous/Next story navigation
  • Collection-specific reading statistics

Collection EPUB Export

Export entire collections as single EPUB files:

  • Maintains story order from collection
  • Includes collection metadata as book metadata
  • Generates table of contents with story titles
  • Preserves individual story formatting and structure

9.2 Enhanced Tag System COMPLETED

Comprehensive enhancement of the tagging functionality including color tags, tag deletion, merging, aliases, and AI-powered suggestions.

Color Tags

  • Visual Organization: Assign hex colors to tags for better visual distinction
  • Predefined Palette: Theme-compatible color selection
  • Custom Colors: Full color picker for advanced customization
  • Accessibility: All colors ensure sufficient contrast ratios

Tag Aliases System

  • Automatic Resolution: Users type "magictf" and get "magic tf" automatically
  • Transparent Integration: Aliases work seamlessly in search and autocomplete
  • Merge Integration: Automatically created when tags are merged
  • Hover Display: Show all aliases when hovering over canonical tags

Tag Merging

Sophisticated tag merging with automatic aliasing:

  1. Select multiple tags to merge
  2. Choose canonical tag name
  3. Preview merge impact (story counts)
  4. All associations transfer to canonical tag
  5. Source tags automatically become aliases

AI-Powered Tag Suggestions

  • Content Analysis: Analyze story title, content, and summary
  • Confidence Scoring: Each suggestion includes confidence level
  • Contextual Reasoning: Explanations for why tags were suggested
  • Integration: Available during story creation and editing

Tag Management Interface

Dedicated tag maintenance accessible through Settings:

  • Searchable and filterable tag list
  • Sortable by name, usage count, creation date
  • Bulk selection for merge/delete operations
  • Visual indicators for colors and alias counts
  • Tag statistics and usage analytics

9.3 EPUB Import/Export System COMPLETED

Full-featured EPUB import and export system with advanced metadata handling and reading position preservation.

EPUB Import Features

  • Format Support: EPUB 2.0 and 3.x formats, DRM-free
  • Validation: Comprehensive file format and structure validation
  • Metadata Extraction: Title, author, description, subjects/tags, cover images
  • Content Processing: Multi-chapter combination with proper HTML sanitization
  • Duplicate Detection: Smart detection of potentially duplicate content
  • Batch Import: Support for importing multiple EPUB files

EPUB Export Features

  • Individual Stories: Export single stories as properly formatted EPUB files
  • Collection Export: Export entire collections as single EPUB with TOC
  • Metadata Preservation: Complete metadata including tags, ratings, series info
  • Cover Images: Automatic inclusion and optimization of cover artwork
  • Reading Positions: EPUB CFI standard support for position preservation

Advanced Metadata Handling

Comprehensive extraction and preservation of:

  • Basic Metadata: Title, author, description, language
  • Publication Data: Publisher, publication date, identifiers (ISBN, etc.)
  • Categorization: Subjects converted to tags automatically
  • Cover Processing: Automatic extraction, validation, and optimization
  • Custom Metadata: StoryCove-specific fields for ratings and series

Reading Position Integration

  • EPUB CFI Support: Standard-compliant reading position tracking
  • Cross-Format Preservation: Maintain positions when importing/exporting
  • Chapter Awareness: Chapter-level position tracking with context
  • Percentage Calculation: Accurate progress percentage based on content length
  • Context Preservation: Before/after text context for position recovery

File Handling & Validation

  • Size Limits: 50MB maximum file size with configurable limits
  • Security: Comprehensive validation to prevent malicious content
  • Error Recovery: Graceful handling of corrupted or invalid EPUB files
  • Progress Feedback: Real-time progress indicators during import/export
  • Batch Operations: Support for processing multiple files simultaneously

10. Future Roadmap

10.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

10.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