You've already forked hastebin-ansi
mirror of
https://github.com/armbian/hastebin-ansi.git
synced 2026-01-06 12:30:55 -08:00
refactor: migrate the project to golang
This commit is contained in:
committed by
M. Efe Çetin
parent
cf112b8eb7
commit
b283b1568f
132
cmd/main.go
Normal file
132
cmd/main.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/armbian/ansi-hastebin/config"
|
||||
"github.com/armbian/ansi-hastebin/handler"
|
||||
"github.com/armbian/ansi-hastebin/keygenerator"
|
||||
"github.com/armbian/ansi-hastebin/storage"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Creater router instance
|
||||
r := chi.NewRouter()
|
||||
|
||||
// Add several middlewares
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
// Check if config argument sent
|
||||
var configLocation string
|
||||
flag.StringVar(&configLocation, "config", "", "Pass config yaml")
|
||||
flag.Parse()
|
||||
|
||||
// Parse config fields
|
||||
cfg := config.NewConfig(configLocation)
|
||||
|
||||
var pasteStorage storage.Storage
|
||||
switch cfg.Storage.Type {
|
||||
case "file":
|
||||
pasteStorage = storage.NewFileStorage(cfg.Storage.FilePath, cfg.Expiration)
|
||||
case "redis":
|
||||
pasteStorage = storage.NewRedisStorage(cfg.Storage.Host, cfg.Storage.Port, cfg.Storage.Username, cfg.Storage.Password, cfg.Expiration)
|
||||
case "memcached":
|
||||
pasteStorage = storage.NewMemcachedStorage(cfg.Storage.Host, cfg.Storage.Port, int(cfg.Expiration))
|
||||
case "mongodb":
|
||||
pasteStorage = storage.NewMongoDBStorage(cfg.Storage.Host, cfg.Storage.Port, cfg.Storage.Username, cfg.Storage.Password, cfg.Storage.Database, cfg.Expiration)
|
||||
case "postgres":
|
||||
pasteStorage = storage.NewPostgresStorage(cfg.Storage.Host, cfg.Storage.Port, cfg.Storage.Username, cfg.Storage.Password, cfg.Storage.Database, int(cfg.Expiration))
|
||||
case "s3":
|
||||
pasteStorage = storage.NewS3Storage(cfg.Storage.Host, cfg.Storage.Port, cfg.Storage.Username, cfg.Storage.Password, cfg.Storage.AWSRegion, cfg.Storage.Bucket)
|
||||
default:
|
||||
logrus.Fatalf("Unknown storage type: %s", cfg.Storage.Type)
|
||||
return
|
||||
}
|
||||
|
||||
// Set static documents from config
|
||||
for _, doc := range cfg.Documents {
|
||||
file, err := os.OpenFile(doc.Path, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithField("path", doc.Path).Fatal("Failed to open document")
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithField("path", doc.Path).Fatal("Failed to read document")
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
pasteStorage.Set(doc.Key, string(content), false)
|
||||
}
|
||||
|
||||
var keyGenerator keygenerator.KeyGenerator
|
||||
|
||||
switch cfg.KeyGenerator {
|
||||
case "random":
|
||||
keyGenerator = keygenerator.NewRandomKeyGenerator(cfg.KeySpace)
|
||||
case "phonetic":
|
||||
keyGenerator = keygenerator.NewPhoneticKeyGenerator()
|
||||
default:
|
||||
logrus.Fatalf("Unknown key generator: %s", cfg.KeyGenerator)
|
||||
return
|
||||
}
|
||||
|
||||
// Add document handler
|
||||
document_handler := handler.NewDocumentHandler(cfg.KeyLength, cfg.MaxLength, pasteStorage, keyGenerator)
|
||||
|
||||
// Add prometheus metrics
|
||||
r.Get("/metrics", promhttp.Handler().ServeHTTP)
|
||||
|
||||
// Add document routes
|
||||
r.Get("/raw/{id}", document_handler.HandleRawGet)
|
||||
r.Head("/raw/{id}", document_handler.HandleRawGet)
|
||||
|
||||
r.Post("/log", document_handler.HandlePutLog)
|
||||
r.Put("/log", document_handler.HandlePutLog)
|
||||
|
||||
r.Post("/documents", document_handler.HandlePost)
|
||||
|
||||
r.Get("/documents/{id}", document_handler.HandleGet)
|
||||
r.Head("/documents/{id}", document_handler.HandleGet)
|
||||
|
||||
static := os.DirFS("static")
|
||||
r.Get("/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
if file, err := static.Open(id); err == nil {
|
||||
defer file.Close()
|
||||
io.Copy(w, file)
|
||||
return
|
||||
}
|
||||
|
||||
index, err := static.Open("index.html")
|
||||
if err != nil {
|
||||
http.Error(w, "Not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
defer index.Close()
|
||||
|
||||
io.Copy(w, index)
|
||||
})
|
||||
|
||||
fileServer := http.StripPrefix("/", http.FileServer(http.FS(static)))
|
||||
r.Get("/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
fileServer.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
if err := http.ListenAndServe(cfg.Host+":"+strconv.Itoa(cfg.Port), r); err != nil {
|
||||
fmt.Printf("Failed to start server: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
21
config.yaml
Normal file
21
config.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
host: "0.0.0.0"
|
||||
port: 7777
|
||||
key_length: 10
|
||||
max_length: 4000000
|
||||
static_max_age: 3
|
||||
expiration: 0
|
||||
recompress_static_assets: false
|
||||
key_generator: "phonetic"
|
||||
|
||||
storage:
|
||||
type: "file"
|
||||
file_path: "./test"
|
||||
|
||||
documents:
|
||||
- key: "about.md"
|
||||
path: "./about.md"
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
type: "text"
|
||||
colorize: true
|
||||
301
config/config.go
Normal file
301
config/config.go
Normal file
@@ -0,0 +1,301 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type LoggingConfig struct {
|
||||
Level string `yaml:"level"`
|
||||
Type string `yaml:"type"`
|
||||
Colorize bool `yaml:"colorize"`
|
||||
}
|
||||
|
||||
type StorageConfig struct {
|
||||
// Type is the storage backend to use
|
||||
// Available storage backends are: "redis", "file", "memcached", "mongodb", "s3", "postgres"
|
||||
Type string `yaml:"type"`
|
||||
|
||||
// Host is the hostname or IP address of the storage backend
|
||||
Host string `yaml:"host"`
|
||||
|
||||
// Port is the port of the storage backend
|
||||
Port int `yaml:"port"`
|
||||
|
||||
// Username is the username to use for the storage backend
|
||||
Username string `yaml:"username"`
|
||||
|
||||
// Password is the password to use for the storage backend
|
||||
Password string `yaml:"password"`
|
||||
|
||||
// Database is the database to use for the storage backend
|
||||
Database string `yaml:"database"`
|
||||
|
||||
// Bucket is the bucket to use for the storage backend
|
||||
Bucket string `yaml:"bucket"`
|
||||
|
||||
// AWSRegion is the AWS region to use for the storage backend
|
||||
// This property is only used for the "s3" storage backend
|
||||
AWSRegion string `yaml:"aws_region"`
|
||||
|
||||
// FilePath is the file path to use for the "file" storage backend
|
||||
// This property is only used for the "file" storage backend
|
||||
FilePath string `yaml:"file_path"`
|
||||
}
|
||||
|
||||
type DocumentConfig struct {
|
||||
// Key is the key of the document
|
||||
Key string `yaml:"key"`
|
||||
|
||||
// Path is the path of the document which is going to be read
|
||||
Path string `yaml:"path"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// Host is the hostname or IP address to bind to
|
||||
Host string `yaml:"host"`
|
||||
|
||||
// Port is the port to bind to
|
||||
Port int `yaml:"port"`
|
||||
|
||||
// KeyLength is the length of the key to generate which is used for storage key
|
||||
KeyLength int `yaml:"key_length"`
|
||||
|
||||
// KeySpace is the key space to use for the key generator
|
||||
// This property is only used for the "random" key generator
|
||||
KeySpace string `yaml:"key_space"`
|
||||
|
||||
// MaxLength is the maximum length of the paste
|
||||
MaxLength int `yaml:"max_length"`
|
||||
|
||||
// StaticMaxAge is the maximum age of static assets
|
||||
StaticMaxAge int `yaml:"static_max_age"`
|
||||
|
||||
// Expiration is the maximum lifetime of paste entry
|
||||
// 0 means there will be no expiration.
|
||||
// "file" and "s3" storages don't support expiration control.
|
||||
Expiration time.Duration `yaml:"expiration"`
|
||||
|
||||
// RecompressStaticAssets is a flag to recompress static assets by default
|
||||
RecompressStaticAssets bool `yaml:"recompress_static_assets"`
|
||||
|
||||
// KeyGenerator is the key generator to use
|
||||
// Available key generators are: "random", "phonetic"
|
||||
KeyGenerator string `yaml:"key_generator"`
|
||||
|
||||
// Storage is the storage backend to use
|
||||
// Available storage backends are: "redis", "file", "memcached", "mongodb", "s3", "postgres"
|
||||
Storage StorageConfig `yaml:"storage"`
|
||||
|
||||
// Logging is the logging configuration
|
||||
Logging LoggingConfig `yaml:"logging"`
|
||||
|
||||
// Documents is the list of documents to load statically
|
||||
Documents []DocumentConfig `yaml:"documents"`
|
||||
}
|
||||
|
||||
var DefaultConfig = &Config{
|
||||
Host: "0.0.0.0",
|
||||
Port: 7777,
|
||||
KeyLength: 10,
|
||||
MaxLength: 4000000,
|
||||
StaticMaxAge: 3,
|
||||
RecompressStaticAssets: false,
|
||||
KeyGenerator: "phonetic",
|
||||
Storage: StorageConfig{
|
||||
Type: "file",
|
||||
FilePath: "data",
|
||||
},
|
||||
Logging: LoggingConfig{
|
||||
Level: "info",
|
||||
Type: "text",
|
||||
},
|
||||
Documents: []DocumentConfig{
|
||||
{
|
||||
Key: "about",
|
||||
Path: "about.md",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewConfig creates a new Config instance
|
||||
func NewConfig(configFile string) *Config {
|
||||
cfg := &Config{}
|
||||
|
||||
// Read the configuration file
|
||||
data, err := os.ReadFile(configFile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
logrus.WithError(err).Fatal("Failed to read configuration file")
|
||||
}
|
||||
|
||||
// Unmarshal the configuration file
|
||||
if err := yaml.Unmarshal(data, cfg); err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to unmarshal configuration file")
|
||||
}
|
||||
|
||||
// Override with environment variables
|
||||
if host := os.Getenv("HOST"); host != "" {
|
||||
cfg.Host = host
|
||||
}
|
||||
|
||||
if port := os.Getenv("PORT"); port != "" {
|
||||
portInt, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to parse PORT environment variable")
|
||||
}
|
||||
cfg.Port = portInt
|
||||
}
|
||||
|
||||
if keyLength := os.Getenv("KEY_LENGTH"); keyLength != "" {
|
||||
keyLengthInt, err := strconv.Atoi(keyLength)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to parse KEY_LENGTH environment variable")
|
||||
}
|
||||
cfg.KeyLength = keyLengthInt
|
||||
}
|
||||
|
||||
if maxLength := os.Getenv("MAX_LENGTH"); maxLength != "" {
|
||||
maxLengthInt, err := strconv.Atoi(maxLength)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to parse MAX_LENGTH environment variable")
|
||||
}
|
||||
cfg.MaxLength = maxLengthInt
|
||||
}
|
||||
|
||||
if staticMaxAge := os.Getenv("STATIC_MAX_AGE"); staticMaxAge != "" {
|
||||
staticMaxAgeInt, err := strconv.Atoi(staticMaxAge)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to parse STATIC_MAX_AGE environment variable")
|
||||
}
|
||||
cfg.StaticMaxAge = staticMaxAgeInt
|
||||
}
|
||||
|
||||
if recompressStaticAssets := os.Getenv("RECOMPRESS_STATIC_ASSETS"); recompressStaticAssets != "" {
|
||||
recompressStaticAssetsBool, err := strconv.ParseBool(recompressStaticAssets)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to parse RECOMPRESS_STATIC_ASSETS environment variable")
|
||||
}
|
||||
cfg.RecompressStaticAssets = recompressStaticAssetsBool
|
||||
}
|
||||
|
||||
if keyGenerator := os.Getenv("KEY_GENERATOR"); keyGenerator != "" {
|
||||
cfg.KeyGenerator = keyGenerator
|
||||
}
|
||||
|
||||
if storageType := os.Getenv("STORAGE_TYPE"); storageType != "" {
|
||||
cfg.Storage.Type = storageType
|
||||
}
|
||||
|
||||
if storageHost := os.Getenv("STORAGE_HOST"); storageHost != "" {
|
||||
cfg.Storage.Host = storageHost
|
||||
}
|
||||
|
||||
if storagePort := os.Getenv("STORAGE_PORT"); storagePort != "" {
|
||||
storagePortInt, err := strconv.Atoi(storagePort)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to parse STORAGE_PORT environment variable")
|
||||
}
|
||||
cfg.Storage.Port = storagePortInt
|
||||
}
|
||||
|
||||
if storageUsername := os.Getenv("STORAGE_USERNAME"); storageUsername != "" {
|
||||
cfg.Storage.Username = storageUsername
|
||||
}
|
||||
|
||||
if storagePassword := os.Getenv("STORAGE_PASSWORD"); storagePassword != "" {
|
||||
cfg.Storage.Password = storagePassword
|
||||
}
|
||||
|
||||
if storageDatabase := os.Getenv("STORAGE_DATABASE"); storageDatabase != "" {
|
||||
cfg.Storage.Database = storageDatabase
|
||||
}
|
||||
|
||||
if storageBucket := os.Getenv("STORAGE_BUCKET"); storageBucket != "" {
|
||||
cfg.Storage.Bucket = storageBucket
|
||||
}
|
||||
|
||||
if storageAWSRegion := os.Getenv("STORAGE_AWS_REGION"); storageAWSRegion != "" {
|
||||
cfg.Storage.AWSRegion = storageAWSRegion
|
||||
}
|
||||
|
||||
if storageFilePath := os.Getenv("STORAGE_FILE_PATH"); storageFilePath != "" {
|
||||
cfg.Storage.FilePath = storageFilePath
|
||||
}
|
||||
|
||||
if loggingLevel := os.Getenv("LOGGING_LEVEL"); loggingLevel != "" {
|
||||
cfg.Logging.Level = loggingLevel
|
||||
}
|
||||
|
||||
if loggingType := os.Getenv("LOGGING_TYPE"); loggingType != "" {
|
||||
cfg.Logging.Type = loggingType
|
||||
}
|
||||
|
||||
if loggingColorize := os.Getenv("LOGGING_COLORIZE"); loggingColorize != "" {
|
||||
loggingColorizeBool, err := strconv.ParseBool(loggingColorize)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to parse LOGGING_COLORIZE environment variable")
|
||||
}
|
||||
cfg.Logging.Colorize = loggingColorizeBool
|
||||
}
|
||||
|
||||
// Walk environment variables for documents
|
||||
for _, env := range os.Environ() {
|
||||
if len(env) > 10 && env[:10] == "DOCUMENTS_" {
|
||||
parts := strings.Split(env[10:], "=")
|
||||
if len(parts) == 2 {
|
||||
cfg.Documents = append(cfg.Documents, DocumentConfig{
|
||||
Key: parts[0],
|
||||
Path: parts[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply default values to the configuration
|
||||
if cfg.Host == "" {
|
||||
cfg.Host = DefaultConfig.Host
|
||||
}
|
||||
|
||||
if cfg.Port == 0 {
|
||||
cfg.Port = DefaultConfig.Port
|
||||
}
|
||||
|
||||
if cfg.KeyLength == 0 {
|
||||
cfg.KeyLength = DefaultConfig.KeyLength
|
||||
}
|
||||
|
||||
if cfg.MaxLength == 0 {
|
||||
cfg.MaxLength = DefaultConfig.MaxLength
|
||||
}
|
||||
|
||||
if cfg.StaticMaxAge == 0 {
|
||||
cfg.StaticMaxAge = DefaultConfig.StaticMaxAge
|
||||
}
|
||||
|
||||
if cfg.KeyGenerator == "" {
|
||||
cfg.KeyGenerator = DefaultConfig.KeyGenerator
|
||||
}
|
||||
|
||||
if cfg.Storage.Type == "" {
|
||||
cfg.Storage.Type = DefaultConfig.Storage.Type
|
||||
}
|
||||
|
||||
if cfg.Storage.FilePath == "" {
|
||||
cfg.Storage.FilePath = DefaultConfig.Storage.FilePath
|
||||
}
|
||||
|
||||
if cfg.Logging.Level == "" {
|
||||
cfg.Logging.Level = DefaultConfig.Logging.Level
|
||||
}
|
||||
|
||||
if cfg.Logging.Type == "" {
|
||||
cfg.Logging.Type = DefaultConfig.Logging.Type
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
80
config/config_test.go
Normal file
80
config/config_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewConfig_DefaultValues(t *testing.T) {
|
||||
cfg := NewConfig("nonexistent.yaml") // Should use defaults since file doesn't exist
|
||||
|
||||
assert.Equal(t, "0.0.0.0", cfg.Host)
|
||||
assert.Equal(t, 7777, cfg.Port)
|
||||
assert.Equal(t, 10, cfg.KeyLength)
|
||||
assert.Equal(t, "phonetic", cfg.KeyGenerator)
|
||||
assert.Equal(t, "file", cfg.Storage.Type)
|
||||
assert.Equal(t, "data", cfg.Storage.FilePath)
|
||||
assert.Equal(t, "info", cfg.Logging.Level)
|
||||
assert.Equal(t, "text", cfg.Logging.Type)
|
||||
}
|
||||
|
||||
func TestNewConfig_OverrideWithEnvVars(t *testing.T) {
|
||||
os.Setenv("HOST", "127.0.0.1")
|
||||
os.Setenv("PORT", "8080")
|
||||
os.Setenv("KEY_LENGTH", "15")
|
||||
os.Setenv("MAX_LENGTH", "5000000")
|
||||
os.Setenv("STORAGE_TYPE", "redis")
|
||||
os.Setenv("STORAGE_HOST", "localhost")
|
||||
os.Setenv("STORAGE_PORT", "6379")
|
||||
os.Setenv("LOGGING_LEVEL", "debug")
|
||||
|
||||
defer os.Clearenv()
|
||||
|
||||
cfg := NewConfig("nonexistent.yaml") // Load with environment variables
|
||||
|
||||
assert.Equal(t, "127.0.0.1", cfg.Host)
|
||||
assert.Equal(t, 8080, cfg.Port)
|
||||
assert.Equal(t, 15, cfg.KeyLength)
|
||||
assert.Equal(t, 5000000, cfg.MaxLength)
|
||||
assert.Equal(t, "redis", cfg.Storage.Type)
|
||||
assert.Equal(t, "localhost", cfg.Storage.Host)
|
||||
assert.Equal(t, 6379, cfg.Storage.Port)
|
||||
assert.Equal(t, "debug", cfg.Logging.Level)
|
||||
}
|
||||
|
||||
func TestNewConfig_LoadFromYAML(t *testing.T) {
|
||||
yamlContent := `
|
||||
host: "192.168.1.1"
|
||||
port: 9090
|
||||
key_length: 20
|
||||
storage:
|
||||
type: "mongodb"
|
||||
host: "mongo.example.com"
|
||||
port: 27017
|
||||
logging:
|
||||
level: "warn"
|
||||
type: "json"
|
||||
`
|
||||
|
||||
// Write to a temporary file
|
||||
tmpFile, err := os.CreateTemp("", "config_test_*.yaml")
|
||||
assert.NoError(t, err)
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
_, err = tmpFile.Write([]byte(yamlContent))
|
||||
assert.NoError(t, err)
|
||||
tmpFile.Close()
|
||||
|
||||
cfg := NewConfig(tmpFile.Name()) // Load config from YAML file
|
||||
|
||||
assert.Equal(t, "192.168.1.1", cfg.Host)
|
||||
assert.Equal(t, 9090, cfg.Port)
|
||||
assert.Equal(t, 20, cfg.KeyLength)
|
||||
assert.Equal(t, "mongodb", cfg.Storage.Type)
|
||||
assert.Equal(t, "mongo.example.com", cfg.Storage.Host)
|
||||
assert.Equal(t, 27017, cfg.Storage.Port)
|
||||
assert.Equal(t, "warn", cfg.Logging.Level)
|
||||
assert.Equal(t, "json", cfg.Logging.Type)
|
||||
}
|
||||
102
go.mod
Normal file
102
go.mod
Normal file
@@ -0,0 +1,102 @@
|
||||
module github.com/armbian/ansi-hastebin
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.9
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.62
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.66
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/jackc/pgx/v5 v5.7.2
|
||||
github.com/prometheus/client_golang v1.21.1
|
||||
github.com/redis/go-redis/v9 v9.7.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/testcontainers/testcontainers-go v0.35.0
|
||||
github.com/testcontainers/testcontainers-go/modules/minio v0.35.0
|
||||
go.mongodb.org/mongo-driver v1.17.3
|
||||
go.mongodb.org/mongo-driver/v2 v2.1.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
|
||||
github.com/aws/smithy-go v1.22.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/containerd v1.7.18 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker v27.1.1+incompatible // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/user v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.24.2 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
||||
github.com/tklauser/numcpus v0.7.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
)
|
||||
326
go.sum
Normal file
326
go.sum
Normal file
@@ -0,0 +1,326 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.9 h1:Kg+fAYNaJeGXp1vmjtidss8O2uXIsXwaRqsQJKXVr+0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.9/go.mod h1:oU3jj2O53kgOU4TXq/yipt6ryiooYjlkqqVaZk7gY/U=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.66 h1:MTLivtC3s89de7Fe3P8rzML/8XPNRfuyJhlRTsCEt0k=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.66/go.mod h1:NAuQ2s6gaFEsuTIb2+P5t6amB1w5MhvJFxppoezGWH0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
|
||||
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao=
|
||||
github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
||||
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
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/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
|
||||
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.68 h1:hTqSIfLlpXaKuNy4baAp4Jjy2sqZEN9hRxD0M4aOfrQ=
|
||||
github.com/minio/minio-go/v7 v7.0.68/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
||||
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
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.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc=
|
||||
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y=
|
||||
github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo=
|
||||
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=
|
||||
github.com/testcontainers/testcontainers-go/modules/minio v0.35.0 h1:oJMrfB0hIABClRsJrVJ43zTEsCVk0JTN7RdTz9r+tk4=
|
||||
github.com/testcontainers/testcontainers-go/modules/minio v0.35.0/go.mod h1:Q7gSllC2zi78e2OF6Gwn+DXyqbxdbt6PAuaZdIPh3DQ=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
|
||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
||||
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.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.mongodb.org/mongo-driver/v2 v2.1.0 h1:/ELnVNjmfUKDsoBisXxuJL0noR9CfeUIrP7Yt3R+egg=
|
||||
go.mongodb.org/mongo-driver/v2 v2.1.0/go.mod h1:AWiLRShSrk5RHQS3AEn3RL19rqOzVq49MCpWQ3x/huI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
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-20201020160332-67f06af15bc9/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.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/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-20210616094352-59db8d763f22/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
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/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
|
||||
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
133
handler/document.go
Normal file
133
handler/document.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/armbian/ansi-hastebin/keygenerator"
|
||||
"github.com/armbian/ansi-hastebin/storage"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// DocumentHandler manages document operations
|
||||
type DocumentHandler struct {
|
||||
KeyLength int
|
||||
MaxLength int
|
||||
Store storage.Storage
|
||||
KeyGenerator keygenerator.KeyGenerator
|
||||
}
|
||||
|
||||
func NewDocumentHandler(keyLength, maxLength int, store storage.Storage, keyGenerator keygenerator.KeyGenerator) *DocumentHandler {
|
||||
keyLength = 10
|
||||
return &DocumentHandler{
|
||||
KeyLength: keyLength,
|
||||
MaxLength: maxLength,
|
||||
Store: store,
|
||||
KeyGenerator: keyGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle retrieving a document
|
||||
func (h *DocumentHandler) HandleGet(w http.ResponseWriter, r *http.Request) {
|
||||
key := strings.Split(chi.URLParam(r, "id"), ".")[0]
|
||||
data, err := h.Store.Get(key, false)
|
||||
|
||||
if data != "" && err == nil {
|
||||
logrus.WithField("key", key).Info("Retrieved document")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if r.Method == http.MethodHead {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(map[string]string{"data": data, "key": key})
|
||||
} else {
|
||||
logrus.WithField("key", key).Warn("Document not found")
|
||||
http.Error(w, `{"message": "Document not found."}`, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle retrieving raw document
|
||||
func (h *DocumentHandler) HandleRawGet(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
key := strings.Split(chi.URLParam(r, "id"), ".")[0]
|
||||
data, err := h.Store.Get(key, false)
|
||||
if data != "" && err == nil {
|
||||
logrus.WithField("key", key).Info("Retrieved raw document")
|
||||
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
|
||||
if r.Method == http.MethodHead {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
w.Write([]byte(data))
|
||||
} else {
|
||||
logrus.WithField("key", key).Warn("Raw document not found")
|
||||
http.Error(w, `{"message": "Document not found."}`, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle adding a new document (POST)
|
||||
func (h *DocumentHandler) HandlePost(w http.ResponseWriter, r *http.Request) {
|
||||
var buffer strings.Builder
|
||||
if err := h.readBody(r, &buffer); err != nil {
|
||||
http.Error(w, `{"message": "Error reading request body."}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if h.MaxLength > 0 && buffer.Len() > h.MaxLength {
|
||||
logrus.Warn("Document exceeds max length")
|
||||
http.Error(w, `{"message": "Document exceeds maximum length."}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
key := h.KeyGenerator.Generate(h.KeyLength)
|
||||
fmt.Println(key)
|
||||
h.Store.Set(key, buffer.String(), false)
|
||||
|
||||
logrus.WithField("key", key).Info("Added document")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"key": key})
|
||||
}
|
||||
|
||||
// Handle PUT request that returns a direct link
|
||||
func (h *DocumentHandler) HandlePutLog(w http.ResponseWriter, r *http.Request) {
|
||||
var buffer strings.Builder
|
||||
if err := h.readBody(r, &buffer); err != nil {
|
||||
http.Error(w, `{"message": "Error reading request body."}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if h.MaxLength > 0 && buffer.Len() > h.MaxLength {
|
||||
logrus.Warn("Document exceeds max length")
|
||||
http.Error(w, `{"message": "Document exceeds maximum length."}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
key := h.KeyGenerator.Generate(h.KeyLength)
|
||||
h.Store.Set(key, buffer.String(), false)
|
||||
|
||||
logrus.WithField("key", key).Info("Added document with log link")
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
fmt.Fprintf(w, "\nhttps://%s/%s\n\n", r.Host, key)
|
||||
}
|
||||
|
||||
// Reads body from the request
|
||||
func (h *DocumentHandler) readBody(r *http.Request, buffer *strings.Builder) error {
|
||||
if strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") {
|
||||
r.ParseMultipartForm(32 << 20)
|
||||
if val := r.FormValue("data"); val != "" {
|
||||
buffer.WriteString(val)
|
||||
}
|
||||
} else {
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
logrus.Error("Connection error: ", err)
|
||||
return err
|
||||
}
|
||||
buffer.WriteString(string(data))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
5
keygenerator/key_generator.go
Normal file
5
keygenerator/key_generator.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package keygenerator
|
||||
|
||||
type KeyGenerator interface {
|
||||
Generate(length int) string
|
||||
}
|
||||
45
keygenerator/phonetic.go
Normal file
45
keygenerator/phonetic.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package keygenerator
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PhoneticKeyGenerator struct {
|
||||
}
|
||||
|
||||
var vowels string = "aeiou"
|
||||
var consonants string = "bcdfghjklmnpqrstvwxyz"
|
||||
|
||||
var twoBig = big.NewInt(2)
|
||||
|
||||
func randomFromStr(str string) byte {
|
||||
maxRandomIndex := big.NewInt(int64(len(str)))
|
||||
index, _ := rand.Int(rand.Reader, maxRandomIndex)
|
||||
return str[index.Int64()]
|
||||
}
|
||||
|
||||
func NewPhoneticKeyGenerator() *PhoneticKeyGenerator {
|
||||
return &PhoneticKeyGenerator{}
|
||||
}
|
||||
|
||||
func (p *PhoneticKeyGenerator) Generate(length int) string {
|
||||
var out strings.Builder
|
||||
defer out.Reset()
|
||||
|
||||
start, err := rand.Int(rand.Reader, twoBig)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
if i%2 == int(start.Int64()) {
|
||||
out.WriteByte(randomFromStr(consonants))
|
||||
} else {
|
||||
out.WriteByte(randomFromStr(vowels))
|
||||
}
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
55
keygenerator/phonetic_test.go
Normal file
55
keygenerator/phonetic_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package keygenerator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewPhoneticKeyGenerator(t *testing.T) {
|
||||
kg := NewPhoneticKeyGenerator()
|
||||
require.NotNil(t, kg)
|
||||
}
|
||||
|
||||
func TestPhoneticGenerate(t *testing.T) {
|
||||
kg := NewPhoneticKeyGenerator()
|
||||
|
||||
t.Run("Generate with valid length", func(t *testing.T) {
|
||||
key := kg.Generate(10)
|
||||
fmt.Print(key)
|
||||
require.Len(t, key, 10)
|
||||
for _, ch := range key {
|
||||
require.Contains(t, vowels+consonants, string(ch))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Generate with zero length", func(t *testing.T) {
|
||||
key := kg.Generate(0)
|
||||
require.Empty(t, key)
|
||||
})
|
||||
|
||||
t.Run("Generate with large length", func(t *testing.T) {
|
||||
key := kg.Generate(1000)
|
||||
require.Len(t, key, 1000)
|
||||
})
|
||||
|
||||
t.Run("Check different keys", func(t *testing.T) {
|
||||
var oldKey string
|
||||
|
||||
for i := 0; i < 200; i++ {
|
||||
key := kg.Generate(50)
|
||||
require.NotEqual(t, key, oldKey)
|
||||
|
||||
oldKey = key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPhoneticFromStr(t *testing.T) {
|
||||
t.Run("RandomFromStr", func(t *testing.T) {
|
||||
str := "abc"
|
||||
ch := randomFromStr(str)
|
||||
require.Contains(t, str, string(ch))
|
||||
})
|
||||
}
|
||||
37
keygenerator/random.go
Normal file
37
keygenerator/random.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package keygenerator
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RandomKeyGenerator struct {
|
||||
keyspace string
|
||||
}
|
||||
|
||||
func NewRandomKeyGenerator(keyspace string) *RandomKeyGenerator {
|
||||
if keyspace == "" {
|
||||
keyspace = "abcdefghijklmnopqrstuvwxyz"
|
||||
}
|
||||
return &RandomKeyGenerator{
|
||||
keyspace: keyspace,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RandomKeyGenerator) Generate(length int) string {
|
||||
var out strings.Builder
|
||||
defer out.Reset()
|
||||
|
||||
maxRandomIndex := big.NewInt(int64(len(r.keyspace)))
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
index, err := rand.Int(rand.Reader, maxRandomIndex)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out.WriteByte(r.keyspace[index.Int64()])
|
||||
}
|
||||
|
||||
return out.String()
|
||||
}
|
||||
50
keygenerator/random_test.go
Normal file
50
keygenerator/random_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package keygenerator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewRandomKeyGenerator(t *testing.T) {
|
||||
kg := NewRandomKeyGenerator("abc")
|
||||
require.Equal(t, "abc", kg.keyspace)
|
||||
|
||||
defaultKg := NewRandomKeyGenerator("")
|
||||
require.Equal(t, "abcdefghijklmnopqrstuvwxyz", defaultKg.keyspace)
|
||||
}
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
kg := NewRandomKeyGenerator("abc")
|
||||
|
||||
t.Run("Generate with valid length", func(t *testing.T) {
|
||||
key := kg.Generate(10)
|
||||
fmt.Print(key)
|
||||
require.Len(t, key, 10)
|
||||
for _, ch := range key {
|
||||
require.Contains(t, "abc", string(ch))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Generate with zero length", func(t *testing.T) {
|
||||
key := kg.Generate(0)
|
||||
require.Empty(t, key)
|
||||
})
|
||||
|
||||
t.Run("Generate with large length", func(t *testing.T) {
|
||||
key := kg.Generate(1000)
|
||||
require.Len(t, key, 1000)
|
||||
})
|
||||
|
||||
t.Run("Check different keys", func(t *testing.T) {
|
||||
var oldKey string
|
||||
|
||||
for i := 0; i < 200; i++ {
|
||||
key := kg.Generate(50)
|
||||
require.NotEqual(t, key, oldKey)
|
||||
|
||||
oldKey = key
|
||||
}
|
||||
})
|
||||
}
|
||||
52
storage/file.go
Normal file
52
storage/file.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileStorage struct {
|
||||
path string
|
||||
}
|
||||
|
||||
var _ Storage = (*FileStorage)(nil)
|
||||
|
||||
func md5Hex(input string) string {
|
||||
sum := md5.Sum([]byte(input))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func NewFileStorage(path string, _ time.Duration) Storage {
|
||||
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
|
||||
os.Mkdir(path, 0700)
|
||||
}
|
||||
|
||||
return &FileStorage{path: path}
|
||||
}
|
||||
|
||||
func (fs *FileStorage) Set(key string, value string, skip_expiration bool) error {
|
||||
dst := filepath.Join(fs.path, md5Hex(key))
|
||||
|
||||
file, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = file.WriteString(value)
|
||||
return err
|
||||
}
|
||||
|
||||
func (fs *FileStorage) Get(key string, skip_expiration bool) (string, error) {
|
||||
dst := filepath.Join(fs.path, md5Hex(key))
|
||||
file, err := os.ReadFile(dst)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(file), nil
|
||||
}
|
||||
55
storage/file_test.go
Normal file
55
storage/file_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setupTempDir(t *testing.T) (string, func()) {
|
||||
dir, err := os.MkdirTemp("", "filestorage_test")
|
||||
require.NoError(t, err)
|
||||
cleanup := func() { os.RemoveAll(dir) }
|
||||
return dir, cleanup
|
||||
}
|
||||
|
||||
func TestFileStorage(t *testing.T) {
|
||||
dir, cleanup := setupTempDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
const expiration = 2 // seconds
|
||||
store := NewFileStorage(dir, expiration)
|
||||
|
||||
// Test Set
|
||||
err := store.Set("testKey", "testValue", false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test Get
|
||||
val, err := store.Get("testKey", false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "testValue", val)
|
||||
|
||||
_, err = store.Get("testKey", false)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.Get("testKey2", false)
|
||||
require.True(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func TestFileStorageSkipExpiration(t *testing.T) {
|
||||
dir, cleanup := setupTempDir(t)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
const expiration = 2 // seconds
|
||||
store := NewFileStorage(dir, expiration)
|
||||
|
||||
// Test Set
|
||||
err := store.Set("persistentKey", "persistentValue", true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test Get with skip_expiration
|
||||
val, err := store.Get("persistentKey", true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "persistentValue", val)
|
||||
}
|
||||
55
storage/memcached.go
Normal file
55
storage/memcached.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/bradfitz/gomemcache/memcache"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type MemcachedStorage struct {
|
||||
client *memcache.Client
|
||||
expiration int
|
||||
}
|
||||
|
||||
func NewMemcachedStorage(host string, port int, expiration int) *MemcachedStorage {
|
||||
client := memcache.New(host + ":" + strconv.Itoa(port))
|
||||
|
||||
// Check if connection is established
|
||||
if err := client.Ping(); err != nil {
|
||||
logrus.Fatalf("Failed to connect to Memcached: %v", err)
|
||||
}
|
||||
|
||||
return &MemcachedStorage{client: client, expiration: expiration}
|
||||
}
|
||||
|
||||
var _ Storage = (*MemcachedStorage)(nil)
|
||||
|
||||
func (s *MemcachedStorage) Set(key string, value string, skip_expiration bool) error {
|
||||
item := &memcache.Item{
|
||||
Key: key,
|
||||
Value: []byte(value),
|
||||
Expiration: int32(s.expiration),
|
||||
}
|
||||
if skip_expiration {
|
||||
item.Expiration = 0
|
||||
}
|
||||
return s.client.Set(item)
|
||||
}
|
||||
|
||||
func (s *MemcachedStorage) Get(key string, skip_expiration bool) (string, error) {
|
||||
item, err := s.client.Get(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !skip_expiration {
|
||||
s.client.Replace(&memcache.Item{
|
||||
Key: key,
|
||||
Value: item.Value,
|
||||
Expiration: int32(s.expiration),
|
||||
})
|
||||
}
|
||||
|
||||
return string(item.Value), nil
|
||||
}
|
||||
91
storage/memcached_test.go
Normal file
91
storage/memcached_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bradfitz/gomemcache/memcache"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
)
|
||||
|
||||
func setupMemcachedContainer(t *testing.T) (string, int, func()) {
|
||||
ctx := context.Background()
|
||||
memcachedContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: testcontainers.ContainerRequest{
|
||||
Image: "memcached:latest",
|
||||
ExposedPorts: []string{"11211/tcp"},
|
||||
WaitingFor: wait.ForListeningPort("11211/tcp"),
|
||||
},
|
||||
Started: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
endpoint, err := memcachedContainer.Endpoint(ctx, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
host := strings.Split(endpoint, ":")[0]
|
||||
port, err := memcachedContainer.MappedPort(ctx, "11211")
|
||||
require.NoError(t, err)
|
||||
|
||||
cleanup := func() {
|
||||
require.NoError(t, memcachedContainer.Terminate(ctx))
|
||||
}
|
||||
|
||||
return host, port.Int(), cleanup
|
||||
}
|
||||
|
||||
func TestMemcachedStorage(t *testing.T) {
|
||||
host, port, cleanup := setupMemcachedContainer(t)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
const expiration = 2 // seconds
|
||||
store := NewMemcachedStorage(host, port, expiration)
|
||||
|
||||
// Test Set
|
||||
err := store.Set("testKey", "testValue", false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test Get before expiration
|
||||
val, err := store.Get("testKey", false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "testValue", val)
|
||||
|
||||
// Test if expiration is updated after Get
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err = store.Get("testKey", false)
|
||||
require.NoError(t, err) // Should still exist
|
||||
|
||||
time.Sleep(1 * time.Second) // Should reset expiration
|
||||
_, err = store.Get("testKey", false)
|
||||
require.NoError(t, err) // Should still exist due to refresh
|
||||
|
||||
_, err = store.Get("testKey2", false)
|
||||
require.ErrorIs(t, memcache.ErrCacheMiss, err) // Should not exist
|
||||
}
|
||||
|
||||
func TestMemcachedStorageSkipExpiration(t *testing.T) {
|
||||
host, port, cleanup := setupMemcachedContainer(t)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
const expiration = 2 // seconds
|
||||
store := NewMemcachedStorage(host, port, expiration)
|
||||
|
||||
// Test Set
|
||||
err := store.Set("persistentKey", "persistentValue", true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test Get with skip_expiration
|
||||
val, err := store.Get("persistentKey", true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "persistentValue", val)
|
||||
|
||||
// Wait past expiration but skip expiration should still work
|
||||
time.Sleep(time.Duration(expiration+1) * time.Second)
|
||||
val, err = store.Get("persistentKey", true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "persistentValue", val)
|
||||
}
|
||||
144
storage/mongodb.go
Normal file
144
storage/mongodb.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
type MongoDBStorage struct {
|
||||
db *mongo.Database
|
||||
collection *mongo.Collection
|
||||
expiration time.Duration
|
||||
}
|
||||
|
||||
type item struct {
|
||||
ObjectID any `json:"_id,omitempty" bson:"_id,omitempty"`
|
||||
Key string `json:"key" bson:"key"`
|
||||
Value []byte `json:"value" bson:"value"`
|
||||
Expiration time.Time `json:"expiration,omitempty" bson:"expiration,omitempty"`
|
||||
}
|
||||
|
||||
func NewMongoDBStorage(host string, port int, username string, password string, database string, expiration time.Duration) *MongoDBStorage {
|
||||
ctx := context.Background()
|
||||
|
||||
dsn := "mongodb://"
|
||||
if username != "" {
|
||||
dsn += url.QueryEscape(username)
|
||||
}
|
||||
|
||||
if password != "" {
|
||||
dsn += ":" + url.QueryEscape(password)
|
||||
}
|
||||
|
||||
if username != "" || password != "" {
|
||||
dsn += "@"
|
||||
}
|
||||
|
||||
dsn += host + ":" + strconv.Itoa(port)
|
||||
|
||||
client, err := mongo.Connect(options.Client().ApplyURI(dsn))
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to connect to MongoDB: %v", err)
|
||||
}
|
||||
|
||||
// Check if connection is established
|
||||
if err := client.Ping(ctx, nil); err != nil {
|
||||
logrus.Fatalf("Failed to connect to MongoDB: %v", err)
|
||||
}
|
||||
|
||||
db := client.Database(database)
|
||||
|
||||
command := bson.M{"create": "entries"}
|
||||
var result bson.M
|
||||
if err := db.RunCommand(context.Background(), command).Decode(&result); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create collection if not exists
|
||||
collection := db.Collection("entries")
|
||||
|
||||
indexModel := mongo.IndexModel{
|
||||
Keys: bson.M{"expiration": 1},
|
||||
Options: options.Index().SetExpireAfterSeconds(0),
|
||||
}
|
||||
|
||||
if _, err := collection.Indexes().CreateOne(ctx, indexModel); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
keyIndexModel := mongo.IndexModel{
|
||||
Keys: bson.M{"key": 1},
|
||||
Options: options.Index().SetUnique(true),
|
||||
}
|
||||
|
||||
if _, err := collection.Indexes().CreateOne(ctx, keyIndexModel); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &MongoDBStorage{db: db, collection: collection, expiration: expiration}
|
||||
}
|
||||
|
||||
func (s *MongoDBStorage) Set(key string, value string, skip_expiration bool) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Convert value to []byte
|
||||
valueBytes := []byte(value)
|
||||
|
||||
expiration := time.Now().Add(s.expiration)
|
||||
if skip_expiration {
|
||||
expiration = time.Time{}
|
||||
}
|
||||
|
||||
// Create item
|
||||
i := item{
|
||||
Key: key,
|
||||
Value: valueBytes,
|
||||
Expiration: expiration,
|
||||
}
|
||||
|
||||
// Insert item
|
||||
if _, err := s.collection.InsertOne(ctx, i); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MongoDBStorage) Get(key string, skip_expiration bool) (string, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Find item
|
||||
filter := bson.M{"key": key}
|
||||
var i item
|
||||
if err := s.collection.FindOne(ctx, filter).Decode(&i); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !i.Expiration.IsZero() && i.Expiration.Unix() <= time.Now().Unix() {
|
||||
_, err := s.collection.DeleteOne(ctx, filter)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", ErrNotFound
|
||||
}
|
||||
|
||||
// Update expiration
|
||||
if !skip_expiration {
|
||||
i.Expiration = time.Now().Add(s.expiration)
|
||||
update := bson.M{"$set": bson.M{"expiration": i.Expiration}}
|
||||
if _, err := s.collection.UpdateOne(ctx, filter, update); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return string(i.Value), nil
|
||||
}
|
||||
100
storage/mongodb_test.go
Normal file
100
storage/mongodb_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
func setupMongoContainer(t *testing.T) (string, int, func()) {
|
||||
ctx := context.Background()
|
||||
mongoContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: testcontainers.ContainerRequest{
|
||||
Image: "mongo:latest",
|
||||
ExposedPorts: []string{"27017/tcp"},
|
||||
WaitingFor: wait.ForListeningPort("27017/tcp"),
|
||||
},
|
||||
Started: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get container host and port
|
||||
endpoint, err := mongoContainer.Endpoint(ctx, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
host := strings.Split(endpoint, ":")[0]
|
||||
port, err := mongoContainer.MappedPort(ctx, "27017")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Connect to MongoDB
|
||||
client, err := mongo.Connect(options.Client().ApplyURI("mongodb://" + endpoint))
|
||||
require.NoError(t, err)
|
||||
|
||||
db := client.Database("testdb")
|
||||
|
||||
cleanup := func() {
|
||||
require.NoError(t, db.Drop(ctx))
|
||||
require.NoError(t, client.Disconnect(ctx))
|
||||
require.NoError(t, mongoContainer.Terminate(ctx))
|
||||
}
|
||||
|
||||
return host, port.Int(), cleanup
|
||||
}
|
||||
|
||||
func TestMongoDBStorage(t *testing.T) {
|
||||
host, port, cleanup := setupMongoContainer(t)
|
||||
defer cleanup()
|
||||
|
||||
const expiration = 2 // seconds
|
||||
store := NewMongoDBStorage(host, port, "", "", "testdb", expiration*time.Second)
|
||||
|
||||
// Test Set
|
||||
err := store.Set("testKey", "testValue", false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test Get before expiration
|
||||
val, err := store.Get("testKey", false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "testValue", val)
|
||||
|
||||
// Test expiration mechanism
|
||||
time.Sleep(time.Duration(expiration+1) * time.Second)
|
||||
val, err = store.Get("testKey", false)
|
||||
require.Equal(t, "", val)
|
||||
require.ErrorIs(t, ErrNotFound, err) // Should return error because the key should be expired
|
||||
|
||||
// Test key not existing
|
||||
val, err = store.Get("testKey2", false)
|
||||
require.Equal(t, "", val)
|
||||
require.ErrorIs(t, mongo.ErrNoDocuments, err)
|
||||
}
|
||||
|
||||
func TestMongoDBStorageSkipExpiration(t *testing.T) {
|
||||
host, port, cleanup := setupMongoContainer(t)
|
||||
defer cleanup()
|
||||
|
||||
const expiration = 2 // seconds
|
||||
store := NewMongoDBStorage(host, port, "", "", "testdb", expiration*time.Second)
|
||||
|
||||
// Test Set
|
||||
err := store.Set("persistentKey", "persistentValue", true)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test Get with skip_expiration
|
||||
val, err := store.Get("persistentKey", true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "persistentValue", val)
|
||||
|
||||
// Wait past expiration but skip expiration should still work
|
||||
time.Sleep(time.Duration(expiration+1) * time.Second)
|
||||
val, err = store.Get("persistentKey", true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "persistentValue", val)
|
||||
}
|
||||
88
storage/postgres.go
Normal file
88
storage/postgres.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const setSQLQuery = "INSERT INTO entries (key, value, expiration) VALUES ($1, $2, $3) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, expiration = EXCLUDED.expiration"
|
||||
const getSQLQuery = "SELECT id, value, expiration FROM entries WHERE key = $1"
|
||||
const deleteSQLQuery = "DELETE FROM entries WHERE id = $1"
|
||||
const updateSQLQuery = "UPDATE entries SET expiration = $1 WHERE id = $2"
|
||||
|
||||
type PostgresStorage struct {
|
||||
pool *pgxpool.Pool
|
||||
expiration int
|
||||
}
|
||||
|
||||
var _ Storage = (*PostgresStorage)(nil)
|
||||
|
||||
func NewPostgresStorage(host string, port int, username string, passowrd string, database string, expiration int) *PostgresStorage {
|
||||
dsn := "postgres://" + username + ":" + passowrd + "@" + host + ":" + strconv.Itoa(port) + "/" + database
|
||||
pool, err := pgxpool.New(context.Background(), dsn)
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to connect to PostgreSQL: %v", err)
|
||||
}
|
||||
|
||||
// Check if connection is established
|
||||
if err := pool.Ping(context.Background()); err != nil {
|
||||
logrus.Fatalf("Failed to connect to PostgreSQL: %v", err)
|
||||
}
|
||||
|
||||
// Create table if not exists
|
||||
_, err = pool.Exec(context.Background(), "CREATE TABLE IF NOT EXISTS entries (id SERIAL PRIMARY KEY, key VARCHAR(255) NOT NULL UNIQUE, value TEXT, expiration BIGINT)")
|
||||
if err != nil {
|
||||
logrus.Fatalf("Failed to create table: %v", err)
|
||||
}
|
||||
|
||||
return &PostgresStorage{pool: pool, expiration: expiration}
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) Set(key string, value string, skip_expiration bool) error {
|
||||
ctx := context.Background() // TODO: Add timeout control
|
||||
|
||||
now := time.Now()
|
||||
expiration := now.Add(time.Duration(s.expiration)).Unix()
|
||||
if skip_expiration {
|
||||
expiration = 0
|
||||
}
|
||||
|
||||
_, err := s.pool.Exec(ctx, setSQLQuery, key, value, expiration)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *PostgresStorage) Get(key string, skip_expiration bool) (string, error) {
|
||||
ctx := context.Background() // TODO: Add timeout control
|
||||
|
||||
var id int
|
||||
var value string
|
||||
var expiration int64
|
||||
|
||||
err := s.pool.QueryRow(ctx, getSQLQuery, key).Scan(&id, &value, &expiration)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Delete if expired
|
||||
if expiration != 0 && time.Now().Unix() > expiration {
|
||||
_, err = s.pool.Exec(ctx, deleteSQLQuery, id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Update expiration
|
||||
if !skip_expiration {
|
||||
_, err = s.pool.Exec(ctx, updateSQLQuery, time.Now().Add(time.Duration(s.expiration)).Unix(), id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
70
storage/postgres_test.go
Normal file
70
storage/postgres_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
)
|
||||
|
||||
func setupTestContainer(t *testing.T) (string, int, func()) {
|
||||
req := testcontainers.ContainerRequest{
|
||||
Image: "postgres:latest",
|
||||
Env: map[string]string{"POSTGRES_USER": "test", "POSTGRES_PASSWORD": "test", "POSTGRES_DB": "testdb"},
|
||||
ExposedPorts: []string{"5432/tcp"},
|
||||
WaitingFor: wait.ForListeningPort("5432/tcp"),
|
||||
}
|
||||
|
||||
container, err := testcontainers.GenericContainer(context.Background(), testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: req,
|
||||
Started: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
host, err := container.Host(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
port, err := container.MappedPort(context.Background(), "5432")
|
||||
require.NoError(t, err)
|
||||
|
||||
return host, port.Int(), func() {
|
||||
require.NoError(t, container.Terminate(context.Background()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostgresStorage(t *testing.T) {
|
||||
host, port, cleanup := setupTestContainer(t)
|
||||
defer cleanup()
|
||||
|
||||
store := NewPostgresStorage(host, port, "test", "test", "testdb", 2)
|
||||
|
||||
err := store.Set("key1", "value1", false)
|
||||
require.NoError(t, err)
|
||||
|
||||
val, err := store.Get("key1", false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "value1", val)
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
val, err = store.Get("key1", false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, val)
|
||||
|
||||
// Test with skip expiration
|
||||
err = store.Set("key1", "value1", false)
|
||||
require.NoError(t, err)
|
||||
|
||||
val, err = store.Get("key1", true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "value1", val)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
val, err = store.Get("key1", false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, val)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user