From 1a574add4933c03d768698abe5b42aae82575ef4 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Mon, 22 Aug 2022 16:58:36 +0200 Subject: [PATCH 01/25] remove deletion token notation support --- README.md | 18 +----------------- internal/config/config.go | 8 ++++---- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 48af4d8..741cc1e 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,6 @@ # pasty -Pasty is a fast and lightweight code pasting server - -## !!! Important deprecation notices !!! - -> This version of pasty uses a new field name for the so far called `deletionToken`: `modificationToken`. -> Instances using **PostgreSQL** are **not affected** as a corresponding SQL migration will run before the first startup. -> If you however use **another storage driver** you may have to **update the entries** by hand or using a simple query, depending on your driver as I don't plan to ship migrations for every single storage driver. -> It may be important to know that the **data migrator has been upgraded** too. This may serve as a **convenient workaround** (export data (field will be renamed) and import data with changed field names again). -> -> The old `deletionToken` field will be processed corresponding to these changes but I strongly recommend updating old pastes if possible. - -> Additionally, I changed the three `DELETION_TOKEN*`environment variables to their corresponding `MODIFICATION_TOKEN*` ones: -> - `DELETION_TOKENS` -> `MODIFICATION_TOKENS` -> - `DELETION_TOKEN_MASTER` -> `MODIFICATION_TOKEN_MASTER` -> - `DELETION_TOKEN_LENGTH` -> `MODIFICATION_TOKEN_LENGTH` -> -> Again, **the old ones will still work** because I do not want to jumble your configurations. However, **please consider updating** them to stay future-proof ^^. +pasty is a fast and lightweight code pasting server. ## Support diff --git a/internal/config/config.go b/internal/config/config.go index 6254e28..86ebcf8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -84,12 +84,12 @@ func Load() { HastebinSupport: env.MustBool("HASTEBIN_SUPPORT", false), IDLength: env.MustInt("ID_LENGTH", 6), IDCharacters: env.MustString("ID_CHARACTERS", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), - ModificationTokens: env.MustBool("MODIFICATION_TOKENS", env.MustBool("DELETION_TOKENS", true)), // --- - ModificationTokenMaster: env.MustString("MODIFICATION_TOKEN_MASTER", env.MustString("DELETION_TOKEN_MASTER", "")), // - We don't want to destroy peoples old configuration - ModificationTokenLength: env.MustInt("MODIFICATION_TOKEN_LENGTH", env.MustInt("DELETION_TOKEN_LENGTH", 12)), // --- + ModificationTokens: env.MustBool("MODIFICATION_TOKENS", true), + ModificationTokenMaster: env.MustString("MODIFICATION_TOKEN_MASTER", ""), + ModificationTokenLength: env.MustInt("MODIFICATION_TOKEN_LENGTH", 12), ModificationTokenCharacters: env.MustString("MODIFICATION_TOKEN_CHARACTERS", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), RateLimit: env.MustString("RATE_LIMIT", "30-M"), - LengthCap: env.MustInt("LENGTH_CAP", 50_000), + LengthCap: env.MustInt("LENGTH_CAP", 50000), AutoDelete: &AutoDeleteConfig{ Enabled: env.MustBool("AUTODELETE", false), Lifetime: env.MustDuration("AUTODELETE_LIFETIME", 720*time.Hour), From e93b292dafd0fac05cacc8b36262f1b31b865d30 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Mon, 29 Aug 2022 15:52:21 +0200 Subject: [PATCH 02/25] refactor package structure & remove v1 API --- cmd/transfer/main.go | 5 +- internal/config/config.go | 5 +- internal/paste/paste.go | 30 +++ internal/shared/paste.go | 42 ---- internal/shared/storage_type.go | 11 -- internal/storage/driver.go | 19 +- internal/storage/file/file_driver.go | 10 +- internal/storage/mongodb/mongodb_driver.go | 10 +- internal/storage/postgres/postgres_driver.go | 8 +- internal/storage/s3/s3_driver.go | 8 +- .../web/controllers/v1/hastebin_support.go | 72 ------- internal/web/controllers/v1/paste_adapter.go | 24 --- internal/web/controllers/v1/pastes.go | 180 ------------------ internal/web/{web.go => router.go} | 17 +- internal/web/{controllers => }/v2/pastes.go | 15 +- 15 files changed, 71 insertions(+), 385 deletions(-) create mode 100644 internal/paste/paste.go delete mode 100644 internal/shared/paste.go delete mode 100644 internal/shared/storage_type.go delete mode 100644 internal/web/controllers/v1/hastebin_support.go delete mode 100644 internal/web/controllers/v1/paste_adapter.go delete mode 100644 internal/web/controllers/v1/pastes.go rename internal/web/{web.go => router.go} (86%) rename internal/web/{controllers => }/v2/pastes.go (95%) diff --git a/cmd/transfer/main.go b/cmd/transfer/main.go index f7b9a7d..002bd50 100644 --- a/cmd/transfer/main.go +++ b/cmd/transfer/main.go @@ -5,7 +5,6 @@ import ( "os" "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/shared" "github.com/lus/pasty/internal/storage" ) @@ -20,7 +19,7 @@ func main() { config.Load() // Create and initialize the first (from) driver - from, err := storage.GetDriver(shared.StorageType(os.Args[1])) + from, err := storage.GetDriver(storage.Type(os.Args[1])) if err != nil { panic(err) } @@ -30,7 +29,7 @@ func main() { } // Create and initialize the second (to) driver - to, err := storage.GetDriver(shared.StorageType(os.Args[2])) + to, err := storage.GetDriver(storage.Type(os.Args[2])) if err != nil { panic(err) } diff --git a/internal/config/config.go b/internal/config/config.go index 86ebcf8..72f6fb4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,13 +5,12 @@ import ( "time" "github.com/lus/pasty/internal/env" - "github.com/lus/pasty/internal/shared" ) // Config represents the general application configuration structure type Config struct { WebAddress string - StorageType shared.StorageType + StorageType string HastebinSupport bool IDLength int IDCharacters string @@ -80,7 +79,7 @@ func Load() { Current = &Config{ WebAddress: env.MustString("WEB_ADDRESS", ":8080"), - StorageType: shared.StorageType(strings.ToLower(env.MustString("STORAGE_TYPE", "file"))), + StorageType: strings.ToLower(env.MustString("STORAGE_TYPE", "file")), HastebinSupport: env.MustBool("HASTEBIN_SUPPORT", false), IDLength: env.MustInt("ID_LENGTH", 6), IDCharacters: env.MustString("ID_CHARACTERS", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), diff --git a/internal/paste/paste.go b/internal/paste/paste.go new file mode 100644 index 0000000..3582fa7 --- /dev/null +++ b/internal/paste/paste.go @@ -0,0 +1,30 @@ +package paste + +import ( + "github.com/alexedwards/argon2id" +) + +// Paste represents a paste +type Paste struct { + ID string `json:"id"` + Content string `json:"content"` + ModificationToken string `json:"modificationToken,omitempty"` + Created int64 `json:"created"` + Metadata map[string]interface{} `json:"metadata"` +} + +// HashModificationToken hashes the current modification token of a paste +func (paste *Paste) HashModificationToken() error { + hash, err := argon2id.CreateHash(paste.ModificationToken, argon2id.DefaultParams) + if err != nil { + return err + } + paste.ModificationToken = hash + return nil +} + +// CheckModificationToken checks whether or not the given modification token is correct +func (paste *Paste) CheckModificationToken(modificationToken string) bool { + match, err := argon2id.ComparePasswordAndHash(modificationToken, paste.ModificationToken) + return err == nil && match +} diff --git a/internal/shared/paste.go b/internal/shared/paste.go deleted file mode 100644 index 4c2a762..0000000 --- a/internal/shared/paste.go +++ /dev/null @@ -1,42 +0,0 @@ -package shared - -import ( - "log" - - "github.com/alexedwards/argon2id" -) - -// Paste represents a saved paste -type Paste struct { - ID string `json:"id" bson:"_id"` - Content string `json:"content" bson:"content"` - DeletionToken string `json:"deletionToken,omitempty" bson:"deletionToken"` // Required for legacy paste storage support - ModificationToken string `json:"modificationToken,omitempty" bson:"modificationToken"` - Created int64 `json:"created" bson:"created"` - Metadata map[string]interface{} `json:"metadata" bson:"metadata"` -} - -// HashModificationToken hashes the current modification token of a paste -func (paste *Paste) HashModificationToken() error { - hash, err := argon2id.CreateHash(paste.ModificationToken, argon2id.DefaultParams) - if err != nil { - return err - } - paste.ModificationToken = hash - return nil -} - -// CheckModificationToken checks whether or not the given modification token is correct -func (paste *Paste) CheckModificationToken(modificationToken string) bool { - // The modification token may be stored in the deletion token field in old pastes - usedToken := paste.ModificationToken - if usedToken == "" { - usedToken = paste.DeletionToken - if usedToken != "" { - log.Println("WARNING: You seem to have pastes with the old 'deletionToken' field stored in your storage driver. Though this does not cause any issues right now, it may in the future. Consider some kind of migration.") - } - } - - match, err := argon2id.ComparePasswordAndHash(modificationToken, usedToken) - return err == nil && match -} diff --git a/internal/shared/storage_type.go b/internal/shared/storage_type.go deleted file mode 100644 index e2124fb..0000000 --- a/internal/shared/storage_type.go +++ /dev/null @@ -1,11 +0,0 @@ -package shared - -// StorageType represents a type of storage a paste can be stored with -type StorageType string - -const ( - StorageTypeFile = StorageType("file") - StorageTypePostgres = StorageType("postgres") - StorageTypeMongoDB = StorageType("mongodb") - StorageTypeS3 = StorageType("s3") -) diff --git a/internal/storage/driver.go b/internal/storage/driver.go index c3d9128..384632c 100644 --- a/internal/storage/driver.go +++ b/internal/storage/driver.go @@ -2,9 +2,10 @@ package storage import ( "fmt" + "strings" "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/shared" + "github.com/lus/pasty/internal/paste" "github.com/lus/pasty/internal/storage/file" "github.com/lus/pasty/internal/storage/mongodb" "github.com/lus/pasty/internal/storage/postgres" @@ -19,8 +20,8 @@ type Driver interface { Initialize() error Terminate() error ListIDs() ([]string, error) - Get(id string) (*shared.Paste, error) - Save(paste *shared.Paste) error + Get(id string) (*paste.Paste, error) + Save(paste *paste.Paste) error Delete(id string) error Cleanup() (int, error) } @@ -43,15 +44,15 @@ func Load() error { } // GetDriver returns the driver with the given type if it exists -func GetDriver(storageType shared.StorageType) (Driver, error) { - switch storageType { - case shared.StorageTypeFile: +func GetDriver(storageType string) (Driver, error) { + switch strings.TrimSpace(strings.ToLower(storageType)) { + case "file": return new(file.FileDriver), nil - case shared.StorageTypePostgres: + case "postgres": return new(postgres.PostgresDriver), nil - case shared.StorageTypeMongoDB: + case "mongodb": return new(mongodb.MongoDBDriver), nil - case shared.StorageTypeS3: + case "s3": return new(s3.S3Driver), nil default: return nil, fmt.Errorf("invalid storage type '%s'", storageType) diff --git a/internal/storage/file/file_driver.go b/internal/storage/file/file_driver.go index ccdbc78..aa40ade 100644 --- a/internal/storage/file/file_driver.go +++ b/internal/storage/file/file_driver.go @@ -10,7 +10,7 @@ import ( "time" "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/shared" + "github.com/lus/pasty/internal/paste" ) // FileDriver represents the file storage driver @@ -35,7 +35,7 @@ func (driver *FileDriver) ListIDs() ([]string, error) { var ids []string // Fill the IDs slice - err := filepath.Walk(driver.filePath, func(path string, info os.FileInfo, err error) error { + err := filepath.Walk(driver.filePath, func(_ string, info os.FileInfo, err error) error { // Check if a walking error occurred if err != nil { return err @@ -65,7 +65,7 @@ func (driver *FileDriver) ListIDs() ([]string, error) { } // Get loads a paste -func (driver *FileDriver) Get(id string) (*shared.Paste, error) { +func (driver *FileDriver) Get(id string) (*paste.Paste, error) { // Read the file id = base64.StdEncoding.EncodeToString([]byte(id)) data, err := ioutil.ReadFile(filepath.Join(driver.filePath, id+".json")) @@ -77,7 +77,7 @@ func (driver *FileDriver) Get(id string) (*shared.Paste, error) { } // Unmarshal the file into a paste - paste := new(shared.Paste) + paste := new(paste.Paste) err = json.Unmarshal(data, &paste) if err != nil { return nil, err @@ -86,7 +86,7 @@ func (driver *FileDriver) Get(id string) (*shared.Paste, error) { } // Save saves a paste -func (driver *FileDriver) Save(paste *shared.Paste) error { +func (driver *FileDriver) Save(paste *paste.Paste) error { // Marshal the paste jsonBytes, err := json.Marshal(paste) if err != nil { diff --git a/internal/storage/mongodb/mongodb_driver.go b/internal/storage/mongodb/mongodb_driver.go index 48ee925..951312e 100644 --- a/internal/storage/mongodb/mongodb_driver.go +++ b/internal/storage/mongodb/mongodb_driver.go @@ -5,7 +5,7 @@ import ( "time" "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/shared" + "github.com/lus/pasty/internal/paste" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -65,7 +65,7 @@ func (driver *MongoDBDriver) ListIDs() ([]string, error) { } // Decode all paste documents - var pasteSlice []shared.Paste + var pasteSlice []paste.Paste err = result.All(ctx, &pasteSlice) if err != nil { return nil, err @@ -80,7 +80,7 @@ func (driver *MongoDBDriver) ListIDs() ([]string, error) { } // Get loads a paste -func (driver *MongoDBDriver) Get(id string) (*shared.Paste, error) { +func (driver *MongoDBDriver) Get(id string) (*paste.Paste, error) { // Define the collection to use for this database operation collection := driver.client.Database(driver.database).Collection(driver.collection) @@ -100,7 +100,7 @@ func (driver *MongoDBDriver) Get(id string) (*shared.Paste, error) { } // Return the retrieved paste object - paste := new(shared.Paste) + paste := new(paste.Paste) err = result.Decode(paste) if err != nil { return nil, err @@ -109,7 +109,7 @@ func (driver *MongoDBDriver) Get(id string) (*shared.Paste, error) { } // Save saves a paste -func (driver *MongoDBDriver) Save(paste *shared.Paste) error { +func (driver *MongoDBDriver) Save(paste *paste.Paste) error { // Define the collection to use for this database operation collection := driver.client.Database(driver.database).Collection(driver.collection) diff --git a/internal/storage/postgres/postgres_driver.go b/internal/storage/postgres/postgres_driver.go index 04b994e..f43c61e 100644 --- a/internal/storage/postgres/postgres_driver.go +++ b/internal/storage/postgres/postgres_driver.go @@ -12,7 +12,7 @@ import ( "github.com/jackc/pgx/v4/pgxpool" "github.com/johejo/golang-migrate-extra/source/iofs" "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/shared" + "github.com/lus/pasty/internal/paste" ) //go:embed migrations/*.sql @@ -76,12 +76,12 @@ func (driver *PostgresDriver) ListIDs() ([]string, error) { } // Get loads a paste -func (driver *PostgresDriver) Get(id string) (*shared.Paste, error) { +func (driver *PostgresDriver) Get(id string) (*paste.Paste, error) { query := "SELECT * FROM pastes WHERE id = $1" row := driver.pool.QueryRow(context.Background(), query, id) - paste := new(shared.Paste) + paste := new(paste.Paste) if err := row.Scan(&paste.ID, &paste.Content, &paste.ModificationToken, &paste.Created, &paste.Metadata); err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, nil @@ -92,7 +92,7 @@ func (driver *PostgresDriver) Get(id string) (*shared.Paste, error) { } // Save saves a paste -func (driver *PostgresDriver) Save(paste *shared.Paste) error { +func (driver *PostgresDriver) Save(paste *paste.Paste) error { query := ` INSERT INTO pastes (id, content, "modificationToken", created, metadata) VALUES ($1, $2, $3, $4, $5) diff --git a/internal/storage/s3/s3_driver.go b/internal/storage/s3/s3_driver.go index a16f03b..bf524a5 100644 --- a/internal/storage/s3/s3_driver.go +++ b/internal/storage/s3/s3_driver.go @@ -9,7 +9,7 @@ import ( "time" "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/shared" + "github.com/lus/pasty/internal/paste" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) @@ -59,7 +59,7 @@ func (driver *S3Driver) ListIDs() ([]string, error) { } // Get loads a paste -func (driver *S3Driver) Get(id string) (*shared.Paste, error) { +func (driver *S3Driver) Get(id string) (*paste.Paste, error) { // Read the object object, err := driver.client.GetObject(context.Background(), driver.bucket, id+".json", minio.GetObjectOptions{}) if err != nil { @@ -74,7 +74,7 @@ func (driver *S3Driver) Get(id string) (*shared.Paste, error) { } // Unmarshal the object into a paste - paste := new(shared.Paste) + paste := new(paste.Paste) err = json.Unmarshal(data, &paste) if err != nil { return nil, err @@ -83,7 +83,7 @@ func (driver *S3Driver) Get(id string) (*shared.Paste, error) { } // Save saves a paste -func (driver *S3Driver) Save(paste *shared.Paste) error { +func (driver *S3Driver) Save(paste *paste.Paste) error { // Marshal the paste jsonBytes, err := json.Marshal(paste) if err != nil { diff --git a/internal/web/controllers/v1/hastebin_support.go b/internal/web/controllers/v1/hastebin_support.go deleted file mode 100644 index 7195f4a..0000000 --- a/internal/web/controllers/v1/hastebin_support.go +++ /dev/null @@ -1,72 +0,0 @@ -package v1 - -import ( - "encoding/json" - "time" - - "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/shared" - "github.com/lus/pasty/internal/storage" - "github.com/lus/pasty/internal/utils" - "github.com/valyala/fasthttp" -) - -// HastebinSupportHandler handles the legacy hastebin requests -func HastebinSupportHandler(ctx *fasthttp.RequestCtx) { - // Check content length before reading body into memory - if config.Current.LengthCap > 0 && - ctx.Request.Header.ContentLength() > config.Current.LengthCap { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.SetBodyString("request body length overflow") - return - } - - // Define the paste content - var content string - if string(ctx.Request.Header.ContentType()) == "multipart/form-data" { - content = string(ctx.FormValue("data")) - } else { - content = string(ctx.PostBody()) - } - - // Acquire the paste ID - id, err := storage.AcquireID() - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - - // Create the paste object - paste := &shared.Paste{ - ID: id, - Content: content, - Created: time.Now().Unix(), - } - - // Set a modification token - if config.Current.ModificationTokens { - paste.ModificationToken = utils.RandomString(config.Current.ModificationTokenCharacters, config.Current.ModificationTokenLength) - - err = paste.HashModificationToken() - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - } - - // Save the paste - err = storage.Current.Save(paste) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - - // Respond with the paste key - jsonData, _ := json.Marshal(map[string]string{ - "key": paste.ID, - }) - ctx.SetBody(jsonData) -} diff --git a/internal/web/controllers/v1/paste_adapter.go b/internal/web/controllers/v1/paste_adapter.go deleted file mode 100644 index fae442c..0000000 --- a/internal/web/controllers/v1/paste_adapter.go +++ /dev/null @@ -1,24 +0,0 @@ -package v1 - -import "github.com/lus/pasty/internal/shared" - -type legacyPaste struct { - ID string `json:"id"` - Content string `json:"content"` - DeletionToken string `json:"deletionToken,omitempty"` - Created int64 `json:"created"` -} - -func legacyFromModern(paste *shared.Paste) *legacyPaste { - deletionToken := paste.ModificationToken - if deletionToken == "" { - deletionToken = paste.DeletionToken - } - - return &legacyPaste{ - ID: paste.ID, - Content: paste.Content, - DeletionToken: deletionToken, - Created: paste.Created, - } -} diff --git a/internal/web/controllers/v1/pastes.go b/internal/web/controllers/v1/pastes.go deleted file mode 100644 index 076bd2a..0000000 --- a/internal/web/controllers/v1/pastes.go +++ /dev/null @@ -1,180 +0,0 @@ -package v1 - -import ( - "encoding/json" - "time" - - "github.com/fasthttp/router" - "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/shared" - "github.com/lus/pasty/internal/storage" - "github.com/lus/pasty/internal/utils" - limitFasthttp "github.com/ulule/limiter/v3/drivers/middleware/fasthttp" - "github.com/valyala/fasthttp" -) - -// InitializePastesController initializes the '/v1/pastes/*' controller -func InitializePastesController(group *router.Group, rateLimiterMiddleware *limitFasthttp.Middleware) { - group.GET("/{id}", rateLimiterMiddleware.Handle(v1GetPaste)) - group.POST("", rateLimiterMiddleware.Handle(v1PostPaste)) - group.DELETE("/{id}", rateLimiterMiddleware.Handle(v1DeletePaste)) -} - -// v1GetPaste handles the 'GET /v1/pastes/{id}' endpoint -func v1GetPaste(ctx *fasthttp.RequestCtx) { - // Read the ID - id := ctx.UserValue("id").(string) - - // Retrieve the paste - paste, err := storage.Current.Get(id) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - if paste == nil { - ctx.SetStatusCode(fasthttp.StatusNotFound) - ctx.SetBodyString("paste not found") - return - } - legacyPaste := legacyFromModern(paste) - legacyPaste.DeletionToken = "" - - // Respond with the paste - jsonData, err := json.Marshal(legacyPaste) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - ctx.SetBody(jsonData) -} - -// v1PostPaste handles the 'POST /v1/pastes' endpoint -func v1PostPaste(ctx *fasthttp.RequestCtx) { - // Check content length before reading body into memory - if config.Current.LengthCap > 0 && - ctx.Request.Header.ContentLength() > config.Current.LengthCap { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.SetBodyString("request body length overflow") - return - } - - // Unmarshal the body - values := make(map[string]string) - err := json.Unmarshal(ctx.PostBody(), &values) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.SetBodyString("invalid request body") - return - } - - // Validate the content of the paste - if values["content"] == "" { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.SetBodyString("missing 'content' field") - return - } - - // Acquire the paste ID - id, err := storage.AcquireID() - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - - // Create the paste object - paste := &shared.Paste{ - ID: id, - Content: values["content"], - Created: time.Now().Unix(), - } - - // Set a modification token - modificationToken := "" - if config.Current.ModificationTokens { - modificationToken = utils.RandomString(config.Current.ModificationTokenCharacters, config.Current.ModificationTokenLength) - paste.ModificationToken = modificationToken - - err = paste.HashModificationToken() - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - } - - // Save the paste - err = storage.Current.Save(paste) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - - // Respond with the paste - pasteCopy := legacyFromModern(paste) - pasteCopy.DeletionToken = modificationToken - jsonData, err := json.Marshal(pasteCopy) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - ctx.SetBody(jsonData) -} - -// v1DeletePaste handles the 'DELETE /v1/pastes/{id}' -func v1DeletePaste(ctx *fasthttp.RequestCtx) { - // Read the ID - id := ctx.UserValue("id").(string) - - // Unmarshal the body - values := make(map[string]string) - err := json.Unmarshal(ctx.PostBody(), &values) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.SetBodyString("invalid request body") - return - } - - // Validate the modification token of the paste - modificationToken := values["deletionToken"] - if modificationToken == "" { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.SetBodyString("missing 'deletionToken' field") - return - } - - // Retrieve the paste - paste, err := storage.Current.Get(id) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - if paste == nil { - ctx.SetStatusCode(fasthttp.StatusNotFound) - ctx.SetBodyString("paste not found") - return - } - - // Check if the modification token is correct - if (config.Current.ModificationTokenMaster == "" || modificationToken != config.Current.ModificationTokenMaster) && !paste.CheckModificationToken(modificationToken) { - ctx.SetStatusCode(fasthttp.StatusForbidden) - ctx.SetBodyString("invalid deletion token") - return - } - - // Delete the paste - err = storage.Current.Delete(paste.ID) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - - // Respond with 'ok' - ctx.SetBodyString("ok") -} diff --git a/internal/web/web.go b/internal/web/router.go similarity index 86% rename from internal/web/web.go rename to internal/web/router.go index 497e78e..9cb8efc 100644 --- a/internal/web/web.go +++ b/internal/web/router.go @@ -9,8 +9,7 @@ import ( "github.com/lus/pasty/internal/config" "github.com/lus/pasty/internal/static" "github.com/lus/pasty/internal/storage" - v1 "github.com/lus/pasty/internal/web/controllers/v1" - v2 "github.com/lus/pasty/internal/web/controllers/v2" + v2 "github.com/lus/pasty/internal/web/v2" "github.com/ulule/limiter/v3" limitFasthttp "github.com/ulule/limiter/v3/drivers/middleware/fasthttp" "github.com/ulule/limiter/v3/drivers/store/memory" @@ -57,18 +56,6 @@ func Serve() error { // Route the API endpoints apiRoute := router.Group("/api") { - v1Route := apiRoute.Group("/v1") - { - v1Route.GET("/info", func(ctx *fasthttp.RequestCtx) { - jsonData, _ := json.Marshal(map[string]interface{}{ - "version": static.Version, - "deletionTokens": config.Current.ModificationTokens, - }) - ctx.SetBody(jsonData) - }) - v1.InitializePastesController(v1Route.Group("/pastes"), rateLimiterMiddleware) - } - v2Route := apiRoute.Group("/v2") { pasteLifetime := int64(-1) @@ -90,7 +77,7 @@ func Serve() error { // Route the hastebin documents route if hastebin support is enabled if config.Current.HastebinSupport { - router.POST("/documents", rateLimiterMiddleware.Handle(v1.HastebinSupportHandler)) + // TODO: Reimplement hastebin support } // Serve the web resources diff --git a/internal/web/controllers/v2/pastes.go b/internal/web/v2/pastes.go similarity index 95% rename from internal/web/controllers/v2/pastes.go rename to internal/web/v2/pastes.go index f50b5fa..5ba44f8 100644 --- a/internal/web/controllers/v2/pastes.go +++ b/internal/web/v2/pastes.go @@ -7,8 +7,8 @@ import ( "github.com/fasthttp/router" "github.com/lus/pasty/internal/config" + "github.com/lus/pasty/internal/paste" "github.com/lus/pasty/internal/report" - "github.com/lus/pasty/internal/shared" "github.com/lus/pasty/internal/storage" "github.com/lus/pasty/internal/utils" limitFasthttp "github.com/ulule/limiter/v3/drivers/middleware/fasthttp" @@ -58,7 +58,7 @@ func middlewareInjectPaste(next fasthttp.RequestHandler) fasthttp.RequestHandler // middlewareValidateModificationToken extracts and validates a given modification token for an injected paste func middlewareValidateModificationToken(next fasthttp.RequestHandler) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { - paste := ctx.UserValue("_paste").(*shared.Paste) + paste := ctx.UserValue("_paste").(*paste.Paste) authHeaderSplit := strings.SplitN(string(ctx.Request.Header.Peek("Authorization")), " ", 2) if len(authHeaderSplit) < 2 || authHeaderSplit[0] != "Bearer" { @@ -85,8 +85,7 @@ func middlewareValidateModificationToken(next fasthttp.RequestHandler) fasthttp. // endpointGetPaste handles the 'GET /v2/pastes/{id}' endpoint func endpointGetPaste(ctx *fasthttp.RequestCtx) { - paste := ctx.UserValue("_paste").(*shared.Paste) - paste.DeletionToken = "" + paste := ctx.UserValue("_paste").(*paste.Paste) paste.ModificationToken = "" jsonData, err := json.Marshal(paste) @@ -135,7 +134,7 @@ func endpointCreatePaste(ctx *fasthttp.RequestCtx) { if payload.Metadata == nil { payload.Metadata = map[string]interface{}{} } - paste := &shared.Paste{ + paste := &paste.Paste{ ID: id, Content: payload.Content, Created: time.Now().Unix(), @@ -203,7 +202,7 @@ func endpointModifyPaste(ctx *fasthttp.RequestCtx) { } // Modify the paste itself - paste := ctx.UserValue("_paste").(*shared.Paste) + paste := ctx.UserValue("_paste").(*paste.Paste) if payload.Content != nil { paste.Content = *payload.Content } @@ -227,7 +226,7 @@ func endpointModifyPaste(ctx *fasthttp.RequestCtx) { // endpointDeletePaste handles the 'DELETE /v2/pastes/{id}' endpoint func endpointDeletePaste(ctx *fasthttp.RequestCtx) { - paste := ctx.UserValue("_paste").(*shared.Paste) + paste := ctx.UserValue("_paste").(*paste.Paste) if err := storage.Current.Delete(paste.ID); err != nil { ctx.SetStatusCode(fasthttp.StatusInternalServerError) ctx.SetBodyString(err.Error()) @@ -254,7 +253,7 @@ func endpointReportPaste(ctx *fasthttp.RequestCtx) { } request := &report.ReportRequest{ - Paste: ctx.UserValue("_paste").(*shared.Paste).ID, + Paste: ctx.UserValue("_paste").(*paste.Paste).ID, Reason: payload.Reason, } response, err := report.SendReport(request) From 3575c02c1ebbcd27fa196a813fb16e862fc91fd5 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Wed, 7 Jun 2023 17:46:19 +0200 Subject: [PATCH 03/25] restructure startup & config logic --- cmd/pasty/main.go | 83 +++--- cmd/transfer/main.go | 81 ------ go.mod | 35 ++- go.sum | 131 ++------- internal/config/config.go | 130 +++------ internal/env/env.go | 42 --- internal/meta/metadata.go | 13 + internal/paste/paste.go | 2 +- internal/randx/string.go | 14 + internal/report/report.go | 57 ---- internal/static/static.go | 11 - internal/storage/driver.go | 47 +--- internal/storage/file/file_driver.go | 145 ---------- internal/storage/id_generation.go | 20 -- internal/storage/mongodb/mongodb_driver.go | 171 ------------ internal/storage/postgres/postgres_driver.go | 30 +- internal/storage/s3/s3_driver.go | 136 --------- internal/utils/random_string.go | 16 -- internal/web/logger.go | 9 - internal/web/router.go | 137 ---------- internal/web/v2/pastes.go | 273 ------------------- 21 files changed, 173 insertions(+), 1410 deletions(-) delete mode 100644 cmd/transfer/main.go delete mode 100644 internal/env/env.go create mode 100644 internal/meta/metadata.go create mode 100644 internal/randx/string.go delete mode 100644 internal/report/report.go delete mode 100644 internal/static/static.go delete mode 100644 internal/storage/file/file_driver.go delete mode 100644 internal/storage/id_generation.go delete mode 100644 internal/storage/mongodb/mongodb_driver.go delete mode 100644 internal/storage/s3/s3_driver.go delete mode 100644 internal/utils/random_string.go delete mode 100644 internal/web/logger.go delete mode 100644 internal/web/router.go delete mode 100644 internal/web/v2/pastes.go diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index 2379afd..d22f389 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -1,52 +1,65 @@ package main import ( - "log" - "time" - + "context" "github.com/lus/pasty/internal/config" + "github.com/lus/pasty/internal/meta" "github.com/lus/pasty/internal/storage" - "github.com/lus/pasty/internal/web" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "os" + "os/signal" ) func main() { - // Load the configuration - log.Println("Loading the application configuration...") - config.Load() + // Set up the logger + zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs + if !meta.IsProdEnvironment() { + log.Logger = log.Output(zerolog.ConsoleWriter{ + Out: os.Stderr, + }) + log.Warn().Msg("This distribution was compiled for development mode and is thus not meant to be run in production!") + } - // Load the configured storage driver - log.Println("Loading the configured storage driver...") - err := storage.Load() + // Load the configuration + cfg, err := config.Load() if err != nil { - panic(err) + log.Fatal().Err(err).Msg("Could not load the configuration.") } - defer func() { - log.Println("Terminating the storage driver...") - err := storage.Current.Terminate() + + // Adjust the log level + if !meta.IsProdEnvironment() { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } else { + level, err := zerolog.ParseLevel(cfg.LogLevel) if err != nil { - log.Fatalln(err) + log.Warn().Msg("An invalid log level was configured. Falling back to 'info'.") + level = zerolog.InfoLevel } - }() - - // Schedule the AutoDelete task - if config.Current.AutoDelete.Enabled { - log.Println("Scheduling the AutoDelete task...") - go func() { - for { - // Run the cleanup sequence - deleted, err := storage.Current.Cleanup() - if err != nil { - log.Fatalln(err) - } - log.Printf("AutoDelete: Deleted %d expired pastes", deleted) + zerolog.SetGlobalLevel(level) + } - // Wait until the process should repeat - time.Sleep(config.Current.AutoDelete.TaskInterval) - } - }() + // Initialize the configured storage driver + driver, ok := storage.ResolveDriver(cfg.StorageDriver) + if !ok { + log.Fatal().Str("driver_name", cfg.StorageDriver).Msg("An invalid storage driver name was given.") + return + } + log.Info().Str("driver_name", cfg.StorageDriver).Msg("Initializing the storage driver...") + if err := driver.Initialize(context.Background(), cfg); err != nil { + log.Fatal().Err(err).Str("driver_name", cfg.StorageDriver).Msg("The storage driver could not be initialized.") + return } + defer func() { + log.Info().Msg("Shutting down the storage driver...") + if err := driver.Close(); err != nil { + log.Err(err).Str("driver_name", cfg.StorageDriver).Msg("Could not shut down the storage driver.") + } + }() - // Serve the web resources - log.Println("Serving the web resources...") - panic(web.Serve()) + // Wait for an interrupt signal + log.Info().Msg("The application has been started. Use Ctrl+C to shut it down.") + shutdownChan := make(chan os.Signal, 1) + signal.Notify(shutdownChan, os.Interrupt) + <-shutdownChan } diff --git a/cmd/transfer/main.go b/cmd/transfer/main.go deleted file mode 100644 index 002bd50..0000000 --- a/cmd/transfer/main.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "log" - "os" - - "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/storage" -) - -func main() { - // Validate the command line arguments - if len(os.Args) != 3 { - panic("Invalid command line arguments") - } - - // Load the configuration - log.Println("Loading the application configuration...") - config.Load() - - // Create and initialize the first (from) driver - from, err := storage.GetDriver(storage.Type(os.Args[1])) - if err != nil { - panic(err) - } - err = from.Initialize() - if err != nil { - panic(err) - } - - // Create and initialize the second (to) driver - to, err := storage.GetDriver(storage.Type(os.Args[2])) - if err != nil { - panic(err) - } - err = to.Initialize() - if err != nil { - panic(err) - } - - // Retrieve a list of IDs from the first (from) driver - ids, err := from.ListIDs() - if err != nil { - panic(err) - } - - // Transfer every paste to the second (to) driver - for _, id := range ids { - log.Println("Transferring ID " + id + "...") - - // Retrieve the paste - paste, err := from.Get(id) - if err != nil { - log.Println("[ERR]", err.Error()) - continue - } - - // Move the content of the deletion token field to the modification field - if paste.DeletionToken != "" { - if paste.ModificationToken == "" { - paste.ModificationToken = paste.DeletionToken - } - paste.DeletionToken = "" - log.Println("[INFO] Paste " + id + " was a legacy one.") - } - - // Initialize a new metadata map if the old one is null - if paste.Metadata == nil { - paste.Metadata = make(map[string]interface{}) - } - - // Save the paste - err = to.Save(paste) - if err != nil { - log.Println("[ERR]", err.Error()) - continue - } - - log.Println("Transferred ID " + id + ".") - } -} diff --git a/go.mod b/go.mod index d377f6f..996f579 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,32 @@ module github.com/lus/pasty -go 1.16 +go 1.20 require ( github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b - github.com/fasthttp/router v1.2.4 - github.com/go-stack/stack v1.8.1 // indirect github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574 - github.com/golang/snappy v0.0.4 // indirect github.com/jackc/pgx/v4 v4.11.0 github.com/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16 github.com/joho/godotenv v1.3.0 - github.com/klauspost/compress v1.15.1 // indirect - github.com/minio/minio-go/v7 v7.0.5 - github.com/ulule/limiter/v3 v3.5.0 - github.com/valyala/fasthttp v1.16.0 - github.com/xdg-go/scram v1.1.1 // indirect - github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect - go.mongodb.org/mongo-driver v1.8.4 - golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + github.com/kelseyhightower/envconfig v1.4.0 + github.com/rs/zerolog v1.29.1 +) + +require ( + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.8.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.0.6 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.7.0 // indirect + github.com/jackc/puddle v1.1.3 // indirect + github.com/lib/pq v1.8.0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect + golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect + golang.org/x/text v0.3.3 // indirect ) diff --git a/go.sum b/go.sum index 950fa9f..4e31eb1 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b h1:rcCpjI1OMGtBY8nnBvExeM1pXNoaM35zqmXBGpgJR2o= github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b/go.mod h1:GFtu6vaWaRJV5EvSFaVqgq/3Iq95xyYElBV/aupGzUo= -github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= -github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/apache/arrow/go/arrow v0.0.0-20200601151325-b2287a20f230/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -96,6 +94,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -126,16 +125,12 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fasthttp/router v1.2.4 h1:RBWbCv4vVf+boczSZh/rX9PDSdR9F8I9zSnVJx5YJfU= -github.com/fasthttp/router v1.2.4/go.mod h1:Au2V1CaqqAdzQQcPKrbkFAsImd1aHpadrce21AIPnvE= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -145,18 +140,12 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -199,8 +188,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -211,8 +198,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -228,11 +213,9 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= @@ -272,7 +255,6 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -292,7 +274,6 @@ github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye47 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= @@ -336,30 +317,18 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.6/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A= -github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= -github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -369,7 +338,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -384,6 +352,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -391,21 +361,15 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= -github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= -github.com/minio/minio-go/v7 v7.0.5 h1:I2NIJ2ojwJqD/YByemC1M59e1b4FW9kS7NlOar7HPV4= -github.com/minio/minio-go/v7 v7.0.5/go.mod h1:TA0CQCjJZHM5SJj9IjqR0NmpmQJ6bCbXifAJ3mUU6Hw= -github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= -github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= @@ -413,12 +377,9 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= @@ -437,10 +398,8 @@ github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= @@ -491,16 +450,16 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= +github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/savsgio/gotils v0.0.0-20200616100644-13ff1fd2c28c h1:KKqhycXW1WVNkX7r4ekTV2gFkbhdyihlWD8c0/FiWmk= -github.com/savsgio/gotils v0.0.0-20200616100644-13ff1fd2c28c/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY= @@ -512,10 +471,7 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/snowflakedb/glog v0.0.0-20180824191149-f5055e6f21ce/go.mod h1:EB/w24pR5VKI60ecFnKqXzxX3dOorz1rnVicQTQrGM0= github.com/snowflakedb/gosnowflake v1.3.5/go.mod h1:13Ky+lxzIm3VqNDZJdyvu9MCGy+WgRdYFdXp96UcLZU= @@ -533,41 +489,16 @@ github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ulule/limiter/v3 v3.5.0 h1:QRAebbswjlezHIfiSQgM8+jMxaz/zsrxGRuiUJ43MHo= -github.com/ulule/limiter/v3 v3.5.0/go.mod h1:TgOUQZKZ2KHjemqrC8UHUbKPqpTmSY43/2wbQ7YN1h8= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasthttp v1.15.1/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= -github.com/valyala/fasthttp v1.16.0 h1:9zAqOYLl8Tuy3E5R6ckzGDJ1g8+pw15oQp2iL9Jl6gQ= -github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= -github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= -github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -576,8 +507,6 @@ gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.8.4 h1:NruvZPPL0PBcRJKmbswoWSrmHeUvzdxA3GCPfD/NEOA= -go.mongodb.org/mongo-driver v1.8.4/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -607,16 +536,13 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -670,8 +596,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -683,15 +607,13 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -707,8 +629,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -733,7 +653,6 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -752,27 +671,21 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -792,7 +705,6 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -836,7 +748,6 @@ golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -932,15 +843,12 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= -gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= @@ -949,9 +857,8 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/config/config.go b/internal/config/config.go index 72f6fb4..17c74c2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,123 +1,55 @@ package config import ( - "strings" + "github.com/joho/godotenv" + "github.com/kelseyhightower/envconfig" "time" - - "github.com/lus/pasty/internal/env" ) -// Config represents the general application configuration structure +// Config represents the general application configuration type Config struct { - WebAddress string - StorageType string - HastebinSupport bool - IDLength int - IDCharacters string - ModificationTokens bool - ModificationTokenMaster string - ModificationTokenLength int - ModificationTokenCharacters string - RateLimit string - LengthCap int - AutoDelete *AutoDeleteConfig + LogLevel string `default:"info" split_words:"true"` + WebAddress string `default:":8080" split_words:"true"` + StorageDriver string `default:"sqlite" split_words:"true"` + HastebinSupport bool `default:"false" split_words:"true"` + IDLength int `default:"6" split_words:"true"` + IDCharacters string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` + ModificationTokens bool `default:"true" split_words:"true"` + ModificationTokenMaster string `split_words:"true"` + ModificationTokenLength int `default:"12" split_words:"true"` + ModificationTokenCharacters string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` + RateLimit string `default:"30-M" split_words:"true"` + LengthCap int `default:"50000" split_words:"true"` + AutoDelete *AutoDeleteConfig `split_words:"true"` Reports *ReportConfig - File *FileConfig Postgres *PostgresConfig - MongoDB *MongoDBConfig - S3 *S3Config } // AutoDeleteConfig represents the configuration specific for the AutoDelete behaviour type AutoDeleteConfig struct { - Enabled bool - Lifetime time.Duration - TaskInterval time.Duration + Enabled bool `default:"false"` + Lifetime time.Duration `default:"720h"` + TaskInterval time.Duration `default:"5m" split_words:"true"` } -// FileConfig represents the configuration specific for the file storage driver -type FileConfig struct { - Path string +// ReportConfig represents the configuration specific for the report system +type ReportConfig struct { + Enabled bool `default:"false" split_words:"true"` + WebhookURL string `split_words:"true"` + WebhookToken string `split_words:"true"` } // PostgresConfig represents the configuration specific for the Postgres storage driver type PostgresConfig struct { - DSN string -} - -// MongoDBConfig represents the configuration specific for the MongoDB storage driver -type MongoDBConfig struct { - DSN string - Database string - Collection string -} - -// S3Config represents the configuration specific for the S3 storage driver -type S3Config struct { - Endpoint string - AccessKeyID string - SecretAccessKey string - SecretToken string - Secure bool - Region string - Bucket string + DSN string `default:"postgres://pasty:pasty@localhost/pasty"` } -// ReportConfig represents the configuration specific for the report system -type ReportConfig struct { - Reports bool - ReportWebhook string - ReportWebhookToken string -} - -// Current holds the currently loaded config -var Current *Config - // Load loads the current config from environment variables and an optional .env file -func Load() { - env.Load() - - Current = &Config{ - WebAddress: env.MustString("WEB_ADDRESS", ":8080"), - StorageType: strings.ToLower(env.MustString("STORAGE_TYPE", "file")), - HastebinSupport: env.MustBool("HASTEBIN_SUPPORT", false), - IDLength: env.MustInt("ID_LENGTH", 6), - IDCharacters: env.MustString("ID_CHARACTERS", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), - ModificationTokens: env.MustBool("MODIFICATION_TOKENS", true), - ModificationTokenMaster: env.MustString("MODIFICATION_TOKEN_MASTER", ""), - ModificationTokenLength: env.MustInt("MODIFICATION_TOKEN_LENGTH", 12), - ModificationTokenCharacters: env.MustString("MODIFICATION_TOKEN_CHARACTERS", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), - RateLimit: env.MustString("RATE_LIMIT", "30-M"), - LengthCap: env.MustInt("LENGTH_CAP", 50000), - AutoDelete: &AutoDeleteConfig{ - Enabled: env.MustBool("AUTODELETE", false), - Lifetime: env.MustDuration("AUTODELETE_LIFETIME", 720*time.Hour), - TaskInterval: env.MustDuration("AUTODELETE_TASK_INTERVAL", 5*time.Minute), - }, - Reports: &ReportConfig{ - Reports: env.MustBool("REPORTS", false), - ReportWebhook: env.MustString("REPORT_WEBHOOK", ""), - ReportWebhookToken: env.MustString("REPORT_WEBHOOK_TOKEN", ""), - }, - File: &FileConfig{ - Path: env.MustString("STORAGE_FILE_PATH", "./data"), - }, - Postgres: &PostgresConfig{ - DSN: env.MustString("STORAGE_POSTGRES_DSN", "postgres://pasty:pasty@localhost/pasty"), - }, - MongoDB: &MongoDBConfig{ - DSN: env.MustString("STORAGE_MONGODB_CONNECTION_STRING", "mongodb://pasty:pasty@localhost/pasty"), - Database: env.MustString("STORAGE_MONGODB_DATABASE", "pasty"), - Collection: env.MustString("STORAGE_MONGODB_COLLECTION", "pastes"), - }, - S3: &S3Config{ - Endpoint: env.MustString("STORAGE_S3_ENDPOINT", ""), - AccessKeyID: env.MustString("STORAGE_S3_ACCESS_KEY_ID", ""), - SecretAccessKey: env.MustString("STORAGE_S3_SECRET_ACCESS_KEY", ""), - SecretToken: env.MustString("STORAGE_S3_SECRET_TOKEN", ""), - Secure: env.MustBool("STORAGE_S3_SECURE", true), - Region: env.MustString("STORAGE_S3_REGION", ""), - Bucket: env.MustString("STORAGE_S3_BUCKET", "pasty"), - }, +func Load() (*Config, error) { + _ = godotenv.Overload() + cfg := new(Config) + if err := envconfig.Process("pasty", cfg); err != nil { + return nil, err } + return cfg, nil } diff --git a/internal/env/env.go b/internal/env/env.go deleted file mode 100644 index 4a568a9..0000000 --- a/internal/env/env.go +++ /dev/null @@ -1,42 +0,0 @@ -package env - -import ( - "os" - "strconv" - "time" - - "github.com/joho/godotenv" - "github.com/lus/pasty/internal/static" -) - -// Load loads an optional .env file -func Load() { - godotenv.Load() -} - -// MustString returns the content of the environment variable with the given key or the given fallback -func MustString(key, fallback string) string { - value, found := os.LookupEnv(static.EnvironmentVariablePrefix + key) - if !found { - return fallback - } - return value -} - -// MustBool uses MustString and parses it into a boolean -func MustBool(key string, fallback bool) bool { - parsed, _ := strconv.ParseBool(MustString(key, strconv.FormatBool(fallback))) - return parsed -} - -// MustInt uses MustString and parses it into an integer -func MustInt(key string, fallback int) int { - parsed, _ := strconv.Atoi(MustString(key, strconv.Itoa(fallback))) - return parsed -} - -// MustDuration uses MustString and parses it into a duration -func MustDuration(key string, fallback time.Duration) time.Duration { - parsed, _ := time.ParseDuration(MustString(key, fallback.String())) - return parsed -} diff --git a/internal/meta/metadata.go b/internal/meta/metadata.go new file mode 100644 index 0000000..59cbcf1 --- /dev/null +++ b/internal/meta/metadata.go @@ -0,0 +1,13 @@ +package meta + +import "strings" + +const devEnvironmentName = "dev" + +var ( + Environment = devEnvironmentName +) + +func IsProdEnvironment() bool { + return strings.ToLower(Environment) != devEnvironmentName +} diff --git a/internal/paste/paste.go b/internal/paste/paste.go index 3582fa7..99f7461 100644 --- a/internal/paste/paste.go +++ b/internal/paste/paste.go @@ -23,7 +23,7 @@ func (paste *Paste) HashModificationToken() error { return nil } -// CheckModificationToken checks whether or not the given modification token is correct +// CheckModificationToken checks whether the given modification token is correct func (paste *Paste) CheckModificationToken(modificationToken string) bool { match, err := argon2id.ComparePasswordAndHash(modificationToken, paste.ModificationToken) return err == nil && match diff --git a/internal/randx/string.go b/internal/randx/string.go new file mode 100644 index 0000000..8962838 --- /dev/null +++ b/internal/randx/string.go @@ -0,0 +1,14 @@ +package randx + +import ( + "math/rand" +) + +// String returns a random string with the given length +func String(characters string, length int) string { + bytes := make([]byte, length) + for i := range bytes { + bytes[i] = characters[rand.Int63()%int64(len(characters))] + } + return string(bytes) +} diff --git a/internal/report/report.go b/internal/report/report.go deleted file mode 100644 index 544b687..0000000 --- a/internal/report/report.go +++ /dev/null @@ -1,57 +0,0 @@ -package report - -import ( - "encoding/json" - "fmt" - - "github.com/lus/pasty/internal/config" - "github.com/valyala/fasthttp" -) - -// ReportRequest represents a report request sent to the report webhook -type ReportRequest struct { - Paste string `json:"paste"` - Reason string `json:"reason"` -} - -// ReportResponse represents a report response received from the report webhook -type ReportResponse struct { - Success bool `json:"success"` - Message string `json:"message"` -} - -// SendReport sends a report request to the report webhook -func SendReport(reportRequest *ReportRequest) (*ReportResponse, error) { - request := fasthttp.AcquireRequest() - defer fasthttp.ReleaseRequest(request) - - response := fasthttp.AcquireResponse() - defer fasthttp.ReleaseResponse(response) - - request.Header.SetMethod(fasthttp.MethodPost) - request.SetRequestURI(config.Current.Reports.ReportWebhook) - if config.Current.Reports.ReportWebhookToken != "" { - request.Header.Set("Authorization", "Bearer "+config.Current.Reports.ReportWebhookToken) - } - - data, err := json.Marshal(reportRequest) - if err != nil { - return nil, err - } - request.SetBody(data) - - if err := fasthttp.Do(request, response); err != nil { - return nil, err - } - - status := response.StatusCode() - if status < 200 || status > 299 { - return nil, fmt.Errorf("the report webhook responded with an unexpected error: %d (%s)", status, string(response.Body())) - } - - reportResponse := new(ReportResponse) - if err := json.Unmarshal(response.Body(), reportResponse); err != nil { - return nil, err - } - return reportResponse, nil -} diff --git a/internal/static/static.go b/internal/static/static.go deleted file mode 100644 index e5b2528..0000000 --- a/internal/static/static.go +++ /dev/null @@ -1,11 +0,0 @@ -package static - -// These variables represent the values that may be changed using ldflags -var ( - Version = "dev" - EnvironmentVariablePrefix = "PASTY_" - - // TempFrontendPath defines the path where pasty loads the web frontend from; it will be removed any time soon - // TODO: Remove this when issue #37 is fixed - TempFrontendPath = "./web" -) diff --git a/internal/storage/driver.go b/internal/storage/driver.go index 384632c..d55e0e6 100644 --- a/internal/storage/driver.go +++ b/internal/storage/driver.go @@ -1,24 +1,18 @@ package storage import ( - "fmt" + "context" + "github.com/lus/pasty/internal/config" "strings" - "github.com/lus/pasty/internal/config" "github.com/lus/pasty/internal/paste" - "github.com/lus/pasty/internal/storage/file" - "github.com/lus/pasty/internal/storage/mongodb" "github.com/lus/pasty/internal/storage/postgres" - "github.com/lus/pasty/internal/storage/s3" ) -// Current holds the current storage driver -var Current Driver - // Driver represents a storage driver type Driver interface { - Initialize() error - Terminate() error + Initialize(ctx context.Context, cfg *config.Config) error + Close() error ListIDs() ([]string, error) Get(id string) (*paste.Paste, error) Save(paste *paste.Paste) error @@ -26,35 +20,12 @@ type Driver interface { Cleanup() (int, error) } -// Load loads the current storage driver -func Load() error { - // Define the driver to use - driver, err := GetDriver(config.Current.StorageType) - if err != nil { - return err - } - - // Initialize the driver - err = driver.Initialize() - if err != nil { - return err - } - Current = driver - return nil -} - -// GetDriver returns the driver with the given type if it exists -func GetDriver(storageType string) (Driver, error) { - switch strings.TrimSpace(strings.ToLower(storageType)) { - case "file": - return new(file.FileDriver), nil +// ResolveDriver returns the driver with the given name if it exists +func ResolveDriver(name string) (Driver, bool) { + switch strings.TrimSpace(strings.ToLower(name)) { case "postgres": - return new(postgres.PostgresDriver), nil - case "mongodb": - return new(mongodb.MongoDBDriver), nil - case "s3": - return new(s3.S3Driver), nil + return new(postgres.Driver), true default: - return nil, fmt.Errorf("invalid storage type '%s'", storageType) + return nil, false } } diff --git a/internal/storage/file/file_driver.go b/internal/storage/file/file_driver.go deleted file mode 100644 index aa40ade..0000000 --- a/internal/storage/file/file_driver.go +++ /dev/null @@ -1,145 +0,0 @@ -package file - -import ( - "encoding/base64" - "encoding/json" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" - - "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/paste" -) - -// FileDriver represents the file storage driver -type FileDriver struct { - filePath string -} - -// Initialize initializes the file storage driver -func (driver *FileDriver) Initialize() error { - driver.filePath = config.Current.File.Path - return os.MkdirAll(driver.filePath, os.ModePerm) -} - -// Terminate terminates the file storage driver (does nothing, because the file storage driver does not need any termination) -func (driver *FileDriver) Terminate() error { - return nil -} - -// ListIDs returns a list of all existing paste IDs -func (driver *FileDriver) ListIDs() ([]string, error) { - // Define the IDs slice - var ids []string - - // Fill the IDs slice - err := filepath.Walk(driver.filePath, func(_ string, info os.FileInfo, err error) error { - // Check if a walking error occurred - if err != nil { - return err - } - - // Only count JSON files - if !strings.HasSuffix(info.Name(), ".json") { - return nil - } - - // Decode the file name - decoded, err := base64.StdEncoding.DecodeString(strings.TrimSuffix(info.Name(), ".json")) - if err != nil { - return err - } - - // Append the ID to the IDs slice - ids = append(ids, string(decoded)) - return nil - }) - if err != nil { - return nil, err - } - - // Return the IDs slice - return ids, nil -} - -// Get loads a paste -func (driver *FileDriver) Get(id string) (*paste.Paste, error) { - // Read the file - id = base64.StdEncoding.EncodeToString([]byte(id)) - data, err := ioutil.ReadFile(filepath.Join(driver.filePath, id+".json")) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - return nil, err - } - - // Unmarshal the file into a paste - paste := new(paste.Paste) - err = json.Unmarshal(data, &paste) - if err != nil { - return nil, err - } - return paste, nil -} - -// Save saves a paste -func (driver *FileDriver) Save(paste *paste.Paste) error { - // Marshal the paste - jsonBytes, err := json.Marshal(paste) - if err != nil { - return err - } - - // Create the file to save the paste to - id := base64.StdEncoding.EncodeToString([]byte(paste.ID)) - file, err := os.Create(filepath.Join(driver.filePath, id+".json")) - if err != nil { - return err - } - defer file.Close() - - // Write the JSON data into the file - _, err = file.Write(jsonBytes) - return err -} - -// Delete deletes a paste -func (driver *FileDriver) Delete(id string) error { - id = base64.StdEncoding.EncodeToString([]byte(id)) - return os.Remove(filepath.Join(driver.filePath, id+".json")) -} - -// Cleanup cleans up the expired pastes -func (driver *FileDriver) Cleanup() (int, error) { - // Retrieve all paste IDs - ids, err := driver.ListIDs() - if err != nil { - return 0, err - } - - // Define the amount of deleted items - deleted := 0 - - // Loop through all pastes - for _, id := range ids { - // Retrieve the paste object - paste, err := driver.Get(id) - if err != nil { - return deleted, err - } - - // Delete the paste if it is expired - lifetime := config.Current.AutoDelete.Lifetime - if paste.Created+int64(lifetime.Seconds()) < time.Now().Unix() { - err = driver.Delete(id) - if err != nil { - return deleted, err - } - deleted++ - } - } - return deleted, nil -} diff --git a/internal/storage/id_generation.go b/internal/storage/id_generation.go deleted file mode 100644 index 99b05eb..0000000 --- a/internal/storage/id_generation.go +++ /dev/null @@ -1,20 +0,0 @@ -package storage - -import ( - "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/utils" -) - -// AcquireID generates a new unique ID -func AcquireID() (string, error) { - for { - id := utils.RandomString(config.Current.IDCharacters, config.Current.IDLength) - paste, err := Current.Get(id) - if err != nil { - return "", err - } - if paste == nil { - return id, nil - } - } -} diff --git a/internal/storage/mongodb/mongodb_driver.go b/internal/storage/mongodb/mongodb_driver.go deleted file mode 100644 index 951312e..0000000 --- a/internal/storage/mongodb/mongodb_driver.go +++ /dev/null @@ -1,171 +0,0 @@ -package mongodb - -import ( - "context" - "time" - - "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/paste" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" - "go.mongodb.org/mongo-driver/mongo/readpref" -) - -// MongoDBDriver represents the MongoDB storage driver -type MongoDBDriver struct { - client *mongo.Client - database string - collection string -} - -// Initialize initializes the MongoDB storage driver -func (driver *MongoDBDriver) Initialize() error { - // Define the context for the following database operation - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // Connect to the MongoDB host - client, err := mongo.Connect(ctx, options.Client().ApplyURI(config.Current.MongoDB.DSN)) - if err != nil { - return err - } - - // Ping the MongoDB host - err = client.Ping(ctx, readpref.Primary()) - if err != nil { - return err - } - - // Set the driver attributes - driver.client = client - driver.database = config.Current.MongoDB.Database - driver.collection = config.Current.MongoDB.Collection - return nil -} - -// Terminate terminates the MongoDB storage driver -func (driver *MongoDBDriver) Terminate() error { - return driver.client.Disconnect(context.TODO()) -} - -// ListIDs returns a list of all existing paste IDs -func (driver *MongoDBDriver) ListIDs() ([]string, error) { - // Define the collection to use for this database operation - collection := driver.client.Database(driver.database).Collection(driver.collection) - - // Define the context for the following database operation - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - // Retrieve all paste documents - result, err := collection.Find(ctx, bson.M{}) - if err != nil { - return nil, err - } - - // Decode all paste documents - var pasteSlice []paste.Paste - err = result.All(ctx, &pasteSlice) - if err != nil { - return nil, err - } - - // Read and return the IDs of all paste objects - var ids []string - for _, paste := range pasteSlice { - ids = append(ids, paste.ID) - } - return ids, nil -} - -// Get loads a paste -func (driver *MongoDBDriver) Get(id string) (*paste.Paste, error) { - // Define the collection to use for this database operation - collection := driver.client.Database(driver.database).Collection(driver.collection) - - // Define the context for the following database operation - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - // Try to retrieve the corresponding paste document - filter := bson.M{"_id": id} - result := collection.FindOne(ctx, filter) - err := result.Err() - if err != nil { - if err == mongo.ErrNoDocuments { - return nil, nil - } - return nil, err - } - - // Return the retrieved paste object - paste := new(paste.Paste) - err = result.Decode(paste) - if err != nil { - return nil, err - } - return paste, nil -} - -// Save saves a paste -func (driver *MongoDBDriver) Save(paste *paste.Paste) error { - // Define the collection to use for this database operation - collection := driver.client.Database(driver.database).Collection(driver.collection) - - // Define the context for the following database operation - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - // Upsert the paste object - filter := bson.M{"_id": paste.ID} - _, err := collection.UpdateOne(ctx, filter, bson.M{"$set": paste}, options.Update().SetUpsert(true)) - return err -} - -// Delete deletes a paste -func (driver *MongoDBDriver) Delete(id string) error { - // Define the collection to use for this database operation - collection := driver.client.Database(driver.database).Collection(driver.collection) - - // Define the context for the following database operation - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - // Delete the document - filter := bson.M{"_id": id} - _, err := collection.DeleteOne(ctx, filter) - return err -} - -// Cleanup cleans up the expired pastes -func (driver *MongoDBDriver) Cleanup() (int, error) { - // Retrieve all paste IDs - ids, err := driver.ListIDs() - if err != nil { - return 0, err - } - - // Define the amount of deleted items - deleted := 0 - - // Loop through all pastes - for _, id := range ids { - // Retrieve the paste object - paste, err := driver.Get(id) - if err != nil { - return 0, err - } - - // Delete the paste if it is expired - lifetime := config.Current.AutoDelete.Lifetime - if paste.Created+int64(lifetime.Seconds()) < time.Now().Unix() { - err = driver.Delete(id) - if err != nil { - return 0, err - } - deleted++ - } - } - return deleted, nil -} diff --git a/internal/storage/postgres/postgres_driver.go b/internal/storage/postgres/postgres_driver.go index f43c61e..9b70e7d 100644 --- a/internal/storage/postgres/postgres_driver.go +++ b/internal/storage/postgres/postgres_driver.go @@ -18,14 +18,15 @@ import ( //go:embed migrations/*.sql var migrations embed.FS -// PostgresDriver represents the Postgres storage driver -type PostgresDriver struct { - pool *pgxpool.Pool +// Driver represents the Postgres storage driver +type Driver struct { + pool *pgxpool.Pool + autoDeleteLifetime time.Duration } // Initialize initializes the Postgres storage driver -func (driver *PostgresDriver) Initialize() error { - pool, err := pgxpool.Connect(context.Background(), config.Current.Postgres.DSN) +func (driver *Driver) Initialize(ctx context.Context, cfg *config.Config) error { + pool, err := pgxpool.Connect(ctx, cfg.Postgres.DSN) if err != nil { return err } @@ -35,7 +36,7 @@ func (driver *PostgresDriver) Initialize() error { return err } - migrator, err := migrate.NewWithSourceInstance("iofs", source, config.Current.Postgres.DSN) + migrator, err := migrate.NewWithSourceInstance("iofs", source, cfg.Postgres.DSN) if err != nil { return err } @@ -45,17 +46,18 @@ func (driver *PostgresDriver) Initialize() error { } driver.pool = pool + driver.autoDeleteLifetime = cfg.AutoDelete.Lifetime return nil } -// Terminate terminates the Postgres storage driver -func (driver *PostgresDriver) Terminate() error { +// Close terminates the Postgres storage driver +func (driver *Driver) Close() error { driver.pool.Close() return nil } // ListIDs returns a list of all existing paste IDs -func (driver *PostgresDriver) ListIDs() ([]string, error) { +func (driver *Driver) ListIDs() ([]string, error) { query := "SELECT id FROM pastes" rows, err := driver.pool.Query(context.Background(), query) @@ -76,7 +78,7 @@ func (driver *PostgresDriver) ListIDs() ([]string, error) { } // Get loads a paste -func (driver *PostgresDriver) Get(id string) (*paste.Paste, error) { +func (driver *Driver) Get(id string) (*paste.Paste, error) { query := "SELECT * FROM pastes WHERE id = $1" row := driver.pool.QueryRow(context.Background(), query, id) @@ -92,7 +94,7 @@ func (driver *PostgresDriver) Get(id string) (*paste.Paste, error) { } // Save saves a paste -func (driver *PostgresDriver) Save(paste *paste.Paste) error { +func (driver *Driver) Save(paste *paste.Paste) error { query := ` INSERT INTO pastes (id, content, "modificationToken", created, metadata) VALUES ($1, $2, $3, $4, $5) @@ -108,7 +110,7 @@ func (driver *PostgresDriver) Save(paste *paste.Paste) error { } // Delete deletes a paste -func (driver *PostgresDriver) Delete(id string) error { +func (driver *Driver) Delete(id string) error { query := "DELETE FROM pastes WHERE id = $1" _, err := driver.pool.Exec(context.Background(), query, id) @@ -116,10 +118,10 @@ func (driver *PostgresDriver) Delete(id string) error { } // Cleanup cleans up the expired pastes -func (driver *PostgresDriver) Cleanup() (int, error) { +func (driver *Driver) Cleanup() (int, error) { query := "DELETE FROM pastes WHERE created < $1" - tag, err := driver.pool.Exec(context.Background(), query, time.Now().Add(-config.Current.AutoDelete.Lifetime).Unix()) + tag, err := driver.pool.Exec(context.Background(), query, time.Now().Add(-driver.autoDeleteLifetime).Unix()) if err != nil { return 0, err } diff --git a/internal/storage/s3/s3_driver.go b/internal/storage/s3/s3_driver.go deleted file mode 100644 index bf524a5..0000000 --- a/internal/storage/s3/s3_driver.go +++ /dev/null @@ -1,136 +0,0 @@ -package s3 - -import ( - "bytes" - "context" - "encoding/json" - "io/ioutil" - "strings" - "time" - - "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/paste" - "github.com/minio/minio-go/v7" - "github.com/minio/minio-go/v7/pkg/credentials" -) - -// S3Driver represents the AWS S3 storage driver -type S3Driver struct { - client *minio.Client - bucket string -} - -// Initialize initializes the AWS S3 storage driver -func (driver *S3Driver) Initialize() error { - client, err := minio.New(config.Current.S3.Endpoint, &minio.Options{ - Creds: credentials.NewStaticV4(config.Current.S3.AccessKeyID, config.Current.S3.SecretAccessKey, config.Current.S3.SecretToken), - Secure: config.Current.S3.Secure, - Region: config.Current.S3.Region, - }) - if err != nil { - return err - } - driver.client = client - driver.bucket = config.Current.S3.Bucket - return nil -} - -// Terminate terminates the AWS S3 storage driver (does nothing, because the AWS S3 storage driver does not need any termination) -func (driver *S3Driver) Terminate() error { - return nil -} - -// ListIDs returns a list of all existing paste IDs -func (driver *S3Driver) ListIDs() ([]string, error) { - // Define the IDs slice - var ids []string - - // Fill the IDs slice - channel := driver.client.ListObjects(context.Background(), driver.bucket, minio.ListObjectsOptions{}) - for object := range channel { - if object.Err != nil { - return nil, object.Err - } - ids = append(ids, strings.TrimSuffix(object.Key, ".json")) - } - - // Return the IDs slice - return ids, nil -} - -// Get loads a paste -func (driver *S3Driver) Get(id string) (*paste.Paste, error) { - // Read the object - object, err := driver.client.GetObject(context.Background(), driver.bucket, id+".json", minio.GetObjectOptions{}) - if err != nil { - return nil, err - } - data, err := ioutil.ReadAll(object) - if err != nil { - if minio.ToErrorResponse(err).Code == "NoSuchKey" { - return nil, nil - } - return nil, err - } - - // Unmarshal the object into a paste - paste := new(paste.Paste) - err = json.Unmarshal(data, &paste) - if err != nil { - return nil, err - } - return paste, nil -} - -// Save saves a paste -func (driver *S3Driver) Save(paste *paste.Paste) error { - // Marshal the paste - jsonBytes, err := json.Marshal(paste) - if err != nil { - return err - } - - // Put the object - reader := bytes.NewReader(jsonBytes) - _, err = driver.client.PutObject(context.Background(), driver.bucket, paste.ID+".json", reader, reader.Size(), minio.PutObjectOptions{ - ContentType: "application/json", - }) - return err -} - -// Delete deletes a paste -func (driver *S3Driver) Delete(id string) error { - return driver.client.RemoveObject(context.Background(), driver.bucket, id+".json", minio.RemoveObjectOptions{}) -} - -// Cleanup cleans up the expired pastes -func (driver *S3Driver) Cleanup() (int, error) { - // Retrieve all paste IDs - ids, err := driver.ListIDs() - if err != nil { - return 0, err - } - - // Define the amount of deleted items - deleted := 0 - - // Loop through all pastes - for _, id := range ids { - // Retrieve the paste object - paste, err := driver.Get(id) - if err != nil { - return 0, err - } - - // Delete the paste if it is expired - lifetime := config.Current.AutoDelete.Lifetime - if paste.Created+int64(lifetime.Seconds()) < time.Now().Unix() { - err = driver.Delete(id) - if err != nil { - return 0, err - } - deleted++ - } - } - return deleted, nil -} diff --git a/internal/utils/random_string.go b/internal/utils/random_string.go deleted file mode 100644 index 3798ec5..0000000 --- a/internal/utils/random_string.go +++ /dev/null @@ -1,16 +0,0 @@ -package utils - -import ( - "math/rand" - "time" -) - -// RandomString returns a random string with the given length -func RandomString(characters string, length int) string { - rand.Seed(time.Now().UnixNano()) - bytes := make([]byte, length) - for i := range bytes { - bytes[i] = characters[rand.Int63()%int64(len(characters))] - } - return string(bytes) -} diff --git a/internal/web/logger.go b/internal/web/logger.go deleted file mode 100644 index 035c994..0000000 --- a/internal/web/logger.go +++ /dev/null @@ -1,9 +0,0 @@ -package web - -// nilLogger represents a logger that does not print anything -type nilLogger struct { -} - -// Printf prints nothing -func (logger *nilLogger) Printf(string, ...interface{}) { -} diff --git a/internal/web/router.go b/internal/web/router.go deleted file mode 100644 index 9cb8efc..0000000 --- a/internal/web/router.go +++ /dev/null @@ -1,137 +0,0 @@ -package web - -import ( - "encoding/json" - "path/filepath" - "strings" - - routing "github.com/fasthttp/router" - "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/static" - "github.com/lus/pasty/internal/storage" - v2 "github.com/lus/pasty/internal/web/v2" - "github.com/ulule/limiter/v3" - limitFasthttp "github.com/ulule/limiter/v3/drivers/middleware/fasthttp" - "github.com/ulule/limiter/v3/drivers/store/memory" - "github.com/valyala/fasthttp" -) - -// Serve serves the web resources -func Serve() error { - // Create the router - router := routing.New() - - // Define the 404 handler - router.NotFound = func(ctx *fasthttp.RequestCtx) { - ctx.SetStatusCode(fasthttp.StatusNotFound) - ctx.SetBodyString("not found") - } - - // Route the frontend requests - frontend := frontendHandler() - raw := rawHandler() - router.GET("/{path:*}", func(ctx *fasthttp.RequestCtx) { - path := string(ctx.Path()) - if !strings.HasPrefix(path, "/api") && (strings.Count(path, "/") == 1 || strings.HasPrefix(path, "/assets")) { - if strings.HasPrefix(path, "/assets/js/") { - ctx.SetContentType("text/javascript") - } - frontend(ctx) - return - } else if strings.HasSuffix(strings.TrimSuffix(path, "/"), "/raw") { - raw(ctx) - return - } - router.NotFound(ctx) - }) - - // Set up the rate limiter - rate, err := limiter.NewRateFromFormatted(config.Current.RateLimit) - if err != nil { - return err - } - rateLimiter := limiter.New(memory.NewStore(), rate) - rateLimiterMiddleware := limitFasthttp.NewMiddleware(rateLimiter) - - // Route the API endpoints - apiRoute := router.Group("/api") - { - v2Route := apiRoute.Group("/v2") - { - pasteLifetime := int64(-1) - if config.Current.AutoDelete.Enabled { - pasteLifetime = config.Current.AutoDelete.Lifetime.Milliseconds() - } - v2Route.GET("/info", func(ctx *fasthttp.RequestCtx) { - jsonData, _ := json.Marshal(map[string]interface{}{ - "version": static.Version, - "modificationTokens": config.Current.ModificationTokens, - "reports": config.Current.Reports.Reports, - "pasteLifetime": pasteLifetime, - }) - ctx.SetBody(jsonData) - }) - v2.InitializePastesController(v2Route.Group("/pastes"), rateLimiterMiddleware) - } - } - - // Route the hastebin documents route if hastebin support is enabled - if config.Current.HastebinSupport { - // TODO: Reimplement hastebin support - } - - // Serve the web resources - return (&fasthttp.Server{ - Handler: func(ctx *fasthttp.RequestCtx) { - // Add the CORS headers - ctx.Response.Header.Set("Access-Control-Allow-Methods", "GET,POST,DELETE,OPTIONS") - ctx.Response.Header.Set("Access-Control-Allow-Origin", "*") - - // Call the router handler - router.Handler(ctx) - }, - Logger: new(nilLogger), - }).ListenAndServe(config.Current.WebAddress) -} - -// frontendHandler handles the frontend routing -func frontendHandler() fasthttp.RequestHandler { - // Create the file server - fs := &fasthttp.FS{ - Root: static.TempFrontendPath, - IndexNames: []string{"index.html"}, - CacheDuration: 0, - } - fs.PathNotFound = func(ctx *fasthttp.RequestCtx) { - if strings.HasPrefix(string(ctx.Path()), "/assets") { - ctx.SetStatusCode(fasthttp.StatusNotFound) - ctx.SetBodyString("not found") - return - } - ctx.SendFile(filepath.Join(fs.Root, "index.html")) - } - return fs.NewRequestHandler() -} - -func rawHandler() fasthttp.RequestHandler { - return func(ctx *fasthttp.RequestCtx) { - path := string(ctx.Path()) - pathSanitized := strings.TrimPrefix(strings.TrimSuffix(path, "/"), "/") - pasteID := strings.TrimSuffix(pathSanitized, "/raw") - - paste, err := storage.Current.Get(pasteID) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - - if paste == nil { - ctx.SetStatusCode(fasthttp.StatusNotFound) - ctx.SetBodyString("paste not found") - return - } - - ctx.SetBodyString(paste.Content) - } -} diff --git a/internal/web/v2/pastes.go b/internal/web/v2/pastes.go deleted file mode 100644 index 5ba44f8..0000000 --- a/internal/web/v2/pastes.go +++ /dev/null @@ -1,273 +0,0 @@ -package v2 - -import ( - "encoding/json" - "strings" - "time" - - "github.com/fasthttp/router" - "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/paste" - "github.com/lus/pasty/internal/report" - "github.com/lus/pasty/internal/storage" - "github.com/lus/pasty/internal/utils" - limitFasthttp "github.com/ulule/limiter/v3/drivers/middleware/fasthttp" - "github.com/valyala/fasthttp" -) - -// InitializePastesController initializes the '/v2/pastes/*' controller -func InitializePastesController(group *router.Group, rateLimiterMiddleware *limitFasthttp.Middleware) { - // moms spaghetti - group.GET("/{id}", rateLimiterMiddleware.Handle(middlewareInjectPaste(endpointGetPaste))) - group.POST("", rateLimiterMiddleware.Handle(endpointCreatePaste)) - group.PATCH("/{id}", rateLimiterMiddleware.Handle(middlewareInjectPaste(middlewareValidateModificationToken(endpointModifyPaste)))) - group.DELETE("/{id}", rateLimiterMiddleware.Handle(middlewareInjectPaste(middlewareValidateModificationToken(endpointDeletePaste)))) - - if config.Current.Reports.Reports { - group.POST("/{id}/report", rateLimiterMiddleware.Handle(middlewareInjectPaste(endpointReportPaste))) - } -} - -// middlewareInjectPaste retrieves and injects the paste with the specified ID -func middlewareInjectPaste(next fasthttp.RequestHandler) fasthttp.RequestHandler { - return func(ctx *fasthttp.RequestCtx) { - pasteID := ctx.UserValue("id").(string) - - paste, err := storage.Current.Get(pasteID) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - if paste == nil { - ctx.SetStatusCode(fasthttp.StatusNotFound) - ctx.SetBodyString("paste not found") - return - } - - if paste.Metadata == nil { - paste.Metadata = map[string]interface{}{} - } - - ctx.SetUserValue("_paste", paste) - - next(ctx) - } -} - -// middlewareValidateModificationToken extracts and validates a given modification token for an injected paste -func middlewareValidateModificationToken(next fasthttp.RequestHandler) fasthttp.RequestHandler { - return func(ctx *fasthttp.RequestCtx) { - paste := ctx.UserValue("_paste").(*paste.Paste) - - authHeaderSplit := strings.SplitN(string(ctx.Request.Header.Peek("Authorization")), " ", 2) - if len(authHeaderSplit) < 2 || authHeaderSplit[0] != "Bearer" { - ctx.SetStatusCode(fasthttp.StatusUnauthorized) - ctx.SetBodyString("unauthorized") - return - } - - modificationToken := authHeaderSplit[1] - if config.Current.ModificationTokenMaster != "" && modificationToken == config.Current.ModificationTokenMaster { - next(ctx) - return - } - valid := paste.CheckModificationToken(modificationToken) - if !valid { - ctx.SetStatusCode(fasthttp.StatusUnauthorized) - ctx.SetBodyString("unauthorized") - return - } - - next(ctx) - } -} - -// endpointGetPaste handles the 'GET /v2/pastes/{id}' endpoint -func endpointGetPaste(ctx *fasthttp.RequestCtx) { - paste := ctx.UserValue("_paste").(*paste.Paste) - paste.ModificationToken = "" - - jsonData, err := json.Marshal(paste) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - ctx.SetBody(jsonData) -} - -type endpointCreatePastePayload struct { - Content string `json:"content"` - Metadata map[string]interface{} `json:"metadata"` -} - -// endpointCreatePaste handles the 'POST /v2/pastes' endpoint -func endpointCreatePaste(ctx *fasthttp.RequestCtx) { - // Read, parse and validate the request payload - payload := new(endpointCreatePastePayload) - if err := json.Unmarshal(ctx.PostBody(), payload); err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - if payload.Content == "" { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.SetBodyString("missing paste content") - return - } - if config.Current.LengthCap > 0 && len(payload.Content) > config.Current.LengthCap { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.SetBodyString("too large paste content") - return - } - - // Acquire a new paste ID - id, err := storage.AcquireID() - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - - // Prepare the paste object - if payload.Metadata == nil { - payload.Metadata = map[string]interface{}{} - } - paste := &paste.Paste{ - ID: id, - Content: payload.Content, - Created: time.Now().Unix(), - Metadata: payload.Metadata, - } - - // Create a new modification token if enabled - modificationToken := "" - if config.Current.ModificationTokens { - modificationToken = utils.RandomString(config.Current.ModificationTokenCharacters, config.Current.ModificationTokenLength) - paste.ModificationToken = modificationToken - - err = paste.HashModificationToken() - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - } - - // Save the paste - err = storage.Current.Save(paste) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - - // Respond with the paste - pasteCopy := *paste - pasteCopy.ModificationToken = modificationToken - jsonData, err := json.Marshal(pasteCopy) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - ctx.SetStatusCode(fasthttp.StatusCreated) - ctx.SetBody(jsonData) -} - -type endpointModifyPastePayload struct { - Content *string `json:"content"` - Metadata map[string]interface{} `json:"metadata"` -} - -// endpointModifyPaste handles the 'PATCH /v2/pastes/{id}' endpoint -func endpointModifyPaste(ctx *fasthttp.RequestCtx) { - // Read, parse and validate the request payload - payload := new(endpointModifyPastePayload) - if err := json.Unmarshal(ctx.PostBody(), payload); err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - if payload.Content != nil && *payload.Content == "" { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.SetBodyString("missing paste content") - return - } - if payload.Content != nil && config.Current.LengthCap > 0 && len(*payload.Content) > config.Current.LengthCap { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.SetBodyString("too large paste content") - return - } - - // Modify the paste itself - paste := ctx.UserValue("_paste").(*paste.Paste) - if payload.Content != nil { - paste.Content = *payload.Content - } - if payload.Metadata != nil { - for key, value := range payload.Metadata { - if value == nil { - delete(paste.Metadata, key) - continue - } - paste.Metadata[key] = value - } - } - - // Save the modified paste - if err := storage.Current.Save(paste); err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } -} - -// endpointDeletePaste handles the 'DELETE /v2/pastes/{id}' endpoint -func endpointDeletePaste(ctx *fasthttp.RequestCtx) { - paste := ctx.UserValue("_paste").(*paste.Paste) - if err := storage.Current.Delete(paste.ID); err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } -} - -type endpointReportPastePayload struct { - Reason string `json:"reason"` -} - -func endpointReportPaste(ctx *fasthttp.RequestCtx) { - // Read, parse and validate the request payload - payload := new(endpointReportPastePayload) - if err := json.Unmarshal(ctx.PostBody(), payload); err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - if payload.Reason == "" { - ctx.SetStatusCode(fasthttp.StatusBadRequest) - ctx.SetBodyString("missing report reason") - return - } - - request := &report.ReportRequest{ - Paste: ctx.UserValue("_paste").(*paste.Paste).ID, - Reason: payload.Reason, - } - response, err := report.SendReport(request) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - - jsonData, err := json.Marshal(response) - if err != nil { - ctx.SetStatusCode(fasthttp.StatusInternalServerError) - ctx.SetBodyString(err.Error()) - return - } - ctx.SetBody(jsonData) -} From 8b1a4d23b78e390587c3e10337a1d6636c278529 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Wed, 7 Jun 2023 18:25:28 +0200 Subject: [PATCH 04/25] restructure storage driver --- cmd/pasty/main.go | 14 +- go.mod | 29 +- go.sum | 898 +----------------- internal/config/config.go | 5 - internal/paste/paste.go | 30 - internal/pastes/paste.go | 9 + internal/pastes/repository.go | 14 + internal/randx/string.go | 2 +- internal/storage/driver.go | 22 +- internal/storage/postgres/driver.go | 68 ++ internal/storage/postgres/paste_repository.go | 63 ++ internal/storage/postgres/postgres_driver.go | 129 --- web/assets/js/modules/state.js | 44 +- 13 files changed, 243 insertions(+), 1084 deletions(-) delete mode 100644 internal/paste/paste.go create mode 100644 internal/pastes/paste.go create mode 100644 internal/pastes/repository.go create mode 100644 internal/storage/postgres/driver.go create mode 100644 internal/storage/postgres/paste_repository.go delete mode 100644 internal/storage/postgres/postgres_driver.go diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index d22f389..e763e29 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -5,10 +5,12 @@ import ( "github.com/lus/pasty/internal/config" "github.com/lus/pasty/internal/meta" "github.com/lus/pasty/internal/storage" + "github.com/lus/pasty/internal/storage/postgres" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "os" "os/signal" + "strings" ) func main() { @@ -39,12 +41,18 @@ func main() { zerolog.SetGlobalLevel(level) } - // Initialize the configured storage driver - driver, ok := storage.ResolveDriver(cfg.StorageDriver) - if !ok { + // Determine the correct storage driver to use + var driver storage.Driver + switch strings.TrimSpace(strings.ToLower(cfg.StorageDriver)) { + case "postgres": + driver = new(postgres.Driver) + break + default: log.Fatal().Str("driver_name", cfg.StorageDriver).Msg("An invalid storage driver name was given.") return } + + // Initialize the configured storage driver log.Info().Str("driver_name", cfg.StorageDriver).Msg("Initializing the storage driver...") if err := driver.Initialize(context.Background(), cfg); err != nil { log.Fatal().Err(err).Str("driver_name", cfg.StorageDriver).Msg("The storage driver could not be initialized.") diff --git a/go.mod b/go.mod index 996f579..e098204 100644 --- a/go.mod +++ b/go.mod @@ -3,30 +3,25 @@ module github.com/lus/pasty go 1.20 require ( - github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b - github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574 - github.com/jackc/pgx/v4 v4.11.0 - github.com/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16 + github.com/golang-migrate/migrate/v4 v4.16.1 + github.com/jackc/pgx/v5 v5.3.1 github.com/joho/godotenv v1.3.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/rs/zerolog v1.29.1 ) require ( - github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-multierror v1.1.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.8.1 // indirect - github.com/jackc/pgio v1.0.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.0.6 // indirect - github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.7.0 // indirect - github.com/jackc/puddle v1.1.3 // indirect - github.com/lib/pq v1.8.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.0 // indirect + github.com/lib/pq v1.10.2 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect - golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect - golang.org/x/text v0.3.3 // indirect + go.uber.org/atomic v1.11.0 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect ) diff --git a/go.sum b/go.sum index 4e31eb1..9019a71 100644 --- a/go.sum +++ b/go.sum @@ -1,887 +1,71 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg= -cloud.google.com/go v0.64.0/go.mod h1:xfORb36jGvE+6EexW71nMEtL025s3x6xvuYUKM4JLv4= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/spanner v1.9.0/go.mod h1:xvlEn0NZ5v1iJPYsBnUVRDNvccDxsBTEi16pJRKQVws= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b h1:rcCpjI1OMGtBY8nnBvExeM1pXNoaM35zqmXBGpgJR2o= -github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b/go.mod h1:GFtu6vaWaRJV5EvSFaVqgq/3Iq95xyYElBV/aupGzUo= -github.com/apache/arrow/go/arrow v0.0.0-20200601151325-b2287a20f230/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1 h1:pASeJT3R3YyVn+94qEPk0SnU1OQ20Jd/T+SPKy9xehY= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dhui/dktest v0.3.3 h1:DBuH/9GFaWbDRa42qsut/hbQu+srAQ0rPWnUoiGX7CA= -github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= -github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574 h1:YEVMe8861NCZxTMzTI5BCobpwGpt1Md6D8v00jDc68w= -github.com/golang-migrate/migrate/v4 v4.14.2-0.20201125065321-a53e6fc42574/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/golang-migrate/migrate/v4 v4.16.1 h1:O+0C55RbMN66pWm5MjO6mw0px6usGpY0+bkSGW9zCo0= +github.com/golang-migrate/migrate/v4 v4.16.1/go.mod h1:qXiwa/3Zeqaltm1MxOCZDYysW/F6folYiBgBG03l9hc= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM= -github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= -github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= -github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= -github.com/jackc/pgconn v1.8.1 h1:ySBX7Q87vOMqKU2bbmKbUvtYhauDFclYbNDYIE1/h6s= -github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.0.6 h1:b1105ZGEMFe7aCvrT1Cca3VoVb4ZFMaFJLJcg/3zD+8= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= -github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= -github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= -github.com/jackc/pgtype v1.7.0 h1:6f4kVsW01QftE38ufBYxKciO6gyioXSC0ABIRLcZrGs= -github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= -github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= -github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= -github.com/jackc/pgx/v4 v4.11.0 h1:J86tSWd3Y7nKjwT/43xZBvpi04keQWx8gNC2YkdJhZI= -github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3 h1:JnPg/5Q9xVJGfjsO5CPUOjnJps1JaRUm8I9FXVCFK94= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16 h1:sDjlyV4OzJYR2ZdLAAKZwV6N/CcX60xbGnPZzKJOZ50= -github.com/johejo/golang-migrate-extra v0.0.0-20210217013041-51a992e50d16/go.mod h1:lzH77MbyyahK7YO90wGRb65i9xLSoy2fD0dUSm23yMs= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk= +github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= -github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= -github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY= -github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/snowflakedb/glog v0.0.0-20180824191149-f5055e6f21ce/go.mod h1:EB/w24pR5VKI60ecFnKqXzxX3dOorz1rnVicQTQrGM0= -github.com/snowflakedb/gosnowflake v1.3.5/go.mod h1:13Ky+lxzIm3VqNDZJdyvu9MCGy+WgRdYFdXp96UcLZU= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200911024640-645f7a48b24f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3 h1:sg8vLDNIxFPHTchfhH1E3AI32BL3f23oie38xUWnJM8= -google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= -modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= -modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= -modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= -modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= -modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= -modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/config/config.go b/internal/config/config.go index 17c74c2..0864617 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,7 +6,6 @@ import ( "time" ) -// Config represents the general application configuration type Config struct { LogLevel string `default:"info" split_words:"true"` WebAddress string `default:":8080" split_words:"true"` @@ -25,26 +24,22 @@ type Config struct { Postgres *PostgresConfig } -// AutoDeleteConfig represents the configuration specific for the AutoDelete behaviour type AutoDeleteConfig struct { Enabled bool `default:"false"` Lifetime time.Duration `default:"720h"` TaskInterval time.Duration `default:"5m" split_words:"true"` } -// ReportConfig represents the configuration specific for the report system type ReportConfig struct { Enabled bool `default:"false" split_words:"true"` WebhookURL string `split_words:"true"` WebhookToken string `split_words:"true"` } -// PostgresConfig represents the configuration specific for the Postgres storage driver type PostgresConfig struct { DSN string `default:"postgres://pasty:pasty@localhost/pasty"` } -// Load loads the current config from environment variables and an optional .env file func Load() (*Config, error) { _ = godotenv.Overload() cfg := new(Config) diff --git a/internal/paste/paste.go b/internal/paste/paste.go deleted file mode 100644 index 99f7461..0000000 --- a/internal/paste/paste.go +++ /dev/null @@ -1,30 +0,0 @@ -package paste - -import ( - "github.com/alexedwards/argon2id" -) - -// Paste represents a paste -type Paste struct { - ID string `json:"id"` - Content string `json:"content"` - ModificationToken string `json:"modificationToken,omitempty"` - Created int64 `json:"created"` - Metadata map[string]interface{} `json:"metadata"` -} - -// HashModificationToken hashes the current modification token of a paste -func (paste *Paste) HashModificationToken() error { - hash, err := argon2id.CreateHash(paste.ModificationToken, argon2id.DefaultParams) - if err != nil { - return err - } - paste.ModificationToken = hash - return nil -} - -// CheckModificationToken checks whether the given modification token is correct -func (paste *Paste) CheckModificationToken(modificationToken string) bool { - match, err := argon2id.ComparePasswordAndHash(modificationToken, paste.ModificationToken) - return err == nil && match -} diff --git a/internal/pastes/paste.go b/internal/pastes/paste.go new file mode 100644 index 0000000..59d0a7c --- /dev/null +++ b/internal/pastes/paste.go @@ -0,0 +1,9 @@ +package pastes + +type Paste struct { + ID string `json:"id"` + Content string `json:"content"` + ModificationToken string `json:"modificationToken,omitempty"` + Created int64 `json:"created"` + Metadata map[string]interface{} `json:"metadata"` +} diff --git a/internal/pastes/repository.go b/internal/pastes/repository.go new file mode 100644 index 0000000..4529f5d --- /dev/null +++ b/internal/pastes/repository.go @@ -0,0 +1,14 @@ +package pastes + +import ( + "context" + "time" +) + +type Repository interface { + ListIDs(ctx context.Context) ([]string, error) + FindByID(ctx context.Context, id string) (*Paste, error) + Upsert(ctx context.Context, paste *Paste) error + DeleteByID(ctx context.Context, id string) error + DeleteOlderThan(ctx context.Context, age time.Duration) (int, error) +} diff --git a/internal/randx/string.go b/internal/randx/string.go index 8962838..b530407 100644 --- a/internal/randx/string.go +++ b/internal/randx/string.go @@ -4,7 +4,7 @@ import ( "math/rand" ) -// String returns a random string with the given length +// String generates a random string with the given length. func String(characters string, length int) string { bytes := make([]byte, length) for i := range bytes { diff --git a/internal/storage/driver.go b/internal/storage/driver.go index d55e0e6..3b4042e 100644 --- a/internal/storage/driver.go +++ b/internal/storage/driver.go @@ -3,29 +3,11 @@ package storage import ( "context" "github.com/lus/pasty/internal/config" - "strings" - - "github.com/lus/pasty/internal/paste" - "github.com/lus/pasty/internal/storage/postgres" + "github.com/lus/pasty/internal/pastes" ) -// Driver represents a storage driver type Driver interface { Initialize(ctx context.Context, cfg *config.Config) error Close() error - ListIDs() ([]string, error) - Get(id string) (*paste.Paste, error) - Save(paste *paste.Paste) error - Delete(id string) error - Cleanup() (int, error) -} - -// ResolveDriver returns the driver with the given name if it exists -func ResolveDriver(name string) (Driver, bool) { - switch strings.TrimSpace(strings.ToLower(name)) { - case "postgres": - return new(postgres.Driver), true - default: - return nil, false - } + Pastes() pastes.Repository } diff --git a/internal/storage/postgres/driver.go b/internal/storage/postgres/driver.go new file mode 100644 index 0000000..39f649f --- /dev/null +++ b/internal/storage/postgres/driver.go @@ -0,0 +1,68 @@ +package postgres + +import ( + "context" + "embed" + "errors" + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/golang-migrate/migrate/v4/source/iofs" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/lus/pasty/internal/config" + "github.com/lus/pasty/internal/pastes" + "github.com/lus/pasty/internal/storage" + "github.com/rs/zerolog/log" +) + +//go:embed migrations/*.sql +var migrations embed.FS + +type Driver struct { + connPool *pgxpool.Pool + pastes *pasteRepository +} + +var _ storage.Driver = (*Driver)(nil) + +func (driver *Driver) Initialize(ctx context.Context, cfg *config.Config) error { + pool, err := pgxpool.New(ctx, cfg.Postgres.DSN) + if err != nil { + return err + } + + log.Info().Msg("Performing PostgreSQL database migrations...") + source, err := iofs.New(migrations, "migrations") + if err != nil { + pool.Close() + return err + } + migrator, err := migrate.NewWithSourceInstance("iofs", source, cfg.Postgres.DSN) + if err != nil { + pool.Close() + return err + } + defer func() { + _, _ = migrator.Close() + }() + if err := migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) { + pool.Close() + return err + } + log.Info().Msg("Successfully performed PostgreSQL database migrations.") + + driver.connPool = pool + driver.pastes = &pasteRepository{ + connPool: pool, + } + return nil +} + +func (driver *Driver) Close() error { + driver.pastes = nil + driver.connPool.Close() + return nil +} + +func (driver *Driver) Pastes() pastes.Repository { + return driver.pastes +} diff --git a/internal/storage/postgres/paste_repository.go b/internal/storage/postgres/paste_repository.go new file mode 100644 index 0000000..17ec8dc --- /dev/null +++ b/internal/storage/postgres/paste_repository.go @@ -0,0 +1,63 @@ +package postgres + +import ( + "context" + "errors" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/lus/pasty/internal/pastes" + "time" +) + +type pasteRepository struct { + connPool *pgxpool.Pool +} + +var _ pastes.Repository = (*pasteRepository)(nil) + +func (repo *pasteRepository) ListIDs(ctx context.Context) ([]string, error) { + rows, _ := repo.connPool.Query(ctx, "SELECT id FROM pastes") + result, err := pgx.CollectRows(rows, pgx.RowTo[string]) + if err != nil && !errors.Is(err, pgx.ErrNoRows) { + return nil, err + } + return result, nil +} + +func (repo *pasteRepository) FindByID(ctx context.Context, id string) (*pastes.Paste, error) { + rows, _ := repo.connPool.Query(ctx, "SELECT * FROM pastes WHERE id = $1", id) + result, err := pgx.CollectOneRow(rows, pgx.RowToAddrOfStructByPos[pastes.Paste]) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, nil + } + return nil, err + } + return result, nil +} + +func (repo *pasteRepository) Upsert(ctx context.Context, paste *pastes.Paste) error { + const query = ` + INSERT INTO pastes + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (id) DO UPDATE + SET content = excluded.content, + "modificationToken" = excluded."modificationToken", + metadata = excluded.metadata + ` + _, err := repo.connPool.Exec(ctx, query, paste.ID, paste.Content, paste.ModificationToken, paste.Created, paste.Metadata) + return err +} + +func (repo *pasteRepository) DeleteByID(ctx context.Context, id string) error { + _, err := repo.connPool.Exec(ctx, "DELETE FROM pastes WHERE id = $1", id) + return err +} + +func (repo *pasteRepository) DeleteOlderThan(ctx context.Context, age time.Duration) (int, error) { + tag, err := repo.connPool.Exec(ctx, "DELETE FROM pastes WHERE created < $1", time.Now().Add(-age).Unix()) + if err != nil { + return 0, err + } + return int(tag.RowsAffected()), nil +} diff --git a/internal/storage/postgres/postgres_driver.go b/internal/storage/postgres/postgres_driver.go deleted file mode 100644 index 9b70e7d..0000000 --- a/internal/storage/postgres/postgres_driver.go +++ /dev/null @@ -1,129 +0,0 @@ -package postgres - -import ( - "context" - "embed" - "errors" - "time" - - "github.com/golang-migrate/migrate/v4" - _ "github.com/golang-migrate/migrate/v4/database/postgres" - "github.com/jackc/pgx/v4" - "github.com/jackc/pgx/v4/pgxpool" - "github.com/johejo/golang-migrate-extra/source/iofs" - "github.com/lus/pasty/internal/config" - "github.com/lus/pasty/internal/paste" -) - -//go:embed migrations/*.sql -var migrations embed.FS - -// Driver represents the Postgres storage driver -type Driver struct { - pool *pgxpool.Pool - autoDeleteLifetime time.Duration -} - -// Initialize initializes the Postgres storage driver -func (driver *Driver) Initialize(ctx context.Context, cfg *config.Config) error { - pool, err := pgxpool.Connect(ctx, cfg.Postgres.DSN) - if err != nil { - return err - } - - source, err := iofs.New(migrations, "migrations") - if err != nil { - return err - } - - migrator, err := migrate.NewWithSourceInstance("iofs", source, cfg.Postgres.DSN) - if err != nil { - return err - } - - if err := migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) { - return err - } - - driver.pool = pool - driver.autoDeleteLifetime = cfg.AutoDelete.Lifetime - return nil -} - -// Close terminates the Postgres storage driver -func (driver *Driver) Close() error { - driver.pool.Close() - return nil -} - -// ListIDs returns a list of all existing paste IDs -func (driver *Driver) ListIDs() ([]string, error) { - query := "SELECT id FROM pastes" - - rows, err := driver.pool.Query(context.Background(), query) - if err != nil { - return []string{}, err - } - - var ids []string - for rows.Next() { - var id string - if err := rows.Scan(&id); err != nil { - return []string{}, err - } - ids = append(ids, id) - } - - return ids, nil -} - -// Get loads a paste -func (driver *Driver) Get(id string) (*paste.Paste, error) { - query := "SELECT * FROM pastes WHERE id = $1" - - row := driver.pool.QueryRow(context.Background(), query, id) - - paste := new(paste.Paste) - if err := row.Scan(&paste.ID, &paste.Content, &paste.ModificationToken, &paste.Created, &paste.Metadata); err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return nil, nil - } - return nil, err - } - return paste, nil -} - -// Save saves a paste -func (driver *Driver) Save(paste *paste.Paste) error { - query := ` - INSERT INTO pastes (id, content, "modificationToken", created, metadata) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (id) DO UPDATE - SET content = excluded.content, - "modificationToken" = excluded."modificationToken", - created = excluded.created, - metadata = excluded.metadata - ` - - _, err := driver.pool.Exec(context.Background(), query, paste.ID, paste.Content, paste.ModificationToken, paste.Created, paste.Metadata) - return err -} - -// Delete deletes a paste -func (driver *Driver) Delete(id string) error { - query := "DELETE FROM pastes WHERE id = $1" - - _, err := driver.pool.Exec(context.Background(), query, id) - return err -} - -// Cleanup cleans up the expired pastes -func (driver *Driver) Cleanup() (int, error) { - query := "DELETE FROM pastes WHERE created < $1" - - tag, err := driver.pool.Exec(context.Background(), query, time.Now().Add(-driver.autoDeleteLifetime).Unix()) - if err != nil { - return 0, err - } - return int(tag.RowsAffected()), nil -} diff --git a/web/assets/js/modules/state.js b/web/assets/js/modules/state.js index 55902d4..557f97b 100644 --- a/web/assets/js/modules/state.js +++ b/web/assets/js/modules/state.js @@ -64,20 +64,20 @@ export async function initialize() { } if (location.pathname !== "/") { - // Extract the paste data (ID and language) + // Extract the pastes data (ID and language) const split = location.pathname.replace("/", "").split("."); const pasteID = split[0]; const language = split[1]; - // Try to retrieve the paste data from the API + // Try to retrieve the pastes data from the API const response = await API.getPaste(pasteID); if (!response.ok) { - Notifications.error("Could not load paste: " + await response.text() + ""); + Notifications.error("Could not load pastes: " + await response.text() + ""); setTimeout(() => location.replace(location.protocol + "//" + location.host), 3000); return; } - // Set the persistent paste data + // Set the persistent pastes data PASTE_ID = pasteID; LANGUAGE = language; @@ -95,7 +95,7 @@ export async function initialize() { ENCRYPTION_IV = json.metadata.pf_encryption.iv; } catch (error) { console.log(error); - Notifications.error("Could not decrypt paste; make sure the decryption key is correct."); + Notifications.error("Could not decrypt pastes; make sure the decryption key is correct."); setTimeout(() => location.replace(location.protocol + "//" + location.host), 3000); return; } @@ -104,7 +104,7 @@ export async function initialize() { // Fill the code block with the just received data updateCode(); } else { - // Give the user the opportunity to paste his code + // Give the user the opportunity to pastes his code INPUT_ELEMENT.classList.remove("hidden"); INPUT_ELEMENT.focus(); LIFETIME_CONTAINER_ELEMENT.classList.remove("hidden"); @@ -138,7 +138,7 @@ async function loadAPIInformation() { // Display the API version document.getElementById("version").innerText = API_INFORMATION.version; - // Display the paste lifetime + // Display the pastes lifetime document.getElementById("lifetime").innerText = Duration.format(API_INFORMATION.pasteLifetime); } @@ -289,7 +289,7 @@ function setupButtonFunctionality() { return; } - // Encrypt the paste if needed + // Encrypt the pastes if needed let value = INPUT_ELEMENT.value; let metadata; let key; @@ -305,20 +305,20 @@ function setupButtonFunctionality() { key = encrypted.key; } - // Try to create the paste + // Try to create the pastes const response = await API.createPaste(value, metadata); if (!response.ok) { - Notifications.error("Error while creating paste: " + await response.text() + ""); + Notifications.error("Error while creating pastes: " + await response.text() + ""); return; } const data = await response.json(); // Display the modification token if provided if (data.modificationToken) { - prompt("The modification token for your paste is:", data.modificationToken); + prompt("The modification token for your pastes is:", data.modificationToken); } - // Redirect the user to his newly created paste + // Redirect the user to his newly created pastes location.replace(location.protocol + "//" + location.host + "/" + data.id + (key ? "#" + key : "")); }); }); @@ -333,10 +333,10 @@ function setupButtonFunctionality() { return; } - // Try to delete the paste + // Try to delete the pastes const response = await API.deletePaste(PASTE_ID, modificationToken); if (!response.ok) { - Notifications.error("Error while deleting paste: " + await response.text() + ""); + Notifications.error("Error while deleting pastes: " + await response.text() + ""); return; } @@ -369,17 +369,17 @@ function setupButtonFunctionality() { return; } - // Re-encrypt the paste data if needed + // Re-encrypt the pastes data if needed let value = INPUT_ELEMENT.value; if (ENCRYPTION_KEY && ENCRYPTION_IV) { const encrypted = await Encryption.encrypt(await Encryption.encryptionDataFromHex(ENCRYPTION_KEY, ENCRYPTION_IV), value); value = encrypted.result; } - // Try to edit the paste + // Try to edit the pastes const response = await API.editPaste(PASTE_ID, modificationToken, value); if (!response.ok) { - Notifications.error("Error while editing paste: " + await response.text() + ""); + Notifications.error("Error while editing pastes: " + await response.text() + ""); return; } @@ -387,13 +387,13 @@ function setupButtonFunctionality() { CODE = INPUT_ELEMENT.value; updateCode(); toggleEditMode(); - Notifications.success("Successfully edited paste."); + Notifications.success("Successfully edited pastes."); }); BUTTON_TOGGLE_ENCRYPTION_ELEMENT.addEventListener("click", () => { const active = BUTTON_TOGGLE_ENCRYPTION_ELEMENT.classList.toggle("active"); localStorage.setItem("encryption", active); - Notifications.success((active ? "Enabled" : "Disabled") + " automatic paste encryption."); + Notifications.success((active ? "Enabled" : "Disabled") + " automatic pastes encryption."); }); BUTTON_REPORT_ELEMENT.addEventListener("click", async () => { @@ -403,17 +403,17 @@ function setupButtonFunctionality() { return; } - // Try to report the paste + // Try to report the pastes const response = await API.reportPaste(PASTE_ID, reason); if (!response.ok) { - Notifications.error("Error while reporting paste: " + await response.text() + ""); + Notifications.error("Error while reporting pastes: " + await response.text() + ""); return; } // Show the response message const data = await response.json(); if (!data.success) { - Notifications.error("Error while reporting paste: " + data.message + ""); + Notifications.error("Error while reporting pastes: " + data.message + ""); return; } Notifications.success(data.message); From a8077a54f99f4644a66074c0912ce8783d888f2d Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Wed, 7 Jun 2023 18:27:23 +0200 Subject: [PATCH 05/25] update dependencies --- go.mod | 10 +++++----- go.sum | 21 +++++++++++++-------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index e098204..34ce6e6 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/golang-migrate/migrate/v4 v4.16.1 github.com/jackc/pgx/v5 v5.3.1 - github.com/joho/godotenv v1.3.0 + github.com/joho/godotenv v1.5.1 github.com/kelseyhightower/envconfig v1.4.0 github.com/rs/zerolog v1.29.1 ) @@ -16,11 +16,11 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.0 // indirect - github.com/lib/pq v1.10.2 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/crypto v0.7.0 // indirect + golang.org/x/crypto v0.9.0 // indirect golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect diff --git a/go.sum b/go.sum index 9019a71..98db06e 100644 --- a/go.sum +++ b/go.sum @@ -25,16 +25,19 @@ github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk= github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -53,14 +56,16 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= From 941da057ae2fec0beea35aa99867f63943ad83ef Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Wed, 7 Jun 2023 19:51:54 +0200 Subject: [PATCH 06/25] reimplement v2 API controller --- cmd/pasty/main.go | 4 +- go.mod | 2 + go.sum | 38 ++++++++++++++++ internal/pastes/id_generation.go | 19 ++++++++ internal/pastes/paste.go | 32 ++++++++++--- internal/slices/slice_utils.go | 10 +++++ internal/storage/driver.go | 3 +- internal/storage/postgres/driver.go | 14 ++++-- internal/web/response_writer.go | 33 ++++++++++++++ internal/web/server.go | 49 ++++++++++++++++++++ internal/web/v2_end_create_paste.go | 70 +++++++++++++++++++++++++++++ internal/web/v2_end_delete_paste.go | 18 ++++++++ internal/web/v2_end_get_paste.go | 18 ++++++++ internal/web/v2_end_modify_paste.go | 60 +++++++++++++++++++++++++ internal/web/v2_mid_authorize.go | 37 +++++++++++++++ internal/web/v2_mid_inject_paste.go | 38 ++++++++++++++++ web/assets/js/modules/api.js | 2 +- 17 files changed, 433 insertions(+), 14 deletions(-) create mode 100644 internal/pastes/id_generation.go create mode 100644 internal/slices/slice_utils.go create mode 100644 internal/web/response_writer.go create mode 100644 internal/web/server.go create mode 100644 internal/web/v2_end_create_paste.go create mode 100644 internal/web/v2_end_delete_paste.go create mode 100644 internal/web/v2_end_get_paste.go create mode 100644 internal/web/v2_end_modify_paste.go create mode 100644 internal/web/v2_mid_authorize.go create mode 100644 internal/web/v2_mid_inject_paste.go diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index e763e29..4e8eff9 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -45,7 +45,7 @@ func main() { var driver storage.Driver switch strings.TrimSpace(strings.ToLower(cfg.StorageDriver)) { case "postgres": - driver = new(postgres.Driver) + driver = postgres.New(cfg.Postgres.DSN) break default: log.Fatal().Str("driver_name", cfg.StorageDriver).Msg("An invalid storage driver name was given.") @@ -54,7 +54,7 @@ func main() { // Initialize the configured storage driver log.Info().Str("driver_name", cfg.StorageDriver).Msg("Initializing the storage driver...") - if err := driver.Initialize(context.Background(), cfg); err != nil { + if err := driver.Initialize(context.Background()); err != nil { log.Fatal().Err(err).Str("driver_name", cfg.StorageDriver).Msg("The storage driver could not be initialized.") return } diff --git a/go.mod b/go.mod index 34ce6e6..6f24254 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/lus/pasty go 1.20 require ( + github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 + github.com/go-chi/chi/v5 v5.0.8 github.com/golang-migrate/migrate/v4 v4.16.1 github.com/jackc/pgx/v5 v5.3.1 github.com/joho/godotenv v1.5.1 diff --git a/go.sum b/go.sum index 98db06e..c589896 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 h1:qZaEtLxnqY5mJ0fVKbk31NVhlgi0yrKm51Pq/I5wcz4= +github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736/go.mod h1:mTeFRcTdnpzOlRjMoFYC/80HwVUreupyAiqPkCZQOXc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -8,6 +10,8 @@ github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m3 github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= +github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/golang-migrate/migrate/v4 v4.16.1 h1:O+0C55RbMN66pWm5MjO6mw0px6usGpY0+bkSGW9zCo0= @@ -54,23 +58,57 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/pastes/id_generation.go b/internal/pastes/id_generation.go new file mode 100644 index 0000000..6046e8b --- /dev/null +++ b/internal/pastes/id_generation.go @@ -0,0 +1,19 @@ +package pastes + +import ( + "context" + "github.com/lus/pasty/internal/randx" +) + +func GenerateID(ctx context.Context, repo Repository, charset string, length int) (string, error) { + for { + id := randx.String(charset, length) + existing, err := repo.FindByID(ctx, id) + if err != nil { + return "", err + } + if existing == nil { + return id, nil + } + } +} diff --git a/internal/pastes/paste.go b/internal/pastes/paste.go index 59d0a7c..5371a36 100644 --- a/internal/pastes/paste.go +++ b/internal/pastes/paste.go @@ -1,9 +1,31 @@ package pastes +import "github.com/alexedwards/argon2id" + type Paste struct { - ID string `json:"id"` - Content string `json:"content"` - ModificationToken string `json:"modificationToken,omitempty"` - Created int64 `json:"created"` - Metadata map[string]interface{} `json:"metadata"` + ID string `json:"id"` + Content string `json:"content"` + ModificationToken string `json:"modificationToken,omitempty"` + Created int64 `json:"created"` + Metadata map[string]any `json:"metadata"` +} + +func (paste *Paste) HashModificationToken() error { + if paste.ModificationToken == "" { + return nil + } + hash, err := argon2id.CreateHash(paste.ModificationToken, argon2id.DefaultParams) + if err != nil { + return err + } + paste.ModificationToken = hash + return nil +} + +func (paste *Paste) CheckModificationToken(modificationToken string) bool { + if paste.ModificationToken == "" { + return false + } + match, err := argon2id.ComparePasswordAndHash(modificationToken, paste.ModificationToken) + return err == nil && match } diff --git a/internal/slices/slice_utils.go b/internal/slices/slice_utils.go new file mode 100644 index 0000000..f162737 --- /dev/null +++ b/internal/slices/slice_utils.go @@ -0,0 +1,10 @@ +package slices + +func Contains[T comparable](src []T, val T) bool { + for _, elem := range src { + if elem == val { + return true + } + } + return false +} diff --git a/internal/storage/driver.go b/internal/storage/driver.go index 3b4042e..1283905 100644 --- a/internal/storage/driver.go +++ b/internal/storage/driver.go @@ -2,12 +2,11 @@ package storage import ( "context" - "github.com/lus/pasty/internal/config" "github.com/lus/pasty/internal/pastes" ) type Driver interface { - Initialize(ctx context.Context, cfg *config.Config) error + Initialize(ctx context.Context) error Close() error Pastes() pastes.Repository } diff --git a/internal/storage/postgres/driver.go b/internal/storage/postgres/driver.go index 39f649f..e401044 100644 --- a/internal/storage/postgres/driver.go +++ b/internal/storage/postgres/driver.go @@ -8,7 +8,6 @@ import ( _ "github.com/golang-migrate/migrate/v4/database/postgres" "github.com/golang-migrate/migrate/v4/source/iofs" "github.com/jackc/pgx/v5/pgxpool" - "github.com/lus/pasty/internal/config" "github.com/lus/pasty/internal/pastes" "github.com/lus/pasty/internal/storage" "github.com/rs/zerolog/log" @@ -18,14 +17,21 @@ import ( var migrations embed.FS type Driver struct { + dsn string connPool *pgxpool.Pool pastes *pasteRepository } var _ storage.Driver = (*Driver)(nil) -func (driver *Driver) Initialize(ctx context.Context, cfg *config.Config) error { - pool, err := pgxpool.New(ctx, cfg.Postgres.DSN) +func New(dsn string) *Driver { + return &Driver{ + dsn: dsn, + } +} + +func (driver *Driver) Initialize(ctx context.Context) error { + pool, err := pgxpool.New(ctx, driver.dsn) if err != nil { return err } @@ -36,7 +42,7 @@ func (driver *Driver) Initialize(ctx context.Context, cfg *config.Config) error pool.Close() return err } - migrator, err := migrate.NewWithSourceInstance("iofs", source, cfg.Postgres.DSN) + migrator, err := migrate.NewWithSourceInstance("iofs", source, driver.dsn) if err != nil { pool.Close() return err diff --git a/internal/web/response_writer.go b/internal/web/response_writer.go new file mode 100644 index 0000000..9f3a947 --- /dev/null +++ b/internal/web/response_writer.go @@ -0,0 +1,33 @@ +package web + +import ( + "encoding/json" + "net/http" +) + +func writeErr(writer http.ResponseWriter, err error) { + writeString(writer, http.StatusInternalServerError, err.Error()) +} + +func writeString(writer http.ResponseWriter, status int, value string) { + writer.WriteHeader(status) + writer.Write([]byte(value)) +} + +func writeJSON(writer http.ResponseWriter, status int, value any) error { + jsonData, err := json.Marshal(value) + if err != nil { + return err + } + + writer.WriteHeader(status) + writer.Write(jsonData) + + return nil +} + +func writeJSONOrErr(writer http.ResponseWriter, status int, value any) { + if err := writeJSON(writer, status, value); err != nil { + writeErr(writer, err) + } +} diff --git a/internal/web/server.go b/internal/web/server.go new file mode 100644 index 0000000..9eb484b --- /dev/null +++ b/internal/web/server.go @@ -0,0 +1,49 @@ +package web + +import ( + "github.com/go-chi/chi/v5" + "github.com/lus/pasty/internal/storage" + "net/http" +) + +type Server struct { + // The address the web server should listen to. + Address string + + // The storage driver to use. + Storage storage.Driver + + // Whether the Hastebin support should be enabled. + // If this is set to 'false', the Hastebin specific endpoints will not be registered. + HastebinSupport bool + + // The length of newly generated paste IDs. + PasteIDLength int + // The charset to use when generating new paste IDs. + PasteIDCharset string + + // The maximum length of newly generated pastes. + PasteLengthCap int + + // Whether modification tokens are enabled. + ModificationTokensEnabled bool + // The length of newly generated modification tokens. + ModificationTokenLength int + // The charset to use when generating new modification tokens. + ModificationTokenCharset string + + // The administration tokens. + AdminTokens []string +} + +func (server *Server) Start() error { + router := chi.NewRouter() + + // Register the paste API endpoints + router.With(server.v2MiddlewareInjectPaste).Get("/api/v2/pastes/{paste_id}", server.v2EndpointGetPaste) + router.Post("/api/v2/pastes", server.v2EndpointCreatePaste) + router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Patch("/api/v2/pastes/{paste_id}", server.v2EndpointModifyPaste) + router.Delete("/api/v2/pastes/{paste_id}", server.v2EndpointDeletePaste) + + return http.ListenAndServe(server.Address, router) +} diff --git a/internal/web/v2_end_create_paste.go b/internal/web/v2_end_create_paste.go new file mode 100644 index 0000000..4ac0cb4 --- /dev/null +++ b/internal/web/v2_end_create_paste.go @@ -0,0 +1,70 @@ +package web + +import ( + "encoding/json" + "github.com/lus/pasty/internal/pastes" + "github.com/lus/pasty/internal/randx" + "io" + "net/http" + "time" +) + +type v2EndpointCreatePastePayload struct { + Content string `json:"content"` + Metadata map[string]any `json:"metadata"` +} + +func (server *Server) v2EndpointCreatePaste(writer http.ResponseWriter, request *http.Request) { + // Read, parse and validate the request payload + body, err := io.ReadAll(request.Body) + if err != nil { + writeErr(writer, err) + return + } + payload := new(v2EndpointCreatePastePayload) + if err := json.Unmarshal(body, payload); err != nil { + writeErr(writer, err) + return + } + if payload.Content == "" { + writeString(writer, http.StatusBadRequest, "missing paste content") + return + } + if server.PasteLengthCap > 0 && len(payload.Content) > server.PasteLengthCap { + writeString(writer, http.StatusBadRequest, "too large paste content") + return + } + + id, err := pastes.GenerateID(request.Context(), server.Storage.Pastes(), server.PasteIDCharset, server.PasteIDLength) + if err != nil { + writeErr(writer, err) + return + } + + paste := &pastes.Paste{ + ID: id, + Content: payload.Content, + Created: time.Now().Unix(), + Metadata: payload.Metadata, + } + + modificationToken := "" + if server.ModificationTokensEnabled { + modificationToken = randx.String(server.ModificationTokenCharset, server.ModificationTokenLength) + paste.ModificationToken = modificationToken + + if err := paste.HashModificationToken(); err != nil { + writeErr(writer, err) + return + } + } + + if err := server.Storage.Pastes().Upsert(request.Context(), paste); err != nil { + writeErr(writer, err) + return + } + + cpy := *paste + cpy.ModificationToken = modificationToken + writeJSONOrErr(writer, http.StatusCreated, cpy) +} diff --git a/internal/web/v2_end_delete_paste.go b/internal/web/v2_end_delete_paste.go new file mode 100644 index 0000000..f5e6c1b --- /dev/null +++ b/internal/web/v2_end_delete_paste.go @@ -0,0 +1,18 @@ +package web + +import ( + "github.com/lus/pasty/internal/pastes" + "net/http" +) + +func (server *Server) v2EndpointDeletePaste(writer http.ResponseWriter, request *http.Request) { + paste, ok := request.Context().Value("paste").(*pastes.Paste) + if !ok { + writeString(writer, http.StatusInternalServerError, "missing paste object") + return + } + + if err := server.Storage.Pastes().DeleteByID(request.Context(), paste.ID); err != nil { + writeErr(writer, err) + } +} diff --git a/internal/web/v2_end_get_paste.go b/internal/web/v2_end_get_paste.go new file mode 100644 index 0000000..5be338e --- /dev/null +++ b/internal/web/v2_end_get_paste.go @@ -0,0 +1,18 @@ +package web + +import ( + "github.com/lus/pasty/internal/pastes" + "net/http" +) + +func (server *Server) v2EndpointGetPaste(writer http.ResponseWriter, request *http.Request) { + paste, ok := request.Context().Value("paste").(*pastes.Paste) + if !ok { + writeString(writer, http.StatusInternalServerError, "missing paste object") + return + } + + cpy := *paste + cpy.ModificationToken = "" + writeJSONOrErr(writer, http.StatusOK, cpy) +} diff --git a/internal/web/v2_end_modify_paste.go b/internal/web/v2_end_modify_paste.go new file mode 100644 index 0000000..ad648ea --- /dev/null +++ b/internal/web/v2_end_modify_paste.go @@ -0,0 +1,60 @@ +package web + +import ( + "encoding/json" + "github.com/lus/pasty/internal/pastes" + "io" + "net/http" +) + +type v2EndpointModifyPastePayload struct { + Content *string `json:"content"` + Metadata map[string]any `json:"metadata"` +} + +func (server *Server) v2EndpointModifyPaste(writer http.ResponseWriter, request *http.Request) { + paste, ok := request.Context().Value("paste").(*pastes.Paste) + if !ok { + writeString(writer, http.StatusInternalServerError, "missing paste object") + return + } + + // Read, parse and validate the request payload + body, err := io.ReadAll(request.Body) + if err != nil { + writeErr(writer, err) + return + } + payload := new(v2EndpointModifyPastePayload) + if err := json.Unmarshal(body, payload); err != nil { + writeErr(writer, err) + return + } + if payload.Content != nil && *payload.Content == "" { + writeString(writer, http.StatusBadRequest, "missing paste content") + return + } + if payload.Content != nil && server.PasteLengthCap > 0 && len(*payload.Content) > server.PasteLengthCap { + writeString(writer, http.StatusBadRequest, "too large paste content") + return + } + + // Modify the paste itself + if payload.Content != nil { + paste.Content = *payload.Content + } + if payload.Metadata != nil { + for key, value := range payload.Metadata { + if value == nil { + delete(paste.Metadata, key) + continue + } + paste.Metadata[key] = value + } + } + + // Save the modified paste + if err := server.Storage.Pastes().Upsert(request.Context(), paste); err != nil { + writeErr(writer, err) + } +} diff --git a/internal/web/v2_mid_authorize.go b/internal/web/v2_mid_authorize.go new file mode 100644 index 0000000..5bd66c1 --- /dev/null +++ b/internal/web/v2_mid_authorize.go @@ -0,0 +1,37 @@ +package web + +import ( + "github.com/lus/pasty/internal/pastes" + "github.com/lus/pasty/internal/slices" + "net/http" + "strings" +) + +func (server *Server) v2MiddlewareAuthorize(next http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + paste, ok := request.Context().Value("paste").(*pastes.Paste) + if !ok { + writeString(writer, http.StatusInternalServerError, "missing paste object") + return + } + + authHeader := strings.SplitN(request.Header.Get("Authorization"), " ", 2) + if len(authHeader) != 2 || authHeader[0] != "Bearer" { + writeString(writer, http.StatusUnauthorized, "unauthorized") + return + } + + isAdmin := slices.Contains(server.AdminTokens, authHeader[1]) + if isAdmin { + next.ServeHTTP(writer, request) + return + } + + if !server.ModificationTokensEnabled || !paste.CheckModificationToken(authHeader[1]) { + writeString(writer, http.StatusUnauthorized, "unauthorized") + return + } + + next.ServeHTTP(writer, request) + }) +} diff --git a/internal/web/v2_mid_inject_paste.go b/internal/web/v2_mid_inject_paste.go new file mode 100644 index 0000000..399a1f2 --- /dev/null +++ b/internal/web/v2_mid_inject_paste.go @@ -0,0 +1,38 @@ +package web + +import ( + "context" + "github.com/go-chi/chi/v5" + "net/http" + "strings" +) + +func (server *Server) v2MiddlewareInjectPaste(next http.Handler) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + pasteID := strings.TrimSpace(chi.URLParam(request, "paste_id")) + if pasteID == "" { + writeString(writer, http.StatusNotFound, "paste not found") + return + } + + paste, err := server.Storage.Pastes().FindByID(request.Context(), pasteID) + if err != nil { + if pasteID == "" { + writeErr(writer, err) + return + } + } + if paste == nil { + writeString(writer, http.StatusNotFound, "paste not found") + return + } + + if paste.Metadata == nil { + paste.Metadata = make(map[string]any) + } + + request = request.WithContext(context.WithValue(request.Context(), "paste", paste)) + + next.ServeHTTP(writer, request) + }) +} diff --git a/web/assets/js/modules/api.js b/web/assets/js/modules/api.js index 6140672..d4c84aa 100644 --- a/web/assets/js/modules/api.js +++ b/web/assets/js/modules/api.js @@ -1,4 +1,4 @@ -const API_BASE_URL = location.protocol + "//" + location.host + "/api/v2"; +const API_BASE_URL = location.protocol + "//" + location.host + "/web/v2"; export async function getAPIInformation() { return fetch(API_BASE_URL + "/info"); From a53bd39dbd30482c52e3ec0f82953d9107dd73ae Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Thu, 8 Jun 2023 19:17:34 +0200 Subject: [PATCH 07/25] implement frontend routing --- Dockerfile | 2 +- .../web/frontend}/assets/css/style.css | 0 .../web/frontend}/assets/css/style.css.map | 0 .../web/frontend}/assets/css/style.scss | 0 .../web/frontend}/assets/js/app.js | 0 .../frontend}/assets/js/modules/animation.js | 0 .../web/frontend}/assets/js/modules/api.js | 0 .../frontend}/assets/js/modules/duration.js | 0 .../frontend}/assets/js/modules/encryption.js | 0 .../assets/js/modules/notifications.js | 0 .../frontend}/assets/js/modules/spinner.js | 0 .../web/frontend}/assets/js/modules/state.js | 0 .../frontend}/assets/libs/aesjs/aes.min.js | 0 .../assets/libs/animatecss/animate.min.css | 0 .../assets/libs/highlightjs/highlight.min.js | 0 .../libs/highlightjs/solarized-dark.min.css | 0 {web => internal/web/frontend}/index.html | 0 internal/web/frontend_server.go | 77 +++++++++++++++++++ internal/web/server.go | 4 + 19 files changed, 82 insertions(+), 1 deletion(-) rename {web => internal/web/frontend}/assets/css/style.css (100%) rename {web => internal/web/frontend}/assets/css/style.css.map (100%) rename {web => internal/web/frontend}/assets/css/style.scss (100%) rename {web => internal/web/frontend}/assets/js/app.js (100%) rename {web => internal/web/frontend}/assets/js/modules/animation.js (100%) rename {web => internal/web/frontend}/assets/js/modules/api.js (100%) rename {web => internal/web/frontend}/assets/js/modules/duration.js (100%) rename {web => internal/web/frontend}/assets/js/modules/encryption.js (100%) rename {web => internal/web/frontend}/assets/js/modules/notifications.js (100%) rename {web => internal/web/frontend}/assets/js/modules/spinner.js (100%) rename {web => internal/web/frontend}/assets/js/modules/state.js (100%) rename {web => internal/web/frontend}/assets/libs/aesjs/aes.min.js (100%) rename {web => internal/web/frontend}/assets/libs/animatecss/animate.min.css (100%) rename {web => internal/web/frontend}/assets/libs/highlightjs/highlight.min.js (100%) rename {web => internal/web/frontend}/assets/libs/highlightjs/solarized-dark.min.css (100%) rename {web => internal/web/frontend}/index.html (100%) create mode 100644 internal/web/frontend_server.go diff --git a/Dockerfile b/Dockerfile index 73419e5..e9ab90c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,6 @@ RUN go build \ FROM gcr.io/distroless/base:latest WORKDIR /root COPY --from=build /app/pasty . -COPY web ./web/ +COPY internal/web/web ./web/ EXPOSE 8080 CMD ["./pasty"] \ No newline at end of file diff --git a/web/assets/css/style.css b/internal/web/frontend/assets/css/style.css similarity index 100% rename from web/assets/css/style.css rename to internal/web/frontend/assets/css/style.css diff --git a/web/assets/css/style.css.map b/internal/web/frontend/assets/css/style.css.map similarity index 100% rename from web/assets/css/style.css.map rename to internal/web/frontend/assets/css/style.css.map diff --git a/web/assets/css/style.scss b/internal/web/frontend/assets/css/style.scss similarity index 100% rename from web/assets/css/style.scss rename to internal/web/frontend/assets/css/style.scss diff --git a/web/assets/js/app.js b/internal/web/frontend/assets/js/app.js similarity index 100% rename from web/assets/js/app.js rename to internal/web/frontend/assets/js/app.js diff --git a/web/assets/js/modules/animation.js b/internal/web/frontend/assets/js/modules/animation.js similarity index 100% rename from web/assets/js/modules/animation.js rename to internal/web/frontend/assets/js/modules/animation.js diff --git a/web/assets/js/modules/api.js b/internal/web/frontend/assets/js/modules/api.js similarity index 100% rename from web/assets/js/modules/api.js rename to internal/web/frontend/assets/js/modules/api.js diff --git a/web/assets/js/modules/duration.js b/internal/web/frontend/assets/js/modules/duration.js similarity index 100% rename from web/assets/js/modules/duration.js rename to internal/web/frontend/assets/js/modules/duration.js diff --git a/web/assets/js/modules/encryption.js b/internal/web/frontend/assets/js/modules/encryption.js similarity index 100% rename from web/assets/js/modules/encryption.js rename to internal/web/frontend/assets/js/modules/encryption.js diff --git a/web/assets/js/modules/notifications.js b/internal/web/frontend/assets/js/modules/notifications.js similarity index 100% rename from web/assets/js/modules/notifications.js rename to internal/web/frontend/assets/js/modules/notifications.js diff --git a/web/assets/js/modules/spinner.js b/internal/web/frontend/assets/js/modules/spinner.js similarity index 100% rename from web/assets/js/modules/spinner.js rename to internal/web/frontend/assets/js/modules/spinner.js diff --git a/web/assets/js/modules/state.js b/internal/web/frontend/assets/js/modules/state.js similarity index 100% rename from web/assets/js/modules/state.js rename to internal/web/frontend/assets/js/modules/state.js diff --git a/web/assets/libs/aesjs/aes.min.js b/internal/web/frontend/assets/libs/aesjs/aes.min.js similarity index 100% rename from web/assets/libs/aesjs/aes.min.js rename to internal/web/frontend/assets/libs/aesjs/aes.min.js diff --git a/web/assets/libs/animatecss/animate.min.css b/internal/web/frontend/assets/libs/animatecss/animate.min.css similarity index 100% rename from web/assets/libs/animatecss/animate.min.css rename to internal/web/frontend/assets/libs/animatecss/animate.min.css diff --git a/web/assets/libs/highlightjs/highlight.min.js b/internal/web/frontend/assets/libs/highlightjs/highlight.min.js similarity index 100% rename from web/assets/libs/highlightjs/highlight.min.js rename to internal/web/frontend/assets/libs/highlightjs/highlight.min.js diff --git a/web/assets/libs/highlightjs/solarized-dark.min.css b/internal/web/frontend/assets/libs/highlightjs/solarized-dark.min.css similarity index 100% rename from web/assets/libs/highlightjs/solarized-dark.min.css rename to internal/web/frontend/assets/libs/highlightjs/solarized-dark.min.css diff --git a/web/index.html b/internal/web/frontend/index.html similarity index 100% rename from web/index.html rename to internal/web/frontend/index.html diff --git a/internal/web/frontend_server.go b/internal/web/frontend_server.go new file mode 100644 index 0000000..d8f9e9d --- /dev/null +++ b/internal/web/frontend_server.go @@ -0,0 +1,77 @@ +package web + +import ( + "embed" + "errors" + "io" + "io/fs" + "mime" + "net/http" + "path/filepath" + "strconv" + "strings" +) + +//go:embed frontend/* +var frontend embed.FS + +func frontendHandler(notFoundHandler http.HandlerFunc) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + path := strings.TrimSpace(strings.TrimSuffix(request.URL.Path, "/")) + + isFirstLevel := strings.Count(path, "/") <= 1 + + file, err := frontend.Open(filepath.Join("frontend", path)) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + if isFirstLevel { + serveIndexFile(writer, request) + } else { + notFoundHandler(writer, request) + } + return + } + writeErr(writer, err) + return + } + defer func() { + _ = file.Close() + }() + + fileInfo, err := file.Stat() + if err != nil { + writeErr(writer, err) + return + } + + if fileInfo.IsDir() { + if isFirstLevel { + serveIndexFile(writer, request) + } else { + notFoundHandler(writer, request) + } + return + } + + content, err := io.ReadAll(file) + if err != nil { + writeErr(writer, err) + return + } + + writer.Header().Set("Content-Type", mime.TypeByExtension(fileInfo.Name())) + writer.Header().Set("Content-Length", strconv.Itoa(len(content))) + _, _ = writer.Write(content) + } +} + +func serveIndexFile(writer http.ResponseWriter, _ *http.Request) { + indexFile, err := frontend.ReadFile("frontend/index.html") + if err != nil { + writeErr(writer, err) + return + } + writer.Header().Set("Content-Type", "text/html") + writer.Header().Set("Content-Length", strconv.Itoa(len(indexFile))) + _, _ = writer.Write(indexFile) +} diff --git a/internal/web/server.go b/internal/web/server.go index 9eb484b..fb64a84 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -39,7 +39,11 @@ type Server struct { func (server *Server) Start() error { router := chi.NewRouter() + // Serve the web frontend + router.Get("/*", frontendHandler(router.NotFoundHandler())) + // Register the paste API endpoints + router.Get("/api/*", router.NotFoundHandler()) router.With(server.v2MiddlewareInjectPaste).Get("/api/v2/pastes/{paste_id}", server.v2EndpointGetPaste) router.Post("/api/v2/pastes", server.v2EndpointCreatePaste) router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Patch("/api/v2/pastes/{paste_id}", server.v2EndpointModifyPaste) From 4ce806945de28e7fc3b2879050b56414b6474687 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Thu, 8 Jun 2023 19:24:32 +0200 Subject: [PATCH 08/25] implement raw paste handler --- internal/web/server.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/web/server.go b/internal/web/server.go index fb64a84..4ee2e49 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -2,6 +2,7 @@ package web import ( "github.com/go-chi/chi/v5" + "github.com/lus/pasty/internal/pastes" "github.com/lus/pasty/internal/storage" "net/http" ) @@ -39,9 +40,19 @@ type Server struct { func (server *Server) Start() error { router := chi.NewRouter() - // Serve the web frontend + // Register the web frontend handler router.Get("/*", frontendHandler(router.NotFoundHandler())) + // Register the raw paste handler + router.With(server.v2MiddlewareInjectPaste).Get("/{paste_id}/raw", func(writer http.ResponseWriter, request *http.Request) { + paste, ok := request.Context().Value("paste").(*pastes.Paste) + if !ok { + writeString(writer, http.StatusInternalServerError, "missing paste object") + return + } + _, _ = writer.Write([]byte(paste.Content)) + }) + // Register the paste API endpoints router.Get("/api/*", router.NotFoundHandler()) router.With(server.v2MiddlewareInjectPaste).Get("/api/v2/pastes/{paste_id}", server.v2EndpointGetPaste) From a24be8b2ffe8fb89afc05949b9fd544a634aac16 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Thu, 8 Jun 2023 19:27:27 +0200 Subject: [PATCH 09/25] undo accidental string replacements --- .../web/frontend/assets/js/modules/api.js | 2 +- .../web/frontend/assets/js/modules/state.js | 46 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/internal/web/frontend/assets/js/modules/api.js b/internal/web/frontend/assets/js/modules/api.js index d4c84aa..6140672 100644 --- a/internal/web/frontend/assets/js/modules/api.js +++ b/internal/web/frontend/assets/js/modules/api.js @@ -1,4 +1,4 @@ -const API_BASE_URL = location.protocol + "//" + location.host + "/web/v2"; +const API_BASE_URL = location.protocol + "//" + location.host + "/api/v2"; export async function getAPIInformation() { return fetch(API_BASE_URL + "/info"); diff --git a/internal/web/frontend/assets/js/modules/state.js b/internal/web/frontend/assets/js/modules/state.js index 557f97b..b50ceeb 100644 --- a/internal/web/frontend/assets/js/modules/state.js +++ b/internal/web/frontend/assets/js/modules/state.js @@ -64,20 +64,20 @@ export async function initialize() { } if (location.pathname !== "/") { - // Extract the pastes data (ID and language) + // Extract the paste data (ID and language) const split = location.pathname.replace("/", "").split("."); const pasteID = split[0]; const language = split[1]; - // Try to retrieve the pastes data from the API + // Try to retrieve the paste data from the API const response = await API.getPaste(pasteID); if (!response.ok) { - Notifications.error("Could not load pastes: " + await response.text() + ""); + Notifications.error("Could not load paste: " + await response.text() + ""); setTimeout(() => location.replace(location.protocol + "//" + location.host), 3000); return; } - // Set the persistent pastes data + // Set the persistent paste data PASTE_ID = pasteID; LANGUAGE = language; @@ -95,7 +95,7 @@ export async function initialize() { ENCRYPTION_IV = json.metadata.pf_encryption.iv; } catch (error) { console.log(error); - Notifications.error("Could not decrypt pastes; make sure the decryption key is correct."); + Notifications.error("Could not decrypt paste; make sure the decryption key is correct."); setTimeout(() => location.replace(location.protocol + "//" + location.host), 3000); return; } @@ -104,7 +104,7 @@ export async function initialize() { // Fill the code block with the just received data updateCode(); } else { - // Give the user the opportunity to pastes his code + // Give the user the opportunity to paste his code INPUT_ELEMENT.classList.remove("hidden"); INPUT_ELEMENT.focus(); LIFETIME_CONTAINER_ELEMENT.classList.remove("hidden"); @@ -138,7 +138,7 @@ async function loadAPIInformation() { // Display the API version document.getElementById("version").innerText = API_INFORMATION.version; - // Display the pastes lifetime + // Display the paste lifetime document.getElementById("lifetime").innerText = Duration.format(API_INFORMATION.pasteLifetime); } @@ -215,7 +215,7 @@ function toggleEditMode() { function setupKeybinds() { window.addEventListener("keydown", (event) => { // All keybinds in the default button set include the CTRL key - if ((EDIT_MODE && !event.ctrlKey && event.code !== "Escape") || (!EDIT_MODE && !event.ctrlKey)) { + if ((EDIT_MODE && !event.ctrlKey && !event.metaKey && event.code !== "Escape") || (!EDIT_MODE && !event.ctrlKey && !event.metaKey)) { return; } @@ -289,7 +289,7 @@ function setupButtonFunctionality() { return; } - // Encrypt the pastes if needed + // Encrypt the paste if needed let value = INPUT_ELEMENT.value; let metadata; let key; @@ -305,20 +305,20 @@ function setupButtonFunctionality() { key = encrypted.key; } - // Try to create the pastes + // Try to create the paste const response = await API.createPaste(value, metadata); if (!response.ok) { - Notifications.error("Error while creating pastes: " + await response.text() + ""); + Notifications.error("Error while creating paste: " + await response.text() + ""); return; } const data = await response.json(); // Display the modification token if provided if (data.modificationToken) { - prompt("The modification token for your pastes is:", data.modificationToken); + prompt("The modification token for your paste is:", data.modificationToken); } - // Redirect the user to his newly created pastes + // Redirect the user to his newly created paste location.replace(location.protocol + "//" + location.host + "/" + data.id + (key ? "#" + key : "")); }); }); @@ -333,10 +333,10 @@ function setupButtonFunctionality() { return; } - // Try to delete the pastes + // Try to delete the paste const response = await API.deletePaste(PASTE_ID, modificationToken); if (!response.ok) { - Notifications.error("Error while deleting pastes: " + await response.text() + ""); + Notifications.error("Error while deleting paste: " + await response.text() + ""); return; } @@ -369,17 +369,17 @@ function setupButtonFunctionality() { return; } - // Re-encrypt the pastes data if needed + // Re-encrypt the paste data if needed let value = INPUT_ELEMENT.value; if (ENCRYPTION_KEY && ENCRYPTION_IV) { const encrypted = await Encryption.encrypt(await Encryption.encryptionDataFromHex(ENCRYPTION_KEY, ENCRYPTION_IV), value); value = encrypted.result; } - // Try to edit the pastes + // Try to edit the paste const response = await API.editPaste(PASTE_ID, modificationToken, value); if (!response.ok) { - Notifications.error("Error while editing pastes: " + await response.text() + ""); + Notifications.error("Error while editing paste: " + await response.text() + ""); return; } @@ -387,13 +387,13 @@ function setupButtonFunctionality() { CODE = INPUT_ELEMENT.value; updateCode(); toggleEditMode(); - Notifications.success("Successfully edited pastes."); + Notifications.success("Successfully edited paste."); }); BUTTON_TOGGLE_ENCRYPTION_ELEMENT.addEventListener("click", () => { const active = BUTTON_TOGGLE_ENCRYPTION_ELEMENT.classList.toggle("active"); localStorage.setItem("encryption", active); - Notifications.success((active ? "Enabled" : "Disabled") + " automatic pastes encryption."); + Notifications.success((active ? "Enabled" : "Disabled") + " automatic paste encryption."); }); BUTTON_REPORT_ELEMENT.addEventListener("click", async () => { @@ -403,17 +403,17 @@ function setupButtonFunctionality() { return; } - // Try to report the pastes + // Try to report the paste const response = await API.reportPaste(PASTE_ID, reason); if (!response.ok) { - Notifications.error("Error while reporting pastes: " + await response.text() + ""); + Notifications.error("Error while reporting paste: " + await response.text() + ""); return; } // Show the response message const data = await response.json(); if (!data.success) { - Notifications.error("Error while reporting pastes: " + data.message + ""); + Notifications.error("Error while reporting paste: " + data.message + ""); return; } Notifications.success(data.message); From dc16506932f35b6ed34ba7ef748f1978c8f2358a Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Thu, 8 Jun 2023 19:44:32 +0200 Subject: [PATCH 10/25] integrate the web server into the startup logic --- .gitignore | 5 +---- cmd/pasty/main.go | 29 +++++++++++++++++++++++++++++ internal/storage/postgres/driver.go | 1 - internal/web/server.go | 18 +++++++++++++++++- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 776188e..497aa10 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - # Created by https://www.toptal.com/developers/gitignore/api/jetbrains+all,go # Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains+all,go @@ -114,6 +113,4 @@ modules.xml # End of https://www.toptal.com/developers/gitignore/api/jetbrains+all,go -web/*.gz -data/ -.env \ No newline at end of file +.env diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index 4e8eff9..bbb3a9e 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -2,12 +2,15 @@ package main import ( "context" + "errors" "github.com/lus/pasty/internal/config" "github.com/lus/pasty/internal/meta" "github.com/lus/pasty/internal/storage" "github.com/lus/pasty/internal/storage/postgres" + "github.com/lus/pasty/internal/web" "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "net/http" "os" "os/signal" "strings" @@ -65,6 +68,32 @@ func main() { } }() + // Start the web server + log.Info().Str("address", cfg.WebAddress).Msg("Starting the web server...") + webServer := &web.Server{ + Address: cfg.WebAddress, + Storage: driver, + HastebinSupport: cfg.HastebinSupport, + PasteIDLength: cfg.IDLength, + PasteIDCharset: cfg.IDCharacters, + PasteLengthCap: cfg.LengthCap, + ModificationTokensEnabled: cfg.ModificationTokens, + ModificationTokenLength: cfg.ModificationTokenLength, + ModificationTokenCharset: cfg.ModificationTokenCharacters, + AdminTokens: []string{cfg.ModificationTokenMaster}, + } + go func() { + if err := webServer.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Fatal().Err(err).Msg("Could not start the web server.") + } + }() + defer func() { + log.Info().Msg("Shutting down the web server...") + if err := webServer.Shutdown(context.Background()); err != nil { + log.Err(err).Msg("Could not shut down the web server.") + } + }() + // Wait for an interrupt signal log.Info().Msg("The application has been started. Use Ctrl+C to shut it down.") shutdownChan := make(chan os.Signal, 1) diff --git a/internal/storage/postgres/driver.go b/internal/storage/postgres/driver.go index e401044..9ab10fc 100644 --- a/internal/storage/postgres/driver.go +++ b/internal/storage/postgres/driver.go @@ -54,7 +54,6 @@ func (driver *Driver) Initialize(ctx context.Context) error { pool.Close() return err } - log.Info().Msg("Successfully performed PostgreSQL database migrations.") driver.connPool = pool driver.pastes = &pasteRepository{ diff --git a/internal/web/server.go b/internal/web/server.go index 4ee2e49..21684b9 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -1,6 +1,7 @@ package web import ( + "context" "github.com/go-chi/chi/v5" "github.com/lus/pasty/internal/pastes" "github.com/lus/pasty/internal/storage" @@ -35,6 +36,8 @@ type Server struct { // The administration tokens. AdminTokens []string + + httpServer *http.Server } func (server *Server) Start() error { @@ -60,5 +63,18 @@ func (server *Server) Start() error { router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Patch("/api/v2/pastes/{paste_id}", server.v2EndpointModifyPaste) router.Delete("/api/v2/pastes/{paste_id}", server.v2EndpointDeletePaste) - return http.ListenAndServe(server.Address, router) + // Start the HTTP server + server.httpServer = &http.Server{ + Addr: server.Address, + Handler: router, + } + return server.httpServer.ListenAndServe() +} + +func (server *Server) Shutdown(ctx context.Context) error { + if err := server.httpServer.Shutdown(ctx); err != nil { + return err + } + server.httpServer = nil + return nil } From 6260f20fc47bb50e03fc3f1cd694c0e8b07fd0cb Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Thu, 8 Jun 2023 19:55:43 +0200 Subject: [PATCH 11/25] implement info endpoint --- cmd/pasty/main.go | 6 +++++- internal/meta/metadata.go | 1 + internal/web/frontend_server.go | 2 +- internal/web/server.go | 11 ++++++++++- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index bbb3a9e..ddb0455 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -70,6 +70,10 @@ func main() { // Start the web server log.Info().Str("address", cfg.WebAddress).Msg("Starting the web server...") + var adminTokens []string + if cfg.ModificationTokenMaster != "" { + adminTokens = []string{cfg.ModificationTokenMaster} + } webServer := &web.Server{ Address: cfg.WebAddress, Storage: driver, @@ -80,7 +84,7 @@ func main() { ModificationTokensEnabled: cfg.ModificationTokens, ModificationTokenLength: cfg.ModificationTokenLength, ModificationTokenCharset: cfg.ModificationTokenCharacters, - AdminTokens: []string{cfg.ModificationTokenMaster}, + AdminTokens: adminTokens, } go func() { if err := webServer.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) { diff --git a/internal/meta/metadata.go b/internal/meta/metadata.go index 59cbcf1..8085460 100644 --- a/internal/meta/metadata.go +++ b/internal/meta/metadata.go @@ -6,6 +6,7 @@ const devEnvironmentName = "dev" var ( Environment = devEnvironmentName + Version = "dev" ) func IsProdEnvironment() bool { diff --git a/internal/web/frontend_server.go b/internal/web/frontend_server.go index d8f9e9d..469b30a 100644 --- a/internal/web/frontend_server.go +++ b/internal/web/frontend_server.go @@ -59,7 +59,7 @@ func frontendHandler(notFoundHandler http.HandlerFunc) http.HandlerFunc { return } - writer.Header().Set("Content-Type", mime.TypeByExtension(fileInfo.Name())) + writer.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(fileInfo.Name()))) writer.Header().Set("Content-Length", strconv.Itoa(len(content))) _, _ = writer.Write(content) } diff --git a/internal/web/server.go b/internal/web/server.go index 21684b9..040965d 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -3,6 +3,7 @@ package web import ( "context" "github.com/go-chi/chi/v5" + "github.com/lus/pasty/internal/meta" "github.com/lus/pasty/internal/pastes" "github.com/lus/pasty/internal/storage" "net/http" @@ -61,7 +62,15 @@ func (server *Server) Start() error { router.With(server.v2MiddlewareInjectPaste).Get("/api/v2/pastes/{paste_id}", server.v2EndpointGetPaste) router.Post("/api/v2/pastes", server.v2EndpointCreatePaste) router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Patch("/api/v2/pastes/{paste_id}", server.v2EndpointModifyPaste) - router.Delete("/api/v2/pastes/{paste_id}", server.v2EndpointDeletePaste) + router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Delete("/api/v2/pastes/{paste_id}", server.v2EndpointDeletePaste) + router.Get("/api/v2/info", func(writer http.ResponseWriter, request *http.Request) { + writeJSONOrErr(writer, http.StatusOK, map[string]any{ + "version": meta.Version, + "modificationTokens": server.ModificationTokensEnabled, + "reports": false, // TODO: Return report state + "pasteLifetime": -1, // TODO: Return paste lifetime + }) + }) // Start the HTTP server server.httpServer = &http.Server{ From b9a6a81821ccf14c1984d8b20ff4a49bd483eff0 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Tue, 13 Jun 2023 01:22:05 +0200 Subject: [PATCH 12/25] implement SQLite storage driver --- cmd/pasty/main.go | 4 + go.mod | 16 +++ go.sum | 38 ++++++++ internal/config/config.go | 5 + internal/storage/postgres/driver.go | 1 + .../000001_initialize_schema.down.sql | 2 +- .../000001_initialize_schema.up.sql | 2 +- .../000002_rename_deletion_token.down.sql | 2 +- .../000002_rename_deletion_token.up.sql | 2 +- .../migrations/000003_add_metadata.down.sql | 2 +- .../migrations/000003_add_metadata.up.sql | 2 +- ..._remove_paste_specific_autodelete.down.sql | 2 +- ...04_remove_paste_specific_autodelete.up.sql | 2 +- internal/storage/sqlite/driver.go | 87 +++++++++++++++++ .../000001_initialize_schema.down.sql | 1 + .../000001_initialize_schema.up.sql | 8 ++ internal/storage/sqlite/paste_repository.go | 97 +++++++++++++++++++ 17 files changed, 265 insertions(+), 8 deletions(-) create mode 100644 internal/storage/sqlite/driver.go create mode 100644 internal/storage/sqlite/migrations/000001_initialize_schema.down.sql create mode 100644 internal/storage/sqlite/migrations/000001_initialize_schema.up.sql create mode 100644 internal/storage/sqlite/paste_repository.go diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index ddb0455..070fa49 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -7,6 +7,7 @@ import ( "github.com/lus/pasty/internal/meta" "github.com/lus/pasty/internal/storage" "github.com/lus/pasty/internal/storage/postgres" + "github.com/lus/pasty/internal/storage/sqlite" "github.com/lus/pasty/internal/web" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -50,6 +51,9 @@ func main() { case "postgres": driver = postgres.New(cfg.Postgres.DSN) break + case "sqlite": + driver = sqlite.New(cfg.SQLite.File) + break default: log.Fatal().Str("driver_name", cfg.StorageDriver).Msg("An invalid storage driver name was given.") return diff --git a/go.mod b/go.mod index 6f24254..1688941 100644 --- a/go.mod +++ b/go.mod @@ -10,20 +10,36 @@ require ( github.com/joho/godotenv v1.5.1 github.com/kelseyhightower/envconfig v1.4.0 github.com/rs/zerolog v1.29.1 + modernc.org/sqlite v1.23.1 ) require ( + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/crypto v0.9.0 // indirect + golang.org/x/mod v0.10.0 // indirect golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.9.1 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect ) diff --git a/go.sum b/go.sum index c589896..2a18386 100644 --- a/go.sum +++ b/go.sum @@ -10,12 +10,18 @@ github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m3 github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +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/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/golang-migrate/migrate/v4 v4.16.1 h1:O+0C55RbMN66pWm5MjO6mw0px6usGpY0+bkSGW9zCo0= github.com/golang-migrate/migrate/v4 v4.16.1/go.mod h1:qXiwa/3Zeqaltm1MxOCZDYysW/F6folYiBgBG03l9hc= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -31,6 +37,8 @@ github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -42,6 +50,7 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -50,6 +59,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= @@ -69,6 +81,7 @@ golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -108,7 +121,32 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= +modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= diff --git a/internal/config/config.go b/internal/config/config.go index 0864617..a1e9d52 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -22,6 +22,7 @@ type Config struct { AutoDelete *AutoDeleteConfig `split_words:"true"` Reports *ReportConfig Postgres *PostgresConfig + SQLite *SQLiteConfig } type AutoDeleteConfig struct { @@ -40,6 +41,10 @@ type PostgresConfig struct { DSN string `default:"postgres://pasty:pasty@localhost/pasty"` } +type SQLiteConfig struct { + File string `default:":memory:"` +} + func Load() (*Config, error) { _ = godotenv.Overload() cfg := new(Config) diff --git a/internal/storage/postgres/driver.go b/internal/storage/postgres/driver.go index 9ab10fc..440ceae 100644 --- a/internal/storage/postgres/driver.go +++ b/internal/storage/postgres/driver.go @@ -65,6 +65,7 @@ func (driver *Driver) Initialize(ctx context.Context) error { func (driver *Driver) Close() error { driver.pastes = nil driver.connPool.Close() + driver.connPool = nil return nil } diff --git a/internal/storage/postgres/migrations/000001_initialize_schema.down.sql b/internal/storage/postgres/migrations/000001_initialize_schema.down.sql index 4580395..a824aaf 100644 --- a/internal/storage/postgres/migrations/000001_initialize_schema.down.sql +++ b/internal/storage/postgres/migrations/000001_initialize_schema.down.sql @@ -1,5 +1,5 @@ begin; -drop table if exists "pastes"; +drop table "pastes"; commit; \ No newline at end of file diff --git a/internal/storage/postgres/migrations/000001_initialize_schema.up.sql b/internal/storage/postgres/migrations/000001_initialize_schema.up.sql index a55c395..02d1961 100644 --- a/internal/storage/postgres/migrations/000001_initialize_schema.up.sql +++ b/internal/storage/postgres/migrations/000001_initialize_schema.up.sql @@ -1,6 +1,6 @@ begin; -create table if not exists "pastes" ( +create table "pastes" ( "id" text not null, "content" text not null, "deletionToken" text not null, diff --git a/internal/storage/postgres/migrations/000002_rename_deletion_token.down.sql b/internal/storage/postgres/migrations/000002_rename_deletion_token.down.sql index 357696e..d6407de 100644 --- a/internal/storage/postgres/migrations/000002_rename_deletion_token.down.sql +++ b/internal/storage/postgres/migrations/000002_rename_deletion_token.down.sql @@ -1,5 +1,5 @@ begin; -alter table if exists "pastes" rename column "modificationToken" to "deletionToken"; +alter table "pastes" rename column "modificationToken" to "deletionToken"; commit; \ No newline at end of file diff --git a/internal/storage/postgres/migrations/000002_rename_deletion_token.up.sql b/internal/storage/postgres/migrations/000002_rename_deletion_token.up.sql index f420dd4..bcc09aa 100644 --- a/internal/storage/postgres/migrations/000002_rename_deletion_token.up.sql +++ b/internal/storage/postgres/migrations/000002_rename_deletion_token.up.sql @@ -1,5 +1,5 @@ begin; -alter table if exists "pastes" rename column "deletionToken" to "modificationToken"; +alter table "pastes" rename column "deletionToken" to "modificationToken"; commit; \ No newline at end of file diff --git a/internal/storage/postgres/migrations/000003_add_metadata.down.sql b/internal/storage/postgres/migrations/000003_add_metadata.down.sql index a9915b0..44a5110 100644 --- a/internal/storage/postgres/migrations/000003_add_metadata.down.sql +++ b/internal/storage/postgres/migrations/000003_add_metadata.down.sql @@ -1,5 +1,5 @@ begin; -alter table if exists "pastes" drop column "metadata"; +alter table "pastes" drop column "metadata"; commit; \ No newline at end of file diff --git a/internal/storage/postgres/migrations/000003_add_metadata.up.sql b/internal/storage/postgres/migrations/000003_add_metadata.up.sql index 92d2341..231d0a3 100644 --- a/internal/storage/postgres/migrations/000003_add_metadata.up.sql +++ b/internal/storage/postgres/migrations/000003_add_metadata.up.sql @@ -1,5 +1,5 @@ begin; -alter table if exists "pastes" add column "metadata" jsonb; +alter table "pastes" add column "metadata" jsonb; commit; \ No newline at end of file diff --git a/internal/storage/postgres/migrations/000004_remove_paste_specific_autodelete.down.sql b/internal/storage/postgres/migrations/000004_remove_paste_specific_autodelete.down.sql index b15acea..76fb1d2 100644 --- a/internal/storage/postgres/migrations/000004_remove_paste_specific_autodelete.down.sql +++ b/internal/storage/postgres/migrations/000004_remove_paste_specific_autodelete.down.sql @@ -1,5 +1,5 @@ begin; -alter table if exists "pastes" add column "autoDelete" boolean; +alter table "pastes" add column "autoDelete" boolean; commit; \ No newline at end of file diff --git a/internal/storage/postgres/migrations/000004_remove_paste_specific_autodelete.up.sql b/internal/storage/postgres/migrations/000004_remove_paste_specific_autodelete.up.sql index e637ef2..6d490de 100644 --- a/internal/storage/postgres/migrations/000004_remove_paste_specific_autodelete.up.sql +++ b/internal/storage/postgres/migrations/000004_remove_paste_specific_autodelete.up.sql @@ -1,5 +1,5 @@ begin; -alter table if exists "pastes" drop column "autoDelete"; +alter table "pastes" drop column "autoDelete"; commit; \ No newline at end of file diff --git a/internal/storage/sqlite/driver.go b/internal/storage/sqlite/driver.go new file mode 100644 index 0000000..94c77e6 --- /dev/null +++ b/internal/storage/sqlite/driver.go @@ -0,0 +1,87 @@ +package sqlite + +import ( + "context" + "database/sql" + "embed" + "errors" + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/sqlite" + "github.com/golang-migrate/migrate/v4/source/iofs" + "github.com/lus/pasty/internal/pastes" + "github.com/lus/pasty/internal/storage" + "github.com/rs/zerolog/log" + _ "modernc.org/sqlite" +) + +//go:embed migrations/*.sql +var migrations embed.FS + +type Driver struct { + filePath string + connPool *sql.DB + pastes *pasteRepository +} + +var _ storage.Driver = (*Driver)(nil) + +func New(filePath string) *Driver { + return &Driver{ + filePath: filePath, + } +} + +func (driver *Driver) Initialize(ctx context.Context) error { + db, err := sql.Open("sqlite", driver.filePath) + if err != nil { + return err + } + if err := db.PingContext(ctx); err != nil { + return err + } + + log.Info().Msg("Performing SQLite database migrations...") + source, err := iofs.New(migrations, "migrations") + if err != nil { + _ = db.Close() + return err + } + defer func() { + _ = source.Close() + }() + migrateDriver, err := sqlite.WithInstance(db, &sqlite.Config{ + MigrationsTable: sqlite.DefaultMigrationsTable, + DatabaseName: driver.filePath, + NoTxWrap: false, + }) + if err != nil { + _ = db.Close() + return err + } + migrator, err := migrate.NewWithInstance("iofs", source, "sqlite", migrateDriver) + if err != nil { + _ = db.Close() + return err + } + if err := migrator.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) { + _ = db.Close() + return err + } + + driver.connPool = db + driver.pastes = &pasteRepository{ + connPool: db, + } + return nil +} + +func (driver *Driver) Close() error { + driver.pastes = nil + _ = driver.connPool.Close() + driver.connPool = nil + return nil +} + +func (driver *Driver) Pastes() pastes.Repository { + return driver.pastes +} diff --git a/internal/storage/sqlite/migrations/000001_initialize_schema.down.sql b/internal/storage/sqlite/migrations/000001_initialize_schema.down.sql new file mode 100644 index 0000000..34141bc --- /dev/null +++ b/internal/storage/sqlite/migrations/000001_initialize_schema.down.sql @@ -0,0 +1 @@ +DROP TABLE "pastes"; \ No newline at end of file diff --git a/internal/storage/sqlite/migrations/000001_initialize_schema.up.sql b/internal/storage/sqlite/migrations/000001_initialize_schema.up.sql new file mode 100644 index 0000000..70cded0 --- /dev/null +++ b/internal/storage/sqlite/migrations/000001_initialize_schema.up.sql @@ -0,0 +1,8 @@ +CREATE TABLE "pastes" ( + "id" TEXT NOT NULL, + "content" TEXT NOT NULL, + "modification_token" TEXT NOT NULL, + "created" BIGINT NOT NULL, + "metadata" TEXT NOT NULL, + PRIMARY KEY ("id") +); \ No newline at end of file diff --git a/internal/storage/sqlite/paste_repository.go b/internal/storage/sqlite/paste_repository.go new file mode 100644 index 0000000..4c45704 --- /dev/null +++ b/internal/storage/sqlite/paste_repository.go @@ -0,0 +1,97 @@ +package sqlite + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "github.com/lus/pasty/internal/pastes" + "time" +) + +type pasteRepository struct { + connPool *sql.DB +} + +var _ pastes.Repository = (*pasteRepository)(nil) + +func (repo *pasteRepository) ListIDs(ctx context.Context) ([]string, error) { + rows, err := repo.connPool.QueryContext(ctx, "SELECT id FROM pastes") + if err != nil { + return nil, err + } + defer func() { + _ = rows.Close() + }() + + ids := make([]string, 0) + for rows.Next() { + var id string + if err := rows.Scan(&id); err != nil { + return nil, err + } + ids = append(ids, id) + } + + return ids, nil +} + +func (repo *pasteRepository) FindByID(ctx context.Context, id string) (*pastes.Paste, error) { + row := repo.connPool.QueryRowContext(ctx, "SELECT * FROM pastes WHERE id = ?", id) + + obj := new(pastes.Paste) + + var rawMetadata string + if err := row.Scan(&obj.ID, &obj.Content, &obj.ModificationToken, &obj.Created, &rawMetadata); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return nil, err + } + + var metadata map[string]any + if err := json.Unmarshal([]byte(rawMetadata), &metadata); err != nil { + return nil, err + } + obj.Metadata = metadata + + return obj, nil +} + +func (repo *pasteRepository) Upsert(ctx context.Context, paste *pastes.Paste) error { + const query = ` + INSERT INTO pastes + VALUES (?, ?, ?, ?, ?) + ON CONFLICT (id) DO UPDATE + SET content = excluded.content, + modification_token = excluded.modification_token, + metadata = excluded.metadata + ` + + rawMetadata, err := json.Marshal(paste.Metadata) + if err != nil { + return err + } + + _, err = repo.connPool.ExecContext(ctx, query, paste.ID, paste.Content, paste.ModificationToken, paste.Created, rawMetadata) + return err +} + +func (repo *pasteRepository) DeleteByID(ctx context.Context, id string) error { + _, err := repo.connPool.ExecContext(ctx, "DELETE FROM pastes WHERE id = ?", id) + return err +} + +func (repo *pasteRepository) DeleteOlderThan(ctx context.Context, age time.Duration) (int, error) { + result, err := repo.connPool.ExecContext(ctx, "DELETE FROM pastes WHERE created < ?", time.Now().Add(-age).Unix()) + if err != nil { + return 0, err + } + + affected, err := result.RowsAffected() + if err != nil { + return -1, nil + } + + return int(affected), nil +} From bdac813e5981fde681e0c2b47c8055690901f392 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 15:16:22 +0200 Subject: [PATCH 13/25] implement paste reports --- cmd/pasty/main.go | 15 +++++--- internal/reports/client.go | 60 +++++++++++++++++++++++++++++ internal/web/server.go | 12 +++++- internal/web/v2_end_report_paste.go | 48 +++++++++++++++++++++++ 4 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 internal/reports/client.go create mode 100644 internal/web/v2_end_report_paste.go diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index 070fa49..d7ef5ec 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/lus/pasty/internal/config" "github.com/lus/pasty/internal/meta" + "github.com/lus/pasty/internal/reports" "github.com/lus/pasty/internal/storage" "github.com/lus/pasty/internal/storage/postgres" "github.com/lus/pasty/internal/storage/sqlite" @@ -74,10 +75,6 @@ func main() { // Start the web server log.Info().Str("address", cfg.WebAddress).Msg("Starting the web server...") - var adminTokens []string - if cfg.ModificationTokenMaster != "" { - adminTokens = []string{cfg.ModificationTokenMaster} - } webServer := &web.Server{ Address: cfg.WebAddress, Storage: driver, @@ -88,7 +85,15 @@ func main() { ModificationTokensEnabled: cfg.ModificationTokens, ModificationTokenLength: cfg.ModificationTokenLength, ModificationTokenCharset: cfg.ModificationTokenCharacters, - AdminTokens: adminTokens, + } + if cfg.Reports.Enabled { + webServer.ReportClient = &reports.Client{ + WebhookURL: cfg.Reports.WebhookURL, + WebhookToken: cfg.Reports.WebhookToken, + } + } + if cfg.ModificationTokenMaster != "" { + webServer.AdminTokens = []string{cfg.ModificationTokenMaster} } go func() { if err := webServer.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) { diff --git a/internal/reports/client.go b/internal/reports/client.go new file mode 100644 index 0000000..10982af --- /dev/null +++ b/internal/reports/client.go @@ -0,0 +1,60 @@ +package reports + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + +type Report struct { + Paste string `json:"paste"` + Reason string `json:"reason"` +} + +type Response struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +type Client struct { + WebhookURL string + WebhookToken string +} + +func (client *Client) Send(report *Report) (*Response, error) { + data, err := json.Marshal(report) + if err != nil { + return nil, err + } + + request, err := http.NewRequest(http.MethodPost, client.WebhookURL, bytes.NewReader(data)) + if err != nil { + return nil, err + } + + if client.WebhookToken != "" { + request.Header.Set("Authorization", "Bearer "+client.WebhookToken) + } + + response, err := http.DefaultClient.Do(request) + if err != nil { + return nil, err + } + + body, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + + if response.StatusCode < 200 || response.StatusCode > 299 { + return nil, fmt.Errorf("the report webhook responded with an unexpected error: %d (%s)", response.StatusCode, string(body)) + } + + reportResponse := new(Response) + if err := json.Unmarshal(body, &reportResponse); err != nil { + return nil, err + } + return reportResponse, nil +} diff --git a/internal/web/server.go b/internal/web/server.go index 040965d..a95eeb2 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -5,6 +5,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/lus/pasty/internal/meta" "github.com/lus/pasty/internal/pastes" + "github.com/lus/pasty/internal/reports" "github.com/lus/pasty/internal/storage" "net/http" ) @@ -16,6 +17,10 @@ type Server struct { // The storage driver to use. Storage storage.Driver + // The report client to use to send reports. + // If this is set to nil, the report system will be considered deactivated. + ReportClient *reports.Client + // Whether the Hastebin support should be enabled. // If this is set to 'false', the Hastebin specific endpoints will not be registered. HastebinSupport bool @@ -63,12 +68,15 @@ func (server *Server) Start() error { router.Post("/api/v2/pastes", server.v2EndpointCreatePaste) router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Patch("/api/v2/pastes/{paste_id}", server.v2EndpointModifyPaste) router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Delete("/api/v2/pastes/{paste_id}", server.v2EndpointDeletePaste) + if server.ReportClient != nil { + router.With(server.v2MiddlewareInjectPaste).Post("/api/v2/pastes/{paste_id}/report", server.v2EndpointReportPaste) + } router.Get("/api/v2/info", func(writer http.ResponseWriter, request *http.Request) { writeJSONOrErr(writer, http.StatusOK, map[string]any{ "version": meta.Version, "modificationTokens": server.ModificationTokensEnabled, - "reports": false, // TODO: Return report state - "pasteLifetime": -1, // TODO: Return paste lifetime + "reports": server.ReportClient != nil, + "pasteLifetime": -1, // TODO: Return paste lifetime }) }) diff --git a/internal/web/v2_end_report_paste.go b/internal/web/v2_end_report_paste.go new file mode 100644 index 0000000..186c88e --- /dev/null +++ b/internal/web/v2_end_report_paste.go @@ -0,0 +1,48 @@ +package web + +import ( + "encoding/json" + "github.com/lus/pasty/internal/pastes" + "github.com/lus/pasty/internal/reports" + "io" + "net/http" +) + +type v2EndpointReportPastePayload struct { + Reason string `json:"reason"` +} + +func (server *Server) v2EndpointReportPaste(writer http.ResponseWriter, request *http.Request) { + paste, ok := request.Context().Value("paste").(*pastes.Paste) + if !ok { + writeString(writer, http.StatusInternalServerError, "missing paste object") + return + } + + // Read, parse and validate the request payload + body, err := io.ReadAll(request.Body) + if err != nil { + writeErr(writer, err) + return + } + payload := new(v2EndpointReportPastePayload) + if err := json.Unmarshal(body, payload); err != nil { + writeErr(writer, err) + return + } + if payload.Reason == "" { + writeString(writer, http.StatusBadRequest, "missing report reason") + return + } + + report := &reports.Report{ + Paste: paste.ID, + Reason: payload.Reason, + } + response, err := server.ReportClient.Send(report) + if err != nil { + writeErr(writer, err) + return + } + writeJSONOrErr(writer, http.StatusOK, response) +} From 127af0a90772253f70d9b0ca95e3f38b37dda166 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 15:18:15 +0200 Subject: [PATCH 14/25] print Hastebin support removal notice --- cmd/pasty/main.go | 11 +++++++++++ internal/config/config.go | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index d7ef5ec..03c87b4 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -46,6 +46,10 @@ func main() { zerolog.SetGlobalLevel(level) } + // Run the configuration compatibility layer + // TODO: Remove this at a later state + configCompatibilityLayer(cfg) + // Determine the correct storage driver to use var driver storage.Driver switch strings.TrimSpace(strings.ToLower(cfg.StorageDriver)) { @@ -113,3 +117,10 @@ func main() { signal.Notify(shutdownChan, os.Interrupt) <-shutdownChan } + +func configCompatibilityLayer(cfg *config.Config) { + // Print a notice if the (now removed) Hastebin support has been enabled + if cfg.HastebinSupport { + log.Warn().Msg("You have enabled the legacy 'Hastebin support' feature. This feature has been removed.") + } +} diff --git a/internal/config/config.go b/internal/config/config.go index a1e9d52..1ce7b19 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,7 +10,7 @@ type Config struct { LogLevel string `default:"info" split_words:"true"` WebAddress string `default:":8080" split_words:"true"` StorageDriver string `default:"sqlite" split_words:"true"` - HastebinSupport bool `default:"false" split_words:"true"` + HastebinSupport bool `default:"false" split_words:"true"` // TODO: Legacy IDLength int `default:"6" split_words:"true"` IDCharacters string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` ModificationTokens bool `default:"true" split_words:"true"` From c414fd7c5929e73a410818efbecce801c57c3cd2 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 17:00:44 +0200 Subject: [PATCH 15/25] implement request logging --- internal/web/frontend_server.go | 10 ++-- internal/web/response_writer.go | 8 +-- internal/web/server.go | 6 ++- internal/web/v2_end_create_paste.go | 12 ++--- internal/web/v2_end_delete_paste.go | 3 +- internal/web/v2_end_get_paste.go | 5 +- internal/web/v2_end_modify_paste.go | 6 +-- internal/web/v2_end_report_paste.go | 8 +-- internal/web/v2_mid_inject_paste.go | 5 +- pkg/chizerolog/logger.go | 78 +++++++++++++++++++++++++++++ pkg/chizerolog/recoverer.go | 36 +++++++++++++ 11 files changed, 148 insertions(+), 29 deletions(-) create mode 100644 pkg/chizerolog/logger.go create mode 100644 pkg/chizerolog/recoverer.go diff --git a/internal/web/frontend_server.go b/internal/web/frontend_server.go index 469b30a..d108f50 100644 --- a/internal/web/frontend_server.go +++ b/internal/web/frontend_server.go @@ -31,7 +31,7 @@ func frontendHandler(notFoundHandler http.HandlerFunc) http.HandlerFunc { } return } - writeErr(writer, err) + writeErr(request, writer, err) return } defer func() { @@ -40,7 +40,7 @@ func frontendHandler(notFoundHandler http.HandlerFunc) http.HandlerFunc { fileInfo, err := file.Stat() if err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } @@ -55,7 +55,7 @@ func frontendHandler(notFoundHandler http.HandlerFunc) http.HandlerFunc { content, err := io.ReadAll(file) if err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } @@ -65,10 +65,10 @@ func frontendHandler(notFoundHandler http.HandlerFunc) http.HandlerFunc { } } -func serveIndexFile(writer http.ResponseWriter, _ *http.Request) { +func serveIndexFile(writer http.ResponseWriter, request *http.Request) { indexFile, err := frontend.ReadFile("frontend/index.html") if err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } writer.Header().Set("Content-Type", "text/html") diff --git a/internal/web/response_writer.go b/internal/web/response_writer.go index 9f3a947..accaee9 100644 --- a/internal/web/response_writer.go +++ b/internal/web/response_writer.go @@ -2,10 +2,12 @@ package web import ( "encoding/json" + "github.com/lus/pasty/pkg/chizerolog" "net/http" ) -func writeErr(writer http.ResponseWriter, err error) { +func writeErr(request *http.Request, writer http.ResponseWriter, err error) { + chizerolog.InjectError(request, err) writeString(writer, http.StatusInternalServerError, err.Error()) } @@ -26,8 +28,8 @@ func writeJSON(writer http.ResponseWriter, status int, value any) error { return nil } -func writeJSONOrErr(writer http.ResponseWriter, status int, value any) { +func writeJSONOrErr(request *http.Request, writer http.ResponseWriter, status int, value any) { if err := writeJSON(writer, status, value); err != nil { - writeErr(writer, err) + writeErr(request, writer, err) } } diff --git a/internal/web/server.go b/internal/web/server.go index a95eeb2..da3434b 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -7,6 +7,7 @@ import ( "github.com/lus/pasty/internal/pastes" "github.com/lus/pasty/internal/reports" "github.com/lus/pasty/internal/storage" + "github.com/lus/pasty/pkg/chizerolog" "net/http" ) @@ -49,6 +50,9 @@ type Server struct { func (server *Server) Start() error { router := chi.NewRouter() + router.Use(chizerolog.Logger) + router.Use(chizerolog.Recover) + // Register the web frontend handler router.Get("/*", frontendHandler(router.NotFoundHandler())) @@ -72,7 +76,7 @@ func (server *Server) Start() error { router.With(server.v2MiddlewareInjectPaste).Post("/api/v2/pastes/{paste_id}/report", server.v2EndpointReportPaste) } router.Get("/api/v2/info", func(writer http.ResponseWriter, request *http.Request) { - writeJSONOrErr(writer, http.StatusOK, map[string]any{ + writeJSONOrErr(request, writer, http.StatusOK, map[string]any{ "version": meta.Version, "modificationTokens": server.ModificationTokensEnabled, "reports": server.ReportClient != nil, diff --git a/internal/web/v2_end_create_paste.go b/internal/web/v2_end_create_paste.go index 4ac0cb4..e114043 100644 --- a/internal/web/v2_end_create_paste.go +++ b/internal/web/v2_end_create_paste.go @@ -18,12 +18,12 @@ func (server *Server) v2EndpointCreatePaste(writer http.ResponseWriter, request // Read, parse and validate the request payload body, err := io.ReadAll(request.Body) if err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } payload := new(v2EndpointCreatePastePayload) if err := json.Unmarshal(body, payload); err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } if payload.Content == "" { @@ -37,7 +37,7 @@ func (server *Server) v2EndpointCreatePaste(writer http.ResponseWriter, request id, err := pastes.GenerateID(request.Context(), server.Storage.Pastes(), server.PasteIDCharset, server.PasteIDLength) if err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } @@ -54,17 +54,17 @@ func (server *Server) v2EndpointCreatePaste(writer http.ResponseWriter, request paste.ModificationToken = modificationToken if err := paste.HashModificationToken(); err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } } if err := server.Storage.Pastes().Upsert(request.Context(), paste); err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } cpy := *paste cpy.ModificationToken = modificationToken - writeJSONOrErr(writer, http.StatusCreated, cpy) + writeJSONOrErr(request, writer, http.StatusCreated, cpy) } diff --git a/internal/web/v2_end_delete_paste.go b/internal/web/v2_end_delete_paste.go index f5e6c1b..09926c0 100644 --- a/internal/web/v2_end_delete_paste.go +++ b/internal/web/v2_end_delete_paste.go @@ -13,6 +13,7 @@ func (server *Server) v2EndpointDeletePaste(writer http.ResponseWriter, request } if err := server.Storage.Pastes().DeleteByID(request.Context(), paste.ID); err != nil { - writeErr(writer, err) + writeErr(request, writer, err) } + writer.WriteHeader(http.StatusOK) } diff --git a/internal/web/v2_end_get_paste.go b/internal/web/v2_end_get_paste.go index 5be338e..de18c82 100644 --- a/internal/web/v2_end_get_paste.go +++ b/internal/web/v2_end_get_paste.go @@ -1,6 +1,7 @@ package web import ( + "errors" "github.com/lus/pasty/internal/pastes" "net/http" ) @@ -8,11 +9,11 @@ import ( func (server *Server) v2EndpointGetPaste(writer http.ResponseWriter, request *http.Request) { paste, ok := request.Context().Value("paste").(*pastes.Paste) if !ok { - writeString(writer, http.StatusInternalServerError, "missing paste object") + writeErr(request, writer, errors.New("missing paste object")) return } cpy := *paste cpy.ModificationToken = "" - writeJSONOrErr(writer, http.StatusOK, cpy) + writeJSONOrErr(request, writer, http.StatusOK, cpy) } diff --git a/internal/web/v2_end_modify_paste.go b/internal/web/v2_end_modify_paste.go index ad648ea..6c58882 100644 --- a/internal/web/v2_end_modify_paste.go +++ b/internal/web/v2_end_modify_paste.go @@ -22,12 +22,12 @@ func (server *Server) v2EndpointModifyPaste(writer http.ResponseWriter, request // Read, parse and validate the request payload body, err := io.ReadAll(request.Body) if err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } payload := new(v2EndpointModifyPastePayload) if err := json.Unmarshal(body, payload); err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } if payload.Content != nil && *payload.Content == "" { @@ -55,6 +55,6 @@ func (server *Server) v2EndpointModifyPaste(writer http.ResponseWriter, request // Save the modified paste if err := server.Storage.Pastes().Upsert(request.Context(), paste); err != nil { - writeErr(writer, err) + writeErr(request, writer, err) } } diff --git a/internal/web/v2_end_report_paste.go b/internal/web/v2_end_report_paste.go index 186c88e..b48f1fc 100644 --- a/internal/web/v2_end_report_paste.go +++ b/internal/web/v2_end_report_paste.go @@ -22,12 +22,12 @@ func (server *Server) v2EndpointReportPaste(writer http.ResponseWriter, request // Read, parse and validate the request payload body, err := io.ReadAll(request.Body) if err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } payload := new(v2EndpointReportPastePayload) if err := json.Unmarshal(body, payload); err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } if payload.Reason == "" { @@ -41,8 +41,8 @@ func (server *Server) v2EndpointReportPaste(writer http.ResponseWriter, request } response, err := server.ReportClient.Send(report) if err != nil { - writeErr(writer, err) + writeErr(request, writer, err) return } - writeJSONOrErr(writer, http.StatusOK, response) + writeJSONOrErr(request, writer, http.StatusOK, response) } diff --git a/internal/web/v2_mid_inject_paste.go b/internal/web/v2_mid_inject_paste.go index 399a1f2..26657f9 100644 --- a/internal/web/v2_mid_inject_paste.go +++ b/internal/web/v2_mid_inject_paste.go @@ -17,10 +17,7 @@ func (server *Server) v2MiddlewareInjectPaste(next http.Handler) http.Handler { paste, err := server.Storage.Pastes().FindByID(request.Context(), pasteID) if err != nil { - if pasteID == "" { - writeErr(writer, err) - return - } + writeErr(request, writer, err) } if paste == nil { writeString(writer, http.StatusNotFound, "paste not found") diff --git a/pkg/chizerolog/logger.go b/pkg/chizerolog/logger.go new file mode 100644 index 0000000..2246ab0 --- /dev/null +++ b/pkg/chizerolog/logger.go @@ -0,0 +1,78 @@ +package chizerolog + +import ( + "context" + "fmt" + "github.com/go-chi/chi/v5/middleware" + "github.com/rs/zerolog/log" + "net/http" + "time" +) + +const dataKey = "chzl_meta" + +// Logger uses the global zerolog logger to log HTTP requests. +// Log messages are printed with the debug level. +// This middleware should be registered first. +func Logger(next http.Handler) http.Handler { + fn := func(writer http.ResponseWriter, request *http.Request) { + request = request.WithContext(context.WithValue(request.Context(), dataKey, make(map[string]any))) + + proxy := middleware.NewWrapResponseWriter(writer, request.ProtoMajor) + + start := time.Now() + defer func() { + end := time.Now() + + scheme := "http" + if request.TLS != nil { + scheme = "https" + } + url := fmt.Sprintf("%s://%s%s", scheme, request.Host, request.RequestURI) + + var err error + data := request.Context().Value(dataKey) + if data != nil { + injErr, ok := data.(map[string]any)["err"] + if ok { + err = injErr.(error) + } + } + + if err == nil { + log.Debug(). + Str("proto", request.Proto). + Str("method", request.Method). + Str("route", url). + Str("client_address", request.RemoteAddr). + Int("response_size", proxy.BytesWritten()). + Str("elapsed", fmt.Sprintf("%s", end.Sub(start))). + Int("status_code", proxy.Status()). + Msg("An incoming request has been processed.") + } else { + log.Error(). + Err(err). + Str("proto", request.Proto). + Str("method", request.Method). + Str("route", url). + Str("client_address", request.RemoteAddr). + Int("response_size", proxy.BytesWritten()). + Str("elapsed", fmt.Sprintf("%s", end.Sub(start))). + Int("status_code", proxy.Status()). + Msg("An incoming request has been processed and resulted in an unexpected error.") + } + }() + + next.ServeHTTP(proxy, request) + } + return http.HandlerFunc(fn) +} + +// InjectError injects the given error to a specific key so that Logger will log its occurrence later on in the request chain. +func InjectError(request *http.Request, err error) { + data := request.Context().Value(dataKey) + if data == nil { + return + } + data.(map[string]any)["err"] = err +} diff --git a/pkg/chizerolog/recoverer.go b/pkg/chizerolog/recoverer.go new file mode 100644 index 0000000..0bd4965 --- /dev/null +++ b/pkg/chizerolog/recoverer.go @@ -0,0 +1,36 @@ +package chizerolog + +import ( + "fmt" + "github.com/rs/zerolog/log" + "net/http" + "runtime/debug" +) + +// Recover recovers any call to panic() made by a request handler or middleware. +// It also logs an error-levelled message using the global zerolog logger. +// This middleware should be registered first (or second if Logger is also used). +func Recover(next http.Handler) http.Handler { + fn := func(writer http.ResponseWriter, request *http.Request) { + defer func() { + scheme := "http" + if request.TLS != nil { + scheme = "https" + } + url := fmt.Sprintf("%s://%s%s", scheme, request.Host, request.RequestURI) + + if rec := recover(); rec != nil { + log.Error(). + Str("proto", request.Proto). + Str("method", request.Method). + Str("route", url). + Interface("recovered", rec). + Bytes("stack", debug.Stack()). + Msg("A request handler has panicked.") + http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + }() + next.ServeHTTP(writer, request) + } + return http.HandlerFunc(fn) +} From 18839a202174f90417197e2251602592e60aae47 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 17:05:34 +0200 Subject: [PATCH 16/25] implicitly write 200 HTTP status code --- internal/web/server.go | 2 ++ internal/web/v2_end_delete_paste.go | 1 - pkg/chiimplicitok/middleware.go | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 pkg/chiimplicitok/middleware.go diff --git a/internal/web/server.go b/internal/web/server.go index da3434b..14fc1e4 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -7,6 +7,7 @@ import ( "github.com/lus/pasty/internal/pastes" "github.com/lus/pasty/internal/reports" "github.com/lus/pasty/internal/storage" + "github.com/lus/pasty/pkg/chiimplicitok" "github.com/lus/pasty/pkg/chizerolog" "net/http" ) @@ -52,6 +53,7 @@ func (server *Server) Start() error { router.Use(chizerolog.Logger) router.Use(chizerolog.Recover) + router.Use(chiimplicitok.Middleware) // Register the web frontend handler router.Get("/*", frontendHandler(router.NotFoundHandler())) diff --git a/internal/web/v2_end_delete_paste.go b/internal/web/v2_end_delete_paste.go index 09926c0..a89c2f3 100644 --- a/internal/web/v2_end_delete_paste.go +++ b/internal/web/v2_end_delete_paste.go @@ -15,5 +15,4 @@ func (server *Server) v2EndpointDeletePaste(writer http.ResponseWriter, request if err := server.Storage.Pastes().DeleteByID(request.Context(), paste.ID); err != nil { writeErr(request, writer, err) } - writer.WriteHeader(http.StatusOK) } diff --git a/pkg/chiimplicitok/middleware.go b/pkg/chiimplicitok/middleware.go new file mode 100644 index 0000000..30e98d0 --- /dev/null +++ b/pkg/chiimplicitok/middleware.go @@ -0,0 +1,22 @@ +package chiimplicitok + +import ( + "github.com/go-chi/chi/v5/middleware" + "net/http" +) + +// Middleware sets the status code of a request to http.StatusOK if it was not set explicitly by any handler. +func Middleware(next http.Handler) http.Handler { + fn := func(writer http.ResponseWriter, request *http.Request) { + proxy := middleware.NewWrapResponseWriter(writer, request.ProtoMajor) + + defer func() { + if proxy.Status() == 0 { + proxy.WriteHeader(http.StatusOK) + } + }() + + next.ServeHTTP(writer, request) + } + return http.HandlerFunc(fn) +} From 05a27a00c00cf3b2ec181033fd2ebefdc5cfc1e8 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 18:20:25 +0200 Subject: [PATCH 17/25] make config compatibility layer more abstract --- cmd/pasty/main.go | 25 ++++-------- internal/config/compatibility.go | 69 ++++++++++++++++++++++++++++++++ internal/config/config.go | 31 +++++++------- internal/web/server.go | 4 -- pkg/chiimplicitok/middleware.go | 2 +- 5 files changed, 92 insertions(+), 39 deletions(-) create mode 100644 internal/config/compatibility.go diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index 03c87b4..006144f 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -29,6 +29,7 @@ func main() { } // Load the configuration + config.Compatibility() cfg, err := config.Load() if err != nil { log.Fatal().Err(err).Msg("Could not load the configuration.") @@ -46,10 +47,6 @@ func main() { zerolog.SetGlobalLevel(level) } - // Run the configuration compatibility layer - // TODO: Remove this at a later state - configCompatibilityLayer(cfg) - // Determine the correct storage driver to use var driver storage.Driver switch strings.TrimSpace(strings.ToLower(cfg.StorageDriver)) { @@ -78,17 +75,16 @@ func main() { }() // Start the web server - log.Info().Str("address", cfg.WebAddress).Msg("Starting the web server...") + log.Info().Str("address", cfg.Address).Msg("Starting the web server...") webServer := &web.Server{ - Address: cfg.WebAddress, + Address: cfg.Address, Storage: driver, - HastebinSupport: cfg.HastebinSupport, - PasteIDLength: cfg.IDLength, - PasteIDCharset: cfg.IDCharacters, - PasteLengthCap: cfg.LengthCap, + PasteIDLength: cfg.PasteIDLength, + PasteIDCharset: cfg.PasteIDCharset, + PasteLengthCap: cfg.PasteLengthCap, ModificationTokensEnabled: cfg.ModificationTokens, ModificationTokenLength: cfg.ModificationTokenLength, - ModificationTokenCharset: cfg.ModificationTokenCharacters, + ModificationTokenCharset: cfg.ModificationTokenCharset, } if cfg.Reports.Enabled { webServer.ReportClient = &reports.Client{ @@ -117,10 +113,3 @@ func main() { signal.Notify(shutdownChan, os.Interrupt) <-shutdownChan } - -func configCompatibilityLayer(cfg *config.Config) { - // Print a notice if the (now removed) Hastebin support has been enabled - if cfg.HastebinSupport { - log.Warn().Msg("You have enabled the legacy 'Hastebin support' feature. This feature has been removed.") - } -} diff --git a/internal/config/compatibility.go b/internal/config/compatibility.go new file mode 100644 index 0000000..976ad1b --- /dev/null +++ b/internal/config/compatibility.go @@ -0,0 +1,69 @@ +package config + +import ( + "github.com/joho/godotenv" + "github.com/rs/zerolog/log" + "os" +) + +var removedKeys = []string{ + "PASTY_HASTEBIN_SUPPORT", + "PASTY_STORAGE_FILE_PATH", + "PASTY_STORAGE_MONGODB_CONNECTION_STRING", + "PASTY_STORAGE_MONGODB_DATABASE", + "PASTY_STORAGE_MONGODB_COLLECTION", + "PASTY_STORAGE_S3_ENDPOINT", + "PASTY_STORAGE_S3_ACCESS_KEY_ID", + "PASTY_STORAGE_S3_SECRET_ACCESS_KEY", + "PASTY_STORAGE_S3_SECRET_TOKEN", + "PASTY_STORAGE_S3_SECURE", + "PASTY_STORAGE_S3_REGION", + "PASTY_STORAGE_S3_BUCKET", +} + +var keyRedirects = map[string][]string{ + "PASTY_ADDRESS": {"PASTY_WEB_ADDRESS"}, + "PASTY_STORAGE_DRIVER": {"PASTY_STORAGE_TYPE"}, + "PASTY_POSTGRES_DSN": {"PASTY_STORAGE_POSTGRES_DSN"}, + "PASTY_PASTE_ID_LENGTH": {"PASTY_ID_LENGTH"}, + "PASTY_PASTE_ID_CHARSET": {"PASTY_ID_CHARACTERS"}, + "PASTY_PASTE_LENGTH_CAP": {"PASTY_LENGTH_CAP"}, + "PASTY_REPORTS_ENABLED": {"PASTY_REPORTS_ENABLED"}, + "PASTY_REPORTS_WEBHOOK_URL": {"PASTY_REPORT_WEBHOOK"}, + "PASTY_REPORTS_WEBHOOK_TOKEN": {"PASTY_REPORT_WEBHOOK_TOKEN"}, + "PASTY_MODIFICATION_TOKEN_CHARSET": {"PASTY_MODIFICATION_TOKEN_CHARACTERS"}, + "PASTY_MODIFICATION_TOKENS": {"PASTY_DELETION_TOKENS"}, + "PASTY_MODIFICATION_TOKEN_MASTER": {"PASTY_DELETION_TOKEN_MASTER"}, + "PASTY_MODIFICATION_TOKEN_LENGTH": {"PASTY_DELETION_TOKEN_LENGTH"}, +} + +// Compatibility runs several compatibility measurements. +// This is used to redirect legacy config keys to their new equivalent or print warnings about deprecated ones. +func Compatibility() { + _ = godotenv.Overload() + + for _, key := range removedKeys { + if isSet(key) { + log.Warn().Msgf("You have set the '%s' environment variable. This variable has been discontinued and has no further effect.", key) + } + } + + for newKey, oldKeys := range keyRedirects { + if !isSet(newKey) { + for _, oldKey := range oldKeys { + if isSet(oldKey) { + if err := os.Setenv(newKey, os.Getenv(oldKey)); err != nil { + continue + } + log.Warn().Msgf("You have set the '%s' environment variable. This variable has been renamed to '%s'. The value has been propagated, but please consider changing your configuration to avoid further complications.", oldKey, newKey) + break + } + } + } + } +} + +func isSet(key string) bool { + _, ok := os.LookupEnv(key) + return ok +} diff --git a/internal/config/config.go b/internal/config/config.go index 1ce7b19..3cac944 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,22 +7,21 @@ import ( ) type Config struct { - LogLevel string `default:"info" split_words:"true"` - WebAddress string `default:":8080" split_words:"true"` - StorageDriver string `default:"sqlite" split_words:"true"` - HastebinSupport bool `default:"false" split_words:"true"` // TODO: Legacy - IDLength int `default:"6" split_words:"true"` - IDCharacters string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` - ModificationTokens bool `default:"true" split_words:"true"` - ModificationTokenMaster string `split_words:"true"` - ModificationTokenLength int `default:"12" split_words:"true"` - ModificationTokenCharacters string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` - RateLimit string `default:"30-M" split_words:"true"` - LengthCap int `default:"50000" split_words:"true"` - AutoDelete *AutoDeleteConfig `split_words:"true"` - Reports *ReportConfig - Postgres *PostgresConfig - SQLite *SQLiteConfig + LogLevel string `default:"info" split_words:"true"` + Address string `default:":8080" split_words:"true"` + StorageDriver string `default:"sqlite" split_words:"true"` + PasteIDLength int `default:"6" split_words:"true"` + PasteIDCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` + ModificationTokens bool `default:"true" split_words:"true"` + ModificationTokenMaster string `split_words:"true"` + ModificationTokenLength int `default:"12" split_words:"true"` + ModificationTokenCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` + RateLimit string `default:"30-M" split_words:"true"` + PasteLengthCap int `default:"50000" split_words:"true"` + AutoDelete *AutoDeleteConfig `split_words:"true"` + Reports *ReportConfig + Postgres *PostgresConfig + SQLite *SQLiteConfig } type AutoDeleteConfig struct { diff --git a/internal/web/server.go b/internal/web/server.go index 14fc1e4..f4f244b 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -23,10 +23,6 @@ type Server struct { // If this is set to nil, the report system will be considered deactivated. ReportClient *reports.Client - // Whether the Hastebin support should be enabled. - // If this is set to 'false', the Hastebin specific endpoints will not be registered. - HastebinSupport bool - // The length of newly generated paste IDs. PasteIDLength int // The charset to use when generating new paste IDs. diff --git a/pkg/chiimplicitok/middleware.go b/pkg/chiimplicitok/middleware.go index 30e98d0..446999d 100644 --- a/pkg/chiimplicitok/middleware.go +++ b/pkg/chiimplicitok/middleware.go @@ -16,7 +16,7 @@ func Middleware(next http.Handler) http.Handler { } }() - next.ServeHTTP(writer, request) + next.ServeHTTP(proxy, request) } return http.HandlerFunc(fn) } From 97082633731f71ef069334a2e0b3b72c558e7ecd Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 18:25:16 +0200 Subject: [PATCH 18/25] print specific notices for legacy storage drivers --- cmd/pasty/main.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index 006144f..24f9622 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -56,6 +56,15 @@ func main() { case "sqlite": driver = sqlite.New(cfg.SQLite.File) break + case "file": + // TODO: Readme notice + log.Fatal().Msg("You have configured the legacy 'file' storage driver. This storage driver has been removed in favor of PostgreSQL and SQLite, but the latter one may be a seamless alternative for you. Head over to the projects README for more information.") + break + case "mongodb": + case "s3": + // TODO: Readme notice + log.Fatal().Msg("You have configured a legacy storage driver. This storage driver has been removed in favor of PostgreSQL and SQLite, but the migration process is well-documented. Head over to the projects README for more information.") + break default: log.Fatal().Str("driver_name", cfg.StorageDriver).Msg("An invalid storage driver name was given.") return From 695b900f28dc7c784e6d641abe7ea1ec25573811 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 18:35:26 +0200 Subject: [PATCH 19/25] write Content-Type and Content-Length headers --- cmd/pasty/main.go | 2 +- internal/config/compatibility.go | 28 ++++++++++++++-------------- internal/config/config.go | 30 +++++++++++++++--------------- internal/web/response_writer.go | 7 +++++++ 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index 24f9622..b1fe949 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -91,7 +91,7 @@ func main() { PasteIDLength: cfg.PasteIDLength, PasteIDCharset: cfg.PasteIDCharset, PasteLengthCap: cfg.PasteLengthCap, - ModificationTokensEnabled: cfg.ModificationTokens, + ModificationTokensEnabled: cfg.ModificationTokensEnabled, ModificationTokenLength: cfg.ModificationTokenLength, ModificationTokenCharset: cfg.ModificationTokenCharset, } diff --git a/internal/config/compatibility.go b/internal/config/compatibility.go index 976ad1b..3a73709 100644 --- a/internal/config/compatibility.go +++ b/internal/config/compatibility.go @@ -22,19 +22,19 @@ var removedKeys = []string{ } var keyRedirects = map[string][]string{ - "PASTY_ADDRESS": {"PASTY_WEB_ADDRESS"}, - "PASTY_STORAGE_DRIVER": {"PASTY_STORAGE_TYPE"}, - "PASTY_POSTGRES_DSN": {"PASTY_STORAGE_POSTGRES_DSN"}, - "PASTY_PASTE_ID_LENGTH": {"PASTY_ID_LENGTH"}, - "PASTY_PASTE_ID_CHARSET": {"PASTY_ID_CHARACTERS"}, - "PASTY_PASTE_LENGTH_CAP": {"PASTY_LENGTH_CAP"}, - "PASTY_REPORTS_ENABLED": {"PASTY_REPORTS_ENABLED"}, - "PASTY_REPORTS_WEBHOOK_URL": {"PASTY_REPORT_WEBHOOK"}, - "PASTY_REPORTS_WEBHOOK_TOKEN": {"PASTY_REPORT_WEBHOOK_TOKEN"}, - "PASTY_MODIFICATION_TOKEN_CHARSET": {"PASTY_MODIFICATION_TOKEN_CHARACTERS"}, - "PASTY_MODIFICATION_TOKENS": {"PASTY_DELETION_TOKENS"}, - "PASTY_MODIFICATION_TOKEN_MASTER": {"PASTY_DELETION_TOKEN_MASTER"}, - "PASTY_MODIFICATION_TOKEN_LENGTH": {"PASTY_DELETION_TOKEN_LENGTH"}, + "PASTY_ADDRESS": {"PASTY_WEB_ADDRESS"}, + "PASTY_STORAGE_DRIVER": {"PASTY_STORAGE_TYPE"}, + "PASTY_POSTGRES_DSN": {"PASTY_STORAGE_POSTGRES_DSN"}, + "PASTY_PASTE_ID_LENGTH": {"PASTY_ID_LENGTH"}, + "PASTY_PASTE_ID_CHARSET": {"PASTY_ID_CHARACTERS"}, + "PASTY_PASTE_LENGTH_CAP": {"PASTY_LENGTH_CAP"}, + "PASTY_REPORTS_ENABLED": {"PASTY_REPORTS_ENABLED"}, + "PASTY_REPORTS_WEBHOOK_URL": {"PASTY_REPORT_WEBHOOK"}, + "PASTY_REPORTS_WEBHOOK_TOKEN": {"PASTY_REPORT_WEBHOOK_TOKEN"}, + "PASTY_MODIFICATION_TOKENS_ENABLED": {"PASTY_MODIFICATION_TOKENS", "PASTY_DELETION_TOKENS"}, + "PASTY_MODIFICATION_TOKEN_CHARSET": {"PASTY_MODIFICATION_TOKEN_CHARACTERS"}, + "PASTY_MODIFICATION_TOKEN_MASTER": {"PASTY_DELETION_TOKEN_MASTER"}, + "PASTY_MODIFICATION_TOKEN_LENGTH": {"PASTY_DELETION_TOKEN_LENGTH"}, } // Compatibility runs several compatibility measurements. @@ -55,7 +55,7 @@ func Compatibility() { if err := os.Setenv(newKey, os.Getenv(oldKey)); err != nil { continue } - log.Warn().Msgf("You have set the '%s' environment variable. This variable has been renamed to '%s'. The value has been propagated, but please consider changing your configuration to avoid further complications.", oldKey, newKey) + log.Warn().Msgf("You have set the '%s' environment variable. This variable has been renamed to '%s'. The value has been propagated, but please consider adjusting your configuration to avoid further complications.", oldKey, newKey) break } } diff --git a/internal/config/config.go b/internal/config/config.go index 3cac944..d32789f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,21 +7,21 @@ import ( ) type Config struct { - LogLevel string `default:"info" split_words:"true"` - Address string `default:":8080" split_words:"true"` - StorageDriver string `default:"sqlite" split_words:"true"` - PasteIDLength int `default:"6" split_words:"true"` - PasteIDCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` - ModificationTokens bool `default:"true" split_words:"true"` - ModificationTokenMaster string `split_words:"true"` - ModificationTokenLength int `default:"12" split_words:"true"` - ModificationTokenCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` - RateLimit string `default:"30-M" split_words:"true"` - PasteLengthCap int `default:"50000" split_words:"true"` - AutoDelete *AutoDeleteConfig `split_words:"true"` - Reports *ReportConfig - Postgres *PostgresConfig - SQLite *SQLiteConfig + LogLevel string `default:"info" split_words:"true"` + Address string `default:":8080" split_words:"true"` + StorageDriver string `default:"sqlite" split_words:"true"` + PasteIDLength int `default:"6" split_words:"true"` + PasteIDCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` + ModificationTokensEnabled bool `default:"true" split_words:"true"` + ModificationTokenMaster string `split_words:"true"` + ModificationTokenLength int `default:"12" split_words:"true"` + ModificationTokenCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` + RateLimit string `default:"30-M" split_words:"true"` + PasteLengthCap int `default:"50000" split_words:"true"` + AutoDelete *AutoDeleteConfig `split_words:"true"` + Reports *ReportConfig + Postgres *PostgresConfig + SQLite *SQLiteConfig } type AutoDeleteConfig struct { diff --git a/internal/web/response_writer.go b/internal/web/response_writer.go index accaee9..bb82d97 100644 --- a/internal/web/response_writer.go +++ b/internal/web/response_writer.go @@ -4,14 +4,19 @@ import ( "encoding/json" "github.com/lus/pasty/pkg/chizerolog" "net/http" + "strconv" ) func writeErr(request *http.Request, writer http.ResponseWriter, err error) { chizerolog.InjectError(request, err) + writer.Header().Set("Content-Type", "text/plain") + writer.Header().Set("Content-Length", strconv.Itoa(len(err.Error()))) writeString(writer, http.StatusInternalServerError, err.Error()) } func writeString(writer http.ResponseWriter, status int, value string) { + writer.Header().Set("Content-Type", "text/plain") + writer.Header().Set("Content-Length", strconv.Itoa(len(value))) writer.WriteHeader(status) writer.Write([]byte(value)) } @@ -22,6 +27,8 @@ func writeJSON(writer http.ResponseWriter, status int, value any) error { return err } + writer.Header().Set("Content-Type", "application/json") + writer.Header().Set("Content-Length", strconv.Itoa(len(jsonData))) writer.WriteHeader(status) writer.Write(jsonData) From 5b0ba721b82c4e03e06179cfb09d4507dce4602e Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 18:38:58 +0200 Subject: [PATCH 20/25] validate request content type --- internal/web/request_accept.go | 14 ++++++++++++++ internal/web/v2_end_create_paste.go | 3 +++ internal/web/v2_end_modify_paste.go | 3 +++ internal/web/v2_end_report_paste.go | 3 +++ 4 files changed, 23 insertions(+) create mode 100644 internal/web/request_accept.go diff --git a/internal/web/request_accept.go b/internal/web/request_accept.go new file mode 100644 index 0000000..07ddf30 --- /dev/null +++ b/internal/web/request_accept.go @@ -0,0 +1,14 @@ +package web + +import "net/http" + +func accept(writer http.ResponseWriter, request *http.Request, contentTypes ...string) bool { + contentType := request.Header.Get("Content-Type") + for _, accepted := range contentTypes { + if contentType == accepted { + return true + } + } + writeString(writer, http.StatusUnsupportedMediaType, "unsupported media type") + return false +} diff --git a/internal/web/v2_end_create_paste.go b/internal/web/v2_end_create_paste.go index e114043..b552300 100644 --- a/internal/web/v2_end_create_paste.go +++ b/internal/web/v2_end_create_paste.go @@ -16,6 +16,9 @@ type v2EndpointCreatePastePayload struct { func (server *Server) v2EndpointCreatePaste(writer http.ResponseWriter, request *http.Request) { // Read, parse and validate the request payload + if !accept(writer, request, "application/json") { + return + } body, err := io.ReadAll(request.Body) if err != nil { writeErr(request, writer, err) diff --git a/internal/web/v2_end_modify_paste.go b/internal/web/v2_end_modify_paste.go index 6c58882..8a6d1b7 100644 --- a/internal/web/v2_end_modify_paste.go +++ b/internal/web/v2_end_modify_paste.go @@ -20,6 +20,9 @@ func (server *Server) v2EndpointModifyPaste(writer http.ResponseWriter, request } // Read, parse and validate the request payload + if !accept(writer, request, "application/json") { + return + } body, err := io.ReadAll(request.Body) if err != nil { writeErr(request, writer, err) diff --git a/internal/web/v2_end_report_paste.go b/internal/web/v2_end_report_paste.go index b48f1fc..2fb0513 100644 --- a/internal/web/v2_end_report_paste.go +++ b/internal/web/v2_end_report_paste.go @@ -20,6 +20,9 @@ func (server *Server) v2EndpointReportPaste(writer http.ResponseWriter, request } // Read, parse and validate the request payload + if !accept(writer, request, "application/json") { + return + } body, err := io.ReadAll(request.Body) if err != nil { writeErr(request, writer, err) From e8a88e21ae8156a64ed37964acd9417c3c822f1f Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 18:55:55 +0200 Subject: [PATCH 21/25] implement cleanup task --- cmd/pasty/main.go | 16 +++++++++++ internal/cleanup/task.go | 48 ++++++++++++++++++++++++++++++++ internal/config/compatibility.go | 3 ++ internal/config/config.go | 32 ++++++++++----------- 4 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 internal/cleanup/task.go diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index b1fe949..76b43d2 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -3,6 +3,7 @@ package main import ( "context" "errors" + "github.com/lus/pasty/internal/cleanup" "github.com/lus/pasty/internal/config" "github.com/lus/pasty/internal/meta" "github.com/lus/pasty/internal/reports" @@ -83,6 +84,21 @@ func main() { } }() + // Schedule the cleanup task if configured + if cfg.Cleanup.Enabled { + task := &cleanup.Task{ + Interval: cfg.Cleanup.TaskInterval, + MaxPasteAge: cfg.Cleanup.PasteLifetime, + Repository: driver.Pastes(), + } + log.Info().Msg("Scheduling the cleanup task...") + task.Start() + defer func() { + log.Info().Msg("Shutting down the cleanup task...") + task.Stop() + }() + } + // Start the web server log.Info().Str("address", cfg.Address).Msg("Starting the web server...") webServer := &web.Server{ diff --git a/internal/cleanup/task.go b/internal/cleanup/task.go new file mode 100644 index 0000000..f028e88 --- /dev/null +++ b/internal/cleanup/task.go @@ -0,0 +1,48 @@ +package cleanup + +import ( + "context" + "github.com/lus/pasty/internal/pastes" + "github.com/rs/zerolog/log" + "time" +) + +type Task struct { + Interval time.Duration + MaxPasteAge time.Duration + Repository pastes.Repository + + running bool + stop chan struct{} +} + +func (task *Task) Start() { + if task.running { + return + } + task.stop = make(chan struct{}, 1) + go func() { + for { + select { + case <-time.After(task.Interval): + n, err := task.Repository.DeleteOlderThan(context.Background(), task.MaxPasteAge) + if err != nil { + log.Err(err).Msg("Could not clean up expired pastes.") + continue + } + log.Debug().Int("amount", n).Msg("Cleaned up expired pastes.") + case <-task.stop: + task.running = false + return + } + } + }() + task.running = true +} + +func (task *Task) Stop() { + if !task.running { + return + } + close(task.stop) +} diff --git a/internal/config/compatibility.go b/internal/config/compatibility.go index 3a73709..cfe5b51 100644 --- a/internal/config/compatibility.go +++ b/internal/config/compatibility.go @@ -31,6 +31,9 @@ var keyRedirects = map[string][]string{ "PASTY_REPORTS_ENABLED": {"PASTY_REPORTS_ENABLED"}, "PASTY_REPORTS_WEBHOOK_URL": {"PASTY_REPORT_WEBHOOK"}, "PASTY_REPORTS_WEBHOOK_TOKEN": {"PASTY_REPORT_WEBHOOK_TOKEN"}, + "PASTY_CLEANUP_ENABLED": {"PASTY_AUTODELETE"}, + "PASTY_CLEANUP_PASTE_LIFETIME": {"PASTY_AUTODELETE_LIFETIME"}, + "PASTY_CLEANUP_TASK_INTERVAL": {"PASTY_AUTODELETE_TASK_INTERVAL"}, "PASTY_MODIFICATION_TOKENS_ENABLED": {"PASTY_MODIFICATION_TOKENS", "PASTY_DELETION_TOKENS"}, "PASTY_MODIFICATION_TOKEN_CHARSET": {"PASTY_MODIFICATION_TOKEN_CHARACTERS"}, "PASTY_MODIFICATION_TOKEN_MASTER": {"PASTY_DELETION_TOKEN_MASTER"}, diff --git a/internal/config/config.go b/internal/config/config.go index d32789f..3796860 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,27 +7,27 @@ import ( ) type Config struct { - LogLevel string `default:"info" split_words:"true"` - Address string `default:":8080" split_words:"true"` - StorageDriver string `default:"sqlite" split_words:"true"` - PasteIDLength int `default:"6" split_words:"true"` - PasteIDCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` - ModificationTokensEnabled bool `default:"true" split_words:"true"` - ModificationTokenMaster string `split_words:"true"` - ModificationTokenLength int `default:"12" split_words:"true"` - ModificationTokenCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` - RateLimit string `default:"30-M" split_words:"true"` - PasteLengthCap int `default:"50000" split_words:"true"` - AutoDelete *AutoDeleteConfig `split_words:"true"` + LogLevel string `default:"info" split_words:"true"` + Address string `default:":8080" split_words:"true"` + StorageDriver string `default:"sqlite" split_words:"true"` + PasteIDLength int `default:"6" split_words:"true"` + PasteIDCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` + ModificationTokensEnabled bool `default:"true" split_words:"true"` + ModificationTokenMaster string `split_words:"true"` + ModificationTokenLength int `default:"12" split_words:"true"` + ModificationTokenCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` + RateLimit string `default:"30-M" split_words:"true"` + PasteLengthCap int `default:"50000" split_words:"true"` + Cleanup *CleanupConfig Reports *ReportConfig Postgres *PostgresConfig SQLite *SQLiteConfig } -type AutoDeleteConfig struct { - Enabled bool `default:"false"` - Lifetime time.Duration `default:"720h"` - TaskInterval time.Duration `default:"5m" split_words:"true"` +type CleanupConfig struct { + Enabled bool `default:"false"` + PasteLifetime time.Duration `default:"720h" split_words:"true"` + TaskInterval time.Duration `default:"5m" split_words:"true"` } type ReportConfig struct { From 9a794a82d07c254a37fab697b415bdadad5e2eb1 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 20:07:19 +0200 Subject: [PATCH 22/25] use native middleware.AllowContentType --- internal/web/request_accept.go | 14 -------------- internal/web/server.go | 7 ++++--- internal/web/v2_end_create_paste.go | 3 --- internal/web/v2_end_modify_paste.go | 3 --- internal/web/v2_end_report_paste.go | 3 --- 5 files changed, 4 insertions(+), 26 deletions(-) delete mode 100644 internal/web/request_accept.go diff --git a/internal/web/request_accept.go b/internal/web/request_accept.go deleted file mode 100644 index 07ddf30..0000000 --- a/internal/web/request_accept.go +++ /dev/null @@ -1,14 +0,0 @@ -package web - -import "net/http" - -func accept(writer http.ResponseWriter, request *http.Request, contentTypes ...string) bool { - contentType := request.Header.Get("Content-Type") - for _, accepted := range contentTypes { - if contentType == accepted { - return true - } - } - writeString(writer, http.StatusUnsupportedMediaType, "unsupported media type") - return false -} diff --git a/internal/web/server.go b/internal/web/server.go index f4f244b..b52846c 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -3,6 +3,7 @@ package web import ( "context" "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" "github.com/lus/pasty/internal/meta" "github.com/lus/pasty/internal/pastes" "github.com/lus/pasty/internal/reports" @@ -67,11 +68,11 @@ func (server *Server) Start() error { // Register the paste API endpoints router.Get("/api/*", router.NotFoundHandler()) router.With(server.v2MiddlewareInjectPaste).Get("/api/v2/pastes/{paste_id}", server.v2EndpointGetPaste) - router.Post("/api/v2/pastes", server.v2EndpointCreatePaste) - router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Patch("/api/v2/pastes/{paste_id}", server.v2EndpointModifyPaste) + router.With(middleware.AllowContentType("application/json")).Post("/api/v2/pastes", server.v2EndpointCreatePaste) + router.With(middleware.AllowContentType("application/json"), server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Patch("/api/v2/pastes/{paste_id}", server.v2EndpointModifyPaste) router.With(server.v2MiddlewareInjectPaste, server.v2MiddlewareAuthorize).Delete("/api/v2/pastes/{paste_id}", server.v2EndpointDeletePaste) if server.ReportClient != nil { - router.With(server.v2MiddlewareInjectPaste).Post("/api/v2/pastes/{paste_id}/report", server.v2EndpointReportPaste) + router.With(middleware.AllowContentType("application/json"), server.v2MiddlewareInjectPaste).Post("/api/v2/pastes/{paste_id}/report", server.v2EndpointReportPaste) } router.Get("/api/v2/info", func(writer http.ResponseWriter, request *http.Request) { writeJSONOrErr(request, writer, http.StatusOK, map[string]any{ diff --git a/internal/web/v2_end_create_paste.go b/internal/web/v2_end_create_paste.go index b552300..e114043 100644 --- a/internal/web/v2_end_create_paste.go +++ b/internal/web/v2_end_create_paste.go @@ -16,9 +16,6 @@ type v2EndpointCreatePastePayload struct { func (server *Server) v2EndpointCreatePaste(writer http.ResponseWriter, request *http.Request) { // Read, parse and validate the request payload - if !accept(writer, request, "application/json") { - return - } body, err := io.ReadAll(request.Body) if err != nil { writeErr(request, writer, err) diff --git a/internal/web/v2_end_modify_paste.go b/internal/web/v2_end_modify_paste.go index 8a6d1b7..6c58882 100644 --- a/internal/web/v2_end_modify_paste.go +++ b/internal/web/v2_end_modify_paste.go @@ -20,9 +20,6 @@ func (server *Server) v2EndpointModifyPaste(writer http.ResponseWriter, request } // Read, parse and validate the request payload - if !accept(writer, request, "application/json") { - return - } body, err := io.ReadAll(request.Body) if err != nil { writeErr(request, writer, err) diff --git a/internal/web/v2_end_report_paste.go b/internal/web/v2_end_report_paste.go index 2fb0513..b48f1fc 100644 --- a/internal/web/v2_end_report_paste.go +++ b/internal/web/v2_end_report_paste.go @@ -20,9 +20,6 @@ func (server *Server) v2EndpointReportPaste(writer http.ResponseWriter, request } // Read, parse and validate the request payload - if !accept(writer, request, "application/json") { - return - } body, err := io.ReadAll(request.Body) if err != nil { writeErr(request, writer, err) From e2c94544308b8f8c6c4d382256798899c9f4f579 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 20:36:15 +0200 Subject: [PATCH 23/25] restrict paste metadata dimensions --- internal/maps/map_utils.go | 24 ++++++++++++++++++++++++ internal/static/static.go | 6 ++++++ internal/web/v2_end_create_paste.go | 7 +++++++ internal/web/v2_end_modify_paste.go | 7 +++++++ 4 files changed, 44 insertions(+) create mode 100644 internal/maps/map_utils.go create mode 100644 internal/static/static.go diff --git a/internal/maps/map_utils.go b/internal/maps/map_utils.go new file mode 100644 index 0000000..2a79a81 --- /dev/null +++ b/internal/maps/map_utils.go @@ -0,0 +1,24 @@ +package maps + +func ExceedsDimensions(src map[string]any, width, depth int) bool { + if width < 0 || depth < 1 || len(src) > width { + return true + } + + for _, value := range src { + childMap, ok := value.(map[string]any) + if !ok { + continue + } + + if depth == 1 { + return true + } + + if ExceedsDimensions(childMap, width, depth-1) { + return true + } + } + + return false +} diff --git a/internal/static/static.go b/internal/static/static.go new file mode 100644 index 0000000..77c4762 --- /dev/null +++ b/internal/static/static.go @@ -0,0 +1,6 @@ +package static + +var ( + MaxMetadataWidth = 10 + MaxMetadataDepth = 5 +) diff --git a/internal/web/v2_end_create_paste.go b/internal/web/v2_end_create_paste.go index e114043..d28e4fe 100644 --- a/internal/web/v2_end_create_paste.go +++ b/internal/web/v2_end_create_paste.go @@ -2,8 +2,11 @@ package web import ( "encoding/json" + "fmt" + "github.com/lus/pasty/internal/maps" "github.com/lus/pasty/internal/pastes" "github.com/lus/pasty/internal/randx" + "github.com/lus/pasty/internal/static" "io" "net/http" "time" @@ -34,6 +37,10 @@ func (server *Server) v2EndpointCreatePaste(writer http.ResponseWriter, request writeString(writer, http.StatusBadRequest, "too large paste content") return } + if payload.Metadata != nil && maps.ExceedsDimensions(payload.Metadata, static.MaxMetadataWidth, static.MaxMetadataDepth) { + writeString(writer, http.StatusBadRequest, fmt.Sprintf("metadata exceeds maximum dimensions (max. width: %d; max. depth: %d)", static.MaxMetadataWidth, static.MaxMetadataDepth)) + return + } id, err := pastes.GenerateID(request.Context(), server.Storage.Pastes(), server.PasteIDCharset, server.PasteIDLength) if err != nil { diff --git a/internal/web/v2_end_modify_paste.go b/internal/web/v2_end_modify_paste.go index 6c58882..d7e8937 100644 --- a/internal/web/v2_end_modify_paste.go +++ b/internal/web/v2_end_modify_paste.go @@ -2,7 +2,10 @@ package web import ( "encoding/json" + "fmt" + "github.com/lus/pasty/internal/maps" "github.com/lus/pasty/internal/pastes" + "github.com/lus/pasty/internal/static" "io" "net/http" ) @@ -38,6 +41,10 @@ func (server *Server) v2EndpointModifyPaste(writer http.ResponseWriter, request writeString(writer, http.StatusBadRequest, "too large paste content") return } + if payload.Metadata != nil && maps.ExceedsDimensions(payload.Metadata, static.MaxMetadataWidth, static.MaxMetadataDepth) { + writeString(writer, http.StatusBadRequest, fmt.Sprintf("metadata exceeds maximum dimensions (max. width: %d; max. depth: %d)", static.MaxMetadataWidth, static.MaxMetadataDepth)) + return + } // Modify the paste itself if payload.Content != nil { From b2ad618781aff992470828b86ad8c67b5a747fad Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 21:52:09 +0200 Subject: [PATCH 24/25] implement console commands --- cmd/pasty/command_cleanup.go | 27 ++++++++ cmd/pasty/command_delete.go | 28 ++++++++ cmd/pasty/command_router.go | 73 +++++++++++++++++++++ cmd/pasty/command_set_modification_token.go | 34 ++++++++++ cmd/pasty/main.go | 12 +++- internal/config/config.go | 1 + 6 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 cmd/pasty/command_cleanup.go create mode 100644 cmd/pasty/command_delete.go create mode 100644 cmd/pasty/command_router.go create mode 100644 cmd/pasty/command_set_modification_token.go diff --git a/cmd/pasty/command_cleanup.go b/cmd/pasty/command_cleanup.go new file mode 100644 index 0000000..16abc57 --- /dev/null +++ b/cmd/pasty/command_cleanup.go @@ -0,0 +1,27 @@ +package main + +import ( + "context" + "fmt" + "time" +) + +func (router *consoleCommandRouter) Cleanup(args []string) { + if len(args) == 0 { + fmt.Println("Expected 1 argument.") + return + } + lifetime, err := time.ParseDuration(args[0]) + if err != nil { + fmt.Printf("Could not parse duration: %s.\n", err.Error()) + return + } + amount, err := router.Storage.Pastes().DeleteOlderThan(context.Background(), lifetime) + if err != nil { + if err != nil { + fmt.Printf("Could not delete pastes: %s.\n", err.Error()) + return + } + } + fmt.Printf("Deleted %d pastes older than %s.\n", amount, lifetime) +} diff --git a/cmd/pasty/command_delete.go b/cmd/pasty/command_delete.go new file mode 100644 index 0000000..2b92be4 --- /dev/null +++ b/cmd/pasty/command_delete.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "fmt" +) + +func (router *consoleCommandRouter) Delete(args []string) { + if len(args) == 0 { + fmt.Println("Expected 1 argument.") + return + } + pasteID := args[0] + paste, err := router.Storage.Pastes().FindByID(context.Background(), pasteID) + if err != nil { + fmt.Printf("Could not look up paste: %s.\n", err.Error()) + return + } + if paste == nil { + fmt.Printf("Invalid paste ID: %s.\n", pasteID) + return + } + if err := router.Storage.Pastes().DeleteByID(context.Background(), pasteID); err != nil { + fmt.Printf("Could not delete paste: %s.\n", err.Error()) + return + } + fmt.Printf("Deleted paste %s.\n", pasteID) +} diff --git a/cmd/pasty/command_router.go b/cmd/pasty/command_router.go new file mode 100644 index 0000000..4f6bdc9 --- /dev/null +++ b/cmd/pasty/command_router.go @@ -0,0 +1,73 @@ +package main + +import ( + "bufio" + "fmt" + "github.com/lus/pasty/internal/config" + "github.com/lus/pasty/internal/storage" + "github.com/rs/zerolog/log" + "os" + "regexp" + "strings" + "syscall" +) + +var whitespaceRegex = regexp.MustCompile("\\s+") + +type consoleCommandRouter struct { + Config *config.Config + Storage storage.Driver +} + +func (router *consoleCommandRouter) Listen() { + reader := bufio.NewReader(os.Stdin) + for { + fmt.Print("> ") + input, err := reader.ReadString('\n') + if err != nil { + log.Err(err).Msg("Could not read console input.") + continue + } + + commandData := strings.Split(whitespaceRegex.ReplaceAllString(strings.TrimSpace(input), " "), " ") + if len(commandData) == 0 { + fmt.Println("Invalid command.") + continue + } + + handle := strings.ToLower(commandData[0]) + var args []string + if len(commandData) > 1 { + args = commandData[1:] + } + + switch handle { + case "help": + fmt.Println("Available commands:") + fmt.Println(" help : Shows this overview") + fmt.Println(" stop : Stops the application") + fmt.Println(" setmodtoken : Changes the modification token of the paste with ID to ") + fmt.Println(" delete : Deletes the paste with ID ") + fmt.Println(" cleanup : Deletes all pastes that are older than ") + break + case "stop": + if err := syscall.Kill(syscall.Getpid(), syscall.SIGINT); err != nil { + fmt.Printf("Could not send interrupt signal: %s.\nUse Ctrl+C instead.\n", err.Error()) + break + } + return + case "setmodtoken": + router.SetModificationToken(args) + break + case "delete": + router.Delete(args) + break + case "cleanup": + router.Cleanup(args) + break + default: + fmt.Println("Invalid command.") + break + } + } +} diff --git a/cmd/pasty/command_set_modification_token.go b/cmd/pasty/command_set_modification_token.go new file mode 100644 index 0000000..c99c2b0 --- /dev/null +++ b/cmd/pasty/command_set_modification_token.go @@ -0,0 +1,34 @@ +package main + +import ( + "context" + "fmt" +) + +func (router *consoleCommandRouter) SetModificationToken(args []string) { + if len(args) < 2 { + fmt.Println("Expected 2 arguments.") + return + } + pasteID := args[0] + newToken := args[1] + paste, err := router.Storage.Pastes().FindByID(context.Background(), pasteID) + if err != nil { + fmt.Printf("Could not look up paste: %s.\n", err.Error()) + return + } + if paste == nil { + fmt.Printf("Invalid paste ID: %s.\n", pasteID) + return + } + paste.ModificationToken = newToken + if err := paste.HashModificationToken(); err != nil { + fmt.Printf("Could not hash modification token: %s.\n", err.Error()) + return + } + if err := router.Storage.Pastes().Upsert(context.Background(), paste); err != nil { + fmt.Printf("Could not update paste: %s.\n", err.Error()) + return + } + fmt.Printf("Changed modification token of paste %s to %s.\n", pasteID, newToken) +} diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index 76b43d2..4ee6028 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -132,8 +132,16 @@ func main() { } }() - // Wait for an interrupt signal - log.Info().Msg("The application has been started. Use Ctrl+C to shut it down.") + if !cfg.ConsoleCommandsEnabled { + log.Info().Msg("The application has been started. Use Ctrl+C to shut it down.") + } else { + log.Info().Msg("The application has been started and listens to console commands. Use Ctrl+C or 'stop' to shut it down.") + go (&consoleCommandRouter{ + Config: cfg, + Storage: driver, + }).Listen() + } + shutdownChan := make(chan os.Signal, 1) signal.Notify(shutdownChan, os.Interrupt) <-shutdownChan diff --git a/internal/config/config.go b/internal/config/config.go index 3796860..e3b4b74 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -18,6 +18,7 @@ type Config struct { ModificationTokenCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` RateLimit string `default:"30-M" split_words:"true"` PasteLengthCap int `default:"50000" split_words:"true"` + ConsoleCommandsEnabled bool `default:"true" split_words:"true"` Cleanup *CleanupConfig Reports *ReportConfig Postgres *PostgresConfig From 3af880042d4fbbecdc67b404acb77812febd9352 Mon Sep 17 00:00:00 2001 From: Lukas Schulte Pelkum Date: Sat, 17 Jun 2023 21:58:05 +0200 Subject: [PATCH 25/25] move console commands to separate package --- cmd/pasty/main.go | 5 ++++- .../consolecommands/cmd_cleanup.go | 4 ++-- .../consolecommands/cmd_delete.go | 4 ++-- .../consolecommands/cmd_set_modification_token.go | 4 ++-- .../command_router.go => internal/consolecommands/router.go | 6 +++--- 5 files changed, 13 insertions(+), 10 deletions(-) rename cmd/pasty/command_cleanup.go => internal/consolecommands/cmd_cleanup.go (87%) rename cmd/pasty/command_delete.go => internal/consolecommands/cmd_delete.go (88%) rename cmd/pasty/command_set_modification_token.go => internal/consolecommands/cmd_set_modification_token.go (90%) rename cmd/pasty/command_router.go => internal/consolecommands/router.go (94%) diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index 4ee6028..95939b2 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/lus/pasty/internal/cleanup" "github.com/lus/pasty/internal/config" + "github.com/lus/pasty/internal/consolecommands" "github.com/lus/pasty/internal/meta" "github.com/lus/pasty/internal/reports" "github.com/lus/pasty/internal/storage" @@ -132,16 +133,18 @@ func main() { } }() + // Listen to console commands if enabled if !cfg.ConsoleCommandsEnabled { log.Info().Msg("The application has been started. Use Ctrl+C to shut it down.") } else { log.Info().Msg("The application has been started and listens to console commands. Use Ctrl+C or 'stop' to shut it down.") - go (&consoleCommandRouter{ + go (&consolecommands.Router{ Config: cfg, Storage: driver, }).Listen() } + // Wait for an interrupt signal shutdownChan := make(chan os.Signal, 1) signal.Notify(shutdownChan, os.Interrupt) <-shutdownChan diff --git a/cmd/pasty/command_cleanup.go b/internal/consolecommands/cmd_cleanup.go similarity index 87% rename from cmd/pasty/command_cleanup.go rename to internal/consolecommands/cmd_cleanup.go index 16abc57..59029d0 100644 --- a/cmd/pasty/command_cleanup.go +++ b/internal/consolecommands/cmd_cleanup.go @@ -1,4 +1,4 @@ -package main +package consolecommands import ( "context" @@ -6,7 +6,7 @@ import ( "time" ) -func (router *consoleCommandRouter) Cleanup(args []string) { +func (router *Router) Cleanup(args []string) { if len(args) == 0 { fmt.Println("Expected 1 argument.") return diff --git a/cmd/pasty/command_delete.go b/internal/consolecommands/cmd_delete.go similarity index 88% rename from cmd/pasty/command_delete.go rename to internal/consolecommands/cmd_delete.go index 2b92be4..081d353 100644 --- a/cmd/pasty/command_delete.go +++ b/internal/consolecommands/cmd_delete.go @@ -1,11 +1,11 @@ -package main +package consolecommands import ( "context" "fmt" ) -func (router *consoleCommandRouter) Delete(args []string) { +func (router *Router) Delete(args []string) { if len(args) == 0 { fmt.Println("Expected 1 argument.") return diff --git a/cmd/pasty/command_set_modification_token.go b/internal/consolecommands/cmd_set_modification_token.go similarity index 90% rename from cmd/pasty/command_set_modification_token.go rename to internal/consolecommands/cmd_set_modification_token.go index c99c2b0..a6ac2b4 100644 --- a/cmd/pasty/command_set_modification_token.go +++ b/internal/consolecommands/cmd_set_modification_token.go @@ -1,11 +1,11 @@ -package main +package consolecommands import ( "context" "fmt" ) -func (router *consoleCommandRouter) SetModificationToken(args []string) { +func (router *Router) SetModificationToken(args []string) { if len(args) < 2 { fmt.Println("Expected 2 arguments.") return diff --git a/cmd/pasty/command_router.go b/internal/consolecommands/router.go similarity index 94% rename from cmd/pasty/command_router.go rename to internal/consolecommands/router.go index 4f6bdc9..1c9ec90 100644 --- a/cmd/pasty/command_router.go +++ b/internal/consolecommands/router.go @@ -1,4 +1,4 @@ -package main +package consolecommands import ( "bufio" @@ -14,12 +14,12 @@ import ( var whitespaceRegex = regexp.MustCompile("\\s+") -type consoleCommandRouter struct { +type Router struct { Config *config.Config Storage storage.Driver } -func (router *consoleCommandRouter) Listen() { +func (router *Router) Listen() { reader := bufio.NewReader(os.Stdin) for { fmt.Print("> ")