Initial commit

This commit is contained in:
2023-08-24 23:40:31 +03:00
commit 49c962e13c
32 changed files with 1360 additions and 0 deletions

43
.gitignore vendored Normal file
View File

@@ -0,0 +1,43 @@
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea/**/aws.xml
.idea/**/contentModel.xml
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
.idea/**/gradle.xml
.idea/**/libraries
cmake-build-*/
.idea/**/mongoSettings.xml
*.iws
out/
.idea_modules/
atlassian-ide-plugin.xml
.idea/replstate.xml
.idea/sonarlint/
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
.idea/httpRequests
.idea/caches/build_file_checksums.ser
*~
.fuse_hidden*
.directory
.Trash-*
.nfs*
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out
go.work

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

15
.idea/git_toolbox_prj.xml generated Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>

17
.idea/golinter.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoLinterSettings">
<option name="enabledLinters">
<list>
<option value="ineffassign" />
<option value="staticcheck" />
<option value="govet" />
<option value="errcheck" />
<option value="unused" />
<option value="gosimple" />
</list>
</option>
<option name="goLinterExe" value="$USER_HOME$/.local/bin/golangci-lint" />
<option name="linterSelected" value="true" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/protect_trans_info.iml" filepath="$PROJECT_DIR$/.idea/protect_trans_info.iml" />
</modules>
</component>
</project>

9
.idea/protect_trans_info.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

7
.idea/yamllint.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="YamllintSettings">
<enabled>true</enabled>
<binPath>/usr/bin/yamllint</binPath>
</component>
</project>

9
Makefile Normal file
View File

@@ -0,0 +1,9 @@
first_run:
docker compose up -d db
@sleep 2
docker compose up -d migrate
docker compose up seed
docker compose up service
start:
docker compose up service

46
README.md Normal file
View File

@@ -0,0 +1,46 @@
## Запуск
### Первый запуск:
```shell
make first_run
```
Команда запустит БД (PostgreSQL 15), Rest-сервис,
накатит миграцию в БД и заполнит базу рандомными данными
(2 миллиона записей).
### Повторный запуск сервиса:
```shell
make start
```
### Вызов метода проверки дубликатов:
```shell
curl -X GET --location "http://127.0.0.1:8001/124/432"
```
### Отдельно добавил выгрузку тестовых данных из БД:
```shell
curl -X GET --location "http://127.0.0.1:8001/list" > data.txt
```
### Запрос с гарантировано дублируемыми IP:
```shell
curl -X GET --location "http://127.0.0.1:8001/88888/99999"
```
## Комментарии к реализации
Есть тесты на основной методв репозитория (с использованием docker через
либу dockertest), на остальное тестов не делал. Добавил ещё бэнчмарк и fuzzy-тест,
но они работать будут только при запущенном в докере сервисе.
При запуске вся база подтягивается в локальный кэш. Кэш обновляется
по умолчанию каждую секунду (настраиваемо, подтягиваются только новые записи).
Был вариант ещё вместо обновления по тикеру сделать триггер в бд и через
notify/listen постгреса добавлять новые записи в кэш. Было бы эффективнее,
но на данном этапе выглядит как оверинженеринг.
Для продакшена не хватает метрик и алертов в сентри, но на это уже
времени тратить не хочется.

View File

@@ -0,0 +1,11 @@
package inmemorycache
type Cache map[uint64][]string
func (c Cache) Get(id uint64) []string {
return c[id]
}
func (c Cache) Append(id uint64, ip string) {
c[id] = append(c[id], ip)
}

View File

