bookwiz.io / app / api / books / route.ts
route.ts
Raw
import { createClient } from '@supabase/supabase-js'
import { NextResponse } from 'next/server'
import type { CreateBookRequest } from '@/lib/types/database'
import { templateService } from '@/lib/services/template-service'

// Create server-side Supabase client with service role for admin operations
function createServerSupabaseClient() {
  const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY
  if (!serviceRoleKey) {
    throw new Error('SUPABASE_SERVICE_ROLE_KEY is not set')
  }
  
  return createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    serviceRoleKey,
    {
      auth: {
        autoRefreshToken: false,
        persistSession: false
      }
    }
  )
}

// GET /api/books - Get all books for the authenticated user
export async function GET(request: Request) {
  try {
    const url = new URL(request.url)
    const userId = url.searchParams.get('userId')
    
    if (!userId) {
      return NextResponse.json({ error: 'User ID is required' }, { status: 400 })
    }

    const supabase = createServerSupabaseClient()

    // Get user's books
    const { data: books, error } = await supabase
      .from('books')
      .select(`
        id,
        title,
        description,
        status,
        word_count,
        created_at,
        updated_at,
        author,
        genre,
        target_word_count,
        cover_image_url
      `)
      .eq('user_id', userId)
      .order('updated_at', { ascending: false })

    if (error) {
      console.error('Error fetching books:', error)
      return NextResponse.json({ error: 'Failed to fetch books' }, { status: 500 })
    }

    // Transform dates to be compatible with frontend
    const transformedBooks = books?.map((book: any) => ({
      ...book,
      lastModified: book.updated_at.split('T')[0], // Convert to YYYY-MM-DD format for compatibility
    })) || []

    return NextResponse.json({ books: transformedBooks })
  } catch (error) {
    console.error('Unexpected error:', error)
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}

// POST /api/books - Create a new book
export async function POST(request: Request) {
  try {
    const supabase = createServerSupabaseClient()
    
    // Parse request body
    const body: CreateBookRequest & { userId: string } = await request.json()
    
    // Validate required fields
    if (!body.title || body.title.trim() === '') {
      return NextResponse.json({ error: 'Title is required' }, { status: 400 })
    }
    
    if (!body.userId) {
      return NextResponse.json({ error: 'User ID is required' }, { status: 400 })
    }

    // **CRITICAL**: Check book creation limits BEFORE creating the book
    try {
      // Get user's current usage
      const { data: usageRecords, error: usageError } = await supabase
        .from('user_usage')
        .select('*')
        .eq('user_id', body.userId)
        .order('updated_at', { ascending: false })
        .limit(1)

      if (usageError) {
        console.error('Error checking book creation limits:', usageError)
        // Continue with creation if we can't check limits (fail open)
      } else {
        const currentUsage = usageRecords?.[0] || null
        const booksCreated = currentUsage?.books_created || 0

        // Get user's subscription to determine book limits
        const { data: subscriptionData, error: subscriptionError } = await supabase
          .from('subscriptions')
          .select('price_id, status')
          .eq('user_id', body.userId)
          .eq('status', 'active')
          .order('created_at', { ascending: false })
          .limit(1)

        if (subscriptionError) {
          console.error('Error getting subscription for book limits:', subscriptionError)
        }

        // Get the first subscription or null if none exists
        const subscription = subscriptionData && subscriptionData.length > 0 ? subscriptionData[0] : null

        // Default to free tier limits
        const { PRICING_TIERS } = await import('@/lib/stripe')
        let bookLimit = PRICING_TIERS.FREE.maxBooks

        // Set limits based on subscription
        if (subscription?.price_id) {
          const { getPlanByPriceId } = await import('@/lib/stripe')
          const plan = getPlanByPriceId(subscription.price_id)
          
          if (plan) {
            bookLimit = plan.maxBooks
          }
        }

        // Check if user has reached their book limit
        if (bookLimit !== undefined && bookLimit !== null && booksCreated >= bookLimit) {
          return NextResponse.json({ 
            error: `You've reached your book creation limit (${booksCreated}/${bookLimit}). Upgrade your plan to create more books.`,
            limitExceeded: true,
            usageInfo: {
              currentUsage: booksCreated,
              limit: bookLimit,
              feature: 'books'
            }
          }, { status: 429 }) // 429 Too Many Requests
        }
      }
    } catch (limitCheckError) {
      console.error('Error checking book creation limits:', limitCheckError)
      // Continue with book creation if limit check fails (fail open for better UX)
    }

    // Handle genres - convert array to comma-separated string or use first genre for backward compatibility
    const genreValue = Array.isArray(body.genres) && body.genres.length > 0 
      ? body.genres.join(', ') 
      : body.genre?.trim() || null

    // Create the book
    const { data: book, error } = await supabase
      .from('books')
      .insert({
        title: body.title.trim(),
        description: body.description?.trim() || null,
        author: body.author?.trim() || null,
        genre: genreValue,
        target_word_count: body.target_word_count || null,
        template_id: body.template_id || null,
        user_id: body.userId,
        status: 'Planning',
        word_count: 0
      })
      .select()
      .single()

    if (error) {
      console.error('Error creating book:', error)
      return NextResponse.json({ error: 'Failed to create book' }, { status: 500 })
    }

    // Create initial folder structure for the new book using template
    await templateService.createBookFromTemplate(supabase, book.id, body.template_id)

    // **IMPORTANT**: Increment book creation counter after successful creation
    try {
      const { error: incrementError } = await supabase
        .rpc('increment_usage', {
          user_uuid: body.userId,
          feature_type_param: 'books',
          amount_param: 1,
          metadata_param: {
            book_id: book.id,
            book_title: book.title,
            template_id: body.template_id
          },
          skip_limit_check: true // Skip limit check since we already validated before creation
        })

      if (incrementError) {
        console.error('Failed to increment book creation counter:', incrementError)
        // Don't fail the whole operation - book creation is more important
      }
    } catch (incrementError) {
      console.error('Error incrementing book creation counter:', incrementError)
      // Don't fail the whole operation
    }

    // Transform the response
    const transformedBook = {
      ...book,
      lastModified: book.updated_at.split('T')[0],
    }

    return NextResponse.json({ book: transformedBook }, { status: 201 })
  } catch (error) {
    console.error('Unexpected error:', error)
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}

// Legacy function - now replaced by template system
// Kept for reference only
async function createInitialBookStructure(supabase: any, bookId: string) {
  try {
    // Create comprehensive initial structure for a new book
    const items = [
      // Root level welcome files
      {
        book_id: bookId,
        name: '📖 Welcome to Your New Book',
        type: 'file',
        content: `# Welcome to Your New Book! 🎉

Congratulations on starting your writing journey with Bookwiz! This structure is designed to help you make the most of our AI-powered writing assistant.

## Quick Start Checklist ✅
- [ ] Read the Getting Started Guide
- [ ] Explore the AI Writing Assistant Guide  
- [ ] Customize your Book Overview in the planning folder
- [ ] Try the example prompts with the AI chat
- [ ] Start writing your first chapter!

## What's Included
This book comes pre-loaded with:
- **Guides** - Learn how to write with AI assistance
- **Templates** - Ready-to-use outlines and character sheets
- **Examples** - See AI collaboration in action
- **Planning** - Structure your story
- **Your Content** - Chapters, characters, research

## Need Help?
Open the AI chat panel and try asking:
- "Help me plan my story structure"
- "Act as my main character and answer questions"
- "Improve this scene I'm working on"

Happy writing! 📝✨`,
        file_extension: 'md',
        mime_type: 'text/markdown',
        sort_order: 1
      },
      {
        book_id: bookId,
        name: '🚀 Getting Started Guide',
        type: 'file',
        content: `# Getting Started with Bookwiz 🚀

## How This Works
Bookwiz combines traditional writing with AI assistance. You write, the AI helps enhance, brainstorm, and develop your ideas.

## Basic Workflow
1. **Plan** - Use templates in planning/ to outline your story
2. **Create** - Start writing in the chapters/ folder
3. **Enhance** - Use AI to improve, expand, or brainstorm
4. **Organize** - Keep research and character notes updated

## Navigation Tips
- **Left Panel**: File explorer (organize your content)
- **Center**: Writing area (your main workspace)  
- **Right Panel**: AI chat (your writing assistant)

## File Organization
- **guides/** - Educational content about writing with AI
- **templates/** - Blank forms you can copy and customize
- **examples/** - See how AI collaboration works
- **planning/** - Your story structure and outlines
- **characters/** - Character development and profiles
- **research/** - Notes, worldbuilding, references
- **chapters/** - Your actual book content

## Customizing This Structure
Feel free to:
- Rename folders to match your book
- Delete files you don't need
- Add new folders for specific needs
- Move files around as needed

Start with the AI Writing Assistant Guide to learn how to collaborate effectively with AI!`,
        file_extension: 'md',
        mime_type: 'text/markdown',
        sort_order: 2
      },
      {
        book_id: bookId,
        name: '🤖 AI Writing Assistant Guide',
        type: 'file',
        content: `# AI Writing Assistant Guide 🤖

## What the AI Can Do
Your AI writing assistant can help with:
- **Brainstorming** ideas and plot developments
- **Character development** and dialogue
- **Scene enhancement** and descriptions
- **Plot problem solving** when you're stuck
- **Research** and fact-checking
- **Style improvements** and editing suggestions
- **Role-playing** as your characters

## How to Get Better Results

### Give Context
Instead of: "Write a scene"
Try: "I'm writing a fantasy novel about a young mage. Write a scene where she discovers her powers for the first time. She's shy and has always felt ordinary."

### Be Specific
Instead of: "Make this better"  
Try: "Make this dialogue more natural and add more tension between these two characters who used to be friends"

### Iterate
- Start with a basic request
- Review the AI's response
- Ask for specific improvements
- Build on what works

## Example Conversation Starters
- "Help me brainstorm what my villain's motivation could be"
- "I'm stuck on this plot point: [describe situation]. What are some ways this could resolve?"
- "Act as [Character Name] and answer some questions about their backstory"
- "Here's a scene I wrote: [paste scene]. How can I make it more engaging?"
- "What are some good plot twists for a [genre] story?"

## Advanced Techniques
- **Character Interviews**: Have AI roleplay as your characters
- **Plot Hole Detection**: Ask AI to find inconsistencies
- **Style Matching**: Ask AI to write in your established style
- **Research Assistant**: Get help with world-building details

Check out the examples/ folder to see these techniques in action!`,
        file_extension: 'md',
        mime_type: 'text/markdown',
        sort_order: 3
      },

      // Guides folder
      {
        book_id: bookId,
        name: 'guides',
        type: 'folder',
        expanded: false,
        sort_order: 4
      },

      // Templates folder  
      {
        book_id: bookId,
        name: 'templates',
        type: 'folder',
        expanded: false,
        sort_order: 5
      },

      // Examples folder
      {
        book_id: bookId,
        name: 'examples',
        type: 'folder',
        expanded: false,
        sort_order: 6
      },

      // Planning folder
      {
        book_id: bookId,
        name: 'planning',
        type: 'folder',
        expanded: true,
        sort_order: 7
      },

      // Characters folder
      {
        book_id: bookId,
        name: 'characters',
        type: 'folder',
        expanded: false,
        sort_order: 8
      },

      // Research folder
      {
        book_id: bookId,
        name: 'research',
        type: 'folder',
        expanded: false,
        sort_order: 9
      },

      // Chapters folder
      {
        book_id: bookId,
        name: 'chapters',
        type: 'folder',
        expanded: true,
        sort_order: 10
      }
    ]

    // Insert base structure first
    const { data: baseItems, error: baseError } = await supabase
      .from('file_system_items')
      .insert(items)
      .select()

    if (baseError) {
      console.error('Error creating base book structure:', baseError)
      return
    }

    // Get folder IDs for nested items
    const guidesFolder = baseItems.find((item: any) => item.name === 'guides')
    const templatesFolder = baseItems.find((item: any) => item.name === 'templates')
    const examplesFolder = baseItems.find((item: any) => item.name === 'examples')
    const planningFolder = baseItems.find((item: any) => item.name === 'planning')
    const charactersFolder = baseItems.find((item: any) => item.name === 'characters')
    const researchFolder = baseItems.find((item: any) => item.name === 'research')
    const chaptersFolder = baseItems.find((item: any) => item.name === 'chapters')

    // Create nested items
    const nestedItems = [
      // Guide files
      {
        book_id: bookId,
        parent_id: guidesFolder?.id,
        name: 'how-to-write-with-ai',
        type: 'file',
        content: `# How to Write with AI 🤖✍️

## Core Principles

### 1. AI as a Collaborator, Not a Replacement
- Use AI to enhance your ideas, not replace your creativity
- Your voice and vision should always guide the story
- AI helps overcome blocks, not create your entire book

### 2. Iterative Development
- Start with rough ideas
- Use AI to explore and expand
- Refine through multiple iterations
- Always maintain creative control

## Effective AI Writing Techniques

### Character Development
\`\`\`
Prompt: "I have a character who is [brief description]. Help me develop their backstory, particularly focusing on [specific aspect]. Ask me questions to help flesh this out."
\`\`\`

### Plot Development
\`\`\`
Prompt: "My story is about [premise]. I'm at the point where [current situation]. What are 5 different ways this could develop, considering my character's personality and the story's themes?"
\`\`\`

### Scene Writing
\`\`\`
Prompt: "Help me write a scene where [situation]. The mood should be [emotion/tone]. Focus on [specific elements like dialogue, action, description]."
\`\`\`

### Dialogue Improvement
\`\`\`
Prompt: "Here's some dialogue I wrote: [paste dialogue]. Make it sound more natural while keeping each character's distinct voice. [Character A] is [personality], [Character B] is [personality]."
\`\`\`

## Best Practices
- **Always provide context** about your story, characters, and goals
- **Be specific** about what kind of help you need
- **Review and edit** AI suggestions to match your style
- **Ask follow-up questions** to refine ideas
- **Combine multiple AI responses** for richer content`,
        file_extension: 'md',
        mime_type: 'text/markdown',
        sort_order: 1
      },

      // Template files
      {
        book_id: bookId,
        parent_id: templatesFolder?.id,
        name: 'character-sheet-template',
        type: 'file',
        content: `# Character Sheet Template

## Basic Information
- **Name**: 
- **Age**: 
- **Occupation**: 
- **Role in Story**: (Protagonist/Antagonist/Supporting)

## Physical Description
- **Appearance**: 
- **Distinguishing Features**: 
- **Style/Clothing**: 

## Personality
- **Core Traits**: 
- **Strengths**: 
- **Flaws**: 
- **Fears**: 
- **Desires**: 
- **Motivations**: 

## Background
- **Family**: 
- **Education**: 
- **Past Experiences**: 
- **Formative Events**: 

## Story Arc
- **Starting Point**: 
- **Character Growth**: 
- **Ending Point**: 
- **Key Relationships**: 

## Voice & Dialogue
- **Speech Patterns**: 
- **Vocabulary Level**: 
- **Accent/Dialect**: 
- **Favorite Phrases**: 

## AI Collaboration Notes
*Use this section to note how you want AI to portray this character*
- **Key personality aspects to emphasize**: 
- **How they would react to conflict**: 
- **Their decision-making style**: 

---
**AI Prompt to develop this character:**
"Act as [Character Name]. You are [brief personality description]. Answer questions and respond to situations as this character would, considering their background and motivations."`,
        file_extension: 'md',
        mime_type: 'text/markdown',
        sort_order: 1
      },

      // Example files
      {
        book_id: bookId,
        parent_id: examplesFolder?.id,
        name: 'ai-prompt-examples',
        type: 'file',
        content: `# AI Prompt Examples 💡

## Plot Development Prompts

### When You're Stuck
\`\`\`
"I'm writing a [genre] story about [brief premise]. My protagonist just [recent event], but I'm not sure what should happen next. Given that the main conflict is [describe conflict] and my character's goal is [goal], what are some compelling directions the story could take?"
\`\`\`

### Creating Tension
\`\`\`
"I need to add more tension to this scene: [describe scene]. The characters are [character descriptions]. How can I increase the stakes and make readers more invested in the outcome?"
\`\`\`

## Character Development Prompts

### Character Interviews
\`\`\`
"Please roleplay as my character [Name]. Here's their background: [background]. I'm going to ask you questions about their past, motivations, and how they'd react in different situations. Stay in character and answer as they would."
\`\`\`

### Relationship Dynamics
\`\`\`
"I have two characters: [Character A description] and [Character B description]. They need to [situation/conflict]. How would their different personalities create tension or cooperation in this scenario?"
\`\`\`

## Scene Enhancement Prompts

### Improving Descriptions
\`\`\`
"Here's a scene I wrote: [paste scene]. Help me make the setting more vivid and atmospheric. The mood should be [mood] and I want readers to feel [emotion]."
\`\`\`

### Dialogue Polish
\`\`\`
"Improve this dialogue to sound more natural: [paste dialogue]. Character A is [personality] and Character B is [personality]. Make sure each character has a distinct voice."
\`\`\`

## Research & Worldbuilding Prompts

### Historical Research
\`\`\`
"I'm writing a story set in [time period/location]. What are some interesting historical details about [specific aspect] that could add authenticity to my story?"
\`\`\`

### Fantasy/Sci-Fi Worldbuilding
\`\`\`
"Help me develop the rules for [magic system/technology/society] in my world. It should be [characteristics] and create interesting limitations that drive conflict."
\`\`\`

## Problem-Solving Prompts

### Plot Holes
\`\`\`
"I think I have a plot hole: [describe issue]. Given what I've established about [relevant story elements], how can I resolve this logically while staying true to my story's rules?"
\`\`\`

### Pacing Issues
\`\`\`
"This part of my story feels too [slow/fast]: [describe section]. How can I adjust the pacing while maintaining [story elements you want to keep]?"
\`\`\``,
        file_extension: 'md',
        mime_type: 'text/markdown',
        sort_order: 1
      },

      // Planning files
      {
        book_id: bookId,
        parent_id: planningFolder?.id,
        name: 'book-overview',
        type: 'file',
        content: `# Book Overview

## Basic Information
- **Title**: 
- **Genre**: 
- **Target Audience**: 
- **Estimated Length**: 

## Core Concept
**Logline** (1-2 sentences):


**Premise** (1 paragraph):


## Main Elements
**Protagonist**: 


**Central Conflict**: 


**Stakes**: 


**Theme**: 


## Setting
**Time Period**: 

**Location(s)**: 

**World Details**: 


## Target Goals
- **Daily Word Count**: 
- **Total Target**: 
- **Completion Date**: 

## AI Collaboration Strategy
*How do you plan to use AI for this project?*
- **Research areas where AI can help**: 
- **Character development assistance needed**: 
- **Plot elements to brainstorm**: 

---
**AI Prompt for this book:**
"I'm writing a [genre] novel about [brief premise]. The main character is [protagonist description] and the central conflict involves [conflict]. Help me develop [specific areas where you need help]."`,
        file_extension: 'md',
        mime_type: 'text/markdown',
        sort_order: 1
      },

      // Sample chapter
      {
        book_id: bookId,
        parent_id: chaptersFolder?.id,
        name: 'chapter-01-sample',
        type: 'file',
        content: `# Chapter 1: The Beginning

*This is a sample chapter showing how you might collaborate with AI. Delete this and start your own story!*

## AI Collaboration Notes
*I asked the AI: "Help me write an opening scene for a mystery novel. The protagonist is a detective who just moved to a small town. Create intrigue but don't reveal too much."*

---

Detective Sarah Chen had been in Millbrook exactly three days when she found the first note.

It was tucked under her windshield wiper, barely visible in the pre-dawn darkness. The paper was thick, expensive—the kind that came from the town's only stationery shop. Someone had written in careful script: "Welcome to town. Some secrets are better left buried."

Sarah glanced around the empty parking lot outside Miller's Diner. Main Street was deserted except for a single streetlight casting long shadows between the antique shops and cafes. Somewhere in the distance, a dog barked twice and fell silent.

*AI Enhancement Note: I asked the AI to "make this opening more atmospheric and add a sense of unease." The AI suggested adding sensory details and the ominous note to create immediate intrigue.*

She folded the note and slipped it into her jacket pocket. In Chicago, anonymous threats were part of the job. But this felt different—personal, deliberate. Someone knew she was here, knew she was a cop, and wanted her gone before she'd even unpacked her boxes.

*Next AI prompt: "What should Sarah's reaction be? She's experienced but this is her first day in a small town where everyone knows everyone. Show her investigative instincts kicking in."*

## Writing Process Notes
- Started with basic scene idea
- Used AI to enhance atmosphere
- Asked for character reaction suggestions
- Will develop mystery elements in next scene

## Questions for AI:
- Who might have left the note?
- What secrets could a small town be hiding?
- How should Sarah investigate without seeming paranoid?`,
        file_extension: 'md',
        mime_type: 'text/markdown',
        sort_order: 1
      }
    ]

    // Insert nested items
    await supabase
      .from('file_system_items')
      .insert(nestedItems)

  } catch (error) {
    console.error('Unexpected error creating book structure:', error)
  }
}