This commit is contained in:
Tyr Mactire 2022-07-26 20:51:03 -07:00 committed by GitHub
parent c1d1f734e0
commit 4307c4b894
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1146 changed files with 458137 additions and 257206 deletions

6
.gitignore vendored
View File

@ -13,3 +13,9 @@
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
/dist
/web/static/css/default.min.css
/web/static/css/error.min.css
/web/static/css/login.min.css
/web/static/js/tooltips.min.js

1
Jenkinsfile vendored
View File

@ -18,6 +18,7 @@ pipeline {
script { script {
sh """#!/bin/bash sh """#!/bin/bash
make clean make clean
make stage-static
""" """
} }
} }

View File

@ -43,6 +43,11 @@ lint:
@echo linting @echo linting
@golint $(shell go list ./... | grep -v /vendor/) @golint $(shell go list ./... | grep -v /vendor/)
stage-static:
minify web/static-src/css/default.css > web/static/css/default.min.css
minify web/static-src/css/error.css > web/static/css/error.min.css
minify web/static-src/css/login.css > web/static/css/login.min.css
test: tidy fmt lint #gosec test: tidy fmt lint #gosec
go test -cover ./... go test -cover ./...
@ -58,4 +63,4 @@ tidy:
vendor: tidy vendor: tidy
go mod vendor go mod vendor
.PHONY: build-snapshot bun-new-migration clean fmt lint stage-static npm-scss npm-upgrade docker-restart docker-start docker-stop test test-ext test-race test-race-ext test-verbose tidy vendor .PHONY: build-snapshot bun-new-migration clean fmt lint stage-static npm-scss npm-upgrade docker-restart docker-start docker-stop stage-static test test-ext test-race test-race-ext test-verbose tidy vendor

32
cmd/relay/account.go Normal file
View File

@ -0,0 +1,32 @@
package main
import (
"github.com/feditools/relay/cmd/relay/action/account"
"github.com/feditools/relay/cmd/relay/flag"
"github.com/feditools/relay/internal/config"
"github.com/spf13/cobra"
)
// accountCommands returns the 'account' subcommand.
func accountCommands() *cobra.Command {
accountCmd := &cobra.Command{
Use: "account",
Short: "manage accounts",
}
flag.Account(accountCmd, config.Defaults)
accountModifyCmd := &cobra.Command{
Use: "modify",
Short: "modify an account",
PreRunE: func(cmd *cobra.Command, args []string) error {
return preRun(cmd)
},
RunE: func(cmd *cobra.Command, args []string) error {
return run(cmd.Context(), account.Modify)
},
}
flag.AccountModify(accountModifyCmd, config.Defaults)
accountCmd.AddCommand(accountModifyCmd)
return accountCmd
}

View File

@ -0,0 +1,7 @@
package account
import "github.com/feditools/relay/internal/log"
type empty struct{}
var logger = log.WithPackageField(empty{})

View File

@ -0,0 +1,107 @@
package account
import (
"context"
"github.com/feditools/go-lib"
"github.com/feditools/go-lib/metrics/statsd"
"github.com/feditools/relay/cmd/relay/action"
"github.com/feditools/relay/internal/config"
"github.com/feditools/relay/internal/db/bun"
"github.com/spf13/viper"
)
// Modify runs database migrations.
var Modify action.Action = func(ctx context.Context) error {
l := logger.WithField("func", "Modify")
// create metrics collector
metricsCollector, err := statsd.New(
viper.GetString(config.Keys.MetricsStatsDAddress),
viper.GetString(config.Keys.MetricsStatsDPrefix),
)
if err != nil {
l.Errorf("metrics: %s", err.Error())
return err
}
defer func() {
l.Info("closing metrics collector")
err := metricsCollector.Close()
if err != nil {
l.Errorf("closing metrics: %s", err.Error())
}
}()
// create database client
l.Info("creating database client")
dbClient, err := bun.New(ctx, metricsCollector)
if err != nil {
l.Errorf("db: %s", err.Error())
return err
}
defer func() {
l.Info("closing database client")
err := dbClient.Close(ctx)
if err != nil {
l.Errorf("closing db: %s", err.Error())
}
}()
accountString := viper.GetString(config.Keys.Account)
username, domain, err := lib.SplitAccount(accountString)
if err != nil {
l.Errorf("invalid account %s: %s", accountString, err.Error())
return err
}
// find instance
instance, err := dbClient.ReadInstanceByDomain(ctx, domain)
if err != nil {
l.Errorf("db read %s: %s", domain, err.Error())
return err
}
if instance == nil {
l.Infof("can't find instance %s", domain)
return nil
}
l.Debugf("found instance %d: %+v", instance.ID, instance)
// find account
account, err := dbClient.ReadAccountByUsername(ctx, instance.ID, username)
if err != nil {
l.Errorf("db read %s: %s", username, err.Error())
return err
}
if instance == nil {
l.Infof("can't find user %s", username)
return nil
}
l.Debugf("found account %d: %+v", account.ID, account)
// add groups
for _, addGroup := range viper.GetStringSlice(config.Keys.AccountAddGroup) {
switch addGroup {
case "admin":
account.IsAdmin = true
default:
l.Warnf("unknown group %s, skipping", addGroup)
}
}
// update database
err = dbClient.UpdateAccount(ctx, account)
if err != nil {
l.Errorf("db update: %s", err.Error())
return err
}
return nil
}

View File

