package com.oliver.kidio.backend.plugins import com.oliver.kidio.backend.* import com.oliver.kidio.backend.domain.repository.KidioRepository import com.oliver.kidio.backend.security.JwtService import io.ktor.http.HttpStatusCode import io.ktor.server.application.* import io.ktor.server.auth.authenticate import io.ktor.server.auth.principal import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* import org.mindrot.jbcrypt.BCrypt import java.util.UUID fun Application.configureRouting( repository: KidioRepository, jwtService: JwtService, ) { routing { post("/api/v1/auth/register") { val registerRequest = call.receive() if (registerRequest.email.isBlank() || registerRequest.passwordPlain.isBlank()) { call.respond(HttpStatusCode.BadRequest, "Bitte füllen Sie alle Felder aus") return@post } if (registerRequest.parentPin.length != 4) { call.respond(HttpStatusCode.BadRequest, "Die Eltern-PIN muss genau 4 Ziffern lang sein") return@post } val existingUser = repository.findByEmail(registerRequest.email) if (existingUser != null) { call.respond(HttpStatusCode.Conflict, "Ein Benutzer mit dieser E-Mail-Adresse existiert bereits") return@post } // Backend generiert die eindeutige User-ID val userId = UUID.randomUUID().toString() val passwordHash = BCrypt.hashpw(registerRequest.passwordPlain, BCrypt.gensalt()) // datenbank insert hinzufügen repository.registerUser( User( userId, registerRequest.email, passwordHash, registerRequest.familyName, registerRequest.childAge, registerRequest.parentPin, ), ) val token = jwtService.generateToken(userId) call.respond( HttpStatusCode.Created, mapOf( "token" to token, "userId" to userId, "message" to "Registrierung erfolgreich", ), ) } // Health-Check get("/api/v1/health") { call.respond(mapOf("status" to "OK", "message" to "Kidio Backend läuft!")) } post("/api/v1/auth/login") { val loginRequest = call.receive() if (loginRequest.email.isBlank() || loginRequest.passwordPlain.isBlank()) { call.respond(HttpStatusCode.BadRequest, "Bitte füllen Sie alle Felder aus") return@post } val getUser = repository.findByEmail(loginRequest.email) if (getUser == null || !repository.verifyPassword(loginRequest.passwordPlain, getUser.passwordHash)) { call.respond(HttpStatusCode.Unauthorized, "Ungültige Anmeldedaten") return@post } val token = jwtService.generateToken(getUser.id) call.respond( HttpStatusCode.OK, AuthResponse( token, userId = getUser.id, familyName = getUser.familyName, childAge = getUser.childAge, ), ) return@post } authenticate("auth-jwt") { // 1. Alle verfügbaren Aufgaben abrufen (mit optionaler Filterung nach Fach und Alter) get("/api/v1/tasks") { val subject = call.request.queryParameters["subject"] val age = call.request.queryParameters["age"]?.toIntOrNull() val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 10 val tasks = repository.getTasks(subject = subject, age = age, limit = limit) call.respond(tasks) } // 2. Antwort auf eine Aufgabe überprüfen post("/api/v1/tasks/verify") { val request = call.receive() val principal = call.principal() val userId = principal?.userId if (userId == null) { call.respond(HttpStatusCode.Unauthorized, mapOf("error" to "Ungültiges oder abgelaufenes Token")) return@post } val taskInfo = repository.getTaskAnswerAndReward(request.taskId) if (taskInfo == null) { call.respond( TaskVerifyResponse( isCorrect = false, earnedMinutes = 0, message = "Aufgabe nicht gefunden!", ), ) return@post } val (correctAnswer, reward) = taskInfo val isCorrect = request.selectedAnswer.trim() == correctAnswer.trim() if (isCorrect) { val currentScreentime = repository.getScreentime(userId) val newSeconds = currentScreentime + (reward * 60) repository.updateScreentime(userId, newSeconds) call.respond( TaskVerifyResponse( isCorrect = true, earnedMinutes = reward, message = "Super gemacht! Du hast $reward Minuten gewonnen.", ), ) } else { call.respond( TaskVerifyResponse( isCorrect = false, earnedMinutes = 0, message = "Schade, das war leider falsch. Versuche es nochmal!", ), ) } } get("/api/v1/screentime") { val principal = call.principal() val userid = principal?.userId ?: run { call.respond(HttpStatusCode.Unauthorized, mapOf("error" to "Ungültiger Token")) return@get } val remainingSeconds = repository.getScreentime(userid) call.respond( HttpStatusCode.OK, SyncResponse( remainingSeconds = remainingSeconds, isBlocked = remainingSeconds <= 0, ), ) } // 3. Bildschirmzeit synchronisieren post("/api/v1/screentime/sync") { val request = call.receive() // Wir holen die echte, fälschungssichere userId aus dem Login-Token: val principal = call.principal() val userId = principal?.userId ?: request.userId // Fallback auf request.userId, falls token noch fehlt val currentSeconds = repository.getScreentime(userId) var newSeconds = currentSeconds - request.usedSecondsSinceLastSync if (newSeconds < 0) newSeconds = 0 repository.updateScreentime(userId, newSeconds) call.respond( SyncResponse( remainingSeconds = newSeconds, isBlocked = newSeconds <= 0, ), ) } } } }