dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

commit 4b9f8e3e0b011b02979b6ccbfd9cf3ef2c8eba95
parent f9d3c45e590816bbaa00903f33ede187a0559d7a
Author: n0tr1v <n0tr1v@protonmail.com>
Date:   Tue, 19 Dec 2023 00:20:04 -0500

move code

Diffstat:
Mpkg/web/handlers/handlers.go | 290-------------------------------------------------------------------------------
Apkg/web/handlers/poker.go | 509+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpkg/web/handlers/poker/poker.go | 198+------------------------------------------------------------------------------
Mpkg/web/web.go | 20++++++++++----------
4 files changed, 521 insertions(+), 496 deletions(-)

diff --git a/pkg/web/handlers/handlers.go b/pkg/web/handlers/handlers.go @@ -9,15 +9,11 @@ import ( dutils "dkforest/pkg/database/utils" "dkforest/pkg/odometer" "dkforest/pkg/utils" - "dkforest/pkg/web/handlers/poker" hutils "dkforest/pkg/web/handlers/utils" "encoding/base64" - "errors" "fmt" - "github.com/asaskevich/govalidator" humanize "github.com/dustin/go-humanize" "github.com/labstack/echo" - wallet1 "github.com/monero-ecosystem/go-monero-rpc-client/wallet" "github.com/pquerna/otp" "github.com/pquerna/otp/totp" "github.com/sirupsen/logrus" @@ -27,7 +23,6 @@ import ( "image" _ "image/gif" "image/png" - "math/rand" "net/http" "net/url" "os" @@ -756,288 +751,3 @@ func BHCHandler(c echo.Context) error { data.Success = fmt.Sprintf("Good answer, go back to BHC and use '%s' as your username", username+h[:3]) return c.Render(http.StatusOK, "bhc", data) } - -var pokerWithdrawCache = cache.NewWithKey[database.UserID, int64](10*time.Minute, time.Hour) - -func PokerHomeHandler(c echo.Context) error { - db := c.Get("database").(*database.DkfDB) - authUser := c.Get("authUser").(*database.User) - getImgStr := func(img image.Image) string { - buf := bytes.NewBuffer([]byte("")) - _ = png.Encode(buf, img) - return base64.StdEncoding.EncodeToString(buf.Bytes()) - } - if authUser.PokerXmrSubAddress == "" { - if resp, err := config.Xmr().CreateAddress(&wallet1.RequestCreateAddress{}); err == nil { - authUser.PokerXmrSubAddress = resp.Address - authUser.DoSave(db) - } - } - const minWithdrawAmount = 1 - var data pokerData - data.XmrPrice = fmt.Sprintf("$%.2f", config.MoneroPrice.Load()) - data.Transactions, _ = db.GetUserPokerXmrTransactions(authUser.ID) - data.PokerXmrSubAddress = authUser.PokerXmrSubAddress - data.ChipsTest = authUser.ChipsTest - data.XmrBalance = authUser.XmrBalance - withdrawUnique := rand.Int63() - data.WithdrawUnique = withdrawUnique - withdrawUniqueOrig, _ := pokerWithdrawCache.Get(authUser.ID) - pokerWithdrawCache.SetD(authUser.ID, withdrawUnique) - pokerTables, _ := db.GetPokerTables() - pxmr := database.Piconero(0) - data.HelperXmr = pxmr.XmrStr() - data.HelperChips = pxmr.ToPokerChip() - data.HelperpXmr = pxmr.RawString() - data.HelperUsd = pxmr.UsdStr() - userTableAccounts, _ := db.GetPokerTableAccounts(authUser.ID) - for _, t := range pokerTables { - var nbSeated int - if g := poker.PokerInstance.GetGame(poker.RoomID(t.Slug)); g != nil { - nbSeated = g.CountSeated() - } - tableBalance := database.PokerChip(0) - for _, a := range userTableAccounts { - if a.PokerTableID == t.ID { - tableBalance = a.Amount - break - } - } - data.Tables = append(data.Tables, TmpTable{PokerTable: t, NbSeated: nbSeated, TableBalance: tableBalance}) - } - - if authUser.PokerXmrSubAddress != "" { - b, _ := authUser.GetImage() - data.Img = getImgStr(b) - } - - if c.Request().Method == http.MethodGet { - return c.Render(http.StatusOK, "poker", data) - } - - formName := c.Request().PostFormValue("form_name") - if formName == "helper" { - data.HelperAmount = c.Request().PostFormValue("amount") - data.HelperType = c.Request().PostFormValue("type") - switch data.HelperType { - case "usd": - amount := utils.DoParseF64(data.HelperAmount) - pxmr = database.Piconero(amount / config.MoneroPrice.Load() * 1_000_000_000_000) - case "xmr": - amount := utils.DoParseF64(data.HelperAmount) - pxmr = database.Piconero(amount * 1_000_000_000_000) - case "pxmr": - amount := utils.DoParseUint64(data.HelperAmount) - pxmr = database.Piconero(amount) - case "chips": - amount := utils.DoParseUint64(data.HelperAmount) - chips := database.PokerChip(amount) - pxmr = chips.ToPiconero() - } - data.HelperXmr = pxmr.XmrStr() - data.HelperChips = pxmr.ToPokerChip() - data.HelperpXmr = pxmr.RawString() - data.HelperUsd = pxmr.UsdStr() - return c.Render(http.StatusOK, "poker", data) - } - - if formName == "join_table" { - pokerTableSlug := c.Request().PostFormValue("table_slug") - playerBuyIn := database.PokerChip(utils.DoParseUint64(c.Request().PostFormValue("buy_in"))) - if err := doJoinTable(db, pokerTableSlug, playerBuyIn, authUser.ID); err != nil { - data.ErrorTable = err.Error() - return c.Render(http.StatusOK, "poker", data) - } - return c.Redirect(http.StatusFound, "/poker/"+pokerTableSlug) - - } else if formName == "cash_out" { - pokerTableSlug := c.Request().PostFormValue("table_slug") - if err := doCashOut(db, pokerTableSlug, authUser.ID); err != nil { - data.ErrorTable = err.Error() - return c.Render(http.StatusOK, "poker", data) - } - return c.Redirect(http.StatusFound, "/poker") - - } else if formName == "reset_chips" { - authUser.ChipsTest = 1000 - authUser.DoSave(db) - return c.Redirect(http.StatusFound, c.Request().Referer()) - } - - if config.PokerWithdrawEnabled.IsFalse() { - data.Error = "withdraw temporarily disabled" - return c.Render(http.StatusOK, "poker", data) - } - - withdrawAmount := database.Piconero(utils.DoParseUint64(c.Request().PostFormValue("withdraw_amount"))) - data.WithdrawAmount = withdrawAmount - data.WithdrawAddress = c.Request().PostFormValue("withdraw_address") - withdrawUniqueSub := utils.DoParseInt64(c.Request().PostFormValue("withdraw_unique")) - - if withdrawUniqueOrig == 0 || withdrawUniqueSub != withdrawUniqueOrig { - data.Error = "form submitted twice, try again" - return c.Render(http.StatusOK, "poker", data) - } - if len(data.WithdrawAddress) != 95 { - data.Error = "invalid xmr address" - return c.Render(http.StatusOK, "poker", data) - } - if !govalidator.Matches(data.WithdrawAddress, `^[0-9][0-9AB][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{93}$`) { - data.Error = "invalid xmr address" - return c.Render(http.StatusOK, "poker", data) - } - if data.WithdrawAddress == authUser.PokerXmrSubAddress { - data.Error = "cannot withdraw to the deposit address" - return c.Render(http.StatusOK, "poker", data) - } - if withdrawAmount < minWithdrawAmount { - data.Error = fmt.Sprintf("minimum withdraw amount is %d", minWithdrawAmount) - return c.Render(http.StatusOK, "poker", data) - } - userBalance := authUser.XmrBalance - if withdrawAmount > userBalance { - data.Error = fmt.Sprintf("maximum withdraw amount is %d (%d)", userBalance, withdrawAmount) - return c.Render(http.StatusOK, "poker", data) - } - withdrawAmount = utils.Clamp(withdrawAmount, minWithdrawAmount, userBalance) - - lastOutTransaction, _ := db.GetLastUserWithdrawPokerXmrTransaction(authUser.ID) - if time.Since(lastOutTransaction.CreatedAt) < 5*time.Minute { - diff := time.Until(lastOutTransaction.CreatedAt.Add(5 * time.Minute)) - data.Error = fmt.Sprintf("Wait %s before doing a new withdraw transaction", utils.ShortDur(diff)) - return c.Render(http.StatusOK, "poker", data) - } - - walletRpcClient := config.Xmr() - - res, err := walletRpcClient.Transfer(&wallet1.RequestTransfer{ - DoNotRelay: true, - GetTxMetadata: true, - Destinations: []*wallet1.Destination{ - {Address: data.WithdrawAddress, - Amount: uint64(withdrawAmount)}}}) - if err != nil { - logrus.Error(err) - data.Error = err.Error() - return c.Render(http.StatusOK, "poker", data) - } - - transactionFee := database.Piconero(res.Fee) - - if withdrawAmount+transactionFee > authUser.XmrBalance { - data.Error = fmt.Sprintf("not enough funds to pay for transaction fee %d (%s xmr)", transactionFee, transactionFee.XmrStr()) - return c.Render(http.StatusOK, "poker", data) - } - - dutils.RootAdminNotify(db, fmt.Sprintf("new withdraw %s xmr by %s", withdrawAmount.XmrStr(), authUser.Username)) - - if err := db.WithE(func(tx *database.DkfDB) error { - xmrBalance, err := authUser.GetXmrBalance(tx) - if err != nil { - return err - } - if withdrawAmount+transactionFee > xmrBalance { - return errors.New("not enough funds") - } - if err := authUser.SubXmrBalance(tx, withdrawAmount+transactionFee); err != nil { - return err - } - if _, err := walletRpcClient.RelayTx(&wallet1.RequestRelayTx{Hex: res.TxMetadata}); err != nil { - logrus.Error(err) - return err - } - if _, err := tx.CreatePokerXmrTransaction(authUser.ID, res); err != nil { - logrus.Error("failed to create poker xmr transaction", err) - return err - } - return nil - }); err != nil { - data.Error = err.Error() - return c.Render(http.StatusOK, "poker", data) - } - - pokerWithdrawCache.Delete(authUser.ID) - return c.Redirect(http.StatusFound, c.Request().Referer()) -} - -func doJoinTable(db *database.DkfDB, pokerTableSlug string, playerBuyIn database.PokerChip, userID database.UserID) error { - err := db.WithE(func(tx *database.DkfDB) error { - if g := poker.PokerInstance.GetGame(poker.RoomID(pokerTableSlug)); g != nil { - g.Players.Lock() - defer g.Players.Unlock() - if g.IsSeated(userID) { - return errors.New("cannot buy-in while seated") - } - } - pokerTable, err := tx.GetPokerTableBySlug(pokerTableSlug) - if err != nil { - return errors.New("table mot found") - } - if playerBuyIn < pokerTable.MinBuyIn { - return errors.New("buy in too small") - } - if playerBuyIn > pokerTable.MaxBuyIn { - return errors.New("buy in too high") - } - xmrBalance, chipsTestBalance, err := tx.GetUserBalances(userID) - if err != nil { - return errors.New("failed to get user's balance") - } - userChips := utils.Ternary(pokerTable.IsTest, chipsTestBalance, xmrBalance.ToPokerChip()) - if userChips < playerBuyIn { - return errors.New("not enough chips to buy-in") - } - tableAccount, err := tx.GetPokerTableAccount(userID, pokerTable.ID) - if err != nil { - return errors.New("failed to get table account") - } - if tableAccount.Amount+playerBuyIn > pokerTable.MaxBuyIn { - return errors.New("buy-in exceed table max buy-in") - } - tableAccount.Amount += playerBuyIn - if err := tx.DecrUserBalance(userID, pokerTable.IsTest, playerBuyIn); err != nil { - return errors.New("failed to update user's balance") - } - if err := tableAccount.Save(tx); err != nil { - return errors.New("failed to update user's table account") - } - return nil - }) - return err -} - -func doCashOut(db *database.DkfDB, pokerTableSlug string, userID database.UserID) error { - err := db.WithE(func(tx *database.DkfDB) error { - if g := poker.PokerInstance.GetGame(poker.RoomID(pokerTableSlug)); g != nil { - g.Players.Lock() - defer g.Players.Unlock() - if g.IsSeated(userID) { - return errors.New("cannot cash out while seated") - } - } - pokerTable, err := tx.GetPokerTableBySlug(pokerTableSlug) - if err != nil { - return errors.New("table mot found") - } - account, err := tx.GetPokerTableAccount(userID, pokerTable.ID) - if err != nil { - return errors.New("failed to get table account") - } - if err := tx.IncrUserBalance(userID, pokerTable.IsTest, account.Amount); err != nil { - return errors.New("failed to update user's balance") - } - account.Amount = 0 - if err := account.Save(tx); err != nil { - return errors.New("failed to update user's table account") - } - return nil - }) - return err -} - -func PokerTableHandler(c echo.Context) error { - roomID := c.Param("roomID") - var data pokerTableData - data.PokerTableSlug = roomID - return c.Render(http.StatusOK, "poker-table", data) -} diff --git a/pkg/web/handlers/poker.go b/pkg/web/handlers/poker.go @@ -0,0 +1,509 @@ +package handlers + +import ( + "bytes" + "dkforest/pkg/cache" + "dkforest/pkg/config" + "dkforest/pkg/database" + dutils "dkforest/pkg/database/utils" + "dkforest/pkg/pubsub" + "dkforest/pkg/utils" + "dkforest/pkg/web/handlers/poker" + "dkforest/pkg/web/handlers/usersStreamsManager" + hutils "dkforest/pkg/web/handlers/utils" + "encoding/base64" + "errors" + "fmt" + "github.com/asaskevich/govalidator" + "github.com/labstack/echo" + wallet1 "github.com/monero-ecosystem/go-monero-rpc-client/wallet" + "github.com/sirupsen/logrus" + "image" + "image/png" + "math/rand" + "net/http" + "strings" + "time" +) + +var pokerWithdrawCache = cache.NewWithKey[database.UserID, int64](10*time.Minute, time.Hour) + +func PokerHomeHandler(c echo.Context) error { + db := c.Get("database").(*database.DkfDB) + authUser := c.Get("authUser").(*database.User) + getImgStr := func(img image.Image) string { + buf := bytes.NewBuffer([]byte("")) + _ = png.Encode(buf, img) + return base64.StdEncoding.EncodeToString(buf.Bytes()) + } + if authUser.PokerXmrSubAddress == "" { + if resp, err := config.Xmr().CreateAddress(&wallet1.RequestCreateAddress{}); err == nil { + authUser.PokerXmrSubAddress = resp.Address + authUser.DoSave(db) + } + } + const minWithdrawAmount = 1 + var data pokerData + data.XmrPrice = fmt.Sprintf("$%.2f", config.MoneroPrice.Load()) + data.Transactions, _ = db.GetUserPokerXmrTransactions(authUser.ID) + data.PokerXmrSubAddress = authUser.PokerXmrSubAddress + data.ChipsTest = authUser.ChipsTest + data.XmrBalance = authUser.XmrBalance + withdrawUnique := rand.Int63() + data.WithdrawUnique = withdrawUnique + withdrawUniqueOrig, _ := pokerWithdrawCache.Get(authUser.ID) + pokerWithdrawCache.SetD(authUser.ID, withdrawUnique) + pokerTables, _ := db.GetPokerTables() + pxmr := database.Piconero(0) + data.HelperXmr = pxmr.XmrStr() + data.HelperChips = pxmr.ToPokerChip() + data.HelperpXmr = pxmr.RawString() + data.HelperUsd = pxmr.UsdStr() + userTableAccounts, _ := db.GetPokerTableAccounts(authUser.ID) + for _, t := range pokerTables { + var nbSeated int + if g := poker.PokerInstance.GetGame(poker.RoomID(t.Slug)); g != nil { + nbSeated = g.CountSeated() + } + tableBalance := database.PokerChip(0) + for _, a := range userTableAccounts { + if a.PokerTableID == t.ID { + tableBalance = a.Amount + break + } + } + data.Tables = append(data.Tables, TmpTable{PokerTable: t, NbSeated: nbSeated, TableBalance: tableBalance}) + } + + if authUser.PokerXmrSubAddress != "" { + b, _ := authUser.GetImage() + data.Img = getImgStr(b) + } + + if c.Request().Method == http.MethodGet { + return c.Render(http.StatusOK, "poker", data) + } + + formName := c.Request().PostFormValue("form_name") + if formName == "helper" { + data.HelperAmount = c.Request().PostFormValue("amount") + data.HelperType = c.Request().PostFormValue("type") + switch data.HelperType { + case "usd": + amount := utils.DoParseF64(data.HelperAmount) + pxmr = database.Piconero(amount / config.MoneroPrice.Load() * 1_000_000_000_000) + case "xmr": + amount := utils.DoParseF64(data.HelperAmount) + pxmr = database.Piconero(amount * 1_000_000_000_000) + case "pxmr": + amount := utils.DoParseUint64(data.HelperAmount) + pxmr = database.Piconero(amount) + case "chips": + amount := utils.DoParseUint64(data.HelperAmount) + chips := database.PokerChip(amount) + pxmr = chips.ToPiconero() + } + data.HelperXmr = pxmr.XmrStr() + data.HelperChips = pxmr.ToPokerChip() + data.HelperpXmr = pxmr.RawString() + data.HelperUsd = pxmr.UsdStr() + return c.Render(http.StatusOK, "poker", data) + } + + if formName == "join_table" { + pokerTableSlug := c.Request().PostFormValue("table_slug") + playerBuyIn := database.PokerChip(utils.DoParseUint64(c.Request().PostFormValue("buy_in"))) + if err := doJoinTable(db, pokerTableSlug, playerBuyIn, authUser.ID); err != nil { + data.ErrorTable = err.Error() + return c.Render(http.StatusOK, "poker", data) + } + return c.Redirect(http.StatusFound, "/poker/"+pokerTableSlug) + + } else if formName == "cash_out" { + pokerTableSlug := c.Request().PostFormValue("table_slug") + if err := doCashOut(db, pokerTableSlug, authUser.ID); err != nil { + data.ErrorTable = err.Error() + return c.Render(http.StatusOK, "poker", data) + } + return c.Redirect(http.StatusFound, "/poker") + + } else if formName == "reset_chips" { + authUser.ChipsTest = 1000 + authUser.DoSave(db) + return c.Redirect(http.StatusFound, c.Request().Referer()) + } + + if config.PokerWithdrawEnabled.IsFalse() { + data.Error = "withdraw temporarily disabled" + return c.Render(http.StatusOK, "poker", data) + } + + withdrawAmount := database.Piconero(utils.DoParseUint64(c.Request().PostFormValue("withdraw_amount"))) + data.WithdrawAmount = withdrawAmount + data.WithdrawAddress = c.Request().PostFormValue("withdraw_address") + withdrawUniqueSub := utils.DoParseInt64(c.Request().PostFormValue("withdraw_unique")) + + if withdrawUniqueOrig == 0 || withdrawUniqueSub != withdrawUniqueOrig { + data.Error = "form submitted twice, try again" + return c.Render(http.StatusOK, "poker", data) + } + if len(data.WithdrawAddress) != 95 { + data.Error = "invalid xmr address" + return c.Render(http.StatusOK, "poker", data) + } + if !govalidator.Matches(data.WithdrawAddress, `^[0-9][0-9AB][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{93}$`) { + data.Error = "invalid xmr address" + return c.Render(http.StatusOK, "poker", data) + } + if data.WithdrawAddress == authUser.PokerXmrSubAddress { + data.Error = "cannot withdraw to the deposit address" + return c.Render(http.StatusOK, "poker", data) + } + if withdrawAmount < minWithdrawAmount { + data.Error = fmt.Sprintf("minimum withdraw amount is %d", minWithdrawAmount) + return c.Render(http.StatusOK, "poker", data) + } + userBalance := authUser.XmrBalance + if withdrawAmount > userBalance { + data.Error = fmt.Sprintf("maximum withdraw amount is %d (%d)", userBalance, withdrawAmount) + return c.Render(http.StatusOK, "poker", data) + } + withdrawAmount = utils.Clamp(withdrawAmount, minWithdrawAmount, userBalance) + + lastOutTransaction, _ := db.GetLastUserWithdrawPokerXmrTransaction(authUser.ID) + if time.Since(lastOutTransaction.CreatedAt) < 5*time.Minute { + diff := time.Until(lastOutTransaction.CreatedAt.Add(5 * time.Minute)) + data.Error = fmt.Sprintf("Wait %s before doing a new withdraw transaction", utils.ShortDur(diff)) + return c.Render(http.StatusOK, "poker", data) + } + + walletRpcClient := config.Xmr() + + res, err := walletRpcClient.Transfer(&wallet1.RequestTransfer{ + DoNotRelay: true, + GetTxMetadata: true, + Destinations: []*wallet1.Destination{ + {Address: data.WithdrawAddress, + Amount: uint64(withdrawAmount)}}}) + if err != nil { + logrus.Error(err) + data.Error = err.Error() + return c.Render(http.StatusOK, "poker", data) + } + + transactionFee := database.Piconero(res.Fee) + + if withdrawAmount+transactionFee > authUser.XmrBalance { + data.Error = fmt.Sprintf("not enough funds to pay for transaction fee %d (%s xmr)", transactionFee, transactionFee.XmrStr()) + return c.Render(http.StatusOK, "poker", data) + } + + dutils.RootAdminNotify(db, fmt.Sprintf("new withdraw %s xmr by %s", withdrawAmount.XmrStr(), authUser.Username)) + + if err := db.WithE(func(tx *database.DkfDB) error { + xmrBalance, err := authUser.GetXmrBalance(tx) + if err != nil { + return err + } + if withdrawAmount+transactionFee > xmrBalance { + return errors.New("not enough funds") + } + if err := authUser.SubXmrBalance(tx, withdrawAmount+transactionFee); err != nil { + return err + } + if _, err := walletRpcClient.RelayTx(&wallet1.RequestRelayTx{Hex: res.TxMetadata}); err != nil { + logrus.Error(err) + return err + } + if _, err := tx.CreatePokerXmrTransaction(authUser.ID, res); err != nil { + logrus.Error("failed to create poker xmr transaction", err) + return err + } + return nil + }); err != nil { + data.Error = err.Error() + return c.Render(http.StatusOK, "poker", data) + } + + pokerWithdrawCache.Delete(authUser.ID) + return c.Redirect(http.StatusFound, c.Request().Referer()) +} + +func doJoinTable(db *database.DkfDB, pokerTableSlug string, playerBuyIn database.PokerChip, userID database.UserID) error { + err := db.WithE(func(tx *database.DkfDB) error { + if g := poker.PokerInstance.GetGame(poker.RoomID(pokerTableSlug)); g != nil { + g.Players.Lock() + defer g.Players.Unlock() + if g.IsSeated(userID) { + return errors.New("cannot buy-in while seated") + } + } + pokerTable, err := tx.GetPokerTableBySlug(pokerTableSlug) + if err != nil { + return errors.New("table mot found") + } + if playerBuyIn < pokerTable.MinBuyIn { + return errors.New("buy in too small") + } + if playerBuyIn > pokerTable.MaxBuyIn { + return errors.New("buy in too high") + } + xmrBalance, chipsTestBalance, err := tx.GetUserBalances(userID) + if err != nil { + return errors.New("failed to get user's balance") + } + userChips := utils.Ternary(pokerTable.IsTest, chipsTestBalance, xmrBalance.ToPokerChip()) + if userChips < playerBuyIn { + return errors.New("not enough chips to buy-in") + } + tableAccount, err := tx.GetPokerTableAccount(userID, pokerTable.ID) + if err != nil { + return errors.New("failed to get table account") + } + if tableAccount.Amount+playerBuyIn > pokerTable.MaxBuyIn { + return errors.New("buy-in exceed table max buy-in") + } + tableAccount.Amount += playerBuyIn + if err := tx.DecrUserBalance(userID, pokerTable.IsTest, playerBuyIn); err != nil { + return errors.New("failed to update user's balance") + } + if err := tableAccount.Save(tx); err != nil { + return errors.New("failed to update user's table account") + } + return nil + }) + return err +} + +func doCashOut(db *database.DkfDB, pokerTableSlug string, userID database.UserID) error { + err := db.WithE(func(tx *database.DkfDB) error { + if g := poker.PokerInstance.GetGame(poker.RoomID(pokerTableSlug)); g != nil { + g.Players.Lock() + defer g.Players.Unlock() + if g.IsSeated(userID) { + return errors.New("cannot cash out while seated") + } + } + pokerTable, err := tx.GetPokerTableBySlug(pokerTableSlug) + if err != nil { + return errors.New("table mot found") + } + account, err := tx.GetPokerTableAccount(userID, pokerTable.ID) + if err != nil { + return errors.New("failed to get table account") + } + if err := tx.IncrUserBalance(userID, pokerTable.IsTest, account.Amount); err != nil { + return errors.New("failed to update user's balance") + } + account.Amount = 0 + if err := account.Save(tx); err != nil { + return errors.New("failed to update user's table account") + } + return nil + }) + return err +} + +func PokerTableHandler(c echo.Context) error { + roomID := c.Param("roomID") + var data pokerTableData + data.PokerTableSlug = roomID + return c.Render(http.StatusOK, "poker-table", data) +} + +func PokerStreamHandler(c echo.Context) error { + roomID := poker.RoomID(c.Param("roomID")) + authUser := c.Get("authUser").(*database.User) + db := c.Get("database").(*database.DkfDB) + + pokerTable, err := db.GetPokerTableBySlug(roomID.String()) + if err != nil { + return c.Redirect(http.StatusFound, "/") + } + + roomTopic := roomID.Topic() + roomUserTopic := roomID.UserTopic(authUser.ID) + send := func(s string) { _, _ = c.Response().Write([]byte(s)) } + + g := poker.PokerInstance.GetOrCreateGame(db, roomID, pokerTable.ID, pokerTable.MinBet, pokerTable.IsTest) + + quit := hutils.CloseSignalChan(c) + hutils.SetStreamingHeaders(c) + + // Keep track of users streams, so we can limit how many are open at one time per user + if err := usersStreamsManager.Inst.Add(authUser.ID, roomTopic); err != nil { + return nil + } + defer usersStreamsManager.Inst.Remove(authUser.ID, roomTopic) + + sub := poker.PokerPubSub.Subscribe([]string{roomTopic, roomUserTopic}) + defer sub.Close() + + send(poker.BuildBaseHtml(g, authUser)) + c.Response().Flush() + +Loop: + for { + select { + case <-quit: + break Loop + default: + } + + _, payload, err := sub.ReceiveTimeout2(1*time.Second, quit) + if err != nil { + if errors.Is(err, pubsub.ErrCancelled) { + break Loop + } + continue + } + + send(poker.BuildPayloadHtml(g, authUser, payload)) + c.Response().Flush() + continue + } + return nil +} + +func PokerLogsHandler(c echo.Context) error { + roomID := poker.RoomID(c.Param("roomID")) + authUser := c.Get("authUser").(*database.User) + send := func(s string) { _, _ = c.Response().Write([]byte(s)) } + g := poker.PokerInstance.GetGame(roomID) + if g == nil { + return c.Redirect(http.StatusFound, "/") + } + ongoing := g.Ongoing + roomLogsTopic := roomID.LogsTopic() + sub := poker.PokerPubSub.Subscribe([]string{roomLogsTopic}) + defer sub.Close() + quit := hutils.CloseSignalChan(c) + hutils.SetStreamingHeaders(c) + + // Keep track of users streams, so we can limit how many are open at one time per user + if err := usersStreamsManager.Inst.Add(authUser.ID, roomLogsTopic); err != nil { + return nil + } + defer usersStreamsManager.Inst.Remove(authUser.ID, roomLogsTopic) + + send(hutils.HtmlCssReset) + send(`<style>body { background-color: #ccc; }</style><div style="display:flex;flex-direction:column-reverse;">`) + if ongoing != nil { + ongoing.LogEvents.RWith(func(v *[]poker.LogEvent) { + for _, evt := range *v { + send(fmt.Sprintf(`<div>%s</div>`, evt.Message)) + } + }) + } + c.Response().Flush() + +Loop: + for { + select { + case <-quit: + break Loop + default: + } + + _, payload, err := sub.ReceiveTimeout2(1*time.Second, quit) + if err != nil { + if errors.Is(err, pubsub.ErrCancelled) { + break Loop + } + continue + } + + switch evt := payload.(type) { + case poker.LogEvent: + send(fmt.Sprintf(`<div>%s</div>`, evt.Message)) + c.Response().Flush() + } + } + return nil +} + +func PokerCheckHandler(c echo.Context) error { + authUser := c.Get("authUser").(*database.User) + roomID := poker.RoomID(c.Param("roomID")) + g := poker.PokerInstance.GetGame(roomID) + if g == nil { + return c.NoContent(http.StatusNotFound) + } + if c.Request().Method == http.MethodPost { + g.Check(authUser.ID) + } + return c.HTML(http.StatusOK, hutils.HtmlCssReset+`<form method="post"><button>Check</button></form>`) +} + +func itoa2(i database.PokerChip) string { + return fmt.Sprintf("%d", i) +} + +func PokerBetHandler(c echo.Context) error { + authUser := c.Get("authUser").(*database.User) + roomID := poker.RoomID(c.Param("roomID")) + g := poker.PokerInstance.GetGame(roomID) + if g == nil { + return c.NoContent(http.StatusNotFound) + } + bet := database.PokerChip(100) + if c.Request().Method == http.MethodPost { + bet = database.PokerChip(utils.DoParseUint64(c.Request().PostFormValue("betValue"))) + betBtn := c.Request().PostFormValue("bet") + if betBtn == "betAllIn" { + g.AllIn(authUser.ID) + } else { + if strings.HasPrefix(betBtn, "bet_") { + bet = database.PokerChip(utils.DoParseUint64(strings.TrimPrefix(betBtn, "bet_"))) + } + if bet > 0 { + g.Bet(authUser.ID, bet) + } + } + } + shortcuts := `<button type="submit" name="bet" value="bet_50">50</button> + <button type="submit" name="bet" value="bet_100">100</button> + <button type="submit" name="bet" value="bet_200">200</button>` + if g.PokerTableMinBet == 3 { + shortcuts = `<button type="submit" name="bet" value="bet_10">10</button> + <button type="submit" name="bet" value="bet_20">20</button> + <button type="submit" name="bet" value="bet_50">50</button>` + } else if g.PokerTableMinBet == 200 { + shortcuts = `<button type="submit" name="bet" value="bet_400">400</button> + <button type="submit" name="bet" value="bet_800">800</button> + <button type="submit" name="bet" value="bet_1500">1500</button>` + } + html := hutils.HtmlCssReset + ` +<form method="post"> + <input type="number" name="betValue" value="` + itoa2(bet) + `" style="width: 90px;" /><button type="submit" name="bet" value="bet">Bet</button><br /> + ` + shortcuts + ` + <button type="submit" name="bet" value="betAllIn">All-in</button> +</form>` + return c.HTML(http.StatusOK, html) +} + +func PokerCallHandler(c echo.Context) error { + authUser := c.Get("authUser").(*database.User) + roomID := poker.RoomID(c.Param("roomID")) + g := poker.PokerInstance.GetGame(roomID) + if g == nil { + return c.NoContent(http.StatusNotFound) + } + if c.Request().Method == http.MethodPost { + g.Call(authUser.ID) + } + return c.HTML(http.StatusOK, hutils.HtmlCssReset+`<form method="post"><button>Call</button></form>`) +} + +func PokerFoldHandler(c echo.Context) error { + authUser := c.Get("authUser").(*database.User) + roomID := poker.RoomID(c.Param("roomID")) + g := poker.PokerInstance.GetGame(roomID) + if g == nil { + return c.NoContent(http.StatusNotFound) + } + if c.Request().Method == http.MethodPost { + g.Fold(authUser.ID) + } + return c.HTML(http.StatusOK, hutils.HtmlCssReset+`<form method="post"><button>Fold</button></form>`) +} diff --git a/pkg/web/handlers/poker/poker.go b/pkg/web/handlers/poker/poker.go @@ -4,7 +4,6 @@ import ( "dkforest/pkg/database" "dkforest/pkg/pubsub" "dkforest/pkg/utils" - "dkforest/pkg/web/handlers/usersStreamsManager" hutils "dkforest/pkg/web/handlers/utils" "errors" "fmt" @@ -1442,88 +1441,6 @@ func (g *PokerGame) CountSeated() (count int) { var PokerPubSub = pubsub.NewPubSub[any]() -func PokerCheckHandler(c echo.Context) error { - authUser := c.Get("authUser").(*database.User) - roomID := RoomID(c.Param("roomID")) - g := PokerInstance.GetGame(roomID) - if g == nil { - return c.NoContent(http.StatusNotFound) - } - if c.Request().Method == http.MethodPost { - g.Check(authUser.ID) - } - return c.HTML(http.StatusOK, hutils.HtmlCssReset+`<form method="post"><button>Check</button></form>`) -} - -func PokerBetHandler(c echo.Context) error { - authUser := c.Get("authUser").(*database.User) - roomID := RoomID(c.Param("roomID")) - g := PokerInstance.GetGame(roomID) - if g == nil { - return c.NoContent(http.StatusNotFound) - } - bet := database.PokerChip(100) - if c.Request().Method == http.MethodPost { - bet = database.PokerChip(utils.DoParseUint64(c.Request().PostFormValue("betValue"))) - betBtn := c.Request().PostFormValue("bet") - if betBtn == "betAllIn" { - g.AllIn(authUser.ID) - } else { - if strings.HasPrefix(betBtn, "bet_") { - bet = database.PokerChip(utils.DoParseUint64(strings.TrimPrefix(betBtn, "bet_"))) - } - if bet > 0 { - g.Bet(authUser.ID, bet) - } - } - } - shortcuts := `<button type="submit" name="bet" value="bet_50">50</button> - <button type="submit" name="bet" value="bet_100">100</button> - <button type="submit" name="bet" value="bet_200">200</button>` - if g.PokerTableMinBet == 3 { - shortcuts = `<button type="submit" name="bet" value="bet_10">10</button> - <button type="submit" name="bet" value="bet_20">20</button> - <button type="submit" name="bet" value="bet_50">50</button>` - } else if g.PokerTableMinBet == 200 { - shortcuts = `<button type="submit" name="bet" value="bet_400">400</button> - <button type="submit" name="bet" value="bet_800">800</button> - <button type="submit" name="bet" value="bet_1500">1500</button>` - } - html := hutils.HtmlCssReset + ` -<form method="post"> - <input type="number" name="betValue" value="` + itoa2(bet) + `" style="width: 90px;" /><button type="submit" name="bet" value="bet">Bet</button><br /> - ` + shortcuts + ` - <button type="submit" name="bet" value="betAllIn">All-in</button> -</form>` - return c.HTML(http.StatusOK, html) -} - -func PokerCallHandler(c echo.Context) error { - authUser := c.Get("authUser").(*database.User) - roomID := RoomID(c.Param("roomID")) - g := PokerInstance.GetGame(roomID) - if g == nil { - return c.NoContent(http.StatusNotFound) - } - if c.Request().Method == http.MethodPost { - g.Call(authUser.ID) - } - return c.HTML(http.StatusOK, hutils.HtmlCssReset+`<form method="post"><button>Call</button></form>`) -} - -func PokerFoldHandler(c echo.Context) error { - authUser := c.Get("authUser").(*database.User) - roomID := RoomID(c.Param("roomID")) - g := PokerInstance.GetGame(roomID) - if g == nil { - return c.NoContent(http.StatusNotFound) - } - if c.Request().Method == http.MethodPost { - g.Fold(authUser.ID) - } - return c.HTML(http.StatusOK, hutils.HtmlCssReset+`<form method="post"><button>Fold</button></form>`) -} - func Refund(db *database.DkfDB) { accounts, _ := db.GetPositivePokerTableAccounts() db.With(func(tx *database.DkfDB) { @@ -1659,7 +1576,7 @@ func buildDevHtml() (html string) { ` } -func buildPayloadHtml(g *PokerGame, authUser *database.User, payload any) (html string) { +func BuildPayloadHtml(g *PokerGame, authUser *database.User, payload any) (html string) { switch evt := payload.(type) { case GameStartedEvent: html += drawGameStartedEvent(evt, authUser) @@ -1708,7 +1625,7 @@ func buildGameDiv(g *PokerGame, authUser *database.User) (html string) { return } -func buildBaseHtml(g *PokerGame, authUser *database.User) (html string) { +func BuildBaseHtml(g *PokerGame, authUser *database.User) (html string) { ongoing := g.Ongoing roomID := g.RoomID html += hutils.HtmlCssReset @@ -2319,114 +2236,3 @@ body { } </style>` - -func PokerLogsHandler(c echo.Context) error { - roomID := RoomID(c.Param("roomID")) - authUser := c.Get("authUser").(*database.User) - send := func(s string) { _, _ = c.Response().Write([]byte(s)) } - g := PokerInstance.GetGame(roomID) - if g == nil { - return c.Redirect(http.StatusFound, "/") - } - ongoing := g.Ongoing - roomLogsTopic := roomID.LogsTopic() - sub := PokerPubSub.Subscribe([]string{roomLogsTopic}) - defer sub.Close() - quit := hutils.CloseSignalChan(c) - hutils.SetStreamingHeaders(c) - - // Keep track of users streams, so we can limit how many are open at one time per user - if err := usersStreamsManager.Inst.Add(authUser.ID, roomLogsTopic); err != nil { - return nil - } - defer usersStreamsManager.Inst.Remove(authUser.ID, roomLogsTopic) - - send(hutils.HtmlCssReset) - send(`<style>body { background-color: #ccc; }</style><div style="display:flex;flex-direction:column-reverse;">`) - if ongoing != nil { - ongoing.LogEvents.RWith(func(v *[]LogEvent) { - for _, evt := range *v { - send(fmt.Sprintf(`<div>%s</div>`, evt.Message)) - } - }) - } - c.Response().Flush() - -Loop: - for { - select { - case <-quit: - break Loop - default: - } - - _, payload, err := sub.ReceiveTimeout2(1*time.Second, quit) - if err != nil { - if errors.Is(err, pubsub.ErrCancelled) { - break Loop - } - continue - } - - switch evt := payload.(type) { - case LogEvent: - send(fmt.Sprintf(`<div>%s</div>`, evt.Message)) - c.Response().Flush() - } - } - return nil -} - -func PokerStreamHandler(c echo.Context) error { - roomID := RoomID(c.Param("roomID")) - authUser := c.Get("authUser").(*database.User) - db := c.Get("database").(*database.DkfDB) - - pokerTable, err := db.GetPokerTableBySlug(roomID.String()) - if err != nil { - return c.Redirect(http.StatusFound, "/") - } - - roomTopic := roomID.Topic() - roomUserTopic := roomID.UserTopic(authUser.ID) - send := func(s string) { _, _ = c.Response().Write([]byte(s)) } - - g := PokerInstance.GetOrCreateGame(db, roomID, pokerTable.ID, pokerTable.MinBet, pokerTable.IsTest) - - quit := hutils.CloseSignalChan(c) - hutils.SetStreamingHeaders(c) - - // Keep track of users streams, so we can limit how many are open at one time per user - if err := usersStreamsManager.Inst.Add(authUser.ID, roomTopic); err != nil { - return nil - } - defer usersStreamsManager.Inst.Remove(authUser.ID, roomTopic) - - sub := PokerPubSub.Subscribe([]string{roomTopic, roomUserTopic}) - defer sub.Close() - - send(buildBaseHtml(g, authUser)) - c.Response().Flush() - -Loop: - for { - select { - case <-quit: - break Loop - default: - } - - _, payload, err := sub.ReceiveTimeout2(1*time.Second, quit) - if err != nil { - if errors.Is(err, pubsub.ErrCancelled) { - break Loop - } - continue - } - - send(buildPayloadHtml(g, authUser, payload)) - c.Response().Flush() - continue - } - return nil -} diff --git a/pkg/web/web.go b/pkg/web/web.go @@ -101,16 +101,16 @@ func getMainServer(db *database.DkfDB, i18nBundle *i18n.Bundle, renderer *tmp.Te authGroup.GET("/poker", handlers.PokerHomeHandler) authGroup.POST("/poker", handlers.PokerHomeHandler, middlewares.AuthRateLimitMiddleware(time.Second, 1)) authGroup.GET("/poker/:roomID", handlers.PokerTableHandler) - authGroup.GET("/poker/:roomID/stream", poker.PokerStreamHandler) - authGroup.GET("/poker/:roomID/logs", poker.PokerLogsHandler) - authGroup.GET("/poker/:roomID/check", poker.PokerCheckHandler) - authGroup.POST("/poker/:roomID/check", poker.PokerCheckHandler) - authGroup.GET("/poker/:roomID/fold", poker.PokerFoldHandler) - authGroup.POST("/poker/:roomID/fold", poker.PokerFoldHandler) - authGroup.GET("/poker/:roomID/call", poker.PokerCallHandler) - authGroup.POST("/poker/:roomID/call", poker.PokerCallHandler) - authGroup.GET("/poker/:roomID/bet", poker.PokerBetHandler) - authGroup.POST("/poker/:roomID/bet", poker.PokerBetHandler) + authGroup.GET("/poker/:roomID/stream", handlers.PokerStreamHandler) + authGroup.GET("/poker/:roomID/logs", handlers.PokerLogsHandler) + authGroup.GET("/poker/:roomID/check", handlers.PokerCheckHandler) + authGroup.POST("/poker/:roomID/check", handlers.PokerCheckHandler) + authGroup.GET("/poker/:roomID/fold", handlers.PokerFoldHandler) + authGroup.POST("/poker/:roomID/fold", handlers.PokerFoldHandler) + authGroup.GET("/poker/:roomID/call", handlers.PokerCallHandler) + authGroup.POST("/poker/:roomID/call", handlers.PokerCallHandler) + authGroup.GET("/poker/:roomID/bet", handlers.PokerBetHandler) + authGroup.POST("/poker/:roomID/bet", handlers.PokerBetHandler) authGroup.GET("/poker/:roomID/deal", poker.PokerDealHandler) authGroup.POST("/poker/:roomID/deal", poker.PokerDealHandler) authGroup.GET("/poker/:roomID/unsit", poker.PokerUnSitHandler)