""" API routes for player clients """ from flask import Blueprint, request, jsonify, url_for from app.models.player import Player from app.models.content import Content from app.extensions import bcrypt, db bp = Blueprint('api', __name__) @bp.route('/playlists', methods=['GET']) def get_playlists(): """Get playlist for a player""" hostname = request.args.get('hostname') quickconnect_code = request.args.get('quickconnect_code') # Validate parameters if not hostname or not quickconnect_code: return jsonify({'error': 'Hostname and quick connect code are required'}), 400 # Find player and verify credentials player = Player.query.filter_by(hostname=hostname).first() if not player or not player.verify_quickconnect_code(quickconnect_code): return jsonify({'error': 'Invalid hostname or quick connect code'}), 404 # Update last seen player.last_seen = db.func.current_timestamp() db.session.commit() # Get content based on player's group status if player.is_locked_to_group: # Player is locked to a group - get shared content group = player.locked_to_group player_ids = [p.id for p in group.players] # Get unique content by filename (first occurrence) content_query = ( db.session.query( Content.file_name, db.func.min(Content.id).label('id'), db.func.min(Content.duration).label('duration'), db.func.min(Content.position).label('position'), db.func.min(Content.content_type).label('content_type') ) .filter(Content.player_id.in_(player_ids)) .group_by(Content.file_name) ) content = db.session.query(Content).filter( Content.id.in_([c.id for c in content_query]) ).order_by(Content.position).all() else: # Individual player content content = Content.query.filter_by(player_id=player.id).order_by(Content.position).all() # Build playlist playlist = [] for media in content: playlist.append({ 'file_name': media.file_name, 'url': url_for('content.media', filename=media.file_name, _external=True), 'duration': media.duration, 'content_type': media.content_type, 'position': media.position }) return jsonify({ 'playlist': playlist, 'playlist_version': player.playlist_version, 'hashed_quickconnect': player.quickconnect_password, 'player_id': player.id, 'player_name': player.username }) @bp.route('/playlist_version', methods=['GET']) def get_playlist_version(): """Get playlist version for a player (for checking updates)""" hostname = request.args.get('hostname') quickconnect_code = request.args.get('quickconnect_code') # Validate parameters if not hostname or not quickconnect_code: return jsonify({'error': 'Hostname and quick connect code are required'}), 400 # Find player and verify credentials player = Player.query.filter_by(hostname=hostname).first() if not player or not player.verify_quickconnect_code(quickconnect_code): return jsonify({'error': 'Invalid hostname or quick connect code'}), 404 # Update last seen player.last_seen = db.func.current_timestamp() db.session.commit() return jsonify({ 'playlist_version': player.playlist_version, 'hashed_quickconnect': player.quickconnect_password }) @bp.route('/player_status', methods=['POST']) def update_player_status(): """Update player status (heartbeat)""" data = request.get_json() if not data: return jsonify({'error': 'JSON data required'}), 400 hostname = data.get('hostname') quickconnect_code = data.get('quickconnect_code') if not hostname or not quickconnect_code: return jsonify({'error': 'Hostname and quick connect code are required'}), 400 # Find player and verify credentials player = Player.query.filter_by(hostname=hostname).first() if not player or not player.verify_quickconnect_code(quickconnect_code): return jsonify({'error': 'Invalid hostname or quick connect code'}), 404 # Update player status player.last_seen = db.func.current_timestamp() player.is_active = True # Optional: Update additional status info if provided if 'status' in data: # Could store additional status information in the future pass db.session.commit() return jsonify({ 'success': True, 'playlist_version': player.playlist_version, 'message': 'Status updated successfully' }) @bp.route('/health', methods=['GET']) def health_check(): """Health check endpoint""" return jsonify({ 'status': 'healthy', 'service': 'SKE Digital Signage Server', 'version': '2.0.0' }) @bp.errorhandler(404) def api_not_found(error): """API 404 handler""" return jsonify({'error': 'API endpoint not found'}), 404 @bp.errorhandler(500) def api_internal_error(error): """API 500 handler""" return jsonify({'error': 'Internal server error'}), 500