refactor: rewrite backend in Go, replacing Python FastAPI
Port the FastAPI backend to Go 1.26, preserving all API functionality: - auth (register/login/guest/logout/profile/history) with JWT + bcrypt - scripts (list/detail) backed by embedded sample_data.json - games (create/join/start/chat/phase/clue/vote/state/replay) + WebSocket - rule-based NPC agent plus DeepSeek/Ollama AI backends - SQLite persistence via modernc.org/sqlite (pure Go, no cgo) Frontend and documents are unchanged.
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Conn *websocket.Conn
|
||||
RoleID string
|
||||
}
|
||||
|
||||
var (
|
||||
connsMu sync.RWMutex
|
||||
activeConns = map[string][]*Client{}
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
}
|
||||
|
||||
func removeConn(gameID string, c *Client) {
|
||||
connsMu.Lock()
|
||||
defer connsMu.Unlock()
|
||||
clients := activeConns[gameID]
|
||||
for i, x := range clients {
|
||||
if x == c {
|
||||
activeConns[gameID] = append(clients[:i], clients[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(activeConns[gameID]) == 0 {
|
||||
delete(activeConns, gameID)
|
||||
}
|
||||
}
|
||||
|
||||
func removeGameConns(gameID string) {
|
||||
connsMu.Lock()
|
||||
defer connsMu.Unlock()
|
||||
if clients, ok := activeConns[gameID]; ok {
|
||||
for _, c := range clients {
|
||||
_ = c.Conn.Close()
|
||||
}
|
||||
}
|
||||
delete(activeConns, gameID)
|
||||
}
|
||||
|
||||
func broadcast(gameID string, message interface{}) {
|
||||
data, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
connsMu.RLock()
|
||||
clients := append([]*Client{}, activeConns[gameID]...)
|
||||
connsMu.RUnlock()
|
||||
for _, c := range clients {
|
||||
if err := c.Conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
removeConn(gameID, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func broadcastToRole(gameID, targetRoleID string, message interface{}) {
|
||||
data, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
connsMu.RLock()
|
||||
clients := append([]*Client{}, activeConns[gameID]...)
|
||||
connsMu.RUnlock()
|
||||
for _, c := range clients {
|
||||
if c.RoleID != "" && c.RoleID != targetRoleID {
|
||||
continue
|
||||
}
|
||||
if err := c.Conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
removeConn(gameID, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GameWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
gameID := chi.URLParam(r, "game_id")
|
||||
roleID := r.URL.Query().Get("role_id")
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
client := &Client{Conn: conn, RoleID: roleID}
|
||||
|
||||
connsMu.Lock()
|
||||
activeConns[gameID] = append(activeConns[gameID], client)
|
||||
connsMu.Unlock()
|
||||
|
||||
defer func() {
|
||||
removeConn(gameID, client)
|
||||
_ = conn.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
_, data, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var msg struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &msg); err != nil {
|
||||
continue
|
||||
}
|
||||
if msg.Type == "ping" {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, []byte(`{"type":"pong"}`)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user