Files
Horchposten/handlers/session_end.go
2026-03-08 14:44:50 +00:00

119 lines
3.5 KiB
Go

package handlers
import (
"database/sql"
"net/http"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/tas/horchposten/models"
)
// SessionEnd handles POST /api/analytics/session/:session_id/end/
func SessionEnd(db *sql.DB) echo.HandlerFunc {
return func(c echo.Context) error {
sessionID := c.Param("session_id")
if _, err := uuid.Parse(sessionID); err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "invalid session_id format"})
}
var req models.SessionEndRequest
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "invalid JSON"})
}
// Apply defaults.
if req.LevelReached == 0 {
req.LevelReached = 1
}
if req.EndReason == "" {
req.EndReason = "death"
}
if !validEndReasons[req.EndReason] {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "invalid end_reason"})
}
tx, err := db.Begin()
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "database error"})
}
defer tx.Rollback()
// Look up session.
var playerID int64
err = tx.QueryRow(
"SELECT player_id FROM game_sessions WHERE id = ?", sessionID,
).Scan(&playerID)
if err == sql.ErrNoRows {
return c.JSON(http.StatusNotFound, echo.Map{"error": "session not found"})
} else if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "database error"})
}
// Update session.
timestamp := now()
_, err = tx.Exec(
`UPDATE game_sessions SET score = ?, level_reached = ?, lives_used = ?,
duration_seconds = ?, end_reason = ?, ended_at = ?
WHERE id = ?`,
req.Score, req.LevelReached, req.LivesUsed,
req.DurationSeconds, req.EndReason, timestamp,
sessionID,
)
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "failed to update session"})
}
// Increment total_sessions and update last_seen on player.
_, err = tx.Exec(
"UPDATE players SET total_sessions = total_sessions + 1, last_seen = ? WHERE id = ?",
timestamp, playerID,
)
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "failed to update player"})
}
// Update high score.
newHighScore := false
var existingScore int
var highScoreID int64
err = tx.QueryRow(
"SELECT id, score FROM high_scores WHERE player_id = ?", playerID,
).Scan(&highScoreID, &existingScore)
if err == sql.ErrNoRows {
// First session for this player - create high score.
_, err = tx.Exec(
"INSERT INTO high_scores (player_id, score, session_id, achieved_at) VALUES (?, ?, ?, ?)",
playerID, req.Score, sessionID, timestamp,
)
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "failed to create high score"})
}
newHighScore = true
} else if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "database error"})
} else if req.Score >= existingScore {
// New score ties or beats existing high score.
_, err = tx.Exec(
"UPDATE high_scores SET score = ?, session_id = ?, achieved_at = ? WHERE id = ?",
req.Score, sessionID, timestamp, highScoreID,
)
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "failed to update high score"})
}
newHighScore = true
}
if err := tx.Commit(); err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "failed to commit transaction"})
}
return c.JSON(http.StatusOK, echo.Map{
"status": "ok",
"new_high_score": newHighScore,
})
}
}