The code and data behind xeiaso.net
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(sponsor-panel): migrate from raw SQL to GORM (#1183)

* refactor(sponsor-panel): rewrite models with GORM tags and helpers

Replace raw pgxpool structs and SQL helper functions with GORM-tagged
models. Rename User to PanelUser to avoid collision with internal/models.
All DB helpers now take *gorm.DB instead of context + *pgxpool.Pool.
Add TableName() methods and PanelModels() for AutoMigrate.

Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

* refactor(sponsor-panel): remove raw SQL migrations

GORM AutoMigrate replaces hand-written CREATE TABLE statements
and ALTER TABLE migrations.

Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

* refactor(sponsor-panel): swap pgxpool for gorm.DB in server

Replace pgxpool.Pool with gorm.DB on Server struct. Use slog-gorm
for structured logging and gorm-prometheus for database metrics.

Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

* refactor(sponsor-panel): update oauth handlers to use GORM

Replace s.pool with s.db, update function signatures, rename User
to PanelUser, and cast user.ID to int for session compatibility.

Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

* refactor(sponsor-panel): update patreon oauth to use GORM

Replace s.pool with s.db, rename User to PanelUser, cast user.ID
to int for session storage.

Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

* refactor(sponsor-panel): update handlers to use GORM

Replace s.pool with s.db for createLogoSubmission call.

Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

* refactor(sponsor-panel): update sync sponsors to use GORM

Replace pgxpool.Pool with gorm.DB in syncSponsors and startSyncLoop.

Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(sponsor-panel): add index drop tool for GORM migration

One-shot tool to drop old hand-created indexes and constraints
so GORM AutoMigrate can recreate them with its own naming scheme.

Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

* build(deps): add slog-gorm and gorm-prometheus dependencies

Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: update templ generated code to v0.3.1001

Signed-off-by: Xe Iaso <me@xeiaso.net>

* docs(sponsor-panel): add GORM migration implementation plan

Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>

authored by

Xe Iaso and committed by
GitHub
92304113 504058e4

+1518 -392
+60
cmd/sponsor-panel-drop-indexes/main.go
··· 1 + package main 2 + 3 + import ( 4 + "database/sql" 5 + "flag" 6 + "fmt" 7 + "log" 8 + "os" 9 + 10 + "github.com/facebookgo/flagenv" 11 + _ "github.com/jackc/pgx/v5/stdlib" 12 + _ "github.com/joho/godotenv/autoload" 13 + ) 14 + 15 + var databaseURL = flag.String("database-url", "", "Database URL") 16 + 17 + func main() { 18 + flagenv.Parse() 19 + flag.Parse() 20 + 21 + if *databaseURL == "" { 22 + fmt.Fprintln(os.Stderr, "database-url is required") 23 + os.Exit(1) 24 + } 25 + 26 + db, err := sql.Open("pgx", *databaseURL) 27 + if err != nil { 28 + log.Fatalf("failed to connect: %v", err) 29 + } 30 + defer db.Close() 31 + 32 + if err := db.Ping(); err != nil { 33 + log.Fatalf("failed to ping: %v", err) 34 + } 35 + 36 + statements := []string{ 37 + // Users table: drop constraints (unique indexes backed by constraints) 38 + `ALTER TABLE users DROP CONSTRAINT IF EXISTS users_github_id_key`, 39 + `ALTER TABLE users DROP CONSTRAINT IF EXISTS users_patreon_id_key`, 40 + // Users table: drop plain indexes 41 + `DROP INDEX IF EXISTS idx_users_github_id`, 42 + `DROP INDEX IF EXISTS idx_users_login`, 43 + `DROP INDEX IF EXISTS idx_users_patreon_id`, 44 + `DROP INDEX IF EXISTS idx_users_provider_login`, 45 + // Sponsor usernames table: drop constraint 46 + `ALTER TABLE github_sponsor_usernames DROP CONSTRAINT IF EXISTS github_sponsor_usernames_username_key`, 47 + // Sponsor usernames table: drop plain indexes 48 + `DROP INDEX IF EXISTS idx_sponsor_active`, 49 + `DROP INDEX IF EXISTS idx_sponsor_usernames`, 50 + } 51 + 52 + for _, stmt := range statements { 53 + fmt.Printf(" %s\n", stmt) 54 + if _, err := db.Exec(stmt); err != nil { 55 + log.Fatalf("failed: %v", err) 56 + } 57 + } 58 + 59 + fmt.Println("done — GORM AutoMigrate will recreate indexes on next startup") 60 + }
+1 -1
cmd/sponsor-panel/handlers.go
··· 281 281 GitHubIssueURL: createdIssue.GetHTMLURL(), 282 282 GitHubIssueNumber: createdIssue.GetNumber(), 283 283 } 284 - if err := createLogoSubmission(r.Context(), s.pool, submission); err != nil { 284 + if err := createLogoSubmission(s.db, submission); err != nil { 285 285 slog.Error("logoHandler: failed to store submission", "err", err, "user_id", user.ID, "issue_number", createdIssue.GetNumber()) 286 286 } else { 287 287 slog.Debug("logoHandler: submission stored in database", "user_id", user.ID, "issue_number", createdIssue.GetNumber())
+25 -19
cmd/sponsor-panel/main.go
··· 19 19 "github.com/facebookgo/flagenv" 20 20 gh "github.com/google/go-github/v82/github" 21 21 "github.com/gorilla/sessions" 22 - "github.com/jackc/pgx/v5/pgxpool" 22 + slogGorm "github.com/orandin/slog-gorm" 23 + "gorm.io/driver/postgres" 24 + "gorm.io/gorm" 25 + gormPrometheus "gorm.io/plugin/prometheus" 23 26 _ "github.com/joho/godotenv/autoload" 24 27 patreon "gopkg.in/mxpv/patreon-go.v1" 25 28 "github.com/prometheus/client_golang/prometheus/promhttp" ··· 60 63 61 64 // Server holds the application dependencies. 62 65 type Server struct { 63 - pool *pgxpool.Pool 66 + db *gorm.DB 64 67 ghClient *gh.Client 65 68 oauth *oauth2.Config 66 69 patreonOAuth *oauth2.Config // nil if Patreon not configured ··· 144 147 os.Exit(1) 145 148 } 146 149 147 - // Connect to database 148 - slog.Debug("main: connecting to database") 149 - ctx := context.Background() 150 - pool, err := pgxpool.New(ctx, *databaseURL) 150 + // Connect to database via GORM 151 + slog.Debug("main: connecting to database via GORM") 152 + db, err := gorm.Open(postgres.Open(*databaseURL), &gorm.Config{ 153 + Logger: slogGorm.New( 154 + slogGorm.WithErrorField("err"), 155 + slogGorm.WithRecordNotFoundError(), 156 + ), 157 + }) 151 158 if err != nil { 152 - slog.Error("failed to create connection pool", "err", err) 159 + slog.Error("failed to connect to database", "err", err) 153 160 os.Exit(1) 154 161 } 155 - defer pool.Close() 156 162 157 - if err := pool.Ping(ctx); err != nil { 158 - slog.Error("failed to ping database", "err", err) 159 - os.Exit(1) 160 - } 163 + db.Use(gormPrometheus.New(gormPrometheus.Config{ 164 + DBName: "sponsor_panel", 165 + })) 166 + 161 167 slog.Info("main: database connection established") 162 168 163 - // Run migrations 164 - slog.Debug("main: running migrations") 165 - if err := runMigrations(ctx, pool); err != nil { 166 - slog.Error("failed to run migrations", "err", err) 169 + // Run GORM AutoMigrate 170 + slog.Debug("main: running GORM auto-migration") 171 + if err := db.AutoMigrate(PanelModels()...); err != nil { 172 + slog.Error("failed to auto-migrate", "err", err) 167 173 os.Exit(1) 168 174 } 169 - slog.Info("main: migrations completed") 175 + slog.Info("main: auto-migration completed") 170 176 171 177 // Start sponsor sync loop in background 172 178 syncCtx, syncCancel := context.WithCancel(context.Background()) 173 179 defer syncCancel() 174 - go startSyncLoop(syncCtx, pool, *githubToken) 180 + go startSyncLoop(syncCtx, db, *githubToken) 175 181 slog.Info("main: sponsor sync loop started") 176 182 177 183 // Create GitHub client ··· 255 261 } 256 262 257 263 server := &Server{ 258 - pool: pool, 264 + db: db, 259 265 ghClient: ghClient, 260 266 oauth: oauthConfig, 261 267 patreonOAuth: patreonConfig,
-103
cmd/sponsor-panel/migrations.go
··· 1 - package main 2 - 3 - import ( 4 - "context" 5 - "log/slog" 6 - 7 - "github.com/jackc/pgx/v5/pgxpool" 8 - ) 9 - 10 - const migrationSchema = ` 11 - -- Users table: GitHub accounts + sponsorship data 12 - CREATE TABLE IF NOT EXISTS users ( 13 - id SERIAL PRIMARY KEY, 14 - github_id BIGINT UNIQUE NOT NULL, 15 - login TEXT NOT NULL UNIQUE, 16 - avatar_url TEXT, 17 - name TEXT, 18 - email TEXT, 19 - 20 - -- Sponsorship data from GraphQL (cached) 21 - sponsorship_data JSONB, 22 - last_sponsorship_check TIMESTAMP DEFAULT NOW(), 23 - 24 - -- Timestamps 25 - created_at TIMESTAMP DEFAULT NOW(), 26 - updated_at TIMESTAMP DEFAULT NOW() 27 - ); 28 - 29 - -- Logo submissions: Simple tracking only 30 - CREATE TABLE IF NOT EXISTS logo_submissions ( 31 - id SERIAL PRIMARY KEY, 32 - user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, 33 - 34 - company_name TEXT NOT NULL, 35 - website TEXT NOT NULL, 36 - logo_url TEXT, 37 - github_issue_url TEXT, 38 - github_issue_number INTEGER, 39 - 40 - submitted_at TIMESTAMP DEFAULT NOW() 41 - ); 42 - 43 - -- Indexes for common queries 44 - CREATE INDEX IF NOT EXISTS idx_users_github_id ON users(github_id); 45 - CREATE INDEX IF NOT EXISTS idx_users_login ON users(login); 46 - CREATE INDEX IF NOT EXISTS idx_logo_user_id ON logo_submissions(user_id); 47 - 48 - -- GitHub sponsor usernames: synced list of all sponsors (users + orgs) 49 - CREATE TABLE IF NOT EXISTS github_sponsor_usernames ( 50 - id SERIAL PRIMARY KEY, 51 - username TEXT NOT NULL UNIQUE, -- GitHub login (user or org) 52 - entity_type TEXT NOT NULL, -- 'User' or 'Organization' 53 - monthly_amount_cents INTEGER DEFAULT 0, -- Sponsorship tier amount 54 - tier_name TEXT, -- Tier name for display 55 - is_active BOOLEAN DEFAULT TRUE, -- Active sponsorship flag 56 - synced_at TIMESTAMP DEFAULT NOW(), -- Last sync timestamp 57 - created_at TIMESTAMP DEFAULT NOW() 58 - ); 59 - 60 - CREATE INDEX IF NOT EXISTS idx_sponsor_usernames ON github_sponsor_usernames(username); 61 - CREATE INDEX IF NOT EXISTS idx_sponsor_active ON github_sponsor_usernames(is_active); 62 - ` 63 - 64 - const migration002 = ` 65 - -- Make github_id nullable (Patreon users won't have one) 66 - ALTER TABLE users ALTER COLUMN github_id DROP NOT NULL; 67 - 68 - -- Add patreon_id column for Patreon OAuth users 69 - ALTER TABLE users ADD COLUMN IF NOT EXISTS patreon_id TEXT UNIQUE; 70 - 71 - -- Add provider column to distinguish auth source 72 - ALTER TABLE users ADD COLUMN IF NOT EXISTS provider TEXT NOT NULL DEFAULT 'github'; 73 - 74 - -- Drop old unique constraint on login (may not exist by name) 75 - DO $$ BEGIN 76 - ALTER TABLE users DROP CONSTRAINT IF EXISTS users_login_key; 77 - EXCEPTION WHEN undefined_object THEN NULL; 78 - END $$; 79 - 80 - -- Uniqueness is now per-provider 81 - DROP INDEX IF EXISTS idx_users_provider_login; 82 - CREATE UNIQUE INDEX IF NOT EXISTS idx_users_provider_login ON users(provider, login); 83 - 84 - -- Index for Patreon lookups 85 - CREATE INDEX IF NOT EXISTS idx_users_patreon_id ON users(patreon_id); 86 - ` 87 - 88 - // runMigrations executes the database schema migration. 89 - func runMigrations(ctx context.Context, pool *pgxpool.Pool) error { 90 - slog.Info("running database migrations") 91 - _, err := pool.Exec(ctx, migrationSchema) 92 - if err != nil { 93 - return err 94 - } 95 - 96 - _, err = pool.Exec(ctx, migration002) 97 - if err != nil { 98 - return err 99 - } 100 - 101 - slog.Info("database migrations completed") 102 - return nil 103 - }
+125 -247
cmd/sponsor-panel/models.go
··· 1 1 package main 2 2 3 3 import ( 4 - "context" 5 4 "encoding/json" 6 5 "log/slog" 7 6 "time" 8 7 9 - "github.com/jackc/pgx/v5/pgxpool" 8 + "gorm.io/gorm" 10 9 ) 11 10 12 - // User represents an authenticated user with cached sponsorship data. 13 - type User struct { 14 - ID int `json:"id" db:"id"` 15 - GitHubID *int64 `json:"github_id" db:"github_id"` 16 - PatreonID *string `json:"patreon_id" db:"patreon_id"` 17 - Provider string `json:"provider" db:"provider"` // "github" or "patreon" 18 - Login string `json:"login" db:"login"` 19 - AvatarURL string `json:"avatar_url" db:"avatar_url"` 20 - Name string `json:"name" db:"name"` 21 - Email string `json:"email" db:"email"` 22 - SponsorshipData string `json:"-" db:"sponsorship_data"` // JSON blob 23 - LastSponsorshipCheck time.Time `json:"last_sponsorship_check" db:"last_sponsorship_check"` 24 - CreatedAt time.Time `json:"created_at" db:"created_at"` 25 - UpdatedAt time.Time `json:"updated_at" db:"updated_at"` 11 + // PanelUser represents an authenticated user with cached sponsorship data. 12 + type PanelUser struct { 13 + ID uint `json:"id" gorm:"primaryKey"` 14 + GitHubID *int64 `json:"github_id" gorm:"uniqueIndex:users_github_id_key"` 15 + PatreonID *string `json:"patreon_id" gorm:"uniqueIndex:users_patreon_id_key"` 16 + Provider string `json:"provider" gorm:"not null;default:'github';uniqueIndex:idx_users_provider_login"` 17 + Login string `json:"login" gorm:"not null;uniqueIndex:idx_users_provider_login"` 18 + AvatarURL string `json:"avatar_url"` 19 + Name string `json:"name"` 20 + Email string `json:"email"` 21 + SponsorshipData string `json:"-" gorm:"type:jsonb"` 22 + LastSponsorshipCheck time.Time `json:"last_sponsorship_check"` 23 + CreatedAt time.Time `json:"created_at"` 24 + UpdatedAt time.Time `json:"updated_at"` 26 25 } 26 + 27 + func (PanelUser) TableName() string { return "users" } 27 28 28 29 // SponsorshipData represents the cached GraphQL response. 29 30 type SponsorshipData struct { ··· 34 35 } 35 36 36 37 // IsSponsorAtTier returns true if user sponsors at or above the given amount (in cents). 37 - func (u *User) IsSponsorAtTier(minCents int) bool { 38 + func (u *PanelUser) IsSponsorAtTier(minCents int) bool { 38 39 if u.SponsorshipData == "" { 39 40 return false 40 41 } 41 42 42 43 var data SponsorshipData 43 44 if err := json.Unmarshal([]byte(u.SponsorshipData), &data); err != nil { 44 - slog.Error("IsSponsorAtTier: failed to parse sponsorship data", "user_id", u.ID, "err", err, "raw_data", u.SponsorshipData) 45 + slog.Error("IsSponsorAtTier: failed to parse sponsorship data", "user_id", u.ID, "err", err) 45 46 return false 46 47 } 47 48 48 - result := data.IsActive && data.MonthlyAmount >= minCents 49 - slog.Debug("IsSponsorAtTier: tier check", 50 - "user_id", u.ID, 51 - "login", u.Login, 52 - "min_cents", minCents, 53 - "actual_cents", data.MonthlyAmount, 54 - "is_active", data.IsActive, 55 - "result", result) 56 - 57 - return result 49 + return data.IsActive && data.MonthlyAmount >= minCents 58 50 } 59 51 60 52 // LogoSubmission represents a logo submission. 61 53 type LogoSubmission struct { 62 - ID int `json:"id" db:"id"` 63 - UserID int `json:"user_id" db:"user_id"` 64 - CompanyName string `json:"company_name" db:"company_name"` 65 - Website string `json:"website" db:"website"` 66 - LogoURL string `json:"logo_url" db:"logo_url"` 67 - GitHubIssueURL string `json:"github_issue_url" db:"github_issue_url"` 68 - GitHubIssueNumber int `json:"github_issue_number" db:"github_issue_number"` 69 - SubmittedAt time.Time `json:"submitted_at" db:"submitted_at"` 54 + ID uint `json:"id" gorm:"primaryKey"` 55 + UserID uint `json:"user_id" gorm:"not null"` 56 + CompanyName string `json:"company_name" gorm:"not null"` 57 + Website string `json:"website" gorm:"not null"` 58 + LogoURL string `json:"logo_url"` 59 + GitHubIssueURL string `json:"github_issue_url"` 60 + GitHubIssueNumber int `json:"github_issue_number"` 61 + SubmittedAt time.Time `json:"submitted_at" gorm:"autoCreateTime"` 70 62 } 71 63 64 + func (LogoSubmission) TableName() string { return "logo_submissions" } 65 + 72 66 // SponsorUsername represents a synced sponsor username (user or org). 73 67 type SponsorUsername struct { 74 - ID int `json:"id" db:"id"` 75 - Username string `json:"username" db:"username"` 76 - EntityType string `json:"entity_type" db:"entity_type"` 77 - MonthlyAmountCents int `json:"monthly_amount_cents" db:"monthly_amount_cents"` 78 - TierName string `json:"tier_name" db:"tier_name"` 79 - IsActive bool `json:"is_active" db:"is_active"` 80 - SyncedAt time.Time `json:"synced_at" db:"synced_at"` 81 - CreatedAt time.Time `json:"created_at" db:"created_at"` 68 + ID uint `json:"id" gorm:"primaryKey"` 69 + Username string `json:"username" gorm:"uniqueIndex:github_sponsor_usernames_username_key;not null"` 70 + EntityType string `json:"entity_type" gorm:"not null"` 71 + MonthlyAmountCents int `json:"monthly_amount_cents" gorm:"default:0"` 72 + TierName string `json:"tier_name"` 73 + IsActive bool `json:"is_active" gorm:"default:true;index"` 74 + SyncedAt time.Time `json:"synced_at"` 75 + CreatedAt time.Time `json:"created_at"` 82 76 } 83 77 84 - // getUserByID retrieves a user by ID from the database. 85 - func getUserByID(ctx context.Context, pool *pgxpool.Pool, userID int) (*User, error) { 86 - slog.Debug("getUserByID: querying user", "user_id", userID) 78 + func (SponsorUsername) TableName() string { return "github_sponsor_usernames" } 87 79 88 - var user User 89 - err := pool.QueryRow(ctx, ` 90 - SELECT id, github_id, patreon_id, provider, login, avatar_url, name, email, 91 - sponsorship_data, last_sponsorship_check, created_at, updated_at 92 - FROM users WHERE id = $1 93 - `, userID).Scan( 94 - &user.ID, &user.GitHubID, &user.PatreonID, &user.Provider, &user.Login, &user.AvatarURL, 95 - &user.Name, &user.Email, &user.SponsorshipData, 96 - &user.LastSponsorshipCheck, &user.CreatedAt, &user.UpdatedAt, 97 - ) 98 - if err != nil { 80 + // PanelModels returns all sponsor-panel models for AutoMigrate. 81 + func PanelModels() []interface{} { 82 + return []interface{}{ 83 + &PanelUser{}, 84 + &LogoSubmission{}, 85 + &SponsorUsername{}, 86 + } 87 + } 88 + 89 + // --- DB helper functions (GORM) --- 90 + 91 + // getUserByID retrieves a user by ID from the database. 92 + func getUserByID(db *gorm.DB, userID int) (*PanelUser, error) { 93 + var user PanelUser 94 + if err := db.First(&user, userID).Error; err != nil { 99 95 slog.Error("getUserByID: user not found", "user_id", userID, "err", err) 100 96 return nil, err 101 97 } 102 - 103 - slog.Debug("getUserByID: user found", "user_id", userID, "login", user.Login) 104 98 return &user, nil 105 99 } 106 100 107 - // upsertUser creates or updates a user in the database. 108 - func upsertUser(ctx context.Context, pool *pgxpool.Pool, user *User) error { 109 - slog.Debug("upsertUser: attempting upsert", "github_id", user.GitHubID, "login", user.Login) 110 - 111 - // Try update first 112 - tag, err := pool.Exec(ctx, ` 113 - UPDATE users 114 - SET login=$1, avatar_url=$2, name=$3, email=$4, 115 - sponsorship_data=$5, last_sponsorship_check=NOW(), updated_at=NOW() 116 - WHERE github_id=$6 117 - `, user.Login, user.AvatarURL, user.Name, user.Email, 118 - user.SponsorshipData, user.GitHubID) 119 - if err != nil { 120 - slog.Error("upsertUser: update failed", "err", err, "github_id", user.GitHubID) 121 - return err 101 + // upsertUser creates or updates a GitHub user in the database. 102 + func upsertUser(db *gorm.DB, user *PanelUser) error { 103 + var existing PanelUser 104 + result := db.Where("github_id = ?", user.GitHubID).First(&existing) 105 + if result.Error == nil { 106 + // Update existing 107 + existing.Login = user.Login 108 + existing.AvatarURL = user.AvatarURL 109 + existing.Name = user.Name 110 + existing.Email = user.Email 111 + existing.SponsorshipData = user.SponsorshipData 112 + existing.LastSponsorshipCheck = time.Now() 113 + if err := db.Save(&existing).Error; err != nil { 114 + return err 115 + } 116 + *user = existing 117 + return nil 122 118 } 123 119 124 - if tag.RowsAffected() > 0 { 125 - slog.Debug("upsertUser: updated existing user", "github_id", user.GitHubID, "rows_affected", tag.RowsAffected()) 126 - return pool.QueryRow(ctx, ` 127 - SELECT id, github_id, patreon_id, provider, login, avatar_url, name, email, 128 - sponsorship_data, last_sponsorship_check, created_at, updated_at 129 - FROM users WHERE github_id = $1 130 - `, user.GitHubID).Scan( 131 - &user.ID, &user.GitHubID, &user.PatreonID, &user.Provider, &user.Login, &user.AvatarURL, 132 - &user.Name, &user.Email, &user.SponsorshipData, 133 - &user.LastSponsorshipCheck, &user.CreatedAt, &user.UpdatedAt, 134 - ) 135 - } 136 - 137 - slog.Debug("upsertUser: inserting new user", "github_id", user.GitHubID, "login", user.Login) 138 - 139 - return pool.QueryRow(ctx, ` 140 - INSERT INTO users (github_id, provider, login, avatar_url, name, email, sponsorship_data) 141 - VALUES ($1, 'github', $2, $3, $4, $5, $6) 142 - RETURNING id, github_id, patreon_id, provider, login, avatar_url, name, email, 143 - sponsorship_data, last_sponsorship_check, created_at, updated_at 144 - `, user.GitHubID, user.Login, user.AvatarURL, user.Name, user.Email, 145 - user.SponsorshipData).Scan( 146 - &user.ID, &user.GitHubID, &user.PatreonID, &user.Provider, &user.Login, &user.AvatarURL, 147 - &user.Name, &user.Email, &user.SponsorshipData, 148 - &user.LastSponsorshipCheck, &user.CreatedAt, &user.UpdatedAt, 149 - ) 120 + // Insert new 121 + user.Provider = "github" 122 + return db.Create(user).Error 150 123 } 151 124 152 - // createLogoSubmission creates a logo submission in the database. 153 - func createLogoSubmission(ctx context.Context, pool *pgxpool.Pool, submission *LogoSubmission) error { 154 - slog.Debug("createLogoSubmission: inserting submission", 155 - "user_id", submission.UserID, 156 - "company", submission.CompanyName, 157 - "issue_number", submission.GitHubIssueNumber) 158 - 159 - err := pool.QueryRow(ctx, ` 160 - INSERT INTO logo_submissions (user_id, company_name, website, logo_url, github_issue_url, github_issue_number) 161 - VALUES ($1, $2, $3, $4, $5, $6) 162 - RETURNING id, submitted_at 163 - `, submission.UserID, submission.CompanyName, submission.Website, 164 - submission.LogoURL, submission.GitHubIssueURL, submission.GitHubIssueNumber, 165 - ).Scan(&submission.ID, &submission.SubmittedAt) 166 - 167 - if err != nil { 168 - slog.Error("createLogoSubmission: failed to insert", "err", err, "user_id", submission.UserID) 169 - return err 125 + // upsertPatreonUser creates or updates a Patreon user in the database. 126 + func upsertPatreonUser(db *gorm.DB, user *PanelUser) error { 127 + var existing PanelUser 128 + result := db.Where("patreon_id = ?", user.PatreonID).First(&existing) 129 + if result.Error == nil { 130 + existing.Login = user.Login 131 + existing.AvatarURL = user.AvatarURL 132 + existing.Name = user.Name 133 + existing.Email = user.Email 134 + existing.SponsorshipData = user.SponsorshipData 135 + existing.LastSponsorshipCheck = time.Now() 136 + if err := db.Save(&existing).Error; err != nil { 137 + return err 138 + } 139 + *user = existing 140 + return nil 170 141 } 171 142 172 - slog.Debug("createLogoSubmission: submission inserted", 173 - "user_id", submission.UserID, 174 - "submission_id", submission.ID, 175 - "submitted_at", submission.SubmittedAt) 143 + user.Provider = "patreon" 144 + return db.Create(user).Error 145 + } 176 146 177 - return nil 147 + // createLogoSubmission creates a logo submission in the database. 148 + func createLogoSubmission(db *gorm.DB, submission *LogoSubmission) error { 149 + return db.Create(submission).Error 178 150 } 179 151 180 152 // getActiveSponsorsByUsernames returns active sponsors matching any of the given usernames. 181 - func getActiveSponsorsByUsernames(ctx context.Context, pool *pgxpool.Pool, usernames []string) ([]*SponsorUsername, error) { 153 + func getActiveSponsorsByUsernames(db *gorm.DB, usernames []string) ([]*SponsorUsername, error) { 182 154 if len(usernames) == 0 { 183 155 return nil, nil 184 156 } 185 157 186 - slog.Debug("getActiveSponsorsByUsernames: querying sponsors", "usernames", usernames) 187 - 188 - rows, err := pool.Query(ctx, ` 189 - SELECT id, username, entity_type, monthly_amount_cents, tier_name, is_active, synced_at, created_at 190 - FROM github_sponsor_usernames 191 - WHERE username = ANY($1) AND is_active = TRUE 192 - ORDER BY monthly_amount_cents DESC 193 - `, usernames) 194 - if err != nil { 195 - slog.Error("getActiveSponsorsByUsernames: query failed", "err", err) 196 - return nil, err 197 - } 198 - defer rows.Close() 199 - 200 158 var sponsors []*SponsorUsername 201 - for rows.Next() { 202 - s := &SponsorUsername{} 203 - if err := rows.Scan(&s.ID, &s.Username, &s.EntityType, &s.MonthlyAmountCents, &s.TierName, &s.IsActive, &s.SyncedAt, &s.CreatedAt); err != nil { 204 - slog.Error("getActiveSponsorsByUsernames: scan failed", "err", err) 205 - return nil, err 206 - } 207 - sponsors = append(sponsors, s) 208 - } 209 - 210 - slog.Debug("getActiveSponsorsByUsernames: found sponsors", "count", len(sponsors)) 211 - return sponsors, nil 159 + err := db.Where("username IN ? AND is_active = ?", usernames, true). 160 + Order("monthly_amount_cents DESC"). 161 + Find(&sponsors).Error 162 + return sponsors, err 212 163 } 213 164 214 165 // upsertSponsorUsername inserts or updates a sponsor username. 215 - func upsertSponsorUsername(ctx context.Context, pool *pgxpool.Pool, sponsor *SponsorUsername) error { 216 - slog.Debug("upsertSponsorUsername: upserting sponsor", 217 - "username", sponsor.Username, 218 - "entity_type", sponsor.EntityType, 219 - "monthly_amount_cents", sponsor.MonthlyAmountCents, 220 - "tier_name", sponsor.TierName) 221 - 222 - tag, err := pool.Exec(ctx, ` 223 - UPDATE github_sponsor_usernames 224 - SET entity_type=$1, monthly_amount_cents=$2, tier_name=$3, is_active=$4, synced_at=NOW() 225 - WHERE username=$5 226 - `, sponsor.EntityType, sponsor.MonthlyAmountCents, sponsor.TierName, sponsor.IsActive, sponsor.Username) 227 - if err != nil { 228 - slog.Error("upsertSponsorUsername: update failed", "err", err, "username", sponsor.Username) 229 - return err 230 - } 231 - 232 - if tag.RowsAffected() > 0 { 233 - slog.Debug("upsertSponsorUsername: updated existing sponsor", "username", sponsor.Username) 234 - return pool.QueryRow(ctx, `SELECT id, created_at FROM github_sponsor_usernames WHERE username = $1`, sponsor.Username).Scan(&sponsor.ID, &sponsor.CreatedAt) 235 - } 236 - 237 - err = pool.QueryRow(ctx, ` 238 - INSERT INTO github_sponsor_usernames (username, entity_type, monthly_amount_cents, tier_name, is_active) 239 - VALUES ($1, $2, $3, $4, $5) 240 - RETURNING id, synced_at, created_at 241 - `, sponsor.Username, sponsor.EntityType, sponsor.MonthlyAmountCents, sponsor.TierName, sponsor.IsActive).Scan(&sponsor.ID, &sponsor.SyncedAt, &sponsor.CreatedAt) 242 - if err != nil { 243 - slog.Error("upsertSponsorUsername: insert failed", "err", err, "username", sponsor.Username) 244 - return err 166 + func upsertSponsorUsername(db *gorm.DB, sponsor *SponsorUsername) error { 167 + var existing SponsorUsername 168 + result := db.Where("username = ?", sponsor.Username).First(&existing) 169 + if result.Error == nil { 170 + existing.EntityType = sponsor.EntityType 171 + existing.MonthlyAmountCents = sponsor.MonthlyAmountCents 172 + existing.TierName = sponsor.TierName 173 + existing.IsActive = sponsor.IsActive 174 + existing.SyncedAt = time.Now() 175 + if err := db.Save(&existing).Error; err != nil { 176 + return err 177 + } 178 + sponsor.ID = existing.ID 179 + sponsor.CreatedAt = existing.CreatedAt 180 + return nil 245 181 } 246 182 247 - slog.Debug("upsertSponsorUsername: inserted new sponsor", "username", sponsor.Username, "id", sponsor.ID) 248 - return nil 249 - } 250 - 251 - // upsertPatreonUser creates or updates a Patreon user in the database. 252 - func upsertPatreonUser(ctx context.Context, pool *pgxpool.Pool, user *User) error { 253 - slog.Debug("upsertPatreonUser: attempting upsert", "patreon_id", user.PatreonID, "login", user.Login) 254 - 255 - // Try update first 256 - tag, err := pool.Exec(ctx, ` 257 - UPDATE users 258 - SET login=$1, avatar_url=$2, name=$3, email=$4, 259 - sponsorship_data=$5, last_sponsorship_check=NOW(), updated_at=NOW() 260 - WHERE patreon_id=$6 261 - `, user.Login, user.AvatarURL, user.Name, user.Email, 262 - user.SponsorshipData, user.PatreonID) 263 - if err != nil { 264 - slog.Error("upsertPatreonUser: update failed", "err", err, "patreon_id", user.PatreonID) 265 - return err 266 - } 267 - 268 - if tag.RowsAffected() > 0 { 269 - slog.Debug("upsertPatreonUser: updated existing user", "patreon_id", user.PatreonID, "rows_affected", tag.RowsAffected()) 270 - return pool.QueryRow(ctx, ` 271 - SELECT id, github_id, patreon_id, provider, login, avatar_url, name, email, 272 - sponsorship_data, last_sponsorship_check, created_at, updated_at 273 - FROM users WHERE patreon_id = $1 274 - `, user.PatreonID).Scan( 275 - &user.ID, &user.GitHubID, &user.PatreonID, &user.Provider, &user.Login, &user.AvatarURL, 276 - &user.Name, &user.Email, &user.SponsorshipData, 277 - &user.LastSponsorshipCheck, &user.CreatedAt, &user.UpdatedAt, 278 - ) 279 - } 280 - 281 - slog.Debug("upsertPatreonUser: inserting new user", "patreon_id", user.PatreonID, "login", user.Login) 282 - 283 - return pool.QueryRow(ctx, ` 284 - INSERT INTO users (patreon_id, provider, login, avatar_url, name, email, sponsorship_data) 285 - VALUES ($1, 'patreon', $2, $3, $4, $5, $6) 286 - RETURNING id, github_id, patreon_id, provider, login, avatar_url, name, email, 287 - sponsorship_data, last_sponsorship_check, created_at, updated_at 288 - `, user.PatreonID, user.Login, user.AvatarURL, user.Name, user.Email, 289 - user.SponsorshipData).Scan( 290 - &user.ID, &user.GitHubID, &user.PatreonID, &user.Provider, &user.Login, &user.AvatarURL, 291 - &user.Name, &user.Email, &user.SponsorshipData, 292 - &user.LastSponsorshipCheck, &user.CreatedAt, &user.UpdatedAt, 293 - ) 183 + return db.Create(sponsor).Error 294 184 } 295 185 296 186 // markInactiveSponsorsNotIn marks all sponsors as inactive that are not in the given usernames list. 297 - func markInactiveSponsorsNotIn(ctx context.Context, pool *pgxpool.Pool, usernames []string) (int64, error) { 187 + func markInactiveSponsorsNotIn(db *gorm.DB, usernames []string) (int64, error) { 188 + var result *gorm.DB 298 189 if len(usernames) == 0 { 299 - tag, err := pool.Exec(ctx, `UPDATE github_sponsor_usernames SET is_active = FALSE WHERE is_active = TRUE`) 300 - if err != nil { 301 - return 0, err 302 - } 303 - slog.Debug("markInactiveSponsorsNotIn: marked all sponsors inactive", "count", tag.RowsAffected()) 304 - return tag.RowsAffected(), nil 190 + result = db.Model(&SponsorUsername{}).Where("is_active = ?", true).Update("is_active", false) 191 + } else { 192 + result = db.Model(&SponsorUsername{}). 193 + Where("username NOT IN ? AND is_active = ?", usernames, true). 194 + Update("is_active", false) 305 195 } 306 - 307 - tag, err := pool.Exec(ctx, ` 308 - UPDATE github_sponsor_usernames 309 - SET is_active = FALSE 310 - WHERE NOT (username = ANY($1)) AND is_active = TRUE 311 - `, usernames) 312 - if err != nil { 313 - slog.Error("markInactiveSponsorsNotIn: update failed", "err", err) 314 - return 0, err 315 - } 316 - 317 - slog.Debug("markInactiveSponsorsNotIn: marked sponsors inactive", "count", tag.RowsAffected()) 318 - return tag.RowsAffected(), nil 196 + return result.RowsAffected, result.Error 319 197 }
+28
cmd/sponsor-panel/models_test.go
··· 1 + package main 2 + 3 + import ( 4 + "testing" 5 + 6 + "gorm.io/gorm" 7 + ) 8 + 9 + // TestModelsCompile verifies GORM model structs exist and have the expected shape. 10 + func TestModelsCompile(t *testing.T) { 11 + // These should compile once models.go is rewritten 12 + var _ gorm.Model 13 + 14 + u := PanelUser{} 15 + if u.TableName() != "users" { 16 + t.Errorf("PanelUser.TableName() = %q, want %q", u.TableName(), "users") 17 + } 18 + 19 + ls := LogoSubmission{} 20 + if ls.TableName() != "logo_submissions" { 21 + t.Errorf("LogoSubmission.TableName() = %q, want %q", ls.TableName(), "logo_submissions") 22 + } 23 + 24 + su := SponsorUsername{} 25 + if su.TableName() != "github_sponsor_usernames" { 26 + t.Errorf("SponsorUsername.TableName() = %q, want %q", su.TableName(), "github_sponsor_usernames") 27 + } 28 + }
+10 -10
cmd/sponsor-panel/oauth.go
··· 12 12 "strings" 13 13 14 14 "github.com/gorilla/sessions" 15 - "github.com/jackc/pgx/v5/pgxpool" 15 + "gorm.io/gorm" 16 16 "xeiaso.net/v4/cmd/sponsor-panel/templates" 17 17 ) 18 18 ··· 366 366 367 367 // fetchSponsorship fetches sponsorship data from GitHub GraphQL API. 368 368 // It checks the explicit allowlist first, then the synced sponsor table, then direct user sponsorship, then organizational membership. 369 - func fetchSponsorship(ctx context.Context, pool *pgxpool.Pool, token string, userLogin string, userOrgs map[string]bool, userOrgsWithSponsorship map[string]*sponsorshipInfo, fiftyPlusSponsors map[string]bool) (string, error) { 369 + func fetchSponsorship(ctx context.Context, db *gorm.DB, token string, userLogin string, userOrgs map[string]bool, userOrgsWithSponsorship map[string]*sponsorshipInfo, fiftyPlusSponsors map[string]bool) (string, error) { 370 370 slog.Debug("fetchSponsorship: checking sponsorship", "user", userLogin) 371 371 372 372 // Check if user is in the fifty-plus sponsors list first (highest priority) ··· 400 400 usernames = append(usernames, org) 401 401 } 402 402 403 - syncedSponsors, err := getActiveSponsorsByUsernames(ctx, pool, usernames) 403 + syncedSponsors, err := getActiveSponsorsByUsernames(db, usernames) 404 404 if err != nil { 405 405 slog.Warn("fetchSponsorship: failed to check synced sponsors table", "err", err) 406 406 } else if len(syncedSponsors) > 0 { ··· 547 547 } 548 548 549 549 // Fetch sponsorship data (checks allowlist, synced table, then user, then org sponsorships) 550 - sponsorData, err := fetchSponsorship(r.Context(), s.pool, token.AccessToken, ghUser.Login, userOrgs, userOrgsWithSponsorship, s.fiftyPlusSponsors) 550 + sponsorData, err := fetchSponsorship(r.Context(), s.db, token.AccessToken, ghUser.Login, userOrgs, userOrgsWithSponsorship, s.fiftyPlusSponsors) 551 551 if err != nil { 552 552 slog.Error("callbackHandler: failed to fetch sponsorship", "err", err) 553 553 // Non-fatal: continue with empty sponsorship data ··· 557 557 slog.Debug("callbackHandler: sponsorship data", "data", sponsorData) 558 558 559 559 // Upsert user in database 560 - user := &User{ 560 + user := &PanelUser{ 561 561 GitHubID: &ghUser.ID, 562 562 Provider: "github", 563 563 Login: ghUser.Login, ··· 567 567 SponsorshipData: sponsorData, 568 568 } 569 569 570 - if err := upsertUser(r.Context(), s.pool, user); err != nil { 570 + if err := upsertUser(s.db, user); err != nil { 571 571 slog.Error("callbackHandler: failed to upsert user", "err", err, "github_id", ghUser.ID) 572 572 renderOAuthError(w, "Failed to create user") 573 573 return ··· 583 583 slog.Debug("callbackHandler: failed to decode existing session, creating new one", "err", err) 584 584 session = sessions.NewSession(s.sessionStore, "session") 585 585 } 586 - session.Values["user_id"] = user.ID 586 + session.Values["user_id"] = int(user.ID) 587 587 if err := s.sessionStore.Save(r, w, session); err != nil { 588 588 slog.Error("callbackHandler: failed to save session", "err", err) 589 589 renderOAuthError(w, "Failed to save session") ··· 632 632 } 633 633 634 634 // getSessionUser retrieves the user from the session. 635 - func (s *Server) getSessionUser(r *http.Request) (*User, error) { 635 + func (s *Server) getSessionUser(r *http.Request) (*PanelUser, error) { 636 636 session, err := s.sessionStore.Get(r, "session") 637 637 if err != nil { 638 638 // Failed to decode session - might be old format, try to read raw cookie ··· 649 649 return nil, fmt.Errorf("invalid user id in session") 650 650 } 651 651 slog.Debug("getSessionUser: fetched user from old session format", "user_id", userID) 652 - return getUserByID(r.Context(), s.pool, userID) 652 + return getUserByID(s.db, userID) 653 653 } 654 654 655 655 userID, ok := session.Values["user_id"].(int) ··· 659 659 } 660 660 661 661 slog.Debug("getSessionUser: fetching user from session", "user_id", userID) 662 - return getUserByID(r.Context(), s.pool, userID) 662 + return getUserByID(s.db, userID) 663 663 } 664 664 665 665 // renderOAuthError renders an OAuth error page.
+3 -3
cmd/sponsor-panel/patreon_oauth.go
··· 222 222 223 223 // Upsert user in database 224 224 patreonID := identity.Data.ID 225 - user := &User{ 225 + user := &PanelUser{ 226 226 PatreonID: &patreonID, 227 227 Provider: "patreon", 228 228 Login: login, ··· 232 232 SponsorshipData: sponsorData, 233 233 } 234 234 235 - if err := upsertPatreonUser(r.Context(), s.pool, user); err != nil { 235 + if err := upsertPatreonUser(s.db, user); err != nil { 236 236 slog.Error("patreonCallbackHandler: failed to upsert user", "err", err, "patreon_id", patreonID) 237 237 renderOAuthError(w, "Failed to create user") 238 238 return ··· 246 246 slog.Debug("patreonCallbackHandler: failed to decode existing session, creating new one", "err", err) 247 247 session = sessions.NewSession(s.sessionStore, "session") 248 248 } 249 - session.Values["user_id"] = user.ID 249 + session.Values["user_id"] = int(user.ID) 250 250 if err := s.sessionStore.Save(r, w, session); err != nil { 251 251 slog.Error("patreonCallbackHandler: failed to save session", "err", err) 252 252 renderOAuthError(w, "Failed to save session")
+7 -7
cmd/sponsor-panel/sync_sponsors.go
··· 10 10 "strings" 11 11 "time" 12 12 13 - "github.com/jackc/pgx/v5/pgxpool" 13 + "gorm.io/gorm" 14 14 ) 15 15 16 16 // graphqlSponsorsResponse represents the GraphQL response for sponsorshipsAsMaintainer. ··· 42 42 } 43 43 44 44 // syncSponsors performs a single sync of all sponsors from GitHub. 45 - func syncSponsors(ctx context.Context, pool *pgxpool.Pool, ghToken string) error { 45 + func syncSponsors(ctx context.Context, db *gorm.DB, ghToken string) error { 46 46 slog.Info("syncSponsors: starting sponsor sync") 47 47 48 48 allSponsors := make([]string, 0) ··· 126 126 } 127 127 128 128 // Upsert to database 129 - if err := upsertSponsorUsername(ctx, pool, sponsor); err != nil { 129 + if err := upsertSponsorUsername(db, sponsor); err != nil { 130 130 slog.Error("syncSponsors: failed to upsert sponsor", "err", err, "username", login) 131 131 continue 132 132 } ··· 142 142 } 143 143 144 144 // Mark inactive sponsors not in current fetch 145 - inactiveCount, err := markInactiveSponsorsNotIn(ctx, pool, allSponsors) 145 + inactiveCount, err := markInactiveSponsorsNotIn(db, allSponsors) 146 146 if err != nil { 147 147 slog.Error("syncSponsors: failed to mark inactive sponsors", "err", err) 148 148 return err ··· 164 164 } 165 165 166 166 // startSyncLoop runs the sync immediately, then every hour. 167 - func startSyncLoop(ctx context.Context, pool *pgxpool.Pool, ghToken string) { 167 + func startSyncLoop(ctx context.Context, db *gorm.DB, ghToken string) { 168 168 // Run initial sync immediately 169 169 slog.Info("startSyncLoop: running initial sponsor sync") 170 - if err := syncSponsors(ctx, pool, ghToken); err != nil { 170 + if err := syncSponsors(ctx, db, ghToken); err != nil { 171 171 slog.Error("startSyncLoop: initial sync failed", "err", err) 172 172 } 173 173 ··· 182 182 return 183 183 case <-ticker.C: 184 184 slog.Info("startSyncLoop: running scheduled sponsor sync") 185 - if err := syncSponsors(ctx, pool, ghToken); err != nil { 185 + if err := syncSponsors(ctx, db, ghToken); err != nil { 186 186 slog.Error("startSyncLoop: scheduled sync failed", "err", err) 187 187 } 188 188 }
+751
docs/superpowers/plans/2026-03-26-sponsor-panel-gorm.md
··· 1 + # Sponsor Panel GORM Migration Plan 2 + 3 + > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. 4 + 5 + **Goal:** Replace raw SQL (`pgxpool` + hand-written queries) in `cmd/sponsor-panel` with GORM, matching the pattern already used by `cmd/github-sponsor-webhook` and `internal/models`. 6 + 7 + **Architecture:** Define three new GORM models (`PanelUser`, `LogoSubmission`, `SponsorUsername`) in `cmd/sponsor-panel/models.go`. Replace every `pgxpool` call site with GORM equivalents. Swap the `*pgxpool.Pool` on the `Server` struct for `*gorm.DB`. Delete `migrations.go` entirely since GORM AutoMigrate handles schema. The existing `internal/models` package is for the webhook service's domain (accounts, tiers, sponsorships, webhook events) and is unrelated -- we do NOT reuse those models here. 8 + 9 + **Tech Stack:** Go, GORM (`gorm.io/gorm` + `gorm.io/driver/postgres`) -- both already in `go.mod`. 10 + 11 + --- 12 + 13 + ## File Structure 14 + 15 + | Action | File | Responsibility | 16 + |--------|------|----------------| 17 + | Rewrite | `cmd/sponsor-panel/models.go` | GORM model structs + all DB helper functions | 18 + | Delete | `cmd/sponsor-panel/migrations.go` | Replaced by GORM AutoMigrate | 19 + | Modify | `cmd/sponsor-panel/main.go` | Swap `pgxpool.Pool` for `gorm.DB`, call AutoMigrate, update `Server` | 20 + | Modify | `cmd/sponsor-panel/oauth.go` | Update all `pool` calls to use `*gorm.DB` | 21 + | Modify | `cmd/sponsor-panel/patreon_oauth.go` | Update `upsertPatreonUser` call to GORM | 22 + | Modify | `cmd/sponsor-panel/handlers.go` | Update `createLogoSubmission` call to GORM | 23 + | Modify | `cmd/sponsor-panel/dashboard.go` | No direct DB calls, but `getSessionUser` flows through `oauth.go` | 24 + | Modify | `cmd/sponsor-panel/sync_sponsors.go` | Update `upsertSponsorUsername`, `markInactiveSponsorsNotIn` to GORM | 25 + 26 + --- 27 + 28 + ### Task 1: Define GORM Models 29 + 30 + Replace the raw structs and SQL helpers in `models.go` with GORM-tagged models and GORM-based DB functions. 31 + 32 + **Files:** 33 + - Rewrite: `cmd/sponsor-panel/models.go` 34 + 35 + - [ ] **Step 1: Write the failing test** 36 + 37 + Create `cmd/sponsor-panel/models_test.go` with a compilation test that imports the new model types and calls a GORM-specific method: 38 + 39 + ```go 40 + package main 41 + 42 + import ( 43 + "testing" 44 + 45 + "gorm.io/gorm" 46 + ) 47 + 48 + // TestModelsCompile verifies GORM model structs exist and have the expected shape. 49 + func TestModelsCompile(t *testing.T) { 50 + // These should compile once models.go is rewritten 51 + var _ gorm.Model 52 + 53 + u := PanelUser{} 54 + if u.TableName() != "users" { 55 + t.Errorf("PanelUser.TableName() = %q, want %q", u.TableName(), "users") 56 + } 57 + 58 + ls := LogoSubmission{} 59 + if ls.TableName() != "logo_submissions" { 60 + t.Errorf("LogoSubmission.TableName() = %q, want %q", ls.TableName(), "logo_submissions") 61 + } 62 + 63 + su := SponsorUsername{} 64 + if su.TableName() != "github_sponsor_usernames" { 65 + t.Errorf("SponsorUsername.TableName() = %q, want %q", su.TableName(), "github_sponsor_usernames") 66 + } 67 + } 68 + ``` 69 + 70 + - [ ] **Step 2: Run test to verify it fails** 71 + 72 + Run: `cd cmd/sponsor-panel && go test -run TestModelsCompile -count=1 ./...` 73 + Expected: compilation error (old `models.go` still has `pgxpool` imports, no `TableName()` methods) 74 + 75 + - [ ] **Step 3: Rewrite `models.go` with GORM models** 76 + 77 + Replace the entire file with GORM-tagged structs. Key changes: 78 + - `User` -> `PanelUser` (avoids collision with `internal/models.Account`; table stays `users`) 79 + - `db:"..."` tags -> `gorm:"..."` tags 80 + - `pgxpool` helper functions -> GORM helper functions 81 + - Keep `SponsorshipData` struct and `IsSponsorAtTier` method unchanged (they're pure logic) 82 + 83 + ```go 84 + package main 85 + 86 + import ( 87 + "encoding/json" 88 + "log/slog" 89 + "time" 90 + 91 + "gorm.io/gorm" 92 + ) 93 + 94 + // PanelUser represents an authenticated user with cached sponsorship data. 95 + type PanelUser struct { 96 + ID uint `json:"id" gorm:"primaryKey"` 97 + GitHubID *int64 `json:"github_id" gorm:"uniqueIndex"` 98 + PatreonID *string `json:"patreon_id" gorm:"uniqueIndex"` 99 + Provider string `json:"provider" gorm:"not null;default:'github'"` 100 + Login string `json:"login" gorm:"not null"` 101 + AvatarURL string `json:"avatar_url"` 102 + Name string `json:"name"` 103 + Email string `json:"email"` 104 + SponsorshipData string `json:"-" gorm:"type:jsonb"` 105 + LastSponsorshipCheck time.Time `json:"last_sponsorship_check"` 106 + CreatedAt time.Time `json:"created_at"` 107 + UpdatedAt time.Time `json:"updated_at"` 108 + } 109 + 110 + func (PanelUser) TableName() string { return "users" } 111 + 112 + // SponsorshipData represents the cached GraphQL response. 113 + type SponsorshipData struct { 114 + IsActive bool `json:"is_active"` 115 + MonthlyAmount int `json:"monthly_amount_cents"` 116 + TierName string `json:"tier_name"` 117 + PrivacyLevel string `json:"privacy_level"` 118 + } 119 + 120 + // IsSponsorAtTier returns true if user sponsors at or above the given amount (in cents). 121 + func (u *PanelUser) IsSponsorAtTier(minCents int) bool { 122 + if u.SponsorshipData == "" { 123 + return false 124 + } 125 + 126 + var data SponsorshipData 127 + if err := json.Unmarshal([]byte(u.SponsorshipData), &data); err != nil { 128 + slog.Error("IsSponsorAtTier: failed to parse sponsorship data", "user_id", u.ID, "err", err) 129 + return false 130 + } 131 + 132 + return data.IsActive && data.MonthlyAmount >= minCents 133 + } 134 + 135 + // LogoSubmission represents a logo submission. 136 + type LogoSubmission struct { 137 + ID uint `json:"id" gorm:"primaryKey"` 138 + UserID uint `json:"user_id" gorm:"not null"` 139 + CompanyName string `json:"company_name" gorm:"not null"` 140 + Website string `json:"website" gorm:"not null"` 141 + LogoURL string `json:"logo_url"` 142 + GitHubIssueURL string `json:"github_issue_url"` 143 + GitHubIssueNumber int `json:"github_issue_number"` 144 + SubmittedAt time.Time `json:"submitted_at" gorm:"autoCreateTime"` 145 + } 146 + 147 + func (LogoSubmission) TableName() string { return "logo_submissions" } 148 + 149 + // SponsorUsername represents a synced sponsor username (user or org). 150 + type SponsorUsername struct { 151 + ID uint `json:"id" gorm:"primaryKey"` 152 + Username string `json:"username" gorm:"uniqueIndex;not null"` 153 + EntityType string `json:"entity_type" gorm:"not null"` 154 + MonthlyAmountCents int `json:"monthly_amount_cents" gorm:"default:0"` 155 + TierName string `json:"tier_name"` 156 + IsActive bool `json:"is_active" gorm:"default:true;index"` 157 + SyncedAt time.Time `json:"synced_at"` 158 + CreatedAt time.Time `json:"created_at"` 159 + } 160 + 161 + func (SponsorUsername) TableName() string { return "github_sponsor_usernames" } 162 + 163 + // PanelModels returns all sponsor-panel models for AutoMigrate. 164 + func PanelModels() []interface{} { 165 + return []interface{}{ 166 + &PanelUser{}, 167 + &LogoSubmission{}, 168 + &SponsorUsername{}, 169 + } 170 + } 171 + 172 + // --- DB helper functions (GORM) --- 173 + 174 + // getUserByID retrieves a user by ID from the database. 175 + func getUserByID(db *gorm.DB, userID int) (*PanelUser, error) { 176 + var user PanelUser 177 + if err := db.First(&user, userID).Error; err != nil { 178 + slog.Error("getUserByID: user not found", "user_id", userID, "err", err) 179 + return nil, err 180 + } 181 + return &user, nil 182 + } 183 + 184 + // upsertUser creates or updates a GitHub user in the database. 185 + func upsertUser(db *gorm.DB, user *PanelUser) error { 186 + var existing PanelUser 187 + result := db.Where("github_id = ?", user.GitHubID).First(&existing) 188 + if result.Error == nil { 189 + // Update existing 190 + existing.Login = user.Login 191 + existing.AvatarURL = user.AvatarURL 192 + existing.Name = user.Name 193 + existing.Email = user.Email 194 + existing.SponsorshipData = user.SponsorshipData 195 + existing.LastSponsorshipCheck = time.Now() 196 + if err := db.Save(&existing).Error; err != nil { 197 + return err 198 + } 199 + *user = existing 200 + return nil 201 + } 202 + 203 + // Insert new 204 + user.Provider = "github" 205 + return db.Create(user).Error 206 + } 207 + 208 + // upsertPatreonUser creates or updates a Patreon user in the database. 209 + func upsertPatreonUser(db *gorm.DB, user *PanelUser) error { 210 + var existing PanelUser 211 + result := db.Where("patreon_id = ?", user.PatreonID).First(&existing) 212 + if result.Error == nil { 213 + existing.Login = user.Login 214 + existing.AvatarURL = user.AvatarURL 215 + existing.Name = user.Name 216 + existing.Email = user.Email 217 + existing.SponsorshipData = user.SponsorshipData 218 + existing.LastSponsorshipCheck = time.Now() 219 + if err := db.Save(&existing).Error; err != nil { 220 + return err 221 + } 222 + *user = existing 223 + return nil 224 + } 225 + 226 + user.Provider = "patreon" 227 + return db.Create(user).Error 228 + } 229 + 230 + // createLogoSubmission creates a logo submission in the database. 231 + func createLogoSubmission(db *gorm.DB, submission *LogoSubmission) error { 232 + return db.Create(submission).Error 233 + } 234 + 235 + // getActiveSponsorsByUsernames returns active sponsors matching any of the given usernames. 236 + func getActiveSponsorsByUsernames(db *gorm.DB, usernames []string) ([]*SponsorUsername, error) { 237 + if len(usernames) == 0 { 238 + return nil, nil 239 + } 240 + 241 + var sponsors []*SponsorUsername 242 + err := db.Where("username IN ? AND is_active = ?", usernames, true). 243 + Order("monthly_amount_cents DESC"). 244 + Find(&sponsors).Error 245 + return sponsors, err 246 + } 247 + 248 + // upsertSponsorUsername inserts or updates a sponsor username. 249 + func upsertSponsorUsername(db *gorm.DB, sponsor *SponsorUsername) error { 250 + var existing SponsorUsername 251 + result := db.Where("username = ?", sponsor.Username).First(&existing) 252 + if result.Error == nil { 253 + existing.EntityType = sponsor.EntityType 254 + existing.MonthlyAmountCents = sponsor.MonthlyAmountCents 255 + existing.TierName = sponsor.TierName 256 + existing.IsActive = sponsor.IsActive 257 + existing.SyncedAt = time.Now() 258 + if err := db.Save(&existing).Error; err != nil { 259 + return err 260 + } 261 + sponsor.ID = existing.ID 262 + sponsor.CreatedAt = existing.CreatedAt 263 + return nil 264 + } 265 + 266 + return db.Create(sponsor).Error 267 + } 268 + 269 + // markInactiveSponsorsNotIn marks all sponsors as inactive that are not in the given usernames list. 270 + func markInactiveSponsorsNotIn(db *gorm.DB, usernames []string) (int64, error) { 271 + var result *gorm.DB 272 + if len(usernames) == 0 { 273 + result = db.Model(&SponsorUsername{}).Where("is_active = ?", true).Update("is_active", false) 274 + } else { 275 + result = db.Model(&SponsorUsername{}). 276 + Where("username NOT IN ? AND is_active = ?", usernames, true). 277 + Update("is_active", false) 278 + } 279 + return result.RowsAffected, result.Error 280 + } 281 + ``` 282 + 283 + - [ ] **Step 4: Run test to verify it passes** 284 + 285 + Run: `cd cmd/sponsor-panel && go test -run TestModelsCompile -count=1 ./...` 286 + Expected: PASS 287 + 288 + - [ ] **Step 5: Commit** 289 + 290 + ```bash 291 + git add cmd/sponsor-panel/models.go cmd/sponsor-panel/models_test.go 292 + git commit -m "refactor(sponsor-panel): rewrite models with GORM tags and helpers" 293 + ``` 294 + 295 + --- 296 + 297 + ### Task 2: Delete Raw SQL Migrations 298 + 299 + Since GORM AutoMigrate handles schema creation, the hand-written `migrations.go` is no longer needed. 300 + 301 + > **Dependency:** Task 1 must be committed first. Task 1 rewrites `models.go` which defines the DB helper functions that replaced the ones previously in `models.go`. Deleting `migrations.go` before Task 1 is committed will cause additional unresolvable compilation errors. 302 + 303 + **Files:** 304 + - Delete: `cmd/sponsor-panel/migrations.go` 305 + 306 + - [ ] **Step 1: Delete `migrations.go`** 307 + 308 + ```bash 309 + git rm cmd/sponsor-panel/migrations.go 310 + ``` 311 + 312 + - [ ] **Step 2: Verify the build still compiles** 313 + 314 + Run: `cd cmd/sponsor-panel && go build ./...` 315 + Expected: compilation errors -- `runMigrations` is called in `main.go` but no longer defined. This is expected and will be fixed in Task 3. 316 + 317 + - [ ] **Step 3: Commit (allow build break, fixed in next task)** 318 + 319 + ```bash 320 + git commit -m "refactor(sponsor-panel): remove raw SQL migrations (replaced by GORM AutoMigrate)" 321 + ``` 322 + 323 + --- 324 + 325 + ### Task 3: Swap `pgxpool.Pool` for `gorm.DB` in `main.go` 326 + 327 + Replace database connection setup, migration call, and `Server` struct to use GORM. 328 + 329 + **Files:** 330 + - Modify: `cmd/sponsor-panel/main.go` 331 + 332 + - [ ] **Step 1: Update the `Server` struct** 333 + 334 + Change `pool *pgxpool.Pool` to `db *gorm.DB`. 335 + 336 + In the import block: 337 + - Remove: `"github.com/jackc/pgx/v5/pgxpool"` 338 + - Add: `"gorm.io/driver/postgres"` and `"gorm.io/gorm"` 339 + 340 + ```go 341 + // Server holds the application dependencies. 342 + type Server struct { 343 + db *gorm.DB 344 + ghClient *gh.Client 345 + oauth *oauth2.Config 346 + patreonOAuth *oauth2.Config 347 + patreonCampaignID string 348 + patreonFiftyPlusSpons map[string]bool 349 + discordInvite string 350 + fiftyPlusSponsors map[string]bool 351 + sessionStore *sessions.CookieStore 352 + cookieSecure bool 353 + bucketName string 354 + s3Client *s3.Client 355 + } 356 + ``` 357 + 358 + - [ ] **Step 2: Replace database connection + migration in `main()`** 359 + 360 + Replace lines 148-169 (the `pgxpool.New` / `pool.Ping` / `runMigrations` block) with: 361 + 362 + ```go 363 + // Connect to database via GORM 364 + slog.Debug("main: connecting to database via GORM") 365 + db, err := gorm.Open(postgres.Open(*databaseURL), &gorm.Config{}) 366 + if err != nil { 367 + slog.Error("failed to connect to database", "err", err) 368 + os.Exit(1) 369 + } 370 + slog.Info("main: database connection established") 371 + 372 + // Run GORM AutoMigrate 373 + slog.Debug("main: running GORM auto-migration") 374 + if err := db.AutoMigrate(PanelModels()...); err != nil { 375 + slog.Error("failed to auto-migrate", "err", err) 376 + os.Exit(1) 377 + } 378 + slog.Info("main: auto-migration completed") 379 + ``` 380 + 381 + - [ ] **Step 3: Update sync loop call** 382 + 383 + Change: 384 + ```go 385 + go startSyncLoop(syncCtx, pool, *githubToken) 386 + ``` 387 + To: 388 + ```go 389 + go startSyncLoop(syncCtx, db, *githubToken) 390 + ``` 391 + 392 + - [ ] **Step 4: Update `Server` initialization** 393 + 394 + Change `pool: pool,` to `db: db,` 395 + 396 + Remove the `defer pool.Close()` line (GORM manages its own connection pool; if you want explicit close, use `sqlDB, _ := db.DB(); defer sqlDB.Close()`). 397 + 398 + - [ ] **Step 5: Verify build compiles** 399 + 400 + Run: `cd cmd/sponsor-panel && go build ./...` 401 + Expected: compilation errors in `oauth.go`, `patreon_oauth.go`, `handlers.go`, `sync_sponsors.go` -- they still reference `s.pool` and old function signatures. This is expected and fixed in Tasks 4-6. 402 + 403 + - [ ] **Step 6: Commit** 404 + 405 + ```bash 406 + git add cmd/sponsor-panel/main.go 407 + git commit -m "refactor(sponsor-panel): swap pgxpool for gorm.DB in Server and main()" 408 + ``` 409 + 410 + --- 411 + 412 + ### Task 4: Update OAuth Handlers to Use GORM 413 + 414 + Replace all `pool`-based DB calls in `oauth.go` with the new GORM helper functions. 415 + 416 + **Files:** 417 + - Modify: `cmd/sponsor-panel/oauth.go` 418 + 419 + - [ ] **Step 1: Update imports** 420 + 421 + Remove: `"github.com/jackc/pgx/v5/pgxpool"` 422 + 423 + - [ ] **Step 2: Update `fetchSponsorship` signature** 424 + 425 + Change: 426 + ```go 427 + func fetchSponsorship(ctx context.Context, pool *pgxpool.Pool, token string, ...) (string, error) { 428 + ``` 429 + To: 430 + ```go 431 + func fetchSponsorship(ctx context.Context, db *gorm.DB, token string, ...) (string, error) { 432 + ``` 433 + 434 + **Important:** Keep `ctx context.Context` as the first parameter. Although the GORM DB helpers no longer need it, `fetchSponsorship` internally calls `fetchSponsorshipForEntity(ctx, ...)` and `fetchUserOrganizationsWithSponsorship(ctx, ...)` which make raw HTTP requests and require a context for cancellation/timeout. 435 + 436 + Update the `getActiveSponsorsByUsernames` call inside: 437 + ```go 438 + // Old: 439 + syncedSponsors, err := getActiveSponsorsByUsernames(ctx, pool, usernames) 440 + // New: 441 + syncedSponsors, err := getActiveSponsorsByUsernames(db, usernames) 442 + ``` 443 + 444 + - [ ] **Step 3: Update `callbackHandler`** 445 + 446 + Change all `s.pool` references: 447 + ```go 448 + // Old: 449 + sponsorData, err := fetchSponsorship(r.Context(), s.pool, token.AccessToken, ...) 450 + // New: 451 + sponsorData, err := fetchSponsorship(r.Context(), s.db, token.AccessToken, ...) 452 + 453 + // Old: 454 + if err := upsertUser(r.Context(), s.pool, user); err != nil { 455 + // New: 456 + if err := upsertUser(s.db, user); err != nil { 457 + ``` 458 + 459 + **Critical: Cast `user.ID` to `int` when storing in session.** Since `PanelUser.ID` is `uint` but gorilla/sessions gob-encodes the value, and `getSessionUser` reads it back with `.(int)`, the type assertion will fail at runtime if `uint` is stored. Fix in `callbackHandler`: 460 + ```go 461 + // Old: 462 + session.Values["user_id"] = user.ID 463 + // New: 464 + session.Values["user_id"] = int(user.ID) 465 + ``` 466 + 467 + - [ ] **Step 4: Update `getSessionUser`** 468 + 469 + Change: 470 + ```go 471 + // Old: 472 + return getUserByID(r.Context(), s.pool, userID) 473 + // New (both call sites): 474 + return getUserByID(s.db, userID) 475 + ``` 476 + 477 + - [ ] **Step 5: Update `User` references to `PanelUser`** 478 + 479 + Throughout `oauth.go`, change: 480 + - `user := &User{` -> `user := &PanelUser{` 481 + - Any type annotations referencing `*User` -> `*PanelUser` 482 + 483 + - [ ] **Step 6: Verify file compiles in isolation** 484 + 485 + Run: `cd cmd/sponsor-panel && go vet ./...` 486 + 487 + - [ ] **Step 7: Commit** 488 + 489 + ```bash 490 + git add cmd/sponsor-panel/oauth.go 491 + git commit -m "refactor(sponsor-panel): update oauth.go to use GORM helpers" 492 + ``` 493 + 494 + --- 495 + 496 + ### Task 5: Update Patreon OAuth and Handlers 497 + 498 + **Files:** 499 + - Modify: `cmd/sponsor-panel/patreon_oauth.go` 500 + - Modify: `cmd/sponsor-panel/handlers.go` 501 + 502 + - [ ] **Step 1: Update `patreon_oauth.go`** 503 + 504 + Change: 505 + ```go 506 + // Old: 507 + if err := upsertPatreonUser(r.Context(), s.pool, user); err != nil { 508 + // New: 509 + if err := upsertPatreonUser(s.db, user); err != nil { 510 + ``` 511 + 512 + Change `user := &User{` to `user := &PanelUser{`. 513 + 514 + **Critical: Cast `user.ID` to `int` when storing in session** (same fix as `callbackHandler` in Task 4): 515 + ```go 516 + // Old: 517 + session.Values["user_id"] = user.ID 518 + // New: 519 + session.Values["user_id"] = int(user.ID) 520 + ``` 521 + 522 + - [ ] **Step 2: Update `handlers.go`** 523 + 524 + Change: 525 + ```go 526 + // Old: 527 + if err := createLogoSubmission(r.Context(), s.pool, submission); err != nil { 528 + // New: 529 + if err := createLogoSubmission(s.db, submission); err != nil { 530 + ``` 531 + 532 + Update `LogoSubmission` struct field `UserID` from `int` to `uint` if needed (GORM primaryKey is `uint`). Check the `submission` initialization in `logoHandler` -- `user.ID` is now `uint`, so the assignment should work directly. 533 + 534 + - [ ] **Step 3: Verify full build** 535 + 536 + Run: `cd cmd/sponsor-panel && go build ./...` 537 + Expected: PASS (compiles cleanly) 538 + 539 + - [ ] **Step 4: Commit** 540 + 541 + ```bash 542 + git add cmd/sponsor-panel/patreon_oauth.go cmd/sponsor-panel/handlers.go 543 + git commit -m "refactor(sponsor-panel): update patreon_oauth and handlers to use GORM" 544 + ``` 545 + 546 + --- 547 + 548 + ### Task 6: Update Sync Sponsors to Use GORM 549 + 550 + **Files:** 551 + - Modify: `cmd/sponsor-panel/sync_sponsors.go` 552 + 553 + - [ ] **Step 1: Update function signatures** 554 + 555 + Change: 556 + ```go 557 + // Old: 558 + func syncSponsors(ctx context.Context, pool *pgxpool.Pool, ghToken string) error { 559 + // New: 560 + func syncSponsors(ctx context.Context, db *gorm.DB, ghToken string) error { 561 + ``` 562 + 563 + ```go 564 + // Old: 565 + func startSyncLoop(ctx context.Context, pool *pgxpool.Pool, ghToken string) { 566 + // New: 567 + func startSyncLoop(ctx context.Context, db *gorm.DB, ghToken string) { 568 + ``` 569 + 570 + - [ ] **Step 2: Update DB call sites** 571 + 572 + ```go 573 + // Old: 574 + if err := upsertSponsorUsername(ctx, pool, sponsor); err != nil { 575 + // New: 576 + if err := upsertSponsorUsername(db, sponsor); err != nil { 577 + 578 + // Old: 579 + inactiveCount, err := markInactiveSponsorsNotIn(ctx, pool, allSponsors) 580 + // New: 581 + inactiveCount, err := markInactiveSponsorsNotIn(db, allSponsors) 582 + ``` 583 + 584 + - [ ] **Step 3: Update imports** 585 + 586 + Remove: `"github.com/jackc/pgx/v5/pgxpool"` 587 + Add: `"gorm.io/gorm"` (if not already imported) 588 + 589 + - [ ] **Step 4: Update `startSyncLoop` internal calls** 590 + 591 + ```go 592 + // Old: 593 + if err := syncSponsors(ctx, pool, ghToken); err != nil { 594 + // New: 595 + if err := syncSponsors(ctx, db, ghToken); err != nil { 596 + ``` 597 + 598 + - [ ] **Step 5: Full build + vet** 599 + 600 + Run: `cd cmd/sponsor-panel && go build ./... && go vet ./...` 601 + Expected: PASS 602 + 603 + - [ ] **Step 6: Commit** 604 + 605 + ```bash 606 + git add cmd/sponsor-panel/sync_sponsors.go 607 + git commit -m "refactor(sponsor-panel): update sync_sponsors to use GORM" 608 + ``` 609 + 610 + --- 611 + 612 + ### Task 7: Update Dashboard (Type Rename) 613 + 614 + `dashboard.go` doesn't call DB directly but references the `User` type via `getSessionUser` and `SponsorshipData`. 615 + 616 + **Files:** 617 + - Modify: `cmd/sponsor-panel/dashboard.go` 618 + 619 + - [ ] **Step 1: Update type references** 620 + 621 + If `getSessionUser` now returns `*PanelUser`, update any explicit type annotations in `dashboard.go`. The `SponsorshipData` struct is unchanged so JSON unmarshaling still works. The `user.ID` field is now `uint` instead of `int` -- check that `session.Values["user_id"]` cast works (it stores `int`; you may need `int(user.ID)` when saving to session and `uint(userID)` when reading). 622 + 623 + - [ ] **Step 2: Verify the session ID type handling** 624 + 625 + In `oauth.go`'s `callbackHandler`, the session stores `user.ID`: 626 + ```go 627 + session.Values["user_id"] = int(user.ID) // cast uint -> int for session compatibility 628 + ``` 629 + 630 + In `getSessionUser`: 631 + ```go 632 + userID, ok := session.Values["user_id"].(int) 633 + // ... 634 + return getUserByID(s.db, userID) // getUserByID takes int, casts internally 635 + ``` 636 + 637 + This works because `getUserByID` takes `int` and GORM's `First(&user, userID)` handles the type. 638 + 639 + - [ ] **Step 3: Full build and test** 640 + 641 + Run: `cd cmd/sponsor-panel && go build ./... && go test ./...` 642 + Expected: PASS 643 + 644 + - [ ] **Step 4: Commit** 645 + 646 + ```bash 647 + git add cmd/sponsor-panel/dashboard.go cmd/sponsor-panel/oauth.go 648 + git commit -m "refactor(sponsor-panel): fix type references for PanelUser rename" 649 + ``` 650 + 651 + --- 652 + 653 + ### Task 8: Add GORM AutoMigrate Unique Index for Provider+Login 654 + 655 + The old migration002 created a unique index on `(provider, login)`. GORM AutoMigrate won't create composite unique indexes from struct tags alone. Add an explicit index. 656 + 657 + **Files:** 658 + - Modify: `cmd/sponsor-panel/models.go` 659 + 660 + - [ ] **Step 1: Add composite unique index to PanelUser** 661 + 662 + Add a GORM hook or use the `gorm:"uniqueIndex:idx_users_provider_login"` tag on both fields: 663 + 664 + ```go 665 + type PanelUser struct { 666 + ID uint `json:"id" gorm:"primaryKey"` 667 + GitHubID *int64 `json:"github_id" gorm:"uniqueIndex"` 668 + PatreonID *string `json:"patreon_id" gorm:"uniqueIndex"` 669 + Provider string `json:"provider" gorm:"not null;default:'github';uniqueIndex:idx_users_provider_login"` 670 + Login string `json:"login" gorm:"not null;uniqueIndex:idx_users_provider_login"` 671 + AvatarURL string `json:"avatar_url"` 672 + Name string `json:"name"` 673 + Email string `json:"email"` 674 + SponsorshipData string `json:"-" gorm:"type:jsonb"` 675 + LastSponsorshipCheck time.Time `json:"last_sponsorship_check"` 676 + CreatedAt time.Time `json:"created_at"` 677 + UpdatedAt time.Time `json:"updated_at"` 678 + } 679 + ``` 680 + 681 + - [ ] **Step 2: Verify build** 682 + 683 + Run: `cd cmd/sponsor-panel && go build ./...` 684 + Expected: PASS 685 + 686 + - [ ] **Step 3: Commit** 687 + 688 + ```bash 689 + git add cmd/sponsor-panel/models.go 690 + git commit -m "refactor(sponsor-panel): add composite unique index for provider+login" 691 + ``` 692 + 693 + --- 694 + 695 + ### Task 9: Clean Up Unused pgx Dependency 696 + 697 + After all files are converted, `pgx` should no longer be imported by `cmd/sponsor-panel`. 698 + 699 + **Files:** 700 + - Verify: `cmd/sponsor-panel/*.go` 701 + 702 + - [ ] **Step 1: Search for lingering pgx references** 703 + 704 + Run: `grep -r "pgx\|pgxpool" cmd/sponsor-panel/` 705 + Expected: no matches 706 + 707 + - [ ] **Step 2: Run go mod tidy** 708 + 709 + Run: `go mod tidy` 710 + 711 + Note: `pgx` may still be in `go.mod` if other packages use it -- that's fine. We just want it removed from `cmd/sponsor-panel`'s imports. 712 + 713 + - [ ] **Step 3: Full test suite** 714 + 715 + Run: `npm test` 716 + Expected: PASS (runs `go test ./...` for the whole project) 717 + 718 + - [ ] **Step 4: Commit** 719 + 720 + ```bash 721 + git add go.mod go.sum 722 + git commit -m "chore: go mod tidy after sponsor-panel GORM migration" 723 + ``` 724 + 725 + --- 726 + 727 + ### Task 10: Manual Integration Test 728 + 729 + Since there's no test database fixture setup, verify the app starts and works against a real Postgres instance. 730 + 731 + - [ ] **Step 1: Start the dev server** 732 + 733 + Run: `npm run dev:sponsor-panel` (or equivalent with required env vars) 734 + 735 + Check logs for: 736 + - "database connection established" 737 + - "auto-migration completed" 738 + - No errors about missing columns or tables 739 + 740 + - [ ] **Step 2: Test login flow** 741 + 742 + Visit the app in a browser, complete OAuth login, verify: 743 + - User record is created in `users` table 744 + - Dashboard renders with correct sponsorship data 745 + - Session persistence works across page loads 746 + 747 + - [ ] **Step 3: Verify sponsor sync** 748 + 749 + Check logs for: 750 + - "syncSponsors: sync completed" within ~1 minute of startup 751 + - `github_sponsor_usernames` table is populated
+3 -1
go.mod
··· 16 16 github.com/google/subcommands v1.2.0 17 17 github.com/google/uuid v1.6.0 18 18 github.com/gorilla/sessions v1.4.0 19 - github.com/jackc/pgx/v5 v5.9.1 20 19 github.com/joho/godotenv v1.5.1 20 + github.com/orandin/slog-gorm v1.4.0 21 21 github.com/prometheus/client_golang v1.23.2 22 22 github.com/stretchr/testify v1.11.1 23 23 github.com/twitchtv/twirp v8.1.3+incompatible ··· 27 27 gopkg.in/mxpv/patreon-go.v1 v1.0.0-20171031001022-1d2f253ac700 28 28 gorm.io/driver/postgres v1.6.0 29 29 gorm.io/gorm v1.31.1 30 + gorm.io/plugin/prometheus v0.1.0 30 31 k8s.io/apimachinery v0.35.3 31 32 k8s.io/client-go v0.35.3 32 33 tailscale.com v1.96.4 ··· 116 117 github.com/huandu/xstrings v1.5.0 // indirect 117 118 github.com/jackc/pgpassfile v1.0.0 // indirect 118 119 github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect 120 + github.com/jackc/pgx/v5 v5.9.1 // indirect 119 121 github.com/jackc/puddle/v2 v2.2.2 // indirect 120 122 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 121 123 github.com/jinzhu/inflection v1.0.0 // indirect
+504
go.sum
··· 2 2 al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= 3 3 charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251120230642-dcccabe2cd63 h1:KgI+p678truaonNOQek4i+aJdWAtdpvFzz5lqHBaDeI= 4 4 charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251120230642-dcccabe2cd63/go.mod h1:bjsp2D+VGi56y8f53S7xCphcoqJb36vo3dBVh0RrpP8= 5 + cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 6 + cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 7 + cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 8 + cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 9 + cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 10 + cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 11 + cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 12 + cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 13 + cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 14 + cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 15 + cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 16 + cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 17 + cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 18 + cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 19 + cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 20 + cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 21 + cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 22 + cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 23 + cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 24 + cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 25 + cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 26 + cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= 27 + cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 28 + cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 29 + cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 30 + cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 31 + cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 32 + cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 33 + cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 34 + cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 35 + cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 36 + cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 37 + cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 5 38 dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= 6 39 dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= 40 + dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 7 41 github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= 8 42 github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0= 43 + github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 9 44 github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= 10 45 github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 46 + github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 11 47 github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= 12 48 github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= 13 49 github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= ··· 38 74 github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= 39 75 github.com/a-h/templ v0.3.1001 h1:yHDTgexACdJttyiyamcTHXr2QkIeVF1MukLy44EAhMY= 40 76 github.com/a-h/templ v0.3.1001/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= 77 + github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE= 78 + github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= 79 + github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 80 + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 81 + github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 82 + github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 83 + github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 84 + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= 41 85 github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 42 86 github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 43 87 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= ··· 84 128 github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= 85 129 github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= 86 130 github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= 131 + github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 132 + github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 87 133 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 88 134 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 89 135 github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= ··· 102 148 github.com/cavaliergopher/rpm v1.3.0/go.mod h1:vEumo1vvtrHM1Ov86f6+k8j7zNKOxQfHDCAIcR/36ZI= 103 149 github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 104 150 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 151 + github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 152 + github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 153 + github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 154 + github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 105 155 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 106 156 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 107 157 github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= ··· 118 168 github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= 119 169 github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= 120 170 github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= 171 + github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 172 + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 173 + github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 121 174 github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= 122 175 github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= 123 176 github.com/cli/go-gh/v2 v2.12.1 h1:SVt1/afj5FRAythyMV3WJKaUfDNsxXTIe7arZbwTWKA= 124 177 github.com/cli/go-gh/v2 v2.12.1/go.mod h1:+5aXmEOJsH9fc9mBHfincDwnS02j2AIA/DsTH0Bk5uw= 125 178 github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= 126 179 github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= 180 + github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 127 181 github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I= 128 182 github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= 129 183 github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= ··· 132 186 github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= 133 187 github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= 134 188 github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= 189 + github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 190 + github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 135 191 github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= 136 192 github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= 137 193 github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= ··· 152 208 github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 153 209 github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 154 210 github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 211 + github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 212 + github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 213 + github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 214 + github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 155 215 github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= 156 216 github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= 157 217 github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456 h1:CkmB2l68uhvRlwOTPrwnuitSxi/S3Cg4L5QYOcL9MBc= ··· 180 240 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= 181 241 github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM= 182 242 github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI= 243 + github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 244 + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 245 + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 183 246 github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I= 184 247 github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= 248 + github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 249 + github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 250 + github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 251 + github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 252 + github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 253 + github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 254 + github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 255 + github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 256 + github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 185 257 github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 186 258 github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 187 259 github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= ··· 194 266 github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= 195 267 github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= 196 268 github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= 269 + github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 197 270 github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 198 271 github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 199 272 github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 200 273 github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 201 274 github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= 202 275 github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= 276 + github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 277 + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 278 + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 279 + github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 280 + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 203 281 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= 204 282 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 283 + github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 284 + github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 285 + github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 286 + github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 287 + github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 288 + github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 289 + github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 290 + github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 291 + github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 292 + github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 293 + github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 294 + github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 295 + github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 296 + github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 297 + github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 298 + github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 299 + github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 300 + github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 301 + github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 302 + github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 303 + github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 304 + github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 305 + github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 306 + github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 307 + github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 308 + github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 205 309 github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= 206 310 github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= 311 + github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 312 + github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 313 + github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 314 + github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 315 + github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 316 + github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 317 + github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 318 + github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 319 + github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 320 + github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 321 + github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 207 322 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 208 323 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 209 324 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= ··· 214 329 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 215 330 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 216 331 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 332 + github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 333 + github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 334 + github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 335 + github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 336 + github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 337 + github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 338 + github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 339 + github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 340 + github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 217 341 github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= 218 342 github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 343 + github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 219 344 github.com/google/rpmpack v0.7.1 h1:YdWh1IpzOjBz60Wvdw0TU0A5NWP+JTVHA5poDqwMO2o= 220 345 github.com/google/rpmpack v0.7.1/go.mod h1:h1JL16sUTWCLI/c39ox1rDaTBo3BXUQGjczVJyK4toU= 221 346 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= ··· 224 349 github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= 225 350 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 226 351 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 352 + github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 353 + github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 227 354 github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= 228 355 github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= 229 356 github.com/goreleaser/chglog v0.7.3 h1:eCKJrvsDgG+F1F2fhwM6qX+S5yMiZgsQ4VNTPFl9qEM= ··· 236 363 github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 237 364 github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= 238 365 github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 366 + github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 367 + github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 239 368 github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= 240 369 github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 370 + github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 241 371 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 242 372 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 243 373 github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= ··· 256 386 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 257 387 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 258 388 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 389 + github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 390 + github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 391 + github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 392 + github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 259 393 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 260 394 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 395 + github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 396 + github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 261 397 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 262 398 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 399 + github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 400 + github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 263 401 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 264 402 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 403 + github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 265 404 github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d h1:RnWZeH8N8KXfbwMTex/KKMYMj0FJRCF6tQubUuQ02GM= 266 405 github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q= 267 406 github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= 268 407 github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= 269 408 github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= 270 409 github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 410 + github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 411 + github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 412 + github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 271 413 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 414 + github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 272 415 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 273 416 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 274 417 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= ··· 290 433 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 291 434 github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= 292 435 github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 436 + github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 437 + github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 293 438 github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 294 439 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 295 440 github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= ··· 297 442 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 298 443 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 299 444 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 445 + github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 446 + github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 300 447 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 301 448 github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= 302 449 github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= ··· 304 451 github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 305 452 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 306 453 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 454 + github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 455 + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 307 456 github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= 308 457 github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= 309 458 github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= 310 459 github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= 311 460 github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= 312 461 github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= 462 + github.com/orandin/slog-gorm v1.4.0 h1:FgA8hJufF9/jeNSYoEXmHPPBwET2gwlF3B85JdpsTUU= 463 + github.com/orandin/slog-gorm v1.4.0/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA= 313 464 github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= 314 465 github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= 466 + github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 467 + github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 468 + github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 315 469 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 316 470 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 317 471 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 318 472 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 319 473 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 474 + github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 475 + github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 476 + github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 477 + github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 478 + github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= 479 + github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= 480 + github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= 481 + github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= 320 482 github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= 321 483 github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= 484 + github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 485 + github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 486 + github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 487 + github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 488 + github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= 489 + github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 490 + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 322 491 github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 323 492 github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 493 + github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 494 + github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 495 + github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 496 + github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 497 + github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= 498 + github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= 499 + github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= 324 500 github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= 325 501 github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= 502 + github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 503 + github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 504 + github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 505 + github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 506 + github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 507 + github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= 508 + github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= 509 + github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= 326 510 github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 327 511 github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 328 512 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 329 513 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 514 + github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 515 + github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 516 + github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 330 517 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 331 518 github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 332 519 github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= ··· 335 522 github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 336 523 github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= 337 524 github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 525 + github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 526 + github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 527 + github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 338 528 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 339 529 github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= 340 530 github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= ··· 347 537 github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= 348 538 github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 349 539 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 540 + github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 541 + github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 542 + github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 350 543 github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 351 544 github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 352 545 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 353 546 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 354 547 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 355 548 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 549 + github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 550 + github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 551 + github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 356 552 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 357 553 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 358 554 github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= ··· 363 559 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 364 560 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 365 561 github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 562 + github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= 563 + github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= 366 564 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= 367 565 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= 368 566 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 369 567 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 370 568 github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 371 569 github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 570 + github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 571 + github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 572 + github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 573 + github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 372 574 gitlab.alpinelinux.org/alpine/go v0.10.1 h1:QoidnfDyC9yeIMj+CvYVyjlroZD/Kl7JRXGEQBvY5XM= 373 575 gitlab.alpinelinux.org/alpine/go v0.10.1/go.mod h1:zwds+1zTmPDgwf/9lOzzn+oZVBr6jyfVgH3zuwkfkzc= 374 576 gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= 375 577 gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= 578 + go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 579 + go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 580 + go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 581 + go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 582 + go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 376 583 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 377 584 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 378 585 go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= ··· 381 588 go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 382 589 go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= 383 590 go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= 591 + golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 592 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 593 + golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 594 + golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 595 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 596 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 597 + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 384 598 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 385 599 golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= 386 600 golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= 601 + golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 602 + golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 603 + golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 604 + golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 605 + golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 606 + golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 607 + golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 608 + golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 609 + golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 610 + golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 387 611 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= 388 612 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= 389 613 golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= 390 614 golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= 615 + golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 616 + golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 617 + golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 618 + golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 619 + golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 620 + golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 621 + golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 622 + golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 623 + golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 624 + golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 625 + golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 626 + golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 627 + golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 628 + golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 629 + golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 630 + golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 631 + golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 632 + golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 633 + golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 634 + golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 635 + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 636 + golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 391 637 golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= 392 638 golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= 639 + golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 640 + golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 641 + golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 642 + golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 643 + golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 644 + golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 645 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 646 + golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 647 + golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 648 + golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 649 + golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 650 + golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 651 + golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 652 + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 653 + golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 654 + golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 655 + golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 656 + golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 657 + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 658 + golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 659 + golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 660 + golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 661 + golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 662 + golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 663 + golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 664 + golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 665 + golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 666 + golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 667 + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 668 + golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 393 669 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 670 + golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 671 + golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 672 + golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 673 + golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 674 + golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 675 + golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 394 676 golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= 395 677 golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= 678 + golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 679 + golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 680 + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 681 + golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 682 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 683 + golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 684 + golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= 685 + golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= 686 + golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 396 687 golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= 397 688 golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= 689 + golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 690 + golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 691 + golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 692 + golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 693 + golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 694 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 695 + golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 696 + golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 697 + golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 698 + golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 699 + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 700 + golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 701 + golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 398 702 golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= 399 703 golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 704 + golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 705 + golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 706 + golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 707 + golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 708 + golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 709 + golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 710 + golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 711 + golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 712 + golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 713 + golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 714 + golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 715 + golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 716 + golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 400 717 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 718 + golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 719 + golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 720 + golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 721 + golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 722 + golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 723 + golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 724 + golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 725 + golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 726 + golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 727 + golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 728 + golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 729 + golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 730 + golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 731 + golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 732 + golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 733 + golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 734 + golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 735 + golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 401 736 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 402 737 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 403 738 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 739 + golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 404 740 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 741 + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 742 + golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 743 + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 405 744 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 745 + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 406 746 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 747 + golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 748 + golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 407 749 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 750 + golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 751 + golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 752 + golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 408 753 golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= 409 754 golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 410 755 golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= 411 756 golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= 412 757 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 758 + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 759 + golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 760 + golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 413 761 golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= 414 762 golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= 763 + golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 764 + golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 765 + golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 766 + golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 767 + golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 415 768 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 769 + golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 770 + golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 771 + golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 416 772 golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= 417 773 golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= 774 + golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 775 + golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 776 + golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 418 777 golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= 419 778 golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 420 779 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 780 + golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 781 + golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 782 + golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 783 + golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 784 + golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 785 + golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 786 + golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 787 + golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 788 + golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 789 + golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 790 + golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 791 + golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 792 + golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 793 + golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 794 + golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 795 + golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 796 + golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 797 + golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 798 + golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 799 + golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 800 + golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 801 + golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 802 + golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 803 + golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 804 + golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 805 + golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 806 + golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 807 + golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 808 + golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 809 + golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 810 + golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 811 + golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 812 + golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 813 + golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 814 + golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 815 + golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 816 + golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 817 + golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 818 + golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 819 + golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 820 + golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 421 821 golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= 422 822 golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= 423 823 golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= 424 824 golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= 825 + golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 826 + golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 827 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 828 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 829 + google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 830 + google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 831 + google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 832 + google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 833 + google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 834 + google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 835 + google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 836 + google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 837 + google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 838 + google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 839 + google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 840 + google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 841 + google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 842 + google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 843 + google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 844 + google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 845 + google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 846 + google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 847 + google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 848 + google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 849 + google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 850 + google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 851 + google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 852 + google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 853 + google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 854 + google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 855 + google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 856 + google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 857 + google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 858 + google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 859 + google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 860 + google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 861 + google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 862 + google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 863 + google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 864 + google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 865 + google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 866 + google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 867 + google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 868 + google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 869 + google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 870 + google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 871 + google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 872 + google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 873 + google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 874 + google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 875 + google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 876 + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 877 + google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 878 + google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 879 + google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 880 + google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 881 + google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 882 + google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 883 + google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 884 + google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 885 + google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 886 + google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 887 + google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 888 + google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 889 + google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 890 + google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 891 + google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 892 + google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 893 + google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 894 + google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 895 + google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 896 + google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 897 + google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 898 + google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 899 + google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 900 + google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 901 + google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 902 + google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 903 + google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 904 + google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 905 + google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 906 + google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 907 + google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 908 + google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 425 909 google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= 426 910 google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 911 + gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 427 912 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 913 + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 428 914 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 429 915 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 430 916 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 917 + gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 431 918 gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= 432 919 gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 433 920 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= ··· 440 927 gopkg.in/mxpv/patreon-go.v1 v1.0.0-20171031001022-1d2f253ac700/go.mod h1:IZaw6NfbSsGszLfPbo9LLlxLIx17eMHWe4cxpM8wUMk= 441 928 gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 442 929 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 930 + gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 443 931 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 932 + gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 933 + gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 934 + gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 444 935 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 445 936 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 446 937 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ··· 448 939 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 449 940 gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= 450 941 gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= 942 + gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 451 943 gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= 452 944 gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= 945 + gorm.io/plugin/prometheus v0.1.0 h1:kDQwAfCUsT9D6jDUpIp7pnc7bCJu/6voM8I/BmFjxUQ= 946 + gorm.io/plugin/prometheus v0.1.0/go.mod h1:5nrc/JrWCUNoDXCY4eOae/FK/J5WjQ0axXuFusCzdTc= 947 + honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 948 + honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 949 + honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 950 + honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 951 + honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 952 + honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 953 + honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 453 954 honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLaf0O3aOenC0Z4ZafIWSpjUzsQaNho= 454 955 honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ= 455 956 k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ= ··· 470 971 pault.ag/go/debian v0.19.0/go.mod h1:1LMojDAazlJ7cA5Ne6H2ZHD4hh3o8NRiW+MpvQRji2o= 471 972 pault.ag/go/topsort v0.1.1 h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4= 472 973 pault.ag/go/topsort v0.1.1/go.mod h1:r1kc/L0/FZ3HhjezBIPaNVhkqv8L0UJ9bxRuHRVZ0q4= 974 + rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 975 + rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 976 + rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 473 977 sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= 474 978 sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 475 979 sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
+1 -1
web/htmx/htmx_templ.go
··· 1 1 // Code generated by templ - DO NOT EDIT. 2 2 3 - // templ: version: v0.3.865 3 + // templ: version: v0.3.1001 4 4 package htmx 5 5 6 6 //lint:file-ignore SA4006 This context is only used if a nested component is present.