feat: Complete Story 1.1 - Project Initialization & Configuration
- Create monorepo directory structure as defined in architecture - Initialize Python project with core dependencies (FastAPI, SQLAlchemy, Click, Pydantic) - Add requirements.txt with all necessary libraries - Create env.example with placeholders for all cloud provider secrets - Implement master.config.json with comprehensive configuration - Add Pydantic-based configuration loading and validation - Create basic CLI framework with health check and config commands - Set up main.py as CLI entry point - Organize all modules according to source tree specification This completes the foundational infrastructure for Epic 1: Foundation & Core Services.main
parent
31b958029b
commit
70b9de20b4
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# BMad Configuration - Do not commit local changes
|
||||||
|
.bmad-core/core-config.yaml
|
||||||
|
|
||||||
|
# Database
|
||||||
|
*.db
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# IDE specific
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Database Configuration
|
||||||
|
DATABASE_URL=sqlite:///./content_automation.db
|
||||||
|
|
||||||
|
# AI Service Configuration
|
||||||
|
AI_API_KEY=your_ai_service_api_key_here
|
||||||
|
AI_API_BASE_URL=https://api.openai.com/v1
|
||||||
|
AI_MODEL=gpt-4
|
||||||
|
|
||||||
|
# AWS S3 Configuration
|
||||||
|
AWS_ACCESS_KEY_ID=your_aws_access_key_here
|
||||||
|
AWS_SECRET_ACCESS_KEY=your_aws_secret_key_here
|
||||||
|
AWS_REGION=us-east-1
|
||||||
|
|
||||||
|
# Azure Blob Storage Configuration
|
||||||
|
AZURE_STORAGE_ACCOUNT_NAME=your_azure_account_name_here
|
||||||
|
AZURE_STORAGE_ACCOUNT_KEY=your_azure_account_key_here
|
||||||
|
|
||||||
|
# Bunny.net Configuration
|
||||||
|
BUNNY_API_KEY=your_bunny_api_key_here
|
||||||
|
BUNNY_STORAGE_ZONE=your_bunny_zone_here
|
||||||
|
|
||||||
|
# Digital Ocean Spaces Configuration
|
||||||
|
DO_SPACES_ACCESS_KEY=your_do_spaces_key_here
|
||||||
|
DO_SPACES_SECRET_KEY=your_do_secret_key_here
|
||||||
|
DO_SPACES_REGION=nyc3
|
||||||
|
DO_SPACES_ENDPOINT=https://nyc3.digitaloceanspaces.com
|
||||||
|
|
||||||
|
# Linode Object Storage Configuration
|
||||||
|
LINODE_ACCESS_KEY=your_linode_access_key_here
|
||||||
|
LINODE_SECRET_KEY=your_linode_secret_key_here
|
||||||
|
LINODE_REGION=us-east-1
|
||||||
|
|
||||||
|
# Backblaze B2 Configuration
|
||||||
|
B2_APPLICATION_KEY_ID=your_b2_key_id_here
|
||||||
|
B2_APPLICATION_KEY=your_b2_application_key_here
|
||||||
|
B2_BUCKET_NAME=your_b2_bucket_name_here
|
||||||
|
|
||||||
|
# Cloudflare Pages Configuration
|
||||||
|
CLOUDFLARE_API_TOKEN=your_cloudflare_token_here
|
||||||
|
CLOUDFLARE_ACCOUNT_ID=your_cloudflare_account_id_here
|
||||||
|
|
||||||
|
# Link Building Machine API
|
||||||
|
LINK_BUILDER_API_URL=http://localhost:8001/api
|
||||||
|
LINK_BUILDER_API_KEY=your_link_builder_api_key_here
|
||||||
|
|
||||||
|
# Application Configuration
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
ENVIRONMENT=development
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Content Automation & Syndication Platform
|
||||||
|
Main CLI entry point
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add src to Python path
|
||||||
|
src_path = Path(__file__).parent / "src"
|
||||||
|
sys.path.insert(0, str(src_path))
|
||||||
|
|
||||||
|
from cli.commands import app
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app()
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
{
|
||||||
|
"application": {
|
||||||
|
"name": "Content Automation & Syndication Platform",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"environment": "development"
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"url": "sqlite:///./content_automation.db",
|
||||||
|
"echo": false,
|
||||||
|
"pool_size": 5,
|
||||||
|
"max_overflow": 10
|
||||||
|
},
|
||||||
|
"ai_service": {
|
||||||
|
"provider": "openai",
|
||||||
|
"model": "gpt-4",
|
||||||
|
"max_tokens": 4000,
|
||||||
|
"temperature": 0.7,
|
||||||
|
"timeout": 30
|
||||||
|
},
|
||||||
|
"content_rules": {
|
||||||
|
"h1_keyword_required": true,
|
||||||
|
"h2_keyword_count": 1,
|
||||||
|
"h3_keyword_count": 1,
|
||||||
|
"faq_section_required": true,
|
||||||
|
"image_alt_text_required": true,
|
||||||
|
"min_content_length": 1000,
|
||||||
|
"max_content_length": 5000
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"default": "basic",
|
||||||
|
"mappings": {
|
||||||
|
"aws-s3-bucket-1": "modern",
|
||||||
|
"bunny-bucket-1": "classic",
|
||||||
|
"azure-bucket-1": "minimal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deployment": {
|
||||||
|
"providers": {
|
||||||
|
"aws": {
|
||||||
|
"enabled": true,
|
||||||
|
"default_region": "us-east-1"
|
||||||
|
},
|
||||||
|
"azure": {
|
||||||
|
"enabled": true,
|
||||||
|
"default_region": "eastus"
|
||||||
|
},
|
||||||
|
"bunny": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"digitalocean": {
|
||||||
|
"enabled": true,
|
||||||
|
"default_region": "nyc3"
|
||||||
|
},
|
||||||
|
"linode": {
|
||||||
|
"enabled": true,
|
||||||
|
"default_region": "us-east-1"
|
||||||
|
},
|
||||||
|
"backblaze": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"cloudflare": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interlinking": {
|
||||||
|
"wheel_links": true,
|
||||||
|
"home_page_link": true,
|
||||||
|
"random_article_link": true,
|
||||||
|
"max_links_per_article": 5
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"level": "INFO",
|
||||||
|
"format": "json",
|
||||||
|
"file": "logs/app.log",
|
||||||
|
"max_size": "10MB",
|
||||||
|
"backup_count": 5
|
||||||
|
},
|
||||||
|
"api": {
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 8000,
|
||||||
|
"reload": true,
|
||||||
|
"workers": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Core Framework
|
||||||
|
fastapi==0.104.1
|
||||||
|
uvicorn[standard]==0.24.0
|
||||||
|
|
||||||
|
# CLI Framework
|
||||||
|
click==8.1.7
|
||||||
|
typer==0.9.0
|
||||||
|
|
||||||
|
# Database
|
||||||
|
sqlalchemy==2.0.23
|
||||||
|
alembic==1.12.1
|
||||||
|
|
||||||
|
# Authentication
|
||||||
|
passlib[bcrypt]==1.7.4
|
||||||
|
python-jose[cryptography]==3.3.0
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
pydantic==2.5.0
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
|
||||||
|
# Cloud Providers
|
||||||
|
boto3==1.34.0
|
||||||
|
azure-storage-blob==12.19.0
|
||||||
|
requests==2.31.0
|
||||||
|
|
||||||
|
# Data Processing
|
||||||
|
pandas==2.1.4
|
||||||
|
openpyxl==3.1.2
|
||||||
|
|
||||||
|
# AI/ML (placeholder - to be specified based on chosen AI service)
|
||||||
|
openai==1.3.7
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
pytest==7.4.3
|
||||||
|
pytest-asyncio==0.21.1
|
||||||
|
pytest-mock==3.12.0
|
||||||
|
moto==4.2.14
|
||||||
|
|
||||||
|
# Development
|
||||||
|
black==23.11.0
|
||||||
|
flake8==6.1.0
|
||||||
|
mypy==1.7.1
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Content Automation & Syndication Platform
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# API module for internal REST API
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# FastAPI app instance
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# API endpoint definitions
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Pydantic models for API
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Authentication module
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Hashing, validation, role checks
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# CLI module
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
"""
|
||||||
|
CLI command definitions using Click
|
||||||
|
"""
|
||||||
|
|
||||||
|
import click
|
||||||
|
from core.config import get_config
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
@click.version_option(version="1.0.0")
|
||||||
|
def app():
|
||||||
|
"""Content Automation & Syndication Platform CLI"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def config():
|
||||||
|
"""Show current configuration"""
|
||||||
|
try:
|
||||||
|
config = get_config()
|
||||||
|
click.echo("Current Configuration:")
|
||||||
|
click.echo(f"Application: {config.application.name} v{config.application.version}")
|
||||||
|
click.echo(f"Environment: {config.application.environment}")
|
||||||
|
click.echo(f"Database: {config.database.url}")
|
||||||
|
click.echo(f"AI Model: {config.ai_service.model}")
|
||||||
|
click.echo(f"Log Level: {config.logging.level}")
|
||||||
|
except Exception as e:
|
||||||
|
click.echo(f"Error loading configuration: {e}", err=True)
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def health():
|
||||||
|
"""Check system health"""
|
||||||
|
try:
|
||||||
|
config = get_config()
|
||||||
|
click.echo("✅ Configuration loaded successfully")
|
||||||
|
click.echo("✅ System is healthy")
|
||||||
|
except Exception as e:
|
||||||
|
click.echo(f"❌ System health check failed: {e}", err=True)
|
||||||
|
raise click.Abort()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app()
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Core application module
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
"""
|
||||||
|
Configuration loading and validation module
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseConfig(BaseModel):
|
||||||
|
url: str
|
||||||
|
echo: bool = False
|
||||||
|
pool_size: int = 5
|
||||||
|
max_overflow: int = 10
|
||||||
|
|
||||||
|
|
||||||
|
class AIServiceConfig(BaseModel):
|
||||||
|
provider: str = "openai"
|
||||||
|
model: str = "gpt-4"
|
||||||
|
max_tokens: int = 4000
|
||||||
|
temperature: float = 0.7
|
||||||
|
timeout: int = 30
|
||||||
|
|
||||||
|
|
||||||
|
class ContentRulesConfig(BaseModel):
|
||||||
|
h1_keyword_required: bool = True
|
||||||
|
h2_keyword_count: int = 1
|
||||||
|
h3_keyword_count: int = 1
|
||||||
|
faq_section_required: bool = True
|
||||||
|
image_alt_text_required: bool = True
|
||||||
|
min_content_length: int = 1000
|
||||||
|
max_content_length: int = 5000
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateConfig(BaseModel):
|
||||||
|
default: str = "basic"
|
||||||
|
mappings: Dict[str, str] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
class DeploymentConfig(BaseModel):
|
||||||
|
providers: Dict[str, Dict[str, Any]] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
class InterlinkingConfig(BaseModel):
|
||||||
|
wheel_links: bool = True
|
||||||
|
home_page_link: bool = True
|
||||||
|
random_article_link: bool = True
|
||||||
|
max_links_per_article: int = 5
|
||||||
|
|
||||||
|
|
||||||
|
class LoggingConfig(BaseModel):
|
||||||
|
level: str = "INFO"
|
||||||
|
format: str = "json"
|
||||||
|
file: str = "logs/app.log"
|
||||||
|
max_size: str = "10MB"
|
||||||
|
backup_count: int = 5
|
||||||
|
|
||||||
|
|
||||||
|
class APIConfig(BaseModel):
|
||||||
|
host: str = "0.0.0.0"
|
||||||
|
port: int = 8000
|
||||||
|
reload: bool = True
|
||||||
|
workers: int = 1
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationConfig(BaseModel):
|
||||||
|
name: str
|
||||||
|
version: str
|
||||||
|
environment: str
|
||||||
|
|
||||||
|
|
||||||
|
class Config(BaseModel):
|
||||||
|
application: ApplicationConfig
|
||||||
|
database: DatabaseConfig
|
||||||
|
ai_service: AIServiceConfig
|
||||||
|
content_rules: ContentRulesConfig
|
||||||
|
templates: TemplateConfig
|
||||||
|
deployment: DeploymentConfig
|
||||||
|
interlinking: InterlinkingConfig
|
||||||
|
logging: LoggingConfig
|
||||||
|
api: APIConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigManager:
|
||||||
|
"""Manages application configuration loading and validation"""
|
||||||
|
|
||||||
|
def __init__(self, config_path: Optional[str] = None):
|
||||||
|
self.config_path = config_path or "master.config.json"
|
||||||
|
self._config: Optional[Config] = None
|
||||||
|
|
||||||
|
def load_config(self) -> Config:
|
||||||
|
"""Load and validate configuration from JSON file"""
|
||||||
|
if self._config is None:
|
||||||
|
# Load environment variables first
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Load JSON configuration
|
||||||
|
config_path = Path(self.config_path)
|
||||||
|
if not config_path.exists():
|
||||||
|
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
||||||
|
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
config_data = json.load(f)
|
||||||
|
|
||||||
|
# Override with environment variables where applicable
|
||||||
|
self._override_with_env(config_data)
|
||||||
|
|
||||||
|
# Validate configuration
|
||||||
|
self._config = Config(**config_data)
|
||||||
|
|
||||||
|
return self._config
|
||||||
|
|
||||||
|
def _override_with_env(self, config_data: Dict[str, Any]) -> None:
|
||||||
|
"""Override configuration with environment variables"""
|
||||||
|
# Database URL
|
||||||
|
if os.getenv("DATABASE_URL"):
|
||||||
|
config_data["database"]["url"] = os.getenv("DATABASE_URL")
|
||||||
|
|
||||||
|
# AI Service configuration
|
||||||
|
if os.getenv("AI_MODEL"):
|
||||||
|
config_data["ai_service"]["model"] = os.getenv("AI_MODEL")
|
||||||
|
|
||||||
|
# Logging level
|
||||||
|
if os.getenv("LOG_LEVEL"):
|
||||||
|
config_data["logging"]["level"] = os.getenv("LOG_LEVEL")
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
if os.getenv("ENVIRONMENT"):
|
||||||
|
config_data["application"]["environment"] = os.getenv("ENVIRONMENT")
|
||||||
|
|
||||||
|
def get_config(self) -> Config:
|
||||||
|
"""Get the current configuration"""
|
||||||
|
if self._config is None:
|
||||||
|
return self.load_config()
|
||||||
|
return self._config
|
||||||
|
|
||||||
|
def reload_config(self) -> Config:
|
||||||
|
"""Reload configuration from file"""
|
||||||
|
self._config = None
|
||||||
|
return self.load_config()
|
||||||
|
|
||||||
|
|
||||||
|
# Global configuration instance
|
||||||
|
config_manager = ConfigManager()
|
||||||
|
|
||||||
|
|
||||||
|
def get_config() -> Config:
|
||||||
|
"""Get the application configuration"""
|
||||||
|
return config_manager.get_config()
|
||||||
|
|
||||||
|
|
||||||
|
def reload_config() -> Config:
|
||||||
|
"""Reload the application configuration"""
|
||||||
|
return config_manager.reload_config()
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Database module
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Abstract repository interfaces
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# SQLAlchemy models
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Concrete repository implementations
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Deployment module
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Manages which strategy to use
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Cloud deployment strategies
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Content generation module
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Content validation rules
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# AI API interaction
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Data ingestion module
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# CORA .xlsx file parsing
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Interlinking module
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Link map generation and injection
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# HTML templating module
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Applies templates to content
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# HTML/CSS templates directory
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Test module
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Pytest fixtures
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Integration tests for workflows
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Unit tests for individual modules
|
||||||
Loading…
Reference in New Issue