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