@@ -0,0 +1,69 @@
package application
import (
"context"
"fmt"
"sync"
"time"
"github.com/uptrace/bun"
"go.uber.org/zap"
"git.derfenix.pro/fenix/protect_trans_info/adapters/inmemorycache"
"git.derfenix.pro/fenix/protect_trans_info/application/repository"
"git.derfenix.pro/fenix/protect_trans_info/port/http"
)
func NewApplication(ctx context.Context, cfg Config, logger *zap.Logger) (*Application, error) {
db, err := repository.NewDB(cfg.DB)
if err != nil {
return nil, fmt.Errorf("new db: %w", err)
}
cache := make(inmemorycache.Cache)
repo, err := repository.NewConnLogs(ctx, db, cache, logger, cfg.UpdateInterval)
if err != nil {
return nil, fmt.Errorf("conn log repo: %w", err)
}
service, err := http.NewService(cfg.HTTPHost, cfg.HTTPPort, repo, logger)
if err != nil {
return nil, fmt.Errorf("new service: %w", err)
}
return &Application{
cfg: cfg,
service: service,
db: db,
}, nil
}
type Application struct {
cfg Config
service *http.Service
db *bun.DB
}
func (a *Application) Start(wg *sync.WaitGroup) {
wg.Add(1)
go func() {
defer wg.Done()
a.service.Start()
}()
}
func (a *Application) Stop(wg *sync.WaitGroup) {
wg.Add(1)
go func() {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
a.service.Stop(ctx)
}()
}

27
application/config.go Normal file
View File

@@ -0,0 +1,27 @@
package application
import (
"context"
"fmt"
"time"
"github.com/sethvargo/go-envconfig"
)
func NewConfig() (Config, error) {
cfg := Config{}
if err := envconfig.Process(context.Background(), &cfg); err != nil {
return Config{}, fmt.Errorf("envconfig process: %w", err)
}
return cfg, nil
}
type Config struct {
Devel bool `env:"DEVEL"`
HTTPPort uint `env:"HTTP_PORT, default=8001"`
HTTPHost string `env:"HTTP_HOST,default=0.0.0.0"`
DB string `env:"DB_DSN"`
UpdateInterval time.Duration `env:"UPDATE_INTERVAL,default=1s"`
}

37
application/migration.go Normal file
View File

@@ -0,0 +1,37 @@
package application
import (
"context"
"embed"
"fmt"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.uber.org/zap"
)
//go:embed migrations/*.sql
var migrationFiles embed.FS
func Migrate(ctx context.Context, db *bun.DB, logger *zap.Logger) error {
migrations := migrate.NewMigrations()
if err := migrations.Discover(migrationFiles); err != nil {
return fmt.Errorf("discover migrations: %w", err)
}
migrator := migrate.NewMigrator(db, migrations)
if initErr := migrator.Init(ctx); initErr != nil {
return fmt.Errorf("init migrations: %w", initErr)
}
group, err := migrator.Migrate(ctx)
if err != nil {
return fmt.Errorf("migrate: %w", err)
}
logger.Sugar().Infof("migrated: %s", group.String())
return nil
}

View File

@@ -0,0 +1,15 @@
CREATE TABLE "conn_log"
(
"user_id" BIGINT,
"ip_addr" VARCHAR(15),
"ts" timestamp
);
--bun:split
CREATE INDEX "conn_log_user_ip_idx" ON "conn_log" ("ip_addr", "user_id");
--bun:split
CREATE INDEX "conn_log_ip_idx" ON "conn_log" ("ip_addr");
--bun:split
CREATE INDEX "conn_log_ts_idx" ON "conn_log" ("ts");

View File

@@ -0,0 +1,23 @@
package repository
import (
"database/sql"
"fmt"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/driver/pgdriver"
)
func NewDB(dsn string) (*bun.DB, error) {
connector := pgdriver.NewConnector(pgdriver.WithDSN(dsn))
sqlDB := sql.OpenDB(connector)
sqlDB.SetMaxOpenConns(10)
db := bun.NewDB(sqlDB, pgdialect.New())
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("ping database: %w", err)
}
return db, nil
}

View File

