diff --git a/alembic/versions/780dec53b460_tests.py b/alembic/versions/780dec53b460_tests.py new file mode 100644 index 0000000..2e718b8 --- /dev/null +++ b/alembic/versions/780dec53b460_tests.py @@ -0,0 +1,57 @@ +"""tests + +Revision ID: 780dec53b460 +Revises: 409f04b7b544 +Create Date: 2025-12-31 01:09:25.135116 + +""" +from collections.abc import Sequence + +from alembic import op +import sqlalchemy as sa + + +revision: str = '780dec53b460' +down_revision: str | None = '409f04b7b544' +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('tests', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=255), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('for_group', sa.Integer(), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('questions', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('test_id', sa.Integer(), nullable=False), + sa.Column('text', sa.Text(), nullable=False), + sa.Column('position', sa.Integer(), nullable=False), + sa.Column('question_type', sa.Enum('SINGLE', 'MULTIPLE', 'INPUT', name='questiontype'), nullable=False), + sa.Column('tg_file_id', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['test_id'], ['tests.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('options', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('question_id', sa.Integer(), nullable=False), + sa.Column('text', sa.String(length=255), nullable=False), + sa.Column('is_correct', sa.Boolean(), nullable=False), + sa.Column('explanation', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['question_id'], ['questions.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('options') + op.drop_table('questions') + op.drop_table('tests') + # ### end Alembic commands ### diff --git a/src/trudex/infrastructure/database/models.py b/src/trudex/infrastructure/database/models.py index 3e067e7..eedf500 100644 --- a/src/trudex/infrastructure/database/models.py +++ b/src/trudex/infrastructure/database/models.py @@ -1,8 +1,10 @@ from datetime import datetime +from enum import Enum +from tokenize import group from typing import final -from sqlalchemy import BigInteger, CheckConstraint, String, func -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column +from sqlalchemy import BigInteger, CheckConstraint, ForeignKey, Integer, String, Text, func +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship class Base(DeclarativeBase): @@ -21,3 +23,54 @@ class User(Base): is_admin: Mapped[bool] = mapped_column(default=False) created_at: Mapped[datetime] = mapped_column(server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(server_default=func.now(), onupdate=func.now()) + + +class QuestionType(str, Enum): + SINGLE = "single" + MULTIPLE = "multiple" + INPUT = "input" + +@final +class Test(Base): + __tablename__ = "tests" + + id: Mapped[int] = mapped_column(primary_key=True) + title: Mapped[str] = mapped_column(String(255)) + description: Mapped[str | None] = mapped_column(Text) + for_group: Mapped[int | None] = mapped_column(default=None) + is_active: Mapped[bool] = mapped_column(default=True) + + questions: Mapped[list["Question"]] = relationship( + back_populates="test", + cascade="all, delete-orphan", + order_by="Question.position" + ) + +@final +class Question(Base): + __tablename__ = "questions" + + id: Mapped[int] = mapped_column(primary_key=True) + test_id: Mapped[int] = mapped_column(ForeignKey("tests.id")) + text: Mapped[str] = mapped_column(Text) + position: Mapped[int] = mapped_column(Integer, default=0) + question_type: Mapped[QuestionType] = mapped_column(default=QuestionType.SINGLE) + tg_file_id: Mapped[str | None] = mapped_column(String(255)) + + test: Mapped["Test"] = relationship(back_populates="questions") + options: Mapped[list["Option"]] = relationship( + back_populates="question", + cascade="all, delete-orphan" + ) + +@final +class Option(Base): + __tablename__ = "options" + + id: Mapped[int] = mapped_column(primary_key=True) + question_id: Mapped[int] = mapped_column(ForeignKey("questions.id")) + text: Mapped[str] = mapped_column(String(255)) + is_correct: Mapped[bool] = mapped_column(default=False) + explanation: Mapped[str | None] = mapped_column(Text) + + question: Mapped["Question"] = relationship(back_populates="options")