This commit is contained in:
Artem Tsyrulnikov
2026-04-06 19:27:27 +03:00
commit 477ddb8bf9
38 changed files with 1916 additions and 0 deletions

0
app/routers/__init__.py Normal file
View File

127
app/routers/hackathons.py Normal file
View File

@@ -0,0 +1,127 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlmodel import Session, select
from app.connection import get_session
from app.models.hackathon import Hackathon
from app.models.hackathon_participant import HackathonParticipant
from app.models.user import User
from app.schemas.hackathon import (
HackathonCreate,
HackathonRead,
HackathonReadWithOrganizer,
ParticipantRegistration,
ParticipantRead,
ParticipantConfirm,
)
from app.auth.dependencies import get_current_user, get_current_organizer
router = APIRouter(prefix="/hackathons", tags=["Hackathons"])
# ── CRUD ────────────────────────────────────────────────────────────────────
@router.post("/", response_model=HackathonRead, status_code=status.HTTP_201_CREATED)
def create_hackathon(
data: HackathonCreate,
session: Session = Depends(get_session),
organizer: User = Depends(get_current_organizer),
) -> Hackathon:
hackathon = Hackathon(**data.model_dump(), organizer_id=organizer.id)
session.add(hackathon)
session.commit()
session.refresh(hackathon)
return hackathon
@router.get("/", response_model=list[HackathonRead])
def list_hackathons(session: Session = Depends(get_session)) -> list[Hackathon]:
return list(session.exec(select(Hackathon)).all())
@router.get("/{hackathon_id}", response_model=HackathonReadWithOrganizer)
def get_hackathon(hackathon_id: int, session: Session = Depends(get_session)) -> Hackathon:
hackathon = session.get(Hackathon, hackathon_id)
if not hackathon:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Хакатон не найден")
return hackathon
@router.delete("/{hackathon_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_hackathon(
hackathon_id: int,
session: Session = Depends(get_session),
organizer: User = Depends(get_current_organizer),
) -> None:
hackathon = session.get(Hackathon, hackathon_id)
if not hackathon:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Хакатон не найден")
session.delete(hackathon)
session.commit()
# ── Регистрация участников ──────────────────────────────────────────────────
@router.post("/{hackathon_id}/register", response_model=ParticipantRead, status_code=status.HTTP_201_CREATED)
def register_for_hackathon(
hackathon_id: int,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_user),
) -> HackathonParticipant:
hackathon = session.get(Hackathon, hackathon_id)
if not hackathon:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Хакатон не найден")
existing = session.exec(
select(HackathonParticipant).where(
HackathonParticipant.user_id == current_user.id,
HackathonParticipant.hackathon_id == hackathon_id,
)
).first()
if existing:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Вы уже зарегистрированы")
participant = HackathonParticipant(
user_id=current_user.id,
hackathon_id=hackathon_id,
)
session.add(participant)
session.commit()
session.refresh(participant)
return participant
@router.get("/{hackathon_id}/participants", response_model=list[ParticipantRead])
def list_participants(
hackathon_id: int,
session: Session = Depends(get_session),
) -> list[HackathonParticipant]:
return list(
session.exec(
select(HackathonParticipant).where(HackathonParticipant.hackathon_id == hackathon_id)
).all()
)
@router.patch("/{hackathon_id}/confirm", response_model=ParticipantRead)
def confirm_participant(
hackathon_id: int,
data: ParticipantConfirm,
session: Session = Depends(get_session),
organizer: User = Depends(get_current_organizer),
) -> HackathonParticipant:
"""Организатор подтверждает регистрацию участника."""
participant = session.exec(
select(HackathonParticipant).where(
HackathonParticipant.user_id == data.user_id,
HackathonParticipant.hackathon_id == hackathon_id,
)
).first()
if not participant:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Регистрация не найдена")
participant.is_confirmed = True
session.add(participant)
session.commit()
session.refresh(participant)
return participant

