119 lines
3.5 KiB
Go
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,
|
|
})
|
|
}
|
|
}
|