team-1/backend/src/app.py

299 lines
7.8 KiB
Python
Raw Normal View History

2025-08-02 03:00:28 +02:00
import uuid
from dataclasses import asdict
2025-08-01 20:59:09 +02:00
import dotenv
2025-08-01 18:46:38 +02:00
from flask import Flask, Response, jsonify, request
2025-08-01 18:07:11 +02:00
from flask_cors import CORS
2025-08-02 02:15:01 +02:00
from flask_socketio import SocketIO, join_room, leave_room
2025-08-01 19:16:20 +02:00
2025-08-02 09:19:12 +02:00
from state import State
from connect import get_connection
from room import Room
from song import Song, init_db, get_song_by_title_artist, add_song_in_db, get_song_by_uuid
from song_fetch import query_search, yt_get_audio_url, yt_search_song
from qrcode_gen import generate_qr
from gps import is_within_range, distance_between_coords, Coordinates
2025-08-01 18:07:11 +02:00
2025-08-02 00:19:07 +02:00
2025-08-01 19:13:12 +02:00
dotenv.load_dotenv()
2025-08-01 19:16:20 +02:00
2025-08-01 18:23:27 +02:00
2025-08-01 18:07:11 +02:00
app = Flask(__name__)
2025-08-01 22:07:39 +02:00
app.config["SECRET_KEY"] = "your_secret_key"
2025-08-02 01:58:29 +02:00
socketio = SocketIO(app, cors_allowed_origins="*", path="/ws")
2025-08-01 18:07:11 +02:00
CORS(app)
2025-08-02 01:58:29 +02:00
2025-08-01 23:07:00 +02:00
db_conn = get_connection()
2025-08-02 00:48:21 +02:00
state = State(app, socketio, db_conn.cursor())
2025-08-01 23:07:00 +02:00
init_db(state.db)
2025-08-01 18:23:27 +02:00
2025-08-02 04:11:54 +02:00
state.rooms[1000] = Room(
id=1000,
2025-08-02 09:07:52 +02:00
coord=Coordinates(1, 5),
2025-08-02 04:11:54 +02:00
name="Test Room",
pin=None,
tags=set(),
range_size=100,
songs={},
history=[],
playing=[],
playing_idx=0,
)
2025-08-02 00:19:07 +02:00
2025-08-01 18:23:27 +02:00
def error(msg: str, status: int = 400) -> Response:
res = jsonify({"success": False, "error": msg})
res.status_code = status
return res
2025-08-02 01:58:29 +02:00
@socketio.on("connect")
def handle_connection():
print("somebody connected to socket.io", flush=True)
@socketio.on("disconnect")
def handle_disconnection():
print("somebody disconnected from socket.io", flush=True)
2025-08-02 00:19:07 +02:00
2025-08-02 01:58:29 +02:00
@socketio.on("join_room")
def on_join(data):
room = data["id"]
join_room(room)
print(f"somebody joined {room=}", flush=True)
2025-08-02 00:19:07 +02:00
2025-08-02 01:58:29 +02:00
@socketio.on("leave_room")
def on_leave(data):
room = data["id"]
leave_room(room)
print(f"somebody left {room=}", flush=True)
2025-08-02 00:19:07 +02:00
2025-08-01 21:39:46 +02:00
@app.get("/api/join")
2025-08-01 18:23:27 +02:00
def join():
room_id = request.args.get("room")
code = request.args.get("code")
if room_id is None:
return error("Missing room id")
2025-08-01 23:07:00 +02:00
if (room := state.rooms.get(int(room_id))) is None:
2025-08-01 18:23:27 +02:00
return error("Invalid room")
if room.pin is not None and room.pin != code:
return error("Invalid code")
2025-08-02 09:11:39 +02:00
distance = distance_between_coords(
lhs=room.coord,
rhs=Coordinates(
latitude=int(request.args["lat"]),
longitude=int(request.args["lon"]),
),
)
2025-08-02 09:05:30 +02:00
if distance > room.range_size:
2025-08-02 08:10:22 +02:00
return error("You are not within the room range")
2025-08-01 18:23:27 +02:00
return {"success": True, "ws": f"/ws/{room_id}"}
2025-08-01 21:39:46 +02:00
@app.get("/api/queue")
def queue():
if (room_id := request.args.get("room")) is None:
return error("Missing room id")
2025-08-01 23:07:00 +02:00
if (room := state.rooms.get(int(room_id))) is None:
2025-08-01 21:39:46 +02:00
return error("Invalid room")
2025-08-01 23:32:48 +02:00
return {"success": True, "queue": room.playing, "index": room.playing_idx}
2025-08-01 21:39:46 +02:00
@app.post("/api/queue/next")
2025-08-01 22:07:39 +02:00
def queue_next():
if (room_id := request.args.get("room")) is None:
return error("Missing room id")
2025-08-01 23:07:00 +02:00
if (room := state.rooms.get(int(room_id))) is None:
2025-08-01 22:07:39 +02:00
return error("Invalid room")
room.playing_idx += 1
if room.playing_idx >= len(room.playing):
## queue ended
2025-08-02 04:17:20 +02:00
room.renew_queue()
2025-08-02 04:34:07 +02:00
ended = True
else:
ended = False
2025-08-01 21:39:46 +02:00
2025-08-02 04:34:07 +02:00
data = {"success": True, "ended": ended, "index": room.playing_idx, "queue": [asdict(s) for s in room.playing]}
state.socketio.emit("queue_update", data, to=str(room.id))
2025-08-01 21:39:46 +02:00
2025-08-02 04:34:07 +02:00
return data
2025-08-01 21:39:46 +02:00
@app.post("/api/room/new")
def room_new():
if (room_name := request.args.get("name")) is None:
return error("Missing room name")
if (room_cords := request.args.get("coords")) is None:
return error("Missing room coords")
2025-08-02 01:14:58 +02:00
if (room_range := request.args.get("range")) is None:
return error("Missing room range")
if room_pin := request.args.get("pin"):
room_pin = int(room_pin)
else:
room_pin = None
lat, lon = room_cords.split(",")
room = Room(
2025-08-02 09:19:12 +02:00
id=max(state.rooms or [0]) + 1,
2025-08-02 09:05:30 +02:00
coord=Coordinates(int(lat), int(lon)),
2025-08-02 01:14:58 +02:00
range_size=int(room_range),
name=room_name,
pin=room_pin,
tags=set([tag for tag in request.args.get("tags", "").split(",") if tag]),
songs={},
history=[],
playing=[],
playing_idx=-1,
)
2025-08-01 23:07:00 +02:00
state.rooms[room.id] = room
return {"success": True, "room_id": room.id}
@app.get("/api/room")
def room():
2025-08-02 08:43:41 +02:00
lat = request.args.get("lat")
lon = request.args.get("lon")
2025-08-02 09:05:30 +02:00
2025-08-02 08:43:41 +02:00
if lat and lon:
2025-08-02 09:05:30 +02:00
user_coords = Coordinates(latitude=int(lat), longitude=int(lon))
2025-08-02 08:43:41 +02:00
else:
return error("Missing user coordinates")
return [
{
"id": room.id,
"name": room.name,
"private": room.pin is not None,
"coords": room.coord,
2025-08-02 01:36:14 +02:00
"range": room.range_size,
2025-08-02 09:05:30 +02:00
"distance": d,
}
2025-08-01 23:07:00 +02:00
for room in state.rooms.values()
2025-08-02 09:05:30 +02:00
if (d := distance_between_coords(user_coords, room.coord)) <= room.range_size
]
2025-08-01 23:23:00 +02:00
@app.post("/api/addsong")
def add_song():
if (room_id := request.args.get("room")) is None:
return error("Missing room id")
if (room := state.rooms.get(int(room_id))) is None:
2025-08-01 23:23:00 +02:00
return error("Invalid room")
if (query := request.args.get("query")) is None:
return error("Missing query")
2025-08-02 06:00:11 +02:00
if (info := query_search(query)) is None:
return error("Search failed")
2025-08-01 23:23:00 +02:00
if (song := get_song_by_title_artist(info.title, info.artist)) is None:
## song not found, downolad from YT
2025-08-02 04:09:01 +02:00
yt_video_id = yt_search_song(info.title, info.artist)
2025-08-02 00:59:38 +02:00
2025-08-01 23:23:00 +02:00
## add in DB
2025-08-02 00:59:38 +02:00
song = Song(
uuid=str(uuid.uuid4()),
title=info.title,
artist=info.artist,
tags=info.tags,
image_id=info.img_id,
2025-08-02 04:09:01 +02:00
youtube_id=yt_video_id,
2025-08-02 00:59:38 +02:00
)
add_song_in_db(song)
2025-08-02 01:02:43 +02:00
## add the song in the room if does not exists
if song.uuid not in room.songs:
room.songs[song.uuid] = (song, 1) # start with one vote
2025-08-02 04:47:04 +02:00
socketio.emit("new_song", {"song": asdict(song) | {"upvote": 1}}, to=str(room.id))
2025-08-02 00:59:38 +02:00
2025-08-02 01:02:43 +02:00
return {"success": True, "song": song}
2025-08-01 23:23:00 +02:00
2025-08-02 00:59:38 +02:00
@app.get("/api/room/suggestions")
def get_room_suggestion():
if (room_id := request.args.get("room")) is None:
return error("Missing room id")
if (room := state.rooms.get(int(room_id))) is None:
return error("Invalid room id")
2025-08-02 01:31:02 +02:00
return {"success": True, "songs": [asdict(song) | {"upvote": score} for song, score in room.songs.values()]}
2025-08-02 00:59:38 +02:00
@app.post("/api/song/voting")
def post_song_vote():
if (room_id := request.args.get("room")) is None:
return error("Missing room id")
if (room := state.rooms.get(int(room_id))) is None:
return error("Invalid room id")
if (song_id := request.args.get("song")) is None:
return error("Missing song id")
if (song_info := room.songs.get(song_id)) is None:
return error("Invalid song id")
## update the song
room.songs[song_id] = (song_info[0], song_info[1] + int(request.args["increment"]))
2025-08-02 04:47:04 +02:00
socketio.emit("new_vote", {"song": asdict(song_info[0]) | {"upvote": song_info[1]}})
2025-08-02 00:59:38 +02:00
return {"success": True}
2025-08-02 00:44:35 +02:00
@app.get("/api/room/qrcode")
def room_qrcode():
if (room_id := request.args.get("room")) is None:
return error("Missing room id")
if (pin := request.args.get("pin")) is not None:
pin = int(pin)
stream = generate_qr(
base_uri="https://chillbox.leoinvents.com",
room_id=int(room_id),
pin=pin,
)
return Response(stream, content_type="image/jpeg")
2025-08-02 04:09:01 +02:00
@app.get("/api/song/audio")
def get_audio_url():
if (song_uuid := request.args.get("song")) is None:
return error("Missing song id")
if (song := get_song_by_uuid(song_uuid)) is None:
return error("Song not found")
if (url := yt_get_audio_url(song.youtube_id)) is None:
return error("Cannot get audio url")
return {"success": True, "url": url}
2025-08-01 18:07:11 +02:00
if __name__ == "__main__":
2025-08-02 07:59:50 +02:00
socketio.run(app, host="0.0.0.0", port=5000, debug=True)