initial commit
This commit is contained in:
commit
39fdd43d8e
127 files changed
+13919
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,159 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import User, Workout
|
||||
from app.routers.auth import require_auth
|
||||
from app.schemas import (
|
||||
AdjustSetRequest,
|
||||
CompleteSetRequest,
|
||||
MoveWorkoutRequest,
|
||||
PromptChoiceRequest,
|
||||
SetWeightRequest,
|
||||
WorkoutDetail,
|
||||
)
|
||||
from app.services.auth import get_or_create_profile
|
||||
from app.services.reschedule import plan_move
|
||||
from app.services.workout import (
|
||||
adjust_completed_set,
|
||||
complete_set,
|
||||
load_workout,
|
||||
pause_workout,
|
||||
resolve_prompt,
|
||||
resume_workout,
|
||||
set_exercise_weight,
|
||||
start_workout,
|
||||
workout_detail,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/workouts", tags=["workout-actions"])
|
||||
|
||||
|
||||
@router.post("/{workout_id}/start", response_model=WorkoutDetail)
|
||||
def api_start(workout_id: int, _: User = Depends(require_auth), db: Session = Depends(get_db)) -> WorkoutDetail:
|
||||
w = start_workout(db, load_workout(db, workout_id))
|
||||
return workout_detail(db, w)
|
||||
|
||||
|
||||
@router.post("/{workout_id}/pause", response_model=WorkoutDetail)
|
||||
def api_pause(workout_id: int, _: User = Depends(require_auth), db: Session = Depends(get_db)) -> WorkoutDetail:
|
||||
w = pause_workout(db, load_workout(db, workout_id))
|
||||
return workout_detail(db, w)
|
||||
|
||||
|
||||
@router.post("/{workout_id}/resume", response_model=WorkoutDetail)
|
||||
def api_resume(workout_id: int, _: User = Depends(require_auth), db: Session = Depends(get_db)) -> WorkoutDetail:
|
||||
w = resume_workout(db, load_workout(db, workout_id))
|
||||
return workout_detail(db, w)
|
||||
|
||||
|
||||
@router.post("/{workout_id}/complete-set", response_model=WorkoutDetail)
|
||||
def api_complete_set(
|
||||
workout_id: int,
|
||||
body: CompleteSetRequest,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> WorkoutDetail:
|
||||
w = complete_set(
|
||||
db,
|
||||
load_workout(db, workout_id),
|
||||
actual_reps=body.actual_reps,
|
||||
actual_weight_kg=body.actual_weight_kg,
|
||||
)
|
||||
return workout_detail(db, w)
|
||||
|
||||
|
||||
@router.post("/{workout_id}/exercises/{exercise_id}/weight", response_model=WorkoutDetail)
|
||||
def api_set_weight(
|
||||
workout_id: int,
|
||||
exercise_id: int,
|
||||
body: SetWeightRequest,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> WorkoutDetail:
|
||||
workout = load_workout(db, workout_id)
|
||||
exercise = next((e for e in workout.exercises if e.id == exercise_id), None)
|
||||
if not exercise:
|
||||
raise HTTPException(404, "Упражнение не найдено")
|
||||
w = set_exercise_weight(db, workout, exercise, body.weight_kg)
|
||||
return workout_detail(db, w)
|
||||
|
||||
|
||||
@router.post("/{workout_id}/exercises/{exercise_id}/sets/{set_id}", response_model=WorkoutDetail)
|
||||
def api_adjust_set(
|
||||
workout_id: int,
|
||||
exercise_id: int,
|
||||
set_id: int,
|
||||
body: AdjustSetRequest,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> WorkoutDetail:
|
||||
workout = load_workout(db, workout_id)
|
||||
exercise = next((e for e in workout.exercises if e.id == exercise_id), None)
|
||||
if not exercise:
|
||||
raise HTTPException(404, "Упражнение не найдено")
|
||||
workout_set = next((s for s in exercise.sets if s.id == set_id), None)
|
||||
if not workout_set:
|
||||
raise HTTPException(404, "Подход не найден")
|
||||
w = adjust_completed_set(
|
||||
db,
|
||||
workout,
|
||||
exercise,
|
||||
workout_set,
|
||||
actual_weight_kg=body.actual_weight_kg,
|
||||
actual_reps=body.actual_reps,
|
||||
)
|
||||
return workout_detail(db, w)
|
||||
|
||||
|
||||
@router.post("/{workout_id}/move", response_model=WorkoutDetail)
|
||||
def api_move(
|
||||
workout_id: int,
|
||||
body: MoveWorkoutRequest,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> WorkoutDetail:
|
||||
workout = load_workout(db, workout_id)
|
||||
profile = get_or_create_profile(db)
|
||||
weekdays = profile.training_weekdays or []
|
||||
if len(weekdays) != 3:
|
||||
raise HTTPException(400, "Не заданы дни тренировок")
|
||||
rows = db.query(Workout).filter(Workout.cycle_number == workout.cycle_number).all()
|
||||
assigned = plan_move(rows, workout_id, body.scheduled_date, list(weekdays))
|
||||
for row in rows:
|
||||
if row.id in assigned:
|
||||
row.scheduled_date = date(2099, 1, 1) + timedelta(days=row.id % 300)
|
||||
db.flush()
|
||||
by_id = {row.id: row for row in rows}
|
||||
for workout_row_id, scheduled in assigned.items():
|
||||
by_id[workout_row_id].scheduled_date = scheduled
|
||||
db.commit()
|
||||
return workout_detail(db, load_workout(db, workout_id))
|
||||
|
||||
|
||||
@router.post("/{workout_id}/exercises/{exercise_id}/prompt", response_model=WorkoutDetail)
|
||||
def api_prompt(
|
||||
workout_id: int,
|
||||
exercise_id: int,
|
||||
body: PromptChoiceRequest,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> WorkoutDetail:
|
||||
workout = load_workout(db, workout_id)
|
||||
exercise = next((e for e in workout.exercises if e.id == exercise_id), None)
|
||||
if not exercise:
|
||||
raise HTTPException(404, "Упражнение не найдено")
|
||||
w = resolve_prompt(
|
||||
db,
|
||||
workout,
|
||||
exercise,
|
||||
choice=body.choice,
|
||||
weight_kg=body.weight_kg,
|
||||
increase_kg=body.increase_kg,
|
||||
decrease_kg=body.decrease_kg,
|
||||
)
|
||||
return workout_detail(db, w)
|
||||
@@ -0,0 +1,79 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import User
|
||||
from app.schemas import LoginRequest, SetupPasswordRequest, StatusResponse, TokenResponse
|
||||
from app.services.auth import (
|
||||
create_access_token,
|
||||
decode_token,
|
||||
get_or_create_profile,
|
||||
get_user,
|
||||
has_password,
|
||||
hash_password,
|
||||
verify_password,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
||||
security = HTTPBearer(auto_error=False)
|
||||
|
||||
|
||||
def require_auth(
|
||||
creds: Optional[HTTPAuthorizationCredentials] = Depends(security),
|
||||
db: Session = Depends(get_db),
|
||||
) -> User:
|
||||
if not creds:
|
||||
raise HTTPException(401, "Требуется авторизация")
|
||||
sub = decode_token(creds.credentials)
|
||||
if not sub:
|
||||
raise HTTPException(401, "Недействительный токен")
|
||||
user = get_user(db)
|
||||
if not user:
|
||||
raise HTTPException(401, "Пользователь не найден")
|
||||
return user
|
||||
|
||||
|
||||
@router.get("/status", response_model=StatusResponse)
|
||||
def auth_status(db: Session = Depends(get_db)) -> StatusResponse:
|
||||
profile = get_or_create_profile(db)
|
||||
return StatusResponse(
|
||||
has_password=has_password(db),
|
||||
onboarded=profile.onboarded,
|
||||
phase=profile.phase,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/setup", response_model=TokenResponse)
|
||||
def setup_password(body: SetupPasswordRequest, db: Session = Depends(get_db)) -> TokenResponse:
|
||||
if has_password(db):
|
||||
raise HTTPException(400, "Пароль уже задан")
|
||||
user = User(password_hash=hash_password(body.password), created_at=datetime.utcnow())
|
||||
db.add(user)
|
||||
get_or_create_profile(db)
|
||||
db.commit()
|
||||
profile = get_or_create_profile(db)
|
||||
return TokenResponse(
|
||||
access_token=create_access_token(),
|
||||
needs_setup=False,
|
||||
onboarded=profile.onboarded,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/login", response_model=TokenResponse)
|
||||
def login(body: LoginRequest, db: Session = Depends(get_db)) -> TokenResponse:
|
||||
user = get_user(db)
|
||||
if not user:
|
||||
raise HTTPException(400, "Сначала задайте пароль")
|
||||
if not verify_password(body.password, user.password_hash):
|
||||
raise HTTPException(401, "Неверный пароль")
|
||||
profile = get_or_create_profile(db)
|
||||
return TokenResponse(
|
||||
access_token=create_access_token(),
|
||||
onboarded=profile.onboarded,
|
||||
)
|
||||
@@ -0,0 +1,366 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import AccessoryState, Profile, User, Workout, WorkoutExercise
|
||||
from app.routers.auth import require_auth
|
||||
from app.schemas import (
|
||||
CalendarResponse,
|
||||
ExportResponse,
|
||||
ImportRequest,
|
||||
MessageResponse,
|
||||
OnboardingRequest,
|
||||
ProbeRequest,
|
||||
ProfileResponse,
|
||||
StartRestRequest,
|
||||
TrainingDaysRequest,
|
||||
WorkoutDetail,
|
||||
WorkoutSummary,
|
||||
)
|
||||
from app.services.reschedule import plan_weekday_change
|
||||
from app.services.auth import get_or_create_profile
|
||||
from app.services.schedule import clear_cycle_workouts, generate_schedule
|
||||
from app.services.workout import (
|
||||
load_workout,
|
||||
profile_response,
|
||||
start_rest_phase,
|
||||
submit_probe,
|
||||
workout_detail,
|
||||
workout_summary,
|
||||
)
|
||||
from app.services.weights import round_to_plate
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["app"])
|
||||
|
||||
|
||||
@router.get("/profile", response_model=ProfileResponse)
|
||||
def get_profile(_: User = Depends(require_auth), db: Session = Depends(get_db)) -> ProfileResponse:
|
||||
return profile_response(get_or_create_profile(db))
|
||||
|
||||
|
||||
@router.post("/onboarding", response_model=ProfileResponse)
|
||||
def onboarding(
|
||||
body: OnboardingRequest,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> ProfileResponse:
|
||||
profile = get_or_create_profile(db)
|
||||
if profile.onboarded:
|
||||
raise HTTPException(400, "Онбординг уже пройден. Используйте сброс в настройках.")
|
||||
|
||||
one_rm = round_to_plate(body.one_rm_kg)
|
||||
profile.one_rm_kg = one_rm
|
||||
profile.training_weekdays = body.training_weekdays
|
||||
profile.start_date = body.start_date
|
||||
profile.onboarded = True
|
||||
profile.phase = "active"
|
||||
profile.cycle_number = 1
|
||||
|
||||
clear_cycle_workouts(db, 1)
|
||||
generate_schedule(
|
||||
db,
|
||||
one_rm_kg=one_rm,
|
||||
training_weekdays=body.training_weekdays,
|
||||
start_date=body.start_date,
|
||||
cycle_number=1,
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(profile)
|
||||
return profile_response(profile)
|
||||
|
||||
|
||||
@router.get("/calendar", response_model=CalendarResponse)
|
||||
def calendar(
|
||||
year: int,
|
||||
month: int,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> CalendarResponse:
|
||||
if month < 1 or month > 12:
|
||||
raise HTTPException(400, "Неверный месяц")
|
||||
profile = get_or_create_profile(db)
|
||||
workouts = (
|
||||
db.query(Workout)
|
||||
.options(joinedload(Workout.exercises))
|
||||
.filter(
|
||||
Workout.cycle_number == profile.cycle_number,
|
||||
)
|
||||
.order_by(Workout.scheduled_date)
|
||||
.all()
|
||||
)
|
||||
# filter by month for display but also include all cycle for week strip — return month filtered
|
||||
month_workouts = [w for w in workouts if w.scheduled_date.year == year and w.scheduled_date.month == month]
|
||||
return CalendarResponse(
|
||||
year=year,
|
||||
month=month,
|
||||
workouts=[workout_summary(w) for w in month_workouts],
|
||||
profile=profile_response(profile),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/training-days", response_model=ProfileResponse)
|
||||
def update_training_days(
|
||||
body: TrainingDaysRequest,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> ProfileResponse:
|
||||
profile = get_or_create_profile(db)
|
||||
if not profile.onboarded:
|
||||
raise HTTPException(400, "Сначала создайте программу")
|
||||
rows = db.query(Workout).filter(Workout.cycle_number == profile.cycle_number).all()
|
||||
done = [w.scheduled_date for w in rows if w.status == "completed"]
|
||||
start = date.today()
|
||||
if done:
|
||||
start = max(start, max(done) + timedelta(days=1))
|
||||
assigned = plan_weekday_change(rows, list(body.training_weekdays), start)
|
||||
for row in rows:
|
||||
if row.id in assigned:
|
||||
row.scheduled_date = date(2099, 1, 1) + timedelta(days=row.id % 300)
|
||||
db.flush()
|
||||
by_id = {row.id: row for row in rows}
|
||||
for workout_row_id, scheduled in assigned.items():
|
||||
by_id[workout_row_id].scheduled_date = scheduled
|
||||
profile.training_weekdays = list(body.training_weekdays)
|
||||
db.commit()
|
||||
db.refresh(profile)
|
||||
return profile_response(profile)
|
||||
|
||||
|
||||
@router.get("/workouts/upcoming", response_model=list[WorkoutSummary])
|
||||
def upcoming(
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> list[WorkoutSummary]:
|
||||
profile = get_or_create_profile(db)
|
||||
workouts = (
|
||||
db.query(Workout)
|
||||
.options(joinedload(Workout.exercises))
|
||||
.filter(Workout.cycle_number == profile.cycle_number)
|
||||
.order_by(Workout.scheduled_date)
|
||||
.all()
|
||||
)
|
||||
return [workout_summary(w) for w in workouts]
|
||||
|
||||
|
||||
@router.get("/workouts/{workout_id}", response_model=WorkoutDetail)
|
||||
def get_workout(
|
||||
workout_id: int,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> WorkoutDetail:
|
||||
return workout_detail(db, load_workout(db, workout_id))
|
||||
|
||||
|
||||
@router.post("/rest/start", response_model=ProfileResponse)
|
||||
def rest_start(
|
||||
body: StartRestRequest,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> ProfileResponse:
|
||||
profile = get_or_create_profile(db)
|
||||
return profile_response(start_rest_phase(db, profile, body.rest_days))
|
||||
|
||||
|
||||
@router.post("/probe", response_model=ProfileResponse)
|
||||
def probe(
|
||||
body: ProbeRequest,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> ProfileResponse:
|
||||
profile = get_or_create_profile(db)
|
||||
return profile_response(
|
||||
submit_probe(
|
||||
db,
|
||||
profile,
|
||||
one_rm_kg=body.one_rm_kg,
|
||||
start_date=body.start_date,
|
||||
training_weekdays=body.training_weekdays,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/export", response_model=ExportResponse)
|
||||
def export_data(_: User = Depends(require_auth), db: Session = Depends(get_db)) -> ExportResponse:
|
||||
profile = get_or_create_profile(db)
|
||||
workouts = (
|
||||
db.query(Workout)
|
||||
.options(joinedload(Workout.exercises).joinedload(WorkoutExercise.sets))
|
||||
.order_by(Workout.scheduled_date)
|
||||
.all()
|
||||
)
|
||||
accessories = db.query(AccessoryState).all()
|
||||
|
||||
def dump_workout(w: Workout) -> dict:
|
||||
return {
|
||||
"cycle_number": w.cycle_number,
|
||||
"week_number": w.week_number,
|
||||
"day_index": w.day_index,
|
||||
"scheduled_date": w.scheduled_date.isoformat(),
|
||||
"status": w.status,
|
||||
"started_at": w.started_at.isoformat() if w.started_at else None,
|
||||
"completed_at": w.completed_at.isoformat() if w.completed_at else None,
|
||||
"current_exercise_index": w.current_exercise_index,
|
||||
"current_set_index": w.current_set_index,
|
||||
"exercises": [
|
||||
{
|
||||
"order_index": e.order_index,
|
||||
"name": e.name,
|
||||
"kind": e.kind,
|
||||
"prescribed_weight_kg": e.prescribed_weight_kg,
|
||||
"weight_locked": e.weight_locked,
|
||||
"needs_weight_input": e.needs_weight_input,
|
||||
"pending_prompt": e.pending_prompt,
|
||||
"sets": [
|
||||
{
|
||||
"set_index": s.set_index,
|
||||
"target_percent": s.target_percent,
|
||||
"target_reps": s.target_reps,
|
||||
"target_weight_kg": s.target_weight_kg,
|
||||
"is_failure": s.is_failure,
|
||||
"completed": s.completed,
|
||||
"actual_reps": s.actual_reps,
|
||||
"actual_weight_kg": s.actual_weight_kg,
|
||||
"completed_at": s.completed_at.isoformat() if s.completed_at else None,
|
||||
}
|
||||
for s in sorted(e.sets, key=lambda x: x.set_index)
|
||||
],
|
||||
}
|
||||
for e in sorted(w.exercises, key=lambda x: x.order_index)
|
||||
],
|
||||
}
|
||||
|
||||
return ExportResponse(
|
||||
exported_at=datetime.utcnow(),
|
||||
data={
|
||||
"profile": {
|
||||
"one_rm_kg": profile.one_rm_kg,
|
||||
"training_weekdays": profile.training_weekdays,
|
||||
"start_date": profile.start_date.isoformat() if profile.start_date else None,
|
||||
"onboarded": profile.onboarded,
|
||||
"phase": profile.phase,
|
||||
"rest_until": profile.rest_until.isoformat() if profile.rest_until else None,
|
||||
"probe_available_at": profile.probe_available_at.isoformat()
|
||||
if profile.probe_available_at
|
||||
else None,
|
||||
"cycle_number": profile.cycle_number,
|
||||
},
|
||||
"workouts": [dump_workout(w) for w in workouts],
|
||||
"accessories": [
|
||||
{
|
||||
"cycle_number": a.cycle_number,
|
||||
"exercise_name": a.exercise_name,
|
||||
"weight_kg": a.weight_kg,
|
||||
"locked": a.locked,
|
||||
"pending_weight_kg": a.pending_weight_kg,
|
||||
}
|
||||
for a in accessories
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/import", response_model=MessageResponse)
|
||||
def import_data(
|
||||
body: ImportRequest,
|
||||
_: User = Depends(require_auth),
|
||||
db: Session = Depends(get_db),
|
||||
) -> MessageResponse:
|
||||
if body.version != 1:
|
||||
raise HTTPException(400, "Неподдерживаемая версия экспорта")
|
||||
data = body.data
|
||||
# full replace of progress
|
||||
db.query(Workout).delete()
|
||||
db.query(AccessoryState).delete()
|
||||
profile = get_or_create_profile(db)
|
||||
p = data.get("profile", {})
|
||||
profile.one_rm_kg = p.get("one_rm_kg")
|
||||
profile.training_weekdays = p.get("training_weekdays")
|
||||
profile.start_date = datetime.fromisoformat(p["start_date"]).date() if p.get("start_date") else None
|
||||
profile.onboarded = bool(p.get("onboarded"))
|
||||
profile.phase = p.get("phase") or "active"
|
||||
profile.rest_until = datetime.fromisoformat(p["rest_until"]) if p.get("rest_until") else None
|
||||
profile.probe_available_at = (
|
||||
datetime.fromisoformat(p["probe_available_at"]) if p.get("probe_available_at") else None
|
||||
)
|
||||
profile.cycle_number = int(p.get("cycle_number") or 1)
|
||||
|
||||
from app.models import WorkoutSet
|
||||
|
||||
for a in data.get("accessories", []):
|
||||
db.add(
|
||||
AccessoryState(
|
||||
cycle_number=a["cycle_number"],
|
||||
exercise_name=a["exercise_name"],
|
||||
weight_kg=a.get("weight_kg"),
|
||||
locked=bool(a.get("locked")),
|
||||
pending_weight_kg=a.get("pending_weight_kg"),
|
||||
)
|
||||
)
|
||||
|
||||
for w in data.get("workouts", []):
|
||||
workout = Workout(
|
||||
cycle_number=w["cycle_number"],
|
||||
week_number=w["week_number"],
|
||||
day_index=w["day_index"],
|
||||
scheduled_date=datetime.fromisoformat(w["scheduled_date"]).date(),
|
||||
status=w.get("status") or "planned",
|
||||
started_at=datetime.fromisoformat(w["started_at"]) if w.get("started_at") else None,
|
||||
completed_at=datetime.fromisoformat(w["completed_at"]) if w.get("completed_at") else None,
|
||||
current_exercise_index=w.get("current_exercise_index") or 0,
|
||||
current_set_index=w.get("current_set_index") or 0,
|
||||
)
|
||||
db.add(workout)
|
||||
db.flush()
|
||||
for e in w.get("exercises", []):
|
||||
ex = WorkoutExercise(
|
||||
workout_id=workout.id,
|
||||
order_index=e["order_index"],
|
||||
name=e["name"],
|
||||
kind=e["kind"],
|
||||
prescribed_weight_kg=e.get("prescribed_weight_kg"),
|
||||
weight_locked=bool(e.get("weight_locked")),
|
||||
needs_weight_input=bool(e.get("needs_weight_input")),
|
||||
pending_prompt=e.get("pending_prompt"),
|
||||
)
|
||||
db.add(ex)
|
||||
db.flush()
|
||||
for s in e.get("sets", []):
|
||||
db.add(
|
||||
WorkoutSet(
|
||||
exercise_id=ex.id,
|
||||
set_index=s["set_index"],
|
||||
target_percent=s.get("target_percent"),
|
||||
target_reps=s.get("target_reps"),
|
||||
target_weight_kg=s.get("target_weight_kg"),
|
||||
is_failure=bool(s.get("is_failure")),
|
||||
completed=bool(s.get("completed")),
|
||||
actual_reps=s.get("actual_reps"),
|
||||
actual_weight_kg=s.get("actual_weight_kg"),
|
||||
completed_at=datetime.fromisoformat(s["completed_at"])
|
||||
if s.get("completed_at")
|
||||
else None,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
return MessageResponse(message="Данные импортированы")
|
||||
|
||||
|
||||
@router.post("/reset", response_model=MessageResponse)
|
||||
def reset_all(_: User = Depends(require_auth), db: Session = Depends(get_db)) -> MessageResponse:
|
||||
db.query(Workout).delete()
|
||||
db.query(AccessoryState).delete()
|
||||
profile = get_or_create_profile(db)
|
||||
profile.one_rm_kg = None
|
||||
profile.training_weekdays = None
|
||||
profile.start_date = None
|
||||
profile.onboarded = False
|
||||
profile.phase = "active"
|
||||
profile.rest_until = None
|
||||
profile.probe_available_at = None
|
||||
profile.cycle_number = 1
|
||||
db.commit()
|
||||
return MessageResponse(message="Прогресс удалён. Пройдите онбординг заново.")
|
||||
Reference in new issue
Block a user