View File

@@ -0,0 +1,59 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlmodel import Session, select
from app.connection import get_session
from app.models.submission import Submission
from app.models.user import User
from app.schemas.submission import SubmissionCreate, SubmissionRead, SubmissionReadWithDetails
from app.auth.dependencies import get_current_user
router = APIRouter(prefix="/submissions", tags=["Submissions"])
@router.post("/", response_model=SubmissionRead, status_code=status.HTTP_201_CREATED)
def create_submission(
data: SubmissionCreate,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_user),
) -> Submission:
submission = Submission(**data.model_dump())
session.add(submission)
session.commit()
session.refresh(submission)
return submission
@router.get("/", response_model=list[SubmissionRead])
def list_submissions(
task_id: int | None = None,
team_id: int | None = None,
session: Session = Depends(get_session),
) -> list[Submission]:
query = select(Submission)
if task_id is not None:
query = query.where(Submission.task_id == task_id)
if team_id is not None:
query = query.where(Submission.team_id == team_id)
return list(session.exec(query).all())
@router.get("/{submission_id}", response_model=SubmissionReadWithDetails)
def get_submission(submission_id: int, session: Session = Depends(get_session)) -> Submission:
"""Возвращает работу с вложенными данными команды и задачи."""
submission = session.get(Submission, submission_id)
if not submission:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Работа не найдена")
return submission
@router.delete("/{submission_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_submission(
submission_id: int,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_user),
) -> None:
submission = session.get(Submission, submission_id)
if not submission:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Работа не найдена")
session.delete(submission)
session.commit()

73
app/routers/tasks.py Normal file
View File

@@ -0,0 +1,73 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlmodel import Session, select
from app.connection import get_session
from app.models.task import Task
from app.models.user import User
from app.schemas.task import TaskCreate, TaskRead, TaskUpdate
from app.auth.dependencies import get_current_organizer
router = APIRouter(prefix="/tasks", tags=["Tasks"])
@router.post("/", response_model=TaskRead, status_code=status.HTTP_201_CREATED)
def create_task(
data: TaskCreate,
session: Session = Depends(get_session),
organizer: User = Depends(get_current_organizer),
) -> Task:
task = Task(**data.model_dump())
session.add(task)
session.commit()
session.refresh(task)
return task
@router.get("/", response_model=list[TaskRead])
def list_tasks(
hackathon_id: int | None = None,
session: Session = Depends(get_session),
) -> list[Task]:
query = select(Task)
if hackathon_id is not None:
query = query.where(Task.hackathon_id == hackathon_id)
return list(session.exec(query).all())
@router.get("/{task_id}", response_model=TaskRead)
def get_task(task_id: int, session: Session = Depends(get_session)) -> Task:
task = session.get(Task, task_id)
if not task:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Задача не найдена")
return task
@router.patch("/{task_id}", response_model=TaskRead)
def update_task(
task_id: int,
data: TaskUpdate,
session: Session = Depends(get_session),
organizer: User = Depends(get_current_organizer),
) -> Task:
task = session.get(Task, task_id)
if not task:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Задача не найдена")
for field, value in data.model_dump(exclude_unset=True).items():
setattr(task, field, value)
session.add(task)
session.commit()
session.refresh(task)
return task
@router.delete("/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_task(
task_id: int,
session: Session = Depends(get_session),
organizer: User = Depends(get_current_organizer),
) -> None:
task = session.get(Task, task_id)
if not task:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Задача не найдена")
session.delete(task)
session.commit()

118
app/routers/teams.py Normal file
View File

