任务大厅,限量显示

This commit is contained in:
邹方成 2026-02-19 19:55:25 +08:00
parent af1c16c7c5
commit ec035ffb53
8 changed files with 516 additions and 131 deletions

View File

@ -0,0 +1,76 @@
package main
import (
"bindbox-game/configs"
"bindbox-game/internal/repository/mysql"
"context"
"flag"
"fmt"
)
func main() {
flag.Parse()
configs.Init()
ctx := context.Background()
db, err := mysql.New()
if err != nil {
panic(err)
}
rawDB := db.GetDbR()
// 1. status IN (2,4) 但 balance_amount > 0 的券(有余额却被标记为已使用/占用中)
fmt.Println("=== 异常券status=2或4 但余额>0应改为 status=1 ===")
var abnormal []struct {
ID int64 `gorm:"column:id"`
UserID int64 `gorm:"column:user_id"`
CouponID int64 `gorm:"column:coupon_id"`
Status int32 `gorm:"column:status"`
BalanceAmount int64 `gorm:"column:balance_amount"`
ValidEnd string `gorm:"column:valid_end"`
UsedAt string `gorm:"column:used_at"`
UsedOrderID int64 `gorm:"column:used_order_id"`
}
rawDB.WithContext(ctx).Raw(`
SELECT uc.id, uc.user_id, uc.coupon_id, uc.status, uc.balance_amount, uc.valid_end, uc.used_at, uc.used_order_id
FROM user_coupons uc
WHERE uc.status IN (2, 4)
AND uc.balance_amount > 0
ORDER BY uc.user_id, uc.id
`).Scan(&abnormal)
fmt.Printf("共 %d 条\n\n", len(abnormal))
for _, c := range abnormal {
statusText := map[int32]string{2: "已使用", 4: "占用中"}[c.Status]
// 查券名
var name string
rawDB.WithContext(ctx).Raw("SELECT name FROM system_coupons WHERE id = ?", c.CouponID).Scan(&name)
fmt.Printf("券#%d | 用户#%d | %s(模板#%d) | status=%d(%s) | 余额=%d分(%.2f元) | 有效期至=%s\n",
c.ID, c.UserID, name, c.CouponID, c.Status, statusText,
c.BalanceAmount, float64(c.BalanceAmount)/100, c.ValidEnd)
}
// 2. status=1 但 balance_amount=0 的券(没余额却还标记为未使用)
fmt.Println("\n=== 异常券status=1 但余额=0应改为 status=2 ===")
var zeroBalance []struct {
ID int64 `gorm:"column:id"`
UserID int64 `gorm:"column:user_id"`
CouponID int64 `gorm:"column:coupon_id"`
BalanceAmount int64 `gorm:"column:balance_amount"`
}
rawDB.WithContext(ctx).Raw(`
SELECT id, user_id, coupon_id, balance_amount
FROM user_coupons
WHERE status = 1 AND balance_amount = 0
ORDER BY user_id, id
`).Scan(&zeroBalance)
fmt.Printf("共 %d 条\n\n", len(zeroBalance))
for _, c := range zeroBalance {
var name string
rawDB.WithContext(ctx).Raw("SELECT name FROM system_coupons WHERE id = ?", c.CouponID).Scan(&name)
fmt.Printf("券#%d | 用户#%d | %s(模板#%d) | status=1(未使用) | 余额=0\n",
c.ID, c.UserID, name, c.CouponID)
}
}

View File

