173 lines
3.4 KiB
Go
173 lines
3.4 KiB
Go
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,
|
|
}
|
|
}
|