diff --git a/config/config.go b/config/config.go index a6b49cd..78b4454 100644 --- a/config/config.go +++ b/config/config.go @@ -10,7 +10,7 @@ import ( type Config struct { ServerConfig ServerConfig `toml:"SERVICE"` BaseURL string `toml:"BASE_URL"` - SessionLifetime int `toml:"SESSION_LIFETIME_SECONDS"` + SessionLifetime int64 `toml:"SESSION_LIFETIME_SECONDS"` CookieSecret string `toml:"COOKIE_SECRET"` LLMConfig LLMConfig `toml:"LLM"` } diff --git a/handlers/auth.go b/handlers/auth.go index 816a31f..3ad4eb9 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -183,17 +183,6 @@ func cacheSetSession(key string, session *models.Session) error { return err } memcache.Set(key, sesb) - // TODO: to config - memcache.Expire(key, 60*60) + memcache.Expire(key, cfg.SessionLifetime) return nil } - -// unused -// func updateRoomInSession(ctx context.Context, roomID string) (context.Context, error) { -// s, ok := ctx.Value(models.CtxSessionKey).(*models.Session) -// if !ok { -// return context.TODO(), errors.New("failed to extract session from ctx") -// } -// s.CurrentRoom = roomID -// return context.WithValue(ctx, "session", s), nil -// } diff --git a/handlers/elements.go b/handlers/elements.go index 50b1df1..279d9f9 100644 --- a/handlers/elements.go +++ b/handlers/elements.go @@ -167,8 +167,6 @@ func HandleAddBot(w http.ResponseWriter, r *http.Request) { abortWithError(w, err.Error()) return } - // TODO: what if bot exists already? - // control number and names of bots botname := fmt.Sprintf("bot_%d", len(llmapi.SignalChanMap)+1) // what if many rooms? bot, err := llmapi.NewBot(role, team, botname, fi.Room.ID, cfg) if err != nil { diff --git a/handlers/game.go b/handlers/game.go index 1832f1a..5aa4f18 100644 --- a/handlers/game.go +++ b/handlers/game.go @@ -135,11 +135,14 @@ func HandleStartGame(w http.ResponseWriter, r *http.Request) { abortWithError(w, err.Error()) return } - // TODO: check if enough players; also button should be hidden if already running + // check if enough players + if err := fi.Room.CanStart(); err != nil { + abortWithError(w, err.Error()) + return + } fi.Room.IsRunning = true fi.Room.IsOver = false fi.Room.TeamTurn = "blue" - // fi.Room.LoadTestCards() loadCards(fi.Room) fi.Room.UpdateCounter() fi.Room.TeamWon = "" diff --git a/handlers/handlers.go b/handlers/handlers.go index f776299..61dacd8 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -26,7 +26,6 @@ func init() { memcache = cache.MemCache cfg = config.LoadConfigOrDefault("") Notifier = broker.Notifier - // go Notifier.Listen() } func HandlePing(w http.ResponseWriter, r *http.Request) { @@ -84,6 +83,7 @@ func HandleExit(w http.ResponseWriter, r *http.Request) { if len(exitedRoom.PlayerList) == 0 || creatorLeft { removeRoom(exitedRoom.ID) // TODO: notify users if creator left + // and throw them away notify(models.NotifyRoomListUpdate, "") } if err := saveState(fi.State.Username, fi.State); err != nil { diff --git a/handlers/middleware.go b/handlers/middleware.go index d374c84..2c8eecd 100644 --- a/handlers/middleware.go +++ b/handlers/middleware.go @@ -26,9 +26,7 @@ func LogRequests(next http.Handler) http.Handler { func GetSession(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // TODO: move - cookieName := "session_token" - sessionCookie, err := r.Cookie(cookieName) + sessionCookie, err := r.Cookie(models.AuthCookie) if err != nil { msg := "auth failed; failed to get session token from cookies" log.Debug(msg, "error", err) @@ -54,7 +52,7 @@ func GetSession(next http.Handler) http.Handler { sessionToken := cookieValue[sha256.Size:] //verify signature mac := hmac.New(sha256.New, []byte(cfg.CookieSecret)) - mac.Write([]byte(cookieName)) + mac.Write([]byte(models.AuthCookie)) mac.Write([]byte(sessionToken)) expectedSignature := mac.Sum(nil) if !hmac.Equal([]byte(signature), expectedSignature) { diff --git a/llmapi/main.go b/llmapi/main.go index 9860910..b777f9a 100644 --- a/llmapi/main.go +++ b/llmapi/main.go @@ -15,13 +15,6 @@ import ( "strings" ) -// TODO: config for url and token -// completion prompt -// MIME: llm needs to know all the cards, colors and previous actions -// GUESSER: llm needs to know all the cards and previous actions - -// channels are not serializable - var ( // botname -> channel SignalChanMap = make(map[string]chan bool) @@ -29,7 +22,6 @@ var ( // got prompt: control character (\\u0000-\\u001F) found while parsing a string at line 4 column 0 MimePrompt = `we are playing alias;\nyou are a mime (player who gives a clue of one noun word and number of cards you expect them to open) of the %s team (people who would guess by your clue want open the %s cards);\nplease return your clue, number of cards to open and what words you mean them to find using that clue in json like:\n{\n\"clue\": \"one-word-noun\",\n\"number\": \"number-from-0-to-9\",\n\"words_I_mean_my_team_to_open\": [\"this\", \"that\", ...]\n}\nthe team who openes all their cards first wins.\nplease return json only.\nunopen Blue cards left: %d;\nunopen Red cards left: %d;\nhere is the game info in json:\n%s` GuesserPrompt = `we are playing alias;\nyou are to guess words of the %s team (people who would guess by your clue want open the %s cards) by given clue and a number of meant guesses;\nplease return your guesses and words that you did not wish to open, but think that they could be also meant by the clue in json like:\n{\n\"guesses\": [\"word1\", \"word2\", ...],\n\"could_be\": [\"this\", \"that\", ...]\n}\nthe team who openes all their cards first wins.\nplease return json only.\nunopen Blue cards left: %d;\nunopen Red cards left: %d;\nhere is the game info in json:\n%s` - // notifier = ) type DSResp struct { @@ -245,6 +237,13 @@ func NewBot(role, team, name, roomID string, cfg *config.Config) (*Bot, error) { Role: models.StrToUserRole(role), Team: models.StrToUserTeam(team), } + // check if already has the same team-role bot + // only one is allowed + for n, p := range room.BotMap { + if p.Role == bp.Role && p.Team == bp.Team { + return nil, fmt.Errorf("already has such bot with name: %s", n) + } + } room.BotMap[name] = bp switch team { case "red": @@ -385,11 +384,5 @@ func (b *Bot) CallLLM(prompt string) ([]byte, error) { return nil, err } b.log.Debug("llm resp", "body", string(body)) - // TODO: case where it has other text, not only json - // TODO: how to know which resp it is? mime or guessser? - // resp := &MimeResp{} - // if err := json.Unmarshal(body, &resp); err != nil { - // return nil, err - // } return body, nil } diff --git a/main.go b/main.go index e4e4562..f414608 100644 --- a/main.go +++ b/main.go @@ -1,20 +1,27 @@ package main import ( + "fmt" + "golias/config" "golias/handlers" "log/slog" "net/http" "time" ) -// TODO: add config as param +var cfg *config.Config + +func init() { + cfg = config.LoadConfigOrDefault("") +} + func ListenToRequests(port string) error { mux := http.NewServeMux() server := &http.Server{ Handler: handlers.LogRequests(handlers.GetSession(mux)), - Addr: port, - ReadTimeout: time.Second * 5, - WriteTimeout: 0, // sse streaming + Addr: fmt.Sprintf(":%s", port), + ReadTimeout: time.Second * 5, // TODO: to cfg + WriteTimeout: 0, // sse streaming } fs := http.FileServer(http.Dir("assets/")) mux.Handle("GET /assets/", http.StripPrefix("/assets/", fs)) @@ -43,8 +50,7 @@ func ListenToRequests(port string) error { } func main() { - port := ":3000" - err := ListenToRequests(port) + err := ListenToRequests(cfg.ServerConfig.Port) if err != nil { panic(err) } diff --git a/models/keys.go b/models/keys.go index 46308b9..967105a 100644 --- a/models/keys.go +++ b/models/keys.go @@ -1,6 +1,7 @@ package models var ( + AuthCookie = "session_token" CtxRoomIDKey = "current_room" CtxUsernameKey = "username" CtxSessionKey = "session" diff --git a/models/main.go b/models/main.go index 17583a6..759e3d2 100644 --- a/models/main.go +++ b/models/main.go @@ -1,6 +1,7 @@ package models import ( + "errors" "fmt" "golias/utils" "time" @@ -83,6 +84,25 @@ type Room struct { TeamWon UserTeam // blue | red } +func (r *Room) CanStart() error { + if r.IsRunning { + return errors.New("cannot start; game is already running") + } + if r.RedTeam.Mime == "" { + return errors.New("cannot start; red team has no mime") + } + if r.BlueTeam.Mime == "" { + return errors.New("cannot start; blue team has no mime") + } + if len(r.RedTeam.Guessers) == 0 { + return errors.New("cannot start; red team has no guessers") + } + if len(r.BlueTeam.Guessers) == 0 { + return errors.New("cannot start; blue team has no guessers") + } + return nil +} + // WhichBotToMove returns bot name that have to move or empty string func (r *Room) WhichBotToMove() string { fmt.Println("looking for bot to move", "team-turn", r.TeamTurn, "mime-done", r.MimeDone, "bot-map", r.BotMap, "is_running", r.IsRunning) @@ -141,46 +161,6 @@ func (r *Room) UpdateCounter() { r.BlueCounter = blueCounter } -// func (r *Room) LoadTestCards() { -// // TODO: pass room settings -// // TODO: map language to path -// 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) -// } -// r.Cards = cards -// // cards := []WordCard{ -// // {Word: "hamster", Color: "blue"}, -// // {Word: "child", Color: "red"}, -// // {Word: "wheel", Color: "white"}, -// // {Word: "condition", Color: "black"}, -// // {Word: "test", Color: "white"}, -// // {Word: "ball", Color: "blue"}, -// // {Word: "violin", Color: "red"}, -// // {Word: "rat", Color: "white"}, -// // {Word: "perplexity", Color: "blue"}, -// // {Word: "notion", Color: "red"}, -// // {Word: "guitar", Color: "blue"}, -// // {Word: "ocean", Color: "blue"}, -// // {Word: "moon", Color: "blue"}, -// // {Word: "coffee", Color: "blue"}, -// // {Word: "mountain", Color: "blue"}, -// // {Word: "book", Color: "blue"}, -// // {Word: "camera", Color: "blue"}, -// // {Word: "apple", Color: "red"}, -// // {Word: "fire", Color: "red"}, -// // {Word: "rose", Color: "red"}, -// // {Word: "sun", Color: "red"}, -// // {Word: "cherry", Color: "red"}, -// // {Word: "heart", Color: "red"}, -// // {Word: "tomato", Color: "red"}, -// // {Word: "cloud", Color: "white"}, -// // } -// // r.Cards = cards -// } - func (r *Room) ChangeTurn() { switch r.TeamTurn { case "blue":