Implementación inicial del sistema de automatización de redes sociales
- Estructura completa del proyecto con FastAPI - Modelos de base de datos (productos, servicios, posts, calendario, interacciones) - Publishers para X, Threads, Instagram, Facebook - Generador de contenido con DeepSeek API - Worker de Celery con tareas programadas - Dashboard básico con templates HTML - Docker Compose para despliegue - Documentación completa Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1
app/core/__init__.py
Normal file
1
app/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Core module
|
||||
60
app/core/config.py
Normal file
60
app/core/config.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Configuración central del sistema.
|
||||
Carga variables de entorno y define settings globales.
|
||||
"""
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Configuración de la aplicación."""
|
||||
|
||||
# Aplicación
|
||||
APP_NAME: str = "social-media-automation"
|
||||
APP_ENV: str = "development"
|
||||
DEBUG: bool = True
|
||||
SECRET_KEY: str = "change-this-in-production"
|
||||
|
||||
# Base de datos
|
||||
DATABASE_URL: str = "postgresql://social_user:social_pass@localhost:5432/social_automation"
|
||||
|
||||
# Redis
|
||||
REDIS_URL: str = "redis://localhost:6379/0"
|
||||
|
||||
# DeepSeek API
|
||||
DEEPSEEK_API_KEY: Optional[str] = None
|
||||
DEEPSEEK_BASE_URL: str = "https://api.deepseek.com/v1"
|
||||
|
||||
# X (Twitter) API
|
||||
X_API_KEY: Optional[str] = None
|
||||
X_API_SECRET: Optional[str] = None
|
||||
X_ACCESS_TOKEN: Optional[str] = None
|
||||
X_ACCESS_TOKEN_SECRET: Optional[str] = None
|
||||
X_BEARER_TOKEN: Optional[str] = None
|
||||
|
||||
# Meta API (Facebook, Instagram, Threads)
|
||||
META_APP_ID: Optional[str] = None
|
||||
META_APP_SECRET: Optional[str] = None
|
||||
META_ACCESS_TOKEN: Optional[str] = None
|
||||
FACEBOOK_PAGE_ID: Optional[str] = None
|
||||
INSTAGRAM_ACCOUNT_ID: Optional[str] = None
|
||||
THREADS_USER_ID: Optional[str] = None
|
||||
|
||||
# Información del negocio
|
||||
BUSINESS_NAME: str = "Consultoría AS"
|
||||
BUSINESS_LOCATION: str = "Tijuana, México"
|
||||
BUSINESS_WEBSITE: str = "https://consultoria-as.com"
|
||||
CONTENT_TONE: str = "profesional pero cercano, educativo, orientado a soluciones"
|
||||
|
||||
# Notificaciones
|
||||
TELEGRAM_BOT_TOKEN: Optional[str] = None
|
||||
TELEGRAM_CHAT_ID: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = True
|
||||
|
||||
|
||||
# Instancia global de configuración
|
||||
settings = Settings()
|
||||
31
app/core/database.py
Normal file
31
app/core/database.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Configuración de la base de datos PostgreSQL.
|
||||
"""
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
# Crear engine de SQLAlchemy
|
||||
engine = create_engine(
|
||||
settings.DATABASE_URL,
|
||||
pool_pre_ping=True,
|
||||
pool_size=10,
|
||||
max_overflow=20
|
||||
)
|
||||
|
||||
# Crear sesión
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# Base para modelos
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def get_db():
|
||||
"""Dependency para obtener sesión de base de datos."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
78
app/core/security.py
Normal file
78
app/core/security.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Configuración de seguridad y autenticación.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
# Configuración de hashing de contraseñas
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
# Configuración de JWT
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 # 24 horas
|
||||
|
||||
# Security scheme
|
||||
security = HTTPBearer()
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""Verificar contraseña contra hash."""
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""Generar hash de contraseña."""
|
||||
return pwd_context.hash(password)
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
||||
"""Crear token JWT."""
|
||||
to_encode = data.copy()
|
||||
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)
|
||||
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def decode_token(token: str) -> dict:
|
||||
"""Decodificar y validar token JWT."""
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
|
||||
return payload
|
||||
except JWTError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Token inválido o expirado",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security)
|
||||
) -> dict:
|
||||
"""Obtener usuario actual desde el token."""
|
||||
token = credentials.credentials
|
||||
payload = decode_token(token)
|
||||
|
||||
username = payload.get("sub")
|
||||
if username is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Token inválido"
|
||||
)
|
||||
|
||||
return {"username": username}
|
||||
Reference in New Issue
Block a user