134 lines
3.9 KiB
Go
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
|
|
}
|