package voice import ( "database/sql" "encoding/json" "net/http" "git.dustin.coffee/hobokenchicken/dumpsterChat/internal/gateway" "git.dustin.coffee/hobokenchicken/dumpsterChat/internal/middleware" "github.com/go-chi/chi/v5" "github.com/livekit/protocol/livekit" ) type Handler struct { db *sql.DB client *Client hub *gateway.Hub } func NewHandler(db *sql.DB, client *Client, hub *gateway.Hub) *Handler { return &Handler{ db: db, client: client, hub: hub, } } func (h *Handler) RegisterRoutes(r chi.Router) { r.Post("/token", h.GetToken) r.Get("/rooms/{roomID}/participants", h.GetParticipants) } type tokenRequest struct { RoomName string `json:"room_name"` } type tokenResponse struct { Token string `json:"token"` LiveKitURL string `json:"livekit_url"` } func (h *Handler) GetToken(w http.ResponseWriter, r *http.Request) { userID, ok := middleware.UserIDFromContext(r.Context()) if !ok { http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized) return } var req tokenRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest) return } if req.RoomName == "" { http.Error(w, `{"error":"missing room_name"}`, http.StatusBadRequest) return } // Get username for the identity var username string err := h.db.QueryRowContext(r.Context(), `SELECT username FROM users WHERE id = $1`, userID).Scan(&username) if err != nil { http.Error(w, `{"error":"user not found"}`, http.StatusNotFound) return } // Create room if it doesn't exist _, err = h.client.CreateRoom(req.RoomName) if err != nil { // Room might already exist, that's fine } token, err := h.client.GenerateToken(req.RoomName, userID, username) if err != nil { http.Error(w, `{"error":"token generation failed"}`, http.StatusInternalServerError) return } // Broadcast voice join event h.hub.BroadcastEvent(gateway.Event{ Type: "VOICE_JOIN", Data: map[string]interface{}{ "room_name": req.RoomName, "user_id": userID, "username": username, }, }) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(tokenResponse{ Token: token, LiveKitURL: h.client.url, }) } func (h *Handler) GetParticipants(w http.ResponseWriter, r *http.Request) { roomID := chi.URLParam(r, "roomID") if roomID == "" { http.Error(w, `{"error":"missing room ID"}`, http.StatusBadRequest) return } participants, err := h.client.ListParticipants(roomID) if err != nil { http.Error(w, `{"error":"failed to list participants"}`, http.StatusInternalServerError) return } type participantInfo struct { Identity string `json:"identity"` State string `json:"state"` } result := make([]participantInfo, 0, len(participants)) for _, p := range participants { state := "connected" if p.State == livekit.ParticipantInfo_DISCONNECTED { state = "disconnected" } result = append(result, participantInfo{ Identity: p.Identity, State: state, }) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) }