@@ -0,0 +1,142 @@
package repository
import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/uptrace/bun"
"go.uber.org/zap"
)
type Cache interface {
Get(id uint64) []string
Append(id uint64, ip string)
}
type ConnLog struct {
bun.BaseModel `bun:"table:conn_log"`
UserID uint64 `bun:"user_id"`
IP string `bun:"ip_addr"`
TS time.Time `bun:"ts"`
}
func NewConnLogs(ctx context.Context, db *bun.DB, cache Cache, logger *zap.Logger, updateInterval time.Duration) (*ConnLogs, error) {
connLogs := &ConnLogs{db: db, cache: cache}
logger.Info("filling initial cache")
err := connLogs.fillCache(ctx)
if err != nil {
return nil, err
}
logger.Info("initial cache filled")
go func() {
ticker := time.NewTicker(updateInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
var err error
err = connLogs.fillCache(ctx)
if err != nil {
logger.Error("update cache", zap.Error(err))
}
}
}
}()
return connLogs, nil
}
type ConnLogs struct {
db *bun.DB
mu sync.RWMutex
cache Cache
lastTS time.Time
}
func (l *ConnLogs) fillCache(ctx context.Context) error {
var entity []ConnLog
l.mu.Lock()
defer l.mu.Unlock()
query := l.db.NewSelect().Model(&entity).
Order("ts").
Group("user_id", "ip_addr").
Column("user_id", "ip_addr").
ColumnExpr(`max("ts") as ts`)
if !l.lastTS.IsZero() {
query.Where(`"ts" > ? `, l.lastTS)
}
if err := query.Scan(ctx); err != nil {
return fmt.Errorf("select: %w", err)
}
loop:
for i := range entity {
item := &entity[i]
if ips := l.cache.Get(item.UserID); len(ips) == 0 {
l.cache.Append(item.UserID, item.IP)
continue
}
for _, s := range l.cache.Get(item.UserID) {
if s == item.IP {
continue loop
}
}
l.cache.Append(item.UserID, item.IP)
}
if len(entity) > 0 {
l.lastTS = entity[len(entity)-1].TS
}
return nil
}
func (l *ConnLogs) Get(_ context.Context, first, second uint64) (bool, error) {
ips1 := l.cache.Get(first)
ips2 := l.cache.Get(second)
if len(ips1) == 0 || len(ips2) == 0 {
return false, nil
}
for i := range ips1 {
for j := range ips2 {
if ips1[i] == ips2[j] {
return true, nil
}
}
}
return false, nil
}
func (l *ConnLogs) List(ctx context.Context) (string, error) {
var entity []ConnLog
if err := l.db.NewSelect().Model(&entity).Scan(ctx); err != nil {
return "", fmt.Errorf("select: %w", err)
}
marshal, err := json.Marshal(entity)
if err != nil {
return "", fmt.Errorf("marshal: %w", err)
}
return string(marshal), nil
}

View File

@@ -0,0 +1,151 @@
package repository_test
import (
"context"
"database/sql"
"fmt"
"testing"
"time"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/driver/pgdriver"
"go.uber.org/zap/zaptest"
"git.derfenix.pro/fenix/protect_trans_info/adapters/inmemorycache"
"git.derfenix.pro/fenix/protect_trans_info/application"
. "git.derfenix.pro/fenix/protect_trans_info/application/repository"
)
func TestLogs_Get(t *testing.T) {
if testing.Short() {
t.Skip("skip long test")
}
t.Parallel()
dockerPool, err := dockertest.NewPool("")
dockerPool.MaxWait = time.Second * 10
require.NoError(t, err)
resource, err := dockerPool.RunWithOptions(&dockertest.RunOptions{
Repository: "postgres",
Tag: "15",
Env: []string{
"POSTGRES_USER=test",
"POSTGRES_PASSWORD=test",
"POSTGRES_DB=test",
"POSTGRES_HOST_AUTH_METHOD=md5",
"POSTGRES_INITDB_ARGS=--auth-host=md5",
},
PortBindings: map[docker.Port][]docker.PortBinding{"5432/tcp": {{HostIP: "0.0.0.0", HostPort: "55432"}}},
}, func(config *docker.HostConfig) {
config.AutoRemove = true
})
require.NoError(t, err)
t.Cleanup(func() {
err := dockerPool.Purge(resource)
assert.NoError(t, err)
})
connector := pgdriver.NewConnector(pgdriver.WithDSN("postgresql://test:test@localhost:55432/test?sslmode=disable"))
sqlDB := sql.OpenDB(connector)
sqlDB.SetMaxOpenConns(10)
db := bun.NewDB(sqlDB, pgdialect.New())
err = dockerPool.Retry(func() error {
pingCtx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if err := db.PingContext(pingCtx); err != nil {
return fmt.Errorf("ping database: %w", err)
}
return nil
})
require.NoError(t, err)
ctx := context.Background()
logger := zaptest.NewLogger(t)
require.NoError(t, application.Migrate(ctx, db, logger))
testData := []ConnLog{
{
UserID: 1,
IP: "123.123.123.123",
TS: time.Now(),
},
{
UserID: 2,
IP: "123.123.123.123",
TS: time.Now().Add(time.Hour),
},
{
UserID: 3,
IP: "124.123.123.123",
TS: time.Now().Add(time.Hour * 2),
},
}
_, err = db.NewInsert().Model(&testData).Exec(ctx)
require.NoError(t, err)
repo, err := NewConnLogs(ctx, db, make(inmemorycache.Cache), logger, time.Millisecond*100)
require.NoError(t, err)
t.Run("found dup", func(t *testing.T) {
t.Parallel()
get, err := repo.Get(ctx, 1, 2)
require.NoError(t, err)
require.True(t, get)
})
t.Run("no dup 1", func(t *testing.T) {
t.Parallel()
get, err := repo.Get(ctx, 1, 3)
require.NoError(t, err)
require.False(t, get)
})
t.Run("no dup 2", func(t *testing.T) {
t.Parallel()
get, err := repo.Get(ctx, 2, 3)
require.NoError(t, err)
require.False(t, get)
})
t.Run("added item", func(t *testing.T) {
t.Parallel()
get, err := repo.Get(ctx, 4, 3)
require.NoError(t, err)
require.False(t, get)
_, err = db.NewInsert().Model(&ConnLog{
BaseModel: bun.BaseModel{},
UserID: 4,
IP: "124.123.123.123",
TS: time.Now().Add(time.Hour * 3),
}).Exec(ctx)
require.NoError(t, err)
get, err = repo.Get(ctx, 4, 3)
require.NoError(t, err)
assert.False(t, get)
time.Sleep(time.Millisecond * 200)
get, err = repo.Get(ctx, 4, 3)
require.NoError(t, err)
require.True(t, get)
})
}

30
bench_test.go Normal file
View File

@@ -0,0 +1,30 @@
package protect_trans_info
import (
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/require"
)
func BenchmarkDup(b *testing.B) {
c := http.Client{}
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8001/43/333", nil)
require.NoError(b, err)
for i := 0; i < b.N; i++ {
_, err := c.Do(req)
require.NoError(b, err)
}
}
func FuzzDup(f *testing.F) {
f.Fuzz(func(t *testing.T, firstID uint16, secondID uint16) {
c := http.Client{}
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://127.0.0.1:8001/%d/%d", firstID, secondID), nil)
require.NoError(t, err)
_, err = c.Do(req)
require.NoError(t, err)
})
}

36
cmd/migrate/main.go Normal file
View File

@@ -0,0 +1,36 @@
package main
import (
"context"
"fmt"
"os/signal"
"go.uber.org/zap"
"git.derfenix.pro/fenix/protect_trans_info/application"
"git.derfenix.pro/fenix/protect_trans_info/application/repository"
)
func main() {
cfg, err := application.NewConfig()
if err != nil {
panic(fmt.Sprintf("load config: %v", err))
}
db, err := repository.NewDB(cfg.DB)
if err != nil {
panic(fmt.Sprintf("new db: %v", err))
}
logger, err := zap.NewProduction()
if err != nil {
panic(fmt.Sprintf("new logger: %v", err))
}
ctx, cancel := signal.NotifyContext(context.Background())
defer cancel()
if err := application.Migrate(ctx, db, logger); err != nil {
logger.Fatal("migrate failed", zap.Error(err))
}
}

41
cmd/seed/main.go Normal file
View File

@@ -0,0 +1,41 @@
package main
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"git.derfenix.pro/fenix/protect_trans_info/application"
"git.derfenix.pro/fenix/protect_trans_info/application/repository"
"git.derfenix.pro/fenix/protect_trans_info/scripts"
)
func main() {
cfg, err := application.NewConfig()
if err != nil {
panic(fmt.Sprintf("load config: %v", err))
}
db, err := repository.NewDB(cfg.DB)
if err != nil {
panic(fmt.Sprintf("new db: %v", err))
}
logger, err := zap.NewProduction()
if err != nil {
panic(fmt.Sprintf("new logger: %v", err))
}
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*3)
defer cancel()
logger.Info("start seeding")
if err := scripts.SeedData(ctx, db, logger); err != nil {
logger.Fatal("failed to seed data", zap.Error(err))
}
logger.Info("stop seeding")
}

56
cmd/service/main.go Normal file
View File

@@ -0,0 +1,56 @@
package main
import (
"context"
"fmt"
"os"
"os/signal"
"sync"
"go.uber.org/zap"
"git.derfenix.pro/fenix/protect_trans_info/application"
)
func main() {
cfg, err := application.NewConfig()
if err != nil {
panic(fmt.Sprintf("load config: %v", err))
}
var logger *zap.Logger
switch cfg.Devel {
case true:
logger, err = zap.NewDevelopment()
case false:
logger, err = zap.NewProduction()
}
if err != nil {
panic(fmt.Sprintf("init logger: %v", err))
}
ctx, cancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt)
defer cancel()
app, err := application.NewApplication(ctx, cfg, logger)
if err != nil {
logger.Fatal("create application failed", zap.Error(err))
}
wg := sync.WaitGroup{}
go func() {
<-ctx.Done()
app.Stop(&wg)
}()
app.Start(&wg)
wg.Wait()
// Context did not stop the application
if ctx.Err() == nil {
os.Exit(2)
}
}

18
deploy/Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM golang:1.21 as builder
WORKDIR /project
COPY go.* .
RUN go get ./...
COPY . .
RUN export CGO_ENABLED=0 && go build -o service ./cmd/service/main.go &&\
go build -o seed ./cmd/seed/main.go && \
go build -o migrate ./cmd/migrate/main.go
FROM alpine:latest
COPY --from=builder /project/service /service
COPY --from=builder /project/seed /seed
COPY --from=builder /project/migrate /migrate

49
docker-compose.yaml Normal file
View File

@@ -0,0 +1,49 @@
version: "3"
volumes:
db:
services:
base:
build:
dockerfile: deploy/Dockerfile
context: .
db:
image: postgres:15
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test
POSTGRES_HOST_AUTH_METHOD: md5
POSTGRES_INITDB_ARGS: --auth-host=md5
service:
extends:
service: base
environment:
DB_DSN: "postgresql://test:test@db:5432/test?sslmode=disable"
PORT: 8001
DEVEL: true
ports:
- "8001:8001"
command:
- /service
depends_on:
- db
migrate:
extends:
service: base
environment:
DB_DSN: "postgresql://test:test@db:5432/test?sslmode=disable"
command:
- /migrate
depends_on:
- db
seed:
extends:
service: base
environment:
DB_DSN: "postgresql://test:test@db:5432/test?sslmode=disable"
command:
- /seed
depends_on:
- db

54
go.mod Normal file
View File

@@ -0,0 +1,54 @@
module git.derfenix.pro/fenix/protect_trans_info
go 1.21
require (
github.com/go-chi/chi/v5 v5.0.10
github.com/ory/dockertest/v3 v3.10.0
github.com/sethvargo/go-envconfig v0.9.0
github.com/stretchr/testify v1.8.1
github.com/uptrace/bun v1.1.14
github.com/uptrace/bun/dialect/pgdialect v1.1.14
github.com/uptrace/bun/driver/pgdriver v1.1.14
go.uber.org/zap v1.25.0
)
require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/docker v20.10.7+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.1.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/tools v0.7.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
mellium.im/sasl v0.3.1 // indirect
)

