from microdot import Microdot from microdot.session import with_session from models.sequence import Sequence from models.profile import Profile from models.transport import get_current_sender import json controller = Microdot() sequences = Sequence() profiles = Profile() def get_current_profile_id(session=None): """Get the current active profile ID from session or fallback to first.""" profile_list = profiles.list() session_profile = None if session is not None: session_profile = session.get("current_profile") if session_profile and session_profile in profile_list: return session_profile if profile_list: return profile_list[0] return None @controller.get("") @with_session async def list_sequences(request, session): """List sequences for the current profile.""" current_profile_id = get_current_profile_id(session) if not current_profile_id: return json.dumps({}), 200, {"Content-Type": "application/json"} scoped = { sid: sdata for sid, sdata in sequences.items() if isinstance(sdata, dict) and str(sdata.get("profile_id")) == str(current_profile_id) } return json.dumps(scoped), 200, {"Content-Type": "application/json"} @controller.get("/") @with_session async def get_sequence(request, session, id): """Get a specific sequence by ID (current profile only).""" current_profile_id = get_current_profile_id(session) seq = sequences.read(id) if ( seq and current_profile_id and str(seq.get("profile_id")) == str(current_profile_id) ): return json.dumps(seq), 200, {"Content-Type": "application/json"} return json.dumps({"error": "Sequence not found"}), 404 @controller.post("") @with_session async def create_sequence(request, session): """Create a new sequence for the current profile.""" try: try: data = request.json or {} except Exception: return ( json.dumps({"error": "Invalid JSON"}), 400, {"Content-Type": "application/json"}, ) current_profile_id = get_current_profile_id(session) if not current_profile_id: return ( json.dumps({"error": "No profile available"}), 404, {"Content-Type": "application/json"}, ) sequence_id = sequences.create(current_profile_id) if not isinstance(data, dict): data = {} data = dict(data) data["profile_id"] = str(current_profile_id) if sequences.update(sequence_id, data): seq_data = sequences.read(sequence_id) return ( json.dumps({sequence_id: seq_data}), 201, {"Content-Type": "application/json"}, ) return ( json.dumps({"error": "Failed to create sequence"}), 400, {"Content-Type": "application/json"}, ) except Exception as e: return json.dumps({"error": str(e)}), 400, {"Content-Type": "application/json"} @controller.put("/") @with_session async def update_sequence(request, session, id): """Update an existing sequence (current profile only).""" try: current_profile_id = get_current_profile_id(session) seq = sequences.read(id) if not seq or str(seq.get("profile_id")) != str(current_profile_id): return json.dumps({"error": "Sequence not found"}), 404 data = request.json if not isinstance(data, dict): return ( json.dumps({"error": "Invalid JSON"}), 400, {"Content-Type": "application/json"}, ) data = dict(data) data["profile_id"] = str(current_profile_id) if sequences.update(id, data): try: from util.sequence_playback import stop_if_playing_sequence stop_if_playing_sequence(str(id)) except Exception: pass return json.dumps(sequences.read(id)), 200, {"Content-Type": "application/json"} return json.dumps({"error": "Sequence not found"}), 404 except Exception as e: return json.dumps({"error": str(e)}), 400, {"Content-Type": "application/json"} @controller.delete("/") @with_session async def delete_sequence(request, session, id): """Delete a sequence (current profile only).""" current_profile_id = get_current_profile_id(session) seq = sequences.read(id) if not seq or str(seq.get("profile_id")) != str(current_profile_id): return json.dumps({"error": "Sequence not found"}), 404 try: from util.sequence_playback import stop_if_playing_sequence stop_if_playing_sequence(str(id)) except Exception: pass if sequences.delete(id): return ( json.dumps({"message": "Sequence deleted successfully"}), 200, {"Content-Type": "application/json"}, ) return json.dumps({"error": "Sequence not found"}), 404 @controller.post("/stop") @with_session async def stop_sequence_playback(request, session): """Stop server-driven zone sequence playback.""" _ = request try: from util.sequence_playback import stop stop() return json.dumps({"ok": True}), 200, {"Content-Type": "application/json"} except Exception as e: return json.dumps({"error": str(e)}), 500, {"Content-Type": "application/json"} @controller.post("//play") @with_session async def play_sequence(request, session, id): """Start server-driven playback for a sequence in a zone (body: {\"zone_id\": \"...\"}).""" if not get_current_sender(): return ( json.dumps({"error": "Transport not configured"}), 503, {"Content-Type": "application/json"}, ) current_profile_id = get_current_profile_id(session) if not current_profile_id: return ( json.dumps({"error": "No profile available"}), 404, {"Content-Type": "application/json"}, ) try: data = request.json or {} except Exception: data = {} if not isinstance(data, dict): data = {} zone_id = data.get("zone_id") or data.get("zoneId") if zone_id is None or str(zone_id).strip() == "": return ( json.dumps({"error": "zone_id required"}), 400, {"Content-Type": "application/json"}, ) zone_id = str(zone_id).strip() try: from util.sequence_playback import start await start(zone_id, str(id), str(current_profile_id)) return json.dumps({"ok": True}), 200, {"Content-Type": "application/json"} except ValueError as e: return json.dumps({"error": str(e)}), 400, {"Content-Type": "application/json"} except RuntimeError as e: return json.dumps({"error": str(e)}), 503, {"Content-Type": "application/json"} except Exception as e: return json.dumps({"error": str(e)}), 500, {"Content-Type": "application/json"}