diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4ba72ac --- /dev/null +++ b/.dockerignore @@ -0,0 +1,56 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js + +# testing +coverage + +# next.js +.next/ +out/ +build + +# production +dist + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# IDE +.vscode +.idea + +# OS +Thumbs.db + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# Git +.git +.gitignore \ No newline at end of file diff --git a/.env.example b/.env.example index 19790e1..fa9b504 100644 --- a/.env.example +++ b/.env.example @@ -10,4 +10,27 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key_here OPENAI_API_KEY=your_openai_api_key_here # Anthropic API (optional, for Claude models) -ANTHROPIC_API_KEY=your_anthropic_api_key_here \ No newline at end of file +ANTHROPIC_API_KEY=your_anthropic_api_key_here + +# Database +DATABASE_URL=postgresql://postgres:postgres@localhost:5432/codeguide_db + +# Redis (for session caching and rate limiting) +REDIS_URL=redis://localhost:6379 + +# AWS S3 Configuration +AWS_ACCESS_KEY_ID=your_aws_access_key_id +AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key +AWS_REGION=us-east-1 +AWS_S3_BUCKET_NAME=your-s3-bucket-name + +# JWT Secret (generate a secure random string) +JWT_SECRET=your_jwt_secret_here_make_it_long_and_random + +# Next.js Configuration +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=your_nextauth_secret_here + +# Rate Limiting +RATE_LIMIT_MAX=100 +RATE_LIMIT_WINDOW_MS=900000 \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f704a1d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,51 @@ +# Build outputs +.next +out +dist +build + +# Dependencies +node_modules +.pnp +.pnp.js + +# Logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Environment variables +.env* + +# Docker +Dockerfile* + +# Package manager files +package-lock.json +yarn.lock +pnpm-lock.yaml + +# IDE files +.vscode +.idea + +# OS files +.DS_Store +Thumbs.db + +# Generated files +*.d.ts + +# Database +*.sqlite +*.db \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..f6a8bf3 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,19 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": false, + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "endOfLine": "lf", + "arrowParens": "always", + "bracketSpacing": true, + "embeddedLanguageFormatting": "auto", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxSingleQuote": false, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "bracketSameLine": false +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 2f1c205..bbe261e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,416 +1,71 @@ -# CLAUDE.md - CodeGuide Starter Kit +# Claude Code Task Management Guide -This file contains essential context about the project structure, technologies, and conventions to help Claude understand and work effectively within this codebase. +## Documentation Available -## Project Overview +📚 **Project Documentation**: Check the documentation files in this directory for project-specific setup instructions and guides. +**Project Tasks**: Check the tasks directory in documentation/tasks for the list of tasks to be completed. Use the CLI commands below to interact with them. -**CodeGuide Starter Kit** is a modern Next.js starter template featuring authentication, database integration, AI capabilities, and a comprehensive UI component system. +## MANDATORY Task Management Workflow -### Core Technologies - -- **Framework**: Next.js 15 with App Router (`/src/app` directory structure) -- **Language**: TypeScript with strict mode enabled -- **Styling**: TailwindCSS v4 with CSS custom properties -- **UI Components**: shadcn/ui (New York style) with Lucide icons -- **Authentication**: Clerk with middleware protection -- **Database**: Supabase with third-party auth integration -- **AI Integration**: Vercel AI SDK with support for Anthropic Claude and OpenAI -- **Theme System**: next-themes with dark mode support - -## Project Structure - -``` -src/ -├── app/ # Next.js App Router -│ ├── api/ # API routes -│ │ └── chat/ # AI chat endpoint -│ ├── globals.css # Global styles with dark mode -│ ├── layout.tsx # Root layout with providers -│ └── page.tsx # Home page with status dashboard -├── components/ -│ ├── ui/ # shadcn/ui components (40+ components) -│ ├── chat.tsx # AI chat interface -│ ├── setup-guide.tsx # Configuration guide -│ ├── theme-provider.tsx # Theme context provider -│ └── theme-toggle.tsx # Dark mode toggle components -├── lib/ -│ ├── utils.ts # Utility functions (cn, etc.) -│ ├── supabase.ts # Supabase client configurations -│ ├── user.ts # User utilities using Clerk -│ └── env-check.ts # Environment validation -└── middleware.ts # Clerk authentication middleware -``` - -## Key Configuration Files - -- **package.json**: Dependencies and scripts -- **components.json**: shadcn/ui configuration (New York style, neutral colors) -- **tsconfig.json**: TypeScript configuration with path aliases (`@/`) -- **.env.example**: Environment variables template -- **SUPABASE_CLERK_SETUP.md**: Integration setup guide - -## Authentication & Database - -### Clerk Integration -- Middleware protects `/dashboard(.*)` and `/profile(.*)` routes -- Components: `SignInButton`, `SignedIn`, `SignedOut`, `UserButton` -- User utilities in `src/lib/user.ts` use `currentUser()` from Clerk - -### Supabase Integration -- **Client**: `createSupabaseServerClient()` for server-side with Clerk tokens -- **RLS**: Row Level Security uses `auth.jwt() ->> 'sub'` for Clerk user IDs -- **Example Migration**: `supabase/migrations/001_example_tables_with_rls.sql` - -#### Supabase Client Usage Patterns - -**Server-side (Recommended for data fetching):** -```typescript -import { createSupabaseServerClient } from "@/lib/supabase" - -export async function getServerData() { - const supabase = await createSupabaseServerClient() - - const { data, error } = await supabase - .from('posts') - .select('*') - .order('created_at', { ascending: false }) - - if (error) { - console.error('Database error:', error) - return null - } - - return data -} -``` - -**Client-side (For interactive operations):** -```typescript -"use client" - -import { supabase } from "@/lib/supabase" -import { useAuth } from "@clerk/nextjs" - -function ClientComponent() { - const { getToken } = useAuth() - - const fetchData = async () => { - const token = await getToken() - - // Pass token manually for client-side operations - const { data, error } = await supabase - .from('posts') - .select('*') - .auth(token) - - return data - } -} -``` - -## UI & Styling - -### TailwindCSS Setup -- **Version**: TailwindCSS v4 with PostCSS -- **Custom Properties**: CSS variables for theming -- **Dark Mode**: Class-based with `next-themes` -- **Animations**: `tw-animate-css` package included - -### shadcn/ui Components -- **Style**: New York variant -- **Theme**: Neutral base color with CSS variables -- **Icons**: Lucide React -- **Components Available**: 40+ UI components (Button, Card, Dialog, etc.) - -### Theme System -- **Provider**: `ThemeProvider` in layout with system detection -- **Toggle Components**: `ThemeToggle` (dropdown) and `SimpleThemeToggle` (button) -- **Persistence**: Automatic theme persistence across sessions - -## AI Integration - -### Vercel AI SDK -- **Endpoint**: `/api/chat/route.ts` -- **Providers**: Anthropic Claude and OpenAI support -- **Chat Component**: Real-time streaming chat interface -- **Authentication**: Requires Clerk authentication - -## Development Conventions - -### File Organization -- **Components**: Use PascalCase, place in appropriate directories -- **Utilities**: Place reusable functions in `src/lib/` -- **Types**: Define alongside components or in dedicated files -- **API Routes**: Follow Next.js App Router conventions - -### Import Patterns -```typescript -// Path aliases (configured in tsconfig.json) -import { Button } from "@/components/ui/button" -import { getCurrentUser } from "@/lib/user" -import { supabase } from "@/lib/supabase" - -// External libraries -import { useTheme } from "next-themes" -import { SignedIn, useAuth } from "@clerk/nextjs" -``` - -### Component Patterns -```typescript -// Client components (when using hooks/state) -"use client" - -// Server components (default, for data fetching) -export default async function ServerComponent() { - const user = await getCurrentUser() - // ... -} -``` - -## Environment Variables - -Required for full functionality: +🚨 **YOU MUST FOLLOW THIS EXACT WORKFLOW - NO EXCEPTIONS** 🚨 +### **STEP 1: DISCOVER TASKS (MANDATORY)** +You MUST start by running this command to see all available tasks: ```bash -# Clerk Authentication -NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... -CLERK_SECRET_KEY=sk_test_... - -# Supabase Database -NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co -NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... - -# AI Integration (optional) -OPENAI_API_KEY=sk-... -ANTHROPIC_API_KEY=sk-ant-... -``` - -## Common Patterns - -### Row Level Security (RLS) Policies - -All database tables should use RLS policies that reference Clerk user IDs via `auth.jwt() ->> 'sub'`. - -**Basic User-Owned Data Pattern:** -```sql --- Enable RLS on table -ALTER TABLE posts ENABLE ROW LEVEL SECURITY; - --- Users can read all posts (public) -CREATE POLICY "Anyone can read posts" ON posts - FOR SELECT USING (true); - --- Users can only insert posts as themselves -CREATE POLICY "Users can insert own posts" ON posts - FOR INSERT WITH CHECK (auth.jwt() ->> 'sub' = user_id); - --- Users can only update their own posts -CREATE POLICY "Users can update own posts" ON posts - FOR UPDATE USING (auth.jwt() ->> 'sub' = user_id); - --- Users can only delete their own posts -CREATE POLICY "Users can delete own posts" ON posts - FOR DELETE USING (auth.jwt() ->> 'sub' = user_id); -``` - -**Private Data Pattern:** -```sql --- Completely private to each user -CREATE POLICY "Users can only access own data" ON private_notes - FOR ALL USING (auth.jwt() ->> 'sub' = user_id); -``` - -**Conditional Visibility Pattern:** -```sql --- Public profiles or own profile -CREATE POLICY "Users can read public profiles or own profile" ON profiles - FOR SELECT USING ( - is_public = true OR auth.jwt() ->> 'sub' = user_id - ); +task-manager list-tasks ``` -**Collaboration Pattern:** -```sql --- Owner and collaborators can access -CREATE POLICY "Owners and collaborators can read" ON collaborations - FOR SELECT USING ( - auth.jwt() ->> 'sub' = owner_id OR - auth.jwt() ->> 'sub' = ANY(collaborators) - ); +### **STEP 2: START EACH TASK (MANDATORY)** +Before working on any task, you MUST mark it as started: +```bash +task-manager start-task ``` -### Database Operations with Supabase - -**Complete CRUD Example:** -```typescript -import { createSupabaseServerClient } from "@/lib/supabase" -import { getCurrentUser } from "@/lib/user" - -// CREATE - Insert new record -export async function createPost(title: string, content: string) { - const user = await getCurrentUser() - if (!user) return null - - const supabase = await createSupabaseServerClient() - - const { data, error } = await supabase - .from('posts') - .insert({ - title, - content, - user_id: user.id, // Clerk user ID - }) - .select() - .single() - - if (error) { - console.error('Error creating post:', error) - return null - } - - return data -} - -// READ - Fetch user's posts -export async function getUserPosts() { - const supabase = await createSupabaseServerClient() - - const { data, error } = await supabase - .from('posts') - .select(` - id, - title, - content, - created_at, - user_id - `) - .order('created_at', { ascending: false }) - - if (error) { - console.error('Error fetching posts:', error) - return [] - } - - return data -} - -// UPDATE - Modify existing record -export async function updatePost(postId: string, updates: { title?: string; content?: string }) { - const supabase = await createSupabaseServerClient() - - const { data, error } = await supabase - .from('posts') - .update(updates) - .eq('id', postId) - .select() - .single() - - if (error) { - console.error('Error updating post:', error) - return null - } - - return data -} - -// DELETE - Remove record -export async function deletePost(postId: string) { - const supabase = await createSupabaseServerClient() - - const { error } = await supabase - .from('posts') - .delete() - .eq('id', postId) - - if (error) { - console.error('Error deleting post:', error) - return false - } - - return true -} +### **STEP 3: COMPLETE EACH TASK (MANDATORY)** +After finishing implementation, you MUST mark the task as completed: +```bash +task-manager complete-task "Brief description of what was implemented" ``` -**Real-time Subscriptions:** -```typescript -"use client" - -import { useEffect, useState } from "react" -import { supabase } from "@/lib/supabase" -import { useAuth } from "@clerk/nextjs" +## Task Files Location -function useRealtimePosts() { - const [posts, setPosts] = useState([]) - const { getToken } = useAuth() +📁 **Task Data**: Your tasks are organized in the `documentation/tasks/` directory: +- Task JSON files contain complete task information +- Use ONLY the `task-manager` commands listed above +- Follow the mandatory workflow sequence for each task - useEffect(() => { - const fetchPosts = async () => { - const token = await getToken() - - const { data } = await supabase - .from('posts') - .select('*') - .auth(token) - - setPosts(data || []) - } +## MANDATORY Task Workflow Sequence - fetchPosts() +🔄 **For EACH individual task, you MUST follow this sequence:** - // Subscribe to changes - const subscription = supabase - .channel('posts-channel') - .on('postgres_changes', - { event: '*', schema: 'public', table: 'posts' }, - (payload) => { - fetchPosts() // Refetch on any change - } - ) - .subscribe() +1. 📋 **DISCOVER**: `task-manager list-tasks` (first time only) +2. 🚀 **START**: `task-manager start-task ` (mark as in progress) +3. 💻 **IMPLEMENT**: Do the actual coding/implementation work +4. ✅ **COMPLETE**: `task-manager complete-task "What was done"` +5. 🔁 **REPEAT**: Go to next task (start from step 2) - return () => { - subscription.unsubscribe() - } - }, [getToken]) +## Task Status Options - return posts -} -``` - -### Protected Routes -Routes matching `/dashboard(.*)` and `/profile(.*)` are automatically protected by Clerk middleware. - -### Theme-Aware Components -```typescript -// Automatic dark mode support via CSS custom properties -
- -
-``` - -## Development Commands - -```bash -npm run dev # Start development server with Turbopack -npm run build # Build for production -npm run start # Start production server -npm run lint # Run ESLint -``` +- `pending` - Ready to work on +- `in_progress` - Currently being worked on +- `completed` - Successfully finished +- `blocked` - Cannot proceed (waiting for dependencies) +- `cancelled` - No longer needed -## Best Practices +## CRITICAL WORKFLOW RULES -1. **Authentication**: Always check user state with Clerk hooks/utilities -2. **Database**: Use RLS policies with Clerk user IDs for security -3. **UI**: Leverage existing shadcn/ui components before creating custom ones -4. **Styling**: Use TailwindCSS classes and CSS custom properties for theming -5. **Types**: Maintain strong TypeScript typing throughout -6. **Performance**: Use server components by default, client components only when needed +❌ **NEVER skip** the `task-manager start-task` command +❌ **NEVER skip** the `task-manager complete-task` command +❌ **NEVER work on multiple tasks simultaneously** +✅ **ALWAYS complete one task fully before starting the next** +✅ **ALWAYS provide completion details in the complete command** +✅ **ALWAYS follow the exact 3-step sequence: list → start → complete** -## Integration Notes +## Final Requirements -- **Clerk + Supabase**: Uses modern third-party auth (not deprecated JWT templates) -- **AI Chat**: Requires authentication and environment variables -- **Dark Mode**: Automatically applied to all shadcn components -- **Mobile**: Responsive design with TailwindCSS breakpoints +🚨 **CRITICAL**: Your work is not complete until you have: +1. ✅ Completed ALL tasks using the mandatory workflow +2. ✅ Committed all changes with comprehensive commit messages +3. ✅ Created a pull request with proper description -This starter kit provides a solid foundation for building modern web applications with authentication, database integration, AI capabilities, and polished UI components. \ No newline at end of file +Remember: The task management workflow is MANDATORY, not optional! diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1fdfdb3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +# Use official Node.js runtime as base image +FROM node:18-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Copy package files +COPY package.json package-lock.json* ./ +RUN npm ci + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Build the application +RUN npm run build + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production +# Uncomment the following line in case you want to disable telemetry during runtime. +# ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 +ENV HOSTNAME "0.0.0.0" + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/README.md b/README.md index 4ba2b0e..28397af 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,105 @@ [![CodeGuide](/codeguide-backdrop.svg)](https://codeguide.dev) -# CodeGuide Starter Kit +# CodeGuide Starter Kit Lite -A modern web application starter template built with Next.js 15, featuring authentication, database integration, AI capabilities, and dark mode support. +A comprehensive web application framework built with Next.js 15, featuring full-stack development tools, AI integration, and production-ready infrastructure. ## Tech Stack +### Frontend - **Framework:** [Next.js 15](https://nextjs.org/) (App Router) - **Language:** TypeScript -- **Authentication:** [Clerk](https://clerk.com/) -- **Database:** [Supabase](https://supabase.com/) - **Styling:** [Tailwind CSS v4](https://tailwindcss.com/) - **UI Components:** [shadcn/ui](https://ui.shadcn.com/) -- **AI Integration:** [Vercel AI SDK](https://sdk.vercel.ai/) - **Theme System:** [next-themes](https://github.com/pacocoursey/next-themes) +### Backend & Database +- **Database:** [PostgreSQL](https://postgresql.org/) with [Supabase](https://supabase.com/) +- **Caching:** [Redis](https://redis.io/) for sessions and rate limiting +- **Authentication:** [Clerk](https://clerk.com/) +- **File Storage:** [AWS S3](https://aws.amazon.com/s3/) + +### AI & APIs +- **AI Integration:** [Vercel AI SDK](https://sdk.vercel.ai/) +- **Language Models:** OpenAI GPT-4, Anthropic Claude +- **Rate Limiting:** Advanced rate limiting with Redis + +### Infrastructure +- **Containerization:** Docker with Docker Compose +- **CI/CD:** GitHub Actions +- **Production Deploy:** Vercel/AWS with monitoring + ## Prerequisites Before you begin, ensure you have the following: - Node.js 18+ installed +- Docker and Docker Compose for development environment - A [Clerk](https://clerk.com/) account for authentication - A [Supabase](https://supabase.com/) account for database -- Optional: [OpenAI](https://platform.openai.com/) or [Anthropic](https://console.anthropic.com/) API key for AI features +- Redis instance (local or cloud) +- AWS account for S3 storage +- [OpenAI](https://platform.openai.com/) API key for AI features - Generated project documents from [CodeGuide](https://codeguide.dev/) for best development experience ## Getting Started +### Quick Start (Docker) + 1. **Clone the repository** ```bash git clone - cd codeguide-starter-kit + cd codeguide-starter-kit-lite-v2 ``` -2. **Install dependencies** +2. **Environment setup** ```bash - npm install - # or - yarn install - # or - pnpm install + cp .env.example .env.local + # Edit .env.local with your API keys and configuration ``` -3. **Environment Variables Setup** - - Copy the `.env.example` file to `.env.local`: - ```bash - cp .env.example .env.local - ``` - - Fill in the environment variables in `.env.local` (see Configuration section below) +3. **Start with Docker** + ```bash + npm run docker:dev + ``` -4. **Start the development server** +4. **Install frontend dependencies and start Next.js** ```bash + npm install npm run dev - # or - yarn dev - # or - pnpm dev ``` -5. **Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.** +5. **Open [http://localhost:3000](http://localhost:3000)** + +### Manual Setup + +1. **Clone and install** + ```bash + git clone + cd codeguide-starter-kit-lite-v2 + npm install + ``` + +2. **Set up services** (PostgreSQL, Redis, S3) + - See Configuration section below for detailed setup + +3. **Environment variables** + ```bash + cp .env.example .env.local + # Fill in all required environment variables + ``` + +4. **Database setup** + ```bash + # Run migrations (when database is connected) + npm run db:migrate + ``` + +5. **Start development** + ```bash + npm run dev + ``` -The homepage includes a setup dashboard with direct links to configure each service. +The homepage includes a comprehensive setup dashboard showing the status of all services. ## Configuration diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2b705f0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,80 @@ +version: '3.8' + +services: + # PostgreSQL Database + postgres: + image: postgres:15-alpine + environment: + POSTGRES_DB: codeguide_db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./supabase/migrations:/docker-entrypoint-initdb.d + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 30s + timeout: 10s + retries: 5 + + # Redis for session caching and rate limiting + redis: + image: redis:7-alpine + ports: + - "6379:6379" + volumes: + - redis_data:/data + command: redis-server --appendonly yes + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 5 + + # Next.js Application + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "3000:3000" + environment: + - NODE_ENV=development + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/codeguide_db + - REDIS_URL=redis://redis:6379 + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - .:/app + - /app/node_modules + - /app/.next + env_file: + - .env.local + + # MinIO for local S3 storage (development) + minio: + image: minio/minio:latest + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin123 + volumes: + - minio_data:/data + command: server /data --console-address ":9001" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 10s + retries: 5 + +volumes: + postgres_data: + redis_data: + minio_data: \ No newline at end of file diff --git a/documentation/app_flowchart.md b/documentation/app_flowchart.md new file mode 100644 index 0000000..bfc4976 --- /dev/null +++ b/documentation/app_flowchart.md @@ -0,0 +1,12 @@ +flowchart TD + Start[Start] + Start --> Input[User Input] + Input --> Outline[Automated Outline Generation] + Outline --> Analysis[Contextual Analysis and Summary Composition] + Analysis --> Decision{Need Template Customization} + Decision -->|Yes| Template[Customizable Template Framework] + Decision -->|No| Collaboration[Collaboration and Version Control] + Template --> Collaboration + Collaboration --> Export[Multi-Format Export and Integration] + Export --> UX[Intuitive User Experience] + UX --> End[Finish] \ No newline at end of file diff --git a/documentation/backend_structure_document.md b/documentation/backend_structure_document.md new file mode 100644 index 0000000..41560af --- /dev/null +++ b/documentation/backend_structure_document.md @@ -0,0 +1,191 @@ +# Backend Structure Document + +This document outlines the design and setup of the backend system for our intelligent outline and summary generation tool. It covers architecture, databases, APIs, hosting, infrastructure, security, and maintenance. The goal is to give a clear, step-by-step view of how the backend works and how it’s hosted, without assuming deep technical knowledge. + +## 1. Backend Architecture + +Overview: +- We use a service-oriented approach, breaking the backend into focused components (services) that handle specific tasks. +- A central API Gateway routes requests from the frontend or other services to the right place. + +Key Components: +- **API Gateway (Node.js + Express)**: Receives all incoming calls, handles authentication, and sends requests to the appropriate microservice. +- **NLP Service (Python + Flask)**: Processes text inputs, runs natural language analysis, and returns summaries or outlines. +- **Template Service (Node.js)**: Manages outline templates, applying user settings to generate custom outputs. +- **Collaboration Service (Node.js)**: Tracks edits, comments, and versions for each project. +- **Export Service (Node.js)**: Converts final documents into PDF, DOCX, or Markdown. +- **Authentication Service**: Manages user accounts, tokens, and permissions. + +How It Supports: +- **Scalability**: Each service can be scaled independently. If the NLP processor needs more power, we add more instances without touching other services. +- **Maintainability**: Clear boundaries mean teams can work on one service without affecting others. +- **Performance**: Services communicate over fast internal networks; heavy tasks (like NLP) run in optimized environments. + +## 2. Database Management + +We use a combination of relational and non-relational databases to store different kinds of data: + +- **PostgreSQL (SQL)** + - User accounts, project metadata, version histories, and access controls. +- **MongoDB (NoSQL)** + - Flexible storage of raw input texts, generated outlines, logs, and audit trails. +- **Redis (In-Memory Cache)** + - Caches frequent lookups (user sessions, template data) to speed up responses. + +Data Practices: +- Regular backups of PostgreSQL and MongoDB with automated snapshots. +- Read-replicas for PostgreSQL to handle high read loads. +- Data retention policies to archive or purge old logs. + +## 3. Database Schema + +### PostgreSQL Schema (Human-Readable) +- **Users**: Stores user profiles and credentials. + - ID, Email, PasswordHash, Name, CreatedAt +- **Projects**: Holds information about each outline project. + - ID, UserID, Title, Description, CreatedAt, UpdatedAt +- **Templates**: Defines the structure and settings for outlines. + - ID, UserID, Name, JSONDefinition, CreatedAt +- **Versions**: Tracks changes to each project’s outline. + - ID, ProjectID, TemplateID, VersionNumber, ChangeNotes, CreatedAt +- **Comments**: Collaboration notes on specific sections. + - ID, ProjectID, UserID, SectionReference, Text, CreatedAt + +### PostgreSQL Schema (SQL) +```sql +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + name VARCHAR(100), + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE projects ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id), + title VARCHAR(255) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE templates ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id), + name VARCHAR(100) NOT NULL, + json_definition JSONB NOT NULL, + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE versions ( + id SERIAL PRIMARY KEY, + project_id INTEGER REFERENCES projects(id), + template_id INTEGER REFERENCES templates(id), + version_number INTEGER NOT NULL, + change_notes TEXT, + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE comments ( + id SERIAL PRIMARY KEY, + project_id INTEGER REFERENCES projects(id), + user_id INTEGER REFERENCES users(id), + section_reference VARCHAR(255), + text TEXT NOT NULL, + created_at TIMESTAMP DEFAULT NOW() +); +``` + +### MongoDB Schema (NoSQL) +- **Outlines Collection**: + - _id, projectId, rawInput, generatedOutline (array of sections), createdAt +- **Logs Collection**: + - _id, serviceName, level, message, timestamp + +## 4. API Design and Endpoints + +We follow a RESTful style so that each endpoint represents a resource and uses standard HTTP methods. + +Key Endpoints: + +- **Authentication** + - `POST /auth/register` – Create a new user account + - `POST /auth/login` – Authenticate and return a token +- **Projects** + - `GET /projects` – List all projects for the user + - `POST /projects` – Create a new project + - `GET /projects/{id}` – Retrieve project details + - `PUT /projects/{id}` – Update project metadata + - `DELETE /projects/{id}` – Remove a project +- **Outlines** + - `POST /projects/{id}/outline` – Generate an outline from user input + - `GET /projects/{id}/outline` – Fetch the latest outline +- **Templates** + - `GET /templates` – List user’s templates + - `POST /templates` – Create a template + - `PUT /templates/{id}` – Update a template +- **Collaboration** + - `GET /projects/{id}/comments` – List comments + - `POST /projects/{id}/comments` – Add a comment +- **Exports** + - `POST /projects/{id}/export?format=pdf|docx|md` – Generate a downloadable file + +Authentication tokens are sent in the `Authorization` header as a Bearer token. + +## 5. Hosting Solutions + +We host in the cloud for reliability and easy scaling: +- **Provider**: Amazon Web Services (AWS) +- **Compute**: Containers managed by Elastic Container Service (ECS) or Elastic Kubernetes Service (EKS) +- **Databases**: + - PostgreSQL on Amazon RDS with multi-AZ deployment + - MongoDB Atlas for managed NoSQL hosting + - Redis on Amazon ElastiCache +- **File Storage**: Amazon S3 for exported documents and backups +- **Benefits**: + - High uptime with multi-zone failover + - Pay-as-you-go keeps costs aligned with usage + - Built-in monitoring and security tools + +## 6. Infrastructure Components + +- **Load Balancer**: AWS Application Load Balancer distributes incoming traffic across service instances. +- **Caching**: Redis stores session data and template lookups for sub-millisecond response times. +- **CDN**: Amazon CloudFront serves static assets (front-end bundle, exports) from edge locations. +- **Containerization**: Docker images for each service, ensuring consistent environments. +- **Orchestration**: ECS/EKS auto-scales containers based on CPU and memory usage. +- **Service Discovery**: Internal DNS for services to find and communicate with each other securely. + +## 7. Security Measures + +- **Encryption**: + - TLS everywhere for in-transit data protection + - AES-256 encryption at rest for databases and S3 buckets +- **Authentication & Authorization**: + - JWT-based tokens with short lifetimes and refresh tokens + - Role-based access control (RBAC) ensures users only see their own projects +- **Network Security**: + - Private subnets for databases and internal services + - Public subnets only for load balancers +- **Data Validation**: + - Input sanitization to prevent injection attacks +- **Compliance**: + - Regular security audits and vulnerability scanning + +## 8. Monitoring and Maintenance + +- **Monitoring Tools**: + - AWS CloudWatch for metrics and logs + - ELK (Elasticsearch, Logstash, Kibana) stack for centralized log analysis + - Prometheus + Grafana for service health dashboards +- **Alerts**: + - Automated alerts on CPU/memory spikes, error rates, or high latency +- **Backups & Updates**: + - Daily automated database snapshots, weekly full backups + - Rolling updates to containers with zero downtime deployments + - Dependency updates tracked by a CI/CD pipeline (GitHub Actions) + +## 9. Conclusion and Overall Backend Summary + +Our backend is a collection of specialized services working together through a simple API gateway. It uses reliable, managed databases and cloud infrastructure to ensure the system can grow as demand increases. Strong security and monitoring practices protect user data and guarantee high availability. These choices align with our goals of providing a fast, scalable, and dependable outline-generation tool that users can trust. \ No newline at end of file diff --git a/documentation/frontend_guidelines_document.md b/documentation/frontend_guidelines_document.md new file mode 100644 index 0000000..24e4f93 --- /dev/null +++ b/documentation/frontend_guidelines_document.md @@ -0,0 +1,120 @@ +# Frontend Guideline Document + +This document outlines the frontend architecture, design principles, and technologies powering our Outline Generation and Summary Composition tool. It’s written in everyday language so everyone—technical or non-technical—can understand how the frontend is set up and why. + +## 1. Frontend Architecture + +### Overview +We use a component-based setup built on React with TypeScript. Our build tool is Vite, chosen for fast startup and hot-module reloading. + +### Key Libraries and Frameworks +- **React**: For building reusable UI components. +- **TypeScript**: Adds type safety and better code clarity. +- **Vite**: Modern build tool for quick development feedback. +- **React Router**: Manages navigation between different pages. + +### Scalability, Maintainability, Performance +- **Modular Components**: Each feature lives in its own folder, keeping code organized as the app grows. +- **Lazy Loading**: We split code so that each section loads only when needed, speeding up initial page loads. +- **Clear Type Definitions**: TypeScript interfaces describe data shapes, reducing bugs and making future changes easier. + +## 2. Design Principles + +We follow three main principles: + +### Usability +- **Simple Flows**: Every screen guides users step by step—no surprises. +- **Clear Labels**: Buttons and headings use everyday words (e.g., “Generate Outline,” “Download PDF”). + +### Accessibility +- **Keyboard Navigation**: All interactive elements can be reached by tabbing. +- **ARIA Labels**: Screen-reader friendly attributes on custom controls. +- **Color Contrast**: Meets WCAG AA standards for text readability. + +### Responsiveness +- **Mobile-First**: We design for phones first, then scale up to tablets and desktops. +- **Flexible Layouts**: CSS Grid and Flexbox adapt to different screen sizes. + +## 3. Styling and Theming + +### Styling Approach +- **Tailwind CSS**: Utility-first framework for quick and consistent styling. +- **BEM Naming**: When writing custom CSS modules, we follow Block-Element-Modifier conventions. + +### Theming +We keep a central theme file (`theme.ts`) defining colors, spacing, and typography. This ensures consistent branding. + +### Visual Style +- **Style**: Modern flat design with subtle glassmorphism touches on modal backgrounds. +- **Color Palette**: + - Primary: #4F46E5 (indigo) + - Secondary: #10B981 (emerald) + - Accent: #F59E0B (amber) + - Background: #F3F4F6 (light gray) + - Text: #111827 (dark gray) + +### Typography +We use the **Inter** font (free from Google Fonts) for its clean, modern look. Headings are slightly heavier to create visual hierarchy. + +## 4. Component Structure + +We organize components by feature (also known as “feature folders”): + +- `src/components`: Shared UI elements like Button, Modal, Input. +- `src/features/outline`: Components and hooks specific to outline generation (e.g., `OutlineForm`, `OutlinePreview`). +- `src/features/summary`: Components for summary composition. + +### Reusability +- **Atomic Design**: Atoms (Button, Input), Molecules (FormGroup), Organisms (OutlineForm). +- **Single Responsibility**: Each component handles one piece of the UI, making maintenance straightforward. + +## 5. State Management + +We use React’s Context API with useReducer for global state: + +- **Context**: Stores user input, generated outline, and export options. +- **Reducer**: Defines actions like `SET_INPUT`, `GENERATE_OUTLINE`, `RESET_DATA`. +- **Local State**: Minor UI states (like modal open/closed) live in individual components via useState. + +This approach avoids over-complexity while keeping data flow clear. + +## 6. Routing and Navigation + +We use **React Router v6**: + +- `/`: Home page with tool description. +- `/outline`: Outline generation interface. +- `/summary`: Summary composition interface. +- `/settings`: Theme and export preferences. + +Navigation is handled by a top-level `` component. Links update the URL without a full page reload. + +## 7. Performance Optimization + +### Strategies +- **Code Splitting**: Each route’s code is loaded only when the user visits it. +- **Lazy Loading Images**: Thumbnails and illustrations load as they scroll into view. +- **Tree Shaking**: We rely on Vite’s optimized bundling to remove unused code. +- **Minified Assets**: CSS and JS files are minified in production. + +These measures ensure fast load times and a snappy experience. + +## 8. Testing and Quality Assurance + +### Unit Tests +- **Jest** + **React Testing Library** for testing components in isolation. +- We aim for at least 80% coverage on core logic. + +### Integration Tests +- Combine multiple components to test flows (e.g., entering input and seeing an outline preview). + +### End-to-End Tests +- **Cypress**: Simulates user interactions—filling forms, clicking buttons, downloading files. + +### Linting and Formatting +- **ESLint** + **Prettier** enforce code style. +- **Husky** + **lint-staged** run checks before every commit. + +## 9. Conclusion and Overall Frontend Summary + +Our frontend is a modern, scalable React app that balances simplicity with performance. By following clear design principles, a component-based structure, and thorough testing strategies, we ensure a reliable and user-friendly experience. The consistent theming and responsive layouts keep the interface approachable on any device. This setup makes it easy to add new features—like alternative export formats or collaboration tools—without disrupting the core user experience. \ No newline at end of file diff --git a/documentation/project_requirements_document.md b/documentation/project_requirements_document.md new file mode 100644 index 0000000..5d68ec7 --- /dev/null +++ b/documentation/project_requirements_document.md @@ -0,0 +1,86 @@ +# Project Requirements Document (PRD) + +## 1. Project Overview + +This project, **codeguide-starter-lite**, is an intelligent outline and summary generation tool. It transforms a user’s rough description or prompt into a structured project outline, complete with objectives, deliverables, and milestones. By combining predefined templates with adaptive logic, it removes the manual effort of rearranging sections and editing for consistency. + +Built on natural language processing, the engine also extracts context and synthesizes concise summaries, so stakeholders can quickly grasp the scope and rationale behind any project. The tool supports customizable templates, version control for collaboration, and multi-format export, making it easy to integrate generated documents into existing workflows. + +**Key Objectives** +- Automate creation of clear, consistent project outlines and summaries. +- Provide a flexible template framework that adapts to branding or organizational standards. +- Enable real-time collaboration with version history and comment tracking. +- Offer one-click export to PDF, DOCX, and Markdown for seamless integration. + +**Success Criteria** +- Users can generate a full outline and summary from a prompt in under 30 seconds. +- Template customization covers at least 5 common section structures. +- Collaboration features record and display a history of edits reliably. +- Exported documents retain layout fidelity across supported formats. + +## 2. In-Scope vs. Out-of-Scope + +### In-Scope (Version 1) +- Automated outline generation using templates and adaptive logic. +- Contextual analysis and summary composition via NLP. +- Customizable template system (rename sections, reorder, inject custom text). +- Basic collaboration tools: comments on sections and simple version history. +- Multi-format export: PDF, DOCX, and Markdown. +- Interactive user interface with real-time preview and contextual tooltips. + +### Out-of-Scope (Planned for Later Phases) +- Deep AI-based editing recommendations (tone, readability scoring). +- Mobile-native apps (iOS or Android). +- Third-party project management integrations (e.g., Jira, Asana API). +- Rich text collaboration (track changes inside exports). +- Advanced analytics dashboard on usage and document quality metrics. +- On-premises deployment (initial release will be cloud-only). + +## 3. User Flow + +A new user lands on the welcome page and signs up with email/password or OAuth. After logging in, they see a dashboard listing previous projects and a prominent “Create New Outline” button. Clicking that button opens an input modal where they paste or type a project description. The user then chooses from a list of templates (basic, technical, marketing), or starts from a blank template. + +Next, the user configures template settings—renaming sections, setting order, or injecting custom placeholders. They hit “Generate,” and within seconds the engine displays a live preview of the outline and summary side by side. The user can add comments in the sidebar, switch between versions in the history panel, and once satisfied, click “Export” to download a PDF, DOCX, or Markdown file. + +## 4. Core Features + +- **Automated Outline Generation**: Parse user prompts to build section headers, bullet points, and milestones. +- **Contextual Summary Composition**: Use NLP to extract background, goals, and rationale into a concise paragraph. +- **Customizable Template Framework**: Allow users to rename sections, reorder content, and add custom text fields. +- **Collaboration & Version Control**: Enable comments on specific sections, record each save as a version, and view a timeline of changes. +- **Multi-Format Export**: Export finalized outlines to PDF, DOCX, or Markdown with consistent styling. +- **Real-Time Preview & Tooltips**: Show live updates as users type or adjust settings, plus inline help tips. + +## 5. Tech Stack & Tools + +- **Frontend**: Next.js (React + TypeScript), Tailwind CSS for styling, React Query for data fetching, MDX renderer for preview. +- **Backend**: Node.js with Express or NestJS (TypeScript), OpenAI GPT-4 API for NLP tasks, WebSocket (Socket.io) for real-time collaboration. +- **Database**: PostgreSQL (version history, templates, user data), Redis (session caching, rate limiting). +- **Storage & File Conversion**: AWS S3 for exports, Puppeteer or PDFKit for PDF generation, mammoth.js for DOCX. +- **Authentication**: JWT-based, OAuth 2.0 support (Google, GitHub). +- **Dev & IDE Tools**: VS Code with ESLint, Prettier; GitHub Actions for CI/CD; Docker for local environment. + +## 6. Non-Functional Requirements + +- **Performance**: 95th percentile response time under 2 seconds for outline generation (excluding model latency). Preview updates in under 200ms. +- **Scalability**: Support up to 1,000 concurrent users with horizontal backend scaling. +- **Security & Compliance**: HTTPS everywhere, JWT tokens stored securely, data encrypted at rest (AES-256) and in transit (TLS 1.2+). GDPR-friendly data handling. +- **Usability**: WCAG 2.1 AA accessible UI, mobile-responsive design, clear error messaging. +- **Reliability**: 99.9% uptime SLA, automated backups daily, failover database replica. + +## 7. Constraints & Assumptions + +- **GPT-4 API Availability**: Relies on OpenAI’s uptime and rate limits; assume quota can be increased as needed. +- **Template Definitions**: Stored in JSON files; assume users upload or choose only valid JSON schemas. +- **Cloud-Only Deployment**: No on-premises option in initial release. +- **Browser Support**: Latest versions of Chrome, Firefox, Safari, and Edge. + +## 8. Known Issues & Potential Pitfalls + +- **API Rate Limits**: Hitting OpenAI limits could delay generation. Mitigation: queue requests and fallback to simpler templates if overloaded. +- **Version Conflicts**: Simultaneous edits may overwrite each other. Mitigation: lock sections during editing or implement optimistic concurrency control. +- **Formatting Drifts**: Exported DOCX/PDF may differ slightly from preview. Mitigation: include a style guide, run automated visual regression tests. +- **Prompt Ambiguity**: Vague user descriptions yield poor outlines. Mitigation: add guided prompt helper and sample inputs. + +--- +_End of PRD for codeguide-starter-lite._ \ No newline at end of file diff --git a/documentation/security_guideline_document.md b/documentation/security_guideline_document.md new file mode 100644 index 0000000..7efc062 --- /dev/null +++ b/documentation/security_guideline_document.md @@ -0,0 +1,125 @@ +# Security Guidelines for the Automated Outline & Summary Generation Tool + +## 1. Introduction +This document outlines the security requirements, principles, and controls for the Automated Outline & Summary Generation Tool. It ensures the solution is designed, implemented, and operated with security and privacy in mind, from end to end. + +## 2. Scope +Applies to all components and environments supporting: +- Intelligent outline generation +- Contextual analysis and summary composition +- Customizable template framework +- Collaboration and version control +- Multi-format export (PDF, DOCX, Markdown) +- Interactive web interface and APIs + +## 3. Core Security Principles +- **Security by Design**: Integrate security reviews during design, development, testing, and deployment. +- **Least Privilege**: Grant minimal permissions to services, users, and processes. +- **Defense in Depth**: Layer controls (network, application, data) to mitigate single points of failure. +- **Fail Securely**: Default to denial of access on errors; avoid exposing stack traces or sensitive data. +- **Secure Defaults**: Ship with hardened configurations; require explicit opt-in for less-secure features. + +## 4. Authentication & Access Control +1. **User Authentication**: + - Enforce strong password policies (minimum 12 characters, complexity rules, rotation). + - Store passwords using Argon2id or bcrypt with unique salts. + - Offer Multi-Factor Authentication (MFA) for administrators and power users. +2. **Session Management**: + - Issue cryptographically strong, unpredictable session IDs. + - Set idle and absolute session timeouts. + - Secure cookies with `HttpOnly`, `Secure`, and `SameSite=Strict` attributes. +3. **Role-Based Access Control (RBAC)**: + - Define roles (e.g., Reader, Editor, Admin). + - Enforce server-side permission checks on every endpoint. + - Restrict document creation, editing, export, and version history based on roles. +4. **API Security**: + - Require authentication (JWT or API tokens) on all APIs. + - Validate token signature, expiration (`exp`), and issuer claims. + - Rotate and revoke tokens securely. + +## 5. Input Handling & Template Safety +1. **Input Validation**: + - Validate all user inputs (text prompts, template parameters) server-side. + - Enforce length limits and allowable character sets. +2. **Template Sanitization**: + - Use a sandboxed templating engine to prevent server-side code execution. + - Escape or whitelist variables when injecting into HTML or document templates. + - Disallow arbitrary file inclusion or dynamic import in templates. +3. **Prevent Injection Attacks**: + - Use parameterized queries or ORM for metadata storage. + - Escape or encode user inputs before writing to PDF, DOCX, or HTML. +4. **Redirect & Forward Validation**: + - Maintain an allow-list of internal URLs or resource identifiers. + +## 6. Data Protection & Privacy +1. **Encryption in Transit & at Rest**: + - Enforce TLS 1.2+ (HTTPS) for all web and API traffic. + - Encrypt stored documents and metadata (AES-256). +2. **Sensitive Data Handling**: + - Do not log raw user content; mask or hash identifiers in logs. + - Comply with GDPR/CCPA for any PII in templates or user profiles. +3. **Secrets Management**: + - Store API keys, database credentials, and TLS certificates in a vault (e.g., AWS Secrets Manager). + - Avoid hard-coding secrets in code or config files. + +## 7. Collaboration & Version Control Security +1. **Access Auditing**: + - Log document access, edits, and version events with user ID and timestamp. +2. **Change Control**: + - Require review/approval workflows for template library updates. +3. **Data Integrity**: + - Calculate and store checksums (SHA-256) for each version to detect tampering. + +## 8. Multi-Format Export Hygiene +1. **Output Encoding**: + - Ensure PDF/DOCX generation libraries are patched and sandboxed. + - Strip or encode any HTML/CSS injected by users. +2. **File Permissions & Storage**: + - Store exported files outside the web root. + - Assign restrictive permissions; expire or clean up temporary files. + +## 9. Web Application Security Controls +- **CSRF Protection**: Implement anti-CSRF tokens on all state-changing forms and AJAX calls. +- **Security Headers**: + - Content-Security-Policy: restrict script sources and inline usage. + - X-Frame-Options: SAMEORIGIN. + - X-Content-Type-Options: nosniff. + - Referrer-Policy: no-referrer-when-downgrade. + - Strict-Transport-Security: max-age=31536000; includeSubDomains. +- **CORS**: Allow only trusted origins; restrict allowed methods and headers. +- **Clickjacking Protection**: Use frame-ancestors directive in CSP. + +## 10. Infrastructure & Configuration +- **Hardened Hosts**: Disable unused services; apply OS-level security benchmarks (CIS). +- **Network Controls**: Limit inbound ports; use firewalls and network segmentation. +- **TLS Configuration**: Disable SSLv3/TLS1.0-1.1; prefer strong cipher suites (ECDHE). +- **Configuration Management**: Store configurations in version control; encrypt secrets. +- **Patch Management**: Automate OS and dependency updates; monitor for critical CVEs. + +## 11. Dependency & Supply Chain Security +- **Secure Dependencies**: + - Vet libraries before inclusion; prefer actively maintained packages. + - Use lockfiles (package-lock.json, Pipfile.lock) to guarantee reproducible builds. +- **Vulnerability Scanning**: + - Integrate SCA tools in CI/CD to detect known CVEs. +- **Minimal Footprint**: + - Remove unused features and dev-only dependencies in production builds. + +## 12. Monitoring, Logging & Incident Response +- **Centralized Logging**: Aggregate logs in a secure SIEM; protect log integrity. +- **Alerting & Monitoring**: + - Detect anomalous behavior (e.g., brute-force attempts, unusual export volume). +- **Incident Playbook**: + - Define roles, communication, and containment steps in case of a breach. + +## 13. Secure CI/CD Practices +- **Pipeline Hardening**: + - Enforce least privilege for build agents. + - Sign build artifacts; verify signatures before deployment. +- **Automated Tests**: + - Include static analysis (SAST) and dynamic security tests (DAST). +- **Secrets in Pipelines**: + - Inject secrets at runtime from a vault; do not store in CI logs. + +--- +By adhering to these guidelines, we ensure the Automated Outline & Summary Generation Tool remains robust, resilient, and trustworthy across its entire lifecycle. Continuous review and adjustment of these controls are mandatory as the threat landscape and feature set evolve. \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index e9ffa30..ba2a654 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,67 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + // Enable standalone output for Docker deployment + output: 'standalone', + + // Experimental features for better performance + experimental: { + // Enable turbopack for faster builds (optional) + turbo: { + rules: { + '*.svg': { + loaders: ['@svgr/webpack'], + as: '*.js', + }, + }, + }, + }, + + // Image optimization for production + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'avatars.githubusercontent.com', + }, + { + protocol: 'https', + hostname: 'lh3.googleusercontent.com', + }, + // Add your S3 bucket domain when configured + // { + // protocol: 'https', + // hostname: 'your-bucket.s3.amazonaws.com', + // }, + ], + }, + + // Security headers + async headers() { + return [ + { + source: '/(.*)', + headers: [ + { + key: 'X-Content-Type-Options', + value: 'nosniff', + }, + { + key: 'X-Frame-Options', + value: 'DENY', + }, + { + key: 'X-XSS-Protection', + value: '1; mode=block', + }, + { + key: 'Referrer-Policy', + value: 'strict-origin-when-cross-origin', + }, + ], + }, + ]; + }, }; export default nextConfig; diff --git a/package.json b/package.json index 10313d1..834e261 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,15 @@ "dev": "next dev --turbopack", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "format": "prettier --write .", + "format:check": "prettier --check .", + "type-check": "tsc --noEmit", + "docker:dev": "docker-compose up -d", + "docker:down": "docker-compose down", + "db:migrate": "echo 'Run Supabase migrations manually or set up migration script'", + "db:seed": "echo 'Database seeded with default templates via migration'", + "test": "echo 'Tests will be added in future iterations'" }, "dependencies": { "@ai-sdk/anthropic": "^1.2.12", @@ -60,7 +68,13 @@ "svix": "^1.69.0", "tailwind-merge": "^3.3.1", "vaul": "^1.1.2", - "zod": "^3.25.76" + "zod": "^3.25.76", + "@aws-sdk/client-s3": "^3.723.0", + "@aws-sdk/s3-request-presigner": "^3.723.0", + "ioredis": "^5.4.2", + "jsonwebtoken": "^9.0.2", + "bcryptjs": "^2.4.3", + "rate-limiter-flexible": "^5.2.1" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -72,6 +86,9 @@ "eslint-config-next": "15.4.4", "tailwindcss": "^4", "tw-animate-css": "^1.3.6", - "typescript": "^5" + "typescript": "^5", + "@types/jsonwebtoken": "^9.0.7", + "@types/bcryptjs": "^2.4.6", + "prettier": "^3.4.2" } } diff --git a/src/app/api/ai/generate/route.ts b/src/app/api/ai/generate/route.ts new file mode 100644 index 0000000..5d95820 --- /dev/null +++ b/src/app/api/ai/generate/route.ts @@ -0,0 +1,166 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from '@clerk/nextjs/server'; +import { openai } from '@ai-sdk/openai'; +import { generateObject } from 'ai'; +import { z } from 'zod'; +import { checkRateLimit, getUserRateLimitKey } from '@/lib/rate-limit'; +import { logAIGeneration, updateAIGeneration } from '@/lib/database'; + +// Schema for AI-generated outline +const OutlineSchema = z.object({ + sections: z.array( + z.object({ + id: z.string(), + title: z.string(), + content: z.string(), + subsections: z.array( + z.object({ + id: z.string(), + title: z.string(), + content: z.string(), + }) + ).optional(), + }) + ), + summary: z.string(), + keywords: z.array(z.string()), +}); + +export async function POST(request: NextRequest) { + let aiGenerationId: string | null = null; + + try { + const { userId } = await auth(); + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting for AI generation (stricter limits) + const rateLimitResult = await checkRateLimit( + getUserRateLimitKey(userId, 'ai'), + 'aiGeneration' + ); + + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'AI generation rate limit exceeded. Please try again later.' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '10', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + if (!process.env.OPENAI_API_KEY) { + return NextResponse.json( + { error: 'OpenAI API key not configured' }, + { status: 500 } + ); + } + + const body = await request.json(); + const { prompt, project_id, template_context } = body; + + if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) { + return NextResponse.json( + { error: 'Valid prompt is required' }, + { status: 400 } + ); + } + + // Log the AI generation request + const aiGeneration = await logAIGeneration({ + project_id: project_id || null, + user_id: userId, + prompt: prompt.trim(), + model_used: 'gpt-4o', + status: 'pending', + }); + + aiGenerationId = aiGeneration.id; + + const startTime = Date.now(); + + // Create a comprehensive prompt for outline generation + const systemPrompt = `You are an expert content strategist and outline generator. Generate a structured outline based on the user's prompt. + + ${template_context ? `Context: This outline is for a ${template_context.category || 'general'} document using template "${template_context.name}".` : ''} + + Guidelines: + - Create logical, well-structured sections + - Include detailed content suggestions for each section + - Provide actionable subsections where appropriate + - Include relevant keywords for searchability + - Ensure the outline flows logically from introduction to conclusion + - Adapt the structure to match the user's specific needs and context`; + + // Generate structured outline using AI + const result = await generateObject({ + model: openai('gpt-4o'), + schema: OutlineSchema, + system: systemPrompt, + prompt: prompt.trim(), + temperature: 0.7, + }); + + const endTime = Date.now(); + const processingTime = endTime - startTime; + + // Update the AI generation log with results + await updateAIGeneration(aiGenerationId, { + response: result.object, + processing_time_ms: processingTime, + tokens_used: result.usage?.totalTokens || 0, + status: 'completed', + }); + + return NextResponse.json({ + success: true, + data: result.object, + processing_time_ms: processingTime, + tokens_used: result.usage?.totalTokens || 0, + }); + + } catch (error: any) { + console.error('Error generating AI outline:', error); + + // Update AI generation log with error + if (aiGenerationId) { + await updateAIGeneration(aiGenerationId, { + status: 'failed', + error_message: error.message || 'Unknown error occurred', + }); + } + + // Handle specific error types + if (error.name === 'AI_APICallError' && error.status === 429) { + return NextResponse.json( + { error: 'OpenAI API rate limit exceeded. Please try again later.' }, + { status: 429 } + ); + } + + if (error.name === 'AI_APICallError' && error.status === 401) { + return NextResponse.json( + { error: 'OpenAI API authentication failed' }, + { status: 500 } + ); + } + + return NextResponse.json( + { + error: 'Failed to generate outline. Please try again.', + details: process.env.NODE_ENV === 'development' ? error.message : undefined + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/projects/[id]/route.ts b/src/app/api/projects/[id]/route.ts new file mode 100644 index 0000000..83e9e2d --- /dev/null +++ b/src/app/api/projects/[id]/route.ts @@ -0,0 +1,186 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from '@clerk/nextjs/server'; +import { getProject, updateProject, deleteProject, canAccessProject } from '@/lib/database'; +import { checkRateLimit, getUserRateLimitKey } from '@/lib/rate-limit'; +import { UpdateProjectSchema, validateProjectContent } from '@/lib/template-validation'; + +interface RouteParams { + params: { + id: string; + }; +} + +export async function GET(request: NextRequest, { params }: RouteParams) { + try { + const { userId } = await auth(); + const { id } = params; + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + // Check access permissions + if (!(await canAccessProject(id, userId))) { + return NextResponse.json( + { error: 'Project not found' }, + { status: 404 } + ); + } + + const project = await getProject(id); + return NextResponse.json(project); + } catch (error) { + console.error('Error fetching project:', error); + return NextResponse.json( + { error: 'Failed to fetch project' }, + { status: 500 } + ); + } +} + +export async function PUT(request: NextRequest, { params }: RouteParams) { + try { + const { userId } = await auth(); + const { id } = params; + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + // Check access permissions + if (!(await canAccessProject(id, userId))) { + return NextResponse.json( + { error: 'Project not found' }, + { status: 404 } + ); + } + + const body = await request.json(); + + // Validate input + const validation = UpdateProjectSchema.safeParse(body); + if (!validation.success) { + return NextResponse.json( + { error: 'Invalid input', details: validation.error.errors }, + { status: 400 } + ); + } + + const updates = validation.data; + + // Get existing project to validate against template + const existingProject = await getProject(id); + + // If content is being updated and project has a template, validate content + if (updates.content && existingProject.template) { + const contentValidation = validateProjectContent(updates.content, existingProject.template); + if (!contentValidation.valid) { + return NextResponse.json( + { + error: 'Invalid project content', + details: contentValidation.errors, + warnings: contentValidation.warnings + }, + { status: 400 } + ); + } + } + + const updatedProject = await updateProject(id, updates); + + return NextResponse.json(updatedProject); + } catch (error) { + console.error('Error updating project:', error); + return NextResponse.json( + { error: 'Failed to update project' }, + { status: 500 } + ); + } +} + +export async function DELETE(request: NextRequest, { params }: RouteParams) { + try { + const { userId } = await auth(); + const { id } = params; + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + // Check if project exists and user owns it + const project = await getProject(id); + if (!project || project.user_id !== userId) { + return NextResponse.json( + { error: 'Project not found' }, + { status: 404 } + ); + } + + await deleteProject(id); + + return NextResponse.json({ message: 'Project deleted successfully' }); + } catch (error) { + console.error('Error deleting project:', error); + return NextResponse.json( + { error: 'Failed to delete project' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/projects/[id]/versions/route.ts b/src/app/api/projects/[id]/versions/route.ts new file mode 100644 index 0000000..26b32b4 --- /dev/null +++ b/src/app/api/projects/[id]/versions/route.ts @@ -0,0 +1,123 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from '@clerk/nextjs/server'; +import { getProjectVersions, createProjectVersion, canAccessProject, getProject } from '@/lib/database'; +import { checkRateLimit, getUserRateLimitKey } from '@/lib/rate-limit'; + +interface RouteParams { + params: { + id: string; + }; +} + +export async function GET(request: NextRequest, { params }: RouteParams) { + try { + const { userId } = await auth(); + const { id } = params; + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + // Check access permissions + if (!(await canAccessProject(id, userId))) { + return NextResponse.json( + { error: 'Project not found' }, + { status: 404 } + ); + } + + const versions = await getProjectVersions(id); + return NextResponse.json(versions); + } catch (error) { + console.error('Error fetching project versions:', error); + return NextResponse.json( + { error: 'Failed to fetch project versions' }, + { status: 500 } + ); + } +} + +export async function POST(request: NextRequest, { params }: RouteParams) { + try { + const { userId } = await auth(); + const { id } = params; + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + // Check access permissions (must be able to edit) + const project = await getProject(id); + if (!project || project.user_id !== userId) { + return NextResponse.json( + { error: 'Permission denied' }, + { status: 403 } + ); + } + + const body = await request.json(); + const { change_summary } = body; + + // Get current project content and settings + const currentProject = await getProject(id); + + // Get existing versions to determine next version number + const existingVersions = await getProjectVersions(id); + const nextVersionNumber = Math.max(...existingVersions.map(v => v.version_number), 0) + 1; + + const newVersion = await createProjectVersion({ + project_id: id, + version_number: nextVersionNumber, + content: currentProject.content, + settings: currentProject.settings, + change_summary: change_summary || `Version ${nextVersionNumber}`, + created_by: userId, + }); + + return NextResponse.json(newVersion, { status: 201 }); + } catch (error) { + console.error('Error creating project version:', error); + return NextResponse.json( + { error: 'Failed to create project version' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/projects/route.ts b/src/app/api/projects/route.ts new file mode 100644 index 0000000..75db0e4 --- /dev/null +++ b/src/app/api/projects/route.ts @@ -0,0 +1,121 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from '@clerk/nextjs/server'; +import { getProjects, createProject, getTemplate } from '@/lib/database'; +import { checkRateLimit, getUserRateLimitKey } from '@/lib/rate-limit'; +import { CreateProjectSchema, getDefaultProjectContent } from '@/lib/template-validation'; + +export async function GET(request: NextRequest) { + try { + const { userId } = await auth(); + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + const projects = await getProjects(userId); + + return NextResponse.json(projects); + } catch (error) { + console.error('Error fetching projects:', error); + return NextResponse.json( + { error: 'Failed to fetch projects' }, + { status: 500 } + ); + } +} + +export async function POST(request: NextRequest) { + try { + const { userId } = await auth(); + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + const body = await request.json(); + + // Validate input + const validation = CreateProjectSchema.safeParse(body); + if (!validation.success) { + return NextResponse.json( + { error: 'Invalid input', details: validation.error.errors }, + { status: 400 } + ); + } + + const { title, description, template_id, content, settings } = validation.data; + + // If template_id is provided, get the template and use its default content + let projectContent = content; + if (template_id) { + try { + const template = await getTemplate(template_id); + if (template) { + // If no content provided, use template defaults + if (!content || Object.keys(content.sections || {}).length === 0) { + projectContent = getDefaultProjectContent(template); + } + } + } catch (error) { + // Template not found or not accessible, continue with provided content + console.warn('Template not found or not accessible:', template_id); + } + } + + const project = await createProject({ + title, + description: description || null, + template_id: template_id || null, + content: projectContent, + settings: settings, + status: 'draft', + user_id: userId, + }); + + return NextResponse.json(project, { status: 201 }); + } catch (error) { + console.error('Error creating project:', error); + return NextResponse.json( + { error: 'Failed to create project' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/templates/[id]/clone/route.ts b/src/app/api/templates/[id]/clone/route.ts new file mode 100644 index 0000000..a4049c7 --- /dev/null +++ b/src/app/api/templates/[id]/clone/route.ts @@ -0,0 +1,94 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from '@clerk/nextjs/server'; +import { getTemplate, createTemplate, canAccessTemplate } from '@/lib/database'; +import { checkRateLimit, getUserRateLimitKey } from '@/lib/rate-limit'; +import { TemplateService } from '@/lib/template-service'; +import { z } from 'zod'; + +interface RouteParams { + params: { + id: string; + }; +} + +const CloneRequestSchema = z.object({ + name: z.string().min(1).max(255).optional(), + make_public: z.boolean().default(false), +}); + +export async function POST(request: NextRequest, { params }: RouteParams) { + try { + const { userId } = await auth(); + const { id } = params; + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + // Check if user can access the original template + if (!(await canAccessTemplate(id, userId))) { + return NextResponse.json( + { error: 'Template not found' }, + { status: 404 } + ); + } + + const originalTemplate = await getTemplate(id); + + const body = await request.json(); + const validation = CloneRequestSchema.safeParse(body); + if (!validation.success) { + return NextResponse.json( + { error: 'Invalid input', details: validation.error.errors }, + { status: 400 } + ); + } + + const { name, make_public } = validation.data; + + // Clone the template + const clonedTemplateData = await TemplateService.cloneTemplate( + originalTemplate, + userId, + name + ); + + // Set public status + clonedTemplateData.is_public = make_public; + + // Create the cloned template + const clonedTemplate = await createTemplate(clonedTemplateData); + + return NextResponse.json({ + message: 'Template cloned successfully', + template: clonedTemplate, + original_template_id: id, + }, { status: 201 }); + + } catch (error) { + console.error('Error cloning template:', error); + return NextResponse.json( + { error: 'Failed to clone template' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/templates/[id]/route.ts b/src/app/api/templates/[id]/route.ts new file mode 100644 index 0000000..77ca76c --- /dev/null +++ b/src/app/api/templates/[id]/route.ts @@ -0,0 +1,192 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from '@clerk/nextjs/server'; +import { getTemplate, updateTemplate, deleteTemplate, canAccessTemplate } from '@/lib/database'; +import { checkRateLimit, getUserRateLimitKey } from '@/lib/rate-limit'; +import { UpdateTemplateSchema, validateTemplateStructure } from '@/lib/template-validation'; + +interface RouteParams { + params: { + id: string; + }; +} + +export async function GET(request: NextRequest, { params }: RouteParams) { + try { + const { userId } = await auth(); + const { id } = params; + + // Rate limiting + if (userId) { + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + } + + const template = await getTemplate(id); + + // Check if user can access this template + if (!template.is_public && !template.is_system && template.created_by !== userId) { + return NextResponse.json( + { error: 'Template not found' }, + { status: 404 } + ); + } + + return NextResponse.json(template); + } catch (error) { + console.error('Error fetching template:', error); + return NextResponse.json( + { error: 'Failed to fetch template' }, + { status: 500 } + ); + } +} + +export async function PUT(request: NextRequest, { params }: RouteParams) { + try { + const { userId } = await auth(); + const { id } = params; + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + // Check if template exists and user can modify it + const existingTemplate = await getTemplate(id); + if (!existingTemplate) { + return NextResponse.json( + { error: 'Template not found' }, + { status: 404 } + ); + } + + // Only allow template owner to update (not system templates) + if (existingTemplate.is_system || existingTemplate.created_by !== userId) { + return NextResponse.json( + { error: 'Permission denied' }, + { status: 403 } + ); + } + + const body = await request.json(); + + // Validate input + const validation = UpdateTemplateSchema.safeParse(body); + if (!validation.success) { + return NextResponse.json( + { error: 'Invalid input', details: validation.error.errors }, + { status: 400 } + ); + } + + const updates = validation.data; + + // Validate template structure if provided + if (updates.structure && updates.default_sections) { + const structureValidation = validateTemplateStructure(updates.structure, updates.default_sections); + if (!structureValidation.valid) { + return NextResponse.json( + { error: 'Invalid template structure', details: structureValidation.errors }, + { status: 400 } + ); + } + } + + const updatedTemplate = await updateTemplate(id, updates); + + return NextResponse.json(updatedTemplate); + } catch (error) { + console.error('Error updating template:', error); + return NextResponse.json( + { error: 'Failed to update template' }, + { status: 500 } + ); + } +} + +export async function DELETE(request: NextRequest, { params }: RouteParams) { + try { + const { userId } = await auth(); + const { id } = params; + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + // Check if template exists and user can delete it + const template = await getTemplate(id); + if (!template) { + return NextResponse.json( + { error: 'Template not found' }, + { status: 404 } + ); + } + + // Only allow template owner to delete (not system templates) + if (template.is_system || template.created_by !== userId) { + return NextResponse.json( + { error: 'Permission denied' }, + { status: 403 } + ); + } + + await deleteTemplate(id); + + return NextResponse.json({ message: 'Template deleted successfully' }); + } catch (error) { + console.error('Error deleting template:', error); + return NextResponse.json( + { error: 'Failed to delete template' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/templates/[id]/stats/route.ts b/src/app/api/templates/[id]/stats/route.ts new file mode 100644 index 0000000..e4a4780 --- /dev/null +++ b/src/app/api/templates/[id]/stats/route.ts @@ -0,0 +1,93 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from '@clerk/nextjs/server'; +import { getTemplate, getProjects, canAccessTemplate } from '@/lib/database'; +import { checkRateLimit, getUserRateLimitKey } from '@/lib/rate-limit'; +import { TemplateService } from '@/lib/template-service'; + +interface RouteParams { + params: { + id: string; + }; +} + +export async function GET(request: NextRequest, { params }: RouteParams) { + try { + const { userId } = await auth(); + const { id } = params; + + // Rate limiting (allow anonymous access for public templates) + if (userId) { + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + } + + // Check if user can access the template + if (!(await canAccessTemplate(id, userId))) { + return NextResponse.json( + { error: 'Template not found' }, + { status: 404 } + ); + } + + const template = await getTemplate(id); + + // For privacy, only show detailed stats to template owner or for public templates + const canSeeDetailedStats = template.created_by === userId || template.is_public || template.is_system; + + if (!canSeeDetailedStats) { + return NextResponse.json({ + template_id: id, + template_name: template.name, + public_stats_only: true, + message: 'Detailed statistics are only available to template owners or for public templates', + }); + } + + // Get all projects that use this template + // Note: This would need optimization for large datasets + const allProjects = userId ? await getProjects(userId) : []; + + // Get template statistics + const stats = await TemplateService.getTemplateStats(id, allProjects); + + // Additional metadata + const response = { + template_id: id, + template_name: template.name, + template_category: template.category, + is_system: template.is_system, + is_public: template.is_public, + created_at: template.created_at, + ...stats, + sections_info: { + total_sections: template.default_sections.length, + required_sections: template.default_sections.filter(s => s.required).length, + section_types: template.default_sections.reduce((acc, section) => { + acc[section.type] = (acc[section.type] || 0) + 1; + return acc; + }, {} as Record), + }, + }; + + return NextResponse.json(response); + + } catch (error) { + console.error('Error fetching template statistics:', error); + return NextResponse.json( + { error: 'Failed to fetch template statistics' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/templates/categories/route.ts b/src/app/api/templates/categories/route.ts new file mode 100644 index 0000000..6430d64 --- /dev/null +++ b/src/app/api/templates/categories/route.ts @@ -0,0 +1,62 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { TemplateCategories } from '@/lib/template-validation'; +import { supabase } from '@/lib/database'; + +export async function GET(request: NextRequest) { + try { + // Get template counts per category + const { data: categoryCounts, error } = await supabase + .from('templates') + .select('category') + .or('is_public.eq.true,is_system.eq.true'); + + if (error) { + console.error('Error fetching category counts:', error); + // Return categories without counts if query fails + const categories = TemplateCategories.map(category => ({ + id: category, + name: category.charAt(0).toUpperCase() + category.slice(1), + count: 0, + })); + + return NextResponse.json(categories); + } + + // Count templates per category + const countMap = categoryCounts.reduce((acc, template) => { + acc[template.category] = (acc[template.category] || 0) + 1; + return acc; + }, {} as Record); + + // Format response with counts + const categories = TemplateCategories.map(category => ({ + id: category, + name: category.charAt(0).toUpperCase() + category.slice(1), + count: countMap[category] || 0, + description: getCategoryDescription(category), + })); + + return NextResponse.json(categories); + } catch (error) { + console.error('Error fetching template categories:', error); + return NextResponse.json( + { error: 'Failed to fetch template categories' }, + { status: 500 } + ); + } +} + +function getCategoryDescription(category: string): string { + const descriptions = { + general: 'General purpose templates for various use cases', + technical: 'Technical documentation and specifications', + marketing: 'Marketing materials and campaign briefs', + business: 'Business plans and proposals', + academic: 'Academic papers and research documents', + creative: 'Creative briefs and artistic projects', + legal: 'Legal documents and contracts', + personal: 'Personal projects and documentation', + }; + + return descriptions[category as keyof typeof descriptions] || 'Various templates'; +} \ No newline at end of file diff --git a/src/app/api/templates/route.ts b/src/app/api/templates/route.ts new file mode 100644 index 0000000..5e0cfd1 --- /dev/null +++ b/src/app/api/templates/route.ts @@ -0,0 +1,109 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { auth } from '@clerk/nextjs/server'; +import { getTemplates, createTemplate } from '@/lib/database'; +import { checkRateLimit, getUserRateLimitKey } from '@/lib/rate-limit'; +import { CreateTemplateSchema, validateTemplateStructure } from '@/lib/template-validation'; + +export async function GET(request: NextRequest) { + try { + const { userId } = await auth(); + + // Rate limiting + if (userId) { + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + } + + const templates = await getTemplates(userId || undefined); + + return NextResponse.json(templates); + } catch (error) { + console.error('Error fetching templates:', error); + return NextResponse.json( + { error: 'Failed to fetch templates' }, + { status: 500 } + ); + } +} + +export async function POST(request: NextRequest) { + try { + const { userId } = await auth(); + + if (!userId) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Rate limiting + const rateLimitResult = await checkRateLimit(getUserRateLimitKey(userId), 'api'); + if (!rateLimitResult.allowed) { + return NextResponse.json( + { error: 'Rate limit exceeded' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': '100', + 'X-RateLimit-Remaining': rateLimitResult.remainingPoints?.toString() || '0', + 'X-RateLimit-Reset': new Date(Date.now() + (rateLimitResult.msBeforeNext || 0)).toISOString(), + } + } + ); + } + + const body = await request.json(); + + // Validate input using schema + const validation = CreateTemplateSchema.safeParse(body); + if (!validation.success) { + return NextResponse.json( + { error: 'Invalid input', details: validation.error.errors }, + { status: 400 } + ); + } + + const { name, description, category, structure, default_sections, is_public } = validation.data; + + // Validate template structure + const structureValidation = validateTemplateStructure(structure, default_sections); + if (!structureValidation.valid) { + return NextResponse.json( + { error: 'Invalid template structure', details: structureValidation.errors }, + { status: 400 } + ); + } + + const template = await createTemplate({ + name, + description: description || null, + category, + structure, + default_sections, + is_system: false, + is_public: is_public || false, + created_by: userId, + }); + + return NextResponse.json(template, { status: 201 }); + } catch (error) { + console.error('Error creating template:', error); + return NextResponse.json( + { error: 'Failed to create template' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/lib/database.ts b/src/lib/database.ts new file mode 100644 index 0000000..049b599 --- /dev/null +++ b/src/lib/database.ts @@ -0,0 +1,376 @@ +import { createClient } from '@supabase/supabase-js'; + +// Initialize Supabase client +export const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! +); + +// Database types +export interface Template { + id: string; + name: string; + description?: string; + category: string; + structure: any; + default_sections: any[]; + is_system: boolean; + is_public: boolean; + created_by?: string; + created_at: string; + updated_at: string; +} + +export interface Project { + id: string; + title: string; + description?: string; + template_id?: string; + content: any; + settings: any; + status: 'draft' | 'published' | 'archived'; + user_id: string; + created_at: string; + updated_at: string; + template?: Template; +} + +export interface ProjectVersion { + id: string; + project_id: string; + version_number: number; + content: any; + settings: any; + change_summary?: string; + created_by: string; + created_at: string; +} + +export interface ProjectCollaborator { + id: string; + project_id: string; + user_id: string; + role: 'owner' | 'editor' | 'viewer'; + invited_by: string; + created_at: string; +} + +export interface ProjectComment { + id: string; + project_id: string; + section_id?: string; + content: string; + user_id: string; + parent_comment_id?: string; + resolved: boolean; + created_at: string; + updated_at: string; +} + +export interface AIGeneration { + id: string; + project_id?: string; + user_id: string; + prompt: string; + response?: any; + model_used?: string; + tokens_used?: number; + processing_time_ms?: number; + status: 'pending' | 'completed' | 'failed'; + error_message?: string; + created_at: string; +} + +export interface ProjectFile { + id: string; + project_id: string; + filename: string; + file_size: number; + file_type: string; + s3_key: string; + s3_url: string; + uploaded_by: string; + created_at: string; +} + +// Template functions +export async function getTemplates(userId?: string) { + const { data, error } = await supabase + .from('templates') + .select('*') + .or(`is_public.eq.true,is_system.eq.true${userId ? `,created_by.eq.${userId}` : ''}`) + .order('created_at', { ascending: false }); + + if (error) throw error; + return data as Template[]; +} + +export async function getTemplate(id: string) { + const { data, error } = await supabase + .from('templates') + .select('*') + .eq('id', id) + .single(); + + if (error) throw error; + return data as Template; +} + +export async function createTemplate(template: Omit) { + const { data, error } = await supabase + .from('templates') + .insert(template) + .select() + .single(); + + if (error) throw error; + return data as Template; +} + +export async function updateTemplate(id: string, updates: Partial