From 5dafa00c7f1ee9e1ebf754d70ab73cd9a2347583 Mon Sep 17 00:00:00 2001 From: codens-agent Date: Sun, 28 Jun 2026 09:23:30 +0000 Subject: [PATCH 1/2] =?UTF-8?q?chore:=20Add=20artifact=20cleanup=20Asynq?= =?UTF-8?q?=20worker=20for=20expired=20artifact=20deletion=20and=20docker-?= =?UTF-8?q?compose=20MinIO=20init=20bucket=20job=20=E2=80=94=20with=20unit?= =?UTF-8?q?=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/cmd/server/main.go | 11 ++ .../infrastructure/queue/asynq_client.go | 6 + .../worker/artifact_cleanup_worker.go | 57 ++++++ .../worker/artifact_cleanup_worker_test.go | 171 ++++++++++++++++++ docker-compose.yml | 12 ++ 5 files changed, 257 insertions(+) create mode 100644 backend/internal/worker/artifact_cleanup_worker.go create mode 100644 backend/internal/worker/artifact_cleanup_worker_test.go diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 91ba47c2..d7bbb894 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -537,6 +537,17 @@ func registerHandlers(e *echo.Echo, cfg config.Config, db *sql.DB) (*sshinfra.SS mux := asynq.NewServeMux() mcpWorker := worker.NewMCPVerificationWorker(mcpVerificationRepo, cfg.WebBaseURL) mux.HandleFunc(queue.TypeMCPVerification, mcpWorker.HandleMCPVerification) + artifactCleanupWorker := worker.NewArtifactCleanupWorker(artifactRepo, artifactStorage, minioBucket) + mux.HandleFunc(queue.TypeArtifactCleanup, artifactCleanupWorker.HandleCleanup) + scheduler := asynq.NewScheduler(asynq.RedisClientOpt{Addr: cfg.RedisAddr}, nil) + if _, err := scheduler.Register("@hourly", asynq.NewTask(queue.TypeArtifactCleanup, nil)); err != nil { + return nil, fmt.Errorf("register artifact cleanup scheduler: %w", err) + } + go func() { + if err := scheduler.Run(); err != nil { + middleware.Log().Info("asynq scheduler stopped", "error", err) + } + }() go func() { middleware.Log().Info("asynq server starting") if err := asynqServer.Run(mux); err != nil { diff --git a/backend/internal/infrastructure/queue/asynq_client.go b/backend/internal/infrastructure/queue/asynq_client.go index e5630e8c..1d747cc0 100644 --- a/backend/internal/infrastructure/queue/asynq_client.go +++ b/backend/internal/infrastructure/queue/asynq_client.go @@ -11,6 +11,7 @@ import ( const ( TypeWebhookDeliver = "webhook:deliver" TypeMCPVerification = "mcp:verification" + TypeArtifactCleanup = "artifact:cleanup" ) type MCPVerificationPayload struct { @@ -58,3 +59,8 @@ func EnqueueWebhookDelivery(ctx context.Context, client *asynq.Client, payload W task := asynq.NewTask(TypeWebhookDeliver, data) return client.EnqueueContext(ctx, task, asynq.MaxRetry(5)) } + +func EnqueueArtifactCleanup(ctx context.Context, client *asynq.Client) (*asynq.TaskInfo, error) { + task := asynq.NewTask(TypeArtifactCleanup, nil) + return client.EnqueueContext(ctx, task, asynq.Queue("default")) +} diff --git a/backend/internal/worker/artifact_cleanup_worker.go b/backend/internal/worker/artifact_cleanup_worker.go new file mode 100644 index 00000000..fea58fd1 --- /dev/null +++ b/backend/internal/worker/artifact_cleanup_worker.go @@ -0,0 +1,57 @@ +package worker + +import ( + "context" + "log/slog" + + "github.com/hibiken/asynq" + + domainrepo "github.com/open-git/backend/internal/domain/repository" + artifactusecase "github.com/open-git/backend/internal/usecase/artifact" +) + +type ArtifactCleanupWorker struct { + artifactRepo domainrepo.IArtifactRepository + storage artifactusecase.ArtifactStorage + bucket string +} + +func NewArtifactCleanupWorker( + repo domainrepo.IArtifactRepository, + storage artifactusecase.ArtifactStorage, + bucket string, +) *ArtifactCleanupWorker { + return &ArtifactCleanupWorker{ + artifactRepo: repo, + storage: storage, + bucket: bucket, + } +} + +func (w *ArtifactCleanupWorker) HandleCleanup(ctx context.Context, task *asynq.Task) error { + const batchSize = 100 + for { + artifacts, err := w.artifactRepo.ListExpired(ctx, batchSize) + if err != nil { + return err + } + if len(artifacts) == 0 { + break + } + + for _, artifact := range artifacts { + if err := w.storage.DeleteObject(ctx, w.bucket, artifact.StorageKey); err != nil { + slog.Error("delete artifact object", "artifact_id", artifact.ID, "error", err) + continue + } + if err := w.artifactRepo.SoftDelete(ctx, artifact.ID, artifact.OrganizationID); err != nil { + slog.Error("soft delete artifact", "artifact_id", artifact.ID, "error", err) + } + } + + if len(artifacts) < batchSize { + break + } + } + return nil +} diff --git a/backend/internal/worker/artifact_cleanup_worker_test.go b/backend/internal/worker/artifact_cleanup_worker_test.go new file mode 100644 index 00000000..f84d2fd3 --- /dev/null +++ b/backend/internal/worker/artifact_cleanup_worker_test.go @@ -0,0 +1,171 @@ +package worker + +import ( + "context" + "database/sql" + "testing" + "time" + + "github.com/google/uuid" + "github.com/hibiken/asynq" + "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" + + "github.com/open-git/backend/internal/domain/entity" + infrarepo "github.com/open-git/backend/internal/infrastructure/repository" +) + +const artifactCleanupTestSchema = ` +CREATE TABLE organizations ( + id TEXT PRIMARY KEY, + login TEXT NOT NULL, + name TEXT NOT NULL, + plan_tier TEXT NOT NULL DEFAULT 'free', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); +CREATE TABLE repositories ( + id TEXT PRIMARY KEY, + organization_id TEXT NOT NULL, + name TEXT NOT NULL +); +CREATE TABLE workflow_runs ( + id TEXT PRIMARY KEY, + organization_id TEXT NOT NULL, + repository_id TEXT NOT NULL, + workflow TEXT NOT NULL, + status TEXT NOT NULL, + conclusion TEXT, + started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + completed_at TIMESTAMP +); +CREATE TABLE artifacts ( + id TEXT PRIMARY KEY, + organization_id TEXT NOT NULL, + repository_id TEXT NOT NULL, + workflow_run_id TEXT NOT NULL, + name TEXT NOT NULL, + storage_key TEXT NOT NULL, + size_in_bytes INTEGER NOT NULL DEFAULT 0, + status TEXT NOT NULL CHECK(status IN ('pending','uploading','completed','failed','expired')), + retention_days INTEGER NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + expires_at TIMESTAMP NOT NULL, + deleted_at TIMESTAMP +); +CREATE UNIQUE INDEX idx_artifacts_run_name ON artifacts(workflow_run_id, name) WHERE deleted_at IS NULL; +` + +func newArtifactCleanupTestDB(t *testing.T) *sqlx.DB { + t.Helper() + db, err := sql.Open("sqlite3", ":memory:") + if err != nil { + t.Fatalf("open sqlite: %v", err) + } + if _, err := db.Exec(artifactCleanupTestSchema); err != nil { + _ = db.Close() + t.Fatalf("create schema: %v", err) + } + t.Cleanup(func() { _ = db.Close() }) + return sqlx.NewDb(db, "sqlite3") +} + +type mockArtifactCleanupStorage struct { + deletedKeys []string +} + +func (m *mockArtifactCleanupStorage) PresignedPutURL(context.Context, string, string, time.Duration) (string, error) { + return "", nil +} + +func (m *mockArtifactCleanupStorage) PresignedGetURL(context.Context, string, string, time.Duration) (string, error) { + return "", nil +} + +func (m *mockArtifactCleanupStorage) DeleteObject(_ context.Context, _, key string) error { + m.deletedKeys = append(m.deletedKeys, key) + return nil +} + +func TestArtifactCleanupWorker_HandleCleanup(t *testing.T) { + db := newArtifactCleanupTestDB(t) + ctx := context.Background() + + orgID := uuid.MustParse("11111111-1111-1111-1111-111111111111") + repoID := uuid.MustParse("33333333-3333-3333-3333-333333333333") + runID := uuid.MustParse("44444444-4444-4444-4444-444444444444") + + mustExecArtifactCleanup(t, db, `INSERT INTO organizations (id, login, name, plan_tier) VALUES (?, ?, ?, ?)`, + orgID.String(), "acme", "Acme", "free") + mustExecArtifactCleanup(t, db, `INSERT INTO repositories (id, organization_id, name) VALUES (?, ?, ?)`, + repoID.String(), orgID.String(), "widgets") + mustExecArtifactCleanup(t, db, `INSERT INTO workflow_runs (id, organization_id, repository_id, workflow, status) VALUES (?, ?, ?, ?, ?)`, + runID.String(), orgID.String(), repoID.String(), "ci.yml", "completed") + + now := time.Now().UTC() + expiredKey := "org/acme/repo/widgets/runs/1/expired.zip" + futureKey := "org/acme/repo/widgets/runs/1/future.zip" + + artifactRepo := infrarepo.NewArtifactRepository(db) + expiredArtifact := &entity.Artifact{ + OrganizationID: orgID, + RunID: runID, + Name: "expired-artifact", + StorageKey: expiredKey, + ExpiresAt: now.Add(-1 * time.Hour), + } + futureArtifact := &entity.Artifact{ + OrganizationID: orgID, + RunID: runID, + Name: "future-artifact", + StorageKey: futureKey, + ExpiresAt: now.Add(24 * time.Hour), + } + if err := artifactRepo.Create(ctx, expiredArtifact); err != nil { + t.Fatalf("Create expired artifact: %v", err) + } + if err := artifactRepo.Create(ctx, futureArtifact); err != nil { + t.Fatalf("Create future artifact: %v", err) + } + if err := artifactRepo.UpdateStatus(ctx, expiredArtifact.ID, entity.ArtifactStatusCompleted, 100); err != nil { + t.Fatalf("UpdateStatus expired artifact: %v", err) + } + if err := artifactRepo.UpdateStatus(ctx, futureArtifact.ID, entity.ArtifactStatusCompleted, 200); err != nil { + t.Fatalf("UpdateStatus future artifact: %v", err) + } + + storage := &mockArtifactCleanupStorage{} + worker := NewArtifactCleanupWorker(artifactRepo, storage, "artifacts") + task := asynq.NewTask("artifact:cleanup", nil) + if err := worker.HandleCleanup(ctx, task); err != nil { + t.Fatalf("HandleCleanup returned error: %v", err) + } + + var expiredDeletedAt sql.NullTime + if err := db.QueryRowContext(ctx, `SELECT deleted_at FROM artifacts WHERE id = ?`, expiredArtifact.ID.String()). + Scan(&expiredDeletedAt); err != nil { + t.Fatalf("query expired artifact deleted_at: %v", err) + } + if !expiredDeletedAt.Valid { + t.Fatal("expected expired artifact deleted_at to be set") + } + + var futureDeletedAt sql.NullTime + if err := db.QueryRowContext(ctx, `SELECT deleted_at FROM artifacts WHERE id = ?`, futureArtifact.ID.String()). + Scan(&futureDeletedAt); err != nil { + t.Fatalf("query future artifact deleted_at: %v", err) + } + if futureDeletedAt.Valid { + t.Fatal("expected non-expired artifact deleted_at to remain NULL") + } + + if len(storage.deletedKeys) != 1 || storage.deletedKeys[0] != expiredKey { + t.Fatalf("DeleteObject calls = %v, want [%q]", storage.deletedKeys, expiredKey) + } +} + +func mustExecArtifactCleanup(t *testing.T, db *sqlx.DB, q string, args ...any) { + t.Helper() + if _, err := db.Exec(q, args...); err != nil { + t.Fatalf("exec %q: %v", q, err) + } +} diff --git a/docker-compose.yml b/docker-compose.yml index db7aed55..53af5471 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -92,6 +92,18 @@ services: retries: 3 restart: unless-stopped + createbuckets: + image: minio/mc + depends_on: + minio: + condition: service_healthy + entrypoint: > + /bin/sh -c " + mc alias set myminio http://minio:9000 $${MINIO_ROOT_USER:-minioadmin} $${MINIO_ROOT_PASSWORD:-minioadmin}; + mc mb --ignore-existing myminio/artifacts; + exit 0; + " + traefik: image: traefik:v3 command: From c8818cf6ef8e652a902e042969c902aad7a26d4d Mon Sep 17 00:00:00 2001 From: codens-agent Date: Sun, 28 Jun 2026 09:34:39 +0000 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20Add=20artifact=20cleanup=20Asynq?= =?UTF-8?q?=20worker=20for=20expired=20artifact=20deletion=20and=20docker-?= =?UTF-8?q?compose=20MinIO=20init=20bucket=20job=20=E2=80=94=20with=20unit?= =?UTF-8?q?=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/go.mod | 2 +- backend/go.sum | 4 ++-- .../internal/worker/artifact_cleanup_worker_test.go | 13 +++---------- go.work.sum | 8 ++++++++ 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index b59cd17b..59765bbb 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -25,7 +25,6 @@ require ( github.com/testcontainers/testcontainers-go/modules/postgres v0.35.0 github.com/vektah/gqlparser/v2 v2.5.22 golang.org/x/crypto v0.51.0 - golang.org/x/time v0.14.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -128,6 +127,7 @@ require ( golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.45.0 // indirect golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.44.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/backend/go.sum b/backend/go.sum index 77d867d6..d723277d 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -235,10 +235,10 @@ 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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= diff --git a/backend/internal/worker/artifact_cleanup_worker_test.go b/backend/internal/worker/artifact_cleanup_worker_test.go index f84d2fd3..2f755cce 100644 --- a/backend/internal/worker/artifact_cleanup_worker_test.go +++ b/backend/internal/worker/artifact_cleanup_worker_test.go @@ -94,11 +94,11 @@ func TestArtifactCleanupWorker_HandleCleanup(t *testing.T) { repoID := uuid.MustParse("33333333-3333-3333-3333-333333333333") runID := uuid.MustParse("44444444-4444-4444-4444-444444444444") - mustExecArtifactCleanup(t, db, `INSERT INTO organizations (id, login, name, plan_tier) VALUES (?, ?, ?, ?)`, + mustExec(t, db.DB, `INSERT INTO organizations (id, login, name, plan_tier) VALUES (?, ?, ?, ?)`, orgID.String(), "acme", "Acme", "free") - mustExecArtifactCleanup(t, db, `INSERT INTO repositories (id, organization_id, name) VALUES (?, ?, ?)`, + mustExec(t, db.DB, `INSERT INTO repositories (id, organization_id, name) VALUES (?, ?, ?)`, repoID.String(), orgID.String(), "widgets") - mustExecArtifactCleanup(t, db, `INSERT INTO workflow_runs (id, organization_id, repository_id, workflow, status) VALUES (?, ?, ?, ?, ?)`, + mustExec(t, db.DB, `INSERT INTO workflow_runs (id, organization_id, repository_id, workflow, status) VALUES (?, ?, ?, ?, ?)`, runID.String(), orgID.String(), repoID.String(), "ci.yml", "completed") now := time.Now().UTC() @@ -162,10 +162,3 @@ func TestArtifactCleanupWorker_HandleCleanup(t *testing.T) { t.Fatalf("DeleteObject calls = %v, want [%q]", storage.deletedKeys, expiredKey) } } - -func mustExecArtifactCleanup(t *testing.T, db *sqlx.DB, q string, args ...any) { - t.Helper() - if _, err := db.Exec(q, args...); err != nil { - t.Fatalf("exec %q: %v", q, err) - } -} diff --git a/go.work.sum b/go.work.sum index 38d4b008..c51e203a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -90,6 +90,7 @@ github.com/cockroachdb/cockroach-go/v2 v2.1.1 h1:3XzfSMuUT0wBe1a3o5C0eOTcArhmmFA github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM= github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369 h1:XNT/Zf5l++1Pyg08/HV04ppB0gKxAqtZQBRYiYrUuYk= github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= @@ -118,6 +119,7 @@ github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556 h1:N/MD/sr6o61X+iZBAT2 github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= @@ -194,6 +196,9 @@ github.com/markbates/pkger v0.15.1 h1:3MPelV53RnGSW07izx5xGxl4e/sdRD6zqseIk0rMAS github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/matryer/moq v0.4.0 h1:HsZIdEsj8+9nE940WW7FFxMgrgSxGfMkNXhVTHUhfMU= github.com/matryer/moq v0.4.0/go.mod h1:kUfalaLk7TcyXhrhonBYQ2Ewun63+/xGbZ7/MzzzC4Y= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/microsoft/go-mssqldb v1.0.0 h1:k2p2uuG8T5T/7Hp7/e3vMGTnnR0sU4h8d1CcC71iLHU= github.com/microsoft/go-mssqldb v1.0.0/go.mod h1:+4wZTUnz/SV6nffv+RRRB/ss8jPng5Sho2SmM1l2ts4= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= @@ -226,6 +231,7 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6O github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rqlite/gorqlite v0.0.0-20230708021416-2acd02b70b79 h1:V7x0hCAgL8lNGezuex1RW1sh7VXXCqfw8nXZti66iFg= github.com/rqlite/gorqlite v0.0.0-20230708021416-2acd02b70b79/go.mod h1:xF/KoXmrRyahPfo5L7Szb5cAAUl53dMWBh9cMruGEZg= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= @@ -269,6 +275,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4= golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE=