package handlers import ( "context" "encoding/json" "errors" "fmt" "gralias/broker" "gralias/llmapi" "gralias/models" "gralias/utils" "gralias/wordloader" "strings" ) func createRoom(ctx context.Context, req *models.RoomReq) (*models.Room, error) { creator, ok := ctx.Value(models.CtxUsernameKey).(string) if !ok { err := errors.New("failed to extract user from ctx") return nil, err } room := req.CreateRoom(creator) room.RoomLink = cfg.BaseURL + "/room-join?id=" + room.ID if err := saveRoom(room); err != nil { return nil, err } return room, nil } func saveRoom(room *models.Room) error { key := models.CacheRoomPrefix + room.ID data, err := json.Marshal(room) if err != nil { return err } memcache.Set(key, data) // do I need last action here? since room save is kind of an action on itself // time.Now().Add(time.Hour).Sub(room.LastActionTS) anHour := int64(216000) // 60 * 60 * 60 memcache.Expire(key, anHour) return nil } func getRoomByID(roomID string) (*models.Room, error) { roomBytes, err := memcache.Get(models.CacheRoomPrefix + roomID) if err != nil { return nil, err } resp := &models.Room{} if err := json.Unmarshal(roomBytes, &resp); err != nil { return nil, err } return resp, nil } func removeRoom(roomID string) { key := models.CacheRoomPrefix + roomID memcache.RemoveKey(key) } // context func getStateByCtx(ctx context.Context) (*models.UserState, error) { username, ok := ctx.Value(models.CtxUsernameKey).(string) if !ok { log.Debug("no username in ctx") return &models.UserState{}, errors.New("no username in ctx") } us, err := loadState(username) if err != nil { return &models.UserState{}, err } return us, nil } func saveFullInfo(fi *models.FullInfo) error { // INFO: no transactions; so case is possible where first object is updated but the second is not if err := saveState(fi.State.Username, fi.State); err != nil { return err } log.Debug("saved user state", "state", fi.State) if err := saveRoom(fi.Room); err != nil { return err } return nil } func notifyBotIfNeeded(fi *models.FullInfo) { if botName := fi.Room.WhichBotToMove(); botName != "" { log.Debug("got botname", "name", botName, "channel_len", len(llmapi.SignalChanMap[botName])) llmapi.SignalChanMap[botName] <- true log.Debug("after sending signal", "name", botName) } } // cache func saveState(username string, state *models.UserState) error { key := models.CacheStatePrefix + username data, err := json.Marshal(state) if err != nil { return err } memcache.Set(key, data) return nil } func loadState(username string) (*models.UserState, error) { key := models.CacheStatePrefix + username data, err := memcache.Get(key) if err != nil { return nil, err } resp := &models.UserState{} if err := json.Unmarshal(data, &resp); err != nil { return nil, err } return resp, nil } // not used // func loadBot(botName, roomID string) (*llmapi.Bot, error) { // key := "botkey_" + roomID + botName // data, err := memcache.Get(key) // if err != nil { // return nil, err // } // resp := &llmapi.Bot{} // if err := json.Unmarshal(data, &resp); err != nil { // return nil, err // } // return resp, nil // } func getAllNames() []string { names := []string{} // will not scale wholeMemStore := memcache.GetAll() session := &models.Session{} // filter by key size only sessions for k, v := range wholeMemStore { // xid is 20 in len if len(k) != 20 { continue } if err := json.Unmarshal(v, &session); err != nil { log.Error("failed to unmarshal", "error", err) continue } names = append(names, session.Username) } return names } // can room exists without state? I think no func getFullInfoByCtx(ctx context.Context) (*models.FullInfo, error) { resp := &models.FullInfo{} state, err := getStateByCtx(ctx) if err != nil { return nil, err } resp.State = state if state.RoomID == "" { return resp, nil } room, err := getRoomByID(state.RoomID) if err != nil { log.Warn("failed to find room despite knowing room_id;", "room_id", state.RoomID) return nil, err } resp.Room = room return resp, nil } func leaveRole(fi *models.FullInfo) { fi.Room.RedTeam.Guessers = utils.RemoveFromSlice(fi.State.Username, fi.Room.RedTeam.Guessers) fi.Room.BlueTeam.Guessers = utils.RemoveFromSlice(fi.State.Username, fi.Room.BlueTeam.Guessers) if fi.Room.RedTeam.Mime == fi.State.Username { fi.Room.RedTeam.Mime = "" } if fi.Room.BlueTeam.Mime == fi.State.Username { fi.Room.BlueTeam.Mime = "" } } func joinTeam(ctx context.Context, role, team string) (*models.FullInfo, error) { // get username fi, _ := getFullInfoByCtx(ctx) // leave gueesers if present leaveRole(fi) // get room if role == "mime" { if team == "blue" { if fi.Room.BlueTeam.Mime != "" { // error: alredy taken err := errors.New("Mime role already taken!") return fi, err } fi.Room.BlueTeam.Mime = fi.State.Username fi.Room.BlueTeam.Color = "blue" fi.State.Team = "blue" fi.State.Role = "mime" } else if team == "red" { if fi.Room.RedTeam.Mime != "" { // error: alredy taken err := errors.New("Mime role already taken!") return fi, err } fi.Room.RedTeam.Mime = fi.State.Username fi.Room.RedTeam.Color = "red" fi.State.Team = "red" fi.State.Role = "mime" } else { err := errors.New("uknown team:" + team) return nil, err } } else if role == "guesser" { if team == "blue" { fi.Room.BlueTeam.Guessers = append(fi.Room.BlueTeam.Guessers, fi.State.Username) fi.Room.BlueTeam.Color = "blue" fi.State.Team = "blue" fi.State.Role = "guesser" } else if team == "red" { fi.Room.RedTeam.Guessers = append(fi.Room.RedTeam.Guessers, fi.State.Username) fi.Room.RedTeam.Color = "red" fi.State.Team = "red" fi.State.Role = "guesser" } else { err := errors.New("uknown team:" + team) return nil, err } } else { err := errors.New("uknown role:" + role) return nil, err } if err := saveFullInfo(fi); err != nil { return nil, err } return fi, nil } // get all rooms func listRooms(allRooms bool) []*models.Room { cacheMap := memcache.GetAll() publicRooms := []*models.Room{} // no way to know if room is public until unmarshal -_-; for key, value := range cacheMap { if strings.HasPrefix(key, models.CacheRoomPrefix) { room := &models.Room{} if err := json.Unmarshal(value, &room); err != nil { log.Warn("failed to unmarshal room", "error", err) continue } if room.IsPublic || allRooms { publicRooms = append(publicRooms, room) } } } return publicRooms } // get bots func listBots() map[string]map[string]string { cacheMap := memcache.GetAll() resp := make(map[string]map[string]string) // no way to know if room is public until unmarshal -_-; for key, value := range cacheMap { if strings.HasPrefix(key, models.CacheBotPredix) { botMap := make(map[string]string) if err := json.Unmarshal(value, &botMap); err != nil { log.Warn("failed to unmarshal bot", "error", err) continue } resp[botMap["bot_name"]] = botMap } } return resp } // get players func listPlayers() map[string]map[string]string { cacheMap := memcache.GetAll() resp := make(map[string]map[string]string) // no way to know if room is public until unmarshal -_-; for key, value := range cacheMap { if strings.HasPrefix(key, models.CacheStatePrefix) { playerMap := make(map[string]string) if err := json.Unmarshal(value, &playerMap); err != nil { log.Warn("failed to unmarshal player", "error", err) continue } resp[playerMap["Username"]] = playerMap } } return resp } func notify(event, msg string) { Notifier.Notifier <- broker.NotificationEvent{ EventName: event, Payload: msg, } } func loadCards(room *models.Room) { wl := wordloader.InitDefaultLoader("assets/words/en_nouns.txt") cards, err := wl.Load() if err != nil { // no logger fmt.Println("failed to load cards", "error", err) } room.Cards = cards room.WCMap = make(map[string]models.WordColor) for _, card := range room.Cards { room.WCMap[card.Word] = card.Color } } func recoverBots() { bots := listBots() for botName, botMap := range bots { if err := recoverBot(botMap); err != nil { log.Warn("failed to recover bot", "botName", botName, "error", err) } } } func recoverBot(bm map[string]string) error { // check if room still exists if _, err := getRoomByID(bm["room_id"]); err != nil { return fmt.Errorf("no such room: %s; err: %w", bm["room_id"], err) } log.Debug("recovering bot", "bot", bm) _, err := llmapi.NewBot(bm["role"], bm["team"], bm["bot_name"], bm["room_id"], cfg, true) if err != nil { return err } return nil } func recoverPlayers() { players := listPlayers() for playerName, playerMap := range players { if err := recoverPlayer(playerMap); err != nil { log.Warn("failed to recover player", "playerName", playerName, "error", err) } } } func recoverPlayer(pm map[string]string) error { // check if room still exists room, err := getRoomByID(pm["RoomID"]) if err != nil { return fmt.Errorf("no such room: %s; err: %w", pm["RoomID"], err) } log.Debug("recovering player", "player", pm) role, team, ok := room.GetPlayerByName(pm["Username"]) if !ok { return fmt.Errorf("failed to find player %s in the room %v", pm["Username"], room) } us := &models.UserState{ Username: pm["Username"], RoomID: pm["RoomID"], Team: team, Role: role, } return saveState(pm["Username"], us) }