package api import ( "fmt" "net/http" "sync" "sprintpadawan/lib" ) type contextKey string const userKey contextKey = "user" type sseClient struct { ch chan string userID int } type MemberView struct { Username string HasVoted bool ID int } type RoomData struct { *lib.Room User *lib.User Members []MemberView Stories []lib.Story IsOwner bool UserVotes map[int]string StoryVotes map[int][]lib.VoteView } var ( roomClients = make(map[int][]*sseClient) clientsMu sync.Mutex ) func addSSEClient(roomID, userID int, ch chan string) { clientsMu.Lock() defer clientsMu.Unlock() roomClients[roomID] = append(roomClients[roomID], &sseClient{ch: ch, userID: userID}) } func removeSSEClient(roomID int, ch chan string) { clientsMu.Lock() defer clientsMu.Unlock() clients := roomClients[roomID] for i, c := range clients { if c.ch == ch { roomClients[roomID] = append(clients[:i], clients[i+1:]...) break } } if len(roomClients[roomID]) == 0 { delete(roomClients, roomID) } } func broadcast(roomID int, event string) { clientsMu.Lock() defer clientsMu.Unlock() alive := roomClients[roomID][:0] for _, c := range roomClients[roomID] { select { case c.ch <- event: alive = append(alive, c) default: // drop dead client } } roomClients[roomID] = alive } func GetConnectedUserIDs(roomID int) []int { clientsMu.Lock() defer clientsMu.Unlock() seen := make(map[int]bool) var ids []int for _, c := range roomClients[roomID] { if !seen[c.userID] { seen[c.userID] = true ids = append(ids, c.userID) } } return ids } func scaleToOptions(scale string) []string { switch scale { case "fibonacci": return []string{"1", "2", "3", "5", "8", "13", "21", "?"} case "tshirt": return []string{"XS", "S", "M", "L", "XL", "XXL", "?"} case "linear": return []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "?"} default: return []string{"1", "2", "3", "5", "8", "13", "21", "?"} } } func getRoomID(r *http.Request) int { return getPathInt(r, "id") } func getPathInt(r *http.Request, key string) int { var id int fmt.Sscanf(r.PathValue(key), "%d", &id) return id } func buildRoomData(room *lib.Room, user *lib.User) RoomData { members, _ := lib.GetRoomMembers(room.ID) stories, _ := lib.GetStoriesForRoom(room.ID) connectedIDs := GetConnectedUserIDs(room.ID) connectedMap := make(map[int]bool) for _, cid := range connectedIDs { connectedMap[cid] = true } connectedMap[user.ID] = true var activeVotes []lib.Vote if room.ActiveStoryID != nil { activeVotes, _ = lib.GetVotesForStory(*room.ActiveStoryID) } var memberViews []MemberView for _, m := range members { if !connectedMap[m.ID] { continue } hasVoted := false for _, v := range activeVotes { if v.UserID == m.ID { hasVoted = true break } } memberViews = append(memberViews, MemberView{ Username: m.Username, HasVoted: hasVoted, ID: m.ID, }) } userVotes := make(map[int]string) storyVotes := make(map[int][]lib.VoteView) for _, s := range stories { votes, _ := lib.GetVotesForStory(s.ID) for _, v := range votes { if v.UserID == user.ID { userVotes[s.ID] = v.Value } } if s.Voted { vv, _ := lib.GetVotesWithUsernames(s.ID) storyVotes[s.ID] = vv } } return RoomData{ Room: room, User: user, Members: memberViews, Stories: stories, IsOwner: room.OwnerID == user.ID, UserVotes: userVotes, StoryVotes: storyVotes, } }