package eric.Roullette.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import io.javalin.Javalin; import io.javalin.http.Context; import eric.Roullette.service.GameService; import eric.Roullette.service.SpotifyAuthService; import eric.Roullette.websocket.GameWebSocketHandler; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.MediaType; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.*; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; public class GameController { private final GameService gameService; private final SpotifyAuthService authService; private final GameWebSocketHandler webSocketHandler; private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GameController.class); public GameController(Javalin app, GameService gs, SpotifyAuthService sas, GameWebSocketHandler wsHandler) { this.gameService = gs; this.authService = sas; this.webSocketHandler = wsHandler; app.post("/api/create-game", this::createGame); app.post("/api/join-game", this::joinGame); app.get("/api/game/{gameId}/players", this::getPlayers); app.post("/api/game/{gameId}/start-round", this::startRound); app.post("/api/game/{gameId}/guess", this::guess); app.post("/api/spotify/play", this::playTrack); app.get("/api/spotify/devices", ctx -> { String username = ctx.queryParam("username"); if (username == null) { ctx.status(400).result("username fehlt"); return; } var devices = authService.getDevices(username); // Diese Methode muss es im SpotifyAuthService geben ctx.json(Map.of("devices", devices)); }); } private void createGame(Context ctx) { Map body = ctx.bodyAsClass(Map.class); String user = (String) body.get("username"); if (user == null || user.isBlank()) { ctx.status(400).result("username fehlt"); return; } String gameId; do { gameId = String.format("%04d", ThreadLocalRandom.current().nextInt(0, 10000)); } while (gameService.gameExists(gameId)); gameService.createGame(gameId); gameService.addPlayer(gameId, user); gameService.broadcastPlayers(gameId); ctx.json(Map.of("status", "ok", "gameId", gameId)); } private void joinGame(Context ctx) { Map body = ctx.bodyAsClass(Map.class); String user = body.get("username"); String gameId = body.get("gameId"); if (user == null || gameId == null) { ctx.status(400).result("username oder gameId fehlt"); return; } gameService.addPlayer(gameId, user); gameService.broadcastPlayers(gameId); ctx.json(Map.of("status", "ok")); } private void getPlayers(Context ctx) { String gameId = ctx.pathParam("gameId"); var game = gameService.getOrCreateGame(gameId); ctx.json(Map.of( "players", game.players() )); } private void startRound(Context ctx) { String gameId = ctx.pathParam("gameId"); ctx.json(Map.of("status", "ok")); webSocketHandler.broadcastRoundStart(gameId); } private void guess(Context ctx) { String gameId = ctx.pathParam("gameId"); Map body = ctx.bodyAsClass(Map.class); String guess = body.get("guess"); String user = body.get("username"); GameService.Game game = gameService.getOrCreateGame(gameId); String owner = game.currentOwner(); if (owner == null || guess == null) { ctx.status(400).result("ungültig"); return; } boolean correct = guess.equals(owner); if (correct) game.scores().merge(user, 1, Integer::sum); ctx.json(Map.of( "correct", correct, "owner", owner, "scores", game.scores() )); } private void playTrack(Context ctx) { Map body = ctx.bodyAsClass(Map.class); String username = body.get("username"); String deviceId = body.get("device_id"); String trackUri = body.get("track_uri"); if (username == null || deviceId == null || trackUri == null) { ctx.status(400).result("Fehlende Parameter: username, device_id oder track_uri"); return; } try { String accessToken = authService.getAccessTokenForUser(username); OkHttpClient client = new OkHttpClient(); // 1. Track-Details holen String trackId = trackUri.split(":")[2]; Request getTrack = new Request.Builder() .url("https://api.spotify.com/v1/tracks/" + trackId) .addHeader("Authorization", "Bearer " + accessToken) .build(); try (Response trackResp = client.newCall(getTrack).execute()) { if (!trackResp.isSuccessful()) { ctx.status(500).result("Fehler beim Laden der Track-Details"); return; } var node = new com.fasterxml.jackson.databind.ObjectMapper().readTree(trackResp.body().string()); long durationMs = node.get("duration_ms").asLong(); long startOffset = durationMs / 2; // 2. Play-Request mit position_ms ObjectNode jsonNode = JsonNodeFactory.instance.objectNode(); jsonNode.putArray("uris").add(trackUri); jsonNode.put("position_ms", startOffset); String jsonBody = jsonNode.toString(); Request playReq = new Request.Builder() .url("https://api.spotify.com/v1/me/player/play?device_id=" + deviceId) .addHeader("Authorization", "Bearer " + accessToken) .put(RequestBody.create(jsonBody, MediaType.parse("application/json"))) .build(); try (Response playResp = client.newCall(playReq).execute()) { if (playResp.isSuccessful()) { ctx.status(204).result("Track erfolgreich abgespielt"); } else { ctx.status(playResp.code()).result("Fehler: " + playResp.body().string()); } } } } catch (Exception e) { log.error("Fehler beim Abspielen des Tracks", e); ctx.status(500).result("Interner Fehler"); } } }