| import os |
| from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, ForeignKey, JSON, Boolean |
| from sqlalchemy.orm import declarative_base, sessionmaker, relationship |
| from datetime import datetime |
| from contextlib import contextmanager |
|
|
| DATABASE_URL = os.environ.get('DATABASE_URL', 'sqlite:///output/app.db') |
|
|
| |
| engine = create_engine( |
| DATABASE_URL, |
| pool_size=20 if not DATABASE_URL.startswith("sqlite") else 5, |
| max_overflow=40 if not DATABASE_URL.startswith("sqlite") else 10, |
| connect_args={"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {} |
| ) |
|
|
| SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) |
| Base = declarative_base() |
|
|
| |
| @contextmanager |
| def get_db_session(): |
| """Context manager for atomic DB transactions with automatic rollback.""" |
| db = SessionLocal() |
| try: |
| yield db |
| db.commit() |
| except Exception: |
| db.rollback() |
| raise |
| finally: |
| db.close() |
|
|
| class Company(Base): |
| __tablename__ = "companies" |
| id = Column(Integer, primary_key=True, index=True) |
| name = Column(String, nullable=False) |
| domain = Column(String, index=True, nullable=True) |
| created_at = Column(DateTime, default=datetime.utcnow, index=True) |
| users = relationship("User", back_populates="company") |
|
|
| class User(Base): |
| __tablename__ = "users" |
| id = Column(Integer, primary_key=True, index=True) |
| email = Column(String, unique=True, index=True, nullable=False) |
| password_hash = Column(String, nullable=False) |
| role = Column(String, default='user') |
| company_id = Column(Integer, ForeignKey("companies.id"), index=True, nullable=True) |
| created_at = Column(DateTime, default=datetime.utcnow, index=True) |
| company = relationship("Company", back_populates="users") |
| jobs = relationship("Job", back_populates="user") |
|
|
| class Job(Base): |
| __tablename__ = "jobs" |
| id = Column(Integer, primary_key=True, index=True) |
| url = Column(String, nullable=False) |
| org_name = Column(String, index=True, nullable=False) |
| org_url = Column(String, nullable=True) |
| max_pages = Column(Integer, default=3) |
| runs = Column(Integer, default=1) |
| |
| |
| status = Column(String, default="pending", index=True) |
| progress = Column(JSON, default=dict) |
| result_path = Column(String, nullable=True) |
| |
| user_id = Column(Integer, ForeignKey("users.id"), index=True, nullable=True) |
| company_id = Column(Integer, index=True, nullable=True) |
| industry_override = Column(String, nullable=True) |
| |
| |
| estimated_cost = Column(Integer, default=0) |
| |
| created_at = Column(DateTime, default=datetime.utcnow, index=True) |
| updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, index=True) |
| user = relationship("User", back_populates="jobs") |
|
|
| def get_db(): |
| """Dependency for FastAPI""" |
| db = SessionLocal() |
| try: |
| yield db |
| finally: |
| db.close() |
|
|
| class Action(Base): |
| __tablename__ = "actions" |
| id = Column(Integer, primary_key=True, index=True) |
| job_id = Column(Integer, ForeignKey("jobs.id"), index=True, nullable=True) |
| type = Column(String, index=True) |
| task = Column(String) |
| priority_score = Column(Integer, default=50) |
| status = Column(String, default="pending", index=True) |
| result = Column(JSON, default=dict) |
| |
| |
| initial_metrics = Column(JSON, default=dict) |
| latest_metrics = Column(JSON, default=dict) |
| impact_score = Column(Integer, default=0) |
| |
| created_at = Column(DateTime, default=datetime.utcnow, index=True) |
| updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) |
|
|
| class ProjectMetric(Base): |
| __tablename__ = "project_metrics" |
| id = Column(Integer, primary_key=True, index=True) |
| job_id = Column(Integer, ForeignKey("jobs.id"), index=True) |
| date = Column(DateTime, default=datetime.utcnow, index=True) |
| clicks = Column(Integer, default=0) |
| impressions = Column(Integer, default=0) |
| avg_position = Column(Integer, default=100) |
|
|
| class Mention(Base): |
| __tablename__ = "mentions" |
| id = Column(Integer, primary_key=True, index=True) |
| brand = Column(String, index=True) |
| source = Column(String) |
| content = Column(String) |
| url = Column(String) |
| sentiment_score = Column(Integer, nullable=True) |
| created_at = Column(DateTime, default=datetime.utcnow, index=True) |
|
|
| class Lead(Base): |
| __tablename__ = "leads" |
| id = Column(Integer, primary_key=True, index=True) |
| job_id = Column(Integer, ForeignKey("jobs.id"), index=True, nullable=True) |
| url = Column(String) |
| email = Column(String, index=True, nullable=True) |
| status = Column(String, default="found", index=True) |
| created_at = Column(DateTime, default=datetime.utcnow, index=True) |
|
|
| class Entity(Base): |
| __tablename__ = "entities" |
| id = Column(Integer, primary_key=True, index=True) |
| name = Column(String, unique=True, index=True) |
| type = Column(String, index=True) |
| roi_score = Column(Integer, default=0) |
| |
| |
| relations = relationship("EntityRelation", foreign_keys="[EntityRelation.source_id]", back_populates="source") |
|
|
| class EntityRelation(Base): |
| __tablename__ = "entity_relations" |
| id = Column(Integer, primary_key=True, index=True) |
| source_id = Column(Integer, ForeignKey("entities.id"), index=True) |
| target_id = Column(Integer, ForeignKey("entities.id"), index=True) |
| relation_type = Column(String) |
| |
| source = relationship("Entity", foreign_keys=[source_id]) |
| target = relationship("Entity", foreign_keys=[target_id]) |
|
|
| class ActionEntity(Base): |
| __tablename__ = "action_entities" |
| id = Column(Integer, primary_key=True, index=True) |
| action_id = Column(Integer, ForeignKey("actions.id"), index=True) |
| entity_id = Column(Integer, ForeignKey("entities.id"), index=True) |
|
|
| class AIPrompt(Base): |
| __tablename__ = "ai_prompts" |
| id = Column(Integer, primary_key=True, index=True) |
| job_id = Column(Integer, ForeignKey("jobs.id"), index=True) |
| prompt_text = Column(String, index=True) |
| category = Column(String) |
| |
| class AIResponse(Base): |
| __tablename__ = "ai_responses" |
| id = Column(Integer, primary_key=True, index=True) |
| prompt_id = Column(Integer, ForeignKey("ai_prompts.id"), index=True) |
| model_name = Column(String, index=True) |
| raw_content = Column(Text) |
| |
| |
| mentioned_brand = Column(String, nullable=True) |
| mention_found = Column(Integer, default=0) |
| rank_position = Column(Integer, nullable=True) |
| sentiment_score = Column(Integer, default=0) |
| competitors_mentioned = Column(JSON, default=list) |
| |
| created_at = Column(DateTime, default=datetime.utcnow, index=True) |
|
|
| class ProjectSettings(Base): |
| __tablename__ = "project_settings" |
| id = Column(Integer, primary_key=True, index=True) |
| job_id = Column(Integer, ForeignKey("jobs.id"), unique=True, index=True) |
| autopilot_enabled = Column(Boolean, default=False) |
| budget_limit = Column(Integer, default=1000) |
| preferred_mix = Column(JSON, default=lambda: {"content": 70, "outreach": 20, "tech": 10}) |
|
|
| class SuccessLog(Base): |
| __tablename__ = "success_logs" |
| id = Column(Integer, primary_key=True, index=True) |
| action_type = Column(String, index=True) |
| avg_roi = Column(Integer, default=0) |
| success_count = Column(Integer, default=0) |
| fail_count = Column(Integer, default=0) |
|
|
| def init_db(): |
| Base.metadata.create_all(bind=engine) |
|
|