Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Git
.git
.gitignore

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
*.egg-info/
dist/
build/

# IDE
.idea/
.vscode/
*.swp
*.swo

# Logs
logs/
*.log

# OS
.DS_Store
Thumbs.db
42 changes: 42 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Environment Configuration
# Valid values: development, production, staging, testing
ENV=development
UVICORN_PORT=8000

# API Keys
CRYPTOPANIC_API_URL=https://cryptopanic.com/api/v1
CRYPTOPANIC_API_KEY=your_cryptopanic_api_key
OPENAI_API_KEY=your_openai_api_key
GEMINI_API_KEY=your_gemini_api_key
BITQUERY_API_KEY=your_bitquery_api_key

# Life Simulator API Keys
OPENROUTER_API_KEY=your_openrouter_api_key_here
OPENWEATHER_API_KEY=your_openweather_api_key_here
GOOGLE_MAPS_API_KEY=your_google_maps_api_key_here

# Logging Configuration
LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR, CRITICAL
LOG_FILE=logs/app.log # Optional: Comment out to log to console only
#LOG_FORMAT=%(asctime)s - %(name)s - %(levelname)s - %(message)s
#LOG_DATE_FORMAT=%Y-%m-%d %H:%M:%S
#LOG_MAX_SIZE=10485760 # 10 MB
#LOG_BACKUP_COUNT=5


# LLM Settings
LLM_TIMEOUT=10 # seconds
LLM_MAX_RETRIES=2
LLM_MODEL=gpt-4o-mini # or other OpenAI model
LLM_MAX_TOKENS=4000

# Firecrawl Settings
FIRECRAWL_API_KEY=your_firecrawl_api_key
# FIRECRAWL_BASE_URL=optional_custom_firecrawl_url # Uncomment if using a custom Firecrawl instance

# Supabase Configuration
SUPABASE_URL=your_supabase_project_url
SUPABASE_SERVICE_KEY=your_supabase_service_role_key

# Admin Configuration
ADMIN_SECRET=your_admin_secret
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
venv/
.env
__pycache__/
.DS_Store
app/__pycache__/
app/services/__pycache__/
.venv/
.pytest_cache/
42 changes: 42 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Use Python 3.11 slim image
FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
ENVIRONMENT=production

# Install system dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*

# Copy requirements first to leverage Docker cache
COPY requirements.txt .

# Set environment variables
ENV PLAYWRIGHT_BROWSERS_PATH=/app/ms-playwright

# Install Playwright browsers and dependencies
RUN pip install playwright
RUN playwright install --with-deps chromium

# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application
COPY . .

# Create logs directory
RUN mkdir -p logs

# Expose port
EXPOSE 8000

# Command to run the application
CMD ["python", "run.py"]
Empty file added app/__init__.py
Empty file.
91 changes: 91 additions & 0 deletions app/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from pydantic_settings import BaseSettings
from typing import Optional
from app.utils.enums import Environment
import logging

class Settings(BaseSettings):
# Environment
ENV: Environment = Environment.DEVELOPMENT
UVICORN_PORT: int = 8000

# admin key
ADMIN_SECRET: str

# API Settings
CRYPTOPANIC_API_URL: str = "https://cryptopanic.com/api/v1"
CRYPTOPANIC_API_KEY: str

# BitQuery Settings
BITQUERY_API_KEY: str

# OpenAI Settings
OPENAI_API_KEY: str

# Gemini Settings
GEMINI_API_KEY: str

# Proxy Settings
PROXY_CONFIG: Optional[str] = None

# Supabase Settings
SUPABASE_URL: str
SUPABASE_SERVICE_KEY: str

# LLM Settings
LLM_TIMEOUT: int = 10 # seconds
LLM_MAX_RETRIES: int = 2
LLM_MODEL: str = "gpt-4o-mini"
OPENAI_API_URL: str = "https://api.openai.com/v1/chat/completions"
LLM_MAX_TOKENS: int = 4000

# Logging Settings
LOG_LEVEL: str = "INFO"
LOG_FILE: Optional[str] = None # Path to log file (optional)
LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
LOG_DATE_FORMAT: str = "%Y-%m-%d %H:%M:%S"
LOG_MAX_SIZE: int = 10 * 1024 * 1024 # 10 MB
LOG_BACKUP_COUNT: int = 5

class Config:
env_file = ".env"
case_sensitive = True
extra = "ignore"

def get_log_level(self) -> str:
"""Get the appropriate log level based on environment"""
if self.ENV == Environment.DEVELOPMENT:
return "DEBUG"
return self.LOG_LEVEL

@property
def is_production(self) -> bool:
"""Check if running in production environment"""
return self.ENV == Environment.PRODUCTION

@property
def is_development(self) -> bool:
"""Check if running in development environment"""
return self.ENV == Environment.DEVELOPMENT

def configure_logging(self) -> None:
"""Configure logging based on environment"""
log_level = self.get_log_level()
logging.basicConfig(
level=log_level,
format=self.LOG_FORMAT,
datefmt=self.LOG_DATE_FORMAT
)

# Adjust third-party loggers
if self.is_production:
logging.getLogger("uvicorn").setLevel(logging.WARNING)
logging.getLogger("aiohttp").setLevel(logging.WARNING)
logging.getLogger("asyncio").setLevel(logging.WARNING)
else:
logging.getLogger("uvicorn").setLevel(logging.INFO)
logging.getLogger("aiohttp").setLevel(logging.INFO)
logging.getLogger("asyncio").setLevel(logging.INFO)

# Initialize settings
settings = Settings()
settings.configure_logging()
38 changes: 38 additions & 0 deletions app/dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from fastapi import Header, HTTPException, status, Request
from typing import Optional
from app.services.supabase_service import SupabaseService
import logging

logger = logging.getLogger(__name__)
supabase_service = SupabaseService()

async def require_api_key(
request: Request,
x_api_key: Optional[str] = Header(None, convert_underscores=True)
):
"""
Global dependency for all non-admin endpoints.
- Checks if 'X-API-Key' is valid/active in Supabase.
- Resets daily usage if 'last_reset' < today's date.
- Stores user record in request.state.user for downstream usage.
"""
# Skip check for admin routes if needed
if request.url.path.startswith("/api/v1/admin"):
return

if not x_api_key:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="X-API-Key header is required"
)

user = supabase_service.get_user_by_api_key(x_api_key)
if not user or not user.get("active", False):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or inactive API key"
)

# Reset daily usage if a new day
user = supabase_service.reset_daily_usage_if_needed(user)
request.state.user = user # store the user for later usage
Loading