@@ -0,0 +1,118 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlmodel import Session, select
from app.connection import get_session
from app.models.team import Team
from app.models.team_member import TeamMember
from app.models.user import User
from app.schemas.team import (
TeamCreate,
TeamRead,
TeamReadWithMembers,
TeamMemberAdd,
TeamMemberRead,
)
from app.auth.dependencies import get_current_user
router = APIRouter(prefix="/teams", tags=["Teams"])
@router.post("/", response_model=TeamRead, status_code=status.HTTP_201_CREATED)
def create_team(
data: TeamCreate,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_user),
) -> Team:
team = Team(**data.model_dump())
session.add(team)
session.commit()
session.refresh(team)
# Создатель автоматически становится участником команды
member = TeamMember(user_id=current_user.id, team_id=team.id, role="captain")
session.add(member)
session.commit()
session.refresh(team)
return team
@router.get("/", response_model=list[TeamRead])
def list_teams(
hackathon_id: int | None = None,
session: Session = Depends(get_session),
) -> list[Team]:
query = select(Team)
if hackathon_id is not None:
query = query.where(Team.hackathon_id == hackathon_id)
return list(session.exec(query).all())
@router.get("/{team_id}", response_model=TeamReadWithMembers)
def get_team(team_id: int, session: Session = Depends(get_session)) -> Team:
"""Возвращает команду с вложенным списком участников (one-to-many)."""
team = session.get(Team, team_id)
if not team:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Команда не найдена")
return team
@router.delete("/{team_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_team(
team_id: int,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_user),
) -> None:
team = session.get(Team, team_id)
if not team:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Команда не найдена")
session.delete(team)
session.commit()
# ── Участники команды ───────────────────────────────────────────────────────
@router.post("/{team_id}/members", response_model=TeamMemberRead, status_code=status.HTTP_201_CREATED)
def add_member(
team_id: int,
data: TeamMemberAdd,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_user),
) -> TeamMember:
team = session.get(Team, team_id)
if not team:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Команда не найдена")
existing = session.exec(
select(TeamMember).where(
TeamMember.user_id == data.user_id,
TeamMember.team_id == team_id,
)
).first()
if existing:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Пользователь уже в команде")
member = TeamMember(user_id=data.user_id, team_id=team_id, role=data.role)
session.add(member)
session.commit()
session.refresh(member)
return member
@router.delete("/{team_id}/members/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def remove_member(
team_id: int,
user_id: int,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_user),
) -> None:
member = session.exec(
select(TeamMember).where(
TeamMember.user_id == user_id,
TeamMember.team_id == team_id,
)
).first()
if not member:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Участник не найден")
session.delete(member)
session.commit()

66
app/routers/users.py Normal file
View File

@@ -0,0 +1,66 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlmodel import Session, select
from app.connection import get_session
from app.models.user import User
from app.schemas.user import UserRead, UserUpdate, PasswordChange
from app.auth.dependencies import get_current_user
from app.auth.utils import verify_password, hash_password
router = APIRouter(prefix="/users", tags=["Users"])
@router.get("/me", response_model=UserRead)
def get_me(current_user: User = Depends(get_current_user)) -> User:
return current_user
@router.get("/", response_model=list[UserRead])
def list_users(
session: Session = Depends(get_session),
current_user: User = Depends(get_current_user),
) -> list[User]:
return list(session.exec(select(User)).all())
@router.get("/{user_id}", response_model=UserRead)
def get_user(
user_id: int,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_user),
) -> User:
user = session.get(User, user_id)
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Пользователь не найден")
return user
@router.patch("/me", response_model=UserRead)
def update_me(
data: UserUpdate,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_user),
) -> User:
for field, value in data.model_dump(exclude_unset=True).items():
setattr(current_user, field, value)
session.add(current_user)
session.commit()
session.refresh(current_user)
return current_user
@router.post("/me/change-password", status_code=status.HTTP_200_OK)
def change_password(
data: PasswordChange,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_user),
) -> dict:
if not verify_password(data.old_password, current_user.hashed_password):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Неверный старый пароль",
)
current_user.hashed_password = hash_password(data.new_password)
session.add(current_user)
session.commit()
return {"detail": "Пароль успешно изменён"}