""" api/routes/tasks.py - One-off scheduled task CRUD """ import uuid import flask import jwt import os from datetime import datetime import core.postgres as postgres JWT_SECRET = os.getenv("JWT_SECRET") def _get_user_uuid(request): """Extract and validate user UUID from JWT token.""" auth_header = request.headers.get("Authorization", "") if not auth_header.startswith("Bearer "): return None token = auth_header[7:] try: payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) return payload.get("sub") except (jwt.ExpiredSignatureError, jwt.InvalidTokenError): return None def register(app): @app.route("/api/tasks", methods=["GET"]) def get_tasks(): user_uuid = _get_user_uuid(flask.request) if not user_uuid: return flask.jsonify({"error": "unauthorized"}), 401 status_filter = flask.request.args.get("status", "pending") if status_filter == "all": tasks = postgres.select( "tasks", where={"user_uuid": user_uuid}, order_by="scheduled_datetime ASC", ) else: tasks = postgres.select( "tasks", where={"user_uuid": user_uuid, "status": status_filter}, order_by="scheduled_datetime ASC", ) # Serialize datetimes for JSON for t in tasks: for key in ("scheduled_datetime", "created_at", "updated_at"): if key in t and hasattr(t[key], "isoformat"): t[key] = t[key].isoformat() return flask.jsonify(tasks), 200 @app.route("/api/tasks", methods=["POST"]) def create_task(): user_uuid = _get_user_uuid(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 title = data.get("title", "").strip() scheduled_datetime = data.get("scheduled_datetime", "").strip() if not title: return flask.jsonify({"error": "title is required"}), 400 if not scheduled_datetime: return flask.jsonify({"error": "scheduled_datetime is required"}), 400 task_id = str(uuid.uuid4()) task = { "id": task_id, "user_uuid": user_uuid, "title": title, "description": data.get("description") or None, "scheduled_datetime": scheduled_datetime, "reminder_minutes_before": int(data.get("reminder_minutes_before", 15)), "status": "pending", } postgres.insert("tasks", task) return flask.jsonify(task), 201 @app.route("/api/tasks/", methods=["PATCH"]) def update_task(task_id): user_uuid = _get_user_uuid(flask.request) if not user_uuid: return flask.jsonify({"error": "unauthorized"}), 401 task = postgres.select_one("tasks", {"id": task_id, "user_uuid": user_uuid}) if not task: return flask.jsonify({"error": "not found"}), 404 data = flask.request.get_json() or {} updates = {} for field in ["title", "description", "scheduled_datetime", "reminder_minutes_before", "status"]: if field in data: updates[field] = data[field] updates["updated_at"] = datetime.utcnow().isoformat() postgres.update("tasks", updates, {"id": task_id}) return flask.jsonify({**{k: (v.isoformat() if hasattr(v, "isoformat") else v) for k, v in task.items()}, **updates}), 200 @app.route("/api/tasks/", methods=["DELETE"]) def delete_task(task_id): user_uuid = _get_user_uuid(flask.request) if not user_uuid: return flask.jsonify({"error": "unauthorized"}), 401 task = postgres.select_one("tasks", {"id": task_id, "user_uuid": user_uuid}) if not task: return flask.jsonify({"error": "not found"}), 404 postgres.delete("tasks", {"id": task_id}) return flask.jsonify({"success": True}), 200