@ -2,10 +2,10 @@ package database
import ( import (
"context" "context"
"github.com/feditools/go-lib/metrics/statsd"
"github.com/feditools/relay/cmd/relay/action" "github.com/feditools/relay/cmd/relay/action"
"github.com/feditools/relay/internal/config" "github.com/feditools/relay/internal/config"
"github.com/feditools/relay/internal/db/bun" "github.com/feditools/relay/internal/db/bun"
"github.com/feditools/relay/internal/metrics/statsd"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -14,7 +14,10 @@ var Migrate action.Action = func(ctx context.Context) error {
l := logger.WithField("func", "Migrate") l := logger.WithField("func", "Migrate")
// create metrics collector // create metrics collector
metricsCollector, err := statsd.New() metricsCollector, err := statsd.New(
viper.GetString(config.Keys.MetricsStatsDAddress),
viper.GetString(config.Keys.MetricsStatsDPrefix),
)
if err != nil { if err != nil {
l.Errorf("metrics: %s", err.Error()) l.Errorf("metrics: %s", err.Error())
return err return err
@ -46,13 +49,5 @@ var Migrate action.Action = func(ctx context.Context) error {
return err return err
} }
if viper.GetBool(config.Keys.DbLoadTestData) {
err = dbClient.LoadTestData(ctx)
if err != nil {
l.Errorf("migration: %s", err.Error())
return err
}
}
return nil return nil
} }

View File

@ -3,17 +3,23 @@ package server
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/feditools/go-lib"
"github.com/feditools/go-lib/language"
"github.com/feditools/go-lib/metrics/statsd"
"github.com/feditools/relay/cmd/relay/action" "github.com/feditools/relay/cmd/relay/action"
"github.com/feditools/relay/internal/clock" "github.com/feditools/relay/internal/clock"
"github.com/feditools/relay/internal/config" "github.com/feditools/relay/internal/config"
"github.com/feditools/relay/internal/db/bun" "github.com/feditools/relay/internal/db/bun"
"github.com/feditools/relay/internal/fedi"
"github.com/feditools/relay/internal/http" "github.com/feditools/relay/internal/http"
"github.com/feditools/relay/internal/http/activitypub" "github.com/feditools/relay/internal/http/activitypub"
"github.com/feditools/relay/internal/logic" "github.com/feditools/relay/internal/http/static"
"github.com/feditools/relay/internal/metrics/statsd" "github.com/feditools/relay/internal/http/webapp"
"github.com/feditools/relay/internal/kv/redis"
"github.com/feditools/relay/internal/logic/logic1"
"github.com/feditools/relay/internal/runner/faktory" "github.com/feditools/relay/internal/runner/faktory"
"github.com/feditools/relay/internal/token"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/tyrm/go-util"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
@ -27,7 +33,10 @@ var Start action.Action = func(topCtx context.Context) error {
ctx, cancel := context.WithCancel(topCtx) ctx, cancel := context.WithCancel(topCtx)
// create metrics collector // create metrics collector
metricsCollector, err := statsd.New() metricsCollector, err := statsd.New(
viper.GetString(config.Keys.MetricsStatsDAddress),
viper.GetString(config.Keys.MetricsStatsDPrefix),
)
if err != nil { if err != nil {
l.Errorf("metrics: %s", err.Error()) l.Errorf("metrics: %s", err.Error())
cancel() cancel()
@ -35,12 +44,17 @@ var Start action.Action = func(topCtx context.Context) error {
return err return err
} }
defer func() { defer func() {
l.Info("closing metrics collector")
err := metricsCollector.Close() err := metricsCollector.Close()
if err != nil { if err != nil {
l.Errorf("closing metrics: %s", err.Error()) l.Errorf("closing metrics: %s", err.Error())
} }
}() }()
// create clock module
l.Debug("creating clock")
clockMod := clock.NewClock()
// create database client // create database client
l.Debug("creating database client") l.Debug("creating database client")
dbClient, err := bun.New(ctx, metricsCollector) dbClient, err := bun.New(ctx, metricsCollector)
@ -57,13 +71,51 @@ var Start action.Action = func(topCtx context.Context) error {
} }
}() }()
// create clock module // create http client
l.Debug("creating clock") httpClient, err := http.NewClient(ctx)
clockMod := clock.NewClock() if err != nil {
l.Errorf("http client: %s", err.Error())
cancel()
return err
}
// create kv client
kvClient, err := redis.New(ctx)
if err != nil {
l.Errorf("redis: %s", err.Error())
cancel()
return err
}
defer func() {
err := kvClient.Close(ctx)
if err != nil {
l.Errorf("closing redis: %s", err.Error())
}
}()
// create language module
languageMod, err := language.New()
if err != nil {
l.Errorf("language: %s", err.Error())
cancel()
return err
}
// create tokenizer
tokz, err := token.New()
if err != nil {
l.Errorf("create tokenizer: %s", err.Error())
cancel()
return err
}
// create logic module // create logic module
l.Debug("creating logic module") l.Debug("creating logic module")
logicMod, err := logic.New(ctx, clockMod, dbClient) logicMod, err := logic1.New(ctx, clockMod, dbClient, httpClient)
if err != nil { if err != nil {
l.Errorf("logic: %s", err.Error()) l.Errorf("logic: %s", err.Error())
cancel() cancel()
@ -71,6 +123,16 @@ var Start action.Action = func(topCtx context.Context) error {
return err return err
} }
// create fedi module
fediMod, err := fedi.New(dbClient, logicMod.Transport(), kvClient, tokz)
if err != nil {
l.Errorf("fedi: %s", err.Error())
cancel()
return err
}
logicMod.SetFedi(fediMod)
// create runner // create runner
runnerMod, err := faktory.New(logicMod) runnerMod, err := faktory.New(logicMod)
if err != nil { if err != nil {
@ -94,7 +156,7 @@ var Start action.Action = func(topCtx context.Context) error {
// create web modules // create web modules
var webModules []http.Module var webModules []http.Module
if util.ContainsString(viper.GetStringSlice(config.Keys.ServerRoles), config.ServerRoleActivityPub) { if lib.ContainsString(viper.GetStringSlice(config.Keys.ServerRoles), config.ServerRoleActivityPub) {
l.Infof("adding %s module", config.ServerRoleActivityPub) l.Infof("adding %s module", config.ServerRoleActivityPub)
apMod, err := activitypub.New(ctx, logicMod, runnerMod) apMod, err := activitypub.New(ctx, logicMod, runnerMod)
if err != nil { if err != nil {
@ -105,9 +167,42 @@ var Start action.Action = func(topCtx context.Context) error {
} }
webModules = append(webModules, apMod) webModules = append(webModules, apMod)
} }
if lib.ContainsString(viper.GetStringSlice(config.Keys.ServerRoles), config.ServerRoleStatic) {
l.Infof("adding %s module", config.ServerRoleStatic)
apMod, err := static.New()
if err != nil {
l.Errorf("%s: %s", config.ServerRoleStatic, err.Error())
cancel()
return err
}
webModules = append(webModules, apMod)
}
if lib.ContainsString(viper.GetStringSlice(config.Keys.ServerRoles), config.ServerRoleWebapp) {
l.Infof("adding %s module", config.ServerRoleWebapp)
apMod, err := webapp.New(
ctx,
dbClient,
fediMod,
languageMod,
logicMod,
metricsCollector,
kvClient.RedisClient(),
runnerMod,
tokz,
)
if err != nil {
l.Errorf("%s: %s", config.ServerRoleWebapp, err.Error())
cancel()
return err
}
webModules = append(webModules, apMod)
}
// add modules to servers // add modules to servers
for _, mod := range webModules { for _, mod := range webModules {
mod.SetServer(server)
err := mod.Route(server) err := mod.Route(server)
if err != nil { if err != nil {
l.Errorf("loading %s module: %s", mod.Name(), err.Error()) l.Errorf("loading %s module: %s", mod.Name(), err.Error())

16
cmd/relay/flag/account.go Normal file
View File

@ -0,0 +1,16 @@
package flag
import (
"github.com/feditools/relay/internal/config"
"github.com/spf13/cobra"
)
// Account adds all flags for running the account command.
func Account(cmd *cobra.Command, values config.Values) {
cmd.PersistentFlags().String(config.Keys.Account, values.Account, usage.Account)
}
// AccountModify adds all flags for running the account modify command.
func AccountModify(cmd *cobra.Command, values config.Values) {
cmd.PersistentFlags().StringArray(config.Keys.AccountAddGroup, values.AccountAddGroup, usage.AccountAddGroup)
}

View File

@ -12,5 +12,4 @@ func Database(cmd *cobra.Command, values config.Values) {
// DatabaseMigrate adds all flags for running the database migrate command. // DatabaseMigrate adds all flags for running the database migrate command.
func DatabaseMigrate(cmd *cobra.Command, values config.Values) { func DatabaseMigrate(cmd *cobra.Command, values config.Values) {
Database(cmd, values) Database(cmd, values)
cmd.PersistentFlags().Bool(config.Keys.DbLoadTestData, values.DbLoadTestData, usage.DbLoadTestData)
} }

View File

@ -16,6 +16,7 @@ func Global(cmd *cobra.Command, values config.Values) {
cmd.PersistentFlags().Int(config.Keys.CachedActivityLimit, values.CachedActivityLimit, usage.CachedActivityLimit) cmd.PersistentFlags().Int(config.Keys.CachedActivityLimit, values.CachedActivityLimit, usage.CachedActivityLimit)
cmd.PersistentFlags().Int(config.Keys.CachedActorLimit, values.CachedActorLimit, usage.CachedActorLimit) cmd.PersistentFlags().Int(config.Keys.CachedActorLimit, values.CachedActorLimit, usage.CachedActorLimit)
cmd.PersistentFlags().Int(config.Keys.CachedDigestLimit, values.CachedDigestLimit, usage.CachedDigestLimit) cmd.PersistentFlags().Int(config.Keys.CachedDigestLimit, values.CachedDigestLimit, usage.CachedDigestLimit)
cmd.PersistentFlags().String(config.Keys.EncryptionKey, values.EncryptionKey, usage.EncryptionKey)
// database // database
cmd.PersistentFlags().String(config.Keys.DbType, values.DbType, usage.DbType) cmd.PersistentFlags().String(config.Keys.DbType, values.DbType, usage.DbType)
@ -26,7 +27,6 @@ func Global(cmd *cobra.Command, values config.Values) {
cmd.PersistentFlags().String(config.Keys.DbDatabase, values.DbDatabase, usage.DbDatabase) cmd.PersistentFlags().String(config.Keys.DbDatabase, values.DbDatabase, usage.DbDatabase)
cmd.PersistentFlags().String(config.Keys.DbTLSMode, values.DbTLSMode, usage.DbTLSMode) cmd.PersistentFlags().String(config.Keys.DbTLSMode, values.DbTLSMode, usage.DbTLSMode)
cmd.PersistentFlags().String(config.Keys.DbTLSCACert, values.DbTLSCACert, usage.DbTLSCACert) cmd.PersistentFlags().String(config.Keys.DbTLSCACert, values.DbTLSCACert, usage.DbTLSCACert)
cmd.PersistentFlags().String(config.Keys.DbEncryptionKey, values.DbEncryptionKey, usage.DbEncryptionKey)
// metrics // metrics
cmd.PersistentFlags().String(config.Keys.MetricsStatsDAddress, values.MetricsStatsDAddress, usage.MetricsStatsDAddress) cmd.PersistentFlags().String(config.Keys.MetricsStatsDAddress, values.MetricsStatsDAddress, usage.MetricsStatsDAddress)

13
cmd/relay/flag/redis.go Normal file
View File

@ -0,0 +1,13 @@
package flag
import (
"github.com/feditools/relay/internal/config"
"github.com/spf13/cobra"
)
// Redis adds flags that are common to redis.
func Redis(cmd *cobra.Command, values config.Values) {
cmd.PersistentFlags().String(config.Keys.RedisAddress, values.RedisAddress, usage.RedisAddress)
cmd.PersistentFlags().Int(config.Keys.RedisDB, values.RedisDB, usage.RedisDB)
cmd.PersistentFlags().String(config.Keys.RedisPassword, values.RedisPassword, usage.RedisPassword)
}

View File

@ -7,6 +7,8 @@ import (
// Server adds all flags for running the server. // Server adds all flags for running the server.
func Server(cmd *cobra.Command, values config.Values) { func Server(cmd *cobra.Command, values config.Values) {
Redis(cmd, values)
// server // server
cmd.PersistentFlags().String(config.Keys.ServerExternalHostname, values.ServerExternalHostname, usage.ServerExternalHostname) cmd.PersistentFlags().String(config.Keys.ServerExternalHostname, values.ServerExternalHostname, usage.ServerExternalHostname)
cmd.PersistentFlags().String(config.Keys.ServerHTTPBind, values.ServerHTTPBind, usage.ServerHTTPBind) cmd.PersistentFlags().String(config.Keys.ServerHTTPBind, values.ServerHTTPBind, usage.ServerHTTPBind)
@ -15,4 +17,14 @@ func Server(cmd *cobra.Command, values config.Values) {
// runner // runner
cmd.PersistentFlags().Int(config.Keys.RunnerConcurrency, values.RunnerConcurrency, usage.RunnerConcurrency) cmd.PersistentFlags().Int(config.Keys.RunnerConcurrency, values.RunnerConcurrency, usage.RunnerConcurrency)
// webapp
cmd.PersistentFlags().String(config.Keys.WebappBootstrapCSSURI, values.WebappBootstrapCSSURI, usage.WebappBootstrapCSSURI)
cmd.PersistentFlags().String(config.Keys.WebappBootstrapCSSIntegrity, values.WebappBootstrapCSSIntegrity, usage.WebappBootstrapCSSIntegrity)
cmd.PersistentFlags().String(config.Keys.WebappBootstrapJSURI, values.WebappBootstrapJSURI, usage.WebappBootstrapJSURI)
cmd.PersistentFlags().String(config.Keys.WebappBootstrapJSIntegrity, values.WebappBootstrapJSIntegrity, usage.WebappBootstrapJSIntegrity)
cmd.PersistentFlags().String(config.Keys.WebappFontAwesomeCSSURI, values.WebappFontAwesomeCSSURI, usage.WebappFontAwesomeCSSURI)
cmd.PersistentFlags().String(config.Keys.WebappFontAwesomeCSSIntegrity, values.WebappFontAwesomeCSSIntegrity, usage.WebappFontAwesomeCSSIntegrity)
cmd.PersistentFlags().String(config.Keys.WebappLogoSrcDark, values.WebappLogoSrcDark, usage.WebappLogoSrcDark)
cmd.PersistentFlags().String(config.Keys.WebappLogoSrcLight, values.WebappLogoSrcLight, usage.WebappLogoSrcLight)
} }

View File

@ -10,15 +10,14 @@ var usage = config.KeyNames{
ApplicationName: "Name of the application, used in various places internally", ApplicationName: "Name of the application, used in various places internally",
// database // database
DbType: "Database type: eg., postgres", DbType: "Database type: eg., postgres",
DbAddress: "Database ipv4 address, hostname, or filename", DbAddress: "Database ipv4 address, hostname, or filename",
DbPort: "Database port", DbPort: "Database port",
DbUser: "Database username", DbUser: "Database username",
DbPassword: "Database password", DbPassword: "Database password",
DbDatabase: "Database name", DbDatabase: "Database name",
DbTLSMode: "Database tls mode", DbTLSMode: "Database tls mode",
DbTLSCACert: "Path to CA cert for db tls connection", DbTLSCACert: "Path to CA cert for db tls connection",
DbLoadTestData: "Should test data be loaded into the database",
// server // server
ServerExternalHostname: "The external hostname used by the server", ServerExternalHostname: "The external hostname used by the server",

View File

@ -48,6 +48,7 @@ func main() {
} }
// add commands // add commands
rootCmd.AddCommand(accountCommands())
rootCmd.AddCommand(serverCommands()) rootCmd.AddCommand(serverCommands())
rootCmd.AddCommand(databaseCommands()) rootCmd.AddCommand(databaseCommands())

View File

@ -19,6 +19,12 @@ services:
environment: environment:
- FAKTORY_PASSWORD=test - FAKTORY_PASSWORD=test
restart: always restart: always
redis:
image: redis:6
command: redis-server --requirepass test
ports:
- "127.0.0.1:6379:6379/tcp"
restart: always
influxdb: influxdb:
image: influxdb:2.1 image: influxdb:2.1

58
go.mod
View File

@ -3,40 +3,50 @@ module github.com/feditools/relay
go 1.18 go 1.18
require ( require (
github.com/cactus/go-statsd-client/v5 v5.0.0
github.com/contribsys/faktory v1.6.1 github.com/contribsys/faktory v1.6.1
github.com/contribsys/faktory_worker_go v1.6.0 github.com/contribsys/faktory_worker_go v1.6.0
github.com/feditools/go-lib v0.12.0
github.com/go-fed/activity v1.0.0 github.com/go-fed/activity v1.0.0
github.com/go-fed/httpsig v1.1.0 github.com/go-fed/httpsig v1.1.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-playground/validator/v10 v10.11.0 github.com/go-playground/validator/v10 v10.11.0
github.com/go-redis/redis/v8 v8.3.3
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.5.1 github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/sessions v1.2.1
github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/golang-lru v0.5.4
github.com/jackc/pgconn v1.12.1 github.com/jackc/pgconn v1.12.1
github.com/jackc/pgx/v4 v4.16.1 github.com/jackc/pgx/v4 v4.16.1
github.com/rbcervilla/redisstore/v8 v8.1.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/speps/go-hashids/v2 v2.0.1
github.com/spf13/cobra v1.5.0 github.com/spf13/cobra v1.5.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.12.0 github.com/spf13/viper v1.12.0
github.com/tyrm/go-util v0.4.2 github.com/tdewolff/minify/v2 v2.12.0
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc
github.com/uptrace/bun v1.1.6 github.com/uptrace/bun v1.1.6
github.com/uptrace/bun/dialect/pgdialect v1.1.6 github.com/uptrace/bun/dialect/pgdialect v1.1.6
github.com/uptrace/bun/dialect/sqlitedialect v1.1.6 github.com/uptrace/bun/dialect/sqlitedialect v1.1.6
github.com/uptrace/bun/extra/bundebug v1.1.6 github.com/uptrace/bun/extra/bundebug v1.1.6
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
modernc.org/sqlite v1.17.0 modernc.org/sqlite v1.17.1
) )
require ( require (
github.com/cactus/go-statsd-client/v5 v5.0.0 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.13.0 // indirect github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect
github.com/gin-gonic/gin v1.7.7 // indirect github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect
@ -46,15 +56,14 @@ require (
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.11.0 // indirect github.com/jackc/pgtype v1.11.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/magiconair/properties v1.8.6 // indirect github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-mastodon v0.0.5-0.20211214115546-7745e19ff787 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/nicksnyder/go-i18n/v2 v2.2.0 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
@ -62,27 +71,28 @@ require (
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.3.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/tdewolff/parse/v2 v2.6.1 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect go.opentelemetry.io/otel v0.13.0 // indirect
golang.org/x/mod v0.5.1 // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.0 // indirect golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.1.1 // indirect lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.35.26 // indirect modernc.org/cc/v3 v3.36.0 // indirect
modernc.org/ccgo/v3 v3.16.2 // indirect modernc.org/ccgo/v3 v3.16.6 // indirect
modernc.org/libc v1.15.0 // indirect modernc.org/libc v1.16.2 // indirect
modernc.org/mathutil v1.4.1 // indirect modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.0.7 // indirect modernc.org/memory v1.1.1 // indirect
modernc.org/opt v0.1.1 // indirect modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.1 // indirect modernc.org/strutil v1.1.1 // indirect
modernc.org/token v1.0.0 // indirect modernc.org/token v1.0.0 // indirect
) )

307
go.sum
View File

@ -37,12 +37,19 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/cactus/go-statsd-client/v5 v5.0.0 h1:KqvIQtc9qt34uq+nu4nd1PwingWfBt/IISgtUQ2nSJk= github.com/cactus/go-statsd-client/v5 v5.0.0 h1:KqvIQtc9qt34uq+nu4nd1PwingWfBt/IISgtUQ2nSJk=
github.com/cactus/go-statsd-client/v5 v5.0.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY= github.com/cactus/go-statsd-client/v5 v5.0.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -58,6 +65,8 @@ github.com/contribsys/faktory_worker_go v1.6.0 h1:ov69BLHL62i/wRLJwvuj5UphwgjMOI
github.com/contribsys/faktory_worker_go v1.6.0/go.mod h1:XMNGn3sBJdqFGfTH4SkmYkMovhdkq5cDJj36wowfbNY= github.com/contribsys/faktory_worker_go v1.6.0/go.mod h1:XMNGn3sBJdqFGfTH4SkmYkMovhdkq5cDJj36wowfbNY=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -65,6 +74,9 @@ github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhr
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -75,15 +87,16 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/feditools/go-lib v0.12.0 h1:T10Cy3GKPFMOmeKCz/b0KHgt5tN15mj5+5wNX4SmznI=
github.com/feditools/go-lib v0.12.0/go.mod h1:LtdLBvApYAhdblS6rTGQ12ZzySI9/6PyTiZlxw5uX10=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
github.com/go-fed/activity v1.0.0 h1:j7w3auHZnVCjUcgA1mE+UqSOjFBhvW2Z2res3vNol+o= github.com/go-fed/activity v1.0.0 h1:j7w3auHZnVCjUcgA1mE+UqSOjFBhvW2Z2res3vNol+o=
github.com/go-fed/activity v1.0.0/go.mod h1:v4QoPaAzjWZ8zN2VFVGL5ep9C02mst0hQYHUpQwso4Q= github.com/go-fed/activity v1.0.0/go.mod h1:v4QoPaAzjWZ8zN2VFVGL5ep9C02mst0hQYHUpQwso4Q=
github.com/go-fed/httpsig v0.1.1-0.20190914113940-c2de3672e5b5/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= github.com/go-fed/httpsig v0.1.1-0.20190914113940-c2de3672e5b5/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
@ -92,19 +105,24 @@ github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 h1:zga7zaRE8HCbWjcXMDlfvmQtH0/kMVLo7cQ48dy6kWg=
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1/go.mod h1:PumS+5d59wmAGsZo6IfRpVNaJUq+6xjC4Utt/k8GO6Q=
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 h1:O6yi4xa9b2DMosGsXzlMe2E9qXgXCVkRLCoRX+5amxI=
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27/go.mod h1:AYvN8omj7nKLmbcXS2dyABYU6JB1Lz1bHmkkq1kf4I4=
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno=
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-redis/redis/v8 v8.3.3 h1:e0CL9fsFDK92pkIJH2XAeS/NwO2VuIOAoJvI6yktZFk=
github.com/go-redis/redis/v8 v8.3.3/go.mod h1:jszGxBCez8QA1HWSmQxJO9Y82kNibbUmeYhKWrBejTU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
@ -136,9 +154,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -151,9 +166,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/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.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 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -178,12 +191,20 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@ -238,9 +259,6 @@ github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 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/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
@ -258,7 +276,6 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -268,26 +285,41 @@ github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-mastodon v0.0.5-0.20211214115546-7745e19ff787 h1:5CxQvPUr7Aupo8RFBcJM6A3yuK491j7WEx/jgQY/zr0=
github.com/mattn/go-mastodon v0.0.5-0.20211214115546-7745e19ff787/go.mod h1:D8ScK24P+nSB6g5xdsi/m40TIWispU4yyhJw9rGgHx4=
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/nicksnyder/go-i18n/v2 v2.2.0 h1:MNXbyPvd141JJqlU6gJKrczThxJy+kdCNivxZpBQFkw=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/nicksnyder/go-i18n/v2 v2.2.0/go.mod h1:4OtLfzqyAxsscyCb//3gfqSvBc81gImX91LrZzczN1o=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
@ -300,6 +332,8 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rbcervilla/redisstore/v8 v8.1.0 h1:YmNOHjAIb7+DLbqLPxSFAxmbtXbDgFcY2/eXrf1KoEY=
github.com/rbcervilla/redisstore/v8 v8.1.0/go.mod h1:JGDqTj9JQ28J1c+2u3iEnOUBC7W5WMW/YRKLqRm0pOk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -309,15 +343,19 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/speps/go-hashids/v2 v2.0.1 h1:ViWOEqWES/pdOSq+C1SLVa8/Tnsd52XC34RY7lt7m4g=
github.com/speps/go-hashids/v2 v2.0.1/go.mod h1:47LKunwvDZki/uRVD6NImtyk712yFzIs3UF3KlHohGw=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
@ -343,15 +381,17 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=
github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
github.com/tdewolff/minify/v2 v2.12.0 h1:ZyvMKeciyR3vzJrK/oHyBcSmpttQ/V+ah7qOqTZclaU=
github.com/tdewolff/minify/v2 v2.12.0/go.mod h1:8mvf+KglD7XurfvvFZDUYvVURy6bA/r0oTvmakXMnyg=
github.com/tdewolff/parse/v2 v2.6.1 h1:RIfy1erADkO90ynJWvty8VIkqqKYRzf2iLp8ObG174I=
github.com/tdewolff/parse/v2 v2.6.1/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM=
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= 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/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/tyrm/go-util v0.4.2 h1:SFcHyY39tzl2v5tr35vVvRbaTDkLNQeaHgnCf0214D8= github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tyrm/go-util v0.4.2/go.mod h1:DPIb77f7bqnK517jXiV86pBmdQYRiuTY09YOCPZrxUA= github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/uptrace/bun v1.1.6 h1:vDJ1Qs6fXock5+q/PSOZZ7vZVZABmWkGlgZDUkJwbfc= github.com/uptrace/bun v1.1.6 h1:vDJ1Qs6fXock5+q/PSOZZ7vZVZABmWkGlgZDUkJwbfc=
github.com/uptrace/bun v1.1.6/go.mod h1:Z2Pd3cRvNKbrYuL6Gp1XGjA9QEYz+rDz5KkEi9MZLnQ= github.com/uptrace/bun v1.1.6/go.mod h1:Z2Pd3cRvNKbrYuL6Gp1XGjA9QEYz+rDz5KkEi9MZLnQ=
github.com/uptrace/bun/dialect/pgdialect v1.1.6 h1:mDGOPm9FkWaM2kQLddzcRW6juUdITJ1XPUr1vt2anYw= github.com/uptrace/bun/dialect/pgdialect v1.1.6 h1:mDGOPm9FkWaM2kQLddzcRW6juUdITJ1XPUr1vt2anYw=
@ -360,6 +400,7 @@ github.com/uptrace/bun/dialect/sqlitedialect v1.1.6 h1:+bFY45/nIA/yTGOPKOPUVKZJm
github.com/uptrace/bun/dialect/sqlitedialect v1.1.6/go.mod h1:lSX4rOiE1QevAtbevyrvtnK6fLkT08QGJn4/F8evygk= github.com/uptrace/bun/dialect/sqlitedialect v1.1.6/go.mod h1:lSX4rOiE1QevAtbevyrvtnK6fLkT08QGJn4/F8evygk=
github.com/uptrace/bun/extra/bundebug v1.1.6 h1:dlChNwf2nieAZwFpbd2ewrNp/0tUOyN5y8E/FODAZ6k= github.com/uptrace/bun/extra/bundebug v1.1.6 h1:dlChNwf2nieAZwFpbd2ewrNp/0tUOyN5y8E/FODAZ6k=
github.com/uptrace/bun/extra/bundebug v1.1.6/go.mod h1:Umf4HLt7D2zgnFFolJulu+bgIJmUs8WB6hybq11kPac= github.com/uptrace/bun/extra/bundebug v1.1.6/go.mod h1:Umf4HLt7D2zgnFFolJulu+bgIJmUs8WB6hybq11kPac=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= 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/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 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
@ -375,6 +416,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opentelemetry.io/otel v0.13.0 h1:2isEnyzjjJZq6r2EKMsFj4TxiQiexsM04AVhwbR/oBA=
go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@ -400,8 +443,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -435,10 +478,11 @@ 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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -460,16 +504,21 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -488,11 +537,13 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -505,8 +556,12 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -521,12 +576,12 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -536,15 +591,16 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I=
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -609,8 +665,9 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -707,20 +764,20 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@ -734,153 +791,43 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.36.0 h1:0kmRkTmqNidmu3c7BNDSdVHCxXCkWLmWmCIVX4LUboo=
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/ccgo/v3 v3.16.6 h1:3l18poV+iUemQ98O3X5OMr97LOqlzis+ytivU4NqGhA=
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.22/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.24/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.35.25/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.35.26 h1:S4B+fg6/9krLtfZ9lr7pfKiESopiv+Sm6lUUI3oc0fY=
modernc.org/cc/v3 v3.35.26/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4=
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
modernc.org/ccgo/v3 v3.15.12/go.mod h1:VFePOWoCd8uDGRJpq/zfJ29D0EVzMSyID8LCMWYbX6I=
modernc.org/ccgo/v3 v3.15.14/go.mod h1:144Sz2iBCKogb9OKwsu7hQEub3EVgOlyI8wMUPGKUXQ=
modernc.org/ccgo/v3 v3.15.15/go.mod h1:z5qltXjU4PJl0pE5nhYQCvA9DhPHiWsl5GWl89+NSYE=
modernc.org/ccgo/v3 v3.15.16/go.mod h1:XbKRMeMWMdq712Tr5ECgATYMrzJ+g9zAZEj2ktzBe24=
modernc.org/ccgo/v3 v3.15.17/go.mod h1:bofnFkpRFf5gLY+mBZIyTW6FEcp26xi2lgOFk2Rlvs0=
modernc.org/ccgo/v3 v3.15.19/go.mod h1:TDJj+DxR26pkDteH2E5WQDj/xlmtsX7JdzkJkaZhOVU=
modernc.org/ccgo/v3 v3.16.0/go.mod h1:w55kPTAqvRMAYS3Lwij6qhqIuBEYS3Z8QtDkjD8cnik=
modernc.org/ccgo/v3 v3.16.1/go.mod h1:w55kPTAqvRMAYS3Lwij6qhqIuBEYS3Z8QtDkjD8cnik=
modernc.org/ccgo/v3 v3.16.2 h1:FUklsEMps3Y2heuTOmn/l6mv83nQgCjW3nsU+1JXzuQ=
modernc.org/ccgo/v3 v3.16.2/go.mod h1:w55kPTAqvRMAYS3Lwij6qhqIuBEYS3Z8QtDkjD8cnik=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg= modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M= modernc.org/libc v1.16.2 h1:+0cTSYtYuX5PzWOcxc+U3LPRie63WJPLSmR/1yGokjg=
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU= modernc.org/libc v1.16.2/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
modernc.org/libc v1.12.0/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
modernc.org/libc v1.14.6/go.mod h1:2PJHINagVxO4QW/5OQdRrvMYo+bm5ClpUFfyXCYl9ak=
modernc.org/libc v1.14.7/go.mod h1:f8xfWXW8LW41qb4X5+huVQo5dcfPlq7Cbny2TDheMv0=
modernc.org/libc v1.14.8/go.mod h1:9+JCLb1MWSY23smyOpIPbd5ED+rSS/ieiDWUpdyO3mo=
modernc.org/libc v1.14.10/go.mod h1:y1MtIWhwpJFpLYm6grAThtuXJKEsY6xkdZmXbRngIdo=
modernc.org/libc v1.14.12/go.mod h1:fJdoe23MHu2ruPQkFPPqCpToDi5cckzsbmkI6Ez0LqQ=
modernc.org/libc v1.15.0 h1:/CTHjQ1QO5mkLDeQICuA9Vh0YvhQTMqtCF2urQTaod8=
modernc.org/libc v1.15.0/go.mod h1:H1OKCu+NYa9+uQG8WsP7DndMBP61I4PWH8ivWhbdoWQ=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8= modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU=
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM= modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.0.6/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.0.7 h1:UE3cxTRFa5tfUibAV7Jqq8P7zRY0OlJg+yWVIIaluEE=
modernc.org/memory v1.0.7/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.17.0 h1:yF5JlxCzQOn2WzyfGAPvHbMNx98ifXLno7a97qggXjE= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/sqlite v1.17.0/go.mod h1:yMNaeEckF88G+PcfRcZRwGE+XnBkzWl/j15bPsDm4QM= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.17.1 h1:jDhTlgJqYRhQyH4qinGzw/XUznohc1d8sy/r+/1C9bQ=
modernc.org/sqlite v1.17.1/go.mod h1:IzkwM+vJC9CUg3UfogunxU4Sim6N6cL46AVlIaG7nio=
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/tcl v1.12.0 h1:Mw2Ukszv5qZbwk/wC9HkDjxhPD4exnd/7/zVxqrB4rY= modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao=
modernc.org/tcl v1.12.0/go.mod h1:9zyAWctRV6IAkMTBeGLyYYqcBrTlVy3ubqiY3dzMfwI= modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.4.0 h1:IpbQb3bOi5Fz17UVGU/mSor8sKIu/7pdCsmGGnQHcxs= modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM=
modernc.org/z v1.4.0/go.mod h1:x6vxerH3hHCPGA3DAM5pERRzuyJEO4UGVfdQC4NZYl0= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -1,6 +1,10 @@
package config package config
const ( const (
// ServerRoleActivityPub represents the activity pub server role // ServerRoleActivityPub represents the activity pub server role.
ServerRoleActivityPub = "activity-pub" ServerRoleActivityPub = "activity-pub"
// ServerRoleStatic represents the static asset server role.
ServerRoleStatic = "static"
// ServerRoleWebapp represents the webapp server role.
ServerRoleWebapp = "webapp"
) )

View File

@ -8,24 +8,30 @@ type KeyNames struct {
// application // application
ActorKeySize string ActorKeySize string
ApplicationName string ApplicationName string
ApplicationWebsite string
CachedActivityLimit string CachedActivityLimit string
CachedActorLimit string CachedActorLimit string
CachedDigestLimit string CachedDigestLimit string
EncryptionKey string
SoftwareVersion string SoftwareVersion string
TokenSalt string
// database // database
DbType string DbType string
DbAddress string DbAddress string
DbPort string DbPort string
DbUser string DbUser string
DbPassword string DbPassword string
DbDatabase string DbDatabase string
DbTLSMode string DbTLSMode string
DbTLSCACert string DbTLSCACert string
DbLoadTestData string
DbEncryptionKey string
// running // redis
RedisAddress string
RedisDB string
RedisPassword string
// runner
RunnerConcurrency string RunnerConcurrency string
// server // server
@ -34,6 +40,20 @@ type KeyNames struct {
ServerMinifyHTML string ServerMinifyHTML string
ServerRoles string ServerRoles string
// webapp
WebappBootstrapCSSURI string
WebappBootstrapCSSIntegrity string
WebappBootstrapJSURI string
WebappBootstrapJSIntegrity string
WebappFontAwesomeCSSURI string
WebappFontAwesomeCSSIntegrity string
WebappLogoSrcDark string
WebappLogoSrcLight string
// account
Account string
AccountAddGroup string
// metrics // metrics
MetricsStatsDAddress string MetricsStatsDAddress string
MetricsStatsDPrefix string MetricsStatsDPrefix string
@ -47,22 +67,28 @@ var Keys = KeyNames{
// application // application
ActorKeySize: "actor-key-size", ActorKeySize: "actor-key-size",
ApplicationName: "application-name", ApplicationName: "application-name",
ApplicationWebsite: "application-website",
CachedActivityLimit: "cached-activity-limit", CachedActivityLimit: "cached-activity-limit",
CachedActorLimit: "cached-actor-limit", CachedActorLimit: "cached-actor-limit",
CachedDigestLimit: "cached-digest-limit", CachedDigestLimit: "cached-digest-limit",
EncryptionKey: "encryption-key",
SoftwareVersion: "software-version", // Set at build SoftwareVersion: "software-version", // Set at build
TokenSalt: "token-salt",
// database // database
DbType: "db-type", DbType: "db-type",
DbAddress: "db-address", DbAddress: "db-address",
DbPort: "db-port", DbPort: "db-port",
DbUser: "db-user", DbUser: "db-user",
DbPassword: "db-password", DbPassword: "db-password",
DbDatabase: "db-database", DbDatabase: "db-database",
DbTLSMode: "db-tls-mode", DbTLSMode: "db-tls-mode",
DbTLSCACert: "db-tls-ca-cert", DbTLSCACert: "db-tls-ca-cert",
DbLoadTestData: "test-data", // CLI only
DbEncryptionKey: "db-crypto-key", // redis
RedisAddress: "redis-address",
RedisDB: "redis-db",
RedisPassword: "redis-password",
// runner // runner
RunnerConcurrency: "runner-concurrency", RunnerConcurrency: "runner-concurrency",
@ -73,6 +99,20 @@ var Keys = KeyNames{
ServerMinifyHTML: "minify-html", ServerMinifyHTML: "minify-html",
ServerRoles: "server-role", ServerRoles: "server-role",
// webapp
WebappBootstrapCSSURI: "webapp-bootstrap-css-uri",
WebappBootstrapCSSIntegrity: "webapp-bootstrap-css-integrity",
WebappBootstrapJSURI: "webapp-bootstrap-js-uri",
WebappBootstrapJSIntegrity: "webapp-bootstrap-js-integrity",
WebappFontAwesomeCSSURI: "webapp-fontawesome-css-uri",
WebappFontAwesomeCSSIntegrity: "webapp-fontawesome-css-integrity",
WebappLogoSrcDark: "webapp-logo-src-dark",
WebappLogoSrcLight: "webapp-logo-src-light",
// account
Account: "account",
AccountAddGroup: "add-group",
// metrics // metrics
MetricsStatsDAddress: "statsd-addr", MetricsStatsDAddress: "statsd-addr",
MetricsStatsDPrefix: "statsd-prefix", MetricsStatsDPrefix: "statsd-prefix",

View File

@ -8,24 +8,30 @@ type Values struct {
// application // application
ActorKeySize int ActorKeySize int
ApplicationName string ApplicationName string
ApplicationWebsite string
CachedActivityLimit int CachedActivityLimit int
CachedActorLimit int CachedActorLimit int
CachedDigestLimit int CachedDigestLimit int
EncryptionKey string
SoftwareVersion string SoftwareVersion string
TokenSalt string
// database // database
DbType string DbType string
DbAddress string DbAddress string
DbPort int DbPort int
DbUser string DbUser string
DbPassword string DbPassword string
DbDatabase string DbDatabase string
DbTLSMode string DbTLSMode string
DbTLSCACert string DbTLSCACert string
DbLoadTestData bool
DbEncryptionKey string
// running // redis
RedisAddress string
RedisDB int
RedisPassword string
// runner
RunnerConcurrency int RunnerConcurrency int
// server // server
@ -34,6 +40,20 @@ type Values struct {
ServerMinifyHTML bool ServerMinifyHTML bool
ServerRoles []string ServerRoles []string
// webapp
WebappBootstrapCSSURI string
WebappBootstrapCSSIntegrity string
WebappBootstrapJSURI string
WebappBootstrapJSIntegrity string
WebappFontAwesomeCSSURI string
WebappFontAwesomeCSSIntegrity string
WebappLogoSrcDark string
WebappLogoSrcLight string
// account
Account string
AccountAddGroup []string
// metrics // metrics
MetricsStatsDAddress string MetricsStatsDAddress string
MetricsStatsDPrefix string MetricsStatsDPrefix string
@ -47,20 +67,24 @@ var Defaults = Values{
// application // application
ActorKeySize: 2048, ActorKeySize: 2048,
ApplicationName: "feditools-relay", ApplicationName: "feditools-relay",
ApplicationWebsite: "https://github.com/feditools/relay",
CachedActivityLimit: 1024, CachedActivityLimit: 1024,
CachedActorLimit: 1024, CachedActorLimit: 1024,
CachedDigestLimit: 1024, CachedDigestLimit: 1024,
// database // database
DbType: "postgres", DbType: "postgres",
DbAddress: "", DbAddress: "",
DbPort: 5432, DbPort: 5432,
DbUser: "", DbUser: "",
DbPassword: "", DbPassword: "",
DbDatabase: "relay", DbDatabase: "relay",
DbTLSMode: "disable", DbTLSMode: "disable",
DbTLSCACert: "", DbTLSCACert: "",
DbLoadTestData: false,
// redis
RedisAddress: "localhost:6379",
RedisDB: 0,
// runner // runner
RunnerConcurrency: 4, RunnerConcurrency: 4,
@ -71,8 +95,20 @@ var Defaults = Values{
ServerMinifyHTML: true, ServerMinifyHTML: true,
ServerRoles: []string{ ServerRoles: []string{
ServerRoleActivityPub, ServerRoleActivityPub,
ServerRoleStatic,
ServerRoleWebapp,
}, },
// webapp
WebappBootstrapCSSURI: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css",
WebappBootstrapCSSIntegrity: "sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor",
WebappBootstrapJSURI: "https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js",
WebappBootstrapJSIntegrity: "sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2",
WebappFontAwesomeCSSURI: "https://cdn.fedi.tools/vendor/fontawesome-free-6.1.1/css/all.min.css",
WebappFontAwesomeCSSIntegrity: "sha384-/frq1SRXYH/bSyou/HUp/hib7RVN1TawQYja658FEOodR/FQBKVqT9Ol+Oz3Olq5",
WebappLogoSrcDark: "https://cdn.fedi.tools/img/feditools-logo-dark.svg",
WebappLogoSrcLight: "https://cdn.fedi.tools/img/feditools-logo-light.svg",
// metrics // metrics
MetricsStatsDAddress: "localhost:8125", MetricsStatsDAddress: "localhost:8125",
MetricsStatsDPrefix: "relay", MetricsStatsDPrefix: "relay",

192
internal/db/bun/account.go Normal file
View File

@ -0,0 +1,192 @@
package bun
import (
"context"
"database/sql"
"errors"
libdatabase "github.com/feditools/go-lib/database"
"github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/models"
"github.com/uptrace/bun"
"time"
)
// CountAccounts returns the number of federated social account.
func (c *Client) CountAccounts(ctx context.Context) (int64, db.Error) {
metric := c.metrics.NewDBQuery("CountAccounts")
count, err := newAccountQ(c.bun, (*models.Account)(nil)).Count(ctx)
if err != nil {
go metric.Done(true)
return 0, c.bun.errProc(err)
}
go metric.Done(false)
return int64(count), nil
}
// CountAccountsForInstance returns the number of federated social account for an instance.
func (c *Client) CountAccountsForInstance(ctx context.Context, instanceID int64) (int64, db.Error) {
metric := c.metrics.NewDBQuery("CountAccountsForInstance")
count, err := newAccountQ(c.bun, (*models.Account)(nil)).Where("instance_id = ?", instanceID).Count(ctx)
if err != nil {
go metric.Done(true)
return 0, c.bun.errProc(err)
}
go metric.Done(false)
return int64(count), nil
}
// CreateAccount stores the federated social account.
func (c *Client) CreateAccount(ctx context.Context, account *models.Account) db.Error {
metric := c.metrics.NewDBQuery("CreateAccount")
if err := create(ctx, c.bun, account); err != nil {
go metric.Done(true)
return c.bun.errProc(err)
}
go metric.Done(false)
return nil
}
// IncAccountLoginCount updates the login count of a stored federated instance.
func (c *Client) IncAccountLoginCount(ctx context.Context, account *models.Account) db.Error {
metric := c.metrics.NewDBQuery("IncAccountLoginCount")
err := c.bun.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
err := tx.NewSelect().Model(account).Where("id = ?", account.ID).Scan(ctx)
if err != nil {
return err
}
account.LogInCount++
account.LogInLast = time.Now()
_, err = tx.NewUpdate().Model(account).Where("id = ?", account.ID).Exec(ctx)
if err != nil {
return err
}
return err
})
if err != nil {
go metric.Done(true)
return c.bun.errProc(err)
}
go metric.Done(false)
return nil
}
// ReadAccount returns one federated social account.
func (c *Client) ReadAccount(ctx context.Context, id int64) (*models.Account, db.Error) {
metric := c.metrics.NewDBQuery("ReadAccount")
account := new(models.Account)
err := newAccountQ(c.bun, account).Where("id = ?", id).Scan(ctx)
if err != nil {
dbErr := c.bun.ProcessError(err)
if errors.Is(dbErr, db.ErrNoEntries) {
// report no entries as a non error
go metric.Done(false)
} else {
go metric.Done(true)
}
return nil, dbErr
}
go metric.Done(false)
return account, nil
}
// ReadAccountByUsername returns one federated social account.
func (c *Client) ReadAccountByUsername(ctx context.Context, instanceID int64, username string) (*models.Account, db.Error) {
metric := c.metrics.NewDBQuery("ReadAccountByUsername")
account := new(models.Account)
err := newAccountQ(c.bun, account).
ColumnExpr("account.*").
Join("RIGHT JOIN instances").
JoinOn("account.instance_id = instances.id").
Where("instances.id = ?", instanceID).
Where("lower(account.username) = lower(?)", username).
Scan(ctx)
if err != nil {
dbErr := c.bun.ProcessError(err)
if errors.Is(dbErr, db.ErrNoEntries) {
// report no entries as a non error
go metric.Done(false)
} else {
go metric.Done(true)
}
return nil, dbErr
}
go metric.Done(false)
return account, nil
}
// ReadAccountsPage returns a page of federated social accounts.
func (c *Client) ReadAccountsPage(ctx context.Context, index, count int) ([]*models.Account, db.Error) {
metric := c.metrics.NewDBQuery("ReadAccountsPage")
var accounts []*models.Account
err := newAccountsQ(c.bun, &accounts).
Limit(count).
Offset(libdatabase.Offset(index, count)).
Scan(ctx)
if err != nil {
go metric.Done(true)
return nil, c.bun.ProcessError(err)
}
go metric.Done(false)
return accounts, nil
}
// UpdateAccount updates the stored federated social account.
func (c *Client) UpdateAccount(ctx context.Context, account *models.Account) db.Error {
metric := c.metrics.NewDBQuery("UpdateAccount")
if err := update(ctx, c.bun, account); err != nil {
go metric.Done(true)
return c.bun.errProc(err)
}
go metric.Done(false)
return nil
}
func newAccountQ(c bun.IDB, account *models.Account) *bun.SelectQuery {
return c.
NewSelect().
Model(account)
}
func newAccountsQ(c bun.IDB, accounts *[]*models.Account) *bun.SelectQuery {
return c.
NewSelect().
Model(accounts)
}

View File

@ -2,93 +2,186 @@ package bun
import ( import (
"context" "context"
"database/sql" "errors"
libdatabase "github.com/feditools/go-lib/database"
"github.com/feditools/relay/internal/db" "github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/models" "github.com/feditools/relay/internal/models"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"time"
) )
// CreateBlock stores the domain block // CountBlocks returns the number of domain blocks.
func (c *Client) CreateBlock(ctx context.Context, block *models.Block) db.Error { func (c *Client) CountBlocks(ctx context.Context) (int64, db.Error) {
start := time.Now() metric := c.metrics.NewDBQuery("CountBlocks")
err := c.Create(ctx, block) count, err := newBlockQ(c.bun, (*models.Block)(nil)).
Where("marked_for_deletion_on is NULL").
Count(ctx)
if err != nil { if err != nil {
ended := time.Since(start) go metric.Done(true)
go c.metrics.DBQuery(ended, "CreateBlock", true)
return 0, c.bun.errProc(err)
}
go metric.Done(false)
return int64(count), nil
}
// CreateBlock stores the domain block
func (c *Client) CreateBlock(ctx context.Context, blocks ...*models.Block) db.Error {
metric := c.metrics.NewDBQuery("CreateBlock")
if err := create(ctx, c.bun, &blocks); err != nil {
go metric.Done(true)
return c.bun.errProc(err) return c.bun.errProc(err)
} }
ended := time.Since(start) go metric.Done(false)
go c.metrics.DBQuery(ended, "CreateBlock", false)
return nil return nil
} }
// ReadBlockByID returns one domain block // DeleteBlock deletes the stored domain block
func (c *Client) ReadBlockByID(ctx context.Context, id int64) (*models.Block, db.Error) { func (c *Client) DeleteBlock(ctx context.Context, blocks ...*models.Block) db.Error {
start := time.Now() metric := c.metrics.NewDBQuery("DeleteBlock")
block := &models.Block{} if err := delete(ctx, c.bun, &blocks); err != nil {
go metric.Done(true)
err := c.newBlockQ(block).Where("id = ?", id).Scan(ctx) return c.bun.errProc(err)
if err == sql.ErrNoRows {
ended := time.Since(start)
go c.metrics.DBQuery(ended, "ReadBlockByID", false)
return nil, c.bun.ProcessError(err)
} }
go metric.Done(false)
return nil
}
// ReadBlock returns one domain block
func (c *Client) ReadBlock(ctx context.Context, id int64) (*models.Block, db.Error) {
metric := c.metrics.NewDBQuery("ReadBlock")
block := new(models.Block)
err := newBlockQ(c.bun, block).
Where("id = ?", id).
Scan(ctx)
if err != nil { if err != nil {
ended := time.Since(start) dbErr := c.bun.ProcessError(err)
go c.metrics.DBQuery(ended, "ReadBlockByID", true)
return nil, c.bun.ProcessError(err) if errors.Is(dbErr, db.ErrNoEntries) {
// report no entries as a non error
go metric.Done(false)
} else {
go metric.Done(true)
}
return nil, dbErr
} }
ended := time.Since(start) go metric.Done(false)
go c.metrics.DBQuery(ended, "ReadBlockByID", false)
return block, nil return block, nil
} }
// ReadBlockByDomain returns one domain block // ReadBlockByDomain returns one domain block
func (c *Client) ReadBlockByDomain(ctx context.Context, domain string) (*models.Block, db.Error) { func (c *Client) ReadBlockByDomain(ctx context.Context, domain string) (*models.Block, db.Error) {
start := time.Now() metric := c.metrics.NewDBQuery("ReadBlockByDomain")
block := &models.Block{} block := new(models.Block)
err := newBlockQ(c.bun, block).
err := c.newBlockQ(block).Where("lower(domain) = lower(?)", domain).Scan(ctx) Where("lower(domain) = lower(?)", domain).
if err == sql.ErrNoRows { Order("domain ASC").
ended := time.Since(start) Scan(ctx)
go c.metrics.DBQuery(ended, "ReadBlockByDomain", false)
return nil, c.bun.ProcessError(err)
}
if err != nil { if err != nil {
ended := time.Since(start) dbErr := c.bun.ProcessError(err)
go c.metrics.DBQuery(ended, "ReadBlockByDomain", true)
return nil, c.bun.ProcessError(err) if errors.Is(dbErr, db.ErrNoEntries) {
// report no entries as a non error
go metric.Done(false)
} else {
go metric.Done(true)
}
return nil, dbErr
} }
ended := time.Since(start) go metric.Done(false)
go c.metrics.DBQuery(ended, "ReadBlockByDomain", false)
return block, nil return block, nil
} }
// UpdateBlock updates the stored domain block // ReadBlocks returns all domain block
func (c *Client) UpdateBlock(ctx context.Context, block *models.Block) db.Error { func (c *Client) ReadBlocks(ctx context.Context) ([]*models.Block, db.Error) {
start := time.Now() metric := c.metrics.NewDBQuery("ReadBlocks")
err := c.Update(ctx, block) var blocks []*models.Block
err := newBlocksQ(c.bun, &blocks).
Where("marked_for_deletion_on is NULL").
Order("domain ASC").
Scan(ctx)
if err != nil { if err != nil {
ended := time.Since(start) dbErr := c.bun.ProcessError(err)
go c.metrics.DBQuery(ended, "UpdateBlock", true)
if errors.Is(dbErr, db.ErrNoEntries) {
// report no entries as a non error
go metric.Done(false)
} else {
go metric.Done(true)
}
return nil, dbErr
}
go metric.Done(false)
return blocks, nil
}
// ReadBlocksPage returns a page of domain blocks.
func (c *Client) ReadBlocksPage(ctx context.Context, index, count int) ([]*models.Block, db.Error) {
metric := c.metrics.NewDBQuery("ReadAccountsPage")
var blocks []*models.Block
err := newBlocksQ(c.bun, &blocks).
Where("marked_for_deletion_on is NULL").
Order("domain ASC").
Limit(count).
Offset(libdatabase.Offset(index, count)).
Scan(ctx)
if err != nil {
go metric.Done(true)
return nil, c.bun.ProcessError(err)
}
go metric.Done(false)
return blocks, nil
}
// UpdateBlock updates the stored domain block
func (c *Client) UpdateBlock(ctx context.Context, blocks ...*models.Block) db.Error {
metric := c.metrics.NewDBQuery("UpdateBlock")
if err := update(ctx, c.bun, &blocks); err != nil {
go metric.Done(true)
return c.bun.errProc(err) return c.bun.errProc(err)
} }
ended := time.Since(start) go metric.Done(false)
go c.metrics.DBQuery(ended, "UpdateBlock", false)
return nil return nil
} }
func (c *Client) newBlockQ(block *models.Block) *bun.SelectQuery { func newBlockQ(c bun.IDB, block *models.Block) *bun.SelectQuery {
return c.bun. return c.
NewSelect(). NewSelect().
Model(block) Model(block)
} }
func newBlocksQ(c bun.IDB, blocks *[]*models.Block) *bun.SelectQuery {
return c.
NewSelect().
Model(blocks)
}

View File

@ -8,9 +8,9 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"github.com/feditools/go-lib/metrics"
"github.com/feditools/relay/internal/config" "github.com/feditools/relay/internal/config"
"github.com/feditools/relay/internal/db" "github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/metrics"
"github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/stdlib" "github.com/jackc/pgx/v4/stdlib"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -48,8 +48,6 @@ type Client struct {
metrics metrics.Collector metrics metrics.Collector
} }
var _ db.DB = (*Client)(nil)
// New creates a new bun database client // New creates a new bun database client
func New(ctx context.Context, m metrics.Collector) (*Client, error) { func New(ctx context.Context, m metrics.Collector) (*Client, error) {
var newBun *Bun var newBun *Bun

View File

@ -4,10 +4,9 @@ package bun
import ( import (
"context" "context"
"github.com/feditools/go-lib/mock"
"github.com/feditools/relay/internal/config" "github.com/feditools/relay/internal/config"
"github.com/feditools/relay/internal/db" "github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/mock"
"github.com/feditools/relay/internal/models/testdata"
"github.com/spf13/viper" "github.com/spf13/viper"
"testing" "testing"
) )
@ -84,7 +83,6 @@ func testNewPostresClient() (db.DB, error) {
viper.Set(config.Keys.DbPassword, "test") viper.Set(config.Keys.DbPassword, "test")
viper.Set(config.Keys.DbPort, 5432) viper.Set(config.Keys.DbPort, 5432)
viper.Set(config.Keys.DbUser, "test") viper.Set(config.Keys.DbUser, "test")
viper.Set(config.Keys.DbEncryptionKey, testdata.TestEncryptionKey)
metricsCollector, _ := mock.NewMetricsCollector() metricsCollector, _ := mock.NewMetricsCollector()
@ -98,10 +96,5 @@ func testNewPostresClient() (db.DB, error) {
return nil, err return nil, err
} }
err = client.LoadTestData(context.Background())
if err != nil {
return nil, err
}
return client, nil return client, nil
} }

View File

@ -5,10 +5,9 @@ import (
"crypto/tls" "crypto/tls"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/feditools/go-lib/mock"
"github.com/feditools/relay/internal/config" "github.com/feditools/relay/internal/config"
"github.com/feditools/relay/internal/db" "github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/mock"
"github.com/feditools/relay/internal/models/testdata"
"github.com/jackc/pgconn" "github.com/jackc/pgconn"
"github.com/spf13/viper" "github.com/spf13/viper"
"testing" "testing"
@ -377,7 +376,6 @@ func testNewSqliteClient() (db.DB, error) {
viper.Set(config.Keys.DbType, "sqlite") viper.Set(config.Keys.DbType, "sqlite")
viper.Set(config.Keys.DbAddress, ":memory:") viper.Set(config.Keys.DbAddress, ":memory:")
viper.Set(config.Keys.DbEncryptionKey, testdata.TestEncryptionKey)
metricsCollector, _ := mock.NewMetricsCollector() metricsCollector, _ := mock.NewMetricsCollector()
@ -391,7 +389,6 @@ func testNewSqliteClient() (db.DB, error) {
return nil, err return nil, err
} }
err = client.LoadTestData(context.Background())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2,13 +2,9 @@ package bun
import ( import (
"context" "context"
"errors"
"fmt"
"github.com/feditools/relay/internal/db" "github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/db/bun/migrations" "github.com/feditools/relay/internal/db/bun/migrations"
"github.com/feditools/relay/internal/models" "github.com/uptrace/bun"
"github.com/feditools/relay/internal/models/testdata"
"github.com/uptrace/bun/dialect"
"github.com/uptrace/bun/migrate" "github.com/uptrace/bun/migrate"
) )
@ -19,15 +15,6 @@ func (c *Client) Close(ctx context.Context) db.Error {
return c.bun.Close() return c.bun.Close()
} }
// Create inserts an object into the database
func (c *Client) Create(ctx context.Context, i any) db.Error {
_, err := c.bun.NewInsert().Model(i).Exec(ctx)
if err != nil {
logger.WithField("func", "Create").Errorf("db: %s", err.Error())
}
return c.bun.ProcessError(err)
}
// DoMigration runs schema migrations on the database // DoMigration runs schema migrations on the database
func (c *Client) DoMigration(ctx context.Context) db.Error { func (c *Client) DoMigration(ctx context.Context) db.Error {
l := logger.WithField("func", "DoMigration") l := logger.WithField("func", "DoMigration")
@ -55,76 +42,31 @@ func (c *Client) DoMigration(ctx context.Context) db.Error {
return nil return nil
} }
// LoadTestData adds test data to the database func create(ctx context.Context, c bun.IDB, i interface{}) error {
func (c *Client) LoadTestData(ctx context.Context) db.Error { _, err := c.NewInsert().Model(i).Exec(ctx)
l := logger.WithField("func", "LoadTestData") if err != nil {
l.Debugf("adding test data") logger.WithField("func", "create").Error(err.Error())
// Truncate
modelList := []interface{}{
&models.Instance{},
} }
for _, m := range modelList { return err
l.Debugf("truncating %T", m)
_, err := c.bun.NewTruncateTable().Model(m).Exec(ctx)
if err != nil {
l.Errorf("truncating %T: %s", m, err.Error())
return err
}
}
// Create Instances
l.Debugf("creating %d Instances", len(testdata.TestInstances))
for i := 0; i < len(testdata.TestInstances); i++ {
err := c.Create(ctx, testdata.TestInstances[i])
if err != nil {
l.Errorf("[%d] creating Instances: %s", i, err.Error())
return err
}
}
// fix sequences
sequences := []struct {
table string
currentValue int
}{
{
table: "instances",
currentValue: len(testdata.TestInstances),
},
}
switch c.bun.Dialect().Name() {
case dialect.SQLite:
// nothing to do
case dialect.PG:
for _, s := range sequences {
_, err := c.bun.Exec("SELECT setval(?, ?, true);", fmt.Sprintf("%s_id_seq", s.table), s.currentValue)
if err != nil {
l.Errorf("can't update sequence for %s: %s", s.table, err.Error())
return err
}
}
default:
return errors.New("unknown dialect")
}
return nil
} }
// ReadByID returns a model by its ID func delete(ctx context.Context, c bun.IDB, i interface{}) error {
func (c *Client) ReadByID(ctx context.Context, id int64, i any) db.Error { _, err := c.NewDelete().Model(i).Exec(ctx)
q := c.bun.NewSelect().Model(i).Where("id = ?", id) if err != nil {
logger.WithField("func", "delete").Error(err.Error())
}
err := q.Scan(ctx) return err
return c.bun.ProcessError(err)
} }
// Update updates stored data func update(ctx context.Context, c bun.IDB, i interface{}) error {
func (c *Client) Update(ctx context.Context, i any) db.Error { q := c.NewUpdate().Model(i).WherePK()
q := c.bun.NewUpdate().Model(i).WherePK()
_, err := q.Exec(ctx) _, err := q.Exec(ctx)
return c.bun.ProcessError(err) if err != nil {
logger.WithField("func", "update").Error(err.Error())
}
return err
} }

View File

@ -2,140 +2,248 @@ package bun
import ( import (
"context" "context"
"database/sql" "errors"
"fmt"
libdatabase "github.com/feditools/go-lib/database"
"github.com/feditools/relay/internal/db" "github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/models" "github.com/feditools/relay/internal/models"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"time"
) )
// CountInstances returns the number of federated instances.
func (c *Client) CountInstances(ctx context.Context) (int64, db.Error) {
metric := c.metrics.NewDBQuery("CountInstances")
count, err := newInstanceQ(c.bun, (*models.Instance)(nil)).
Count(ctx)
if err != nil {
go metric.Done(true)
return 0, c.bun.errProc(err)
}
go metric.Done(false)
return int64(count), nil
}
// CreateInstance stores the federated instance // CreateInstance stores the federated instance
func (c *Client) CreateInstance(ctx context.Context, instance *models.Instance) db.Error { func (c *Client) CreateInstance(ctx context.Context, instance *models.Instance) db.Error {
start := time.Now() metric := c.metrics.NewDBQuery("CreateInstance")
if err := create(ctx, c.bun, instance); err != nil {
go metric.Done(true)
err := c.Create(ctx, instance)
if err != nil {
ended := time.Since(start)
go c.metrics.DBQuery(ended, "CreateInstance", true)
return c.bun.errProc(err) return c.bun.errProc(err)
} }
ended := time.Since(start) go metric.Done(false)
go c.metrics.DBQuery(ended, "CreateInstance", false)
return nil return nil
} }
// ReadInstanceByID returns one federated social instance // ReadInstance returns one federated social instance
func (c *Client) ReadInstanceByID(ctx context.Context, id int64) (*models.Instance, db.Error) { func (c *Client) ReadInstance(ctx context.Context, id int64) (*models.Instance, db.Error) {
start := time.Now() metric := c.metrics.NewDBQuery("ReadInstance")
instance := &models.Instance{} instance := new(models.Instance)
err := newInstanceQ(c.bun, instance).
err := c.newInstanceQ(instance).Where("id = ?", id).Scan(ctx) Where("id = ?", id).
if err == sql.ErrNoRows { Scan(ctx)
ended := time.Since(start)
go c.metrics.DBQuery(ended, "ReadInstanceByID", false)
return nil, c.bun.ProcessError(err)
}
if err != nil { if err != nil {
ended := time.Since(start) dbErr := c.bun.ProcessError(err)
go c.metrics.DBQuery(ended, "ReadInstanceByID", true)
return nil, c.bun.ProcessError(err) if errors.Is(dbErr, db.ErrNoEntries) {
// report no entries as a non error
go metric.Done(false)
} else {
go metric.Done(true)
}
return nil, dbErr
} }
ended := time.Since(start) go metric.Done(false)
go c.metrics.DBQuery(ended, "ReadInstanceByID", false)
return instance, nil return instance, nil
} }
// ReadInstanceByActorIRI returns one federated social instance // ReadInstanceByActorIRI returns one federated social instance
func (c *Client) ReadInstanceByActorIRI(ctx context.Context, actorIRI string) (*models.Instance, db.Error) { func (c *Client) ReadInstanceByActorIRI(ctx context.Context, actorIRI string) (*models.Instance, db.Error) {
start := time.Now() metric := c.metrics.NewDBQuery("ReadInstanceByActorIRI")
instance := new(models.Instance) instance := new(models.Instance)
err := newInstanceQ(c.bun, instance).
err := c.newInstanceQ(instance).Where("actor_iri = ?", actorIRI).Scan(ctx) Where("actor_iri = ?", actorIRI).
if err == sql.ErrNoRows { Scan(ctx)
ended := time.Since(start)
go c.metrics.DBQuery(ended, "ReadInstanceByActorIRI", false)
return nil, c.bun.ProcessError(err)
}
if err != nil { if err != nil {
ended := time.Since(start) dbErr := c.bun.ProcessError(err)
go c.metrics.DBQuery(ended, "ReadInstanceByActorIRI", true)
return nil, c.bun.ProcessError(err) if errors.Is(dbErr, db.ErrNoEntries) {
// report no entries as a non error
go metric.Done(false)
} else {
go metric.Done(true)
}
return nil, dbErr
} }
ended := time.Since(start) go metric.Done(false)
go c.metrics.DBQuery(ended, "ReadInstanceByActorIRI", false)
return instance, nil return instance, nil
} }
// ReadInstanceByDomain returns one federated social instance // ReadInstanceByDomain returns one federated social instance
func (c *Client) ReadInstanceByDomain(ctx context.Context, domain string) (*models.Instance, db.Error) { func (c *Client) ReadInstanceByDomain(ctx context.Context, domain string) (*models.Instance, db.Error) {
start := time.Now() metric := c.metrics.NewDBQuery("ReadInstanceByDomain")
instance := &models.Instance{} instance := new(models.Instance)
err := newInstanceQ(c.bun, instance).
err := c.newInstanceQ(instance).Where("lower(domain) = lower(?)", domain).Scan(ctx) Where("lower(domain) = lower(?)", domain).
if err == sql.ErrNoRows { Scan(ctx)
ended := time.Since(start)
go c.metrics.DBQuery(ended, "ReadInstanceByDomain", false)
return nil, c.bun.ProcessError(err)
}
if err != nil { if err != nil {
ended := time.Since(start) dbErr := c.bun.ProcessError(err)
go c.metrics.DBQuery(ended, "ReadInstanceByDomain", true)
if errors.Is(dbErr, db.ErrNoEntries) {
// report no entries as a non error
go metric.Done(false)
} else {
go metric.Done(true)
}
return nil, dbErr
}
go metric.Done(false)
return instance, nil
}
// ReadInstancesPage returns a page of domain blocks.
func (c *Client) ReadInstancesPage(ctx context.Context, index, count int) ([]*models.Instance, db.Error) {
metric := c.metrics.NewDBQuery("ReadInstancesPage")
var instances []*models.Instance
err := newInstancesQ(c.bun, &instances).
Where("private_key is NULL").
Order("domain ASC").
Limit(count).
Offset(libdatabase.Offset(index, count)).
Scan(ctx)
if err != nil {
go metric.Done(true)
return nil, c.bun.ProcessError(err) return nil, c.bun.ProcessError(err)
} }
ended := time.Since(start) go metric.Done(false)
go c.metrics.DBQuery(ended, "ReadInstanceByDomain", false)
return instance, nil return instances, nil
}
// ReadInstancesWithBlockID returns all instances with domain suffix
func (c *Client) ReadInstancesWithBlockID(ctx context.Context, blockID int64) ([]*models.Instance, db.Error) {
metric := c.metrics.NewDBQuery("ReadInstancesWithBlockID")
var instances []*models.Instance
err := newInstancesQ(c.bun, &instances).
Where("block_id = ?", blockID).
Scan(ctx)
if err != nil {
dbErr := c.bun.ProcessError(err)
if errors.Is(dbErr, db.ErrNoEntries) {
// report no entries as a non error
go metric.Done(false)
} else {
go metric.Done(true)
}
return nil, dbErr
}
go metric.Done(false)
return instances, nil
}
// ReadInstancesWithDomainSuffix returns all instances with domain suffix
func (c *Client) ReadInstancesWithDomainSuffix(ctx context.Context, domainSuffix string) ([]*models.Instance, db.Error) {
metric := c.metrics.NewDBQuery("ReadInstancesWithDomainSuffix")
var instances []*models.Instance
err := newInstancesQ(c.bun, &instances).
Where("domain = ?", domainSuffix).
WhereOr("domain LIKE ?", fmt.Sprintf("%%.%s", domainSuffix)).
Scan(ctx)
if err != nil {
dbErr := c.bun.ProcessError(err)
if errors.Is(dbErr, db.ErrNoEntries) {
// report no entries as a non error
go metric.Done(false)
} else {
go metric.Done(true)
}
return nil, dbErr
}
go metric.Done(false)
return instances, nil
} }
// ReadInstancesWhereFollowing returns all federated social instances which are following this relay // ReadInstancesWhereFollowing returns all federated social instances which are following this relay
func (c *Client) ReadInstancesWhereFollowing(ctx context.Context) ([]*models.Instance, db.Error) { func (c *Client) ReadInstancesWhereFollowing(ctx context.Context) ([]*models.Instance, db.Error) {
start := time.Now() metric := c.metrics.NewDBQuery("ReadInstancesWhereFollowing")
var instances []*models.Instance var instances []*models.Instance
err := newInstancesQ(c.bun, &instances).
err := c.newInstancesQ(&instances).Where("followed = true").Scan(ctx) Where("followed = true").
Scan(ctx)
if err != nil { if err != nil {
ended := time.Since(start) dbErr := c.bun.ProcessError(err)
go c.metrics.DBQuery(ended, "ReadInstancesWhereFollowing", true)
return nil, c.bun.ProcessError(err) if errors.Is(dbErr, db.ErrNoEntries) {
// report no entries as a non error
go metric.Done(false)
} else {
go metric.Done(true)
}
return nil, dbErr
} }
ended := time.Since(start) go metric.Done(false)
go c.metrics.DBQuery(ended, "ReadInstancesWhereFollowing", false)
return instances, nil return instances, nil
} }
// UpdateInstance updates the stored federated instance // UpdateInstance updates the stored federated instance
func (c *Client) UpdateInstance(ctx context.Context, instance *models.Instance) db.Error { func (c *Client) UpdateInstance(ctx context.Context, instance *models.Instance) db.Error {
start := time.Now() metric := c.metrics.NewDBQuery("UpdateInstance")
if err := update(ctx, c.bun, instance); err != nil {
go metric.Done(true)
err := c.Update(ctx, instance)
if err != nil {
ended := time.Since(start)
go c.metrics.DBQuery(ended, "UpdateInstance", true)
return c.bun.errProc(err) return c.bun.errProc(err)
} }
ended := time.Since(start) go metric.Done(false)
go c.metrics.DBQuery(ended, "UpdateInstance", false)
return nil return nil
} }
func (c *Client) newInstanceQ(instance *models.Instance) *bun.SelectQuery { func newInstanceQ(c bun.IDB, instance *models.Instance) *bun.SelectQuery {
return c.bun. return c.
NewSelect(). NewSelect().
Model(instance) Model(instance)
} }
func (c *Client) newInstancesQ(instances *[]*models.Instance) *bun.SelectQuery { func newInstancesQ(c bun.IDB, instances *[]*models.Instance) *bun.SelectQuery {
return c.bun. return c.
NewSelect(). NewSelect().
Model(instances) Model(instances)
} }

View File

@ -13,6 +13,7 @@ func init() {
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
modelList := []interface{}{ modelList := []interface{}{
&models.Instance{}, &models.Instance{},
&models.Account{},
&models.Block{}, &models.Block{},
} }
for _, i := range modelList { for _, i := range modelList {

View File

@ -0,0 +1,20 @@
package models
import "time"
// Account represents a federated social account.
type Account struct {
ID int64 `validate:"-" bun:"id,pk,autoincrement"`
CreatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"`
UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"`
ActorURI string `validate:"url" bun:",nullzero,notnull"`
Username string `validate:"-" bun:",unique:unique_fedi_user,nullzero,notnull"`
InstanceID int64 `validate:"-" bun:",unique:unique_fedi_user,nullzero,notnull"`
Instance *Instance `validate:"-" bun:"rel:belongs-to,join:instance_id=id"`
DisplayName string `validate:"-" bun:",nullzero"`
LastFinger time.Time `validate:"-" bun:",notnull"`
LogInCount int64 `validate:"-" bun:",notnull"`
LogInLast time.Time `validate:"-" bun:",nullzero"`
IsAdmin bool `validate:"-" bun:",notnull"`
IsMod bool `validate:"-" bun:",notnull"`
}

View File

@ -4,9 +4,11 @@ import "time"
// Block represents a block of a domain // Block represents a block of a domain
type Block struct { type Block struct {
ID int64 `validate:"-" bun:",pk,autoincrement,nullzero,notnull,unique"` ID int64 `validate:"-" bun:",pk,autoincrement,nullzero,notnull,unique"`
CreatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` CreatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"`
UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"`
Domain string `validate:"required,fqdn" bun:",nullzero,notnull,unique"` MarkedForDeletionOn time.Time `validate:"-" bun:",nullzero"`
BlockSubdomains bool `validate:"-" bun:",notnull,default:true"` Domain string `validate:"required,fqdn" bun:",nullzero,notnull,unique"`
ObfuscatedDomain string `validate:"-" bun:",nullzero"`
BlockSubdomains bool `validate:"-" bun:",notnull"`
} }

View File

@ -7,16 +7,17 @@ import (
// Instance represents a federated social instance // Instance represents a federated social instance
type Instance struct { type Instance struct {
ID int64 `validate:"-" bun:",pk,autoincrement,nullzero,notnull,unique"` ID int64 `validate:"-" bun:",pk,autoincrement,nullzero,notnull,unique"`
CreatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` CreatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"`
UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"`
AccountDomain string `validate:"required,fqdn" bun:",nullzero,notnull,unique"` Domain string `validate:"required,fqdn" bun:",nullzero,notnull,unique"`
Domain string `validate:"required,fqdn" bun:",nullzero,notnull,unique"` ServerHostname string `validate:"required,fqdn" bun:",nullzero,notnull,unique"`
PublicKey *rsa.PublicKey `validate:"-"` Software string `validate:"-" bun:",nullzero"`
PrivateKey *rsa.PrivateKey `validate:"-"` PublicKey *rsa.PublicKey `validate:"-"`
ActorIRI string `validate:"required,url" bun:",nullzero,notnull,unique"` PrivateKey *rsa.PrivateKey `validate:"-"`
InboxIRI string `validate:"required,url" bun:",nullzero,notnull,unique"` ActorIRI string `validate:"required,url" bun:",nullzero,notnull,unique"`
Followed bool `validate:"-" bun:",notnull,default:false"` InboxIRI string `validate:"required,url" bun:",nullzero,notnull,unique"`
BlockID int64 `validate:"-" bun:",nullzero"` Followed bool `validate:"-" bun:",notnull,default:false"`
Block *Block `validate:"-" bun:"rel:belongs-to"` BlockID int64 `validate:"-" bun:",nullzero"`
Block *Block `validate:"-" bun:"rel:belongs-to"`
} }

View File

@ -9,38 +9,65 @@ import (
type DB interface { type DB interface {
// Close closes the db connections // Close closes the db connections
Close(ctx context.Context) Error Close(ctx context.Context) Error
// Create stores the object
Create(ctx context.Context, i any) Error
// DoMigration runs database migrations // DoMigration runs database migrations
DoMigration(ctx context.Context) Error DoMigration(ctx context.Context) Error
// LoadTestData adds test data to the database
LoadTestData(ctx context.Context) Error // Accounts
// ReadByID returns a model by its ID
ReadByID(ctx context.Context, id int64, i any) Error // CountAccounts returns the number of federated social account
// Update updates stored data CountAccounts(ctx context.Context) (count int64, err Error)
Update(ctx context.Context, i any) Error // CountAccountsForInstance returns the number of federated social account for an instance
CountAccountsForInstance(ctx context.Context, instanceID int64) (count int64, err Error)
// CreateAccount stores the federated social account
CreateAccount(ctx context.Context, account *models.Account) (err Error)
// IncAccountLoginCount updates the login count of a stored federated instance
IncAccountLoginCount(ctx context.Context, account *models.Account) (err Error)
// ReadAccount returns one federated social account
ReadAccount(ctx context.Context, id int64) (account *models.Account, err Error)
// ReadAccountByUsername returns one federated social account
ReadAccountByUsername(ctx context.Context, instanceID int64, username string) (account *models.Account, err Error)
// ReadAccountsPage returns a page of federated social accounts
ReadAccountsPage(ctx context.Context, index, count int) (instances []*models.Account, err Error)
// UpdateAccount updates the stored federated instance
UpdateAccount(ctx context.Context, account *models.Account) (err Error)
// Block // Block
// CountBlocks returns the number of domain blocks
CountBlocks(ctx context.Context) (count int64, err Error)
// CreateBlock stores the domain block // CreateBlock stores the domain block
CreateBlock(ctx context.Context, block *models.Block) (err Error) CreateBlock(ctx context.Context, blocks ...*models.Block) (err Error)
// ReadBlockByID returns one domain block // DeleteBlock deletes a domain block
ReadBlockByID(ctx context.Context, id int64) (block *models.Block, err Error) DeleteBlock(ctx context.Context, blocks ...*models.Block) (err Error)
// ReadBlock returns one domain block
ReadBlock(ctx context.Context, id int64) (block *models.Block, err Error)
// ReadBlockByDomain returns one domain block by domain name // ReadBlockByDomain returns one domain block by domain name
ReadBlockByDomain(ctx context.Context, domain string) (block *models.Block, err Error) ReadBlockByDomain(ctx context.Context, domain string) (block *models.Block, err Error)
// ReadBlocks returns all domain blocks
ReadBlocks(ctx context.Context) (blocks []*models.Block, err Error)
// ReadBlocksPage returns a page of domain blocks
ReadBlocksPage(ctx context.Context, index, count int) (blocks []*models.Block, err Error)
// UpdateBlock updates the stored domain block // UpdateBlock updates the stored domain block
UpdateBlock(ctx context.Context, block *models.Block) (err Error) UpdateBlock(ctx context.Context, blocks ...*models.Block) (err Error)
// Instance // Instance
// CountInstances returns the number of federated instance
CountInstances(ctx context.Context) (count int64, err Error)
// CreateInstance stores the federated instance // CreateInstance stores the federated instance
CreateInstance(ctx context.Context, instance *models.Instance) (err Error) CreateInstance(ctx context.Context, instance *models.Instance) (err Error)
// ReadInstanceByID returns one federated social instance // ReadInstance returns one federated social instance
ReadInstanceByID(ctx context.Context, id int64) (instance *models.Instance, err Error) ReadInstance(ctx context.Context, id int64) (instance *models.Instance, err Error)
// ReadInstanceByActorIRI returns one federated social instance // ReadInstanceByActorIRI returns one federated social instance
ReadInstanceByActorIRI(ctx context.Context, actorIRI string) (instance *models.Instance, err Error) ReadInstanceByActorIRI(ctx context.Context, actorIRI string) (instance *models.Instance, err Error)
// ReadInstanceByDomain returns one federated social instance // ReadInstanceByDomain returns one federated social instance
ReadInstanceByDomain(ctx context.Context, domain string) (instance *models.Instance, err Error) ReadInstanceByDomain(ctx context.Context, domain string) (instance *models.Instance, err Error)
// ReadInstancesPage returns a page of federated social instances
ReadInstancesPage(ctx context.Context, index, count int) (instances []*models.Instance, err Error)
// ReadInstancesWithBlockID returns all instances with block id
ReadInstancesWithBlockID(ctx context.Context, blockID int64) (instances []*models.Instance, err Error)
// ReadInstancesWithDomainSuffix returns all instances with domain suffix
ReadInstancesWithDomainSuffix(ctx context.Context, domainSuffix string) (instances []*models.Instance, err Error)
// ReadInstancesWhereFollowing returns all federated social instances which are following this relay // ReadInstancesWhereFollowing returns all federated social instances which are following this relay
ReadInstancesWhereFollowing(ctx context.Context) (instances []*models.Instance, err Error) ReadInstancesWhereFollowing(ctx context.Context) (instances []*models.Instance, err Error)
// UpdateInstance updates the stored federated instance // UpdateInstance updates the stored federated instance

5
internal/fedi/errors.go Normal file
View File

@ -0,0 +1,5 @@
package fedi
import "errors"
var ErrCantCast = errors.New("unable to cast interface to type")

107
internal/fedi/fedi.go Normal file
View File

@ -0,0 +1,107 @@
package fedi
import (
"context"
"github.com/feditools/go-lib/fedihelper"
"github.com/feditools/go-lib/fedihelper/mastodon"
"github.com/feditools/relay/internal/config"
"github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/kv"
"github.com/feditools/relay/internal/models"
"github.com/feditools/relay/internal/token"
"github.com/spf13/viper"
"net/url"
)
func New(d db.DB, t *fedihelper.Transport, k kv.KV, tok *token.Tokenizer) (*Module, error) {
appName := viper.GetString(config.Keys.ApplicationName)
appWebsite := viper.GetString(config.Keys.ApplicationWebsite)
externalHostname := viper.GetString(config.Keys.ServerExternalHostname)
// prep fedi helpers
var fediHelpers []fedihelper.Helper
mastoHelper, err := mastodon.New(k, t, appName, appWebsite, "https://"+externalHostname)
if err != nil {
return nil, err
}
fediHelpers = append(fediHelpers, mastoHelper)
// prep fedi
newModule := &Module{
db: d,
tokz: tok,
}
fedi, err := fedihelper.New(k, t, appName, fediHelpers)
if err != nil {
return nil, err
}
fedi.SetCreateAccountHandler(newModule.CreateAccountHandler)
fedi.SetGetAccountHandler(newModule.GetAccountHandler)
fedi.SetNewAccountHandler(newModule.NewAccountHandler)
newModule.helper = fedi
return newModule, nil
}
type Module struct {
db db.DB
tokz *token.Tokenizer
helper *fedihelper.FediHelper
}
func (m *Module) FetchActor(ctx context.Context, actorIRI *url.URL) (*fedihelper.Actor, error) {
return m.helper.FetchActor(ctx, actorIRI)
}
func (m *Module) FetchHostMeta(ctx context.Context, domain string) (*fedihelper.HostMeta, error) {
return m.helper.FetchHostMeta(ctx, domain)
}
func (m *Module) FetchWebFinger(ctx context.Context, wfURI fedihelper.WebfingerURI, username, domain string) (*fedihelper.WebFinger, error) {
return m.helper.FetchWebFinger(ctx, wfURI, username, domain)
}
func (m *Module) GetLoginURL(ctx context.Context, redirectURI *url.URL, instance *models.Instance) (*url.URL, error) {
return m.helper.GetLoginURL(ctx, redirectURI, instance)
}
func (m *Module) GenInstanceFromDomain(ctx context.Context, domain string) (*models.Instance, error) {
instance := new(models.Instance)
err := m.helper.GenerateFediInstanceFromDomain(ctx, domain, instance)
if err != nil {
return nil, err
}
return instance, nil
}
func (m *Module) Helper(s fedihelper.SoftwareName) fedihelper.Helper {
return m.helper.Helper(s)
}
func (m *Module) NewAccountFromUsername(ctx context.Context, username string, instance *models.Instance) (*models.Account, error) {
account := new(models.Account)
err := m.helper.GenerateFediAccountFromUsername(ctx, username, instance, account)
if err != nil {
return nil, err
}
return account, nil
}
func (m *Module) NewInstanceFromDomain(ctx context.Context, domain string) (*models.Instance, error) {
instance := new(models.Instance)
err := m.helper.GenerateFediInstanceFromDomain(ctx, domain, instance)
if err != nil {
return nil, err
}
return instance, nil
}

40
internal/fedi/handler.go Normal file
View File

@ -0,0 +1,40 @@
package fedi
import (
"context"
"errors"
"github.com/feditools/go-lib/fedihelper"
"github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/models"
)
func (m *Module) CreateAccountHandler(ctx context.Context, accountI fedihelper.Account) (err error) {
account, ok := accountI.(*models.Account)
if !ok {
return ErrCantCast
}
return m.db.CreateAccount(ctx, account)
}
func (m *Module) GetAccountHandler(ctx context.Context, instanceI fedihelper.Instance, username string) (fedihelper.Account, bool, error) {
instance, ok := instanceI.(*models.Instance)
if !ok {
return nil, false, ErrCantCast
}
account, err := m.db.ReadAccountByUsername(ctx, instance.ID, username)
if err != nil {
if errors.Is(err, db.ErrNoEntries) {
return nil, false, nil
}
return nil, false, err
}
return account, true, nil
}
func (*Module) NewAccountHandler(_ context.Context) (account fedihelper.Account, err error) {
return &models.Account{}, nil
}

View File

@ -3,6 +3,7 @@ package activitypub
import ( import (
"context" "context"
"github.com/feditools/relay/internal/config" "github.com/feditools/relay/internal/config"
ihttp "github.com/feditools/relay/internal/http"
"github.com/feditools/relay/internal/logic" "github.com/feditools/relay/internal/logic"
"github.com/feditools/relay/internal/runner" "github.com/feditools/relay/internal/runner"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -10,7 +11,7 @@ import (
// Module is an http module that handles activity pub activity // Module is an http module that handles activity pub activity
type Module struct { type Module struct {
logic *logic.Logic logic logic.Logic
runner runner.Runner runner runner.Runner
appName string appName string
@ -19,7 +20,7 @@ type Module struct {
} }
// New creates a new activity pub module // New creates a new activity pub module
func New(ctx context.Context, l *logic.Logic, r runner.Runner) (*Module, error) { func New(ctx context.Context, l logic.Logic, r runner.Runner) (*Module, error) {
log := logger.WithField("func", "New") log := logger.WithField("func", "New")
module := &Module{ module := &Module{
@ -53,3 +54,6 @@ func New(ctx context.Context, l *logic.Logic, r runner.Runner) (*Module, error)
func (m *Module) Name() string { func (m *Module) Name() string {
return config.ServerRoleActivityPub return config.ServerRoleActivityPub
} }
// SetServer adds a reference to the server to the module.
func (*Module) SetServer(_ *ihttp.Server) {}

View File

@ -2,9 +2,9 @@ package activitypub
import ( import (
"encoding/json" "encoding/json"
"github.com/feditools/relay/internal/models" "github.com/feditools/go-lib/fedihelper"
libhttp "github.com/feditools/go-lib/http"
"github.com/feditools/relay/internal/path" "github.com/feditools/relay/internal/path"
"github.com/tyrm/go-util/mimetype"
"net/http" "net/http"
) )
@ -13,17 +13,17 @@ func (m *Module) actorGetHandler(w http.ResponseWriter, r *http.Request) {
actor := m.genRelayActor() actor := m.genRelayActor()
w.Header().Set("Content-Type", mimetype.ApplicationJSON) w.Header().Set("Content-Type", libhttp.MimeAppJSON.String())
err := json.NewEncoder(w).Encode(actor) err := json.NewEncoder(w).Encode(actor)
if err != nil { if err != nil {
l.Errorf("marshaling json: %s", err.Error()) l.Errorf("marshaling json: %s", err.Error())
} }
} }
func (m *Module) genRelayActor() *models.Actor { func (m *Module) genRelayActor() *fedihelper.Actor {
return &models.Actor{ return &fedihelper.Actor{
Context: ContextActivityStreams, Context: ContextActivityStreams,
Endpoints: models.Endpoints{ Endpoints: fedihelper.Endpoints{
SharedInbox: path.GenInbox(m.logic.Domain()), SharedInbox: path.GenInbox(m.logic.Domain()),
}, },
Followers: path.GenFollowers(m.logic.Domain()), Followers: path.GenFollowers(m.logic.Domain()),
@ -32,7 +32,7 @@ func (m *Module) genRelayActor() *models.Actor {
Name: m.appName, Name: m.appName,
Type: "Application", Type: "Application",
ID: path.GenActor(m.logic.Domain()), ID: path.GenActor(m.logic.Domain()),
PublicKey: models.PublicKey{ PublicKey: fedihelper.PublicKey{
ID: path.GenPublicKey(m.logic.Domain()), ID: path.GenPublicKey(m.logic.Domain()),
Owner: path.GenActor(m.logic.Domain()), Owner: path.GenActor(m.logic.Domain()),
PublicKeyPEM: m.publicKeyPem, PublicKeyPEM: m.publicKeyPem,

View File

@ -0,0 +1,11 @@
package activitypub
// ContextKey is a key used in http request contexts
type ContextKey int
const (
// ContextKeyKeyVerifier is a http signature verifier
ContextKeyKeyVerifier ContextKey = iota
// ContextKeyHTTPSignature is a http signature
ContextKeyHTTPSignature
)

View File

@ -3,6 +3,7 @@ package activitypub
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/feditools/go-lib/fedihelper"
"github.com/feditools/relay/internal/db" "github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/models" "github.com/feditools/relay/internal/models"
nethttp "net/http" nethttp "net/http"
@ -13,7 +14,7 @@ func (m *Module) inboxPostHandler(w nethttp.ResponseWriter, r *nethttp.Request)
l := logger.WithField("func", "inboxPostHandler") l := logger.WithField("func", "inboxPostHandler")
// parse activity // parse activity
var activity models.Activity var activity fedihelper.Activity
err := json.NewDecoder(r.Body).Decode(&activity) err := json.NewDecoder(r.Body).Decode(&activity)
if err != nil { if err != nil {
l.Errorf("decoding activity: %+v", err) l.Errorf("decoding activity: %+v", err)
@ -35,6 +36,7 @@ func (m *Module) inboxPostHandler(w nethttp.ResponseWriter, r *nethttp.Request)
if !ok { if !ok {
l.Debugf("activity actor isn't string: %+v", activity) l.Debugf("activity actor isn't string: %+v", activity)
nethttp.Error(w, nethttp.StatusText(nethttp.StatusBadRequest), nethttp.StatusBadRequest) nethttp.Error(w, nethttp.StatusText(nethttp.StatusBadRequest), nethttp.StatusBadRequest)
return return
} }
actorIRI, err := url.Parse(actorStr) actorIRI, err := url.Parse(actorStr)
@ -49,7 +51,7 @@ func (m *Module) inboxPostHandler(w nethttp.ResponseWriter, r *nethttp.Request)
l.Tracef("validating actor: %s", actorStr) l.Tracef("validating actor: %s", actorStr)
validated, actor := m.logic.ValidateRequest(r, actorIRI) validated, actor := m.logic.ValidateRequest(r, actorIRI)
if !validated { if !validated {
l.Debugf("validation failed for actor: %s", actor) l.Debugf("validation failed for actor: %s", actorStr)
nethttp.Error(w, nethttp.StatusText(nethttp.StatusUnauthorized), nethttp.StatusUnauthorized) nethttp.Error(w, nethttp.StatusText(nethttp.StatusUnauthorized), nethttp.StatusUnauthorized)
return return
@ -57,7 +59,7 @@ func (m *Module) inboxPostHandler(w nethttp.ResponseWriter, r *nethttp.Request)
var instance *models.Instance var instance *models.Instance
switch actor.Type { switch actor.Type {
case models.TypeApplication: case fedihelper.TypeApplication:
instance, err = m.logic.GetInstanceForActor(r.Context(), actorIRI) instance, err = m.logic.GetInstanceForActor(r.Context(), actorIRI)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNoEntries) { if errors.Is(err, db.ErrNoEntries) {
@ -70,7 +72,7 @@ func (m *Module) inboxPostHandler(w nethttp.ResponseWriter, r *nethttp.Request)
return return
} }
case models.TypePerson: case fedihelper.TypePerson:
instance, err = m.logic.GetInstance(r.Context(), actorIRI.Host) instance, err = m.logic.GetInstance(r.Context(), actorIRI.Host)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNoEntries) { if errors.Is(err, db.ErrNoEntries) {
@ -91,23 +93,16 @@ func (m *Module) inboxPostHandler(w nethttp.ResponseWriter, r *nethttp.Request)
} }
// get activity type // get activity type
activityTypeI, ok := activity["type"] activityType, err := activity.Type()
if !ok { if err != nil {
l.Debugf("activity missing type: %+v", activity) l.Debugf("can't get type: %s", err.Error())
nethttp.Error(w, nethttp.StatusText(nethttp.StatusBadRequest), nethttp.StatusBadRequest)
return
}
activityType, ok := activityTypeI.(string)
if !ok {
l.Debugf("activity type isn't string: %+v", activity)
nethttp.Error(w, nethttp.StatusText(nethttp.StatusBadRequest), nethttp.StatusBadRequest) nethttp.Error(w, nethttp.StatusText(nethttp.StatusBadRequest), nethttp.StatusBadRequest)
return return
} }
// drop non-Follow activities from instances that don't follow our relay // drop non-Follow activities from instances that don't follow our relay
if activityType != models.TypeFollow && !instance.Followed { if activityType != fedihelper.TypeFollow && !instance.Followed {
l.Debugf("got non follow from an unfollowed instance: %s", activityType) l.Debugf("got non follow from an unfollowed instance: %s", activityType)
nethttp.Error(w, nethttp.StatusText(nethttp.StatusUnauthorized), nethttp.StatusUnauthorized) nethttp.Error(w, nethttp.StatusText(nethttp.StatusUnauthorized), nethttp.StatusUnauthorized)

View File

@ -2,9 +2,8 @@ package activitypub
import ( import (
"context" "context"
"github.com/feditools/relay/internal/http" libhttp "github.com/feditools/go-lib/http"
"github.com/go-fed/httpsig" "github.com/go-fed/httpsig"
"github.com/tyrm/go-util/mimetype"
nethttp "net/http" nethttp "net/http"
"net/url" "net/url"
) )
@ -39,19 +38,19 @@ func (m *Module) middlewareCheckHTTPSig(next nethttp.Handler) nethttp.Handler {
return return
} }
ctx := context.WithValue(r.Context(), http.ContextKeyKeyVerifier, verifier) ctx := context.WithValue(r.Context(), ContextKeyKeyVerifier, verifier)
// check for domain block // check for domain block
isBlocked, err := m.logic.IsDomainBlocked(ctx, KeyIDURI.Host) isBlocked, err := m.logic.IsDomainBlocked(ctx, KeyIDURI.Host)
if err != nil { if err != nil {
l.Errorf("is domain blocked: %s", err.Error()) l.Errorf("is domain blocked: %s", err.Error())
w.Header().Set("Content-Type", mimetype.TextPlain) w.Header().Set("Content-Type", libhttp.MimeTextPlain.String())
nethttp.Error(w, nethttp.StatusText(nethttp.StatusInternalServerError), nethttp.StatusInternalServerError) nethttp.Error(w, nethttp.StatusText(nethttp.StatusInternalServerError), nethttp.StatusInternalServerError)
return return
} }
if isBlocked { if isBlocked {
l.Debugf("domain %s is blocked", KeyIDURI.Host) l.Debugf("domain %s is blocked", KeyIDURI.Host)
w.Header().Set("Content-Type", mimetype.TextPlain) w.Header().Set("Content-Type", libhttp.MimeTextPlain.String())
nethttp.Error(w, nethttp.StatusText(nethttp.StatusUnauthorized), nethttp.StatusUnauthorized) nethttp.Error(w, nethttp.StatusText(nethttp.StatusUnauthorized), nethttp.StatusUnauthorized)
return return
} }
@ -59,7 +58,7 @@ func (m *Module) middlewareCheckHTTPSig(next nethttp.Handler) nethttp.Handler {
// get signature // get signature
signature := r.Header.Get("signature") signature := r.Header.Get("signature")
if signature != "" { if signature != "" {
ctx = context.WithValue(ctx, http.ContextKeyHTTPSignature, signature) ctx = context.WithValue(ctx, ContextKeyHTTPSignature, signature)
} }
// do request with verifier // do request with verifier

View File

@ -2,9 +2,9 @@ package activitypub
import ( import (
"encoding/json" "encoding/json"
"github.com/feditools/relay/internal/models" "github.com/feditools/go-lib/fedihelper"
libhttp "github.com/feditools/go-lib/http"
"github.com/feditools/relay/internal/path" "github.com/feditools/relay/internal/path"
"github.com/tyrm/go-util/mimetype"
"net/http" "net/http"
) )
@ -14,35 +14,35 @@ func (m *Module) nodeinfo20GetHandler(w http.ResponseWriter, r *http.Request) {
peers, err := m.logic.GetPeers(r.Context()) peers, err := m.logic.GetPeers(r.Context())
if err != nil { if err != nil {
l.Errorf("get peers: %s", err.Error()) l.Errorf("get peers: %s", err.Error())
w.Header().Set("Content-Type", mimetype.TextPlain) w.Header().Set("Content-Type", libhttp.MimeTextPlain.String())
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
nodeinfo := models.NodeInfo{ nodeinfo := fedihelper.NodeInfoV2{
Metadata: map[string]interface{}{ Metadata: map[string]interface{}{
"peers": peers, "peers": peers,
}, },
OpenRegistrations: true, OpenRegistrations: true,
Protocols: []string{"activitypub"}, Protocols: []string{"activitypub"},
Services: models.Services{ Services: fedihelper.Services{
Inbound: []string{}, Inbound: []string{},
Outbound: []string{}, Outbound: []string{},
}, },
Software: models.Software{ Software: fedihelper.Software{
Name: m.appName, Name: m.appName,
Version: m.appVersion, Version: m.appVersion,
}, },
Usage: models.Usage{ Usage: fedihelper.Usage{
LocalPosts: 0, LocalPosts: 0,
Users: models.UsageUsers{ Users: fedihelper.UsageUsers{
Total: 1, Total: 1,
}, },
}, },
Version: "2.0", Version: "2.0",
} }
w.Header().Set("Content-Type", mimetype.ApplicationJSON) w.Header().Set("Content-Type", libhttp.MimeAppJSON.String())
err = json.NewEncoder(w).Encode(nodeinfo) err = json.NewEncoder(w).Encode(nodeinfo)
if err != nil { if err != nil {
l.Errorf("marshaling json: %s", err.Error()) l.Errorf("marshaling json: %s", err.Error())
@ -52,8 +52,8 @@ func (m *Module) nodeinfo20GetHandler(w http.ResponseWriter, r *http.Request) {
func (m *Module) wellknownNodeInfoGetHandler(w http.ResponseWriter, r *http.Request) { func (m *Module) wellknownNodeInfoGetHandler(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "wellknownNodeInfoGetHandler") l := logger.WithField("func", "wellknownNodeInfoGetHandler")
wellknown := models.NodeInfoWellKnown{ wellknown := fedihelper.WellKnownNodeInfo{
Links: []models.Link{ Links: []fedihelper.Link{
{ {
Rel: "http://nodeinfo.diaspora.software/ns/schema/2.0", Rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
Href: path.GenNodeinfo20(m.logic.Domain()), Href: path.GenNodeinfo20(m.logic.Domain()),
@ -61,7 +61,7 @@ func (m *Module) wellknownNodeInfoGetHandler(w http.ResponseWriter, r *http.Requ
}, },
} }
w.Header().Set("Content-Type", mimetype.ApplicationJSON) w.Header().Set("Content-Type", libhttp.MimeAppJSON.String())
err := json.NewEncoder(w).Encode(wellknown) err := json.NewEncoder(w).Encode(wellknown)
if err != nil { if err != nil {
l.Errorf("marshaling json: %s", err.Error()) l.Errorf("marshaling json: %s", err.Error())

View File

@ -7,12 +7,10 @@ import (
// Route attaches routes to the web server // Route attaches routes to the web server
func (m *Module) Route(s *http.Server) error { func (m *Module) Route(s *http.Server) error {
ap := s.PathPrefix("/").Subrouter() s.HandleFunc(path.APActor, m.actorGetHandler).Methods("GET")
ap.Use(m.middlewareCheckHTTPSig) s.HandleFunc(path.APInbox, m.inboxPostHandler).Methods("POST")
ap.HandleFunc(path.APActor, m.actorGetHandler).Methods("GET") s.HandleFunc(path.APNodeInfo20, m.nodeinfo20GetHandler).Methods("GET")
ap.HandleFunc(path.APInbox, m.inboxPostHandler).Methods("POST") s.HandleFunc(path.APWellKnownNodeInfo, m.wellknownNodeInfoGetHandler).Methods("GET")
ap.HandleFunc(path.APNodeInfo20, m.nodeinfo20GetHandler).Methods("GET") s.HandleFunc(path.APWellKnownWebFinger, m.wellknownWebFingerGetHandler).Methods("GET")
ap.HandleFunc(path.APWellKnownNodeInfo, m.wellknownNodeInfoGetHandler).Methods("GET")
ap.HandleFunc(path.APWellKnownWebFinger, m.wellknownWebFingerGetHandler).Methods("GET")
return nil return nil
} }

View File

@ -3,9 +3,9 @@ package activitypub
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/feditools/relay/internal/models" "github.com/feditools/go-lib/fedihelper"
libhttp "github.com/feditools/go-lib/http"
"github.com/feditools/relay/internal/path" "github.com/feditools/relay/internal/path"
"github.com/tyrm/go-util/mimetype"
"net/http" "net/http"
) )
@ -14,29 +14,29 @@ func (m *Module) wellknownWebFingerGetHandler(w http.ResponseWriter, r *http.Req
subject := r.URL.Query().Get("resource") subject := r.URL.Query().Get("resource")
if subject != fmt.Sprintf("acct:relay@%s", m.logic.Domain()) { if subject != fmt.Sprintf("acct:relay@%s", m.logic.Domain()) {
w.Header().Set("Content-Type", mimetype.TextPlain) w.Header().Set("Content-Type", libhttp.MimeTextPlain.String())
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return return
} }
webfinger := models.WebFinger{ webfinger := fedihelper.WebFinger{
Aliases: []string{path.GenActor(m.logic.Domain())}, Aliases: []string{path.GenActor(m.logic.Domain())},
Links: []models.Link{ Links: []fedihelper.Link{
{ {
Href: path.GenActor(m.logic.Domain()), Href: path.GenActor(m.logic.Domain()),
Rel: "self", Rel: "self",
Type: mimetype.ApplicationActivityJSON, Type: libhttp.MimeAppActivityJSON.String(),
}, },
{ {
Href: path.GenActor(m.logic.Domain()), Href: path.GenActor(m.logic.Domain()),
Rel: "self", Rel: "self",
Type: mimetype.ApplicationLDJSONActivityStreams, Type: libhttp.MimeAppActivityLDJSON.String(),
}, },
}, },
Subject: subject, Subject: subject,
} }
w.Header().Set("Content-Type", mimetype.ApplicationJSON) w.Header().Set("Content-Type", libhttp.MimeAppJSON.String())
err := json.NewEncoder(w).Encode(webfinger) err := json.NewEncoder(w).Encode(webfinger)
if err != nil { if err != nil {
l.Errorf("marshaling json: %s", err.Error()) l.Errorf("marshaling json: %s", err.Error())

71
internal/http/client.go Normal file
View File

@ -0,0 +1,71 @@
package http
import (
"context"
"fmt"
"github.com/feditools/relay/internal/config"
"github.com/spf13/viper"
"io"
"net/http"
)
func NewClient(_ context.Context) (*Client, error) {
userAgent := fmt.Sprintf("Go-http-client/2.0 (%s/%s; +https://%s/)",
viper.GetString(config.Keys.ApplicationName),
viper.GetString(config.Keys.SoftwareVersion),
viper.GetString(config.Keys.ServerExternalHostname),
)
return &Client{
userAgent: userAgent,
}, nil
}
type Client struct {
userAgent string
}
// Do runs a request with the http client.
func (*Client) Do(req *http.Request) (resp *http.Response, err error) {
client := &http.Client{}
return client.Do(req)
}
// Get calls http.Get with expected http User-Agent.
func (c *Client) Get(ctx context.Context, url string) (resp *http.Response, err error) {
req, err := c.NewRequest(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
// NewRequest calls http.NewRequest with expected http User-Agent.
func (c *Client) NewRequest(ctx context.Context, method, url string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, method, url, body)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", c.userAgent)
return req, nil
}
func (c *Client) Transport() (transport http.RoundTripper) {
return &Transport{userAgent: c.userAgent}
}
// Transport adds the expected http User-Agent to any request.
type Transport struct {
userAgent string
}
// RoundTrip executes the default http.Transport with expected http User-Agent.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", t.userAgent)
return http.DefaultTransport.RoundTrip(req)
}

View File

@ -1,11 +0,0 @@
package http
// ContextKey is a key used in http request contexts
type ContextKey int
const (
// ContextKeyKeyVerifier is a http signature verifier
ContextKeyKeyVerifier ContextKey = iota
// ContextKeyHTTPSignature is a http signature
ContextKeyHTTPSignature
)

View File

@ -1,22 +1,50 @@
package http package http
import ( import (
libhttp "github.com/feditools/go-lib/http"
"github.com/go-http-utils/etag"
"github.com/gorilla/handlers"
"github.com/sirupsen/logrus"
"net/http" "net/http"
"time"
) )
func (s *Server) middlewareMetrics(next http.Handler) http.Handler { func (s *Server) MiddlewareMetrics(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now() metric := s.metrics.NewHTTPRequest(r.Method, r.URL.Path)
l := logger.WithField("func", "middlewareMetrics")
client := r.RemoteAddr
if forwardedFor := r.Header.Get("X-Forwarded-For"); forwardedFor != "" {
client = forwardedFor
}
l := logger.WithFields(logrus.Fields{
"func": "middlewareMetrics",
"client": client,
"useragent": r.UserAgent(),
})
wx := NewResponseWriter(w) wx := NewResponseWriter(w)
// Do Request // Do Request
next.ServeHTTP(wx, r) next.ServeHTTP(wx, r)
ended := time.Since(start) go func() {
l.Debugf("rendering %s took %d ms", r.URL.Path, ended.Milliseconds()) ended := metric.Done(wx.Status())
go s.metrics.HTTPRequestTiming(ended, wx.Status(), r.Method, r.URL.Path) l.Debugf("rendering %s took %d ms", r.URL.Path, ended.Milliseconds())
}()
}) })
} }
// WrapInMiddlewares wraps an http.Handler in the server's middleware.
func (s *Server) WrapInMiddlewares(h http.Handler) http.Handler {
return s.MiddlewareMetrics(
libhttp.BlockMissingUserAgent(
libhttp.BlockFloc(
etag.Handler(
handlers.CompressHandler(
h,
), false,
),
),
),
)
}

View File

@ -4,4 +4,5 @@ package http
type Module interface { type Module interface {
Name() string Name() string
Route(s *Server) error Route(s *Server) error
SetServer(s *Server)
} }

View File

@ -3,13 +3,11 @@ package http
import ( import (
"context" "context"
"fmt" "fmt"
libhttp "github.com/feditools/go-lib/http"
"github.com/feditools/go-lib/metrics"
"github.com/feditools/relay/internal/config" "github.com/feditools/relay/internal/config"
"github.com/feditools/relay/internal/metrics"
"github.com/gorilla/handlers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/tyrm/go-util/middleware"
"github.com/tyrm/go-util/mimetype"
"net/http" "net/http"
"time" "time"
) )
@ -39,9 +37,7 @@ func NewServer(_ context.Context, m metrics.Collector) (*Server, error) {
} }
// add global middlewares // add global middlewares
r.Use(server.middlewareMetrics) r.Use(server.WrapInMiddlewares)
r.Use(handlers.CompressHandler)
r.Use(middleware.BlockFlocMux)
r.NotFoundHandler = server.notFoundHandler() r.NotFoundHandler = server.notFoundHandler()
r.MethodNotAllowedHandler = server.methodNotAllowedHandler() r.MethodNotAllowedHandler = server.methodNotAllowedHandler()
@ -73,16 +69,16 @@ func (s *Server) Stop(ctx context.Context) error {
func (s *Server) methodNotAllowedHandler() http.Handler { func (s *Server) methodNotAllowedHandler() http.Handler {
// wrap in middleware since middlware isn't run on error pages // wrap in middleware since middlware isn't run on error pages
return s.middlewareMetrics(middleware.BlockFlocMux(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return s.WrapInMiddlewares(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", mimetype.TextPlain) w.Header().Set("Content-Type", libhttp.MimeTextPlain.String())
w.Write([]byte(fmt.Sprintf("%d %s", http.StatusMethodNotAllowed, http.StatusText(http.StatusMethodNotAllowed)))) w.Write([]byte(fmt.Sprintf("%d %s", http.StatusMethodNotAllowed, http.StatusText(http.StatusMethodNotAllowed))))
}))) }))
} }
func (s *Server) notFoundHandler() http.Handler { func (s *Server) notFoundHandler() http.Handler {
// wrap in middleware since middlware isn't run on error pages // wrap in middleware since middlware isn't run on error pages
return s.middlewareMetrics(middleware.BlockFlocMux(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return s.WrapInMiddlewares(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", mimetype.TextPlain) w.Header().Set("Content-Type", libhttp.MimeTextPlain.String())
w.Write([]byte(fmt.Sprintf("%d %s", http.StatusNotFound, http.StatusText(http.StatusNotFound)))) w.Write([]byte(fmt.Sprintf("%d %s", http.StatusNotFound, http.StatusText(http.StatusNotFound))))
}))) }))
} }

View File

@ -0,0 +1,42 @@
package static
import (
"github.com/feditools/relay/internal/config"
ihttp "github.com/feditools/relay/internal/http"
"github.com/feditools/relay/internal/path"
"github.com/feditools/relay/web"
"io/fs"
nethttp "net/http"
)
const DirStatic = "static"
func New() (*Module, error) {
staticFS, err := fs.Sub(web.Files, DirStatic)
if err != nil {
return nil, err
}
return &Module{
fs: staticFS,
}, nil
}
type Module struct {
fs fs.FS
}
// Name return the module name
func (m *Module) Name() string {
return config.ServerRoleActivityPub
}
// SetServer adds a reference to the server to the module.
func (*Module) SetServer(_ *ihttp.Server) {}
// Route attaches routes to the web server.
func (m *Module) Route(s *ihttp.Server) error {
s.PathPrefix(path.Static).Handler(nethttp.StripPrefix(path.Static, nethttp.FileServer(nethttp.FS(m.fs))))
return nil
}

View File

@ -0,0 +1,39 @@
package template
import (
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/models"
)
// AdminBlockName is the name of the admin block list template.
const AdminBlockName = "admin_block"
// AdminBlock contains the variables for the admin block list template.
type AdminBlock struct {
Common
Blocks []*models.Block
Pagination libtemplate.Pagination
FormAddError *libtemplate.Alert
FormAddAction libtemplate.FormInput
FormAddDomain libtemplate.FormInput
FormAddObfuscatedDomain libtemplate.FormInput
FormAddBlockSubdomains libtemplate.FormInput
FormDeleteError *libtemplate.Alert
FormDeleteAction libtemplate.FormInput
FormDeleteToken libtemplate.FormInput
FormEditError *libtemplate.Alert
FormEditAction libtemplate.FormInput
FormEditToken libtemplate.FormInput
FormEditDomain libtemplate.FormInput
FormEditObfuscatedDomain libtemplate.FormInput
FormEditBlockSubdomains libtemplate.FormInput
FormImportError *libtemplate.Alert
FormImportAction libtemplate.FormInput
FormImportFile libtemplate.FormInput
FormImportBlockSubdomains libtemplate.FormInput
}

View File

@ -0,0 +1,9 @@
package template
// AdminHomeName is the name of the admin home template.
const AdminHomeName = "admin_home"
// AdminHome contains the variables for the admin home template.
type AdminHome struct {
Common
}

View File

@ -0,0 +1,17 @@
package template
import (
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/models"
)
// AdminInstanceName is the name of the admin block list template.
const AdminInstanceName = "admin_instance"
// AdminInstance contains the variables for the admin block list template.
type AdminInstance struct {
Common
Instances []*models.Instance
Pagination libtemplate.Pagination
}

View File

@ -0,0 +1,80 @@
package template
import (
"github.com/feditools/go-lib/language"
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/models"
)
// Common contains the variables used in nearly every template.
type Common struct {
Language string
Localizer *language.Localizer
Account *models.Account
Alerts *[]libtemplate.Alert
FooterScripts []libtemplate.Script
FooterExtraScript []string
HeadLinks []libtemplate.HeadLink
LogoSrcDark string
LogoSrcLight string
NavBar Navbar
NavBarDark bool
PageTitle string
}
// AddHeadLink adds a headder link to the template.
func (t *Common) AddHeadLink(l libtemplate.HeadLink) {
if t.HeadLinks == nil {
t.HeadLinks = []libtemplate.HeadLink{}
}
t.HeadLinks = append(t.HeadLinks, l)
}
// AddFooterScript adds a footer script to the template.
func (t *Common) AddFooterScript(s libtemplate.Script) {
if t.FooterScripts == nil {
t.FooterScripts = []libtemplate.Script{}
}
t.FooterScripts = append(t.FooterScripts, s)
}
// AddFooterExtraScript adds a footer script to the template.
func (t *Common) AddFooterExtraScript(s string) {
if t.FooterExtraScript == nil {
t.FooterExtraScript = []string{}
}
t.FooterExtraScript = append(t.FooterExtraScript, s)
}
// SetLanguage sets the template's default language.
func (t *Common) SetLanguage(l string) {
t.Language = l
}
// SetLocalizer sets the localizer the template will use to generate text.
func (t *Common) SetLocalizer(l *language.Localizer) {
t.Localizer = l
}
// SetLogoSrc sets the src for the logo image.
func (t *Common) SetLogoSrc(dark, light string) {
t.LogoSrcDark = dark
t.LogoSrcLight = light
}
// SetNavbar sets the top level navbar used by the template.
func (t *Common) SetNavbar(nodes Navbar) {
t.NavBar = nodes
}
// SetNavbarDark sets the navbar theme.
func (t *Common) SetNavbarDark(dark bool) {
t.NavBarDark = dark
}
// SetAccount sets the currently logged-in account.
func (t *Common) SetAccount(account *models.Account) {
t.Account = account
}

View File

@ -0,0 +1,16 @@
package template
// ErrorPageName is the name of the error template.
const ErrorName = "error"
// Error contains the variables for the error template.
type Error struct {
Common
Header string
Image string
SubHeader string
Paragraph string
ButtonHRef string
ButtonLabel string
}

View File

@ -0,0 +1,17 @@
package template
type FormInput struct {
Class string
ID string
Name string
Placeholder string
Type string
Value string
Validation *FormInputValidation
}
type FormInputValidation struct {
Message string
Valid bool
}

View File

@ -0,0 +1,16 @@
package template
import "github.com/feditools/relay/internal/models"
// HomeName is the name of the home template.
const HomeName = "home"
// Home contains the variables for the home template.
type Home struct {
Common
ActorHref string
InboxHref string
FollowingInstances []*models.Instance
Blocks []*models.Block
}

View File

@ -0,0 +1,12 @@
package template
// LoginName is the name of the login template.
const LoginName = "login"
// Login contains the variables for the "login" template.
type Login struct {
Common
FormError string
FormAccount string
}

View File

@ -0,0 +1,52 @@
package template
import (
"regexp"
libtemplate "github.com/feditools/go-lib/template"
)
// Navbar is a navbar that can be added to a page.
type Navbar []NavbarNode
// ActivateFromPath sets the active bool based on the match regex.
func (n *Navbar) ActivateFromPath(path string) {
libtemplate.SetActive(n, path)
}
// GetChildren returns the children of the node or nil if no children.
func (n *Navbar) GetChildren(i int) libtemplate.ActivableSlice {
if len((*n)[i].Children) == 0 {
return nil
}
return &(*n)[i].Children
}
// GetMatcher returns the matcher of the node or nil if no matcher.
func (n *Navbar) GetMatcher(i int) *regexp.Regexp {
return (*n)[i].MatchStr
}
// SetActive sets the active bool based on the match regex.
func (n *Navbar) SetActive(i int, a bool) {
(*n)[i].Active = a
}
// Len returns the matcher of the node or nil if no matcher.
func (n *Navbar) Len() int {
return len(*n)
}
// NavbarNode is an entry on a navbar, can be nested one level.
type NavbarNode struct {
Text string
URL string
MatchStr *regexp.Regexp
FAIcon string
Active bool
Disabled bool
Children Navbar
}

View File

@ -0,0 +1,77 @@
package template
import (
"github.com/feditools/go-lib/language"
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/models"
"github.com/feditools/relay/internal/path"
"github.com/feditools/relay/internal/token"
"github.com/feditools/relay/web"
"html/template"
"io/ioutil"
"strings"
)
const templateDir = "template"
// InitTemplate are the functions a template implementing Common will have.
type InitTemplate interface {
AddHeadLink(l libtemplate.HeadLink)
AddFooterScript(s libtemplate.Script)
SetAccount(account *models.Account)
SetLanguage(l string)
SetLocalizer(l *language.Localizer)
SetLogoSrc(dark, light string)
SetNavbar(nodes Navbar)
SetNavbarDark(dark bool)
}
// New creates a new template.
func New(tokz *token.Tokenizer) (*template.Template, error) {
funMap := template.FuncMap{
"token": tokz.GetToken,
"pathAppAdminBlockExport": path.GenAppAdminBlockExport,
"pathAppAdminBlockExportCSV": path.GenAppAdminBlockExportCSV,
"pathAppAdminBlockExportJSON": path.GenAppAdminBlockExportJSON,
"pathAppAdminHome": path.GenAppAdminHomePath,
"pathAppHome": path.GenAppHomePath,
"pathAppLogin": path.GenAppLoginPath,
"pathAppLogout": path.GenAppLogoutPath,
}
tpl, err := libtemplate.New(funMap)
if err != nil {
return nil, err
}
dir, err := web.Files.ReadDir(templateDir)
if err != nil {
panic(err)
}
for _, d := range dir {
filePath := templateDir + "/" + d.Name()
if d.IsDir() || !strings.HasSuffix(d.Name(), ".gohtml") {
continue
}
// open it
file, err := web.Files.Open(filePath)
if err != nil {
return nil, err
}
// read it
tmplData, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
// It can now be parsed as a string.
_, err = tpl.Parse(string(tmplData))
if err != nil {
return nil, err
}
}
return tpl, nil
}

View File

@ -1,13 +0,0 @@
package http
import "net/http"
// Transport adds the expected http User-Agent to any request
type Transport struct {
}
// RoundTrip executes the default http.Transport with expected http User-Agent
func (*Transport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", GetUserAgent())
return http.DefaultTransport.RoundTrip(req)
}

View File

@ -1,36 +0,0 @@
package http
import (
"fmt"
"github.com/feditools/relay/internal/config"
"github.com/spf13/viper"
"sync"
)
var (
initOnce sync.Once
userAgentLock sync.RWMutex
userAgent string
)
// doInit sets the User-Agent for all subsequent requests
func doInit() {
userAgentLock.Lock()
userAgent = fmt.Sprintf("Go-http-client/2.0 (%s/%s; +https://%s/)",
viper.GetString(config.Keys.ApplicationName),
viper.GetString(config.Keys.SoftwareVersion),
viper.GetString(config.Keys.ServerExternalHostname),
)
userAgentLock.Unlock()
}
// GetUserAgent returns the generated http User-Agent
func GetUserAgent() string {
initOnce.Do(doInit)
userAgentLock.RLock()
ua := userAgent
userAgentLock.RUnlock()
return ua
}

View File

@ -0,0 +1,33 @@
package webapp
import (
"github.com/feditools/go-lib/language"
"github.com/feditools/relay/internal/http/template"
"github.com/feditools/relay/internal/path"
nethttp "net/http"
)
func makeAdminNavbar(r *nethttp.Request) template.Navbar {
// get localizer
l := r.Context().Value(ContextKeyLocalizer).(*language.Localizer) // nolint
// create navbar
newNavbar := template.Navbar{
{
Text: l.TextInstance(2).String(),
MatchStr: path.ReAppAdminInstancesPre,
FAIcon: "server",
URL: path.AppAdminInstance,
},
{
Text: l.TextBlock(2).String(),
MatchStr: path.ReAppAdminBlockPre,
FAIcon: "user-slash",
URL: path.AppAdminBlock,
},
}
newNavbar.ActivateFromPath(r.URL.Path)
return newNavbar
}

View File

@ -0,0 +1,275 @@
package webapp
import (
libhttp "github.com/feditools/go-lib/http"
"github.com/feditools/go-lib/language"
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/http/template"
"github.com/feditools/relay/internal/path"
"net/http"
)
// AdminBlockGetHandler serves the home page.
func (m *Module) AdminBlockGetHandler(w http.ResponseWriter, r *http.Request) {
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
FormAddBlockSubdomainsValue: true,
})
}
// AdminBlockPostHandler serves the home page.
func (m *Module) AdminBlockPostHandler(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "AdminBlockPostHandler")
// get form data
if err := r.ParseForm(); err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
// execute action
action := r.FormValue(FormAction)
l.Debugf("got action: %s", action)
switch action {
case ActionAdd:
m.doAddBlock(w, r)
case ActionDelete:
m.doDeleteBlock(w, r)
case ActionEdit:
m.doEditBlock(w, r)
case ActionImport:
m.doImportBlocks(w, r)
case "":
m.returnErrorPage(w, r, http.StatusBadRequest, "action missing")
default:
m.returnErrorPage(w, r, http.StatusBadRequest, "unknown action "+action)
}
}
type displayAdminBlockListConfig struct {
DisplayModal string
Alerts *[]libtemplate.Alert
FormAddError *libtemplate.Alert
FormAddDomainValue string
FormAddDomainValidation *libtemplate.FormValidation
FormAddObfuscatedDomainValue string
FormAddObfuscatedDomainValidation *libtemplate.FormValidation
FormAddBlockSubdomainsValue bool
FormDeleteError *libtemplate.Alert
FormDeleteTokenValue string
FormEditError *libtemplate.Alert
FormEditTokenValue string
FormEditDomainValue string
FormEditObfuscatedDomainValue string
FormEditObfuscatedDomainValidation *libtemplate.FormValidation
FormEditBlockSubdomainsValue bool
FormInputError *libtemplate.Alert
}
func (m *Module) displayAdminBlockList(w http.ResponseWriter, r *http.Request, config displayAdminBlockListConfig) {
l := logger.WithField("func", "displayAdminBlockList")
// get localizer
localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer) //nolint
// Init template variables
tmplVars := &template.AdminBlock{
Common: template.Common{
PageTitle: localizer.TextBlock(2).String(),
},
}
err := m.initTemplateAdmin(w, r, tmplVars)
if err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
// create add form inputs
tmplVars.FormAddAction = libtemplate.FormInput{
ID: "formAddInputAction",
Type: libtemplate.FormInputTypeHidden,
Name: FormAction,
Value: ActionAdd,
}
tmplVars.FormAddDomain = libtemplate.FormInput{
ID: "formAddInputDomain",
Type: libtemplate.FormInputTypeText,
Name: FormDomain,
Placeholder: "example.com",
Label: localizer.TextDomain(1),
LabelClass: "form-label",
Value: config.FormAddDomainValue,
Disabled: false,
Required: true,
Validation: config.FormAddDomainValidation,
}
tmplVars.FormAddObfuscatedDomain = libtemplate.FormInput{
ID: "formAddInputObfuscatedDomain",
Type: libtemplate.FormInputTypeText,
Name: FormObfuscatedDomain,
Placeholder: "e*****e.com",
Label: localizer.TextObfuscatedDomain(1),
LabelClass: "form-label",
Value: config.FormAddObfuscatedDomainValue,
Disabled: false,
Required: false,
Validation: config.FormAddObfuscatedDomainValidation,
}
tmplVars.FormAddBlockSubdomains = libtemplate.FormInput{
ID: "formAddInputSubdomain",
Type: libtemplate.FormInputTypeCheckbox,
Name: FormSubdomain,
Label: localizer.TextBlockSubdomain(2),
LabelClass: "form-check-label",
Checked: config.FormAddBlockSubdomainsValue,
Disabled: false,
Required: false,
}
// create edit form inputs
tmplVars.FormEditAction = libtemplate.FormInput{
ID: "formEditInputAction",
Type: libtemplate.FormInputTypeHidden,
Name: FormAction,
Value: ActionEdit,
}
tmplVars.FormEditToken = libtemplate.FormInput{
ID: "formEditInputToken",
Type: libtemplate.FormInputTypeHidden,
Name: FormToken,
Value: config.FormEditTokenValue,
}
tmplVars.FormEditDomain = libtemplate.FormInput{
ID: "formEditInputDomain",
Type: libtemplate.FormInputTypeText,
Name: FormDomain,
Placeholder: "example.com",
Label: localizer.TextDomain(1),
LabelClass: "form-label",
Value: config.FormAddDomainValue,
Disabled: false,
Required: true,
Validation: config.FormAddDomainValidation,
}
tmplVars.FormEditObfuscatedDomain = libtemplate.FormInput{
ID: "formEditInputObfuscatedDomain",
Type: libtemplate.FormInputTypeText,
Name: FormObfuscatedDomain,
Placeholder: "e*****e.com",
Label: localizer.TextObfuscatedDomain(1),
LabelClass: "form-label",
Value: config.FormAddObfuscatedDomainValue,
Disabled: false,
Required: false,
Validation: config.FormAddObfuscatedDomainValidation,
}
tmplVars.FormEditBlockSubdomains = libtemplate.FormInput{
ID: "formEditInputSubdomain",
Type: libtemplate.FormInputTypeCheckbox,
Name: FormSubdomain,
Label: localizer.TextBlockSubdomain(2),
LabelClass: "form-check-label",
Checked: config.FormAddBlockSubdomainsValue,
Disabled: false,
Required: false,
}
// create delete form inputs
tmplVars.FormDeleteAction = libtemplate.FormInput{
ID: "formDeleteInputAction",
Type: libtemplate.FormInputTypeHidden,
Name: FormAction,
Value: ActionDelete,
}
tmplVars.FormDeleteToken = libtemplate.FormInput{
ID: "formDeleteInputToken",
Type: libtemplate.FormInputTypeHidden,
Name: FormToken,
Value: config.FormDeleteTokenValue,
}
// create delete form inputs
tmplVars.FormImportAction = libtemplate.FormInput{
ID: "formImportInputAction",
Type: libtemplate.FormInputTypeHidden,
Name: FormAction,
Value: ActionImport,
}
tmplVars.FormImportFile = libtemplate.FormInput{
ID: "formImportInputFile",
Type: libtemplate.FormInputTypeFile,
Name: FormFile,
Label: localizer.TextObfuscatedDomain(1),
LabelClass: "form-label",
}
tmplVars.FormImportBlockSubdomains = libtemplate.FormInput{
ID: "formImportInputSubdomain",
Type: libtemplate.FormInputTypeCheckbox,
Name: FormSubdomain,
Label: localizer.TextBlockSubdomain(2),
LabelClass: "form-check-label",
Checked: true,
Disabled: false,
Required: false,
}
// add javascript
tmplVars.AddFooterExtraScript(JSAdminBlock(
localizer.TextDeleteBlockDomain("${domain}"),
localizer.TextDeleteBlockConfirmDomain("${domain}"),
localizer.TextEditBlockDomain("${domain}"),
))
// alerts
tmplVars.Alerts = config.Alerts
tmplVars.FormAddError = config.FormAddError
tmplVars.FormDeleteError = config.FormDeleteError
tmplVars.FormEditError = config.FormEditError
tmplVars.FormImportError = config.FormInputError
if config.DisplayModal != "" {
tmplVars.AddFooterExtraScript(JSOpenModal(config.DisplayModal))
}
// get blocks
page, count, countFound := libhttp.GetPaginationFromURL(r.URL, defaultCount)
blockCount, err := m.db.CountBlocks(r.Context())
if err != nil {
l.Errorf("db count: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, ErrorResponseDBError)
return
}
blocks, err := m.db.ReadBlocksPage(r.Context(), page-1, count)
if err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, ErrorResponseDBError)
return
}
tmplVars.Blocks = blocks
// make pagination
pageConf := &libtemplate.PaginationConfig{
Count: int(blockCount),
DisplayCount: count,
HRef: path.AppAdminBlock,
MaxPagination: maxPagination,
Page: page,
}
if countFound {
pageConf.HRefCount = count
}
tmplVars.Pagination = libtemplate.MakePagination(pageConf)
err = m.executeTemplate(w, template.AdminBlockName, tmplVars)
if err != nil {
l.Errorf("could not render '%s' template: %s", template.AdminBlockName, err.Error())
}
}

View File

@ -0,0 +1,121 @@
package webapp
import (
"github.com/feditools/go-lib/language"
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/models"
"github.com/feditools/relay/internal/util"
"net/http"
)
func (m *Module) doAddBlock(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "doAddBlock")
// get localizer
localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer)
domain := r.FormValue(FormDomain)
obfuscatedDomain := r.FormValue(FormObfuscatedDomain)
blockSubdomains := false
if r.FormValue(FormSubdomain) != "" {
blockSubdomains = true
}
// validate forms
formValidationFailed := false
formDomainValidation := &libtemplate.FormValidation{
Response: localizer.TextLooksGood().String(),
Valid: true,
}
if err := validate.Var(domain, "required,fqdn"); err != nil {
l.Debugf("domain validation error: %s", err.Error())
formDomainValidation = &libtemplate.FormValidation{
Response: "Must be valid fqdn",
Valid: false,
}
formValidationFailed = true
}
var formObfuscatedDomainValidation *libtemplate.FormValidation
if obfuscatedDomain != "" {
formObfuscatedDomainValidation = &libtemplate.FormValidation{
Response: localizer.TextLooksGood().String(),
Valid: true,
}
}
if obfuscatedDomain != "" && !util.ValidateDomainObfuscation(domain, obfuscatedDomain) {
formObfuscatedDomainValidation = &libtemplate.FormValidation{
Response: "Obfuscated domain doesn't match domain",
Valid: false,
}
formValidationFailed = true
}
if formValidationFailed {
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "addModal",
FormAddDomainValue: domain,
FormAddDomainValidation: formDomainValidation,
FormAddObfuscatedDomainValue: obfuscatedDomain,
FormAddObfuscatedDomainValidation: formObfuscatedDomainValidation,
FormAddBlockSubdomainsValue: blockSubdomains,
})
return
}
// check for existing block
blocked, err := m.logic.IsDomainBlocked(r.Context(), domain)
if err != nil {
l.Errorf("checking domain block: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, ErrorResponseDBError)
return
}
if blocked {
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "addModal",
FormAddError: &libtemplate.Alert{
Level: "danger",
Text: localizer.TextBlockExists(domain).String(),
},
FormAddDomainValue: domain,
FormAddDomainValidation: formDomainValidation,
FormAddObfuscatedDomainValue: obfuscatedDomain,
FormAddObfuscatedDomainValidation: formObfuscatedDomainValidation,
FormAddBlockSubdomainsValue: blockSubdomains,
})
return
}
// create new block
newBlock := &models.Block{
Domain: domain,
ObfuscatedDomain: obfuscatedDomain,
BlockSubdomains: blockSubdomains,
}
err = m.logic.AddBlock(r.Context(), newBlock)
if err != nil {
l.Errorf("adding : %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, ErrorResponseDBError)
return
}
// display page
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
Alerts: &[]libtemplate.Alert{
{
Level: "success",
Text: "Block created.",
},
},
})
}

View File

@ -0,0 +1,63 @@
package webapp
import (
"errors"
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/token"
"net/http"
)
func (m *Module) doDeleteBlock(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "doDeleteBlock")
// get localizer
//localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer)
blockToken := r.FormValue(FormToken)
// read block
kind, id, err := m.tokz.DecodeToken(blockToken)
if err != nil {
l.Debugf("decode token: %s", err.Error())
m.returnErrorPage(w, r, http.StatusBadRequest, "bad token")
return
}
if kind != token.KindBlock {
l.Debug("token is wrong kind")
m.returnErrorPage(w, r, http.StatusBadRequest, "bad token")
return
}
block, err := m.db.ReadBlock(r.Context(), id)
if err != nil {
if errors.Is(err, db.ErrNoEntries) {
m.returnErrorPage(w, r, http.StatusNotFound, blockToken)
return
}
l.Errorf("db read instance: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
err = m.logic.DeleteBlock(r.Context(), block)
if err != nil {
l.Errorf("updating block: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, ErrorResponseDBError)
return
}
// display page
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
Alerts: &[]libtemplate.Alert{
{
Level: "success",
Text: "Block deleted.",
},
},
})
}

View File

@ -0,0 +1,105 @@
package webapp
import (
"errors"
"github.com/feditools/go-lib/language"
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/token"
"github.com/feditools/relay/internal/util"
"net/http"
)
func (m *Module) doEditBlock(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "doEditBlock")
// get localizer
localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer)
blockToken := r.FormValue(FormToken)
obfuscatedDomain := r.FormValue(FormObfuscatedDomain)
blockSubdomains := false
if r.FormValue(FormSubdomain) != "" {
blockSubdomains = true
}
// read block
kind, id, err := m.tokz.DecodeToken(blockToken)
if err != nil {
l.Debugf("decode token: %s", err.Error())
m.returnErrorPage(w, r, http.StatusBadRequest, "bad token")
return
}
if kind != token.KindBlock {
l.Debug("token is wrong kind")
m.returnErrorPage(w, r, http.StatusBadRequest, "bad token")
return
}
block, err := m.db.ReadBlock(r.Context(), id)
if err != nil {
if errors.Is(err, db.ErrNoEntries) {
m.returnErrorPage(w, r, http.StatusNotFound, blockToken)
return
}
l.Errorf("db read instance: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
// validate forms
formValidationFailed := false
var formObfuscatedDomainValidation *libtemplate.FormValidation
if obfuscatedDomain != "" {
formObfuscatedDomainValidation = &libtemplate.FormValidation{
Response: localizer.TextLooksGood().String(),
Valid: true,
}
}
if obfuscatedDomain != "" && !util.ValidateDomainObfuscation(block.Domain, obfuscatedDomain) {
formObfuscatedDomainValidation = &libtemplate.FormValidation{
Response: "Obfuscated domain doesn't match domain",
Valid: false,
}
formValidationFailed = true
}
if formValidationFailed {
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "editModal",
FormEditDomainValue: block.Domain,
FormEditObfuscatedDomainValue: obfuscatedDomain,
FormEditObfuscatedDomainValidation: formObfuscatedDomainValidation,
FormEditBlockSubdomainsValue: blockSubdomains,
})
return
}
// update block
block.ObfuscatedDomain = obfuscatedDomain
block.BlockSubdomains = blockSubdomains
err = m.logic.UpdateBlock(r.Context(), block)
if err != nil {
l.Errorf("updating block: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, ErrorResponseDBError)
return
}
// display page
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
Alerts: &[]libtemplate.Alert{
{
Level: "success",
Text: "Block updated.",
},
},
})
}

View File

@ -0,0 +1,78 @@
package webapp
import (
"encoding/csv"
"encoding/json"
"fmt"
libhttp "github.com/feditools/go-lib/http"
"net/http"
"strings"
)
// AdminBlockExportGetHandler serves the home page.
func (m *Module) AdminBlockExportGetHandler(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "AdminBlockExportGetHandler")
blockList, err := m.logic.GetBlockList(r.Context())
if err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
w.Header().Add("Content-Type", libhttp.MimeTextPlain.String())
w.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=\"blocklist.%s\"", libhttp.SuffixTextPlain.String()))
_, err = w.Write([]byte(strings.Join(*blockList, "\n")))
if err != nil {
l.Warnf("couldn't write to request: %s", err.Error())
}
}
// AdminBlockExportCSVGetHandler serves the home page.
func (m *Module) AdminBlockExportCSVGetHandler(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "AdminBlockExportCSVGetHandler")
blockList, err := m.db.ReadBlocks(r.Context())
if err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
var blockListData = [][]string{
{CSVHeaderDomain, CSVHeaderObfuscatedDomain, CSVHeaderBlockSubdomains},
}
for _, block := range blockList {
blockListData = append(blockListData, []string{block.Domain, block.ObfuscatedDomain, fmt.Sprintf("%t", block.BlockSubdomains)})
}
w.Header().Add("Content-Type", libhttp.MimeTextCSV.String())
w.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=\"blocklist.%s\"", libhttp.SuffixTextCSV.String()))
err = csv.NewWriter(w).WriteAll(blockListData)
if err != nil {
l.Warnf("couldn't write to request: %s", err.Error())
}
}
// AdminBlockExportJSONGetHandler serves the home page.
func (m *Module) AdminBlockExportJSONGetHandler(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "AdminBlockExportJSONGetHandler")
blockList, err := m.db.ReadBlocks(r.Context())
if err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
w.Header().Add("Content-Type", libhttp.MimeAppJSON.String())
w.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=\"blocklist.%s\"", libhttp.SuffixAppJSON.String()))
err = json.NewEncoder(w).Encode(blockList)
if err != nil {
l.Warnf("couldn't write to request: %s", err.Error())
}
}

View File

@ -0,0 +1,396 @@
package webapp
import (
"encoding/csv"
"encoding/json"
"fmt"
libhttp "github.com/feditools/go-lib/http"
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/models"
"github.com/feditools/relay/internal/util"
"io"
"io/ioutil"
"net/http"
"strings"
)
func (m *Module) doImportBlocks(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "doImportBlocks")
// get localizer
//localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer)
blockSubdomains := false
if r.FormValue(FormSubdomain) != "" {
blockSubdomains = true
}
file, handler, err := r.FormFile(FormFile)
if err != nil {
l.Errorf("reading file from request: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
defer file.Close()
filename := strings.Split(handler.Filename, ".")
switch strings.ToLower(filename[len(filename)-1]) {
case libhttp.SuffixAppJSON.String():
m.doImportBlocksAppJSON(w, r, file)
case libhttp.SuffixTextCSV.String():
m.doImportBlocksTextCSV(w, r, file, blockSubdomains)
case libhttp.SuffixTextPlain.String():
m.doImportBlocksTextPlain(w, r, file, blockSubdomains)
default:
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: fmt.Sprintf("Unknown file type %s.", filename[len(filename)-1]),
},
})
}
}
func (m *Module) doImportBlocksAppJSON(w http.ResponseWriter, r *http.Request, file io.Reader) {
l := logger.WithField("func", "doImportBlocks")
// get localizer
//localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer)
fileBlocks := new([]*models.Block)
err := json.NewDecoder(file).Decode(fileBlocks)
if err != nil {
msg := fmt.Sprintf("reading json: %s", err.Error())
l.Error(msg)
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: msg,
},
})
return
}
// make blocks
var blocksToAdd []*models.Block
for i, fileBlock := range *fileBlocks {
// skip empty line
if fileBlock.Domain == "" {
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: fmt.Sprintf("Block object %d missing domain", i),
},
})
return
}
// check if block exists, if yes skip
foundBlock, err := m.logic.IsDomainBlocked(r.Context(), fileBlock.Domain)
if err != nil {
l.Debugf("error getting domain block: %s", err.Error())
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: fmt.Sprintf("Error checking for block %s: %s", fileBlock.Domain, err.Error()),
},
})
return
}
if foundBlock {
l.Tracef("skipping %s, already blocked", fileBlock.Domain)
continue
}
// validate domain obfuscation
if fileBlock.ObfuscatedDomain != "" && !util.ValidateDomainObfuscation(fileBlock.Domain, fileBlock.ObfuscatedDomain) {
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: fmt.Sprintf("Block object %d has invalid domain obfuscation", i),
},
})
return
}
// add new block to list
blocksToAdd = append(blocksToAdd, fileBlock)
}
// add blocks to database
if len(blocksToAdd) > 0 {
err := m.logic.AddBlock(r.Context(), blocksToAdd...)
if err != nil {
l.Errorf("add blocks: %s", err.Error())
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: ErrorResponseDBError,
},
})
return
}
}
// display page
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
Alerts: &[]libtemplate.Alert{
{
Level: "success",
Text: fmt.Sprintf("Imported %d blocks", len(blocksToAdd)),
},
},
})
}
func (m *Module) doImportBlocksTextCSV(w http.ResponseWriter, r *http.Request, file io.Reader, blockSubdomainsDefault bool) {
l := logger.WithField("func", "doImportBlocksTextCSV")
// get localizer
//localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer)
// read csv file
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
msg := fmt.Sprintf("reading csv: %s", err.Error())
l.Error(msg)
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: msg,
},
})
return
}
// check header row
metadata := map[string]int{
CSVHeaderDomain: -1,
CSVHeaderObfuscatedDomain: -1,
CSVHeaderBlockSubdomains: -1,
}
for i, cell := range records[0] {
for header, _ := range metadata {
if cell == header {
metadata[header] = i
}
}
}
// fail if Domain header not found
if metadata[CSVHeaderDomain] == -1 {
msg := fmt.Sprintf("header %s not found %+v", CSVHeaderDomain, metadata)
l.Debugf(msg)
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: msg,
},
})
return
}
// make blocks
var blocksToAdd []*models.Block
for i, row := range records {
// skip header
if i == 0 {
continue
}
domain := row[metadata[CSVHeaderDomain]]
// check if block exists, if yes skip
foundBlock, err := m.logic.IsDomainBlocked(r.Context(), domain)
if err != nil {
l.Debugf("error getting domain block: %s", err.Error())
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: fmt.Sprintf("Error checking for block %s: %s", domain, err.Error()),
},
})
return
}
if foundBlock {
l.Tracef("skipping %s, already blocked", domain)
continue
}
// create new block
newBlock := &models.Block{
Domain: domain,
}
if metadata[CSVHeaderObfuscatedDomain] != -1 {
obfuscatedDomain := row[metadata[CSVHeaderObfuscatedDomain]]
// validate domain obfuscation
if obfuscatedDomain != "" && !util.ValidateDomainObfuscation(domain, obfuscatedDomain) {
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: fmt.Sprintf("Block line %d has invalid domain obfuscation", i),
},
})
return
}
newBlock.ObfuscatedDomain = obfuscatedDomain
}
if metadata[CSVHeaderBlockSubdomains] != -1 {
if row[metadata[CSVHeaderBlockSubdomains]] == "true" {
newBlock.BlockSubdomains = true
} else {
newBlock.BlockSubdomains = false
}
} else {
newBlock.BlockSubdomains = blockSubdomainsDefault
}
// add new block to list
blocksToAdd = append(blocksToAdd, newBlock)
}
// add blocks to database
if len(blocksToAdd) > 0 {
err := m.logic.AddBlock(r.Context(), blocksToAdd...)
if err != nil {
l.Errorf("add blocks: %s", err.Error())
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: ErrorResponseDBError,
},
})
return
}
}
// display page
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
Alerts: &[]libtemplate.Alert{
{
Level: "success",
Text: fmt.Sprintf("Imported %d blocks", len(blocksToAdd)),
},
},
})
}
func (m *Module) doImportBlocksTextPlain(w http.ResponseWriter, r *http.Request, file io.Reader, blockSubdomainsDefault bool) {
l := logger.WithField("func", "doImportBlocksTextPlain")
// get localizer
//localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer)
fileBytes, err := ioutil.ReadAll(file)
if err != nil {
l.Errorf("reading file: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
domainStrings := strings.Split(string(fileBytes), "\n")
var blocksToAdd []*models.Block
for _, domainString := range domainStrings {
// skip empty line
if domainString == "" {
l.Tracef("skipping empty line")
continue
}
// check if block exists, if yes skip
foundBlock, err := m.logic.IsDomainBlocked(r.Context(), domainString)
if err != nil {
l.Debugf("error getting domain block: %s", err.Error())
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: fmt.Sprintf("Error checking for block %s: %s", domainString, err.Error()),
},
})
return
}
if foundBlock {
l.Tracef("skipping %s, already blocked", domainString)
continue
}
// add new block to list
blocksToAdd = append(blocksToAdd, &models.Block{
Domain: domainString,
BlockSubdomains: blockSubdomainsDefault,
})
}
// add blocks to database
if len(blocksToAdd) > 0 {
err := m.logic.AddBlock(r.Context(), blocksToAdd...)
if err != nil {
l.Errorf("add blocks: %s", err.Error())
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
DisplayModal: "importModal",
FormInputError: &libtemplate.Alert{
Level: "danger",
Text: ErrorResponseDBError,
},
})
return
}
}
// display page
m.displayAdminBlockList(w, r, displayAdminBlockListConfig{
Alerts: &[]libtemplate.Alert{
{
Level: "success",
Text: fmt.Sprintf("Imported %d blocks", len(blocksToAdd)),
},
},
})
}

