First synculous 2 Big-Pickle pass.
This commit is contained in:
168
api/routes/routine_templates.py
Normal file
168
api/routes/routine_templates.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user