package routes import ( "bufio" "encoding/json" "fmt" "log" "strconv" "time" "github.com/aziis98/lupus-lite/events" "github.com/aziis98/lupus-lite/lupus" "github.com/aziis98/lupus-lite/model" "github.com/aziis98/lupus-lite/util" "github.com/gofiber/fiber/v2" "github.com/valyala/fasthttp" ) func (s *Server) Api(api fiber.Router) { api.Get("/status", func(c *fiber.Ctx) error { s, err := json.MarshalIndent(s.db, "", " ") if err != nil { return err } log.Println(string(s)) return c.SendString("ok") }) api.Post("/login", func(c *fiber.Ctx) error { var loginForm struct { Username string `form:"username"` Password string `form:"password"` } if err := c.BodyParser(&loginForm); err != nil { return err } token, err := s.auth.Login(loginForm.Username, loginForm.Password) if err != nil { return err } c.Cookie(&fiber.Cookie{ Name: "sid", Value: token, Path: "/", Expires: time.Now().Add(3 * 24 * time.Hour), }) return c.Redirect("/") }) api.Post("/logout", func(c *fiber.Ctx) error { c.Cookie(&fiber.Cookie{ Name: "sid", Value: "", Path: "/", Expires: time.Now(), }) return c.SendString("ok") }) api.Post("/register", func(c *fiber.Ctx) error { var loginForm struct { Username string `form:"username"` Password string `form:"password"` Password2 string `form:"password2"` } if err := c.BodyParser(&loginForm); err != nil { return err } if err := util.ValidateUsername(loginForm.Username); err != nil { return err } if err := util.ValidatePasswords(loginForm.Password, loginForm.Password2); err != nil { return err } if err := s.auth.Register(loginForm.Username, loginForm.Password); err != nil { return err } return c.Redirect("/login") }) api.Post("/crea-partita", s.requireLogged, func(c *fiber.Ctx) error { user := requestUser(c) var form struct { NumGiocatori string `form:"numero-giocatori"` NumLupi string `form:"numero-lupi"` NumFattucchiere string `form:"numero-fattucchiere"` NumGuardie string `form:"numero-guardie"` NumCacciatori string `form:"numero-cacciatori"` NumMedium string `form:"numero-medium"` NumVeggenti string `form:"numero-veggenti"` } if err := c.BodyParser(&form); err != nil { return err } cfg := model.PartitaConfig{ NumeroPerRuolo: map[string]int{}, } if err := util.AtoiInto(form.NumGiocatori, &cfg.NumeroGiocatori); err != nil { return err } // Questo codice magico scorre su questa "tabella" con colonne ruolo e stringa del ruolo nel form e popola la mappa NumeroPerRuolo da uid ruolo a numero per ruolo. for _, r := range []struct { ruolo lupus.Ruolo num string }{ {lupus.Lupo, form.NumLupi}, {lupus.Fattucchiera, form.NumFattucchiere}, {lupus.Guardia, form.NumGuardie}, {lupus.Cacciatore, form.NumCacciatori}, {lupus.Medium, form.NumMedium}, {lupus.Veggente, form.NumVeggenti}, } { num, err := strconv.Atoi(r.num) if err != nil { return err } cfg.NumeroPerRuolo[r.ruolo.Uid] = num } partita, err := s.db.CreatePartita(user.Username, cfg) if err != nil { return err } return c.JSON(partita) }) api.Get("/user", s.requireLogged, func(c *fiber.Ctx) error { return c.JSON(requestUser(c).PublicUser()) }) api.Get("/partita/:partita/joined-players", func(c *fiber.Ctx) error { partitaUid := c.Params("partita") partita, err := s.db.GetPartita(partitaUid) if err != nil { return err } return c.JSON(partita.Players) }) api.Get("/partita/:partita/joined-players/events", func(c *fiber.Ctx) error { partitaUid := c.Params("partita") c.Set("Content-Type", "text/event-stream") c.Set("Cache-Control", "no-cache") c.Set("Connection", "keep-alive") c.Set("Transfer-Encoding", "chunked") sse := make(chan any, 1) done := make(chan bool) clientId := util.GenerateRandomString(16) log.Printf(`[%v] New SSE listener`, clientId) l := s.eventBus.Subscribe( events.OnPartitaPlayerJoins(partitaUid), func(e interface{}) { sse <- e }, ) go func() { <-done close(sse) close(done) s.eventBus.Unsubscribe(l) }() c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) { log.Printf("[%v] Starting communication...", clientId) if _, err := fmt.Fprintf(w, "data: initial message\n\n"); err != nil { log.Printf(`SSE Writer error: %v`, err) return } outer: for { select { case e := <-sse: log.Printf(`[%v] Event: New player joined`, clientId) players := e.([]string) msg, err := json.Marshal(players) if err != nil { log.Printf("[%v] Error %v", clientId, err) break outer } if _, err := fmt.Fprintf(w, "data: %s\n\n", msg); err != nil { log.Printf("[%v] Error: sse writer error, %v", clientId, err) break outer } err = w.Flush() log.Printf(`[%v] Flush result %v`, clientId, err) if err != nil { log.Printf("[%v] Error: error while flushing, %v, Closing connection.", clientId, err) break outer } case <-time.After(10 * time.Second): break outer } } log.Printf("[%v] Connection timeout", clientId) done <- true })) return nil }) api.Get("/partita/:partita/join-partita", s.requireLogged, func(c *fiber.Ctx) error { user := requestUser(c) partitaUid := c.Params("partita") partita, err := s.db.GetPartita(partitaUid) if err != nil { return err } partita.Players = append(partita.Players, user.Username) if err := s.db.UpdatePartita(partita); err != nil { return err } s.eventBus.Dispatch(events.OnPartitaPlayerJoins(partitaUid), partita.Players) return c.SendString("ok") }) }