View File

@ -0,0 +1,33 @@
package webapp
import (
"github.com/feditools/go-lib/language"
"github.com/feditools/relay/internal/http/template"
"net/http"
)
// AdminHomeGetHandler serves the home page.
func (m *Module) AdminHomeGetHandler(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "AdminHomeGetHandler")
// get localizer
localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer) //nolint
// Init template variables
tmplVars := &template.AdminHome{
Common: template.Common{
PageTitle: localizer.TextRelay(1).String(),
},
}
err := m.initTemplateAdmin(w, r, tmplVars)
if err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
err = m.executeTemplate(w, template.AdminHomeName, tmplVars)
if err != nil {
l.Errorf("could not render '%s' template: %s", template.AdminHomeName, err.Error())
}
}

View File

@ -0,0 +1,79 @@
package webapp
import (
libhttp "github.com/feditools/go-lib/http"
"github.com/feditools/go-lib/language"
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/http/template"
"github.com/feditools/relay/internal/path"
"net/http"
)
// AdminInstanceGetHandler serves the home page.
func (m *Module) AdminInstanceGetHandler(w http.ResponseWriter, r *http.Request) {
m.displayAdminInstance(w, r, displayAdminInstanceConfig{})
}
type displayAdminInstanceConfig struct {
Alerts *[]libtemplate.Alert
}
func (m *Module) displayAdminInstance(w http.ResponseWriter, r *http.Request, config displayAdminInstanceConfig) {
l := logger.WithField("func", "displayAdminInstance")
// get localizer
localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer) //nolint
// Init template variables
tmplVars := &template.AdminInstance{
Common: template.Common{
PageTitle: localizer.TextInstance(2).String(),
},
}
err := m.initTemplateAdmin(w, r, tmplVars)
if err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
// alerts
tmplVars.Alerts = config.Alerts
// get blocks
page, count, countFound := libhttp.GetPaginationFromURL(r.URL, defaultCount)
instanceCount, err := m.db.CountInstances(r.Context())
if err != nil {
l.Errorf("db count: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, ErrorResponseDBError)
return
}
instances, err := m.db.ReadInstancesPage(r.Context(), page-1, count)
if err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, ErrorResponseDBError)
return
}
tmplVars.Instances = instances
// make pagination
pageConf := &libtemplate.PaginationConfig{
Count: int(instanceCount),
DisplayCount: count,
HRef: path.AppAdminInstance,
MaxPagination: maxPagination,
Page: page,
}
if countFound {
pageConf.HRefCount = count
}
tmplVars.Pagination = libtemplate.MakePagination(pageConf)
err = m.executeTemplate(w, template.AdminInstanceName, tmplVars)
if err != nil {
l.Errorf("could not render '%s' template: %s", template.AdminInstanceName, err.Error())
}
}