@ -15,14 +15,16 @@ type listTasksRequest struct {
} }
type taskItem struct { type taskItem struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Status int32 `json:"status"` Status int32 `json:"status"`
StartTime int64 `json:"start_time"` StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"` EndTime int64 `json:"end_time"`
Tiers []taskTierItem `json:"tiers"` Quota int32 `json:"quota"`
Rewards []taskRewardItem `json:"rewards"` ClaimedCount int32 `json:"claimed_count"`
Tiers []taskTierItem `json:"tiers"`
Rewards []taskRewardItem `json:"rewards"`
} }
type taskTierItem struct { type taskTierItem struct {
@ -79,7 +81,7 @@ func (h *handler) ListTasksForApp() core.HandlerFunc {
res.Total = total res.Total = total
res.List = make([]taskItem, len(items)) res.List = make([]taskItem, len(items))
for i, v := range items { for i, v := range items {
ti := taskItem{ID: v.ID, Name: v.Name, Description: v.Description, Status: v.Status, StartTime: v.StartTime, EndTime: v.EndTime} ti := taskItem{ID: v.ID, Name: v.Name, Description: v.Description, Status: v.Status, StartTime: v.StartTime, EndTime: v.EndTime, Quota: v.Quota, ClaimedCount: v.ClaimedCount}
if len(v.Tiers) > 0 { if len(v.Tiers) > 0 {
ti.Tiers = make([]taskTierItem, len(v.Tiers)) ti.Tiers = make([]taskTierItem, len(v.Tiers))
for j, t := range v.Tiers { for j, t := range v.Tiers {

View File

@ -13,7 +13,7 @@ import (
type listCouponsRequest struct { type listCouponsRequest struct {
Page int `form:"page"` Page int `form:"page"`
PageSize int `form:"page_size"` PageSize int `form:"page_size"`
Status *int32 `form:"status"` // 1未使用 2已使用 3已过期不传默认1 Status *int32 `form:"status"` // 1有效 2已失效不传默认1
} }
type listCouponsResponse struct { type listCouponsResponse struct {
Page int `json:"page"` Page int `json:"page"`
@ -30,7 +30,8 @@ type couponItem struct {
ValidStart string `json:"valid_start"` ValidStart string `json:"valid_start"`
ValidEnd string `json:"valid_end"` ValidEnd string `json:"valid_end"`
Status int32 `json:"status"` Status int32 `json:"status"`
StatusDesc string `json:"status_desc"` // 状态描述:未使用、已用完、已过期 SubStatus string `json:"sub_status"` // 子状态unused/in_use/depleted/used/expired
StatusDesc string `json:"status_desc"` // 状态描述
Rules string `json:"rules"` Rules string `json:"rules"`
UsedAt string `json:"used_at,omitempty"` // 使用时间(已使用时返回) UsedAt string `json:"used_at,omitempty"` // 使用时间(已使用时返回)
UsedAmount int64 `json:"used_amount"` // 已使用金额 UsedAmount int64 `json:"used_amount"` // 已使用金额
@ -46,7 +47,7 @@ type couponItem struct {
// @Security LoginVerifyToken // @Security LoginVerifyToken
// @Param page query int true "页码" default(1) // @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量最多100" default(20) // @Param page_size query int true "每页数量最多100" default(20)
// @Param status query int false "状态1未使用 2已使用 3已过期不传默认1" // @Param status query int false "状态1有效 2已失效不传默认1"
// @Success 200 {object} listCouponsResponse // @Success 200 {object} listCouponsResponse
// @Failure 400 {object} code.Failure // @Failure 400 {object} code.Failure
// @Router /api/app/users/{user_id}/coupons [get] // @Router /api/app/users/{user_id}/coupons [get]
@ -96,11 +97,10 @@ func (h *handler) ListUserCoupons() core.HandlerFunc {
mp[c.ID] = c mp[c.ID] = c
} }
rsp.List = make([]couponItem, 0, len(items)) rsp.List = make([]couponItem, 0, len(items))
rsp.List = make([]couponItem, 0, len(items))
for _, it := range items { for _, it := range items {
sc := mp[it.CouponID] sc := mp[it.CouponID]
if sc == nil { if sc == nil {
continue // 找不到模板或模板设为不显示,跳过 continue
} }
name := sc.Name name := sc.Name
@ -118,27 +118,14 @@ func (h *handler) ListUserCoupons() core.HandlerFunc {
usedAt = it.UsedAt.Format("2006-01-02 15:04:05") usedAt = it.UsedAt.Format("2006-01-02 15:04:05")
} }
statusDesc := "未使用"
// 状态1未使用 2已使用 3已过期 4占用中(视为使用中)
switch it.Status {
case 2:
statusDesc = "已使用"
case 3:
// 若余额小于面值,说明用过一部分但过期了,否则是纯过期
if it.BalanceAmount < amount {
statusDesc = "已到期"
} else {
statusDesc = "已过期"
}
case 4:
statusDesc = "使用中"
}
usedAmount := amount - remaining usedAmount := amount - remaining
if usedAmount < 0 { if usedAmount < 0 {
usedAmount = 0 usedAmount = 0
} }
// 计算子状态和描述
subStatus, statusDesc := calcCouponSubStatus(it, sc)
vi := couponItem{ vi := couponItem{
ID: it.ID, ID: it.ID,
Name: name, Name: name,
@ -148,6 +135,7 @@ func (h *handler) ListUserCoupons() core.HandlerFunc {
ValidStart: vs, ValidStart: vs,
ValidEnd: ve, ValidEnd: ve,
Status: it.Status, Status: it.Status,
SubStatus: subStatus,
StatusDesc: statusDesc, StatusDesc: statusDesc,
Rules: rules, Rules: rules,
UsedAt: usedAt, UsedAt: usedAt,
@ -171,3 +159,33 @@ func buildCouponRules(c *model.SystemCoupons) string {
return "" return ""
} }
} }
// calcCouponSubStatus 计算优惠券的子状态和描述
// 子状态unused(未使用) in_use(使用中) depleted(用完) used(已使用) expired(已过期)
func calcCouponSubStatus(uc *model.UserCoupons, sc *model.SystemCoupons) (subStatus string, statusDesc string) {
amount := sc.DiscountValue
switch uc.Status {
case 1: // 数据库 status=1
if uc.BalanceAmount <= 0 {
// 余额=0 → 用完了
return "depleted", "已用完"
}
if sc.DiscountType == 1 && uc.BalanceAmount < amount {
// 金额券且余额 < 面值 → 使用中
return "in_use", "使用中"
}
return "unused", "可用"
case 2: // 数据库 status=2 → 已使用(满减/折扣券核销)
if sc.DiscountType == 1 {
return "depleted", "已用完"
}
return "used", "已使用"
case 3: // 数据库 status=3 → 已过期
return "expired", "已过期"
case 4: // 数据库 status=4 → 占用中(预扣),视为使用中
return "in_use", "使用中"
default:
return "unused", "可用"
}
}

View File

@ -5,9 +5,8 @@ import (
) )
type couponStatsResponse struct { type couponStatsResponse struct {
UnusedCount int64 `json:"unused_count"` // 可用优惠券数量 ValidCount int64 `json:"valid_count"` // 有效优惠券数量
UsedCount int64 `json:"used_count"` // 已使用优惠券数量 InvalidCount int64 `json:"invalid_count"` // 已失效优惠券数量
ExpiredCount int64 `json:"expired_count"` // 已过期优惠券数量
TotalRemaining int64 `json:"total_remaining"` // 可用优惠券总余额(分) TotalRemaining int64 `json:"total_remaining"` // 可用优惠券总余额(分)
TotalSaved int64 `json:"total_saved"` // 累计节省金额(分) TotalSaved int64 `json:"total_saved"` // 累计节省金额(分)
} }
@ -28,31 +27,35 @@ func (h *handler) GetUserCouponStats() core.HandlerFunc {
rsp := new(couponStatsResponse) rsp := new(couponStatsResponse)
userID := int64(ctx.SessionUserInfo().Id) userID := int64(ctx.SessionUserInfo().Id)
// 统计各状态数量 uc := h.readDB.UserCoupons
unusedCount, _ := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().
Where(h.readDB.UserCoupons.UserID.Eq(userID), h.readDB.UserCoupons.Status.Eq(1)).Count()
usedCount, _ := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().
Where(h.readDB.UserCoupons.UserID.Eq(userID), h.readDB.UserCoupons.Status.Eq(2)).Count()
expiredCount, _ := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().
Where(h.readDB.UserCoupons.UserID.Eq(userID), h.readDB.UserCoupons.Status.Eq(3)).Count()
// 统计可用优惠券总余额 // 有效status=1 且 balance>0
validCount, _ := uc.WithContext(ctx.RequestContext()).ReadDB().
Where(uc.UserID.Eq(userID), uc.Status.Eq(1), uc.BalanceAmount.Gt(0)).Count()
// 已失效:(status=1 且 balance=0) 或 status=2 或 status=3
var invalidCount int64
_ = h.repo.GetDbR().Raw(
"SELECT COUNT(*) FROM user_coupons WHERE user_id = ? AND ((status = 1 AND balance_amount = 0) OR status IN (2, 3))",
userID,
).Scan(&invalidCount).Error
// 可用优惠券总余额
var totalRemaining int64 var totalRemaining int64
_ = h.repo.GetDbR().Raw( _ = h.repo.GetDbR().Raw(
"SELECT COALESCE(SUM(balance_amount), 0) FROM user_coupons WHERE user_id = ? AND status = 1", "SELECT COALESCE(SUM(balance_amount), 0) FROM user_coupons WHERE user_id = ? AND status = 1 AND balance_amount > 0",
userID, userID,
).Scan(&totalRemaining).Error ).Scan(&totalRemaining).Error
// 统计累计节省金额(所有使用记录的扣减金额总和) // 累计节省金额
var totalSaved int64 var totalSaved int64
_ = h.repo.GetDbR().Raw( _ = h.repo.GetDbR().Raw(
"SELECT COALESCE(SUM(ABS(change_amount)), 0) FROM user_coupon_ledger WHERE user_id = ? AND change_amount < 0", "SELECT COALESCE(SUM(ABS(change_amount)), 0) FROM user_coupon_ledger WHERE user_id = ? AND change_amount < 0",
userID, userID,
).Scan(&totalSaved).Error ).Scan(&totalSaved).Error
rsp.UnusedCount = unusedCount rsp.ValidCount = validCount
rsp.UsedCount = usedCount rsp.InvalidCount = invalidCount
rsp.ExpiredCount = expiredCount
rsp.TotalRemaining = totalRemaining rsp.TotalRemaining = totalRemaining
rsp.TotalSaved = totalSaved rsp.TotalSaved = totalSaved

View File

@ -36,13 +36,12 @@ func (s *service) ListCouponsByStatus(ctx context.Context, userID int64, status
return items, total, nil return items, total, nil
} }
// ListAppCoupons APP端查看优惠券(分类逻辑优化:未使用=未动过,已使用=部分使用or已用完 // ListAppCoupons APP端查看优惠券
// ListAppCoupons APP端查看优惠券分类逻辑优化未使用=未动过,已使用=部分使用or已用完 // status=1 有效(未使用+使用中) status=2 已失效(用完+已使用+已过期
func (s *service) ListAppCoupons(ctx context.Context, userID int64, status int32, page, pageSize int) (items []*model.UserCoupons, total int64, err error) { func (s *service) ListAppCoupons(ctx context.Context, userID int64, status int32, page, pageSize int) (items []*model.UserCoupons, total int64, err error) {
u := s.readDB.UserCoupons u := s.readDB.UserCoupons
c := s.readDB.SystemCoupons c := s.readDB.SystemCoupons
// 使用 UnderlyingDB 绕过 GEN 的类型限制,确保 SQL 逻辑正确
tableName := u.TableName() tableName := u.TableName()
sysTableName := c.TableName() sysTableName := c.TableName()
db := u.UnderlyingDB().WithContext(ctx). db := u.UnderlyingDB().WithContext(ctx).
@ -51,20 +50,15 @@ func (s *service) ListAppCoupons(ctx context.Context, userID int64, status int32
Joins("LEFT JOIN `"+sysTableName+"` ON `"+sysTableName+"`.id = `"+tableName+"`.coupon_id"). Joins("LEFT JOIN `"+sysTableName+"` ON `"+sysTableName+"`.id = `"+tableName+"`.coupon_id").
Where("`"+tableName+"`.user_id = ?", userID) Where("`"+tableName+"`.user_id = ?", userID)
// 过滤逻辑
switch status { switch status {
case 1: // 未使用 (Status=1且余额>0) case 1: // 有效status=1 且余额>0包含未使用和使用中
db = db.Where(u.TableName()+".status = ? AND "+u.TableName()+".balance_amount > ?", 1, 0) db = db.Where(tableName+".status = ? AND "+tableName+".balance_amount > ?", 1, 0)
case 2: // 已使用 (Status=2 或 Status=1且余额=0) case 2: // 已失效:余额用完(status=1且balance=0) 或 已使用(status=2) 或 已过期(status=3)
// Condition: (Status=1 AND Balance = 0) OR Status=2 db = db.Where("("+tableName+".status = ? AND "+tableName+".balance_amount = ?) OR "+tableName+".status IN (?,?)", 1, 0, 2, 3)
db = db.Where("("+u.TableName()+".status = ? AND "+u.TableName()+".balance_amount = ?) OR "+u.TableName()+".status = ?", 1, 0, 2) default:
case 3: // 已过期 db = db.Where(tableName+".status = ? AND "+tableName+".balance_amount > ?", 1, 0)
db = db.Where(u.TableName()+".status = ?", 3)
default: // 默认只查未使用 (fallback to 1 logic)
db = db.Where(u.TableName()+".status = ? AND "+u.TableName()+".balance_amount > ?", 1, 0)
} }
// Count
if err = db.Count(&total).Error; err != nil { if err = db.Count(&total).Error; err != nil {
return nil, 0, err return nil, 0, err
} }
@ -76,7 +70,6 @@ func (s *service) ListAppCoupons(ctx context.Context, userID int64, status int32
pageSize = 20 pageSize = 20
} }
// Find
err = db.Order("`" + tableName + "`.id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Scan(&items).Error err = db.Order("`" + tableName + "`.id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Scan(&items).Error
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err

View File

@ -1,3 +1,14 @@
module test_matchmaker module test_matchmaker
go 1.24.2 go 1.24.2
require (
github.com/ascii8/nakama-go v0.8.7
github.com/heroiclabs/nakama-common v1.31.0
)
require (
golang.org/x/net v0.19.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
nhooyr.io/websocket v1.8.10 // indirect
)

View File

@ -0,0 +1,274 @@
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Microsoft/hcsshim v0.12.0-rc.1 h1:Hy+xzYujv7urO5wrgcG58SPMOXNLrj4WCJbySs2XX/A=
github.com/Microsoft/hcsshim v0.12.0-rc.1/go.mod h1:Y1a1S0QlYp1mBpyvGiuEdOfZqnao+0uX5AWHXQ5NhZU=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/ascii8/nakama-go v0.8.7 h1:3P6V29Yw3Q9Il6mpfuNb5ObJSa1Yi19mFfIa1yiUNp0=
github.com/ascii8/nakama-go v0.8.7/go.mod h1:AolUWpv3vcen2w0MjOfr3BPdMFEXi+KNCCtRgwcW+dA=
github.com/ascii8/nktest v0.12.3 h1:5tR/vg8o354TnXIdPhYdde7GuMTYTSOYUVoGjTrWFmg=
github.com/ascii8/nktest v0.12.3/go.mod h1:FoylDjwHaLiF2CCBSQElBGmz2qaJiF6HqUz7kv4tNpY=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/cilium/ebpf v0.9.3 h1:5KtxXZU+scyERvkJMEm16TbScVvuuMrlhPly78ZMbSc=
github.com/cilium/ebpf v0.9.3/go.mod h1:w27N4UjpaQ9X/DGrSugxUG+H+NhgntDuPb5lCzxCn8A=
github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0=
github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE=
github.com/containerd/containerd v1.7.9 h1:KOhK01szQbM80YfW1H6RZKh85PHGqY/9OcEZ35Je8sc=
github.com/containerd/containerd v1.7.9/go.mod h1:0/W44LWEYfSHoxBtsHIiNU/duEkgpMokemafHVCpq9Y=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU=
github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk=
github.com/containers/buildah v1.33.3 h1:wkNu4to1vS4ZskV8TExRVFZoEiOrVISeB5qdGH2Hle8=
github.com/containers/buildah v1.33.3/go.mod h1:twgn5g4zD8rUCfU/3PqfaaiepB2Ybz/tEACza18iuvE=
github.com/containers/common v0.57.2 h1:50Zp9VuXt2u5uNTKEc4aU2+PfWzFNAulD9JX43VihyI=
github.com/containers/common v0.57.2/go.mod h1:ZG9ab1bEssX98ZBclWFIyzx4+MyUN8dXj1oSEugp7N0=
github.com/containers/image/v5 v5.29.1 h1:9COTXQpl3FgrW/jw/roLAWlW4TN9ly7/bCAKY76wYl8=
github.com/containers/image/v5 v5.29.1/go.mod h1:kQ7qcDsps424ZAz24thD+x7+dJw1vgur3A9tTDsj97E=
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA=
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
github.com/containers/ocicrypt v1.1.9 h1:2Csfba4jse85Raxk5HIyEk8OwZNjRvfkhEGijOjIdEM=
github.com/containers/ocicrypt v1.1.9/go.mod h1:dTKx1918d8TDkxXvarscpNVY+lyPakPNFN4jwA9GBys=
github.com/containers/podman/v4 v4.8.3 h1:gufLP9pJL/pBKW2P9CHmvfpXqjIRpo8lhHeZiwh8tpc=
github.com/containers/podman/v4 v4.8.3/go.mod h1:ArI44imM/lS2u5XAXXtdehFT5cWPimzA/oyICc03IqI=
github.com/containers/psgo v1.8.0 h1:2loGekmGAxM9ir5OsXWEfGwFxorMPYnc6gEDsGFQvhY=
github.com/containers/psgo v1.8.0/go.mod h1:T8ZxnX3Ur4RvnhxFJ7t8xJ1F48RhiZB4rSrOaR/qGHc=
github.com/containers/storage v1.51.0 h1:AowbcpiWXzAjHosKz7MKvPEqpyX+ryZA/ZurytRrFNA=
github.com/containers/storage v1.51.0/go.mod h1:ybl8a3j1PPtpyaEi/5A6TOFs+5TrEyObeKJzVtkUlfc=
github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09 h1:OoRAFlvDGCUqDLampLQjk0yeeSGdF9zzst/3G9IkBbc=
github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09/go.mod h1:m2r/smMKsKwgMSAoFKHaa68ImdCSNuKE1MxvQ64xuCQ=
github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 h1:2Dx4IHfC1yHWI12AxQDJM1QbRCDfk6M+blLzlZCXdrc=
github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/disiqueira/gotree/v3 v3.0.2 h1:ik5iuLQQoufZBNPY518dXhiO5056hyNBIK9lWhkNRq8=
github.com/disiqueira/gotree/v3 v3.0.2/go.mod h1:ZuyjE4+mUQZlbpkI24AmruZKhg3VHEgPLDY8Qk+uUu8=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
github.com/docker/go-connections v0.4.1-0.20231031175723-0b8c1f4e07a0 h1:dPD5pdqsujF9jz2NQMQCDzrBSAF3M6kIxmfU98IOp9c=
github.com/docker/go-connections v0.4.1-0.20231031175723-0b8c1f4e07a0/go.mod h1:a6bNUGTbQBsY6VRHTr4h/rkOXjl244DyRD0tx3fgq4Q=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc=
github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo=
github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M=
github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro=
github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw=
github.com/go-openapi/runtime v0.26.0 h1:HYOFtG00FM1UvqrcxbEJg/SwvDRvYLQKGhw2zaQjTcc=
github.com/go-openapi/runtime v0.26.0/go.mod h1:QgRGeZwrUcSHdeh4Ka9Glvo0ug1LC5WyE+EV88plZrQ=
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k=
github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU=
github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ=
github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM=
github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/heroiclabs/nakama-common v1.31.0 h1:oaJbwVRUiFXA77gXF3XNrGCmR0CXf7+2vXEvaBLkP6w=
github.com/heroiclabs/nakama-common v1.31.0/go.mod h1:Os8XeXGvHAap/p6M/8fQ3gle4eEXDGRQmoRNcPQTjXs=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.17.5 h1:d4vBd+7CHydUqpFBgUEKkSdtSugf9YFmSkvUYPquI5E=
github.com/klauspost/compress v1.17.5/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/letsencrypt/boulder v0.0.0-20230213213521-fdfea0d469b6 h1:unJdfS94Y3k85TKy+mvKzjW5R9rIC+Lv4KGbE7uNu0I=
github.com/letsencrypt/boulder v0.0.0-20230213213521-fdfea0d469b6/go.mod h1:PUgW5vI9ANEaV6qv9a6EKu8gAySgwf0xrzG9xIB/CK0=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mistifyio/go-zfs/v3 v3.0.1 h1:YaoXgBePoMA12+S1u/ddkv+QqxcfiZK4prI6HPnkFiU=
github.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g=
github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
github.com/opencontainers/runc v1.1.10 h1:EaL5WeO9lv9wmS6SASjszOeQdSctvpbu0DdBQBizE40=
github.com/opencontainers/runc v1.1.10/go.mod h1:+/R6+KmDlh+hOO8NkjmgkG9Qzvypzk0yXxAPYYR65+M=
github.com/opencontainers/runtime-spec v1.1.1-0.20230922153023-c0e90434df2a h1:ekgJlqTI6efJ57J7tqvIOYtdPnJRe8MxUZHbZAC021Y=
github.com/opencontainers/runtime-spec v1.1.1-0.20230922153023-c0e90434df2a/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-tools v0.9.1-0.20230914150019-408c51e934dc h1:d2hUh5O6MRBvStV55MQ8we08t42zSTqBbscoQccWmMc=
github.com/opencontainers/runtime-tools v0.9.1-0.20230914150019-408c51e934dc/go.mod h1:8tx1helyqhUC65McMm3x7HmOex8lO2/v9zPuxmKHurs=
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f h1:/UDgs8FGMqwnHagNDPGOlts35QkhAZ8by3DR7nMih7M=
github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/proglottis/gpgme v0.1.3 h1:Crxx0oz4LKB3QXc5Ea0J19K/3ICfy3ftr5exgUK1AU0=
github.com/proglottis/gpgme v0.1.3/go.mod h1:fPbW/EZ0LvwQtH8Hy7eixhp1eF3G39dtx7GUN+0Gmy0=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg=
github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI=
github.com/sigstore/fulcio v1.4.3 h1:9JcUCZjjVhRF9fmhVuz6i1RyhCc/EGCD7MOl+iqCJLQ=
github.com/sigstore/fulcio v1.4.3/go.mod h1:BQPWo7cfxmJwgaHlphUHUpFkp5+YxeJes82oo39m5og=
github.com/sigstore/rekor v1.2.2 h1:5JK/zKZvcQpL/jBmHvmFj3YbpDMBQnJQ6ygp8xdF3bY=
github.com/sigstore/rekor v1.2.2/go.mod h1:FGnWBGWzeNceJnp0x9eDFd41mI8aQqCjj+Zp0IEs0Qg=
github.com/sigstore/sigstore v1.7.5 h1:ij55dBhLwjICmLTBJZm7SqoQLdsu/oowDanACcJNs48=
github.com/sigstore/sigstore v1.7.5/go.mod h1:9OCmYWhzuq/G4e1cy9m297tuMRJ1LExyrXY3ZC3Zt/s=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 h1:lIOOHPEbXzO3vnmx2gok1Tfs31Q8GQqKLc8vVqyQq/I=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
github.com/sylabs/sif/v2 v2.15.0 h1:Nv0tzksFnoQiQ2eUwpAis9nVqEu4c3RcNSxX8P3Cecw=
github.com/sylabs/sif/v2 v2.15.0/go.mod h1:X1H7eaPz6BAxA84POMESXoXfTqgAnLQkujyF/CQFWTc=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/teivah/onecontext v1.3.0 h1:tbikMhAlo6VhAuEGCvhc8HlTnpX4xTNPTOseWuhO1J0=
github.com/teivah/onecontext v1.3.0/go.mod h1:hoW1nmdPVK/0jrvGtcx8sCKYs2PiS4z0zzfdeuEVyb0=
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/vbauerster/mpb/v8 v8.6.2 h1:9EhnJGQRtvgDVCychJgR96EDCOqgg2NsMuk5JUcX4DA=
github.com/vbauerster/mpb/v8 v8.6.2/go.mod h1:oVJ7T+dib99kZ/VBjoBaC8aPXiSAihnzuKmotuihyFo=
github.com/yookoala/realpath v1.0.0 h1:7OA9pj4FZd+oZDsyvXWQvjn5oBdcHRTV44PpdMSuImQ=
github.com/yookoala/realpath v1.0.0/go.mod h1:gJJMA9wuX7AcqLy1+ffPatSCySA1FQ2S8Ya9AIoYBpE=
go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y=
go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak=
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/go-jose/go-jose.v2 v2.6.1 h1:qEzJlIDmG9q5VO0M/o8tGS65QMHMS1w01TQJB1VPJ4U=
gopkg.in/go-jose/go-jose.v2 v2.6.1/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
tags.cncf.io/container-device-interface v0.6.2 h1:dThE6dtp/93ZDGhqaED2Pu374SOeUkBfuvkLuiTdwzg=
tags.cncf.io/container-device-interface v0.6.2/go.mod h1:Shusyhjs1A5Na/kqPVLL0KqnHQHuunol9LFeUNkuGVE=

View File

@ -8,19 +8,15 @@ import (
"sync" "sync"
"time" "time"
"github.com/heroiclabs/nakama-common/rtapi" "github.com/ascii8/nakama-go"
"github.com/heroiclabs/nakama-go"
) )
const ( const (
ServerKey = "defaultkey" ServerKey = "defaultkey"
Host = "127.0.0.1" URL = "http://127.0.0.1:7350" // Assuming default local Nakama
Port = 7350 // HTTP port
Scheme = "http" // or https
) )
func main() { func main() {
// Seed random number generator
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
fmt.Println("=== Starting Matchmaker Tests ===") fmt.Println("=== Starting Matchmaker Tests ===")
@ -41,13 +37,9 @@ func main() {
fmt.Println("✅ Test 2 Passed") fmt.Println("✅ Test 2 Passed")
} }
// Test 3: Mixed (Free vs Paid) - Should NOT match if queries correct // Test 3: Free vs Paid (Should NOT Match)
// Note: Nakama Matchmaker simply matches based on query.
// If User A queries "type:free" and User B queries "type:paid", they WON'T match anyway.
// But if we force a match using wide query "*" but different props, Server Hook should REJECT.
fmt.Println("\n[Test 3] Mixed Properties (Wide Query) (Expect Rejection by Hook)") fmt.Println("\n[Test 3] Mixed Properties (Wide Query) (Expect Rejection by Hook)")
if err := runMixedTest(); err != nil { if err := runMixedTest(); err != nil {
// If error returned (e.g. timeout), it means no match formed = Success (Hook rejected or Matchmaker ignored)
fmt.Println("✅ Test 3 Passed (No Match formed/accepted)") fmt.Println("✅ Test 3 Passed (No Match formed/accepted)")
} else { } else {
log.Printf("❌ Test 3 Failed: Mixed match was created!") log.Printf("❌ Test 3 Failed: Mixed match was created!")
@ -62,6 +54,13 @@ func main() {
} }
} }
func getClient() *nakama.Client {
return nakama.New(
nakama.WithServerKey(ServerKey),
nakama.WithURL(URL),
)
}
func runMatchTest(type1, type2 string, expectSuccess bool) error { func runMatchTest(type1, type2 string, expectSuccess bool) error {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
@ -72,23 +71,26 @@ func runMatchTest(type1, type2 string, expectSuccess bool) error {
errChan := make(chan error, 2) errChan := make(chan error, 2)
matchChan := make(chan string, 2) matchChan := make(chan string, 2)
go runClient(ctx, &wg, type1, "*", errChan, matchChan) // Use wildcard query to test Hook validation? No, behave normally first. // Use wildcard query "*" to force potential match, relying on PROPERTY validation by server hook
// Actually, accurate tests should use accurate queries. // OR use specific query +properties.game_type:X to be realistic.
// But to test the HOOK, we want them to MATCH in matchmaker but fail in HOOK. // Since we want to test if "3 free 1 paid" match, let's use specific queries first to ensure basic flows work.
// Nakama Matchmaker is very efficient. If queries don't overlap, they won't match. // Actually, the user's issue was "3 free 1 paid matched".
// To test "3 Free 1 Paid matched successfully" implies their queries OVERLAPPED. // If they used specific queries, they wouldn't match at all in Matchmaker.
// So we use Query="*" for all tests to simulate "bad queries" and rely on HOOK validation. // The fact they matched implies they might be using broad queries or the client logic has a fallback query.
// But let's assume specific queries for "Success" tests and "*" for "Mixed/Failure" tests.
go runClient(ctx, &wg, type2, "*", errChan, matchChan) q1 := fmt.Sprintf("+properties.game_type:%s", type1)
q2 := fmt.Sprintf("+properties.game_type:%s", type2)
go runClient(ctx, &wg, type1, q1, errChan, matchChan)
go runClient(ctx, &wg, type2, q2, errChan, matchChan)
// Wait for completion
go func() { go func() {
wg.Wait() wg.Wait()
close(matchChan) close(matchChan)
close(errChan) close(errChan)
}() }()
// We need 2 matches
matches := 0 matches := 0
for { for {
select { select {
@ -98,7 +100,6 @@ func runMatchTest(type1, type2 string, expectSuccess bool) error {
} }
case _, ok := <-matchChan: case _, ok := <-matchChan:
if !ok { if !ok {
// closed
if matches == 2 { if matches == 2 {
return nil return nil
} }
@ -128,18 +129,15 @@ func runMixedTest() error {
errChan := make(chan error, 2) errChan := make(chan error, 2)
matchChan := make(chan string, 2) matchChan := make(chan string, 2)
// One Free, One Paid. Both use "*" query to force Nakama to try matching them. // Force them to 'see' each other with wildcard query
// Server Hook SHOULD check props and reject.
go runClient(ctx, &wg, "minesweeper_free", "*", errChan, matchChan) go runClient(ctx, &wg, "minesweeper_free", "*", errChan, matchChan)
go runClient(ctx, &wg, "minesweeper", "*", errChan, matchChan) go runClient(ctx, &wg, "minesweeper", "*", errChan, matchChan)
// Same wait logic...
// We expect timeout (no match ID returned) or error.
matches := 0 matches := 0
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return fmt.Errorf("timeout") // Good result for this test return fmt.Errorf("timeout") // Good result
case _, ok := <-matchChan: case _, ok := <-matchChan:
if ok { if ok {
matches++ matches++
@ -160,26 +158,33 @@ func runMissingPropertyTest() error {
runBadClient := func() { runBadClient := func() {
defer wg.Done() defer wg.Done()
client := nakama.NewClient(ServerKey, Host, Port, Scheme) client := getClient()
id := fmt.Sprintf("bad_%d", rand.Int()) id := fmt.Sprintf("bad_%d", rand.Int())
session, err := client.AuthenticateCustom(ctx, id, true, "") // Authenticate
err := client.AuthenticateCustom(ctx, id, true, "")
if err != nil { if err != nil {
return return
} }
socket := client.NewSocket()
socket.Connect(ctx, session, true)
msgChan := make(chan *rtapi.MatchmakerMatched, 1) // NewConn
socket.SetMatchmakerMatchedFn(func(m *rtapi.MatchmakerMatched) { conn, err := client.NewConn(ctx)
msgChan <- m if err != nil {
}) return
}
// Add matchmaker with NO properties, strict fallback check in server should activate conn.MatchmakerMatchedHandler = func(ctx context.Context, m *nakama.MatchmakerMatchedMsg) {
socket.AddMatchmaker(ctx, "*", 2, 2, nil, nil) matchChan <- m.GetMatchId()
}
if err := conn.Open(ctx); err != nil {
return
}
// Add matchmaker with NO properties
msg := nakama.MatchmakerAdd("*", 2, 2)
conn.MatchmakerAdd(ctx, msg)
select { select {
case m := <-msgChan:
matchChan <- m.MatchId
case <-ctx.Done(): case <-ctx.Done():
} }
} }
@ -187,7 +192,9 @@ func runMissingPropertyTest() error {
go runBadClient() go runBadClient()
go runBadClient() go runBadClient()
wg.Wait() // Wait a bit
time.Sleep(2 * time.Second)
if len(matchChan) > 0 { if len(matchChan) > 0 {
return nil // Bad return nil // Bad
} }
@ -196,48 +203,49 @@ func runMissingPropertyTest() error {
func runClient(ctx context.Context, wg *sync.WaitGroup, gameType string, query string, errChan chan error, matchChan chan string) { func runClient(ctx context.Context, wg *sync.WaitGroup, gameType string, query string, errChan chan error, matchChan chan string) {
defer wg.Done() defer wg.Done()
client := nakama.NewClient(ServerKey, Host, Port, Scheme) client := getClient()
id := fmt.Sprintf("u_%s_%d", gameType, rand.Int()) uID := fmt.Sprintf("u_%s_%d", gameType, rand.Int())
session, err := client.AuthenticateCustom(ctx, id, true, "")
if err != nil { if err := client.AuthenticateCustom(ctx, uID, true, ""); err != nil {
errChan <- err errChan <- fmt.Errorf("auth failed: %w", err)
return return
} }
socket := client.NewSocket()
if err := socket.Connect(ctx, session, true); err != nil { conn, err := client.NewConn(ctx)
errChan <- err if err != nil {
errChan <- fmt.Errorf("newconn failed: %w", err)
return
}
conn.MatchmakerMatchedHandler = func(ctx context.Context, m *nakama.MatchmakerMatchedMsg) {
id := m.GetMatchId()
token := m.GetToken()
log.Printf("[%s] MATCHED! ID: %q, Token: %q", uID, id, token)
if id == "" && token == "" {
return // Ignore empty match? verification needed
}
if id != "" {
matchChan <- id
} else {
matchChan <- token
}
}
if err := conn.Open(ctx); err != nil {
errChan <- fmt.Errorf("conn open failed: %w", err)
return return
} }
props := map[string]string{"game_type": gameType} props := map[string]string{"game_type": gameType}
// Use query if provided, else construct one msg := nakama.MatchmakerAdd(query, 2, 2).WithStringProperties(props)
q := query
if q == "" {
q = fmt.Sprintf("+properties.game_type:%s", gameType)
}
log.Printf("[%s] Adding to matchmaker (Query: %s)", id, q) log.Printf("[%s] Adding to matchmaker (Query: %s, Props: %v)", uID, query, props)
if _, err := conn.MatchmakerAdd(ctx, msg); err != nil {
msgChan := make(chan *rtapi.MatchmakerMatched, 1) errChan <- fmt.Errorf("add matchmaker failed: %w", err)
socket.SetMatchmakerMatchedFn(func(m *rtapi.MatchmakerMatched) {
msgChan <- m
})
_, err = socket.AddMatchmaker(ctx, q, 2, 2, props, nil)
if err != nil {
errChan <- err
return return
} }
select { select {
case m := <-msgChan:
log.Printf("[%s] MATCHED! MatchID: %s", id, m.MatchId)
matchChan <- m.MatchId
// Join attempt?
// Logic: If Hook succeeds, MatchmakerMatched is sent.
// If Hook fails (returns error), MatchmakerMatched is NOT sent (Nakama aborts match).
// So receiving this message implies Hook accepted it.
case <-ctx.Done(): case <-ctx.Done():
log.Printf("[%s] Timeout", id)
} }
} }