Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
66 changes: 34 additions & 32 deletions ai-backend/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,73 +21,73 @@ class Settings(BaseSettings):
"""Application settings loaded from environment variables with validation."""

# Required settings
openai_api_key: str = Field(..., min_length=20, description="OpenAI API key")
supabase_url: str = Field(..., description="Supabase project URL")
supabase_service_role_key: str = Field(
OPENAI_API_KEY: str = Field(..., min_length=20, description="OpenAI API key")
SUPABASE_URL: str = Field(..., description="Supabase project URL")
SUPABASE_SERVICE_ROLE_KEY: str = Field(
..., min_length=20, description="Supabase service role key"
)
rapidapi_key: str = Field(
RAPIDAPI_KEY: str = Field(
..., min_length=10, description="RapidAPI key for API-Football"
)

# OpenAI Configuration
openai_model: str = Field(default="gpt-4-turbo", description="OpenAI model to use")
OPENAI_MODEL: str = Field(default="gpt-4-turbo", description="OpenAI model to use")

# FastAPI Configuration
fastapi_host: str = Field(default="127.0.0.1", description="FastAPI host")
fastapi_port: int = Field(default=8000, ge=1, le=65535, description="FastAPI port")
fastapi_reload: bool = Field(default=False, description="FastAPI reload mode")
FASTAPI_HOST: str = Field(default="127.0.0.1", description="FastAPI host")
FASTAPI_PORT: int = Field(default=8000, ge=1, le=65535, description="FastAPI port")
FASTAPI_RELOAD: bool = Field(default=False, description="FastAPI reload mode")

# Chainlit Configuration
chainlit_host: str = Field(default="127.0.0.1", description="Chainlit host")
chainlit_port: int = Field(
CHAINLIT_HOST: str = Field(default="127.0.0.1", description="Chainlit host")
CHAINLIT_PORT: int = Field(
default=8001, ge=1, le=65535, description="Chainlit port"
)

# Environment Configuration
environment: str = Field(default="development", description="Environment name")
debug: bool = Field(default=False, description="Debug mode")
log_level: str = Field(default="INFO", description="Logging level")
log_format: str = Field(default="json", description="Log format")
ENVIRONMENT: str = Field(default="development", description="Environment name")
DEBUG: bool = Field(default=False, description="Debug mode")
LOG_LEVEL: str = Field(default="INFO", description="Logging level")
LOG_FORMAT: str = Field(default="json", description="Log format")

# API Configuration
api_football_base_url: str = Field(
API_FOOTBALL_BASE_URL: str = Field(
default="https://api-football-v1.p.rapidapi.com/v3",
description="API-Football base URL",
description="API-Football base URL"
)

@validator("openai_api_key")
@validator("OPENAI_API_KEY")
def validate_openai_key(cls, v: str) -> str: # noqa: N805
if not v or v == "your-openai-api-key" or v == "sk-...":
raise ValueError("Valid OpenAI API key is required")
if not v.startswith("sk-"):
raise ValueError('OpenAI API key must start with "sk-"')
return v

@validator("supabase_url")
@validator("SUPABASE_URL")
def validate_supabase_url(cls, v: str) -> str: # noqa: N805
if not v.startswith("https://"):
raise ValueError("Supabase URL must be a valid HTTPS URL")
if not v.endswith(".supabase.co"):
raise ValueError("Supabase URL must end with .supabase.co")
return v

@validator("environment")
@validator("ENVIRONMENT")
def validate_environment(cls, v: str) -> str: # noqa: N805
allowed = ["development", "staging", "production"]
if v not in allowed:
raise ValueError(f"Environment must be one of: {allowed}")
return v

@validator("log_level")
@validator("LOG_LEVEL")
def validate_log_level(cls, v: str) -> str: # noqa: N805
allowed = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
v_upper = v.upper()
if v_upper not in allowed:
raise ValueError(f"Log level must be one of: {allowed}")
return v_upper

@validator("log_format")
@validator("LOG_FORMAT")
def validate_log_format(cls, v: str) -> str: # noqa: N805
allowed = ["json", "text"]
if v not in allowed:
Expand All @@ -97,17 +97,17 @@ def validate_log_format(cls, v: str) -> str: # noqa: N805
def to_dict(self) -> dict[str, Any]:
"""Convert settings to dictionary (excluding sensitive values)."""
return {
"openai_model": self.openai_model,
"fastapi_host": self.fastapi_host,
"fastapi_port": self.fastapi_port,
"fastapi_reload": self.fastapi_reload,
"chainlit_host": self.chainlit_host,
"chainlit_port": self.chainlit_port,
"log_level": self.log_level,
"log_format": self.log_format,
"debug": self.debug,
"environment": self.environment,
"api_football_base_url": self.api_football_base_url,
"openai_model": self.OPENAI_MODEL,
"fastapi_host": self.FASTAPI_HOST,
"fastapi_port": self.FASTAPI_PORT,
"fastapi_reload": self.FASTAPI_RELOAD,
"chainlit_host": self.CHAINLIT_HOST,
"chainlit_port": self.CHAINLIT_PORT,
"log_level": self.LOG_LEVEL,
"log_format": self.LOG_FORMAT,
"debug": self.DEBUG,
"environment": self.ENVIRONMENT,
"api_football_base_url": self.API_FOOTBALL_BASE_URL,
}

class Config:
Expand All @@ -117,6 +117,8 @@ class Config:
case_sensitive = True
# Allow extra fields for forward compatibility
extra = "ignore"
# Allow field aliases
populate_by_name = True


# Global settings instance
Expand Down
20 changes: 10 additions & 10 deletions ai-backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,9 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
# Startup
logger.info(
"Starting Sport Scribe AI Backend",
environment=settings.environment,
debug=settings.debug,
log_level=settings.log_level,
environment=settings.ENVIRONMENT,
debug=settings.DEBUG,
log_level=settings.LOG_LEVEL,
version="1.0.0",
)

Expand Down Expand Up @@ -210,7 +210,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
# Add trusted host middleware for security
allowed_hosts = (
["*"]
if settings.environment == "development"
if settings.ENVIRONMENT == "development"
else [
"localhost",
"127.0.0.1",
Expand Down Expand Up @@ -240,7 +240,7 @@ async def health_check() -> HealthResponse:
"""Health check endpoint."""
return HealthResponse(
status="healthy",
environment=settings.environment,
environment=settings.ENVIRONMENT,
agents_status={
"data_collector": "ready",
"researcher": "ready",
Expand Down Expand Up @@ -276,12 +276,12 @@ async def root() -> dict[str, str]:
import uvicorn

logger.info(
"Starting server on %s:%s", settings.fastapi_host, settings.fastapi_port
"Starting server on %s:%s", settings.FASTAPI_HOST, settings.FASTAPI_PORT
)
uvicorn.run(
"main:app",
host=settings.fastapi_host,
port=settings.fastapi_port,
reload=settings.fastapi_reload,
log_level=settings.log_level.lower(),
host=settings.FASTAPI_HOST,
port=settings.FASTAPI_PORT,
reload=settings.FASTAPI_RELOAD,
log_level=settings.LOG_LEVEL.lower(),
)
4 changes: 2 additions & 2 deletions ai-backend/utils/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ def setup_logging(

try:
app_settings = get_settings()
level = level or app_settings.log_level
format_type = format_type or app_settings.log_format
level = level or app_settings.LOG_LEVEL
format_type = format_type or app_settings.LOG_FORMAT
except RuntimeError:
# Fallback to defaults if settings not available
level = level or "INFO"
Expand Down
16 changes: 12 additions & 4 deletions scripts/seed-data.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
from datetime import datetime, timedelta
from typing import Dict, List

# Load environment variables from .env file
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
print("⚠️ python-dotenv not installed. Install with: pip install python-dotenv")
# Continue without dotenv - environment variables must be set manually

# Add the shared directory to the Python path
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'shared'))

Expand Down Expand Up @@ -45,7 +53,7 @@
"game_date": datetime.now() - timedelta(days=1),
"home_score": 112,
"away_score": 108,
"status": "completed",
"status": "final",
"venue": "Crypto.com Arena"
},
{
Expand All @@ -64,7 +72,7 @@
"title": "Lakers Edge Warriors in Thrilling Overtime Victory",
"content": "In a spectacular display of basketball prowess, the Los Angeles Lakers defeated the Golden State Warriors 112-108 in overtime...",
"summary": "Lakers win in overtime against Warriors",
"author": "AI Sports Writer",
"author_id": None, # Will be set to null for AI-generated content
"status": "published",
"tags": ["basketball", "NBA", "Lakers", "Warriors"],
"game_id": 1,
Expand Down Expand Up @@ -167,8 +175,8 @@ def seed_articles(self, games: List[Dict]) -> List[Dict]:
if game_index < len(games):
article_copy["game_id"] = games[game_index]["id"]

# Convert tags list to JSON
article_copy["tags"] = json.dumps(article_copy["tags"])
# Keep tags as array (don't convert to JSON string)
# article_copy["tags"] = json.dumps(article_copy["tags"])

articles_data.append(article_copy)

Expand Down
4 changes: 4 additions & 0 deletions web/.husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

cd web && npx --no -- commitlint --edit $1
4 changes: 4 additions & 0 deletions web/.husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

cd web && npx lint-staged
5 changes: 5 additions & 0 deletions web/.lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{js,jsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,css}": ["prettier --write"]
}
4 changes: 2 additions & 2 deletions web/app/admin/analytics/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Card, CardBody, CardHeader } from "@heroui/react";
import { Card, CardBody, CardHeader } from '@heroui/react'

export default function AdminAnalyticsPage() {
return (
Expand Down Expand Up @@ -31,5 +31,5 @@ export default function AdminAnalyticsPage() {
</Card>
</div>
</div>
);
)
}
8 changes: 4 additions & 4 deletions web/app/admin/articles/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Card, CardBody, CardHeader, Button } from "@heroui/react";
import { Card, CardBody, CardHeader, Button } from '@heroui/react'

export default function AdminArticlesPage() {
return (
Expand All @@ -14,11 +14,11 @@ export default function AdminArticlesPage() {
<CardBody>
<p>This is a placeholder for the article management interface.</p>
<p className="mt-2 text-gray-600">
Features will include: article status, editing, publishing controls,
and AI agent monitoring.
Features will include: article status, editing, publishing controls, and AI agent
monitoring.
</p>
</CardBody>
</Card>
</div>
);
)
}
6 changes: 3 additions & 3 deletions web/app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Card, CardBody, CardHeader, Button } from "@heroui/react";
import Link from "next/link";
import { Card, CardBody, CardHeader, Button } from '@heroui/react'
import Link from 'next/link'

export default function AdminPage() {
return (
Expand Down Expand Up @@ -30,5 +30,5 @@ export default function AdminPage() {
</Card>
</div>
</div>
);
)
}
21 changes: 9 additions & 12 deletions web/app/api/analytics/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NextRequest, NextResponse } from "next/server";
import { NextRequest, NextResponse } from 'next/server'

// GET /api/analytics - Get platform analytics
export async function GET(request: NextRequest) {
Expand All @@ -8,20 +8,17 @@ export async function GET(request: NextRequest) {
totalArticles: 42,
totalViews: 1234,
articlesThisWeek: 7,
topSports: ["football", "basketball", "baseball"],
topSports: ['football', 'basketball', 'baseball'],
aiAgentStatus: {
dataCollector: "active",
researcher: "active",
writer: "active",
editor: "active",
dataCollector: 'active',
researcher: 'active',
writer: 'active',
editor: 'active',
},
};
}

return NextResponse.json({ analytics });
return NextResponse.json({ analytics })
} catch (error) {
return NextResponse.json(
{ error: "Failed to fetch analytics" },
{ status: 500 },
);
return NextResponse.json({ error: 'Failed to fetch analytics' }, { status: 500 })
}
}
Loading