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