Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ node_modules/
/playwright-report/
/blob-report/
/playwright/.cache/
certs/localhost-key.pem
certs/localhost.pem
3 changes: 3 additions & 0 deletions backend/app/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
# target_metadata = mymodel.Base.metadata
# target_metadata = None

import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

from app.models import SQLModel # noqa
from app.core.config import settings # noqa

Expand Down
33 changes: 33 additions & 0 deletions backend/app/alembic/versions/8d6c1843c4b1_change_structure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""change structure

Revision ID: 8d6c1843c4b1
Revises: bf333f65b637
Create Date: 2025-03-08 11:50:26.757404

"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes


# revision identifiers, used by Alembic.
revision = '8d6c1843c4b1'
down_revision = 'bf333f65b637'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('tasks', sa.Column('illumination_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False))
op.add_column('tasks', sa.Column('category', sqlmodel.sql.sqltypes.AutoString(), nullable=False))
op.drop_column('tasks', 'category_type')
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('tasks', sa.Column('category_type', sa.VARCHAR(), autoincrement=False, nullable=False))
op.drop_column('tasks', 'category')
op.drop_column('tasks', 'illumination_type')
# ### end Alembic commands ###
56 changes: 56 additions & 0 deletions backend/app/alembic/versions/bf333f65b637_add_tables_for_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""add tables for task

Revision ID: bf333f65b637
Revises: 1a31ce608336
Create Date: 2025-03-03 16:14:20.916533

"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes


# revision identifiers, used by Alembic.
revision = 'bf333f65b637'
down_revision = '1a31ce608336'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('tasks',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('test_cases', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('constraints', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('difficulty', sa.Enum('ONE_STAR', 'TWO_STAR', 'THREE_STAR', 'FOUR_STAR', 'FIVE_STAR', name='taskdifficulty'), nullable=False),
sa.Column('tags', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('category_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('hint', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tasks_id'), 'tasks', ['id'], unique=False)
op.create_table('solutions',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('task_id', sa.Integer(), nullable=False),
sa.Column('solution_code', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column('is_correct', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_solutions_id'), 'solutions', ['id'], unique=False)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_solutions_id'), table_name='solutions')
op.drop_table('solutions')
op.drop_index(op.f('ix_tasks_id'), table_name='tasks')
op.drop_table('tasks')
# ### end Alembic commands ###
4 changes: 3 additions & 1 deletion backend/app/api/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from fastapi import APIRouter

from app.api.routes import items, login, private, users, utils
from app.api.routes import items, login, private, users, utils, illuminations, task
from app.core.config import settings

api_router = APIRouter()
api_router.include_router(login.router)
api_router.include_router(users.router)
api_router.include_router(utils.router)
api_router.include_router(items.router)
api_router.include_router(illuminations.router)
api_router.include_router(task.router)


if settings.ENVIRONMENT == "local":
Expand Down
29 changes: 29 additions & 0 deletions backend/app/api/routes/illuminations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from fastapi import APIRouter, HTTPException
from app.api.deps import SessionDep
from app.models import (
Task,
IlluminationPublic,
TaskPublic
)

router = APIRouter(
prefix="/illuminations",
tags=["illuminations"]
)

@router.get("/{illumination_type}", response_model=IlluminationPublic)
def read_by_illumination_type(illumination_type: str, session: SessionDep):
tasks = session.query(Task).filter(Task.illumination_type == illumination_type).all()
tasks = [TaskPublic(
id=task.id,
title=task.title,
description=task.description,
difficulty=task.difficulty,
illumination_type=task.illumination_type,
category=task.category,
created_at=task.created_at
) for task in tasks]

if not tasks:
raise HTTPException(status_code=404, detail="No illuminations found")
return IlluminationPublic(illumination_type=illumination_type, tasks=tasks)
28 changes: 28 additions & 0 deletions backend/app/api/routes/task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from fastapi import APIRouter, HTTPException
from typing import Optional
from app.api.deps import SessionDep
from app.models import (
Task,
TaskPublic
)

router = APIRouter(
prefix="/task",
tags=["task"]
)

@router.get("/{id}", response_model=TaskPublic)
def read_task(id: Optional[int], session: SessionDep):
task = session.get(Task, id)
task = TaskPublic(
id=task.id,
title=task.title,
description=task.description,
difficulty=task.difficulty,
illumination_type=task.illumination_type,
category=task.category,
created_at=task.created_at
)
if not task:
raise HTTPException(status_code=404, detail="No task found")
return task
53 changes: 52 additions & 1 deletion backend/app/core/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from app import crud
from app.core.config import settings
from app.models import User, UserCreate
from app.models import User, UserCreate, Task, TaskDifficulty
from datetime import datetime

engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))

Expand Down Expand Up @@ -31,3 +32,53 @@ def init_db(session: Session) -> None:
is_superuser=True,
)
user = crud.create_user(session=session, user_create=user_in)

def init_tasks(session: Session):
task = session.exec(
select(Task).where(Task.illumination_type == "interview")
).first()

if not task:
initial_tasks = [
Task(
title="Сумма чисел",
description="Напишите программу, которая считает сумму двух чисел.",
test_cases='[{"input": "2 3", "output": "5"}, {"input": "10 15", "output": "25"}]',
constraints='{"time_limit": "1s", "memory_limit": "256MB"}',
difficulty=TaskDifficulty.ONE_STAR,
tags='["interview"]',
illumination_type='interview',
category='Числа',
hint="Попробуйте использовать встроенные арифметические операции.",
created_at=datetime.now(),
is_active=True
),
Task(
title="Фибоначчи",
description="Определите n-е число Фибоначчи.",
test_cases='[{"input": "10", "output": "55"}, {"input": "20", "output": "6765"}]',
constraints='{"time_limit": "2s", "memory_limit": "256MB"}',
difficulty=TaskDifficulty.TWO_STAR,
tags='["interview"]',
illumination_type='interview',
category='Числа',
hint="Используйте рекурсию или динамическое программирование для оптимизации.",
created_at=datetime.now(),
is_active=True
),
Task(
title="Обратная строка",
description="Напишити функцию, которая переворачивает строку.",
test_cases='[{"input": "10", "output": "55"}, {"input": "20", "output": "6765"}]',
constraints='{"time_limit": "2s", "memory_limit": "256MB"}',
difficulty=TaskDifficulty.THREE_STAR,
tags='["interview"]',
illumination_type='interview',
category='Строки',
hint="Используйте метод двух указателей.",
created_at=datetime.now(),
is_active=True
)
]
session.add_all(initial_tasks)
session.commit()
3 changes: 2 additions & 1 deletion backend/app/initial_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from sqlmodel import Session

from app.core.db import engine, init_db
from app.core.db import engine, init_db, init_tasks

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
Expand All @@ -11,6 +11,7 @@
def init() -> None:
with Session(engine) as session:
init_db(session)
init_tasks(session)


def main() -> None:
Expand Down
55 changes: 54 additions & 1 deletion backend/app/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from typing import Optional
import uuid

from pydantic import EmailStr
from pydantic import EmailStr, BaseModel
from sqlmodel import Field, Relationship, SQLModel
from datetime import datetime
import enum


# Shared properties
Expand Down Expand Up @@ -111,3 +114,53 @@ class TokenPayload(SQLModel):
class NewPassword(SQLModel):
token: str
new_password: str = Field(min_length=8, max_length=40)

class TaskDifficulty(enum.Enum):
ONE_STAR = "one_star"
TWO_STAR = "two_star"
THREE_STAR = "three_star"
FOUR_STAR = "four_star"
FIVE_STAR = "five_star"


class Task(SQLModel, table=True):
__tablename__ = "tasks"

id: Optional[int] = Field(default=None, primary_key=True, index=True)
title: str = Field(..., max_length=255, description="Заголовок задачи (короткое описание)")
description: str = Field(..., description="Полный текст задачи (условие)")
test_cases: str = Field(..., description="JSON-объект с тестами (входные данные и ожидание ответа)")
constraints: str = Field(default=None, description="JSON-объект с ограничениями (например, время/память)")
difficulty: TaskDifficulty = Field(default=TaskDifficulty.ONE_STAR, description="Сложность задачи (1-5 звезд)")
tags: str = Field(default=None, description="Тема задачи или теги (например, ['dynamic programming', 'math'])")
illumination_type: str = Field(default=None, description="Тип иллюминации, к которой относится задача (interview, kids)")
category: str = Field(description="Категория задачи (строки, числа, связные списки и пр.)")
hint: Optional[str] = Field(default=None, description="Подсказка к задаче")
created_at: datetime = Field(default_factory=datetime, description="Дата создания задачи")
is_active: bool = Field(default=True, description="Активность задачи (например, скрытие её в системе)")
solutions: list["Solution"] = Relationship(back_populates="task")


class Solution(SQLModel, table=True):
__tablename__ = "solutions"

id: Optional[int] = Field(default=None, primary_key=True, index=True)
task_id: int = Field(..., foreign_key="tasks.id", description="Связь с задачей")
solution_code: str = Field(..., description="Текст решения (например, Python-код)")
is_correct: bool = Field(default=False, description="Флаг корректности решения")
created_at: datetime = Field(default_factory=datetime.utcnow, description="Дата создания решения")
task: Task = Relationship(back_populates="solutions")

class TaskPublic(BaseModel):
id: Optional[int] = Field(default=None, primary_key=True, index=True)
title: str = Field(..., max_length=255, description="Заголовок задачи (короткое описание)")
description: str = Field(..., description="Полный текст задачи (условие)")
difficulty: TaskDifficulty = Field(default=TaskDifficulty.ONE_STAR, description="Сложность задачи (1-5 звезд)")
illumination_type: str = Field(default=None, description="Тип иллюминации, к которой относится задача (interview, kids)")
category: str = Field(description="Категория задачи (строки, числа, связные списки и пр.)")
created_at: datetime = Field(default_factory=datetime, description="Дата создания задачи")

class IlluminationPublic(SQLModel):
illumination_type: str
tasks: list[TaskPublic]

2 changes: 1 addition & 1 deletion frontend/.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
VITE_API_URL=http://localhost:8000
VITE_API_URL=localhost:8000
MAILCATCHER_HOST=http://localhost:1080
Empty file added frontend/build/.gitkeep
Empty file.
Loading
Loading