From f0a6a5ebbdafca9added16ba68388c8935e4d22f Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Sat, 22 Nov 2025 20:35:23 -0800 Subject: [PATCH 01/34] update .env.example --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index 42c924e..4029fdc 100644 --- a/.env.example +++ b/.env.example @@ -2,3 +2,4 @@ DATABASE_URL="file:dev.db?cache=shared&mode=rwc" PORT="" TRUSTED_PROXIES="" ALLOWED_ORIGINS="" +DISCORD_BOT_TOKEN="" From f05f0c3be479caffa9046776cead9ea28a2cb11b Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Sat, 22 Nov 2025 20:42:15 -0800 Subject: [PATCH 02/34] add ENV var --- .env.example | 1 + internal/api/config/config.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.env.example b/.env.example index 4029fdc..3d7c17a 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ +ENV="development" DATABASE_URL="file:dev.db?cache=shared&mode=rwc" PORT="" TRUSTED_PROXIES="" diff --git a/internal/api/config/config.go b/internal/api/config/config.go index c39d2df..90b232f 100644 --- a/internal/api/config/config.go +++ b/internal/api/config/config.go @@ -3,6 +3,7 @@ package config import "github.com/acmcsufoss/api.acmcsuf.com/utils" type Config struct { + Env string Port string DatabaseURL string TrustedProxies []string @@ -11,6 +12,7 @@ type Config struct { func Load() *Config { return &Config{ + Env: utils.GetEnv("ENV", "development"), Port: utils.GetEnv("PORT", "8080"), DatabaseURL: utils.GetEnv("DATABASE_URL", "file:dev.db?cache=shared&mode=rwc"), TrustedProxies: utils.GetEnvAsSlice("TRUSTED_PROXIES", []string{"127.0.0.1/32"}), From ee1ecdbd6f797b9a1cd74e033c6d6575dbd4fc30 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Sat, 22 Nov 2025 21:23:46 -0800 Subject: [PATCH 03/34] init stuff --- .env.example | 1 + go.mod | 2 ++ go.sum | 5 +++++ internal/api/config/config.go | 4 +++- internal/api/server.go | 38 +++++++++++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 3d7c17a..cacfd4f 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,4 @@ PORT="" TRUSTED_PROXIES="" ALLOWED_ORIGINS="" DISCORD_BOT_TOKEN="" +GUILD_ID="" diff --git a/go.mod b/go.mod index 5d3a939..9626674 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.0 toolchain go1.23.4 require ( + github.com/bwmarrin/discordgo v0.29.0 github.com/gin-gonic/gin v1.10.0 github.com/spf13/cobra v1.9.1 github.com/swaggo/files v1.0.1 @@ -30,6 +31,7 @@ require ( github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 0870e55..5a5bf50 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= +github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= @@ -47,6 +49,8 @@ github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlG github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= 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/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -111,6 +115,7 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= diff --git a/internal/api/config/config.go b/internal/api/config/config.go index 90b232f..5284497 100644 --- a/internal/api/config/config.go +++ b/internal/api/config/config.go @@ -8,14 +8,16 @@ type Config struct { DatabaseURL string TrustedProxies []string AllowedOrigins []string + GuildID string } func Load() *Config { return &Config{ - Env: utils.GetEnv("ENV", "development"), + Env: utils.GetEnv("ENV", "development"), Port: utils.GetEnv("PORT", "8080"), DatabaseURL: utils.GetEnv("DATABASE_URL", "file:dev.db?cache=shared&mode=rwc"), TrustedProxies: utils.GetEnvAsSlice("TRUSTED_PROXIES", []string{"127.0.0.1/32"}), AllowedOrigins: utils.GetEnvAsSlice("ALLOWED_ORIGINS", []string{"*"}), + GuildID: utils.GetEnv("GUILD_ID", "710225099923521558"), } } diff --git a/internal/api/server.go b/internal/api/server.go index 6f64c9f..dd99076 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -6,7 +6,10 @@ import ( "context" "fmt" "log" + "net/http" + "os" + "github.com/bwmarrin/discordgo" "github.com/gin-gonic/gin" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/config" @@ -20,6 +23,21 @@ import ( // It waits for the context to be canceled to initiate a graceful shutdown. func Run(ctx context.Context) { cfg := config.Load() + botToken := os.Getenv("DISCORD_BOT_TOKEN") + + if botToken == "" && cfg.Env != "development" { + log.Fatal("Error: DISCORD_BOT_TOKEN si not set") + } + var botSession *discordgo.Session + if botToken != "" { + botSession, err := discordgo.New("Bot " + botToken) + if err != nil { + log.Fatalf("%v", err) + } + botSession.Open() + defer botSession.Close() + } + db, closer, err := db.New(ctx, cfg.DatabaseURL) if err != nil { log.Fatal(err) @@ -51,3 +69,23 @@ func Run(ctx context.Context) { <-ctx.Done() log.Println("\x1b[32mServer shut down.\x1b[0m") } + +func DiscordAuthMiddleware(bot *discordgo.Session, requiredRole string) gin.HandlerFunc { + return func(c *gin.Context) { + // expects the header 'Authorization: Bearer ' + authHeader := c.GetHeader("Authorization") + + if authHeader == "" { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Missing Authorization header"}) + return + } + + // dev mode bypass (ENV=development) + if config.Load().Env == "development" && authHeader == "Bearer dev-token" { + c.Set("userID", "dev-user-id") + c.Next() + return + } + } + +} From 40c93cbbc61f22bffeca986b95a20a64498eac85 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Mon, 24 Nov 2025 13:46:33 -0800 Subject: [PATCH 04/34] wip --- internal/api/middleware/oauth.go | 40 ++++++++++++++++++++++++++++++++ internal/api/server.go | 23 +----------------- 2 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 internal/api/middleware/oauth.go diff --git a/internal/api/middleware/oauth.go b/internal/api/middleware/oauth.go new file mode 100644 index 0000000..4656f77 --- /dev/null +++ b/internal/api/middleware/oauth.go @@ -0,0 +1,40 @@ +package middleware + +import ( + "net/http" + "sync" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/gin-gonic/gin" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/config" +) + +var roleCache = sync.Map{} + +type cacheEntry struct { + Roles []string + UserID string + expiresAt time.Time +} + +func DiscordAuthMiddleware(bot *discordgo.Session, requiredRole string) gin.HandlerFunc { + return func(c *gin.Context) { + // expects the header 'Authorization: Bearer ' + authHeader := c.GetHeader("Authorization") + + if authHeader == "" { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Missing Authorization header"}) + return + } + + // dev mode bypass (ENV=development) + if config.Load().Env == "development" && authHeader == "Bearer dev-token" { + c.Set("userID", "dev-user-id") + c.Next() + return + } + } + +} diff --git a/internal/api/server.go b/internal/api/server.go index dd99076..8318409 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -6,7 +6,6 @@ import ( "context" "fmt" "log" - "net/http" "os" "github.com/bwmarrin/discordgo" @@ -26,7 +25,7 @@ func Run(ctx context.Context) { botToken := os.Getenv("DISCORD_BOT_TOKEN") if botToken == "" && cfg.Env != "development" { - log.Fatal("Error: DISCORD_BOT_TOKEN si not set") + log.Fatal("Error: DISCORD_BOT_TOKEN is not set") } var botSession *discordgo.Session if botToken != "" { @@ -69,23 +68,3 @@ func Run(ctx context.Context) { <-ctx.Done() log.Println("\x1b[32mServer shut down.\x1b[0m") } - -func DiscordAuthMiddleware(bot *discordgo.Session, requiredRole string) gin.HandlerFunc { - return func(c *gin.Context) { - // expects the header 'Authorization: Bearer ' - authHeader := c.GetHeader("Authorization") - - if authHeader == "" { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Missing Authorization header"}) - return - } - - // dev mode bypass (ENV=development) - if config.Load().Env == "development" && authHeader == "Bearer dev-token" { - c.Set("userID", "dev-user-id") - c.Next() - return - } - } - -} From f8d8950e500bad5b32a2c111a2791d6fe8303bdc Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Mon, 24 Nov 2025 13:55:57 -0800 Subject: [PATCH 05/34] move logic to routes package --- internal/api/middleware/oauth.go | 2 +- internal/api/routes/v1.go | 24 ++++++++++++++++++++++++ internal/api/server.go | 16 ---------------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/internal/api/middleware/oauth.go b/internal/api/middleware/oauth.go index 4656f77..821c879 100644 --- a/internal/api/middleware/oauth.go +++ b/internal/api/middleware/oauth.go @@ -19,7 +19,7 @@ type cacheEntry struct { expiresAt time.Time } -func DiscordAuthMiddleware(bot *discordgo.Session, requiredRole string) gin.HandlerFunc { +func DiscordAuth(bot *discordgo.Session, requiredRole string) gin.HandlerFunc { return func(c *gin.Context) { // expects the header 'Authorization: Bearer ' authHeader := c.GetHeader("Authorization") diff --git a/internal/api/routes/v1.go b/internal/api/routes/v1.go index 73fe690..c465041 100644 --- a/internal/api/routes/v1.go +++ b/internal/api/routes/v1.go @@ -3,17 +3,41 @@ package routes import ( + "log" + "os" + + "github.com/bwmarrin/discordgo" "github.com/gin-gonic/gin" + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/config" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/handlers" + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/middleware" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/services" ) func SetupV1(router *gin.Engine, eventService services.EventsServicer, announcementService services.AnnouncementServicer, boardService services.BoardServicer) { + cfg := config.Load() + botToken := os.Getenv("DISCORD_BOT_TOKEN") + if botToken == "" && cfg.Env != "development" { + log.Fatal("Error: DISCORD_BOT_TOKEN is not set") + } + + var botSession *discordgo.Session + if botToken != "" { + botSession, err := discordgo.New("Bot " + botToken) + if err != nil { + log.Fatalf("%v", err) + } + botSession.Open() + defer botSession.Close() + } + // Version 1 routes v1 := router.Group("/v1") + // All v1 routes are protected + v1.Use(middleware.DiscordAuth(botSession, "board")) { events := v1.Group("/events") { diff --git a/internal/api/server.go b/internal/api/server.go index 8318409..467db3a 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -6,9 +6,7 @@ import ( "context" "fmt" "log" - "os" - "github.com/bwmarrin/discordgo" "github.com/gin-gonic/gin" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/config" @@ -22,20 +20,6 @@ import ( // It waits for the context to be canceled to initiate a graceful shutdown. func Run(ctx context.Context) { cfg := config.Load() - botToken := os.Getenv("DISCORD_BOT_TOKEN") - - if botToken == "" && cfg.Env != "development" { - log.Fatal("Error: DISCORD_BOT_TOKEN is not set") - } - var botSession *discordgo.Session - if botToken != "" { - botSession, err := discordgo.New("Bot " + botToken) - if err != nil { - log.Fatalf("%v", err) - } - botSession.Open() - defer botSession.Close() - } db, closer, err := db.New(ctx, cfg.DatabaseURL) if err != nil { From 81896fee529693a201b81de67d6ca565dee18cfa Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Mon, 24 Nov 2025 17:25:19 -0800 Subject: [PATCH 06/34] wip --- internal/api/middleware/oauth.go | 57 ++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/internal/api/middleware/oauth.go b/internal/api/middleware/oauth.go index 821c879..b507ea3 100644 --- a/internal/api/middleware/oauth.go +++ b/internal/api/middleware/oauth.go @@ -16,7 +16,7 @@ var roleCache = sync.Map{} type cacheEntry struct { Roles []string UserID string - expiresAt time.Time + ExpiresAt time.Time } func DiscordAuth(bot *discordgo.Session, requiredRole string) gin.HandlerFunc { @@ -25,16 +25,67 @@ func DiscordAuth(bot *discordgo.Session, requiredRole string) gin.HandlerFunc { authHeader := c.GetHeader("Authorization") if authHeader == "" { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Missing Authorization header"}) + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ + "error": "Missing Authorization header", + }) return } // dev mode bypass (ENV=development) - if config.Load().Env == "development" && authHeader == "Bearer dev-token" { + cfg := config.Load() + if cfg.Env == "development" && authHeader == "Bearer dev-token" { c.Set("userID", "dev-user-id") c.Next() return } + + // using a cache is required since discord has pretty strict rate limits on their API + if value, ok := roleCache.Load(authHeader); ok { + cached := value.(cacheEntry) + if time.Now().Before(cached.ExpiresAt) { + if checkRoles(cached.Roles, requiredRole) { + c.Set("userID", cached.UserID) + c.Next() + return + } else { + c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ + "error": "Insufficient permissions (cached)", + }) + return + } + } else { + roleCache.Delete(authHeader) + } + + } + + userSession, _ := discordgo.New(authHeader) + user, err := userSession.User("@me") + if err != nil { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ + "error": "Invalid or expired Discord token", + }) + return + } + + member, err := bot.GuildMember(cfg.GuildID, user.ID) + if err != nil { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ + "error": "You are not a member of the Discord server", + }) + } + + roleCache.Store(authHeader, cacheEntry{ + Roles: member.Roles, + UserID: user.ID, + ExpiresAt: time.Now().Add(time.Minute * 5), + }) + } +} +func checkRoles(roles []string, requiredRole string) bool { + _ = roles + _ = requiredRole + return true } From cb3aeeb1bab627bf1c684d37a70a3833b8eb33ce Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Mon, 24 Nov 2025 17:55:26 -0800 Subject: [PATCH 07/34] fleshed out at this point --- internal/api/middleware/oauth.go | 33 ++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/internal/api/middleware/oauth.go b/internal/api/middleware/oauth.go index b507ea3..42c17b7 100644 --- a/internal/api/middleware/oauth.go +++ b/internal/api/middleware/oauth.go @@ -11,6 +11,11 @@ import ( "github.com/acmcsufoss/api.acmcsuf.com/internal/api/config" ) +var RoleMap = map[string]string{ + "123": "Board", + "456": "President", +} + var roleCache = sync.Map{} type cacheEntry struct { @@ -81,11 +86,31 @@ func DiscordAuth(bot *discordgo.Session, requiredRole string) gin.HandlerFunc { ExpiresAt: time.Now().Add(time.Minute * 5), }) + if checkRoles(member.Roles, requiredRole) { + c.Set("userID", user.ID) + c.Next() + } else { + c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ + "error": "Insufficient permissions", + }) + } + } } -func checkRoles(roles []string, requiredRole string) bool { - _ = roles - _ = requiredRole - return true +func checkRoles(userRoleIDs []string, requiredRole string) bool { + for _, id := range userRoleIDs { + roleName, exists := RoleMap[id] + if !exists { + continue + } + + if roleName == requiredRole { + return true + } + if roleName == "President" && requiredRole == "Board" { + return true + } + } + return false } From 80229d6fac6ebe40916a7579f9f785e838230290 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Mon, 24 Nov 2025 18:20:53 -0800 Subject: [PATCH 08/34] update go version --- go.mod | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9626674..65c27f3 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/acmcsufoss/api.acmcsuf.com -go 1.23.0 - -toolchain go1.23.4 +go 1.24 require ( github.com/bwmarrin/discordgo v0.29.0 From 12841908d6d33b55d0d01e72dd6bb7f01c320a13 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Mon, 24 Nov 2025 18:21:24 -0800 Subject: [PATCH 09/34] go get ./... --- go.mod | 77 ++++++++++++++++++++++++++++-------------------- go.sum | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index 65c27f3..9e26370 100644 --- a/go.mod +++ b/go.mod @@ -1,60 +1,75 @@ module github.com/acmcsufoss/api.acmcsuf.com -go 1.24 +go 1.24.0 require ( github.com/bwmarrin/discordgo v0.29.0 - github.com/gin-gonic/gin v1.10.0 - github.com/spf13/cobra v1.9.1 + github.com/gin-gonic/gin v1.11.0 + github.com/spf13/cobra v1.10.1 github.com/swaggo/files v1.0.1 - github.com/swaggo/gin-swagger v1.6.0 - github.com/swaggo/swag v1.16.4 - modernc.org/sqlite v1.36.0 + github.com/swaggo/gin-swagger v1.6.1 + github.com/swaggo/swag v1.16.6 + modernc.org/sqlite v1.40.1 ) require ( github.com/KyleBanks/depth v1.2.1 // indirect - github.com/bytedance/sonic v1.13.2 // indirect - github.com/bytedance/sonic/loader v0.2.4 // indirect - github.com/cloudwego/base64x v0.1.5 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.14.2 // indirect + github.com/bytedance/sonic/loader v0.4.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gabriel-vasile/mimetype v1.4.11 // indirect github.com/gin-contrib/sse v1.1.0 // indirect - github.com/go-openapi/jsonpointer v0.21.1 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/spec v0.21.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/jsonpointer v0.22.3 // indirect + github.com/go-openapi/jsonreference v0.21.3 // indirect + github.com/go-openapi/spec v0.22.1 // indirect + github.com/go-openapi/swag v0.25.3 // indirect + github.com/go-openapi/swag/conv v0.25.3 // indirect + github.com/go-openapi/swag/jsonname v0.25.3 // indirect + github.com/go-openapi/swag/jsonutils v0.25.3 // indirect + github.com/go-openapi/swag/loading v0.25.3 // indirect + github.com/go-openapi/swag/stringutils v0.25.3 // indirect + github.com/go-openapi/swag/typeutils v0.25.3 // indirect + github.com/go-openapi/swag/yamlutils v0.25.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.26.0 // indirect + github.com/go-playground/validator/v10 v10.28.0 // indirect github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.4.2 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.57.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.16.0 // indirect - golang.org/x/crypto v0.37.0 // indirect - golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect - golang.org/x/net v0.39.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect - golang.org/x/tools v0.32.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.uber.org/mock v0.6.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/arch v0.23.0 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect + golang.org/x/mod v0.30.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.39.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.61.13 // indirect + modernc.org/libc v1.67.1 // indirect modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.8.2 // indirect + modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index 5a5bf50..f3b43f7 100644 --- a/go.sum +++ b/go.sum @@ -2,13 +2,21 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= +github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= +github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= +github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -18,20 +26,46 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= +github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= +github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8= +github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= +github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k= +github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA= github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/swag v0.25.3 h1:FAa5wJXyDtI7yUztKDfZxDrSx+8WTg31MfCQ9s3PV+s= +github.com/go-openapi/swag v0.25.3/go.mod h1:tX9vI8Mj8Ny+uCEk39I1QADvIPI7lkndX4qCsEqhkS8= +github.com/go-openapi/swag/conv v0.25.3 h1:PcB18wwfba7MN5BVlBIV+VxvUUeC2kEuCEyJ2/t2X7E= +github.com/go-openapi/swag/conv v0.25.3/go.mod h1:n4Ibfwhn8NJnPXNRhBO5Cqb9ez7alBR40JS4rbASUPU= +github.com/go-openapi/swag/jsonname v0.25.3 h1:U20VKDS74HiPaLV7UZkztpyVOw3JNVsit+w+gTXRj0A= +github.com/go-openapi/swag/jsonname v0.25.3/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.3 h1:kV7wer79KXUM4Ea4tBdAVTU842Rg6tWstX3QbM4fGdw= +github.com/go-openapi/swag/jsonutils v0.25.3/go.mod h1:ILcKqe4HC1VEZmJx51cVuZQ6MF8QvdfXsQfiaCs0z9o= +github.com/go-openapi/swag/loading v0.25.3 h1:Nn65Zlzf4854MY6Ft0JdNrtnHh2bdcS/tXckpSnOb2Y= +github.com/go-openapi/swag/loading v0.25.3/go.mod h1:xajJ5P4Ang+cwM5gKFrHBgkEDWfLcsAKepIuzTmOb/c= +github.com/go-openapi/swag/stringutils v0.25.3 h1:nAmWq1fUTWl/XiaEPwALjp/8BPZJun70iDHRNq/sH6w= +github.com/go-openapi/swag/stringutils v0.25.3/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.3 h1:2w4mEEo7DQt3V4veWMZw0yTPQibiL3ri2fdDV4t2TQc= +github.com/go-openapi/swag/typeutils v0.25.3/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.3 h1:LKTJjCn/W1ZfMec0XDL4Vxh8kyAnv1orH5F2OREDUrg= +github.com/go-openapi/swag/yamlutils v0.25.3/go.mod h1:Y7QN6Wc5DOBXK14/xeo1cQlq0EA0wvLoSv13gDQoCao= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -40,8 +74,12 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= +github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -51,6 +89,8 @@ 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/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -60,6 +100,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -69,6 +111,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -78,10 +122,16 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE= +github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= @@ -89,51 +139,80 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY= +github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= +github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= +github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo= golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= +golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -143,6 +222,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -152,14 +233,20 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -176,16 +263,22 @@ modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw= modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8= modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E= +modernc.org/libc v1.67.1 h1:bFaqOaa5/zbWYJo8aW0tXPX21hXsngG2M7mckCnFSVk= +modernc.org/libc v1.67.1/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8= modernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU= +modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY= +modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= From 27f9095ae153aba91b379ab8eb398aedcada006a Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Tue, 25 Nov 2025 10:19:55 -0800 Subject: [PATCH 10/34] go mod tidy --- go.mod | 4 -- go.sum | 122 +++++++++++++++------------------------------------------ 2 files changed, 31 insertions(+), 95 deletions(-) diff --git a/go.mod b/go.mod index 9e26370..7cdb0e6 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,6 @@ require ( github.com/go-openapi/jsonpointer v0.22.3 // indirect github.com/go-openapi/jsonreference v0.21.3 // indirect github.com/go-openapi/spec v0.22.1 // indirect - github.com/go-openapi/swag v0.25.3 // indirect github.com/go-openapi/swag/conv v0.25.3 // indirect github.com/go-openapi/swag/jsonname v0.25.3 // indirect github.com/go-openapi/swag/jsonutils v0.25.3 // indirect @@ -40,11 +39,9 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/mailru/easyjson v0.9.1 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -68,7 +65,6 @@ require ( golang.org/x/text v0.31.0 // indirect golang.org/x/tools v0.39.0 // indirect google.golang.org/protobuf v1.36.10 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.67.1 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/go.sum b/go.sum index f3b43f7..6ad31ac 100644 --- a/go.sum +++ b/go.sum @@ -4,60 +4,41 @@ github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+Eg github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= -github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= -github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= -github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= -github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= -github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8= github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= -github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= -github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k= github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag v0.25.3 h1:FAa5wJXyDtI7yUztKDfZxDrSx+8WTg31MfCQ9s3PV+s= -github.com/go-openapi/swag v0.25.3/go.mod h1:tX9vI8Mj8Ny+uCEk39I1QADvIPI7lkndX4qCsEqhkS8= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag/conv v0.25.3 h1:PcB18wwfba7MN5BVlBIV+VxvUUeC2kEuCEyJ2/t2X7E= github.com/go-openapi/swag/conv v0.25.3/go.mod h1:n4Ibfwhn8NJnPXNRhBO5Cqb9ez7alBR40JS4rbASUPU= github.com/go-openapi/swag/jsonname v0.25.3 h1:U20VKDS74HiPaLV7UZkztpyVOw3JNVsit+w+gTXRj0A= github.com/go-openapi/swag/jsonname v0.25.3/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= github.com/go-openapi/swag/jsonutils v0.25.3 h1:kV7wer79KXUM4Ea4tBdAVTU842Rg6tWstX3QbM4fGdw= github.com/go-openapi/swag/jsonutils v0.25.3/go.mod h1:ILcKqe4HC1VEZmJx51cVuZQ6MF8QvdfXsQfiaCs0z9o= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.3 h1:/i3E9hBujtXfHy91rjtwJ7Fgv5TuDHgnSrYjhFxwxOw= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.3/go.mod h1:8kYfCR2rHyOj25HVvxL5Nm8wkfzggddgjZm6RgjT8Ao= github.com/go-openapi/swag/loading v0.25.3 h1:Nn65Zlzf4854MY6Ft0JdNrtnHh2bdcS/tXckpSnOb2Y= github.com/go-openapi/swag/loading v0.25.3/go.mod h1:xajJ5P4Ang+cwM5gKFrHBgkEDWfLcsAKepIuzTmOb/c= github.com/go-openapi/swag/stringutils v0.25.3 h1:nAmWq1fUTWl/XiaEPwALjp/8BPZJun70iDHRNq/sH6w= @@ -66,53 +47,46 @@ github.com/go-openapi/swag/typeutils v0.25.3 h1:2w4mEEo7DQt3V4veWMZw0yTPQibiL3ri github.com/go-openapi/swag/typeutils v0.25.3/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= github.com/go-openapi/swag/yamlutils v0.25.3 h1:LKTJjCn/W1ZfMec0XDL4Vxh8kyAnv1orH5F2OREDUrg= github.com/go-openapi/swag/yamlutils v0.25.3/go.mod h1:Y7QN6Wc5DOBXK14/xeo1cQlq0EA0wvLoSv13gDQoCao= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= -github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= -github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 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/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -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/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= -github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -120,8 +94,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w 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/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -134,15 +106,11 @@ github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3Adq github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -151,27 +119,20 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= -github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= -github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY= github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= -github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= -github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -179,38 +140,26 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= -golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo= -golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -220,8 +169,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -231,20 +178,16 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= -golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -253,34 +196,31 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= -modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo= -modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo= -modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= -modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw= -modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8= -modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= +modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= +modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= +modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= +modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/libc v1.67.1 h1:bFaqOaa5/zbWYJo8aW0tXPX21hXsngG2M7mckCnFSVk= modernc.org/libc v1.67.1/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= -modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8= -modernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU= modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY= modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= From e72c7f920e9a09f8d98efccafb7e63fb9a2c6292 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Tue, 25 Nov 2025 12:09:15 -0800 Subject: [PATCH 11/34] rm problematic `layout go` line in .envrc --- .envrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.envrc b/.envrc index fdbe2f7..41c7c29 100644 --- a/.envrc +++ b/.envrc @@ -5,6 +5,5 @@ if has nix; then fi fi use flake -layout go PATH_add bin dotenv ./.env From fd536fb2ef4dc6e3e587c432676b3993563d5c8c Mon Sep 17 00:00:00 2001 From: josh Date: Wed, 3 Dec 2025 13:43:30 -0800 Subject: [PATCH 12/34] fix variable shadowing bug --- internal/api/routes/v1.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/api/routes/v1.go b/internal/api/routes/v1.go index c465041..8014ca4 100644 --- a/internal/api/routes/v1.go +++ b/internal/api/routes/v1.go @@ -25,12 +25,16 @@ func SetupV1(router *gin.Engine, eventService services.EventsServicer, } var botSession *discordgo.Session + var err error if botToken != "" { - botSession, err := discordgo.New("Bot " + botToken) + botSession, err = discordgo.New("Bot " + botToken) if err != nil { log.Fatalf("%v", err) } - botSession.Open() + err = botSession.Open() + if err != nil { + log.Fatalf("Failed to open bot session: %v", err) + } defer botSession.Close() } From a857243935a62c07ffe4a2d7678e4b8715b2f403 Mon Sep 17 00:00:00 2001 From: josh Date: Wed, 3 Dec 2025 13:43:38 -0800 Subject: [PATCH 13/34] add missing return --- internal/api/middleware/oauth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/api/middleware/oauth.go b/internal/api/middleware/oauth.go index 42c17b7..f6f0029 100644 --- a/internal/api/middleware/oauth.go +++ b/internal/api/middleware/oauth.go @@ -78,6 +78,7 @@ func DiscordAuth(bot *discordgo.Session, requiredRole string) gin.HandlerFunc { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": "You are not a member of the Discord server", }) + return } roleCache.Store(authHeader, cacheEntry{ From 9ab90a0470275f78abbe4287070897715d8adeb7 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Tue, 25 Nov 2025 19:21:31 -0800 Subject: [PATCH 14/34] scaffolding for wrapper func --- utils/requests/request_with_auth.go | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 utils/requests/request_with_auth.go diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go new file mode 100644 index 0000000..9d4675a --- /dev/null +++ b/utils/requests/request_with_auth.go @@ -0,0 +1,32 @@ +package requests + +import ( + "fmt" + "io" + "net/http" + + "github.com/acmcsufoss/api.acmcsuf.com/internal/api/config" +) + +func NewRequestWithAuth(method, url string, body io.Reader) (*http.Request, error) { + cfg := config.Load() + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + + var token string + if cfg.Env == "development" { + token = "dev-token" + } else { + // TODO + token = "asdf" + } + + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + req.Header.Set("Content-Type", "application/json") + + return req, nil +} + +// func http.NewRequest(method string, url string, body io.Reader) (*http.Request, error) From 633f22689892c96d95214f77bf8a5c3d7e8ccec1 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Tue, 25 Nov 2025 19:36:16 -0800 Subject: [PATCH 15/34] construct auth token --- .env.example | 2 ++ utils/requests/request_with_auth.go | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index cacfd4f..8d59ba2 100644 --- a/.env.example +++ b/.env.example @@ -5,3 +5,5 @@ TRUSTED_PROXIES="" ALLOWED_ORIGINS="" DISCORD_BOT_TOKEN="" GUILD_ID="" +DISCORD_CLIENT_ID="" +DISCORD_CLIENT_SECRET="" diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index 9d4675a..f146190 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -1,9 +1,11 @@ package requests import ( + "errors" "fmt" "io" "net/http" + "os" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/config" ) @@ -20,7 +22,12 @@ func NewRequestWithAuth(method, url string, body io.Reader) (*http.Request, erro token = "dev-token" } else { // TODO - token = "asdf" + clientID := os.Getenv("DISCORD_CLIENT_ID") + if clientID == "" { + return nil, errors.New("DISCORD_CLIENT_ID is unset") + } + token = fmt.Sprintf(`https://discord.com/oauth2/authorize?client_id=%s +&response_type=code&redirect_uri=http%3A%2F%2Flocalhost&scope=identify`, clientID) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) From afb9714cd758c5c360af2b7ece009b39c5f5d979 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Wed, 26 Nov 2025 09:17:31 -0800 Subject: [PATCH 16/34] flesh out func --- go.mod | 1 + go.sum | 2 + utils/requests/request_with_auth.go | 85 +++++++++++++++++++++++++++-- 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 7cdb0e6..551585e 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.24.0 require ( github.com/bwmarrin/discordgo v0.29.0 + github.com/cli/browser v1.3.0 github.com/gin-gonic/gin v1.11.0 github.com/spf13/cobra v1.10.1 github.com/swaggo/files v1.0.1 diff --git a/go.sum b/go.sum index 6ad31ac..c8e5105 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPII github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= +github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index f146190..2756acd 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -1,15 +1,28 @@ package requests import ( + "encoding/json" "errors" "fmt" "io" "net/http" + "net/url" "os" + "strings" + + "github.com/cli/browser" "github.com/acmcsufoss/api.acmcsuf.com/internal/api/config" ) +type TokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + Scope string `json:"scope"` +} + func NewRequestWithAuth(method, url string, body io.Reader) (*http.Request, error) { cfg := config.Load() req, err := http.NewRequest(method, url, body) @@ -21,13 +34,77 @@ func NewRequestWithAuth(method, url string, body io.Reader) (*http.Request, erro if cfg.Env == "development" { token = "dev-token" } else { - // TODO clientID := os.Getenv("DISCORD_CLIENT_ID") if clientID == "" { return nil, errors.New("DISCORD_CLIENT_ID is unset") } - token = fmt.Sprintf(`https://discord.com/oauth2/authorize?client_id=%s -&response_type=code&redirect_uri=http%3A%2F%2Flocalhost&scope=identify`, clientID) + // TODO: check that this port isn't being used first + const redirectURI = "http://localhost:8888" + const scope = "identify" + params := url.Values{} + params.Add("client_id", clientID) + params.Add("redirect_uri", redirectURI) + params.Add("scope", scope) + params.Add("response_type", "code") + + authURL := "https://discord.com/oauth2/authorize" + params.Encode() + fmt.Println("Opening browser to:", authURL) + browser.OpenURL(authURL) + // TODO: "Press enter to open the following link in your browser" + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + code := r.URL.Query().Get("code") + if code != "" { + fmt.Fprintf(w, "Got code! You can close this window.\n\nCode: %s", code) + fmt.Printf("Success! Auth code: %s\n", code) + + fmt.Println("Exchanging code for access token...") + data := url.Values{} + data.set("client_id", clientID) + clientSecret := os.Getenv("DISCORD_CLIENT_SECRET") + if clientSecret == "" { + fmt.Fprintf(os.Stderr, "DISCORD_CLIENT_SECRET is unset") + return + } + data.set("client_secret", clientSecret) + data.set("grant_type", "authorization_code") + data.set("code", code) + data.set("redirectURI", redirectURI) + + req, _ := http.NewRequest("POST", "https://discord.com/api/oauth2/token", + strings.NewReader(data.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + fmt.Printf("Error exchanging token: %v\n", err) + return + } + + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + + var tokenResp TokenResponse + if err := json.Unmarshal(body, &tokenResp); err != nil { + fmt.Printf("Error parsing JSON: %v\nResponse Body: %s\n", err, string(body)) + return + } + + if tokenResp.AccessToken != "" { + token = tokenResp.AccessToken + } else { + fmt.Fprintf(os.Stderr, "Error: Failed to get token. Discord said:\n%s\n", string(body)) + } + + go func() { + return + }() + } else { + fmt.Fprintln(os.Stderr, "Error: no code in URL") + } + }) + + http.ListenAndServe(":8888", nil) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) @@ -35,5 +112,3 @@ func NewRequestWithAuth(method, url string, body io.Reader) (*http.Request, erro return req, nil } - -// func http.NewRequest(method string, url string, body io.Reader) (*http.Request, error) From 93ef12ff06c3dbb57f4372339c56e27831115bb5 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Wed, 26 Nov 2025 09:41:40 -0800 Subject: [PATCH 17/34] fix shit up --- utils/requests/request_with_auth.go | 122 +++++++++++++++------------- 1 file changed, 67 insertions(+), 55 deletions(-) diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index 2756acd..166f334 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -1,6 +1,7 @@ package requests import ( + "context" "encoding/json" "errors" "fmt" @@ -8,7 +9,6 @@ import ( "net/http" "net/url" "os" - "strings" "github.com/cli/browser" @@ -34,13 +34,68 @@ func NewRequestWithAuth(method, url string, body io.Reader) (*http.Request, erro if cfg.Env == "development" { token = "dev-token" } else { + // Production OAuth2 Flow clientID := os.Getenv("DISCORD_CLIENT_ID") if clientID == "" { return nil, errors.New("DISCORD_CLIENT_ID is unset") } + clientSecret := os.Getenv("DISCORD_CLIENT_SECRET") + if clientSecret == "" { + return nil, errors.New("DISCORD_CLIENT_SECRET is unset") + } // TODO: check that this port isn't being used first const redirectURI = "http://localhost:8888" const scope = "identify" + + tokenChan := make(chan string) + errChan := make(chan error) + mux := http.NewServeMux() + server := &http.Server{Addr: ":8888", Handler: mux} + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + code := r.URL.Query().Get("code") + if code == "" { + fmt.Fprintln(w, "No code found") + return + } + fmt.Fprintf(w, "Got code! You can close this window.") + + data := url.Values{} + data.set("client_id", clientID) + data.set("client_secret", clientSecret) + data.set("grant_type", "authorization_code") + data.set("code", code) + data.set("redirect_uri", redirectURI) + + resp, err := http.PostForm("https://discord.com/api/oauth2/token", data) + if err != nil { + errChan <- fmt.Errorf("failed to exchange token: %w", err) + return + } + defer resp.Body.Close() + + respBody, _ := io.ReadAll(resp.Body) + + if resp.StatusCode != http.StatusOK { + errChan <- fmt.Errorf("Discord API error: %s", string(respBody)) + } + + var tokenResp TokenResponse + if err := json.Unmarshal(respBody, &tokenResp); err != nil { + errChan <- fmt.Errorf("JSON parse error: %w", err) + } + + tokenChan <- tokenResp.AccessToken + }) + + // So server doesn't block + go func() { + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + errChan <- err + } + }() + + // Human needs to open browser to get the token + // A web client can do this more gracefully, but we have to do this since we're on a CLI params := url.Values{} params.Add("client_id", clientID) params.Add("redirect_uri", redirectURI) @@ -49,62 +104,19 @@ func NewRequestWithAuth(method, url string, body io.Reader) (*http.Request, erro authURL := "https://discord.com/oauth2/authorize" + params.Encode() fmt.Println("Opening browser to:", authURL) - browser.OpenURL(authURL) // TODO: "Press enter to open the following link in your browser" - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - code := r.URL.Query().Get("code") - if code != "" { - fmt.Fprintf(w, "Got code! You can close this window.\n\nCode: %s", code) - fmt.Printf("Success! Auth code: %s\n", code) - - fmt.Println("Exchanging code for access token...") - data := url.Values{} - data.set("client_id", clientID) - clientSecret := os.Getenv("DISCORD_CLIENT_SECRET") - if clientSecret == "" { - fmt.Fprintf(os.Stderr, "DISCORD_CLIENT_SECRET is unset") - return - } - data.set("client_secret", clientSecret) - data.set("grant_type", "authorization_code") - data.set("code", code) - data.set("redirectURI", redirectURI) - - req, _ := http.NewRequest("POST", "https://discord.com/api/oauth2/token", - strings.NewReader(data.Encode())) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - fmt.Printf("Error exchanging token: %v\n", err) - return - } - - defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) - - var tokenResp TokenResponse - if err := json.Unmarshal(body, &tokenResp); err != nil { - fmt.Printf("Error parsing JSON: %v\nResponse Body: %s\n", err, string(body)) - return - } - - if tokenResp.AccessToken != "" { - token = tokenResp.AccessToken - } else { - fmt.Fprintf(os.Stderr, "Error: Failed to get token. Discord said:\n%s\n", string(body)) - } - - go func() { - return - }() - } else { - fmt.Fprintln(os.Stderr, "Error: no code in URL") - } - }) + browser.OpenURL(authURL) + + // Block until we get a token or err + select { + case t := <-tokenChan: + token = t + case e := <-errChan: + server.Shutdown(context.Background()) + return nil, e + } - http.ListenAndServe(":8888", nil) + server.Shutdown(context.Background()) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) From 87cbfdbe5517898ba117cfd932de7027eb9e303b Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Wed, 26 Nov 2025 09:48:48 -0800 Subject: [PATCH 18/34] nix flake update --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index cbf2af7..dd5f187 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1750838302, - "narHash": "sha256-aVkL3/yu50oQzi2YuKo0ceiCypVZpZXYd2P2p1FMJM4=", + "lastModified": 1763948260, + "narHash": "sha256-dY9qLD0H0zOUgU3vWacPY6Qc421BeQAfm8kBuBtPVE0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7284e2decc982b81a296ab35aa46e804baaa1cfe", + "rev": "1c8ba8d3f7634acac4a2094eef7c32ad9106532c", "type": "github" }, "original": { From e779353e93b0a998e246256841620545051d2400 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Wed, 26 Nov 2025 09:51:30 -0800 Subject: [PATCH 19/34] use correct method name --- utils/requests/request_with_auth.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index 166f334..f6c60aa 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -60,11 +60,11 @@ func NewRequestWithAuth(method, url string, body io.Reader) (*http.Request, erro fmt.Fprintf(w, "Got code! You can close this window.") data := url.Values{} - data.set("client_id", clientID) - data.set("client_secret", clientSecret) - data.set("grant_type", "authorization_code") - data.set("code", code) - data.set("redirect_uri", redirectURI) + data.Set("client_id", clientID) + data.Set("client_secret", clientSecret) + data.Set("grant_type", "authorization_code") + data.Set("code", code) + data.Set("redirect_uri", redirectURI) resp, err := http.PostForm("https://discord.com/api/oauth2/token", data) if err != nil { From 3390164ee0cbd426f55518f017b4d77d3d8a8cd5 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Wed, 26 Nov 2025 10:03:36 -0800 Subject: [PATCH 20/34] fix odd error --- utils/requests/request_with_auth.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index f6c60aa..d790f53 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -23,9 +23,9 @@ type TokenResponse struct { Scope string `json:"scope"` } -func NewRequestWithAuth(method, url string, body io.Reader) (*http.Request, error) { +func NewRequestWithAuth(method, targetURL string, body io.Reader) (*http.Request, error) { cfg := config.Load() - req, err := http.NewRequest(method, url, body) + req, err := http.NewRequest(method, targetURL, body) if err != nil { return nil, err } @@ -60,7 +60,7 @@ func NewRequestWithAuth(method, url string, body io.Reader) (*http.Request, erro fmt.Fprintf(w, "Got code! You can close this window.") data := url.Values{} - data.Set("client_id", clientID) + // data.Set("client_id", clientID) data.Set("client_secret", clientSecret) data.Set("grant_type", "authorization_code") data.Set("code", code) From cdccc719ff9f6e9a9e2111671986765fc6e7bc1b Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Wed, 26 Nov 2025 10:04:16 -0800 Subject: [PATCH 21/34] fix staticcheck warning --- utils/requests/request_with_auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index d790f53..9a639c2 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -76,7 +76,7 @@ func NewRequestWithAuth(method, targetURL string, body io.Reader) (*http.Request respBody, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK { - errChan <- fmt.Errorf("Discord API error: %s", string(respBody)) + errChan <- fmt.Errorf("discord API error: %s", string(respBody)) } var tokenResp TokenResponse From 8bba91a080ba539a13252dbd1648af98793cf18b Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Wed, 26 Nov 2025 10:29:22 -0800 Subject: [PATCH 22/34] works in dev mode --- internal/cli/events/get.go | 70 +++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/internal/cli/events/get.go b/internal/cli/events/get.go index 5ac110e..e70cc93 100644 --- a/internal/cli/events/get.go +++ b/internal/cli/events/get.go @@ -1,13 +1,16 @@ package events import ( - "encoding/json" + // "encoding/json" "fmt" + "io" "net/http" "net/url" + "os" - "github.com/acmcsufoss/api.acmcsuf.com/internal/db/models" + // "github.com/acmcsufoss/api.acmcsuf.com/internal/db/models" "github.com/acmcsufoss/api.acmcsuf.com/utils" + "github.com/acmcsufoss/api.acmcsuf.com/utils/requests" "github.com/spf13/cobra" ) @@ -60,42 +63,53 @@ func getEvents(id string, port string, host string) { } // ----- Get ----- - response, err := http.Get(getURL.String()) + req, err := requests.NewRequestWithAuth("GET", getURL.String(), nil) if err != nil { fmt.Println("Error getting the request:", err) return } - if response == nil { + client := http.Client{} + resp, err := client.Do(req) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: couldn't make GET request: %v", err) + } + defer resp.Body.Close() + + if req == nil { fmt.Println("no response received") return } - defer response.Body.Close() - // ----- Read Response Information ----- - fmt.Println("Response status:", response.Status) - - if id == "" { - var getPayload []models.CreateEventParams - err = json.NewDecoder(response.Body).Decode(&getPayload) - if err != nil { - fmt.Println("Failed to read response body without id:", err) - return - } - - for i := range getPayload { - utils.PrintStruct(getPayload[i]) - } - } else { - var getPayload models.CreateEventParams - err = json.NewDecoder(response.Body).Decode(&getPayload) - if err != nil { - fmt.Println("Failed to read response body with id:", err) - return - } - - utils.PrintStruct(getPayload) + fmt.Println("Response status:", resp.Status) + + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: couldn't read response body: %v", err) } + fmt.Println(string(body)) + // if id == "" { + // var getPayload []models.CreateEventParams + // err = json.NewDecoder(req.Body).Decode(&getPayload) + // if err != nil { + // fmt.Println("Failed to read response body without id:", err) + // return + // } + // + // for i := range getPayload { + // utils.PrintStruct(getPayload[i]) + // } + // } else { + // var getPayload models.CreateEventParams + // err = json.NewDecoder(req.Body).Decode(&getPayload) + // if err != nil { + // fmt.Println("Failed to read response body with id:", err) + // return + // } + // + // utils.PrintStruct(getPayload) + // } + } From b921679e101bd35c393448b4ad14da3fe0ecfb2c Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Wed, 26 Nov 2025 10:33:08 -0800 Subject: [PATCH 23/34] simplify events get --- go.mod | 1 + go.sum | 2 ++ internal/cli/events/get.go | 28 +++------------------------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 551585e..1ccc6d7 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.1 github.com/swaggo/swag v1.16.6 + github.com/tidwall/pretty v1.2.1 modernc.org/sqlite v1.40.1 ) diff --git a/go.sum b/go.sum index c8e5105..c2c0fb9 100644 --- a/go.sum +++ b/go.sum @@ -133,6 +133,8 @@ github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= diff --git a/internal/cli/events/get.go b/internal/cli/events/get.go index e70cc93..f4e168a 100644 --- a/internal/cli/events/get.go +++ b/internal/cli/events/get.go @@ -1,17 +1,16 @@ package events import ( - // "encoding/json" "fmt" "io" "net/http" "net/url" "os" - // "github.com/acmcsufoss/api.acmcsuf.com/internal/db/models" "github.com/acmcsufoss/api.acmcsuf.com/utils" "github.com/acmcsufoss/api.acmcsuf.com/utils/requests" "github.com/spf13/cobra" + "github.com/tidwall/pretty" ) var GetEvent = &cobra.Command{ @@ -89,27 +88,6 @@ func getEvents(id string, port string, host string) { fmt.Fprintf(os.Stderr, "Error: couldn't read response body: %v", err) } - fmt.Println(string(body)) - // if id == "" { - // var getPayload []models.CreateEventParams - // err = json.NewDecoder(req.Body).Decode(&getPayload) - // if err != nil { - // fmt.Println("Failed to read response body without id:", err) - // return - // } - // - // for i := range getPayload { - // utils.PrintStruct(getPayload[i]) - // } - // } else { - // var getPayload models.CreateEventParams - // err = json.NewDecoder(req.Body).Decode(&getPayload) - // if err != nil { - // fmt.Println("Failed to read response body with id:", err) - // return - // } - // - // utils.PrintStruct(getPayload) - // } - + prettyJSON := pretty.Pretty(body) + fmt.Println(string(prettyJSON)) } From 684e532a441f9a49e2fb3f3d1f7f5d180dd71f8f Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Wed, 26 Nov 2025 10:36:01 -0800 Subject: [PATCH 24/34] use colors in json output --- internal/cli/events/get.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/cli/events/get.go b/internal/cli/events/get.go index f4e168a..b35162d 100644 --- a/internal/cli/events/get.go +++ b/internal/cli/events/get.go @@ -89,5 +89,6 @@ func getEvents(id string, port string, host string) { } prettyJSON := pretty.Pretty(body) - fmt.Println(string(prettyJSON)) + colorfulJSON := pretty.Color(prettyJSON, nil) + fmt.Println(string(colorfulJSON)) } From 0d77960b599c40849058ed46f4d88e81d1e31fea Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Wed, 26 Nov 2025 10:45:47 -0800 Subject: [PATCH 25/34] uncomment clientID --- utils/requests/request_with_auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index 9a639c2..a50694e 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -60,7 +60,7 @@ func NewRequestWithAuth(method, targetURL string, body io.Reader) (*http.Request fmt.Fprintf(w, "Got code! You can close this window.") data := url.Values{} - // data.Set("client_id", clientID) + data.Set("client_id", clientID) data.Set("client_secret", clientSecret) data.Set("grant_type", "authorization_code") data.Set("code", code) From 56c41d27463674e78c2ed1cb612b1127251eee77 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Sat, 29 Nov 2025 11:08:19 -0800 Subject: [PATCH 26/34] fix url encoding --- utils/requests/request_with_auth.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index a50694e..7958a09 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -102,10 +102,12 @@ func NewRequestWithAuth(method, targetURL string, body io.Reader) (*http.Request params.Add("scope", scope) params.Add("response_type", "code") - authURL := "https://discord.com/oauth2/authorize" + params.Encode() - fmt.Println("Opening browser to:", authURL) + baseURL := "https://discord.com/oauth2/authorize" + u, _ := url.Parse(baseURL) + u.RawQuery = params.Encode() + fmt.Println("Opening browser to:", u.String()) // TODO: "Press enter to open the following link in your browser" - browser.OpenURL(authURL) + browser.OpenURL(u.String()) // Block until we get a token or err select { From b28ba7be614e293426b0b17394d31170fdb42745 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Sat, 29 Nov 2025 11:16:07 -0800 Subject: [PATCH 27/34] add GIN_MODE option to .env.example --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index 8d59ba2..18145f3 100644 --- a/.env.example +++ b/.env.example @@ -7,3 +7,4 @@ DISCORD_BOT_TOKEN="" GUILD_ID="" DISCORD_CLIENT_ID="" DISCORD_CLIENT_SECRET="" +GIN_MODE="debug" From 6c6ed9a4be897e37e06619c5285001b78a1d2c98 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Sat, 29 Nov 2025 11:42:38 -0800 Subject: [PATCH 28/34] add docs for env var configuration --- developer-docs/env-vars.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 developer-docs/env-vars.md diff --git a/developer-docs/env-vars.md b/developer-docs/env-vars.md new file mode 100644 index 0000000..a3a176b --- /dev/null +++ b/developer-docs/env-vars.md @@ -0,0 +1,26 @@ +# Configuration via Environment Variables + +See [config.go](../internal/api/config/config.go) for more information/default +values. + +- `GIN_MODE`: One of `debug`, `release`, or `test`. This affects what and how +many HTTP middleware logs appear. Separate from manual logging. + - Default: `debug` (change in production) +- `ENV`: One of `production` or `development`. While in development mode, +authentication with Discord OAuth2 is bypassed. You may need to change this value to +`production` if testing Auth. + - Default: `development` (change in production) +- `PORT`: Port to run on. + - Default: `8080` +- `DATABASE_URL`: The path to the SQLite database file. Takes the format +`file:path/to/database.db`. Can further configure using query params. Example: +`mode=rwc` means read-write and create if doesn't exist. `cache=shared` is more +memory efficient than the default of `cache=private`. + - Default: `file:dev.db?cache=shared&mode=rwc` +- `TRUSTED_PROXIES`: Used in [server.go](../internal/api/server.go) in Gin's +`SetTrustedProxies` setting. Security mechanism to prevent IP spoofing when the +API sits behind a reverse proxy in production. + - Default: `127.0.0.1/32` (change in production) +- `ALLOWED_ORIGINS`: To be used with CORS middleware. Controls which web origins + are allowed to make cross-origin requests to the API. + - Default: `*` (change in production) From 4a1c36d2085c39fd925f2f94e364e5829a007b0a Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Thu, 4 Dec 2025 19:43:39 -0800 Subject: [PATCH 29/34] hardcoded test roles --- internal/api/middleware/oauth.go | 4 ++-- internal/api/routes/v1.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/api/middleware/oauth.go b/internal/api/middleware/oauth.go index f6f0029..b906430 100644 --- a/internal/api/middleware/oauth.go +++ b/internal/api/middleware/oauth.go @@ -12,8 +12,8 @@ import ( ) var RoleMap = map[string]string{ - "123": "Board", - "456": "President", + "1445971950584205554": "Board", + "456": "President", } var roleCache = sync.Map{} diff --git a/internal/api/routes/v1.go b/internal/api/routes/v1.go index 8014ca4..4a36af1 100644 --- a/internal/api/routes/v1.go +++ b/internal/api/routes/v1.go @@ -41,7 +41,7 @@ func SetupV1(router *gin.Engine, eventService services.EventsServicer, // Version 1 routes v1 := router.Group("/v1") // All v1 routes are protected - v1.Use(middleware.DiscordAuth(botSession, "board")) + v1.Use(middleware.DiscordAuth(botSession, "Board")) { events := v1.Group("/events") { From 9b123a459d3c76ada6c74506f566f087834be864 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Thu, 4 Dec 2025 19:51:49 -0800 Subject: [PATCH 30/34] add note --- utils/requests/request_with_auth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index 7958a09..e421268 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -50,6 +50,7 @@ func NewRequestWithAuth(method, targetURL string, body io.Reader) (*http.Request tokenChan := make(chan string) errChan := make(chan error) mux := http.NewServeMux() + // NOTE: port :8888 is hardcoded here. Maybe we can we use `:0` and `server.Addr` to find the port it was assigned? server := &http.Server{Addr: ":8888", Handler: mux} mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") From 21d90f7963a0ccc9646725846da1b61169f4d160 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Thu, 4 Dec 2025 20:26:28 -0800 Subject: [PATCH 31/34] add persistence helper functions --- utils/requests/request_with_auth.go | 67 +++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index e421268..0ace350 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -9,6 +9,8 @@ import ( "net/http" "net/url" "os" + "path/filepath" + "time" "github.com/cli/browser" @@ -23,6 +25,12 @@ type TokenResponse struct { Scope string `json:"scope"` } +type StoredToken struct { + AccessToken string `json:"access_token` + RefreshToken string `json:"refresh_token` + Expiry time.Time `json:expiry` +} + func NewRequestWithAuth(method, targetURL string, body io.Reader) (*http.Request, error) { cfg := config.Load() req, err := http.NewRequest(method, targetURL, body) @@ -127,3 +135,62 @@ func NewRequestWithAuth(method, targetURL string, body io.Reader) (*http.Request return req, nil } + +// ============== persistence helper functions ============== + +func getTokenPath() (string, error) { + configDir, err := os.UserConfigDir() + if err != nil { + return "", err + } + // ''~/.config/acmcsuf-cli/token.json' on Unix systems + appDir := filepath.Join(configDir, "acmcsuf-cli") + if err := os.MkdirAll(appDir, 0700); err != nil { + return "", err + } + return filepath.Join(appDir, "token.json"), nil +} + +func saveToken(resp TokenResponse) error { + path, err := getTokenPath() + if err != nil { + return err + } + + stored := StoredToken{ + AccessToken: resp.AccessToken, + RefreshToken: resp.RefreshToken, + Expiry: time.Now().Add(time.Duration(resp.ExpiresIn-10) * time.Second), + } + + file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer file.Close() + + return json.NewEncoder(file).Encode(stored) +} + +func loadToken() (string, error) { + path, err := getTokenPath() + if err != nil { + return "", err + } + + file, err := os.Open(path) + if err != nil { + return "", err + } + + var stored StoredToken + if err := json.NewDecoder(file).Decode(&stored); err != nil { + return "", err + } + + if time.Now().After(stored.Expiry) { + return "", errors.New("token expired") + } + + return stored.AccessToken, nil +} From 0fed4fff9c214227ddb32adff01e34cedc74d4b8 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Thu, 4 Dec 2025 20:36:07 -0800 Subject: [PATCH 32/34] add logic to save and load token --- utils/requests/request_with_auth.go | 174 +++++++++++++++------------- 1 file changed, 92 insertions(+), 82 deletions(-) diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index 0ace350..97295da 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -26,9 +26,9 @@ type TokenResponse struct { } type StoredToken struct { - AccessToken string `json:"access_token` - RefreshToken string `json:"refresh_token` - Expiry time.Time `json:expiry` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + Expiry time.Time `json:"expiry"` } func NewRequestWithAuth(method, targetURL string, body io.Reader) (*http.Request, error) { @@ -43,91 +43,101 @@ func NewRequestWithAuth(method, targetURL string, body io.Reader) (*http.Request token = "dev-token" } else { // Production OAuth2 Flow - clientID := os.Getenv("DISCORD_CLIENT_ID") - if clientID == "" { - return nil, errors.New("DISCORD_CLIENT_ID is unset") - } - clientSecret := os.Getenv("DISCORD_CLIENT_SECRET") - if clientSecret == "" { - return nil, errors.New("DISCORD_CLIENT_SECRET is unset") - } - // TODO: check that this port isn't being used first - const redirectURI = "http://localhost:8888" - const scope = "identify" - - tokenChan := make(chan string) - errChan := make(chan error) - mux := http.NewServeMux() - // NOTE: port :8888 is hardcoded here. Maybe we can we use `:0` and `server.Addr` to find the port it was assigned? - server := &http.Server{Addr: ":8888", Handler: mux} - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - code := r.URL.Query().Get("code") - if code == "" { - fmt.Fprintln(w, "No code found") - return + if storedToken, err := loadToken(); err == nil { + token = storedToken + } else { + fmt.Println("No valid session found. Authenticating...") + + clientID := os.Getenv("DISCORD_CLIENT_ID") + if clientID == "" { + return nil, errors.New("DISCORD_CLIENT_ID is unset") } - fmt.Fprintf(w, "Got code! You can close this window.") - - data := url.Values{} - data.Set("client_id", clientID) - data.Set("client_secret", clientSecret) - data.Set("grant_type", "authorization_code") - data.Set("code", code) - data.Set("redirect_uri", redirectURI) - - resp, err := http.PostForm("https://discord.com/api/oauth2/token", data) - if err != nil { - errChan <- fmt.Errorf("failed to exchange token: %w", err) - return + clientSecret := os.Getenv("DISCORD_CLIENT_SECRET") + if clientSecret == "" { + return nil, errors.New("DISCORD_CLIENT_SECRET is unset") } - defer resp.Body.Close() - - respBody, _ := io.ReadAll(resp.Body) - - if resp.StatusCode != http.StatusOK { - errChan <- fmt.Errorf("discord API error: %s", string(respBody)) + // TODO: check that this port isn't being used first + const redirectURI = "http://localhost:8888" + const scope = "identify" + + tokenChan := make(chan string) + errChan := make(chan error) + mux := http.NewServeMux() + // NOTE: port :8888 is hardcoded here. Maybe we can we use `:0` and `server.Addr` to find the port it was assigned? + server := &http.Server{Addr: ":8888", Handler: mux} + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + code := r.URL.Query().Get("code") + if code == "" { + fmt.Fprintln(w, "No code found") + return + } + fmt.Fprintf(w, "Got code! You can close this window.") + + data := url.Values{} + data.Set("client_id", clientID) + data.Set("client_secret", clientSecret) + data.Set("grant_type", "authorization_code") + data.Set("code", code) + data.Set("redirect_uri", redirectURI) + + resp, err := http.PostForm("https://discord.com/api/oauth2/token", data) + if err != nil { + errChan <- fmt.Errorf("failed to exchange token: %w", err) + return + } + defer resp.Body.Close() + + respBody, _ := io.ReadAll(resp.Body) + + if resp.StatusCode != http.StatusOK { + errChan <- fmt.Errorf("discord API error: %s", string(respBody)) + } + + var tokenResp TokenResponse + if err := json.Unmarshal(respBody, &tokenResp); err != nil { + errChan <- fmt.Errorf("JSON parse error: %w", err) + } + + // save token to disk HERE >:) + if err := saveToken(tokenResp); err != nil { + fmt.Printf("Warning: failed to save auth token: %v\n", err) + } + tokenChan <- tokenResp.AccessToken + }) + + // So server doesn't block + go func() { + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + errChan <- err + } + }() + + // Human needs to open browser to get the token + // A web client can do this more gracefully, but we have to do this since we're on a CLI + params := url.Values{} + params.Add("client_id", clientID) + params.Add("redirect_uri", redirectURI) + params.Add("scope", scope) + params.Add("response_type", "code") + + baseURL := "https://discord.com/oauth2/authorize" + u, _ := url.Parse(baseURL) + u.RawQuery = params.Encode() + fmt.Println("Opening browser to:", u.String()) + // TODO: "Press enter to open the following link in your browser" + browser.OpenURL(u.String()) + + // Block until we get a token or err + select { + case t := <-tokenChan: + token = t + case e := <-errChan: + server.Shutdown(context.Background()) + return nil, e } - var tokenResp TokenResponse - if err := json.Unmarshal(respBody, &tokenResp); err != nil { - errChan <- fmt.Errorf("JSON parse error: %w", err) - } - - tokenChan <- tokenResp.AccessToken - }) - - // So server doesn't block - go func() { - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - errChan <- err - } - }() - - // Human needs to open browser to get the token - // A web client can do this more gracefully, but we have to do this since we're on a CLI - params := url.Values{} - params.Add("client_id", clientID) - params.Add("redirect_uri", redirectURI) - params.Add("scope", scope) - params.Add("response_type", "code") - - baseURL := "https://discord.com/oauth2/authorize" - u, _ := url.Parse(baseURL) - u.RawQuery = params.Encode() - fmt.Println("Opening browser to:", u.String()) - // TODO: "Press enter to open the following link in your browser" - browser.OpenURL(u.String()) - - // Block until we get a token or err - select { - case t := <-tokenChan: - token = t - case e := <-errChan: server.Shutdown(context.Background()) - return nil, e } - - server.Shutdown(context.Background()) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) From b2f8a498ab72b5e2016c42f93442cf342a6ef90e Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Fri, 5 Dec 2025 12:33:42 -0800 Subject: [PATCH 33/34] use more obscure port for redirect uri --- utils/requests/request_with_auth.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/utils/requests/request_with_auth.go b/utils/requests/request_with_auth.go index 97295da..7e9e474 100644 --- a/utils/requests/request_with_auth.go +++ b/utils/requests/request_with_auth.go @@ -31,6 +31,11 @@ type StoredToken struct { Expiry time.Time `json:"expiry"` } +// NOTE: As far as I can tell this port must be hardcoded as it needs to match the port +// specified in the discord developer portal. It's chosen arbitrarily and is hopefully random +// enough to not run into port conflicts. +const redirectAddr = ":61234" + func NewRequestWithAuth(method, targetURL string, body io.Reader) (*http.Request, error) { cfg := config.Load() req, err := http.NewRequest(method, targetURL, body) @@ -57,14 +62,14 @@ func NewRequestWithAuth(method, targetURL string, body io.Reader) (*http.Request return nil, errors.New("DISCORD_CLIENT_SECRET is unset") } // TODO: check that this port isn't being used first - const redirectURI = "http://localhost:8888" + redirectURI := fmt.Sprintf("http://localhost%s", redirectAddr) const scope = "identify" tokenChan := make(chan string) errChan := make(chan error) mux := http.NewServeMux() // NOTE: port :8888 is hardcoded here. Maybe we can we use `:0` and `server.Addr` to find the port it was assigned? - server := &http.Server{Addr: ":8888", Handler: mux} + server := &http.Server{Addr: redirectAddr, Handler: mux} mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") if code == "" { From 9e30b8a602209087986fdf3ecf7503fa6a65e477 Mon Sep 17 00:00:00 2001 From: Josh Holman Date: Fri, 5 Dec 2025 12:40:06 -0800 Subject: [PATCH 34/34] add documentation!! --- developer-docs/env-vars.md | 12 ++ developer-docs/oauth-authentication.md | 188 +++++++++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 developer-docs/oauth-authentication.md diff --git a/developer-docs/env-vars.md b/developer-docs/env-vars.md index a3a176b..4318b8d 100644 --- a/developer-docs/env-vars.md +++ b/developer-docs/env-vars.md @@ -24,3 +24,15 @@ API sits behind a reverse proxy in production. - `ALLOWED_ORIGINS`: To be used with CORS middleware. Controls which web origins are allowed to make cross-origin requests to the API. - Default: `*` (change in production) +- `DISCORD_BOT_TOKEN`: Discord bot token used by the API server to validate user +tokens and check server membership/roles. Required in production. + - Default: (none, required in production) +- `GUILD_ID`: The Discord server/guild ID where user membership and roles are +checked. + - Default: (none, required in production) +- `DISCORD_CLIENT_ID`: OAuth2 application client ID used by the CLI for Discord +authentication. + - Default: (none, required for CLI OAuth) +- `DISCORD_CLIENT_SECRET`: OAuth2 application client secret used by the CLI for +token exchange. + - Default: (none, required for CLI OAuth) diff --git a/developer-docs/oauth-authentication.md b/developer-docs/oauth-authentication.md new file mode 100644 index 0000000..d526e0e --- /dev/null +++ b/developer-docs/oauth-authentication.md @@ -0,0 +1,188 @@ +# OAuth Authentication + +The ACM CSUF API uses Discord OAuth2 for authentication and authorization. This ensures that only authorized Discord server members with appropriate roles can access protected API endpoints. + +## Overview + +The API implements a role-based access control (RBAC) system that: +1. Authenticates users via Discord OAuth2 +2. Verifies Discord server membership +3. Checks user roles against required permissions +4. Caches role information to avoid hitting Discord's rate limits + +## Architecture + +### Server-Side: API Middleware + +The API uses Discord OAuth2 middleware defined in [`internal/api/middleware/oauth.go`](../internal/api/middleware/oauth.go) to protect all `/v1` routes. + +**How it works:** + +1. **Authorization Header**: The middleware expects requests to include: + ``` + Authorization: Bearer + ``` + +2. **Token Validation**: The middleware validates the token by: + - Making a request to Discord's API to fetch user information + - Verifying the user is a member of the configured Discord guild/server + - Checking if the user has the required role + +3. **Role-Based Access Control**: + - Roles are mapped in `RoleMap` (e.g., Discord role ID `1445971950584205554` → `"Board"`) + - The middleware checks if the user has the required role for the endpoint + - Role hierarchy: `President` role also grants `Board` access + +4. **Caching**: + - Role information is cached for 5 minutes to prevent rate limit issues + - Cache key is the Authorization header value + - Expired cache entries are automatically removed + +5. **Development Mode**: + - When `ENV=development`, the special token `Bearer dev-token` bypasses authentication + - This allows local testing without setting up OAuth + +### Client-Side: CLI OAuth Flow + +The CLI client (defined in [`utils/requests/request_with_auth.go`](../utils/requests/request_with_auth.go)) implements the OAuth2 authorization code flow with a local callback server. + +**How it works:** + +1. **Token Persistence**: + - Tokens are stored in `~/.config/acmcsuf-cli/token.json` on Unix systems + - The file contains: `access_token`, `refresh_token`, and `expiry` timestamp + - Tokens are automatically loaded on subsequent CLI runs + +2. **OAuth Flow** (when no valid token exists): + ``` + ┌─────────┐ ┌─────────────┐ + │ CLI │ │ Discord │ + └────┬────┘ └──────┬──────┘ + │ │ + │ 1. Start local callback server │ + │ on random port (e.g., :61234) │ + │ │ + │ 2. Open browser with OAuth URL │ + ├──────────────────────────────────────────> │ + │ https://discord.com/oauth2/authorize │ + │ ?client_id=... │ + │ &redirect_uri=http://localhost:54321 │ + │ &scope=identify │ + │ &response_type=code │ + │ │ + │ User approves in browser │ + │ │ + │ 3. Discord redirects to callback │ + │ <────────────────────────────────────────── │ + │ http://localhost:54321/?code=... │ + │ │ + │ 4. Exchange code for token │ + ├──────────────────────────────────────────> │ + │ POST /oauth2/token │ + │ │ + │ 5. Receive access token │ + │ <────────────────────────────────────────── │ + │ { access_token, refresh_token, ... } │ + │ │ + │ 6. Save token to ~/.config/acmcsuf-cli/ │ + │ │ + │ 7. Make authenticated API request │ + │ Authorization: Bearer │ + └──────────────────────────────────────────────┘ + ``` + +4. **Token Exchange**: + - The callback server receives the authorization code from Discord + - Exchanges the code for an access token via `POST https://discord.com/api/oauth2/token` + - Stores the token with expiry information for future use + +## Environment Variables + +See [`developer-docs/env-vars.md`](./env-vars.md) for the complete list, but OAuth-specific variables include: + +- `ENV`: Set to `production` to enable OAuth (default: `development`) +- `DISCORD_BOT_TOKEN`: Bot token for server-side API authentication +- `GUILD_ID`: Discord server/guild ID to verify membership +- `DISCORD_CLIENT_ID`: OAuth2 application client ID (for CLI) +- `DISCORD_CLIENT_SECRET`: OAuth2 application client secret (for CLI) + +## Development Mode + +During development (`ENV=development`), authentication is bypassed: + +**API Server:** +```go +// Any request with this header will bypass OAuth +Authorization: Bearer dev-token +``` + +**CLI Client:** +```go +// Automatically uses dev-token when ENV=development +// No OAuth flow occurs, no tokens are exchanged +``` + +To test the actual OAuth flow during development, temporarily set `ENV=production` in your `.env` file. + +## Testing with OAuth + +### Using the CLI + +The CLI handles OAuth automatically: + +```bash +# First run will trigger OAuth flow +./api.acmcsuf.com events get event-id + +# Browser opens for authentication +# Token is saved to ~/.config/acmcsuf-cli/token.json +# Subsequent runs use the cached token +``` + +### Using curl/xh with OAuth + +If testing manually with `curl` or `xh`, you need a valid Discord access token: + +```bash +# Development mode (no real auth needed) +xh :8080/v1/events Authorization:"Bearer dev-token" + +# Production mode (need real Discord token) +xh :8080/v1/events Authorization:"Bearer " +``` + +To get a real Discord access token for testing: +1. Run the CLI once to complete the OAuth flow +2. Extract the token from `~/.config/acmcsuf-cli/token.json` +3. Use that token in your curl/xh commands + +### Token Expiry + +Discord access tokens expire after a period (typically 1 week). When a token expires: + +**CLI**: The OAuth flow automatically re-runs on the next command +**Manual testing**: You'll receive a `401 Unauthorized` response and need a new token + +## Security Considerations + +1. **Never commit tokens**: Token files (`~/.config/acmcsuf-cli/token.json`) contain sensitive credentials +2. **HTTPS in production**: The API should only run behind HTTPS in production to protect tokens in transit +3. **Client secrets**: Keep `DISCORD_CLIENT_SECRET` secure and never commit to git +4. **Rate limiting**: The middleware caches role information for 5 minutes to respect Discord's rate limits +5. **Token storage**: CLI tokens are stored with `0600` permissions (read/write for owner only) + +## Role Configuration + +To add or modify roles, edit the `RoleMap` in [`internal/api/middleware/oauth.go`](../internal/api/middleware/oauth.go): + +```go +var RoleMap = map[string]string{ + "1445971950584205554": "Board", // Discord role ID -> Role name + "another-role-id": "Developer", +} +``` + +To find Discord role IDs: +1. Enable Developer Mode in Discord settings +2. Right-click a role in Server Settings → Roles +3. Click "Copy ID"