Skip to content

Commit ad7ca36

Browse files
committed
small fixes
1 parent b3a4a83 commit ad7ca36

File tree

28 files changed

+4803
-1275
lines changed

28 files changed

+4803
-1275
lines changed

backend/app/alembic/env.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
# target_metadata = mymodel.Base.metadata
1919
# target_metadata = None
2020

21+
import sys
22+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
23+
2124
from app.models import SQLModel # noqa
2225
from app.core.config import settings # noqa
2326

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""change structure
2+
3+
Revision ID: 8d6c1843c4b1
4+
Revises: bf333f65b637
5+
Create Date: 2025-03-08 11:50:26.757404
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = '8d6c1843c4b1'
15+
down_revision = 'bf333f65b637'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.add_column('tasks', sa.Column('illumination_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False))
23+
op.add_column('tasks', sa.Column('category', sqlmodel.sql.sqltypes.AutoString(), nullable=False))
24+
op.drop_column('tasks', 'category_type')
25+
# ### end Alembic commands ###
26+
27+
28+
def downgrade():
29+
# ### commands auto generated by Alembic - please adjust! ###
30+
op.add_column('tasks', sa.Column('category_type', sa.VARCHAR(), autoincrement=False, nullable=False))
31+
op.drop_column('tasks', 'category')
32+
op.drop_column('tasks', 'illumination_type')
33+
# ### end Alembic commands ###
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""add tables for task
2+
3+
Revision ID: bf333f65b637
4+
Revises: 1a31ce608336
5+
Create Date: 2025-03-03 16:14:20.916533
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'bf333f65b637'
15+
down_revision = '1a31ce608336'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.create_table('tasks',
23+
sa.Column('id', sa.Integer(), nullable=False),
24+
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
25+
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
26+
sa.Column('test_cases', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
27+
sa.Column('constraints', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
28+
sa.Column('difficulty', sa.Enum('ONE_STAR', 'TWO_STAR', 'THREE_STAR', 'FOUR_STAR', 'FIVE_STAR', name='taskdifficulty'), nullable=False),
29+
sa.Column('tags', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
30+
sa.Column('category_type', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
31+
sa.Column('hint', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
32+
sa.Column('created_at', sa.DateTime(), nullable=False),
33+
sa.Column('is_active', sa.Boolean(), nullable=False),
34+
sa.PrimaryKeyConstraint('id')
35+
)
36+
op.create_index(op.f('ix_tasks_id'), 'tasks', ['id'], unique=False)
37+
op.create_table('solutions',
38+
sa.Column('id', sa.Integer(), nullable=False),
39+
sa.Column('task_id', sa.Integer(), nullable=False),
40+
sa.Column('solution_code', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
41+
sa.Column('is_correct', sa.Boolean(), nullable=False),
42+
sa.Column('created_at', sa.DateTime(), nullable=False),
43+
sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], ),
44+
sa.PrimaryKeyConstraint('id')
45+
)
46+
op.create_index(op.f('ix_solutions_id'), 'solutions', ['id'], unique=False)
47+
# ### end Alembic commands ###
48+
49+
50+
def downgrade():
51+
# ### commands auto generated by Alembic - please adjust! ###
52+
op.drop_index(op.f('ix_solutions_id'), table_name='solutions')
53+
op.drop_table('solutions')
54+
op.drop_index(op.f('ix_tasks_id'), table_name='tasks')
55+
op.drop_table('tasks')
56+
# ### end Alembic commands ###

backend/app/api/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
from fastapi import APIRouter
22

3-
from app.api.routes import items, login, private, users, utils
3+
from app.api.routes import items, login, private, users, utils, illuminations, task
44
from app.core.config import settings
55

66
api_router = APIRouter()
77
api_router.include_router(login.router)
88
api_router.include_router(users.router)
99
api_router.include_router(utils.router)
1010
api_router.include_router(items.router)
11+
api_router.include_router(illuminations.router)
12+
api_router.include_router(task.router)
1113

1214

1315
if settings.ENVIRONMENT == "local":
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from fastapi import APIRouter, HTTPException
2+
from app.api.deps import SessionDep
3+
from app.models import (
4+
Task,
5+
IlluminationPublic,
6+
TaskPublic
7+
)
8+
9+
router = APIRouter(
10+
prefix="/illuminations",
11+
tags=["illuminations"]
12+
)
13+
14+
@router.get("/{illumination_type}", response_model=IlluminationPublic)
15+
def read_by_illumination_type(illumination_type: str, session: SessionDep):
16+
tasks = session.query(Task).filter(Task.illumination_type == illumination_type).all()
17+
tasks = [TaskPublic(
18+
id=task.id,
19+
title=task.title,
20+
description=task.description,
21+
difficulty=task.difficulty,
22+
illumination_type=task.illumination_type,
23+
category=task.category,
24+
created_at=task.created_at
25+
) for task in tasks]
26+
27+
if not tasks:
28+
raise HTTPException(status_code=404, detail="No illuminations found")
29+
return IlluminationPublic(illumination_type=illumination_type, tasks=tasks)

backend/app/api/routes/task.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from fastapi import APIRouter, HTTPException
2+
from typing import Optional
3+
from app.api.deps import SessionDep
4+
from app.models import (
5+
Task,
6+
TaskPublic
7+
)
8+
9+
router = APIRouter(
10+
prefix="/task",
11+
tags=["task"]
12+
)
13+
14+
@router.get("/{id}", response_model=TaskPublic)
15+
def read_task(id: Optional[int], session: SessionDep):
16+
task = session.get(Task, id)
17+
task = TaskPublic(
18+
id=task.id,
19+
title=task.title,
20+
description=task.description,
21+
difficulty=task.difficulty,
22+
illumination_type=task.illumination_type,
23+
category=task.category,
24+
created_at=task.created_at
25+
)
26+
if not task:
27+
raise HTTPException(status_code=404, detail="No task found")
28+
return task

backend/app/core/db.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
from app import crud
44
from app.core.config import settings
5-
from app.models import User, UserCreate
5+
from app.models import User, UserCreate, Task, TaskDifficulty
6+
from datetime import datetime
67

78
engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))
89

@@ -31,3 +32,53 @@ def init_db(session: Session) -> None:
3132
is_superuser=True,
3233
)
3334
user = crud.create_user(session=session, user_create=user_in)
35+
36+
def init_tasks(session: Session):
37+
task = session.exec(
38+
select(Task).where(Task.illumination_type == "interview")
39+
).first()
40+
41+
if not task:
42+
initial_tasks = [
43+
Task(
44+
title="Сумма чисел",
45+
description="Напишите программу, которая считает сумму двух чисел.",
46+
test_cases='[{"input": "2 3", "output": "5"}, {"input": "10 15", "output": "25"}]',
47+
constraints='{"time_limit": "1s", "memory_limit": "256MB"}',
48+
difficulty=TaskDifficulty.ONE_STAR,
49+
tags='["interview"]',
50+
illumination_type='interview',
51+
category='Числа',
52+
hint="Попробуйте использовать встроенные арифметические операции.",
53+
created_at=datetime.now(),
54+
is_active=True
55+
),
56+
Task(
57+
title="Фибоначчи",
58+
description="Определите n-е число Фибоначчи.",
59+
test_cases='[{"input": "10", "output": "55"}, {"input": "20", "output": "6765"}]',
60+
constraints='{"time_limit": "2s", "memory_limit": "256MB"}',
61+
difficulty=TaskDifficulty.TWO_STAR,
62+
tags='["interview"]',
63+
illumination_type='interview',
64+
category='Числа',
65+
hint="Используйте рекурсию или динамическое программирование для оптимизации.",
66+
created_at=datetime.now(),
67+
is_active=True
68+
),
69+
Task(
70+
title="Обратная строка",
71+
description="Напишити функцию, которая переворачивает строку.",
72+
test_cases='[{"input": "10", "output": "55"}, {"input": "20", "output": "6765"}]',
73+
constraints='{"time_limit": "2s", "memory_limit": "256MB"}',
74+
difficulty=TaskDifficulty.THREE_STAR,
75+
tags='["interview"]',
76+
illumination_type='interview',
77+
category='Строки',
78+
hint="Используйте метод двух указателей.",
79+
created_at=datetime.now(),
80+
is_active=True
81+
)
82+
]
83+
session.add_all(initial_tasks)
84+
session.commit()

backend/app/initial_data.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from sqlmodel import Session
44

5-
from app.core.db import engine, init_db
5+
from app.core.db import engine, init_db, init_tasks
66

77
logging.basicConfig(level=logging.INFO)
88
logger = logging.getLogger(__name__)
@@ -11,6 +11,7 @@
1111
def init() -> None:
1212
with Session(engine) as session:
1313
init_db(session)
14+
init_tasks(session)
1415

1516

1617
def main() -> None:

backend/app/models.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
from typing import Optional
12
import uuid
23

3-
from pydantic import EmailStr
4+
from pydantic import EmailStr, BaseModel
45
from sqlmodel import Field, Relationship, SQLModel
6+
from datetime import datetime
7+
import enum
58

69

710
# Shared properties
@@ -112,3 +115,53 @@ class TokenPayload(SQLModel):
112115
class NewPassword(SQLModel):
113116
token: str
114117
new_password: str = Field(min_length=8, max_length=40)
118+
119+
class TaskDifficulty(enum.Enum):
120+
ONE_STAR = "one_star"
121+
TWO_STAR = "two_star"
122+
THREE_STAR = "three_star"
123+
FOUR_STAR = "four_star"
124+
FIVE_STAR = "five_star"
125+
126+
127+
class Task(SQLModel, table=True):
128+
__tablename__ = "tasks"
129+
130+
id: Optional[int] = Field(default=None, primary_key=True, index=True)
131+
title: str = Field(..., max_length=255, description="Заголовок задачи (короткое описание)")
132+
description: str = Field(..., description="Полный текст задачи (условие)")
133+
test_cases: str = Field(..., description="JSON-объект с тестами (входные данные и ожидание ответа)")
134+
constraints: str = Field(default=None, description="JSON-объект с ограничениями (например, время/память)")
135+
difficulty: TaskDifficulty = Field(default=TaskDifficulty.ONE_STAR, description="Сложность задачи (1-5 звезд)")
136+
tags: str = Field(default=None, description="Тема задачи или теги (например, ['dynamic programming', 'math'])")
137+
illumination_type: str = Field(default=None, description="Тип иллюминации, к которой относится задача (interview, kids)")
138+
category: str = Field(description="Категория задачи (строки, числа, связные списки и пр.)")
139+
hint: Optional[str] = Field(default=None, description="Подсказка к задаче")
140+
created_at: datetime = Field(default_factory=datetime, description="Дата создания задачи")
141+
is_active: bool = Field(default=True, description="Активность задачи (например, скрытие её в системе)")
142+
solutions: list["Solution"] = Relationship(back_populates="task")
143+
144+
145+
class Solution(SQLModel, table=True):
146+
__tablename__ = "solutions"
147+
148+
id: Optional[int] = Field(default=None, primary_key=True, index=True)
149+
task_id: int = Field(..., foreign_key="tasks.id", description="Связь с задачей")
150+
solution_code: str = Field(..., description="Текст решения (например, Python-код)")
151+
is_correct: bool = Field(default=False, description="Флаг корректности решения")
152+
created_at: datetime = Field(default_factory=datetime.utcnow, description="Дата создания решения")
153+
task: Task = Relationship(back_populates="solutions")
154+
155+
class TaskPublic(BaseModel):
156+
id: Optional[int] = Field(default=None, primary_key=True, index=True)
157+
title: str = Field(..., max_length=255, description="Заголовок задачи (короткое описание)")
158+
description: str = Field(..., description="Полный текст задачи (условие)")
159+
difficulty: TaskDifficulty = Field(default=TaskDifficulty.ONE_STAR, description="Сложность задачи (1-5 звезд)")
160+
illumination_type: str = Field(default=None, description="Тип иллюминации, к которой относится задача (interview, kids)")
161+
category: str = Field(description="Категория задачи (строки, числа, связные списки и пр.)")
162+
created_at: datetime = Field(default_factory=datetime, description="Дата создания задачи")
163+
164+
class IlluminationPublic(SQLModel):
165+
illumination_type: str
166+
tasks: list[TaskPublic]
167+

0 commit comments

Comments
 (0)