diff --git a/README.md b/README.md index 3b53819..dbcf1c6 100644 --- a/README.md +++ b/README.md @@ -221,19 +221,19 @@ Defaults live in `config/config.yaml` and are merged under `golem15.user.*`. Any ## CLI commands -| Command | Purpose | -|---------|---------| -| `user:require-password-change ` | Flag an account so its next session must set a new password. | -| `user:backfill-handles` | Assign a unique handle to every user row that still has none. | -| `user:token:create [--name api] [--scopes read,write] [--expires RFC3339]` | Create an application-wide API token and print its secret once. | -| `user:token:list` | List token metadata without secrets or hashes. | -| `user:token:revoke ` | Revoke a shared API token immediately. | +| Command | Purpose | Example | +|---------|---------|---------| +| `user:require-password-change ` | Flag an account so its next session must set a new password. Ends with `Password change required for `. | `bin/ user:require-password-change jane@example.com` | +| `user:backfill-handles` | Assign a unique handle to every user row that still has none, with a progress bar. Ends with `Assigned handles to N user(s)`. | `bin/ user:backfill-handles` | +| `user:token:create [--name api] [--scopes read,write] [--expires RFC3339] [--json]` | Create an application-wide API token and print its secret once (also under `-q`); `--json` prints the token and its details as one object. | `bin/ user:token:create jane@example.com --scopes read --json` | +| `user:token:list` | List token metadata without secrets or hashes; `--json` prints a JSON array with the expiry, revocation and last-use times. | `bin/ user:token:list --json` | +| `user:token:revoke ` | Revoke a shared API token immediately. | `bin/ user:token:revoke 12` | -Each command uses the database the serve command published or, run from the console, opens one from `database.dsn` (and loads the encryption keys from `app.key`) for its duration. +Each command uses the database the serve command published or, run from the console, opens one from `database.dsn` (and loads the encryption keys from `app.key`) for its duration. `bin/ help ` shows each command's long help and examples. The plugin logs through the component `user` (`log.components.user` in `config/log.yaml`). ## Dependencies -- The SummerCMS framework, `git.golem15.com/golem15/summercms` (modules `backpack`, `bonfire`, `bouncer`, `pact`, `party`, `surf` and others). +- The SummerCMS framework v0.4.0, `git.golem15.com/golem15/summercms` (modules `backpack`, `bonfire`, `bouncer`, `diary`, `pact`, `party`, `surf` and others). - gormigrate, GORM, golang-jwt and gocloud.dev, as pinned in `go.mod`. - testcontainers-go for the Postgres-backed tests. @@ -246,4 +246,6 @@ go test ./... # full run, starts Postgres with testcontainers (needs D `TestRegisterCORSPath` reads the host application's `config/http.yaml`, so run the tests inside an application checkout where the plugin is mounted at `plugins/golem15/user`. +`TestCommandsDocumented` (in `-short`) logs, without failing, each command whose help lacks a description, long help, examples or an option description (`bonfire.CheckDocs`). + The admin tests (`TestAdmin*`) boot the plugin with the admin API mounted on a database of their own and sign in backend administrators with a chosen permission set; the helpers are in `admin_harness_test.go`. diff --git a/classes/admin_actions.go b/classes/admin_actions.go index 4565d06..c35db91 100644 --- a/classes/admin_actions.go +++ b/classes/admin_actions.go @@ -3,7 +3,6 @@ package classes import ( "context" "fmt" - "log/slog" "strconv" "time" @@ -241,7 +240,7 @@ func ForceDeleteCleanup(ctx context.Context, db *gorm.DB, bucket *blob.Bucket, u // The rows are gone by now, so a blob that cannot be removed is // orphaned, not lost data: log it and carry on. if err := attach.DeleteKeys(ctx, bucket, keys); err != nil { - slog.Default().Warn("golem15.user: remove attachment blobs of a deleted user", "user_id", userID, "err", err) + pluginLog.Warn("golem15.user: remove attachment blobs of a deleted user", "user_id", userID, "err", err) } }) return nil diff --git a/classes/authorizer.go b/classes/authorizer.go index 2e4daad..82c1cda 100644 --- a/classes/authorizer.go +++ b/classes/authorizer.go @@ -2,7 +2,6 @@ package classes import ( "context" - "log/slog" "strings" "git.golem15.com/golem15/summercms/modules/backpack" @@ -36,7 +35,7 @@ func AuthorizeUser() lighthouse.AuthorizerFunc { func RegisterUserAuthorizer(app *backpack.App) error { svc, err := lighthouse.From(app) if err != nil { - slog.Default().Warn("golem15.user: lighthouse unavailable; user authorizer not registered", "err", err) + pluginLog.Warn("golem15.user: lighthouse unavailable; user authorizer not registered", "err", err) return nil } if svc == nil || svc.Registry() == nil { diff --git a/classes/avatar.go b/classes/avatar.go index 8b1baa4..9315e98 100644 --- a/classes/avatar.go +++ b/classes/avatar.go @@ -5,7 +5,6 @@ import ( "crypto/md5" "errors" "fmt" - "log/slog" "strconv" "strings" @@ -52,7 +51,7 @@ func AvatarURL(ctx context.Context, app *backpack.App, user *models.User, size i } url, err := f.Thumb(ctx, bucket, size, size, "auto") if err != nil { - slog.Default().Error("golem15.user: avatar thumb", "err", err) + pluginLog.Error("golem15.user: avatar thumb", "err", err) return "/" + attach.BlobKey(f.DiskName), nil } return url, nil diff --git a/classes/logger.go b/classes/logger.go new file mode 100644 index 0000000..8145ba2 --- /dev/null +++ b/classes/logger.go @@ -0,0 +1,7 @@ +package classes + +import "git.golem15.com/golem15/summercms/modules/diary" + +// pluginLog is the plugin's logger: its records carry component=user, so +// config/log.yaml can raise or lower them (log.components.user). +var pluginLog = diary.For("user") diff --git a/classes/session_user.go b/classes/session_user.go index 60ed74b..ccd4402 100644 --- a/classes/session_user.go +++ b/classes/session_user.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "log/slog" "net/http" "strconv" "strings" @@ -99,7 +98,7 @@ func EndRequestSession(ctx context.Context, app *backpack.App, w http.ResponseWr if app != nil && app.Config != nil { loaded, err := bouncer.LoadCookieConfig(app.Config) if err != nil { - slog.Default().Warn("golem15.user: end session: cookie config", "err", err) + pluginLog.Warn("golem15.user: end session: cookie config", "err", err) } else { cfg = loaded } @@ -126,7 +125,7 @@ func revokeSessionToken(ctx context.Context, app *backpack.App, raw string) { return } if err := bl.Add(ctx, jti, exp, time.Now()); err != nil { - slog.Default().Warn("golem15.user: end session: blacklist token", "err", err) + pluginLog.Warn("golem15.user: end session: blacklist token", "err", err) } } diff --git a/commands_documented_test.go b/commands_documented_test.go new file mode 100644 index 0000000..81703c4 --- /dev/null +++ b/commands_documented_test.go @@ -0,0 +1,21 @@ +package user + +import ( + "testing" + + "git.golem15.com/golem15/summercms/modules/bonfire" +) + +// TestCommandsDocumented logs every console command whose help lacks a +// description, long help, examples or an option description +// (bonfire.CheckDocs). It only warns: the framework's own coverage test +// fails on a gap, plugin repositories report it. +func TestCommandsDocumented(t *testing.T) { + commands := (&Plugin{}).Commands() + if len(commands) == 0 { + t.Fatal("the plugin declares no commands") + } + for _, problem := range bonfire.CheckDocs(commands) { + t.Logf("%s", problem) + } +} diff --git a/console/api_tokens.go b/console/api_tokens.go index 193482b..eaabd52 100644 --- a/console/api_tokens.go +++ b/console/api_tokens.go @@ -2,6 +2,7 @@ package console import ( "context" + "encoding/json" "fmt" "strconv" "strings" @@ -28,11 +29,25 @@ func APITokenCommands(app *backpack.App, withDB func(context.Context, func(*gorm return []bonfire.Command{ { Name: "user:token:create", Description: "Create one API token usable by application plugins", + Long: `Mints a personal API token that acts as the user with this e-mail, for +scripts and integrations that call the application's API. --scopes limits +what it may do (read, write, ai); --expires sets an RFC3339 expiry, and +without it the token does not expire. The secret is printed once, on its own +line (also under -q), and only its hash is stored, so keep it now; with +--json the token and its details are printed as one JSON object instead. + +Revoke a token with user:token:revoke and list them with user:token:list.`, Args: []bonfire.Arg{{Name: "email", Description: "E-mail of the user the token acts as", Required: true}}, Flags: []bonfire.Flag{ {Name: "name", Description: "What the token is for", Default: "api"}, {Name: "scopes", Description: "Comma-separated scopes: read, write, ai", Default: "read,write"}, {Name: "expires", Description: "RFC3339 expiry; empty means no expiry"}, + {Name: "json", Description: "Print the token and its details as JSON", Bare: true}, + }, + Examples: []bonfire.Example{ + {Command: "user:token:create jane@example.com --name backup-script --scopes read", Description: "A read-only token for a script"}, + {Command: "user:token:create jane@example.com --expires 2027-01-01T00:00:00Z", Description: "A read and write token that expires"}, + {Command: "user:token:create jane@example.com --json", Description: "Print the token and its details as JSON"}, }, Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error { email, _ := in.Argument("email") @@ -71,6 +86,17 @@ func APITokenCommands(app *backpack.App, withDB func(context.Context, func(*gorm if err := db.Create(token).Error; err != nil { return fmt.Errorf("user:token:create: %w", err) } + if flagOn(in, "json") { + return printJSON(out, map[string]any{ + "id": token.ID, + "user_id": token.UserID, + "email": user.Email, + "name": token.Name, + "token": secret, + "scopes": token.Scopes.Get(), + "expires_at": token.ExpiresAt, + }) + } out.Printf("token %d for %s, scopes %s\n", token.ID, email, strings.Join(token.Scopes.Get(), ",")) out.Println(secret) out.Success("user:token:create: store the secret now; it is not shown again") @@ -80,13 +106,25 @@ func APITokenCommands(app *backpack.App, withDB func(context.Context, func(*gorm }, { Name: "user:token:list", Description: "List API tokens", - Run: func(ctx context.Context, _ bonfire.Input, out bonfire.Output) error { + Long: `Lists every personal API token: its id, the user it acts as, its name, its +scopes and its state (active, expired or revoked). Secrets are never shown; +only their hashes are stored. --json prints the tokens as a JSON array with +their expiry, revocation and last use times.`, + Flags: []bonfire.Flag{ + {Name: "json", Description: "Print the tokens as JSON", Bare: true}, + }, + Examples: []bonfire.Example{ + {Command: "user:token:list", Description: "Show every token as a table"}, + {Command: "user:token:list --json", Description: "Print the tokens as JSON for a script"}, + }, + Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error { return open(ctx, func(db *gorm.DB) error { var tokens []models.APIToken if err := db.Order("id").Find(&tokens).Error; err != nil { return fmt.Errorf("user:token:list: %w", err) } rows := make([][]string, 0, len(tokens)) + list := make([]map[string]any, 0, len(tokens)) for _, token := range tokens { state := "active" if token.RevokedAt != nil { @@ -99,6 +137,20 @@ func APITokenCommands(app *backpack.App, withDB func(context.Context, func(*gorm name = *token.Name } rows = append(rows, []string{strconv.FormatUint(uint64(token.ID), 10), strconv.FormatUint(uint64(token.UserID), 10), name, strings.Join(token.Scopes.Get(), ","), state}) + list = append(list, map[string]any{ + "id": token.ID, + "user_id": token.UserID, + "name": token.Name, + "scopes": token.Scopes.Get(), + "state": state, + "expires_at": token.ExpiresAt, + "revoked_at": token.RevokedAt, + "last_used_at": token.LastUsedAt, + "created_at": token.CreatedAt, + }) + } + if flagOn(in, "json") { + return printJSON(out, list) } out.Table([]string{"id", "user", "name", "scopes", "state"}, rows) return nil @@ -107,7 +159,13 @@ func APITokenCommands(app *backpack.App, withDB func(context.Context, func(*gorm }, { Name: "user:token:revoke", Description: "Revoke an API token", + Long: `Revokes the active API token with this id (see user:token:list): requests +that present it are refused from now on. The token row stays, marked +revoked; a token that is already revoked or does not exist is an error.`, Args: []bonfire.Arg{{Name: "id", Description: "Token id", Required: true}}, + Examples: []bonfire.Example{ + {Command: "user:token:revoke 12", Description: "Revoke the token with id 12"}, + }, Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error { raw, _ := in.Argument("id") id, err := strconv.ParseUint(raw, 10, 64) @@ -129,3 +187,24 @@ func APITokenCommands(app *backpack.App, withDB func(context.Context, func(*gorm }, } } + +// printJSON writes v as indented JSON and a newline on out: data, printed +// under -q too. +func printJSON(out bonfire.Output, v any) error { + b, err := json.MarshalIndent(v, "", " ") + if err != nil { + return err + } + out.Println(string(b)) + return nil +} + +// flagOn reads a bare flag ("true" when given). +func flagOn(in bonfire.Input, name string) bool { + v, _ := in.Flag(name) + switch strings.ToLower(strings.TrimSpace(v)) { + case "true", "1", "yes": + return true + } + return false +} diff --git a/console/backfill_handles.go b/console/backfill_handles.go index 27f49a0..711751e 100644 --- a/console/backfill_handles.go +++ b/console/backfill_handles.go @@ -19,7 +19,18 @@ import ( func BackfillHandles(app *backpack.App) bonfire.Command { return bonfire.Command{ Name: "user:backfill-handles", - Description: "Assign unique handles to users that do not have one (D-15)", + Description: "Assign unique handles to users that do not have one", + Long: `Gives every user without a handle a unique one, derived the same way as +for a new registration, with a progress bar over the users (hidden under +-q). Users that already have a handle are not touched, so running it again +changes nothing. + +Run it once after copying users from WinterCMS, which had no handles. It +ends with "Assigned handles to N user(s)".`, + Examples: []bonfire.Example{ + {Command: "user:backfill-handles", Description: "Assign a handle to every user that has none"}, + {Command: "user:backfill-handles -q", Description: "Run it quietly, printing only the result line"}, + }, Run: func(ctx context.Context, _ bonfire.Input, out bonfire.Output) error { if app == nil { return fmt.Errorf("user:backfill-handles: app is nil") @@ -38,11 +49,20 @@ func BackfillHandles(app *backpack.App) bonfire.Command { return err } n := 0 - for i := range users { - if err := classes.AssignHandle(ctx, gdb, &users[i]); err != nil { + if len(users) > 0 { + err := out.Progress(len(users), func(p bonfire.Progress) error { + for i := range users { + if err := classes.AssignHandle(ctx, gdb, &users[i]); err != nil { + return err + } + n++ + p.Advance(1) + } + return nil + }) + if err != nil { return err } - n++ } out.Success(fmt.Sprintf("Assigned handles to %d user(s)", n)) return nil diff --git a/console/require_password_change.go b/console/require_password_change.go index b4a3fa8..31ed83c 100644 --- a/console/require_password_change.go +++ b/console/require_password_change.go @@ -15,7 +15,14 @@ import ( func RequirePasswordChange(withDB func(context.Context, func(*gorm.DB) error) error) bonfire.Command { return bonfire.Command{ Name: "user:require-password-change", - Description: "Force a user to change their password on next login (D-21)", + Description: "Force a user to change their password on next login", + Long: `Marks the account with this e-mail so that it must set a new password at its +next sign-in; setting the password clears the mark. Use it after a suspected +leak or when handing over an account created with a temporary password. It +ends with "Password change required for ".`, + Examples: []bonfire.Example{ + {Command: "user:require-password-change jane@example.com", Description: "Make Jane set a new password at her next sign-in"}, + }, Args: []bonfire.Arg{{ Name: "email", Description: "Account email", diff --git a/controllers/api_controller.go b/controllers/api_controller.go index 141c62f..c57451f 100644 --- a/controllers/api_controller.go +++ b/controllers/api_controller.go @@ -10,7 +10,6 @@ import ( "errors" "fmt" "io" - "log/slog" "net" "net/http" "net/url" @@ -110,7 +109,7 @@ func Login(app *backpack.App) http.HandlerFunc { } // A failed last_seen write is logged and never fails the login. if err := classes.TouchLastSeen(ctx, gdb, user.ID); err != nil { - slog.Default().Warn("golem15.user: stamp last_seen on login", "user_id", user.ID, "err", err) + pluginLog.Warn("golem15.user: stamp last_seen on login", "user_id", user.ID, "err", err) } token, err := mintFor(app, secret, user, requestURL(r)) if err != nil { @@ -204,7 +203,7 @@ func Refresh(app *backpack.App) http.HandlerFunc { if sub, _, _, _, err := bouncer.VerifyClaims(next, secret); err == nil { if id, err := strconv.ParseUint(sub, 10, 64); err == nil && id != 0 { if err := classes.TouchLastSeen(r.Context(), gdb, uint(id)); err != nil { - slog.Default().Warn("golem15.user: stamp last_seen on refresh", "user_id", id, "err", err) + pluginLog.Warn("golem15.user: stamp last_seen on refresh", "user_id", id, "err", err) } } } @@ -269,14 +268,14 @@ func ForgotPassword(app *backpack.App) http.HandlerFunc { ctx := codeContext(r.Context(), app) user, err := classes.LookupByEmail(ctx, gdb, strField(fields, "email")) if err != nil { - slog.Default().Error("golem15.user: forgot-password lookup", "err", err) + pluginLog.Error("golem15.user: forgot-password lookup", "err", err) writeJSON(w, http.StatusOK, map[string]any{"message": forgotPasswordBody}) return } if user != nil { code, err := classes.IssueResetCode(ctx, gdb, user) if err != nil { - slog.Default().Error("golem15.user: issue reset code", "err", err) + pluginLog.Error("golem15.user: issue reset code", "err", err) } else { packed := strconv.FormatUint(uint64(user.ID), 10) + "!" + code sendTemplateVars(ctx, app, mailName(ctx, user, "golem15.user::mail.restore"), user.Email, mailVars(displayName(user), resetLink(app, user.ID, code), packed)) @@ -536,7 +535,7 @@ func ChangePassword(app *backpack.App) http.HandlerFunc { return } if !user.HasSelfSetPassword { - slog.Default().Error("golem15.user: change-password requires a self-set password", "user_id", user.ID) + pluginLog.Error("golem15.user: change-password requires a self-set password", "user_id", user.ID) writeOpaque500(w) return } @@ -690,7 +689,7 @@ func Register(app *backpack.App) http.HandlerFunc { vars := mailVars(displayName(user), "", "") code, err := classes.IssueActivationCode(ctx, gdb, user) if err != nil { - slog.Default().Error("golem15.user: issue activation code", "err", err) + pluginLog.Error("golem15.user: issue activation code", "err", err) } else { packed := strconv.FormatUint(uint64(user.ID), 10) + "!" + code vars = mailVars(displayName(user), activationLink(app, user.ID, code), packed) @@ -935,11 +934,11 @@ func sendTemplateVars(ctx context.Context, app *backpack.App, template, to strin } mailer, ok := app.Lookup[postcard.Mailer]() if !ok || mailer == nil { - slog.Default().Error("golem15.user: mailer is not configured", "template", template) + pluginLog.Error("golem15.user: mailer is not configured", "template", template) return } if err := mailer.Send(ctx, postcard.Message{Template: template, To: []string{to}, Vars: vars}); err != nil { - slog.Default().Error("golem15.user: send mail", "template", template, "err", err) + pluginLog.Error("golem15.user: send mail", "template", template, "err", err) } } @@ -1198,7 +1197,7 @@ func UploadAvatar(app *backpack.App) http.HandlerFunc { } bucket, ok := app.Lookup[*blob.Bucket]() if !ok || bucket == nil { - slog.Default().Error("golem15.user: avatar bucket is not configured") + pluginLog.Error("golem15.user: avatar bucket is not configured") writeOpaque500(w) return } diff --git a/controllers/logger.go b/controllers/logger.go new file mode 100644 index 0000000..36bf467 --- /dev/null +++ b/controllers/logger.go @@ -0,0 +1,7 @@ +package controllers + +import "git.golem15.com/golem15/summercms/modules/diary" + +// pluginLog is the plugin's logger: its records carry component=user, so +// config/log.yaml can raise or lower them (log.components.user). +var pluginLog = diary.For("user") diff --git a/controllers/registration.go b/controllers/registration.go index 89206c3..999e567 100644 --- a/controllers/registration.go +++ b/controllers/registration.go @@ -3,7 +3,6 @@ package controllers import ( "context" "errors" - "log/slog" "maps" "net/http" "time" @@ -118,7 +117,7 @@ func FinishLogin(ctx context.Context, app *backpack.App, db *gorm.DB, user *mode user.LastIPAddress = &addr } if err := classes.TouchLastSeen(ctx, db, user.ID); err != nil { - slog.Default().Warn("golem15.user: stamp last_seen on login", "user_id", user.ID, "err", err) + pluginLog.Warn("golem15.user: stamp last_seen on login", "user_id", user.ID, "err", err) } return nil } diff --git a/go.mod b/go.mod index a150511..3687b2d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.golem15.com/golem15/sm-user-plugin go 1.27.0 require ( - git.golem15.com/golem15/summercms v0.1.9 + git.golem15.com/golem15/summercms v0.4.0 github.com/go-gormigrate/gormigrate/v2 v2.1.7 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/jackc/pgx/v5 v5.10.0 @@ -14,11 +14,50 @@ require ( ) require ( + charm.land/bubbles/v2 v2.2.1 // indirect + charm.land/bubbletea/v2 v2.1.0 // indirect + charm.land/fang/v2 v2.0.1 // indirect + charm.land/huh/v2 v2.0.3 // indirect + charm.land/lipgloss/v2 v2.0.6 // indirect dario.cat/mergo v1.0.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.9 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.20 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.19 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.25 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.2.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.26 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.10 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.25 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.25 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.102.2 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.1.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.19 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.42.3 // indirect + github.com/aws/smithy-go v1.26.0 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/catppuccin/go v0.2.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect + github.com/charmbracelet/harmonica v0.2.0 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20261008173134-6b8d4baf91b4 // indirect + github.com/charmbracelet/x/ansi v0.11.9 // indirect + github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 // indirect + github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect + github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/charmbracelet/x/termios v0.1.1 // indirect + github.com/charmbracelet/x/windows v0.2.2 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect @@ -28,6 +67,7 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/ebitengine/purego v0.10.1 // indirect github.com/felixge/httpsnoop v1.1.0 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect @@ -41,7 +81,9 @@ require ( github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-yaml v1.19.2 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/google/wire v0.7.0 // indirect github.com/googleapis/gax-go/v2 v2.19.0 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -57,9 +99,14 @@ require ( github.com/knadh/koanf/v2 v2.3.6 // indirect github.com/leodido/go-urn v1.5.0 // indirect github.com/lib/pq v1.12.3 // indirect + github.com/lucasb-eyer/go-colorful v1.4.1 // indirect github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect github.com/magiconair/properties v1.8.10 // indirect + github.com/mattn/go-runewidth v0.0.30 // indirect + github.com/maypok86/otter/v2 v2.3.0 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.2.0 // indirect @@ -70,16 +117,23 @@ require ( github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/mango v0.1.0 // indirect + github.com/muesli/mango-cobra v1.2.0 // indirect + github.com/muesli/mango-pflag v0.1.0 // indirect + github.com/muesli/roff v0.1.0 // indirect github.com/nicksnyder/go-i18n/v2 v2.6.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/redis/go-redis/v9 v9.23.0 // indirect github.com/riverqueue/river v0.47.0 // indirect github.com/riverqueue/river/riverdriver v0.47.0 // indirect github.com/riverqueue/river/riverdriver/riverdatabasesql v0.47.0 // indirect github.com/riverqueue/river/riverdriver/riverpgxv5 v0.47.0 // indirect github.com/riverqueue/river/rivershared v0.47.0 // indirect github.com/riverqueue/river/rivertype v0.47.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/shirou/gopsutil/v4 v4.26.6 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/spf13/cobra v1.10.2 // indirect @@ -92,6 +146,7 @@ require ( github.com/tklauser/go-sysconf v0.4.0 // indirect github.com/tklauser/numcpus v0.12.0 // indirect github.com/wneessen/go-mail v0.8.1 // indirect + github.com/xo/terminfo v1.0.0 // indirect github.com/yuin/goldmark v1.8.6 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -101,6 +156,7 @@ require ( go.opentelemetry.io/otel/sdk v1.44.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/crypto v0.57.0 // indirect golang.org/x/image v0.46.0 // indirect diff --git a/go.sum b/go.sum index ecad56f..7af3d20 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,15 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +charm.land/bubbles/v2 v2.2.1 h1:Fq1+qm5hV6GkvzLQDhCBpXXE5tLgvh1PRriCLwSvIQU= +charm.land/bubbles/v2 v2.2.1/go.mod h1:wdMgn+sje1KNXdwFizIWjbf328fIUBxqEmJ/vYPo8yc= +charm.land/bubbletea/v2 v2.1.0 h1:A9fj9h5LFrvQhQHFNggp8K8rvY6CUfgn9dMbb6p2HIw= +charm.land/bubbletea/v2 v2.1.0/go.mod h1:xWHeyxIX9/wiFU2kgPW04ojOZyTzrWf+8lGjUCaftiU= +charm.land/fang/v2 v2.0.1 h1:zQCM8JQJ1JnQX/66B5jlCYBUxL2as5JXQZ2KJ6EL0mY= +charm.land/fang/v2 v2.0.1/go.mod h1:S1GmkpcvK+OB5w9caywUnJcsMew45Ot8FXqoz8ALrII= +charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU= +charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc= +charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ= +charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= @@ -28,8 +38,14 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/alicebob/miniredis/v2 v2.40.0 h1:6IgTQbvm2JiMYZ8yur/acNJzVjtSOWCelxXbk/LUBXc= +github.com/alicebob/miniredis/v2 v2.40.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go-v2 v1.41.9 h1:/rYeyO2+HrMztAmxAq9++XJtFMqSIpSsNA0yDGALYq4= github.com/aws/aws-sdk-go-v2 v1.41.9/go.mod h1:+HsoOEX80qAVUitj1A2DhCNTjmb3edVyuDypb6LNEeo= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11 h1:h5+3VT69KUBK24grGuuA5saDJTj2IIjLb9au668Fo5I= @@ -68,10 +84,52 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.42.3 h1:ErklX/7uhSbkAAeyQD/Y1OoQ9hO3 github.com/aws/aws-sdk-go-v2/service/sts v1.42.3/go.mod h1:ULe4HCzfKPiR6R3HEurE3b1upEkuk8AkMrOKtaOxKO8= github.com/aws/smithy-go v1.26.0 h1:9ouqbi+NyKP7fV3Te7UElCwdAb6Y8uk7LGwPE5tVe/s= github.com/aws/smithy-go v1.26.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= +github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= +github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= +github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/ultraviolet v0.0.0-20261008173134-6b8d4baf91b4 h1:hISLzUbwcvJbGoNR6A3L/TpguRRnACBmZOee8LcHLJw= +github.com/charmbracelet/ultraviolet v0.0.0-20261008173134-6b8d4baf91b4/go.mod h1:aJDVk+WOVM1K04rdc/71PMPoUGfjxjhvXfP3Pjz1Zio= +github.com/charmbracelet/x/ansi v0.11.9 h1:kIrqLBxtTVbLyyHO2Evoi93EjQo1mylWmpNEsfPxXAM= +github.com/charmbracelet/x/ansi v0.11.9/go.mod h1:DG/hpOnaI2QJ/KMMqLL2h0KVhluuJLazf8973P+Vy5w= +github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs= +github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= +github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= +github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 h1:IJDiTgVE56gkAGfq0lBEloWgkXMk4hl/bmuPoicI4R0= +github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444/go.mod h1:T9jr8CzFpjhFVHjNjKwbAD7KwBNyFnj2pntAO7F2zw0= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= +github.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE= +github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8= +github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4= +github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= +github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= +github.com/charmbracelet/x/xpty v0.1.3 h1:eGSitii4suhzrISYH50ZfufV3v085BXQwIytcOdFSsw= +github.com/charmbracelet/x/xpty v0.1.3/go.mod h1:poPYpWuLDBFCKmKLDnhBp51ATa0ooD8FhypRwEFtH3Y= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -96,6 +154,8 @@ github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY= github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= @@ -139,6 +199,12 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-replayers/grpcreplay v1.3.0 h1:1Keyy0m1sIpqstQmgz307zhiJ1pV4uIlFds5weTmxbo= +github.com/google/go-replayers/grpcreplay v1.3.0/go.mod h1:v6NgKtkijC0d3e3RW8il6Sy5sqRVUwoQa4mHOGEy8DI= +github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk= +github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -149,6 +215,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE= github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= @@ -167,6 +235,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= +github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/yaml v1.1.1 h1:u70vV5IyaM0HvONh8HoqBC97oTgO33KcpZbTLiKVinU= @@ -183,16 +253,26 @@ github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0= github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE= github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss= +github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak= github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-runewidth v0.0.30 h1:+KUuiDA4fF0R1p5FeueHefjDm+GIM+kWfFnDjybOPgk= +github.com/mattn/go-runewidth v0.0.30/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/maypok86/otter/v2 v2.3.0 h1:8H8AVVFUSzJwIegKwv1uF5aGitTY+AIrtktg7OcLs8w= +github.com/maypok86/otter/v2 v2.3.0/go.mod h1:XgIdlpmL6jYz882/CAx1E4C1ukfgDKSaw4mWq59+7l8= github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -213,6 +293,16 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI= +github.com/muesli/mango v0.1.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4= +github.com/muesli/mango-cobra v1.2.0 h1:DQvjzAM0PMZr85Iv9LIMaYISpTOliMEg+uMFtNbYvWg= +github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA= +github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg= +github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0= +github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= +github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ= github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -224,6 +314,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/redis/go-redis/v9 v9.23.0 h1:/TTVdJa8BRNsybwwGGnmiYktHNlUXtbOQxlWpHBPL3s= +github.com/redis/go-redis/v9 v9.23.0/go.mod h1:EWP2UUk+XDaI1s5nQB5mablZN6vccXlmBQTyfnF87D0= github.com/riverqueue/river v0.47.0 h1:j8HOEyiOE8gRRhVS2wllKams372TH1WWiH6xCakXHqc= github.com/riverqueue/river v0.47.0/go.mod h1:Wgmwx475ZBd8lQnNrJgyG2DWH7BfyNiSAtv0rC9bJBQ= github.com/riverqueue/river/riverdriver v0.47.0 h1:qU8VkjdMl9plqeRg57SxsDUM/i/eECaSYejZ7HynC60= @@ -236,6 +328,8 @@ github.com/riverqueue/river/rivershared v0.47.0 h1:jdtFsBexCvLqTXf8wnDnGXvB/eeOt github.com/riverqueue/river/rivershared v0.47.0/go.mod h1:w8Pi1T+6ypyko5/hs9Mv7IIIKo4fAL9eXYnkVV/Y418= github.com/riverqueue/river/rivertype v0.47.0 h1:SzNavtLGR4nMT1QkrEYQ7n96OMatYsn/z3aJWhewmv0= github.com/riverqueue/river/rivertype v0.47.0/go.mod h1:XKkcRQR6zm8RR/JQa1Q2ywpj8uXQu21quPa4Lpw1Xhw= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -277,10 +371,16 @@ github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqo github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= github.com/wneessen/go-mail v0.8.1 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pcM= github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w= +github.com/xo/terminfo v1.0.0 h1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY= +github.com/xo/terminfo v1.0.0/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4= github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd/FNgrxcniL7kQrXQ= @@ -301,6 +401,8 @@ go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRk go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= @@ -310,6 +412,8 @@ gocloud.dev v0.46.0 h1:niIuZwSjMtBx8K+ITB2s5kZullB13PGOS2ZoQPZxQ4Q= gocloud.dev v0.46.0/go.mod h1:ACQe+2qO+hEO+pdcvvsM+RB63r8TyGD1W3ESCLFyzvM= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ= golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew= diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..19e6846 --- /dev/null +++ b/logger.go @@ -0,0 +1,7 @@ +package user + +import "git.golem15.com/golem15/summercms/modules/diary" + +// pluginLog is the plugin's logger: its records carry component=user, so +// config/log.yaml can raise or lower them (log.components.user). +var pluginLog = diary.For("user") diff --git a/plugin.go b/plugin.go index 1e92184..ec20953 100644 --- a/plugin.go +++ b/plugin.go @@ -6,7 +6,6 @@ import ( "embed" "fmt" "io/fs" - "log/slog" "net/http" "strings" "time" @@ -138,7 +137,7 @@ func (p *Plugin) startBlacklistSweep(bl bouncer.BlacklistStore) { select { case <-ticker.C: if err := bl.Sweep(context.Background(), time.Now()); err != nil { - slog.Default().Error("golem15.user: jwt blacklist sweep", "err", err) + pluginLog.Error("golem15.user: jwt blacklist sweep", "err", err) } case <-stop: return