View File

@ -0,0 +1,144 @@
package webapp
import (
"errors"
"fmt"
"github.com/feditools/go-lib/fedihelper"
"github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/models"
"github.com/feditools/relay/internal/path"
"github.com/feditools/relay/internal/token"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"net/http"
)
// CallbackOauthGetHandler handles an oauth callback.
func (m *Module) CallbackOauthGetHandler(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "CallbackOauthGetHandler")
// lookup instance
vars := mux.Vars(r)
kind, id, err := m.tokz.DecodeToken(vars[path.VarInstanceID])
if err != nil {
l.Debugf("decode token: %s", err.Error())
m.returnErrorPage(w, r, http.StatusBadRequest, "bad token")
return
}
if kind != token.KindInstance {
l.Debug("token is wrong kind")
m.returnErrorPage(w, r, http.StatusBadRequest, "bad token")
return
}
instance, err := m.db.ReadInstance(r.Context(), id)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
l.Errorf("db read instance: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
if errors.Is(err, db.ErrNoEntries) {
m.returnErrorPage(w, r, http.StatusNotFound, vars[path.VarInstanceID])
return
}
switch fedihelper.SoftwareName(instance.Software) {
case fedihelper.SoftwareMastodon:
// get code
var code []string
var ok bool
if code, ok = r.URL.Query()["code"]; !ok || len(code[0]) < 1 {
l.Debugf("missing code")
m.returnErrorPage(w, r, http.StatusBadRequest, "missing code")
return
}
// retrieve access token
var accessToken string
accessToken, err = m.fedi.Helper(fedihelper.SoftwareMastodon).GetAccessToken(
r.Context(),
path.GenCallbackOauth(m.domain, m.tokz.GetToken(instance)),
instance,
code[0],
)
if err != nil {
l.Errorf("get access token error: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
l.Debugf("access token: %s", accessToken)
// retrieve current account
accountI, err := m.fedi.Helper(fedihelper.SoftwareMastodon).GetCurrentAccount(
r.Context(),
instance,
accessToken,
)
if err != nil {
l.Errorf("get access token error: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
account, ok := accountI.(*models.Account)
if !ok {
msg := "can't cast account to Account"
l.Error(msg)
m.returnErrorPage(w, r, http.StatusInternalServerError, msg)
return
}
// increment login
err = m.db.IncAccountLoginCount(r.Context(), account)
if err != nil {
l.Errorf("db inc login: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
// init session
us := r.Context().Value(ContextKeySession).(*sessions.Session) // nolint
us.Values[SessionKeyAccountID] = account.ID
err = us.Save(r, w)
if err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
l.Debugf("account: %#v", account)
// redirect to last page
val := us.Values[SessionKeyLoginRedirect]
var loginRedirect string
if loginRedirect, ok = val.(string); !ok {
// redirect home page if no login-redirect
http.Redirect(w, r, path.AppHome, http.StatusFound)
return
}
// Set login redirect to nil
us.Values[SessionKeyLoginRedirect] = nil
err = us.Save(r, w)
if err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
http.Redirect(w, r, loginRedirect, http.StatusFound)
return
default:
m.returnErrorPage(w, r, http.StatusNotImplemented, fmt.Sprintf("no helper for '%s'", instance.Software))
return
}
}

View File

@ -0,0 +1,41 @@
package webapp
const (
defaultCount = 20
maxPagination = 5
// COAnonymous is an anonymous cross origin.
COAnonymous = "anonymous"
// ErrorResponseDBError is returned to the user when there is a database error
ErrorResponseDBError = "database error"
// ActionAdd is the value for an add action.
ActionAdd = "add"
// ActionDelete is the value for a delete action.
ActionDelete = "delete"
// ActionEdit is the value for an edit action.
ActionEdit = "edit"
// ActionImport is the value for an import action.
ActionImport = "import"
// CSVHeaderBlockSubdomains is the value for a Block Subdomains csv header.
CSVHeaderBlockSubdomains = "Block Subdomains"
// CSVHeaderDomain is the value for a Domain csv header.
CSVHeaderDomain = "Domain"
// CSVHeaderObfuscatedDomain is the value for an Obfuscated Domain csv header.
CSVHeaderObfuscatedDomain = "Obfuscated Domain"
// FormAction is the key for an action form field.
FormAction = "action"
// FormDomain is the key for a domain form field.
FormDomain = "domain"
// FormFile is the key for a file form field.
FormFile = "file"
// FormObfuscatedDomain is the key for a domain form field.
FormObfuscatedDomain = "obfuscated-domain"
// FormSubdomain is the key for a subdomain form field.
FormSubdomain = "subdomain"
// FormToken is the key for a token form field.
FormToken = "token"
)

View File

@ -0,0 +1,17 @@
package webapp
// ContextKey is a key used in http request contexts.
type ContextKey int
const (
// ContextKeySession is the persistent session.
ContextKeySession ContextKey = iota
// ContextKeyLocalizer is the language localizer.
ContextKeyLocalizer
// ContextKeyLanguage is the language.
ContextKeyLanguage
// ContextKeyAccount is the logged in user's account.
ContextKeyAccount
// ContextKeyOauthNonce is the oauth nonce.
ContextKeyOauthNonce
)

View File

@ -0,0 +1,82 @@
package webapp
import (
"fmt"
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/http/template"
"github.com/feditools/relay/internal/path"
"net/http"
"strings"
)
func (m *Module) returnErrorPage(w http.ResponseWriter, r *http.Request, code int, errStr string) {
l := logger.WithField("func", "returnErrorPage")
// Init template variables
tmplVars := &template.Error{}
err := m.initTemplate(w, r, tmplVars)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// add error css file
signature, err := m.getSignatureCached(strings.TrimPrefix(path.FileErrorCSS, "/"))
if err != nil {
l.Errorf("getting signature for %s: %s", path.FileErrorCSS, err.Error())
}
tmplVars.AddHeadLink(libtemplate.HeadLink{
HRef: path.FileErrorCSS,
Rel: "stylesheet",
CrossOrigin: "anonymous",
Integrity: signature,
})
// set image
tmplVars.Image = m.logoSrcDark
// set text
tmplVars.Header = fmt.Sprintf("%d", code)
tmplVars.SubHeader = http.StatusText(code)
tmplVars.PageTitle = fmt.Sprintf("%d - %s", code, http.StatusText(code))
tmplVars.Paragraph = errStr
// set top button
switch code {
case http.StatusUnauthorized:
tmplVars.ButtonHRef = path.AppLogin
tmplVars.ButtonLabel = "Login"
default:
tmplVars.ButtonHRef = path.AppHome
tmplVars.ButtonLabel = "Home"
}
w.WriteHeader(code)
err = m.executeTemplate(w, "error", tmplVars)
if err != nil {
logger.Errorf("could not render error template: %s", err.Error())
}
}
func (m *Module) methodNotAllowedHandler() http.Handler {
// wrap in middleware since middlware isn't run on error pages
return m.WrapInMiddlewares(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m.returnErrorPage(w, r, http.StatusMethodNotAllowed, r.Method)
}))
}
func (m *Module) notFoundHandler() http.Handler {
// wrap in middleware since middlware isn't run on error pages
return m.WrapInMiddlewares(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m.returnErrorPage(w, r, http.StatusNotFound, fmt.Sprintf("page not found: %s", r.URL.Path))
}))
}
func (m *Module) WrapInMiddlewares(h http.Handler) http.Handler {
return m.srv.WrapInMiddlewares(
m.Middleware(
h,
),
)
}

View File

@ -0,0 +1,60 @@
package webapp
import (
"github.com/feditools/go-lib/language"
"github.com/feditools/relay/internal/http/template"
"github.com/feditools/relay/internal/path"
"net/http"
)
// HomeGetHandler serves the home page.
func (m *Module) HomeGetHandler(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "HomeGetHandler")
// get localizer
localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer) //nolint
// Init template variables
tmplVars := &template.Home{}
err := m.initTemplatePublic(w, r, tmplVars)
if err != nil {
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
tmplVars.PageTitle = localizer.TextRelay(1).String()
tmplVars.InboxHref = path.GenInbox(m.domain)
tmplVars.ActorHref = path.GenActor(m.domain)
// add tooltips script
tmplVars.AddFooterExtraScript(JSTooltip())
followingInstance, err := m.db.ReadInstancesWhereFollowing(r.Context())
if err != nil {
l.Errorf("db read instances: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, ErrorResponseDBError)
return
}
tmplVars.FollowingInstances = followingInstance
blocks, err := m.db.ReadBlocks(r.Context())
if err != nil {
l.Errorf("db read blocks: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, ErrorResponseDBError)
return
}
tmplVars.Blocks = blocks
err = m.executeTemplate(w, template.HomeName, tmplVars)
if err != nil {
l.Errorf("could not render '%s' template: %s", template.HomeName, err.Error())
}
}
// ForwardToHomeHandler serves a home forwarder.
func (m *Module) ForwardToHomeHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, path.AppHome, http.StatusFound)
}

View File

@ -0,0 +1,83 @@
package webapp
import (
"fmt"
"github.com/feditools/go-lib/language"
)
const jsAdminBlock = `
const deleteModal = document.getElementById('deleteModal')
deleteModal.addEventListener('show.bs.modal', event => {
// Button that triggered the modal
const button = event.relatedTarget
// Extract info from data-bs-* attributes
const token = button.getAttribute('data-bs-token')
const domain = button.getAttribute('data-bs-domain')
// Update the modal's content.
const modalTitle = deleteModal.querySelector('.modal-title')
const confirmText = deleteModal.querySelector('.confirm-text')
const inputToken = deleteModal.querySelector('.modal-body #formDeleteInputToken')
modalTitle.textContent = %s
confirmText.textContent = %s
inputToken.value = token
})
const editModal = document.getElementById('editModal')
editModal.addEventListener('show.bs.modal', event => {
// Button that triggered the modal
const button = event.relatedTarget
// Extract info from data-bs-* attributes
const token = button.getAttribute('data-bs-token')
const domain = button.getAttribute('data-bs-domain')
const obfuscatedDomain = button.getAttribute('data-bs-obfuscated-domain')
const blockSubdomains = button.getAttribute('data-bs-block-subdomains')
// Update the modal's content.
const modalTitle = editModal.querySelector('.modal-title')
const inputToken = editModal.querySelector('.modal-body #formEditInputToken')
const inputDomain = editModal.querySelector('.modal-body #formEditInputDomain')
const inputObfuscatedDomain = editModal.querySelector('.modal-body #formEditInputObfuscatedDomain')
const inputSubdomain = editModal.querySelector('.modal-body #formEditInputSubdomain')
modalTitle.textContent = %s
inputToken.value = token
inputDomain.value = domain
inputObfuscatedDomain.value = obfuscatedDomain
if (blockSubdomains == "true") {
inputSubdomain.checked = true
} else {
inputSubdomain.checked = false
}
})
`
func JSAdminBlock(deleteTitleText, deleteConfirmText, editTitleText *language.LocalizedString) string {
return fmt.Sprintf(
jsAdminBlock,
"`"+deleteTitleText.String()+"`",
"`"+deleteConfirmText.String()+"`",
"`"+editTitleText.String()+"`",
)
}
const jsOpenModal = `
var autoOpenModal = new bootstrap.Modal(document.getElementById('%s'), {})
autoOpenModal.toggle()
`
func JSOpenModal(selector string) string {
return fmt.Sprintf(jsOpenModal, selector)
}
const jsTooltip = `
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
`
func JSTooltip() string {
return jsTooltip
}

View File

@ -0,0 +1,9 @@
package webapp
import (
"github.com/feditools/relay/internal/log"
)
type empty struct{}
var logger = log.WithPackageField(empty{})

View File

@ -0,0 +1,119 @@
package webapp
import (
"errors"
"github.com/feditools/go-lib"
"github.com/feditools/go-lib/language"
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/http/template"
"github.com/feditools/relay/internal/models"
"github.com/feditools/relay/internal/path"
nethttp "net/http"
"strings"
)
// LoginGetHandler serves the login page.
func (m *Module) LoginGetHandler(w nethttp.ResponseWriter, r *nethttp.Request) {
m.displayLoginPage(w, r, "", "")
}
// LoginPostHandler attempts a login.
func (m *Module) LoginPostHandler(w nethttp.ResponseWriter, r *nethttp.Request) {
l := logger.WithField("func", "LoginPostHandler")
// parse form data
if err := r.ParseForm(); err != nil {
m.returnErrorPage(w, r, nethttp.StatusInternalServerError, err.Error())
return
}
// split account
formAccount := r.Form.Get("account")
_, domain, err := lib.SplitAccount(formAccount)
if err != nil {
m.returnErrorPage(w, r, nethttp.StatusBadRequest, err.Error())
return
}
// get instance
var instance *models.Instance
instance, err = m.db.ReadInstanceByDomain(r.Context(), domain)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
// actual db error
m.returnErrorPage(w, r, nethttp.StatusInternalServerError, err.Error())
return
}
if errors.Is(err, db.ErrNoEntries) {
instance, err = m.fedi.GenInstanceFromDomain(r.Context(), domain)
if err != nil {
m.returnErrorPage(w, r, nethttp.StatusInternalServerError, err.Error())
return
}
err = m.db.CreateInstance(r.Context(), instance)
if err != nil {
m.returnErrorPage(w, r, nethttp.StatusInternalServerError, err.Error())
return
}
}
// check if account exists
loginURL, err := m.fedi.GetLoginURL(
r.Context(),
path.GenCallbackOauth(m.domain, m.tokz.GetToken(instance)),
instance,
)
if err != nil {
l.Errorf("get login url: %s", err.Error())
m.returnErrorPage(w, r, nethttp.StatusInternalServerError, err.Error())
return
}
nethttp.Redirect(w, r, loginURL.String(), nethttp.StatusFound)
}
func (m *Module) displayLoginPage(w nethttp.ResponseWriter, r *nethttp.Request, account, formError string) {
l := logger.WithField("func", "displayLoginPage")
// get localizer
localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer) // nolint
// Init template variables
tmplVars := &template.Login{}
err := m.initTemplate(w, r, tmplVars)
if err != nil {
nethttp.Error(w, err.Error(), nethttp.StatusInternalServerError)
return
}
// add error css file
signature, err := m.getSignatureCached(strings.TrimPrefix(path.FileLoginCSS, "/"))
if err != nil {
l.Errorf("getting signature for %s: %s", path.FileLoginCSS, err.Error())
}
tmplVars.AddHeadLink(libtemplate.HeadLink{
HRef: path.FileLoginCSS,
Rel: "stylesheet",
CrossOrigin: "anonymous",
Integrity: signature,
})
tmplVars.PageTitle = localizer.TextLogin().String()
// set form values
tmplVars.FormError = formError
tmplVars.FormAccount = account
err = m.executeTemplate(w, template.LoginName, tmplVars)
if err != nil {
l.Errorf("could not render %s template: %s", template.LoginName, err.Error())
}
}

