Initial commit
This commit is contained in:
43
.gitignore
vendored
Normal file
43
.gitignore
vendored
Normal 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
8
.idea/.gitignore
generated
vendored
Normal 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
15
.idea/git_toolbox_prj.xml
generated
Normal 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
17
.idea/golinter.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
9
.idea/protect_trans_info.iml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
7
.idea/yamllint.xml
generated
Normal 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
9
Makefile
Normal 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
46
README.md
Normal 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 постгреса добавлять новые записи в кэш. Было бы эффективнее,
|
||||
но на данном этапе выглядит как оверинженеринг.
|
||||
|
||||
Для продакшена не хватает метрик и алертов в сентри, но на это уже
|
||||
времени тратить не хочется.
|
||||
11
adapters/inmemorycache/cache.go
Normal file
11
adapters/inmemorycache/cache.go
Normal 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)
|
||||
}
|
||||
69
application/application.go
Normal file
69
application/application.go
Normal 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
27
application/config.go
Normal 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
37
application/migration.go
Normal 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
|
||||
}
|
||||
15
application/migrations/20230824180522_conn_logs_table.up.sql
Normal file
15
application/migrations/20230824180522_conn_logs_table.up.sql
Normal 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");
|
||||
23
application/repository/bunpg.go
Normal file
23
application/repository/bunpg.go
Normal 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
|
||||
}
|
||||
142
application/repository/logs.go
Normal file
142
application/repository/logs.go
Normal 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
|
||||
}
|
||||
151
application/repository/logs_test.go
Normal file
151
application/repository/logs_test.go
Normal 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
30
bench_test.go
Normal 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
36
cmd/migrate/main.go
Normal 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
41
cmd/seed/main.go
Normal 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
56
cmd/service/main.go
Normal 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
18
deploy/Dockerfile
Normal 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
49
docker-compose.yaml
Normal 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
54
go.mod
Normal 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
198
go.sum
Normal 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
144
port/http/service.go
Normal 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
75
scripts/seed.go
Normal 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
7
test.http
Normal 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
|
||||
3
testdata/fuzz/FuzzDup/149d6ad96b2f24d6
vendored
Normal file
3
testdata/fuzz/FuzzDup/149d6ad96b2f24d6
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
uint16(91)
|
||||
uint16(10)
|
||||
3
testdata/fuzz/FuzzDup/29aa5edb5a37da5e
vendored
Normal file
3
testdata/fuzz/FuzzDup/29aa5edb5a37da5e
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
uint16(93)
|
||||
uint16(108)
|
||||
3
testdata/fuzz/FuzzDup/642e3858300a23d4
vendored
Normal file
3
testdata/fuzz/FuzzDup/642e3858300a23d4
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
go test fuzz v1
|
||||
uint16(4)
|
||||
uint16(37)
|
||||
Reference in New Issue
Block a user