169 lines
7.1 KiB
Python
169 lines
7.1 KiB
Python
"""
|
|
Routine Templates API - pre-built routines that users can clone
|
|
"""
|
|
|
|
import os
|
|
import uuid
|
|
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):
|
|
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):
|
|
|
|
@app.route("/api/templates", methods=["GET"])
|
|
def api_listTemplates():
|
|
"""List all available templates."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
templates = postgres.select("routine_templates", order_by="name")
|
|
for template in templates:
|
|
steps = postgres.select("routine_template_steps", {"template_id": template["id"]}, order_by="position")
|
|
template["step_count"] = len(steps)
|
|
return flask.jsonify(templates), 200
|
|
|
|
@app.route("/api/templates", methods=["POST"])
|
|
def api_createTemplate():
|
|
"""Create a new template (admin only in production). Body: {name, description?, icon?}"""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
data = flask.request.get_json()
|
|
if not data:
|
|
return flask.jsonify({"error": "missing body"}), 400
|
|
if not data.get("name"):
|
|
return flask.jsonify({"error": "missing required field: name"}), 400
|
|
data["id"] = str(uuid.uuid4())
|
|
data["created_by_admin"] = False
|
|
template = postgres.insert("routine_templates", data)
|
|
return flask.jsonify(template), 201
|
|
|
|
@app.route("/api/templates/<template_id>", methods=["GET"])
|
|
def api_getTemplate(template_id):
|
|
"""Get a template with its steps."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
template = postgres.select_one("routine_templates", {"id": template_id})
|
|
if not template:
|
|
return flask.jsonify({"error": "template not found"}), 404
|
|
steps = postgres.select("routine_template_steps", {"template_id": template_id}, order_by="position")
|
|
return flask.jsonify({"template": template, "steps": steps}), 200
|
|
|
|
@app.route("/api/templates/<template_id>/clone", methods=["POST"])
|
|
def api_cloneTemplate(template_id):
|
|
"""Clone a template to user's routines."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
template = postgres.select_one("routine_templates", {"id": template_id})
|
|
if not template:
|
|
return flask.jsonify({"error": "template not found"}), 404
|
|
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 flask.jsonify(routine), 201
|
|
|
|
@app.route("/api/templates/<template_id>", methods=["PUT"])
|
|
def api_updateTemplate(template_id):
|
|
"""Update a template."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
template = postgres.select_one("routine_templates", {"id": template_id})
|
|
if not template:
|
|
return flask.jsonify({"error": "template not found"}), 404
|
|
data = flask.request.get_json()
|
|
if not data:
|
|
return flask.jsonify({"error": "missing body"}), 400
|
|
allowed = ["name", "description", "icon"]
|
|
updates = {k: v for k, v in data.items() if k in allowed}
|
|
if not updates:
|
|
return flask.jsonify({"error": "no valid fields to update"}), 400
|
|
result = postgres.update("routine_templates", updates, {"id": template_id})
|
|
return flask.jsonify(result[0] if result else {}), 200
|
|
|
|
@app.route("/api/templates/<template_id>", methods=["DELETE"])
|
|
def api_deleteTemplate(template_id):
|
|
"""Delete a template."""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
template = postgres.select_one("routine_templates", {"id": template_id})
|
|
if not template:
|
|
return flask.jsonify({"error": "template not found"}), 404
|
|
postgres.delete("routine_template_steps", {"template_id": template_id})
|
|
postgres.delete("routine_templates", {"id": template_id})
|
|
return flask.jsonify({"deleted": True}), 200
|
|
|
|
@app.route("/api/templates/<template_id>/steps", methods=["POST"])
|
|
def api_addTemplateStep(template_id):
|
|
"""Add a step to a template. Body: {name, instructions?, step_type?, duration_minutes?, position?}"""
|
|
user_uuid = _auth(flask.request)
|
|
if not user_uuid:
|
|
return flask.jsonify({"error": "unauthorized"}), 401
|
|
template = postgres.select_one("routine_templates", {"id": template_id})
|
|
if not template:
|
|
return flask.jsonify({"error": "template not found"}), 404
|
|
data = flask.request.get_json()
|
|
if not data or not data.get("name"):
|
|
return flask.jsonify({"error": "missing required field: name"}), 400
|
|
max_pos = postgres.select(
|
|
"routine_template_steps",
|
|
{"template_id": template_id},
|
|
order_by="position DESC",
|
|
limit=1,
|
|
)
|
|
next_pos = (max_pos[0]["position"] + 1) if max_pos else 1
|
|
step = {
|
|
"id": str(uuid.uuid4()),
|
|
"template_id": template_id,
|
|
"name": data["name"],
|
|
"instructions": data.get("instructions"),
|
|
"step_type": data.get("step_type", "generic"),
|
|
"duration_minutes": data.get("duration_minutes"),
|
|
"media_url": data.get("media_url"),
|
|
"position": data.get("position", next_pos),
|
|
}
|
|
result = postgres.insert("routine_template_steps", step)
|
|
return flask.jsonify(result), 201
|