89 lines
2.9 KiB
Python
89 lines
2.9 KiB
Python
from datetime import datetime, timedelta
|
|
|
|
from flask import Blueprint, abort, jsonify, request, url_for
|
|
|
|
from ..extensions import db
|
|
from ..models import Display, DisplaySession
|
|
|
|
bp = Blueprint("api", __name__, url_prefix="/api")
|
|
|
|
|
|
MAX_ACTIVE_SESSIONS_PER_DISPLAY = 2
|
|
SESSION_TTL_SECONDS = 90
|
|
|
|
|
|
@bp.get("/display/<token>/playlist")
|
|
def display_playlist(token: str):
|
|
display = Display.query.filter_by(token=token).first()
|
|
if not display:
|
|
abort(404)
|
|
|
|
# Enforce: a display URL/token can be opened by max 2 concurrently active sessions.
|
|
# Player sends a stable `sid` via querystring.
|
|
sid = (request.args.get("sid") or "").strip()
|
|
if sid:
|
|
cutoff = datetime.utcnow() - timedelta(seconds=SESSION_TTL_SECONDS)
|
|
DisplaySession.query.filter(
|
|
DisplaySession.display_id == display.id,
|
|
DisplaySession.last_seen_at < cutoff,
|
|
).delete(synchronize_session=False)
|
|
db.session.commit()
|
|
|
|
existing = DisplaySession.query.filter_by(display_id=display.id, sid=sid).first()
|
|
if existing:
|
|
existing.last_seen_at = datetime.utcnow()
|
|
db.session.commit()
|
|
else:
|
|
active_count = (
|
|
DisplaySession.query.filter(
|
|
DisplaySession.display_id == display.id,
|
|
DisplaySession.last_seen_at >= cutoff,
|
|
).count()
|
|
)
|
|
if active_count >= MAX_ACTIVE_SESSIONS_PER_DISPLAY:
|
|
return (
|
|
jsonify(
|
|
{
|
|
"error": "display_limit_reached",
|
|
"message": f"This display URL is already open on {MAX_ACTIVE_SESSIONS_PER_DISPLAY} displays.",
|
|
}
|
|
),
|
|
429,
|
|
)
|
|
|
|
s = DisplaySession(
|
|
display_id=display.id,
|
|
sid=sid,
|
|
last_seen_at=datetime.utcnow(),
|
|
ip=request.headers.get("X-Forwarded-For", request.remote_addr),
|
|
user_agent=(request.headers.get("User-Agent") or "")[:300],
|
|
)
|
|
db.session.add(s)
|
|
db.session.commit()
|
|
|
|
playlist = display.assigned_playlist
|
|
if not playlist:
|
|
return jsonify({"display": display.name, "playlist": None, "items": []})
|
|
|
|
items = []
|
|
for item in playlist.items:
|
|
payload = {
|
|
"id": item.id,
|
|
"type": item.item_type,
|
|
"title": item.title,
|
|
"duration": item.duration_seconds,
|
|
}
|
|
if item.item_type in ("image", "video") and item.file_path:
|
|
payload["src"] = url_for("static", filename=item.file_path)
|
|
if item.item_type == "webpage":
|
|
payload["url"] = item.url
|
|
items.append(payload)
|
|
|
|
return jsonify(
|
|
{
|
|
"display": display.name,
|
|
"playlist": {"id": playlist.id, "name": playlist.name},
|
|
"items": items,
|
|
}
|
|
)
|