225 lines
6.1 KiB
Python
225 lines
6.1 KiB
Python
"""
|
|
core/routines.py - Shared business logic for routines
|
|
|
|
This module contains reusable functions for routine operations
|
|
that can be called from API routes, bot commands, or scheduler.
|
|
"""
|
|
|
|
import uuid
|
|
from datetime import datetime, date
|
|
import core.postgres as postgres
|
|
|
|
|
|
def start_session(routine_id, user_uuid):
|
|
"""
|
|
Create and start a new routine session.
|
|
Returns the session object or None if routine not found.
|
|
"""
|
|
routine = postgres.select_one("routines", {"id": routine_id, "user_uuid": user_uuid})
|
|
if not routine:
|
|
return None
|
|
|
|
active_session = postgres.select_one(
|
|
"routine_sessions",
|
|
{"user_uuid": user_uuid, "status": "active"}
|
|
)
|
|
if active_session:
|
|
return {"error": "already_active", "session_id": active_session["id"]}
|
|
|
|
steps = postgres.select(
|
|
"routine_steps",
|
|
{"routine_id": routine_id},
|
|
order_by="position"
|
|
)
|
|
if not steps:
|
|
return {"error": "no_steps"}
|
|
|
|
session = {
|
|
"id": str(uuid.uuid4()),
|
|
"routine_id": routine_id,
|
|
"user_uuid": user_uuid,
|
|
"status": "active",
|
|
"current_step_index": 0,
|
|
}
|
|
result = postgres.insert("routine_sessions", session)
|
|
return {"session": result, "current_step": steps[0]}
|
|
|
|
|
|
def pause_session(session_id, user_uuid):
|
|
"""Pause an active session."""
|
|
session = postgres.select_one(
|
|
"routine_sessions",
|
|
{"id": session_id, "user_uuid": user_uuid}
|
|
)
|
|
if not session:
|
|
return None
|
|
if session.get("status") != "active":
|
|
return {"error": "not_active"}
|
|
result = postgres.update(
|
|
"routine_sessions",
|
|
{"status": "paused", "paused_at": datetime.now().isoformat()},
|
|
{"id": session_id}
|
|
)
|
|
return result
|
|
|
|
|
|
def resume_session(session_id, user_uuid):
|
|
"""Resume a paused session."""
|
|
session = postgres.select_one(
|
|
"routine_sessions",
|
|
{"id": session_id, "user_uuid": user_uuid}
|
|
)
|
|
if not session:
|
|
return None
|
|
if session.get("status") != "paused":
|
|
return {"error": "not_paused"}
|
|
result = postgres.update(
|
|
"routine_sessions",
|
|
{"status": "active", "paused_at": None},
|
|
{"id": session_id}
|
|
)
|
|
return result
|
|
|
|
|
|
def abort_session(session_id, user_uuid, reason=None):
|
|
"""Abort a session with optional reason."""
|
|
session = postgres.select_one(
|
|
"routine_sessions",
|
|
{"id": session_id, "user_uuid": user_uuid}
|
|
)
|
|
if not session:
|
|
return None
|
|
result = postgres.update(
|
|
"routine_sessions",
|
|
{
|
|
"status": "aborted",
|
|
"abort_reason": reason or "Aborted by user",
|
|
"completed_at": datetime.now().isoformat()
|
|
},
|
|
{"id": session_id}
|
|
)
|
|
return result
|
|
|
|
|
|
def complete_session(session_id, user_uuid):
|
|
"""Mark a session as completed and update streak."""
|
|
session = postgres.select_one(
|
|
"routine_sessions",
|
|
{"id": session_id, "user_uuid": user_uuid}
|
|
)
|
|
if not session:
|
|
return None
|
|
|
|
completed_at = datetime.now()
|
|
result = postgres.update(
|
|
"routine_sessions",
|
|
{"status": "completed", "completed_at": completed_at.isoformat()},
|
|
{"id": session_id}
|
|
)
|
|
|
|
_update_streak(user_uuid, session["routine_id"])
|
|
|
|
return result
|
|
|
|
|
|
def clone_template(template_id, user_uuid):
|
|
"""Clone a template to user's routines."""
|
|
template = postgres.select_one("routine_templates", {"id": template_id})
|
|
if not template:
|
|
return None
|
|
|
|
template_steps = postgres.select(
|
|
"routine_template_steps",
|
|
{"template_id": template_id},
|
|
order_by="position"
|
|
)
|
|
|
|
new_routine = {
|
|
"id": str(uuid.uuid4()),
|
|
"user_uuid": user_uuid,
|
|
"name": template["name"],
|
|
"description": template.get("description"),
|
|
"icon": template.get("icon"),
|
|
}
|
|
routine = postgres.insert("routines", new_routine)
|
|
|
|
for step in template_steps:
|
|
new_step = {
|
|
"id": str(uuid.uuid4()),
|
|
"routine_id": routine["id"],
|
|
"name": step["name"],
|
|
"instructions": step.get("instructions"),
|
|
"step_type": step.get("step_type", "generic"),
|
|
"duration_minutes": step.get("duration_minutes"),
|
|
"media_url": step.get("media_url"),
|
|
"position": step["position"],
|
|
}
|
|
postgres.insert("routine_steps", new_step)
|
|
|
|
return routine
|
|
|
|
|
|
def _update_streak(user_uuid, routine_id):
|
|
"""Update streak after completing a session. Resets if day was missed."""
|
|
today = date.today()
|
|
|
|
streak = postgres.select_one(
|
|
"routine_streaks",
|
|
{"user_uuid": user_uuid, "routine_id": routine_id}
|
|
)
|
|
|
|
if not streak:
|
|
new_streak = {
|
|
"id": str(uuid.uuid4()),
|
|
"user_uuid": user_uuid,
|
|
"routine_id": routine_id,
|
|
"current_streak": 1,
|
|
"longest_streak": 1,
|
|
"last_completed_date": today.isoformat(),
|
|
}
|
|
return postgres.insert("routine_streaks", new_streak)
|
|
|
|
last_completed = streak.get("last_completed_date")
|
|
if last_completed:
|
|
if isinstance(last_completed, str):
|
|
last_completed = date.fromisoformat(last_completed)
|
|
days_diff = (today - last_completed).days
|
|
if days_diff == 0:
|
|
return streak
|
|
elif days_diff == 1:
|
|
new_streak = streak["current_streak"] + 1
|
|
else:
|
|
new_streak = 1
|
|
else:
|
|
new_streak = 1
|
|
|
|
longest = max(streak["longest_streak"], new_streak)
|
|
|
|
postgres.update(
|
|
"routine_streaks",
|
|
{
|
|
"current_streak": new_streak,
|
|
"longest_streak": longest,
|
|
"last_completed_date": today.isoformat(),
|
|
},
|
|
{"id": streak["id"]}
|
|
)
|
|
return streak
|
|
|
|
|
|
def calculate_streak(user_uuid, routine_id):
|
|
"""Get current streak for a routine."""
|
|
streak = postgres.select_one(
|
|
"routine_streaks",
|
|
{"user_uuid": user_uuid, "routine_id": routine_id}
|
|
)
|
|
return streak
|
|
|
|
|
|
def get_active_session(user_uuid):
|
|
"""Get user's currently active session."""
|
|
return postgres.select_one(
|
|
"routine_sessions",
|
|
{"user_uuid": user_uuid, "status": "active"}
|
|
)
|