198
go.sum Normal file
View File

@@ -0,0 +1,198 @@
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/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
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.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
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/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M=
github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ=
github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
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/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.3.0/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.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW7GN5ngLm8YUZIPzf394=
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
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.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/sethvargo/go-envconfig v0.9.0 h1:Q6FQ6hVEeTECULvkJZakq3dZMeBQ3JUpcKMfPQbKMDE=
github.com/sethvargo/go-envconfig v0.9.0/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
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/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.6.1/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.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/uptrace/bun v1.1.14 h1:S5vvNnjEynJ0CvnrBOD7MIRW7q/WbtvFXrdfy0lddAM=
github.com/uptrace/bun v1.1.14/go.mod h1:RHk6DrIisO62dv10pUOJCz5MphXThuOTpVNYEYv7NI8=
github.com/uptrace/bun/dialect/pgdialect v1.1.14 h1:b7+V1KDJPQSFYgkG/6YLXCl2uvwEY3kf/GSM7hTHRDY=
github.com/uptrace/bun/dialect/pgdialect v1.1.14/go.mod h1:v6YiaXmnKQ2FlhRD2c0ZfKd+QXH09pYn4H8ojaavkKk=
github.com/uptrace/bun/driver/pgdriver v1.1.14 h1:V2Etm7mLGS3mhx8ddxZcUnwZLX02Jmq9JTlo0sNVDhA=
github.com/uptrace/bun/driver/pgdriver v1.1.14/go.mod h1:D4FjWV9arDYct6sjMJhFoyU71SpllZRHXFRRP2Kd0Kw=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
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.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
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.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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-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-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/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/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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
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.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
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/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=

144
port/http/service.go Normal file
View File

@@ -0,0 +1,144 @@
package http
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"go.uber.org/zap"
)
type LogStorage interface {
Get(ctx context.Context, first, second uint64) (bool, error)
List(ctx context.Context) (string, error)
}
type Response struct {
Dupes *bool `json:"dupes,omitempty"`
Err string `json:"error,omitempty"`
}
func NewService(host string, port uint, log LogStorage, logger *zap.Logger) (*Service, error) {
srv := http.Server{
Addr: fmt.Sprintf("%s:%d", host, port),
ReadTimeout: time.Second,
ReadHeaderTimeout: time.Millisecond * 500,
WriteTimeout: time.Second * 10,
IdleTimeout: time.Minute,
MaxHeaderBytes: 1024 * 16,
}
service := Service{
server: &srv,
log: log,
logger: logger,
}
mux := chi.NewMux()
mux.Use(middleware.StripSlashes)
mux.Get("/{firstID:[0-9]+}/{secondID:[0-9]+}", service.handleDup)
mux.Get("/list", service.List)
srv.Handler = mux
return &service, nil
}
type Service struct {
server *http.Server
log LogStorage
logger *zap.Logger
}
func (s *Service) Start() {
s.logger.Info("starting http service", zap.String("address", s.server.Addr))
if err := s.server.ListenAndServe(); err != nil {
if !errors.Is(err, http.ErrServerClosed) {
s.logger.Error("listen and serve failed", zap.Error(err))
}
}
}
func (s *Service) Stop(ctx context.Context) {
if err := s.server.Shutdown(ctx); err != nil {
s.logger.Error("the graceful shutdown has failed", zap.Error(err))
}
}
func (s *Service) handleDup(w http.ResponseWriter, r *http.Request) {
started := time.Now()
firstIDStr := chi.URLParam(r, "firstID")
firstID, err := strconv.ParseUint(firstIDStr, 10, 64)
if err != nil {
s.badRequest(w, Response{Err: fmt.Sprintf("invalid first id value: %s", firstIDStr)})
return
}
secondIDStr := chi.URLParam(r, "secondID")
secondID, err := strconv.ParseUint(secondIDStr, 10, 64)
if err != nil {
s.badRequest(w, Response{Err: fmt.Sprintf("invalid second id value: %s", secondIDStr)})
return
}
logger := s.logger.With(zap.Uint64("first_id", firstID), zap.Uint64("second_id", secondID))
logger.Debug("new dup request")
if secondID == firstID {
t := true
s.writeResponse(w, Response{Dupes: &t}, http.StatusOK)
return
}
result, err := s.log.Get(r.Context(), firstID, secondID)
if err != nil {
logger.Error("log storage get failed", zap.Error(err))
s.internalError(w, Response{Err: fmt.Sprintf("storage error")})
return
}
logger.Debug("result", zap.Bool("dup", result))
w.Header().Add("Server-Timing", fmt.Sprintf("db;dur=%dus", time.Since(started).Microseconds()))
s.writeResponse(w, Response{Dupes: &result}, http.StatusOK)
}
func (s *Service) badRequest(w http.ResponseWriter, response Response) {
s.writeResponse(w, response, http.StatusBadRequest)
}
func (s *Service) internalError(w http.ResponseWriter, response Response) {
s.writeResponse(w, response, http.StatusInternalServerError)
}
func (s *Service) writeResponse(w http.ResponseWriter, response Response, code int) {
w.WriteHeader(code)
err := json.NewEncoder(w).Encode(response)
if err != nil {
s.logger.Error("encode bad request response", zap.Error(err), zap.Any("response", response))
}
}
func (s *Service) List(w http.ResponseWriter, r *http.Request) {
list, err := s.log.List(r.Context())
if err != nil {
s.internalError(w, Response{Err: err.Error()})
return
}
_, _ = w.Write([]byte(list))
}

