205 lines
7.8 KiB
Python
205 lines
7.8 KiB
Python
"""
|
|
Routines API - Brilli-style routine management
|
|
|
|
Routines have ordered steps. Users start sessions to walk through them.
|
|
"""
|
|
|
|
import os
|
|
import flask
|
|
import jwt
|
|
import core.auth as auth
|
|
import core.postgres as postgres
|
|
|
|
|
|
def _get_user_uuid(token):
|
|
try:
|
|
payload = jwt.decode(token, os.getenv("JWT_SECRET"), algorithms=["HS256"])
|
|
return payload.get("sub")
|
|
except (jwt.ExpiredSignatureError, jwt.InvalidTokenError):
|
|
return None
|
|
|
|
|
|
def _auth(request):
|
|
"""Extract and verify token. Returns user_uuid or None."""
|
|
header = request.headers.get("Authorization", "")
|
|
if not header.startswith("Bearer "):
|
|
return None
|
|
token = header[7:]
|
|
user_uuid = _get_user_uuid(token)
|
|
if not user_uuid or not auth.verifyLoginToken(token, userUUID=user_uuid):
|
|
return None
|
|
return user_uuid
|
|
|
|
|
|
def register(app):
|
|
|
|
# ── Routines CRUD ─────────────────────────────────────────────
|
|
|
|
@app.route("/api/routines", methods=["GET"])
|
|
def api_listRoutines():
|
|
"""List all routines for the logged-in user."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
pass
|
|
|
|
@app.route("/api/routines", methods=["POST"])
|
|
def api_createRoutine():
|
|
"""Create a new routine. Body: {name, description?, icon?}"""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
data = flask.request.get_json()
|
|
pass
|
|
|
|
@app.route("/api/routines/<routine_id>", methods=["GET"])
|
|
def api_getRoutine(routine_id):
|
|
"""Get a routine with its steps."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
pass
|
|
|
|
@app.route("/api/routines/<routine_id>", methods=["PUT"])
|
|
def api_updateRoutine(routine_id):
|
|
"""Update routine details. Body: {name?, description?, icon?}"""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
data = flask.request.get_json()
|
|
pass
|
|
|
|
@app.route("/api/routines/<routine_id>", methods=["DELETE"])
|
|
def api_deleteRoutine(routine_id):
|
|
"""Delete a routine and all its steps/sessions."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
pass
|
|
|
|
# ── Steps CRUD ────────────────────────────────────────────────
|
|
|
|
@app.route("/api/routines/<routine_id>/steps", methods=["GET"])
|
|
def api_listSteps(routine_id):
|
|
"""List steps for a routine, ordered by position."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
pass
|
|
|
|
@app.route("/api/routines/<routine_id>/steps", methods=["POST"])
|
|
def api_addStep(routine_id):
|
|
"""Add a step to a routine. Body: {name, duration_minutes?, position?}"""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
data = flask.request.get_json()
|
|
pass
|
|
|
|
@app.route("/api/routines/<routine_id>/steps/<step_id>", methods=["PUT"])
|
|
def api_updateStep(routine_id, step_id):
|
|
"""Update a step. Body: {name?, duration_minutes?, position?}"""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
data = flask.request.get_json()
|
|
pass
|
|
|
|
@app.route("/api/routines/<routine_id>/steps/<step_id>", methods=["DELETE"])
|
|
def api_deleteStep(routine_id, step_id):
|
|
"""Delete a step from a routine."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
pass
|
|
|
|
@app.route("/api/routines/<routine_id>/steps/reorder", methods=["PUT"])
|
|
def api_reorderSteps(routine_id):
|
|
"""Reorder steps. Body: {step_ids: [ordered list of step UUIDs]}"""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
data = flask.request.get_json()
|
|
pass
|
|
|
|
# ── Routine Sessions (active run-through) ─────────────────────
|
|
|
|
@app.route("/api/routines/<routine_id>/start", methods=["POST"])
|
|
def api_startRoutine(routine_id):
|
|
"""Start a routine session. Returns the session with first step."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
pass
|
|
|
|
@app.route("/api/sessions/active", methods=["GET"])
|
|
def api_getActiveSession():
|
|
"""Get the user's currently active routine session, if any."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
pass
|
|
|
|
@app.route("/api/sessions/<session_id>/complete-step", methods=["POST"])
|
|
def api_completeStep(session_id):
|
|
"""Mark current step done, advance to next. Body: {step_id}"""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
data = flask.request.get_json()
|
|
pass
|
|
|
|
@app.route("/api/sessions/<session_id>/skip-step", methods=["POST"])
|
|
def api_skipStep(session_id):
|
|
"""Skip current step, advance to next. Body: {step_id}"""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
data = flask.request.get_json()
|
|
pass
|
|
|
|
@app.route("/api/sessions/<session_id>/cancel", methods=["POST"])
|
|
def api_cancelSession(session_id):
|
|
"""Cancel an active routine session."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
pass
|
|
|
|
# ── Routine History / Stats ───────────────────────────────────
|
|
|
|
@app.route("/api/routines/<routine_id>/history", methods=["GET"])
|
|
def api_routineHistory(routine_id):
|
|
"""Get past sessions for a routine. Query: ?days=7"""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
pass
|
|
|
|
# ── Routine Scheduling ────────────────────────────────────────
|
|
|
|
@app.route("/api/routines/<routine_id>/schedule", methods=["PUT"])
|
|
def api_setRoutineSchedule(routine_id):
|
|
"""Set when this routine should run. Body: {days: ["mon","tue",...], time: "08:00", remind: true}"""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
data = flask.request.get_json()
|
|
pass
|
|
|
|
@app.route("/api/routines/<routine_id>/schedule", methods=["GET"])
|
|
def api_getRoutineSchedule(routine_id):
|
|
"""Get the schedule for a routine."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
pass
|
|
|
|
@app.route("/api/routines/<routine_id>/schedule", methods=["DELETE"])
|
|
def api_deleteRoutineSchedule(routine_id):
|
|
"""Remove the schedule from a routine."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
pass
|