package handlers import ( "context" "encoding/json" "errors" "fmt" "golias/broker" "golias/llmapi" "golias/models" "golias/utils" "golias/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(models.CacheRoomPrefix+room.ID, data) log.Debug("saved room", "room", room, "key", key) 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: unfortunately working no transactions; so case are possible where first object is updated but the second is not if err := saveState(fi.State.Username, fi.State); err != nil { return err } // if fi.Room == nil { // can be null on exit // return nil // } if err := saveRoom(fi.Room); err != nil { return err } return nil } // 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 } func loadBot(botName, roomID string) (*llmapi.Bot, error) { return nil, 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 joinTeam(ctx context.Context, role, team string) (*models.FullInfo, error) { // get username fi, _ := getFullInfoByCtx(ctx) 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) // 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" if fi.Room.RedTeam.Mime == fi.State.Username { fi.Room.RedTeam.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" if fi.Room.BlueTeam.Mime == fi.State.Username { fi.Room.BlueTeam.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 listPublicRooms() []*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 } log.Debug("consider room for list", "room", room, "key", key) if room.IsPublic { publicRooms = append(publicRooms, room) } } } return publicRooms } 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 } }