package handlers import ( "crypto/rand" "database/sql" "encoding/json" "fmt" "net/http" "mmgame/internal/database" "mmgame/internal/models" "mmgame/internal/scripts" "mmgame/internal/util" ) func newID() string { b := make([]byte, 16) _, _ = rand.Read(b) b[6] = (b[6] & 0x0f) | 0x40 b[8] = (b[8] & 0x3f) | 0x80 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) } func writeJSON(w http.ResponseWriter, status int, v interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } func writeError(w http.ResponseWriter, status int, detail string) { writeJSON(w, status, map[string]string{"detail": detail}) } func nullStr(s *string) string { if s == nil { return "" } return *s } func Health(w http.ResponseWriter, r *http.Request) { writeJSON(w, 200, map[string]string{"status": "ok"}) } func parseConfig(configStr string) map[string]interface{} { if configStr == "" { return map[string]interface{}{} } var m map[string]interface{} if err := json.Unmarshal([]byte(configStr), &m); err != nil { return map[string]interface{}{} } return m } func scriptFromConfig(configStr string) *scripts.Script { cfg := parseConfig(configStr) raw, ok := cfg["script"] if !ok || raw == nil { return nil } b, err := json.Marshal(raw) if err != nil { return nil } var s scripts.Script if err := json.Unmarshal(b, &s); err != nil { return nil } return &s } func getGame(gameID string) (*models.GameSession, error) { row := database.DB.QueryRow( `SELECT id, script_id, status, phase, phase_index, started_at, completed_at, config, created_at FROM game_sessions WHERE id = ?`, gameID) var g models.GameSession var startedAt, completedAt, configStr, createdAt sql.NullString err := row.Scan(&g.ID, &g.ScriptID, &g.Status, &g.Phase, &g.PhaseIndex, &startedAt, &completedAt, &configStr, &createdAt) if err != nil { return nil, err } if startedAt.Valid { t := util.ParseTime(startedAt.String) g.StartedAt = &t } if completedAt.Valid { t := util.ParseTime(completedAt.String) g.CompletedAt = &t } g.Config = configStr.String g.CreatedAt = util.ParseTime(createdAt.String) return &g, nil } func queryPlayers(gameID string) []*models.SessionPlayer { rows, err := database.DB.Query( `SELECT id, session_id, user_id, role_id, role_name, is_human, is_ready, avatar_url, status, joined_at FROM session_players WHERE session_id = ?`, gameID) if err != nil { return nil } defer rows.Close() var list []*models.SessionPlayer for rows.Next() { p := &models.SessionPlayer{} var userID, avatarURL, joinedAt sql.NullString if err := rows.Scan(&p.ID, &p.SessionID, &userID, &p.RoleID, &p.RoleName, &p.IsHuman, &p.IsReady, &avatarURL, &p.Status, &joinedAt); err != nil { continue } if userID.Valid { p.UserID = &userID.String } if avatarURL.Valid { p.AvatarURL = &avatarURL.String } p.JoinedAt = util.ParseTime(joinedAt.String) list = append(list, p) } return list } func queryMessages(gameID string) []*models.ChatMessage { rows, err := database.DB.Query( `SELECT id, session_id, sender_role_id, sender_name, message_type, content, target_role_id, clue_id, phase_index, created_at FROM chat_messages WHERE session_id = ? ORDER BY created_at`, gameID) if err != nil { return nil } defer rows.Close() var list []*models.ChatMessage for rows.Next() { m := &models.ChatMessage{} var targetRoleID, clueID, createdAt sql.NullString if err := rows.Scan(&m.ID, &m.SessionID, &m.SenderRoleID, &m.SenderName, &m.MessageType, &m.Content, &targetRoleID, &clueID, &m.PhaseIndex, &createdAt); err != nil { continue } if targetRoleID.Valid { m.TargetRoleID = &targetRoleID.String } if clueID.Valid { m.ClueID = &clueID.String } m.CreatedAt = util.ParseTime(createdAt.String) list = append(list, m) } return list } func queryClues(gameID string) []*models.ClueState { rows, err := database.DB.Query( `SELECT id, session_id, clue_id, status, revealed_by, released_at, created_at FROM clues_state WHERE session_id = ?`, gameID) if err != nil { return nil } defer rows.Close() var list []*models.ClueState for rows.Next() { c := &models.ClueState{} var revealedBy, releasedAt, createdAt sql.NullString if err := rows.Scan(&c.ID, &c.SessionID, &c.ClueID, &c.Status, &revealedBy, &releasedAt, &createdAt); err != nil { continue } if revealedBy.Valid { c.RevealedBy = &revealedBy.String } if releasedAt.Valid { t := util.ParseTime(releasedAt.String) c.ReleasedAt = &t } c.CreatedAt = util.ParseTime(createdAt.String) list = append(list, c) } return list } func queryVotes(gameID string) []*models.Vote { rows, err := database.DB.Query( `SELECT id, session_id, round, voter_id, target_id, reason, created_at FROM votes WHERE session_id = ?`, gameID) if err != nil { return nil } defer rows.Close() var list []*models.Vote for rows.Next() { v := &models.Vote{} var reason, createdAt sql.NullString if err := rows.Scan(&v.ID, &v.SessionID, &v.Round, &v.VoterID, &v.TargetID, &reason, &createdAt); err != nil { continue } if reason.Valid { v.Reason = &reason.String } v.CreatedAt = util.ParseTime(createdAt.String) list = append(list, v) } return list } func messageDict(m *models.ChatMessage) map[string]interface{} { d := map[string]interface{}{ "id": m.ID, "sender_role_id": m.SenderRoleID, "sender_name": m.SenderName, "message_type": m.MessageType, "content": m.Content, "target_role_id": m.TargetRoleID, "clue_id": m.ClueID, "phase_index": m.PhaseIndex, "created_at": util.TimeStr(m.CreatedAt), } return d }