First synculous 2 Big-Pickle pass.

This commit is contained in:
2026-02-12 23:07:48 -06:00
parent 25d05e0e86
commit 3e1134575b
26 changed files with 2729 additions and 59 deletions

224
core/routines.py Normal file
View File

@@ -0,0 +1,224 @@
"""
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"}
)