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

134 lines
3.9 KiB
Go

package handlers
import (
"database/sql"
"net/http"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/tas/horchposten/models"
)
// SessionStart handles POST /api/analytics/session/start/
func SessionStart(db *sql.DB) echo.HandlerFunc {
return func(c echo.Context) error {
var req models.SessionStartRequest
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "invalid JSON"})
}
if req.ClientID == "" {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "client_id is required"})
}
if _, err := uuid.Parse(req.ClientID); err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "invalid client_id format"})
}
tx, err := db.Begin()
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "database error"})
}
defer tx.Rollback()
// Get or create player.
timestamp := now()
var playerID int64
err = tx.QueryRow("SELECT id FROM players WHERE client_id = ?", req.ClientID).Scan(&playerID)
if err == sql.ErrNoRows {
res, err := tx.Exec(
"INSERT INTO players (client_id, first_seen, last_seen, total_sessions) VALUES (?, ?, ?, 0)",
req.ClientID, timestamp, timestamp,
)
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "failed to create player"})
}
playerID, _ = res.LastInsertId()
} else if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "database error"})
}
// Update or create PlayerIP.
clientIP := getClientIP(c.Request())
var ipID int64
err = tx.QueryRow(
"SELECT id FROM player_ips WHERE player_id = ? AND ip_address = ?",
playerID, clientIP,
).Scan(&ipID)
if err == sql.ErrNoRows {
_, err = tx.Exec(
"INSERT INTO player_ips (player_id, ip_address, first_seen, last_seen) VALUES (?, ?, ?, ?)",
playerID, clientIP, timestamp, timestamp,
)
} else if err == nil {
_, err = tx.Exec(
"UPDATE player_ips SET last_seen = ? WHERE id = ?",
timestamp, ipID,
)
}
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "failed to update IP record"})
}
// Create GameSession.
sessionID := uuid.New().String()
_, err = tx.Exec(
`INSERT INTO game_sessions (id, player_id, score, level_reached, lives_used,
duration_seconds, end_reason, started_at, ended_at)
VALUES (?, ?, 0, 1, 0, 0, 'death', ?, ?)`,
sessionID, playerID, timestamp, timestamp,
)
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "failed to create session"})
}
// Create DeviceInfo.
userAgent := c.Request().UserAgent()
if req.Device != nil {
d := req.Device
var sw, sh sql.NullInt32
var dpr sql.NullFloat64
if d.ScreenWidth != nil {
sw = sql.NullInt32{Int32: int32(*d.ScreenWidth), Valid: true}
}
if d.ScreenHeight != nil {
sh = sql.NullInt32{Int32: int32(*d.ScreenHeight), Valid: true}
}
if d.DevicePixelRatio != nil {
dpr = sql.NullFloat64{Float64: *d.DevicePixelRatio, Valid: true}
}
_, err = tx.Exec(
`INSERT INTO device_infos (session_id, ip_address, user_agent, platform, language,
screen_width, screen_height, device_pixel_ratio, timezone, webgl_renderer, touch_support)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
sessionID, clientIP, userAgent, d.Platform, d.Language,
sw, sh, dpr, d.Timezone, d.WebGLRenderer, boolToInt(d.TouchSupport),
)
} else {
_, err = tx.Exec(
`INSERT INTO device_infos (session_id, ip_address, user_agent)
VALUES (?, ?, ?)`,
sessionID, clientIP, userAgent,
)
}
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "failed to create device info"})
}
if err := tx.Commit(); err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "failed to commit transaction"})
}
return c.JSON(http.StatusCreated, echo.Map{"session_id": sessionID})
}
}
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}