from datetime import datetime from typing import Optional from sqlalchemy import BigInteger, String, ForeignKey, Integer, DateTime, Boolean from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine from app.config.config import DATABASE_URL engine = create_async_engine(DATABASE_URL) # подключение и создание БД async_session = async_sessionmaker(engine, expire_on_commit=False) class Base(AsyncAttrs, DeclarativeBase): """Base class for all models""" pass class User(Base): """User model for storing telegram user data""" __tablename__ = 'users' id: Mapped[int] = mapped_column(primary_key=True) tg_id: Mapped[int] = mapped_column(BigInteger, unique=True, nullable=False) name: Mapped[str] = mapped_column(String(100), nullable=True) login: Mapped[str] = mapped_column(String(100), nullable=True) contact: Mapped[str] = mapped_column(String(100), nullable=True) subscription_status: Mapped[str] = mapped_column( String(20), default='inactive') # Relationships test_attempts = relationship("TestAttempt", back_populates="user") feedback = relationship("Feedback", back_populates="user") class Service(Base): """Service model for storing available services""" __tablename__ = 'services' id: Mapped[int] = mapped_column(primary_key=True) service_name: Mapped[str] = mapped_column(String(100), unique=True, nullable=False) service_description: Mapped[str] = mapped_column(String(500)) service_price: Mapped[int] = mapped_column(BigInteger, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) # Relationships feedback = relationship("Feedback", back_populates="service") class Test(Base): """Test model for storing quiz/test information""" __tablename__ = 'tests' id: Mapped[int] = mapped_column(primary_key=True) test_name: Mapped[str] = mapped_column(String(100), unique=True, nullable=False) test_type: Mapped[str] = mapped_column(String(20), nullable=False) test_description: Mapped[str] = mapped_column(String(250)) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) completion_message: Mapped[Optional[str]] = mapped_column(String(1000)) # Relationships questions = relationship("TestQuestion", back_populates="test", cascade="all, delete-orphan") results = relationship("TestResult", back_populates="test", cascade="all, delete-orphan") attempts = relationship("TestAttempt", back_populates="test") class TestQuestion(Base): """Model for storing test questions""" __tablename__ = 'test_questions' id: Mapped[int] = mapped_column(primary_key=True) test_id: Mapped[int] = mapped_column(ForeignKey('tests.id', ondelete='CASCADE'), nullable=False) question_content: Mapped[str] = mapped_column(String(150), nullable=False) question_variants: Mapped[str] = mapped_column(String(500), nullable=False) # JSON string question_points: Mapped[str] = mapped_column(String(100), nullable=False) # JSON string # Relationships test = relationship("Test", back_populates="questions") class TestResult(Base): """Model for storing possible test results""" __tablename__ = 'test_results' id: Mapped[int] = mapped_column(primary_key=True) test_id: Mapped[int] = mapped_column(ForeignKey('tests.id', ondelete='CASCADE'), nullable=False) min_points: Mapped[int] = mapped_column(Integer, nullable=False) max_points: Mapped[int] = mapped_column(Integer, nullable=False) result_text: Mapped[str] = mapped_column(String(1000), nullable=False) # Relationships test = relationship("Test", back_populates="results") class TestAttempt(Base): """Model for storing user test attempts""" __tablename__ = 'test_attempts' id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), nullable=False) test_id: Mapped[int] = mapped_column(ForeignKey('tests.id', ondelete='CASCADE'), nullable=False) score: Mapped[int] = mapped_column(Integer, nullable=True) result: Mapped[Optional[str]] = mapped_column(String(1000), nullable=True) completed_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now, nullable=True) # Relationships user = relationship("User", back_populates="test_attempts") test = relationship("Test", back_populates="attempts") class TestAnswer(Base): """Stores individual answers for each question""" __tablename__ = 'test_answers' id: Mapped[int] = mapped_column(primary_key=True) attempt_id: Mapped[int] = mapped_column(ForeignKey('test_attempts.id', ondelete='CASCADE')) question_id: Mapped[int] = mapped_column(ForeignKey('test_questions.id', ondelete='CASCADE')) answer_given: Mapped[str] = mapped_column(String(500)) points_earned: Mapped[int] = mapped_column(Integer, nullable=True) class LeadMagnet(Base): """Model for storing lead magnets/content triggers""" __tablename__ = 'lead_magnets' id: Mapped[int] = mapped_column(primary_key=True) trigger: Mapped[str] = mapped_column(String(50), unique=True, nullable=False) content: Mapped[str] = mapped_column(String(500), nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) class Feedback(Base): """Model for storing user feedback""" __tablename__ = 'feedback' id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), nullable=False) service_id: Mapped[int] = mapped_column(ForeignKey('services.id', ondelete='CASCADE'), nullable=False) rating: Mapped[int] = mapped_column(Integer, nullable=False) review: Mapped[str] = mapped_column(String(1000)) is_new: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) # Relationships user = relationship("User", back_populates="feedback") service = relationship("Service", back_populates="feedback") async def async_main(): """Initialize database tables""" async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all)