From ec035ffb530f2636df6b2c94dc49b420ef44d8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=96=B9=E6=88=90?= Date: Thu, 19 Feb 2026 19:55:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=A4=A7=E5=8E=85=EF=BC=8C?= =?UTF-8?q?=E9=99=90=E9=87=8F=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/debug_coupon_9209/main.go | 76 +++++++ internal/api/task_center/tasks_app.go | 20 +- internal/api/user/coupons_app.go | 60 ++++-- internal/api/user/coupons_stats_app.go | 35 ++-- internal/service/user/coupons_list.go | 23 +-- tools/test_matchmaker/go.mod | 11 + tools/test_matchmaker/go.sum | 274 +++++++++++++++++++++++++ tools/test_matchmaker/main.go | 148 ++++++------- 8 files changed, 516 insertions(+), 131 deletions(-) create mode 100644 cmd/debug_coupon_9209/main.go create mode 100644 tools/test_matchmaker/go.sum diff --git a/cmd/debug_coupon_9209/main.go b/cmd/debug_coupon_9209/main.go new file mode 100644 index 0000000..414e8c3 --- /dev/null +++ b/cmd/debug_coupon_9209/main.go @@ -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) + } +} diff --git a/internal/api/task_center/tasks_app.go b/internal/api/task_center/tasks_app.go index 1b32f0a..ba87e2a 100644 --- a/internal/api/task_center/tasks_app.go +++ b/internal/api/task_center/tasks_app.go @@ -15,14 +15,16 @@ type listTasksRequest struct { } type taskItem struct { - ID int64 `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Status int32 `json:"status"` - StartTime int64 `json:"start_time"` - EndTime int64 `json:"end_time"` - Tiers []taskTierItem `json:"tiers"` - Rewards []taskRewardItem `json:"rewards"` + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Status int32 `json:"status"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` + Quota int32 `json:"quota"` + ClaimedCount int32 `json:"claimed_count"` + Tiers []taskTierItem `json:"tiers"` + Rewards []taskRewardItem `json:"rewards"` } type taskTierItem struct { @@ -79,7 +81,7 @@ func (h *handler) ListTasksForApp() core.HandlerFunc { res.Total = total res.List = make([]taskItem, len(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 { ti.Tiers = make([]taskTierItem, len(v.Tiers)) for j, t := range v.Tiers { diff --git a/internal/api/user/coupons_app.go b/internal/api/user/coupons_app.go index 75dec72..f9d4c1a 100644 --- a/internal/api/user/coupons_app.go +++ b/internal/api/user/coupons_app.go @@ -13,7 +13,7 @@ import ( type listCouponsRequest struct { Page int `form:"page"` PageSize int `form:"page_size"` - Status *int32 `form:"status"` // 1未使用 2已使用 3已过期,不传默认1 + Status *int32 `form:"status"` // 1有效 2已失效,不传默认1 } type listCouponsResponse struct { Page int `json:"page"` @@ -30,7 +30,8 @@ type couponItem struct { ValidStart string `json:"valid_start"` ValidEnd string `json:"valid_end"` 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"` UsedAt string `json:"used_at,omitempty"` // 使用时间(已使用时返回) UsedAmount int64 `json:"used_amount"` // 已使用金额 @@ -46,7 +47,7 @@ type couponItem struct { // @Security LoginVerifyToken // @Param page query int true "页码" default(1) // @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 // @Failure 400 {object} code.Failure // @Router /api/app/users/{user_id}/coupons [get] @@ -96,11 +97,10 @@ func (h *handler) ListUserCoupons() core.HandlerFunc { mp[c.ID] = c } rsp.List = make([]couponItem, 0, len(items)) - rsp.List = make([]couponItem, 0, len(items)) for _, it := range items { sc := mp[it.CouponID] if sc == nil { - continue // 找不到模板或模板设为不显示,跳过 + continue } name := sc.Name @@ -118,27 +118,14 @@ func (h *handler) ListUserCoupons() core.HandlerFunc { 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 if usedAmount < 0 { usedAmount = 0 } + // 计算子状态和描述 + subStatus, statusDesc := calcCouponSubStatus(it, sc) + vi := couponItem{ ID: it.ID, Name: name, @@ -148,6 +135,7 @@ func (h *handler) ListUserCoupons() core.HandlerFunc { ValidStart: vs, ValidEnd: ve, Status: it.Status, + SubStatus: subStatus, StatusDesc: statusDesc, Rules: rules, UsedAt: usedAt, @@ -171,3 +159,33 @@ func buildCouponRules(c *model.SystemCoupons) string { 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", "可用" + } +} diff --git a/internal/api/user/coupons_stats_app.go b/internal/api/user/coupons_stats_app.go index 258a052..1c8c6b5 100644 --- a/internal/api/user/coupons_stats_app.go +++ b/internal/api/user/coupons_stats_app.go @@ -5,9 +5,8 @@ import ( ) type couponStatsResponse struct { - UnusedCount int64 `json:"unused_count"` // 可用优惠券数量 - UsedCount int64 `json:"used_count"` // 已使用优惠券数量 - ExpiredCount int64 `json:"expired_count"` // 已过期优惠券数量 + ValidCount int64 `json:"valid_count"` // 有效优惠券数量 + InvalidCount int64 `json:"invalid_count"` // 已失效优惠券数量 TotalRemaining int64 `json:"total_remaining"` // 可用优惠券总余额(分) TotalSaved int64 `json:"total_saved"` // 累计节省金额(分) } @@ -28,31 +27,35 @@ func (h *handler) GetUserCouponStats() core.HandlerFunc { rsp := new(couponStatsResponse) userID := int64(ctx.SessionUserInfo().Id) - // 统计各状态数量 - 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() + uc := h.readDB.UserCoupons - // 统计可用优惠券总余额 + // 有效: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 _ = 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, ).Scan(&totalRemaining).Error - // 统计累计节省金额(所有使用记录的扣减金额总和) + // 累计节省金额 var totalSaved int64 _ = h.repo.GetDbR().Raw( "SELECT COALESCE(SUM(ABS(change_amount)), 0) FROM user_coupon_ledger WHERE user_id = ? AND change_amount < 0", userID, ).Scan(&totalSaved).Error - rsp.UnusedCount = unusedCount - rsp.UsedCount = usedCount - rsp.ExpiredCount = expiredCount + rsp.ValidCount = validCount + rsp.InvalidCount = invalidCount rsp.TotalRemaining = totalRemaining rsp.TotalSaved = totalSaved diff --git a/internal/service/user/coupons_list.go b/internal/service/user/coupons_list.go index 1f66a07..af4a811 100644 --- a/internal/service/user/coupons_list.go +++ b/internal/service/user/coupons_list.go @@ -36,13 +36,12 @@ func (s *service) ListCouponsByStatus(ctx context.Context, userID int64, status return items, total, nil } -// ListAppCoupons APP端查看优惠券(分类逻辑优化:未使用=未动过,已使用=部分使用or已用完) -// ListAppCoupons APP端查看优惠券(分类逻辑优化:未使用=未动过,已使用=部分使用or已用完) +// ListAppCoupons APP端查看优惠券 +// 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) { u := s.readDB.UserCoupons c := s.readDB.SystemCoupons - // 使用 UnderlyingDB 绕过 GEN 的类型限制,确保 SQL 逻辑正确 tableName := u.TableName() sysTableName := c.TableName() 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"). Where("`"+tableName+"`.user_id = ?", userID) - // 过滤逻辑 switch status { - case 1: // 未使用 (Status=1且余额>0) - db = db.Where(u.TableName()+".status = ? AND "+u.TableName()+".balance_amount > ?", 1, 0) - case 2: // 已使用 (Status=2 或 Status=1且余额=0) - // Condition: (Status=1 AND Balance = 0) OR Status=2 - db = db.Where("("+u.TableName()+".status = ? AND "+u.TableName()+".balance_amount = ?) OR "+u.TableName()+".status = ?", 1, 0, 2) - case 3: // 已过期 - 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) + case 1: // 有效:status=1 且余额>0(包含未使用和使用中) + db = db.Where(tableName+".status = ? AND "+tableName+".balance_amount > ?", 1, 0) + case 2: // 已失效:余额用完(status=1且balance=0) 或 已使用(status=2) 或 已过期(status=3) + db = db.Where("("+tableName+".status = ? AND "+tableName+".balance_amount = ?) OR "+tableName+".status IN (?,?)", 1, 0, 2, 3) + default: + db = db.Where(tableName+".status = ? AND "+tableName+".balance_amount > ?", 1, 0) } - // Count if err = db.Count(&total).Error; err != nil { return nil, 0, err } @@ -76,7 +70,6 @@ func (s *service) ListAppCoupons(ctx context.Context, userID int64, status int32 pageSize = 20 } - // Find err = db.Order("`" + tableName + "`.id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Scan(&items).Error if err != nil { return nil, 0, err diff --git a/tools/test_matchmaker/go.mod b/tools/test_matchmaker/go.mod index de4448f..17610eb 100644 --- a/tools/test_matchmaker/go.mod +++ b/tools/test_matchmaker/go.mod @@ -1,3 +1,14 @@ module test_matchmaker 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 +) diff --git a/tools/test_matchmaker/go.sum b/tools/test_matchmaker/go.sum new file mode 100644 index 0000000..2342246 --- /dev/null +++ b/tools/test_matchmaker/go.sum @@ -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= diff --git a/tools/test_matchmaker/main.go b/tools/test_matchmaker/main.go index 93d70c9..17150a4 100644 --- a/tools/test_matchmaker/main.go +++ b/tools/test_matchmaker/main.go @@ -8,19 +8,15 @@ import ( "sync" "time" - "github.com/heroiclabs/nakama-common/rtapi" - "github.com/heroiclabs/nakama-go" + "github.com/ascii8/nakama-go" ) const ( ServerKey = "defaultkey" - Host = "127.0.0.1" - Port = 7350 // HTTP port - Scheme = "http" // or https + URL = "http://127.0.0.1:7350" // Assuming default local Nakama ) func main() { - // Seed random number generator rand.Seed(time.Now().UnixNano()) fmt.Println("=== Starting Matchmaker Tests ===") @@ -41,13 +37,9 @@ func main() { fmt.Println("✅ Test 2 Passed") } - // Test 3: Mixed (Free vs Paid) - Should NOT match if queries correct - // 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. + // Test 3: Free vs Paid (Should NOT Match) fmt.Println("\n[Test 3] Mixed Properties (Wide Query) (Expect Rejection by Hook)") 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)") } else { 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 { var wg sync.WaitGroup wg.Add(2) @@ -72,23 +71,26 @@ func runMatchTest(type1, type2 string, expectSuccess bool) error { errChan := make(chan error, 2) matchChan := make(chan string, 2) - go runClient(ctx, &wg, type1, "*", errChan, matchChan) // Use wildcard query to test Hook validation? No, behave normally first. - // Actually, accurate tests should use accurate queries. - // But to test the HOOK, we want them to MATCH in matchmaker but fail in HOOK. - // Nakama Matchmaker is very efficient. If queries don't overlap, they won't match. - // To test "3 Free 1 Paid matched successfully" implies their queries OVERLAPPED. - // So we use Query="*" for all tests to simulate "bad queries" and rely on HOOK validation. + // Use wildcard query "*" to force potential match, relying on PROPERTY validation by server hook + // OR use specific query +properties.game_type:X to be realistic. + // Since we want to test if "3 free 1 paid" match, let's use specific queries first to ensure basic flows work. + // Actually, the user's issue was "3 free 1 paid matched". + // If they used specific queries, they wouldn't match at all in Matchmaker. + // 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() { wg.Wait() close(matchChan) close(errChan) }() - // We need 2 matches matches := 0 for { select { @@ -98,7 +100,6 @@ func runMatchTest(type1, type2 string, expectSuccess bool) error { } case _, ok := <-matchChan: if !ok { - // closed if matches == 2 { return nil } @@ -128,18 +129,15 @@ func runMixedTest() error { errChan := make(chan error, 2) matchChan := make(chan string, 2) - // One Free, One Paid. Both use "*" query to force Nakama to try matching them. - // Server Hook SHOULD check props and reject. + // Force them to 'see' each other with wildcard query go runClient(ctx, &wg, "minesweeper_free", "*", errChan, matchChan) go runClient(ctx, &wg, "minesweeper", "*", errChan, matchChan) - // Same wait logic... - // We expect timeout (no match ID returned) or error. matches := 0 for { select { case <-ctx.Done(): - return fmt.Errorf("timeout") // Good result for this test + return fmt.Errorf("timeout") // Good result case _, ok := <-matchChan: if ok { matches++ @@ -160,26 +158,33 @@ func runMissingPropertyTest() error { runBadClient := func() { defer wg.Done() - client := nakama.NewClient(ServerKey, Host, Port, Scheme) + client := getClient() id := fmt.Sprintf("bad_%d", rand.Int()) - session, err := client.AuthenticateCustom(ctx, id, true, "") + // Authenticate + err := client.AuthenticateCustom(ctx, id, true, "") if err != nil { return } - socket := client.NewSocket() - socket.Connect(ctx, session, true) - msgChan := make(chan *rtapi.MatchmakerMatched, 1) - socket.SetMatchmakerMatchedFn(func(m *rtapi.MatchmakerMatched) { - msgChan <- m - }) + // NewConn + conn, err := client.NewConn(ctx) + if err != nil { + return + } - // Add matchmaker with NO properties, strict fallback check in server should activate - socket.AddMatchmaker(ctx, "*", 2, 2, nil, nil) + conn.MatchmakerMatchedHandler = func(ctx context.Context, m *nakama.MatchmakerMatchedMsg) { + 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 { - case m := <-msgChan: - matchChan <- m.MatchId case <-ctx.Done(): } } @@ -187,7 +192,9 @@ func runMissingPropertyTest() error { go runBadClient() go runBadClient() - wg.Wait() + // Wait a bit + time.Sleep(2 * time.Second) + if len(matchChan) > 0 { 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) { defer wg.Done() - client := nakama.NewClient(ServerKey, Host, Port, Scheme) - id := fmt.Sprintf("u_%s_%d", gameType, rand.Int()) - session, err := client.AuthenticateCustom(ctx, id, true, "") - if err != nil { - errChan <- err + client := getClient() + uID := fmt.Sprintf("u_%s_%d", gameType, rand.Int()) + + if err := client.AuthenticateCustom(ctx, uID, true, ""); err != nil { + errChan <- fmt.Errorf("auth failed: %w", err) return } - socket := client.NewSocket() - if err := socket.Connect(ctx, session, true); err != nil { - errChan <- err + + conn, err := client.NewConn(ctx) + 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 } props := map[string]string{"game_type": gameType} - // Use query if provided, else construct one - q := query - if q == "" { - q = fmt.Sprintf("+properties.game_type:%s", gameType) - } + msg := nakama.MatchmakerAdd(query, 2, 2).WithStringProperties(props) - log.Printf("[%s] Adding to matchmaker (Query: %s)", id, q) - - msgChan := make(chan *rtapi.MatchmakerMatched, 1) - socket.SetMatchmakerMatchedFn(func(m *rtapi.MatchmakerMatched) { - msgChan <- m - }) - - _, err = socket.AddMatchmaker(ctx, q, 2, 2, props, nil) - if err != nil { - errChan <- err + log.Printf("[%s] Adding to matchmaker (Query: %s, Props: %v)", uID, query, props) + if _, err := conn.MatchmakerAdd(ctx, msg); err != nil { + errChan <- fmt.Errorf("add matchmaker failed: %w", err) return } 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(): - log.Printf("[%s] Timeout", id) } }