""" 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/", 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//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/", 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/", 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//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