Add comprehensive Docker deployment and file upload functionality

Features Added:
• Docker containerization with multi-stage Python 3.12 build
• Caddy reverse proxy integration with automatic SSL
• File upload interface for .claude.json imports with preview
• Comprehensive hook system with 39+ hook types across 9 categories
• Complete documentation system with Docker and import guides

Technical Improvements:
• Enhanced database models with hook tracking capabilities
• Robust file validation and error handling for uploads
• Production-ready Docker compose configuration
• Health checks and resource limits for containers
• Database initialization scripts for containerized deployments

Documentation:
• Docker Deployment Guide with troubleshooting
• Data Import Guide with step-by-step instructions
• Updated Getting Started guide with new features
• Enhanced documentation index with responsive grid layout

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ryan Malloy 2025-08-11 08:02:09 -06:00
parent bec1606c86
commit 50c80596d0
36 changed files with 4334 additions and 172 deletions

View file

@ -11,7 +11,7 @@ from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional, Any
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
@ -40,6 +40,19 @@ class ClaudeJsonImporter:
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in Claude configuration file: {e}")
return await self._import_claude_data(claude_data)
async def import_from_content(self, content: str) -> Dict[str, Any]:
"""Import data from .claude.json file content."""
try:
claude_data = json.loads(content)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in Claude configuration file: {e}")
return await self._import_claude_data(claude_data)
async def _import_claude_data(self, claude_data: Dict[str, Any]) -> Dict[str, Any]:
"""Common import logic for both file and content imports."""
results = {
"projects_imported": 0,
"sessions_estimated": 0,
@ -337,6 +350,64 @@ async def import_claude_json(
)
@router.post("/import/claude-json/upload")
async def import_claude_json_upload(
file: UploadFile = File(...),
db: AsyncSession = Depends(get_db)
):
"""
Import data from uploaded .claude.json file.
"""
# Validate file type
if file.filename and not file.filename.endswith('.json'):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="File must be a JSON file (.json)"
)
# Check file size (limit to 10MB)
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
content = await file.read()
if len(content) > MAX_FILE_SIZE:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="File too large. Maximum size is 10MB."
)
try:
# Decode file content
file_content = content.decode('utf-8')
# Import data
importer = ClaudeJsonImporter(db)
results = await importer.import_from_content(file_content)
return {
"success": True,
"message": "Import completed successfully",
"file_name": file.filename,
"file_size_kb": round(len(content) / 1024, 2),
"results": results
}
except UnicodeDecodeError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="File must be UTF-8 encoded"
)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid file format: {e}"
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Import failed: {e}"
)
@router.get("/import/claude-json/preview")
async def preview_claude_json_import(
file_path: Optional[str] = None
@ -379,6 +450,73 @@ async def preview_claude_json_import(
"history_entries": 0
}
# Count total history entries across all projects
if "projects" in claude_data:
total_history = sum(
len(proj.get("history", []))
for proj in claude_data["projects"].values()
)
preview["history_entries"] = total_history
return preview
@router.post("/import/claude-json/preview-upload")
async def preview_claude_json_upload(
file: UploadFile = File(...)
):
"""
Preview what would be imported from uploaded .claude.json file without actually importing.
"""
# Validate file type
if file.filename and not file.filename.endswith('.json'):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="File must be a JSON file (.json)"
)
# Check file size (limit to 10MB)
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
content = await file.read()
if len(content) > MAX_FILE_SIZE:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="File too large. Maximum size is 10MB."
)
try:
# Decode and parse file content
file_content = content.decode('utf-8')
claude_data = json.loads(file_content)
except UnicodeDecodeError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="File must be UTF-8 encoded"
)
except json.JSONDecodeError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid JSON in Claude configuration file: {e}"
)
preview = {
"file_name": file.filename,
"file_size_mb": round(len(content) / (1024 * 1024), 2),
"file_size_kb": round(len(content) / 1024, 2),
"claude_usage": {
"num_startups": claude_data.get("numStartups", 0),
"first_start_time": claude_data.get("firstStartTime"),
"prompt_queue_use_count": claude_data.get("promptQueueUseCount", 0)
},
"projects": {
"total_count": len(claude_data.get("projects", {})),
"paths": list(claude_data.get("projects", {}).keys())[:10], # Show first 10
"has_more": len(claude_data.get("projects", {})) > 10
},
"history_entries": 0
}
# Count total history entries across all projects
if "projects" in claude_data:
total_history = sum(