Skip to content
Merged
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
163 changes: 160 additions & 3 deletions backend/app/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy import and_
from sqlalchemy.exc import IntegrityError
from app.models.database_models import User, TrainingProfile, ActiveSession, Course, UserCourseProgress, Training, SavedProgram, TrainingProgress
from app.models.database_models import User, TrainingProfile, ActiveSession, Course, UserCourseProgress, Training, SavedProgram, TrainingProgress, TrainingSchedule
from passlib.context import CryptContext
from datetime import datetime, timedelta
from typing import Optional, List, Dict, Any
Expand Down Expand Up @@ -481,7 +481,18 @@ def is_program_saved_by_user(db: Session, user_id: int, training_id: int) -> boo

def get_training_by_course_id(db: Session, course_id: str) -> Optional[Training]:
"""Получить тренировку по course_id"""
return db.query(Training).filter(Training.course_id == course_id).first()
training = db.query(Training).filter(Training.course_id == course_id).first()
if not training:
print(f"⚠️ Training with course_id '{course_id}' not found in database")
return training


def get_available_course_ids(db: Session) -> List[str]:
"""Получить список всех доступных course_id"""
trainings = db.query(Training.course_id, Training.course_title).all()
course_ids = [f"{t.course_id} ({t.course_title})" for t in trainings]
print(f"📋 Available course_ids: {course_ids}")
return [t.course_id for t in trainings]


