Files
Synculous-2/core/routines.py

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"}
)