View File

@ -0,0 +1,25 @@
package webapp
import (
"github.com/feditools/relay/internal/path"
"github.com/gorilla/sessions"
"net/http"
)
// LogoutGetHandler logs a user out.
func (m *Module) LogoutGetHandler(w http.ResponseWriter, r *http.Request) {
// Init Session
us := r.Context().Value(ContextKeySession).(*sessions.Session) // nolint
// Set account to nil
us.Values[SessionKeyAccountID] = nil
if err := us.Save(r, w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
http.Redirect(w, r, path.AppHome, http.StatusFound)
}

View File

@ -0,0 +1,90 @@
package webapp
import (
"context"
libhttp "github.com/feditools/go-lib/http"
"github.com/feditools/go-lib/language"
"net/http"
)
// Middleware runs on every http request.
func (m *Module) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l := logger.WithField("func", "middleware")
// Init Session
us, err := m.store.Get(r, "relay")
if err != nil {
l.Errorf("get session: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
ctx := context.WithValue(r.Context(), ContextKeySession, us)
// Retrieve our account and type-assert it
val := us.Values[SessionKeyAccountID]
if accountID, ok := val.(int64); ok {
// read federated accounts
account, err := m.db.ReadAccount(ctx, accountID)
if err != nil {
l.Errorf("db read: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
if account != nil {
// read federated instance
instance, err := m.db.ReadInstance(ctx, account.InstanceID)
if err != nil {
l.Errorf("db read: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
account.Instance = instance
ctx = context.WithValue(ctx, ContextKeyAccount, account)
}
}
// create localizer
lang := r.FormValue("lang")
accept := r.Header.Get("Accept-Language")
localizer, err := m.language.NewLocalizer(lang, accept)
if err != nil {
l.Errorf("could get localizer: %s", err.Error())
m.returnErrorPage(w, r, http.StatusInternalServerError, err.Error())
return
}
ctx = context.WithValue(ctx, ContextKeyLocalizer, localizer)
// set request language
ctx = context.WithValue(ctx, ContextKeyLanguage, libhttp.GetPageLang(lang, accept, m.language.Language().String()))
// Do Request
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// MiddlewareRequireAdmin will redirect a user to login page if user not in context and will return unauthorized for
// a non admin user.
func (m *Module) MiddlewareRequireAdmin(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
account, shouldReturn := m.authRequireLoggedIn(w, r)
if shouldReturn {
return
}
if !account.IsAdmin {
localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer) // nolint
m.returnErrorPage(w, r, http.StatusForbidden, localizer.TextUnauthorized().String())
return
}
next.ServeHTTP(w, r)
})
}

View File

@ -0,0 +1,10 @@
package webapp
import (
"github.com/feditools/relay/internal/http/template"
"net/http"
)
func makePublicNavbar(_ *http.Request) template.Navbar {
return template.Navbar{}
}

View File

@ -0,0 +1 @@
package webapp

View File

@ -0,0 +1,39 @@
package webapp
import (
"github.com/feditools/relay/internal/http"
"github.com/feditools/relay/internal/path"
nethttp "net/http"
)
// Route attaches routes to the web server.
func (m *Module) Route(s *http.Server) error {
s.HandleFunc("/", m.ForwardToHomeHandler).Methods(nethttp.MethodGet)
s.HandleFunc(path.App, m.ForwardToHomeHandler).Methods(nethttp.MethodGet)
webapp := s.PathPrefix(path.App).Subrouter()
webapp.Use(m.Middleware)
webapp.NotFoundHandler = m.notFoundHandler()
webapp.MethodNotAllowedHandler = m.methodNotAllowedHandler()
webapp.HandleFunc(path.AppSubCallbackOauth, m.CallbackOauthGetHandler).Methods(nethttp.MethodGet)
webapp.HandleFunc(path.AppSubHome, m.HomeGetHandler).Methods(nethttp.MethodGet)
webapp.HandleFunc(path.AppSubLogin, m.LoginGetHandler).Methods(nethttp.MethodGet)
webapp.HandleFunc(path.AppSubLogin, m.LoginPostHandler).Methods(nethttp.MethodPost)
webapp.HandleFunc(path.AppSubLogout, m.LogoutGetHandler).Methods(nethttp.MethodGet)
admin := webapp.PathPrefix(path.AppAdmin).Subrouter()
admin.Use(m.MiddlewareRequireAdmin)
admin.NotFoundHandler = m.notFoundHandler()
admin.MethodNotAllowedHandler = m.methodNotAllowedHandler()
admin.HandleFunc(path.AppAdminSubBlock, m.AdminBlockGetHandler).Methods(nethttp.MethodGet)
admin.HandleFunc(path.AppAdminSubBlock, m.AdminBlockPostHandler).Methods(nethttp.MethodPost)
admin.HandleFunc(path.AppAdminSubBlockExport, m.AdminBlockExportGetHandler).Methods(nethttp.MethodGet)
admin.HandleFunc(path.AppAdminSubBlockExportCSV, m.AdminBlockExportCSVGetHandler).Methods(nethttp.MethodGet)
admin.HandleFunc(path.AppAdminSubBlockExportJSON, m.AdminBlockExportJSONGetHandler).Methods(nethttp.MethodGet)
admin.HandleFunc(path.AppAdminSubHome, m.AdminHomeGetHandler).Methods(nethttp.MethodGet)
admin.HandleFunc(path.AppAdminSubInstance, m.AdminInstanceGetHandler).Methods(nethttp.MethodGet)
return nil
}

View File

@ -0,0 +1,11 @@
package webapp
// SessionKey is a key used for storing data in a web session.
type SessionKey int
const (
// SessionKeyAccountID contains the id of the currently logged-in user.
SessionKeyAccountID SessionKey = iota
// SessionKeyLoginRedirect contains the url to be redirected too after logging in.
SessionKeyLoginRedirect
)

View File

@ -0,0 +1,101 @@
package webapp
import (
"bytes"
"github.com/feditools/go-lib/language"
"github.com/feditools/relay/internal/http/template"
"github.com/feditools/relay/internal/models"
"net/http"
)
func (m *Module) executeTemplate(w http.ResponseWriter, name string, tmplVars interface{}) error {
b := new(bytes.Buffer)
err := m.templates.ExecuteTemplate(b, name, tmplVars)
if err != nil {
return err
}
if m.minify == nil {
_, err := w.Write(b.Bytes())
return err
}
return m.minify.Minify("text/html", w, b)
}
func (m *Module) initTemplate(_ http.ResponseWriter, r *http.Request, tmpl template.InitTemplate) error {
// l := logger.WithField("func", "initTemplate")
// set text handler
localizer := r.Context().Value(ContextKeyLocalizer).(*language.Localizer) // nolint
tmpl.SetLocalizer(localizer)
// set language
lang := r.Context().Value(ContextKeyLanguage).(string) // nolint
tmpl.SetLanguage(lang)
// set logo image src
tmpl.SetLogoSrc(m.logoSrcDark, m.logoSrcLight)
// add css
for _, link := range m.headLinks {
tmpl.AddHeadLink(link)
}
// add scripts
for _, script := range m.footerScripts {
tmpl.AddFooterScript(script)
}
if r.Context().Value(ContextKeyAccount) != nil {
account := r.Context().Value(ContextKeyAccount).(*models.Account) // nolint
tmpl.SetAccount(account)
}
// try to read session data
/*if r.Context().Value(http.ContextKeySession) == nil {
return nil
}
us := r.Context().Value(http.ContextKeySession).(*sessions.Session)
saveSession := false
if saveSession {
err := us.Save(r, w)
if err != nil {
l.Warningf("initTemplate could not save session: %s", err.Error())
return err
}
}*/
return nil
}
func (m *Module) initTemplateAdmin(w http.ResponseWriter, r *http.Request, tmpl template.InitTemplate) error {
err := m.initTemplate(w, r, tmpl)
if err != nil {
return err
}
// make admin navbar
navbar := makeAdminNavbar(r)
tmpl.SetNavbarDark(true)
tmpl.SetNavbar(navbar)
return nil
}
func (m *Module) initTemplatePublic(w http.ResponseWriter, r *http.Request, tmpl template.InitTemplate) error {
err := m.initTemplate(w, r, tmpl)
if err != nil {
return err
}
// make admin navbar
navbar := makePublicNavbar(r)
tmpl.SetNavbarDark(false)
tmpl.SetNavbar(navbar)
return nil
}

View File

@ -0,0 +1,98 @@
package webapp
import (
"crypto/sha512"
"encoding/base64"
"fmt"
"github.com/feditools/relay/internal/models"
"github.com/feditools/relay/internal/path"
"github.com/feditools/relay/web"
"github.com/gorilla/sessions"
"io/ioutil"
nethttp "net/http"
)
// auth helpers
func (m *Module) authRequireLoggedIn(w nethttp.ResponseWriter, r *nethttp.Request) (*models.Account, bool) {
us := r.Context().Value(ContextKeySession).(*sessions.Session) // nolint
account, ok := r.Context().Value(ContextKeyAccount).(*models.Account)
if !ok {
// Save current page
if r.URL.Query().Encode() == "" {
us.Values[SessionKeyLoginRedirect] = r.URL.Path
} else {
us.Values[SessionKeyLoginRedirect] = r.URL.Path + "?" + r.URL.Query().Encode()
}
err := us.Save(r, w)
if err != nil {
m.returnErrorPage(w, r, nethttp.StatusInternalServerError, err.Error())
return nil, true
}
// redirect to login
nethttp.Redirect(w, r, path.AppLogin, nethttp.StatusFound)
return nil, true
}
return account, false
}
// signature caching
func getSignature(filePath string) (string, error) {
l := logger.WithField("func", "getSignature")
file, err := web.Files.Open(filePath)
if err != nil {
l.Errorf("opening file: %s", err.Error())
return "", err
}
// read it
data, err := ioutil.ReadAll(file)
if err != nil {
return "", err
}
// hash it
h := sha512.New384()
_, err = h.Write(data)
if err != nil {
return "", err
}
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
return fmt.Sprintf("sha384-%s", signature), nil
}
func (m *Module) getSignatureCached(filePath string) (string, error) {
if sig, ok := m.readCachedSignature(filePath); ok {
return sig, nil
}
sig, err := getSignature(filePath)
if err != nil {
return "", err
}
m.writeCachedSignature(filePath, sig)
return sig, nil
}
func (m *Module) readCachedSignature(filePath string) (string, bool) {
m.sigCacheLock.RLock()
val, ok := m.sigCache[filePath]
m.sigCacheLock.RUnlock()
return val, ok
}
func (m *Module) writeCachedSignature(filePath string, sig string) {
m.sigCacheLock.Lock()
m.sigCache[filePath] = sig
m.sigCacheLock.Unlock()
}

View File

@ -0,0 +1,9 @@
package webapp
import "github.com/go-playground/validator/v10"
var validate *validator.Validate
func init() {
validate = validator.New()
}

View File

@ -0,0 +1,176 @@
package webapp
import (
"context"
"encoding/gob"
"github.com/feditools/go-lib/language"
"github.com/feditools/go-lib/metrics"
libtemplate "github.com/feditools/go-lib/template"
"github.com/feditools/relay/internal/config"
"github.com/feditools/relay/internal/db"
"github.com/feditools/relay/internal/fedi"
ihttp "github.com/feditools/relay/internal/http"
itemplate "github.com/feditools/relay/internal/http/template"
"github.com/feditools/relay/internal/kv"
"github.com/feditools/relay/internal/logic"
"github.com/feditools/relay/internal/path"
"github.com/feditools/relay/internal/runner"
"github.com/feditools/relay/internal/token"
"github.com/go-redis/redis/v8"
"github.com/gorilla/sessions"
"github.com/rbcervilla/redisstore/v8"
"github.com/spf13/viper"
"github.com/tdewolff/minify/v2"
"github.com/tdewolff/minify/v2/html"
"html/template"
"strings"
"sync"
"time"
)
const SessionMaxAge = 30 * 24 * time.Hour // 30 days
// Module contains a webapp module for the web server. Implements web.Module.
type Module struct {
db db.DB
fedi *fedi.Module
logic logic.Logic
language *language.Module
metrics metrics.Collector
minify *minify.M
runner runner.Runner
store sessions.Store
srv *ihttp.Server
templates *template.Template
tokz *token.Tokenizer
domain string
logoSrcDark string
logoSrcLight string
headLinks []libtemplate.HeadLink
footerScripts []libtemplate.Script
sigCache map[string]string
sigCacheLock sync.RWMutex
}
//revive:disable:argument-limit
// New returns a new webapp module.
func New(
ctx context.Context,
d db.DB,
f *fedi.Module,
lMod *language.Module,
l logic.Logic,
mc metrics.Collector,
r redis.UniversalClient,
run runner.Runner,
tokz *token.Tokenizer,
) (*Module, error) {
log := logger.WithField("func", "New")
// create new store
store, err := redisstore.NewRedisStore(ctx, r)
if err != nil {
log.Errorf("create redis store: %s", err.Error())
return nil, err
}
store.KeyPrefix(kv.KeySession())
store.Options(sessions.Options{
Path: "/",
Domain: viper.GetString(config.Keys.ServerExternalHostname),
MaxAge: int(SessionMaxAge.Seconds()),
})
// register models for GOB
gob.Register(SessionKey(0))
// minify
var m *minify.M
if viper.GetBool(config.Keys.ServerMinifyHTML) {
m = minify.New()
m.AddFunc("text/html", html.Minify)
}
// get templates
tmpl, err := itemplate.New(tokz)
if err != nil {
log.Errorf("create temates: %s", err.Error())
return nil, err
}
// generate head links
hl := []libtemplate.HeadLink{
{
HRef: viper.GetString(config.Keys.WebappBootstrapCSSURI),
Rel: "stylesheet",
CrossOrigin: COAnonymous,
Integrity: viper.GetString(config.Keys.WebappBootstrapCSSIntegrity),
},
{
HRef: viper.GetString(config.Keys.WebappFontAwesomeCSSURI),
Rel: "stylesheet",
CrossOrigin: COAnonymous,
Integrity: viper.GetString(config.Keys.WebappFontAwesomeCSSIntegrity),
},
}
paths := []string{
path.FileDefaultCSS,
}
for _, p := range paths {
signature, err := getSignature(strings.TrimPrefix(p, "/"))
if err != nil {
log.Errorf("getting signature for %s: %s", p, err.Error())
}
hl = append(hl, libtemplate.HeadLink{
HRef: p,
Rel: "stylesheet",
CrossOrigin: COAnonymous,
Integrity: signature,
})
}
// generate head links
fs := []libtemplate.Script{
{
Src: viper.GetString(config.Keys.WebappBootstrapJSURI),
CrossOrigin: COAnonymous,
Integrity: viper.GetString(config.Keys.WebappBootstrapJSIntegrity),
},
}
return &Module{
db: d,
fedi: f,
language: lMod,
logic: l,
metrics: mc,
minify: m,
runner: run,
store: store,
templates: tmpl,
tokz: tokz,
domain: viper.GetString(config.Keys.ServerExternalHostname),
logoSrcDark: viper.GetString(config.Keys.WebappLogoSrcDark),
logoSrcLight: viper.GetString(config.Keys.WebappLogoSrcLight),
headLinks: hl,
footerScripts: fs,
sigCache: map[string]string{},
}, nil
} //revive:enable:argument-limit
// Name return the module name.
func (*Module) Name() string {
return config.ServerRoleWebapp
}
// SetServer adds a reference to the server to the module.
func (m *Module) SetServer(s *ihttp.Server) {
m.srv = s
}

26
internal/kv/error.go Normal file
View File

@ -0,0 +1,26 @@
package kv
import "fmt"
// Error represents a database specific error.
type Error error
var (
// ErrNil is returned when the kv value is nil.
ErrNil Error = fmt.Errorf("nil")
)
// EncryptionError is returned when a an encryption error occurs.
type EncryptionError struct {
message string
}
// Error returns the error message as a string.
func (e *EncryptionError) Error() string {
return fmt.Sprintf("encryption: %s", e.message)
}
// NewEncryptionError wraps a message in an EncryptionError object.
func NewEncryptionError(msg string) Error {
return &EncryptionError{message: msg}
}

38
internal/kv/keys.go Normal file
View File

@ -0,0 +1,38 @@
package kv
import "strconv"
const (
keyBase = "relay:"
keyAccount = keyBase + "acct:"
keyAccountAccessToken = keyAccount + "at:"
keyFedi = keyBase + "fedi:"
keyFediActor = keyFedi + "actor:"
keyFediHostMeta = keyFedi + "hm:"
keyFediNodeInfo = keyFedi + "ni:"
keyInstance = keyBase + "instance:"
keyInstanceOAuth = keyInstance + "oauth:"
keySession = keyBase + "session:"
)
// KeyAccountAccessToken returns the kv key which holds a user's access token.
func KeyAccountAccessToken(i int64) string { return keyAccountAccessToken + strconv.FormatInt(i, 10) }
// KeyFediActor returns the kv key which holds cached actor.
func KeyFediActor(a string) string { return keyFediActor + a }
// KeyFediNodeInfo returns the kv key which holds cached nodeinfo.
func KeyFediNodeInfo(d string) string { return keyFediNodeInfo + d }
// KeyFediHostMeta returns the kv key which holds cached host meta.
func KeyFediHostMeta(d string) string { return keyFediHostMeta + d }
// KeyInstanceOAuth returns the kv key which holds an instance's oauth tokens.
func KeyInstanceOAuth(i int64) string { return keyInstanceOAuth + strconv.FormatInt(i, 10) }
// KeySession returns the base kv key prefix.
func KeySession() string { return keySession }

10
internal/kv/kv.go Normal file
View File

@ -0,0 +1,10 @@
package kv
import (
"github.com/feditools/go-lib/fedihelper"
)
// KV represents a key value store.
type KV interface {
fedihelper.KV
}

View File

@ -0,0 +1,44 @@
package redis
import (
"context"
"github.com/feditools/relay/internal/kv"
"github.com/feditools/relay/internal/util"
)
func (c *Client) DeleteAccessToken(ctx context.Context, accountID int64) error {
_, err := c.redis.Del(ctx, kv.KeyAccountAccessToken(accountID)).Result()
if err != nil {
return c.ProcessError(err)
}
return nil
}
func (c *Client) GetAccessToken(ctx context.Context, accountID int64) (string, error) {
resp, err := c.redis.Get(ctx, kv.KeyAccountAccessToken(accountID)).Bytes()
if err != nil {
return "", c.ProcessError(err)
}
data, err := util.Decrypt(resp)
if err != nil {
return "", kv.NewEncryptionError(err.Error())
}
return string(data), nil
}
func (c *Client) SetAccessToken(ctx context.Context, accountID int64, accessToken string) error {
data, err := util.Encrypt([]byte(accessToken))
if err != nil {
return kv.NewEncryptionError(err.Error())
}
_, err = c.redis.Set(ctx, kv.KeyAccountAccessToken(accountID), data, 0).Result()
if err != nil {
return c.ProcessError(err)
}
return nil
}

View File

@ -0,0 +1,37 @@
package redis
import (
"context"
"github.com/feditools/relay/internal/kv"
"time"
)
// DeleteActor deletes fedi actor from redis.
func (c *Client) DeleteActor(ctx context.Context, actorURI string) error {
_, err := c.redis.Del(ctx, kv.KeyFediActor(actorURI)).Result()
if err != nil {
return c.ProcessError(err)
}
return nil
}
// GetActor retrieves fedi actor from redis.
func (c *Client) GetActor(ctx context.Context, actorURI string) ([]byte, error) {
resp, err := c.redis.Get(ctx, kv.KeyFediActor(actorURI)).Bytes()
if err != nil {
return nil, c.ProcessError(err)
}
return resp, nil
}
// SetActor adds fedi actor to redis.
func (c *Client) SetActor(ctx context.Context, actorURI string, actor []byte, expire time.Duration) error {
_, err := c.redis.SetEX(ctx, kv.KeyFediActor(actorURI), actor, expire).Result()
if err != nil {
return c.ProcessError(err)
}
return nil
}

View File

@ -0,0 +1,46 @@
package redis
import (
"context"
"github.com/feditools/relay/internal/config"
"github.com/feditools/relay/internal/kv"
"github.com/go-redis/redis/v8"
"github.com/spf13/viper"
)
// New creates a new redis client.
func New(ctx context.Context) (*Client, error) {
l := logger.WithField("func", "New")
r := redis.NewClient(&redis.Options{
Addr: viper.GetString(config.Keys.RedisAddress),
Password: viper.GetString(config.Keys.RedisPassword),
DB: viper.GetInt(config.Keys.RedisDB),
})
c := Client{
redis: r,
}
resp := c.redis.Ping(ctx)
l.Debugf("%s", resp.String())
return &c, nil
}
// Client represents a redis client.
type Client struct {
redis *redis.Client
}
var _ kv.KV = (*Client)(nil)
// Close closes the redis pool.
func (c *Client) Close(_ context.Context) kv.Error {
return c.redis.Close()
}
// RedisClient returns the redis client.
func (c *Client) RedisClient() *redis.Client {
return c.redis
}

View File

@ -0,0 +1,19 @@
package redis
import (
"errors"
"github.com/feditools/relay/internal/kv"
"github.com/go-redis/redis/v8"
)
// ProcessError replaces any known values with our own db.Error types.
func (*Client) ProcessError(err error) kv.Error {
switch {
case err == nil:
return nil
case errors.Is(err, redis.Nil):
return kv.ErrNil
default:
return err
}
}

39
internal/kv/redis/fedi.go Normal file
View File

@ -0,0 +1,39 @@
package redis
import (
"context"
"github.com/feditools/relay/internal/kv"
"time"
)
// fedi node info
// DeleteFediNodeInfo deletes fedi nodeinfo from redis.
func (c *Client) DeleteFediNodeInfo(ctx context.Context, domain string) error {
_, err := c.redis.Del(ctx, kv.KeyFediNodeInfo(domain)).Result()
if err != nil {
return c.ProcessError(err)
}
return nil
}
// GetFediNodeInfo retrieves fedi nodeinfo from redis.
func (c *Client) GetFediNodeInfo(ctx context.Context, domain string) ([]byte, error) {
resp, err := c.redis.Get(ctx, kv.KeyFediNodeInfo(domain)).Bytes()
if err != nil {
return nil, c.ProcessError(err)
}
return resp, nil
}
// SetFediNodeInfo adds fedi nodeinfo to redis.
func (c *Client) SetFediNodeInfo(ctx context.Context, domain string, nodeinfo []byte, expire time.Duration) error {
_, err := c.redis.SetEX(ctx, kv.KeyFediNodeInfo(domain), nodeinfo, expire).Result()
if err != nil {
return c.ProcessError(err)
}
return nil
}

View File

@ -0,0 +1,37 @@
package redis
import (
"context"
"github.com/feditools/relay/internal/kv"
"time"
)
// DeleteHostMeta deletes fedi host meta from redis.
func (c *Client) DeleteHostMeta(ctx context.Context, domain string) error {
_, err := c.redis.Del(ctx, kv.KeyFediHostMeta(domain)).Result()
if err != nil {
return c.ProcessError(err)
}
return nil
}
// GetHostMeta retrieves fedi host meta from redis.
func (c *Client) GetHostMeta(ctx context.Context, domain string) ([]byte, error) {
resp, err := c.redis.Get(ctx, kv.KeyFediHostMeta(domain)).Bytes()
if err != nil {
return nil, c.ProcessError(err)
}
return resp, nil
}
// SetHostMeta adds fedi host meta to redis.
func (c *Client) SetHostMeta(ctx context.Context, domain string, hostmeta []byte, expire time.Duration) error {
_, err := c.redis.SetEX(ctx, kv.KeyFediHostMeta(domain), hostmeta, expire).Result()
if err != nil {
return c.ProcessError(err)
}
return nil
}

View File

@ -0,0 +1,66 @@
package redis
import (
"context"
"encoding/json"
"github.com/feditools/relay/internal/kv"
"github.com/feditools/relay/internal/util"
)
type instanceOAuth struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
}
func (c *Client) DeleteInstanceOAuth(ctx context.Context, instanceID int64) error {
_, err := c.redis.Del(ctx, kv.KeyInstanceOAuth(instanceID)).Result()
if err != nil {
return c.ProcessError(err)
}
return nil
}
func (c *Client) GetInstanceOAuth(ctx context.Context, instanceID int64) (string, string, error) {
resp, err := c.redis.Get(ctx, kv.KeyInstanceOAuth(instanceID)).Bytes()
if err != nil {
return "", "", c.ProcessError(err)
}
jsondata, err := util.Decrypt(resp)
if err != nil {
return "", "", kv.NewEncryptionError(err.Error())
}
var io instanceOAuth
err = json.Unmarshal(jsondata, &io)
if err != nil {
return "", "", c.ProcessError(err)
}
return io.ClientID, io.ClientSecret, nil
}
func (c *Client) SetInstanceOAuth(ctx context.Context, instanceID int64, clientID string, clientSecret string) error {
io := instanceOAuth{
ClientID: clientID,
ClientSecret: clientSecret,
}
jsonData, err := json.Marshal(io)
if err != nil {
return c.ProcessError(err)
}
data, err := util.Encrypt(jsonData)
if err != nil {
return kv.NewEncryptionError(err.Error())
}
_, err = c.redis.Set(ctx, kv.KeyInstanceOAuth(instanceID), data, 0).Result()
if err != nil {
return c.ProcessError(err)
}
return nil
}

View File

@ -0,0 +1,7 @@
package redis
import "github.com/feditools/relay/internal/log"
type empty struct{}
var logger = log.WithPackageField(empty{})

Some files were not shown because too many files have changed in this diff Show More