commit 4b9f8e3e0b011b02979b6ccbfd9cf3ef2c8eba95
parent f9d3c45e590816bbaa00903f33ede187a0559d7a
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Tue, 19 Dec 2023 00:20:04 -0500
move code
Diffstat:
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)