75
scripts/seed.go Normal file
View File

@@ -0,0 +1,75 @@
package scripts
import (
"context"
"encoding/binary"
"fmt"
"math/rand"
"net"
"time"
"github.com/uptrace/bun"
"go.uber.org/zap"
"git.derfenix.pro/fenix/protect_trans_info/application/repository"
)
func SeedData(ctx context.Context, db *bun.DB, logger *zap.Logger) error {
now := time.Now().Add(-24 * time.Hour)
const bufSize = 500
buf := make([]repository.ConnLog, 0, bufSize)
for i := 0; i < 2_000_000; i++ {
buf = append(buf, repository.ConnLog{
UserID: uint64(rand.Int63n(10000)),
IP: randomIP(),
TS: now.Add(time.Second * time.Duration(i)),
})
if len(buf) == bufSize {
_, err := db.NewInsert().Model(&buf).Exec(ctx)
if err != nil {
return fmt.Errorf("insert bulk: %w", err)
}
logger.Sugar().Infof("insert %d items", len(buf))
buf = make([]repository.ConnLog, 0, bufSize)
}
}
if len(buf) > 0 {
_, err := db.NewInsert().Model(&buf).Exec(ctx)
if err != nil {
return fmt.Errorf("insert bulk: %w", err)
}
logger.Sugar().Infof("insert %d items", len(buf))
}
fixedDup := []repository.ConnLog{
repository.ConnLog{
UserID: 88888,
IP: "127.0.0.1",
TS: now.Add(time.Second * time.Duration(888)),
},
repository.ConnLog{
UserID: 99999,
IP: "127.0.0.1",
TS: now.Add(time.Second * time.Duration(999)),
},
}
_, err := db.NewInsert().Model(&fixedDup).Exec(ctx)
if err != nil {
return fmt.Errorf("insert fixed dup: %w", err)
}
return nil
}
func randomIP() string {
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, rand.Uint32())
return net.IP(buf).String()
}

7
test.http Normal file
View File

@@ -0,0 +1,7 @@
GET http://127.0.0.1:8001/12/233/
###
GET http://127.0.0.1:8001/88888/99999
###
GET http://127.0.0.1:8001/list

View File

@@ -0,0 +1,3 @@
go test fuzz v1
uint16(91)
uint16(10)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
uint16(93)
uint16(108)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
uint16(4)
uint16(37)