# Training Progress CRUD operations
Expand Down Expand Up @@ -631,4 +642,150 @@ def get_training_progress_by_course_id(db: Session, user_id: int, course_id: str
if not training:
return None

return get_training_progress(db, user_id, training.id)
return get_training_progress(db, user_id, training.id)


# ===== TRAINING SCHEDULE (TRACKER) CRUD FUNCTIONS =====

def save_user_schedule(db: Session, user_id: int, schedule_data: List[Dict[str, Any]]) -> int:
"""Сохранить расписание пользователя"""
try:
added_count = 0

for item in schedule_data:
# Нормализуем дату: убираем лишние пробелы
normalized_date = item["date"].strip()

# Проверяем, существует ли тренировка с таким course_id
training = get_training_by_course_id(db, item["course_id"])
if not training:
print(f"❌ Training not found for course_id: {item['course_id']}, skipping...")
continue

# Проверяем, не существует ли уже такая запись
existing = db.query(TrainingSchedule).filter(
and_(
TrainingSchedule.user_id == user_id,
TrainingSchedule.course_id == item["course_id"],
TrainingSchedule.date == normalized_date,
TrainingSchedule.training_index == item["index"]
)
).first()

if not existing:
schedule_instance = TrainingSchedule(
user_id=user_id,
course_id=item["course_id"],
date=normalized_date,
training_index=item["index"]
)
db.add(schedule_instance)
added_count += 1
print(f"📅 Adding schedule: user={user_id}, course={item['course_id']}, date='{normalized_date}', index={item['index']}")
else:
print(f"ℹ️ Schedule already exists: user={user_id}, course={item['course_id']}, date='{normalized_date}', index={item['index']}")

db.commit()
print(f"✅ Added {added_count} schedule instances for user {user_id}")
return added_count

except Exception as e:
db.rollback()
print(f"❌ Error saving schedule: {e}")
raise


def get_user_schedule(db: Session, user_id: int, course_id: Optional[str] = None) -> List[TrainingSchedule]:
"""Получить расписание пользователя, опционально фильтруя по course_id"""
query = db.query(TrainingSchedule).filter(TrainingSchedule.user_id == user_id)

if course_id:
query = query.filter(TrainingSchedule.course_id == course_id)

return query.order_by(TrainingSchedule.date, TrainingSchedule.training_index).all()


def get_trainings_by_date(db: Session, user_id: int, date: str) -> List[Dict[str, Any]]:
"""Получить все тренировки на конкретную дату"""
# Нормализуем дату: убираем лишние пробелы
normalized_date = date.strip()

# Получаем все записи расписания для пользователя на эту дату
schedule_items = db.query(TrainingSchedule).filter(
and_(
TrainingSchedule.user_id == user_id,
TrainingSchedule.date == normalized_date
)
).all()

trainings = []

for item in schedule_items:
# Получаем тренировку по course_id
training = get_training_by_course_id(db, item.course_id)

if training and training.training_plan:
# Проверяем, что индекс не выходит за границы
if 0 <= item.training_index < len(training.training_plan):
training_day = training.training_plan[item.training_index]

training_info = {
"course_id": item.course_id,
"course_title": training.course_title,
"training_index": item.training_index,
"training_day": training_day
}
trainings.append(training_info)

return trainings


def delete_user_schedule(db: Session, user_id: int, course_id: str) -> int:
"""Удалить все расписание для конкретного курса пользователя"""
try:
deleted_count = db.query(TrainingSchedule).filter(
and_(
TrainingSchedule.user_id == user_id,
TrainingSchedule.course_id == course_id
)
).count()

db.query(TrainingSchedule).filter(
and_(
TrainingSchedule.user_id == user_id,
TrainingSchedule.course_id == course_id
)
).delete()

db.commit()
print(f"🗑️ Deleted {deleted_count} schedule instances for user {user_id}, course {course_id}")
return deleted_count

except Exception as e:
db.rollback()
print(f"❌ Error deleting schedule: {e}")
raise


def get_user_calendar_dates(db: Session, user_id: int) -> List[str]:
"""Получить все даты с тренировками для календаря пользователя"""
dates = db.query(TrainingSchedule.date).filter(
TrainingSchedule.user_id == user_id
).distinct().order_by(TrainingSchedule.date).all()

date_list = [date[0] for date in dates]
print(f"📅 Calendar dates for user {user_id}: {date_list}")
return date_list


def debug_all_schedules(db: Session) -> None:
"""Отладочная функция для просмотра всех записей в таблице training_schedule"""
all_schedules = db.query(TrainingSchedule).all()
print(f"🔍 DEBUG: Total records in training_schedule table: {len(all_schedules)}")

if all_schedules:
print(f"📋 All schedule records:")
for item in all_schedules:
print(f" - ID: {item.id}, User: {item.user_id}, Date: '{item.date}', Course: {item.course_id}, Index: {item.training_index}")
else:
print(f"❌ No records found in training_schedule table")
25 changes: 24 additions & 1 deletion backend/app/models/database_models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, Text, JSON, ForeignKey, UniqueConstraint
from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, Text, JSON, ForeignKey, UniqueConstraint, Index
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.database import Base
Expand Down Expand Up @@ -216,4 +216,27 @@ class TrainingProgress(Base):
# Уникальная комбинация пользователя и тренировки
__table_args__ = (
UniqueConstraint('user_id', 'training_id', name='unique_user_training_progress'),
)


class TrainingSchedule(Base):
"""
SQLAlchemy модель для расписания тренировок пользователей
"""
__tablename__ = "training_schedule"

id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
course_id = Column(String, nullable=False) # ID тренировочного плана
date = Column(String(10), nullable=False) # формат "ДД.ММ.ГГГГ"
training_index = Column(Integer, nullable=False) # номер в training_plan (0, 1, 2...)
created_at = Column(DateTime, default=datetime.utcnow)

# Связи
user = relationship("User")

# Составной индекс для быстрого поиска
__table_args__ = (
Index('idx_user_date', 'user_id', 'date'),
Index('idx_user_course', 'user_id', 'course_id'),
)
63 changes: 63 additions & 0 deletions backend/app/models/tracker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
from datetime import datetime


class ScheduleInstance(BaseModel):
"""
Модель для одного элемента расписания
"""
date: str = Field(..., description="Дата в формате ДД.ММ.ГГГГ", example="21.07.2025")
index: int = Field(..., ge=0, description="Номер тренировки в training_plan (начиная с 0)")
course_id: str = Field(..., description="ID тренировочного плана")


class AddScheduleRequest(BaseModel):
"""
Запрос на добавление расписания
"""
schedule: List[ScheduleInstance] = Field(..., description="Список элементов расписания")


class ScheduleResponse(BaseModel):
"""
Ответ с расписанием пользователя
"""
user_id: int = Field(..., description="ID пользователя")
total_instances: int = Field(..., description="Общее количество элементов расписания")
schedule: List[ScheduleInstance] = Field(..., description="Список элементов расписания")


class TrainingDayInfo(BaseModel):
"""
Информация о дне тренировки из training_plan
"""
course_id: str = Field(..., description="ID курса")
course_title: str = Field(..., description="Название курса")
training_index: int = Field(..., description="Номер тренировки в плане")
training_day: Dict[str, Any] = Field(..., description="Полная информация о тренировочном дне")


class TrainingByDateResponse(BaseModel):
"""
Ответ с тренировками на конкретную дату
"""
date: str = Field(..., description="Дата в формате ДД.ММ.ГГГГ")
trainings: List[TrainingDayInfo] = Field(..., description="Список тренировок на эту дату")


class AddScheduleResponse(BaseModel):
"""
Ответ на добавление расписания
"""
message: str = Field(..., description="Сообщение о результате")
added_instances: int = Field(..., description="Количество добавленных элементов")
user_id: int = Field(..., description="ID пользователя")


class DeleteScheduleResponse(BaseModel):
"""
Ответ на удаление расписания
"""
message: str = Field(..., description="Сообщение о результате")
deleted_instances: int = Field(..., description="Количество удаленных элементов")
Loading