mirror of
https://git.ptzo.gdn/feditools/relay.git
synced 2024-09-21 09:47:11 +00:00
Webapp (#29)
This commit is contained in:
parent
c1d1f734e0
commit
4307c4b894
6
.gitignore
vendored
6
.gitignore
vendored
@ -13,3 +13,9 @@
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# 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
1
Jenkinsfile
vendored
@ -18,6 +18,7 @@ pipeline {
|
||||
script {
|
||||
sh """#!/bin/bash
|
||||
make clean
|
||||
make stage-static
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
7
Makefile
7
Makefile
@ -43,6 +43,11 @@ lint:
|
||||
@echo linting
|
||||
@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
|
||||
go test -cover ./...
|
||||
|
||||
@ -58,4 +63,4 @@ tidy:
|
||||
vendor: tidy
|
||||
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
32
cmd/relay/account.go
Normal 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
|
||||
}
|
7
cmd/relay/action/account/logger.go
Normal file
7
cmd/relay/action/account/logger.go
Normal file
@ -0,0 +1,7 @@
|
||||
package account
|
||||
|
||||
import "github.com/feditools/relay/internal/log"
|
||||
|
||||
type empty struct{}
|
||||
|
||||
var logger = log.WithPackageField(empty{})
|
107
cmd/relay/action/account/modify.go
Normal file
107
cmd/relay/action/account/modify.go
Normal 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
|
||||
}
|
@ -2,10 +2,10 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/feditools/relay/internal/metrics/statsd"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
@ -14,7 +14,10 @@ var Migrate action.Action = func(ctx context.Context) error {
|
||||
l := logger.WithField("func", "Migrate")
|
||||
|
||||
// create metrics collector
|
||||
metricsCollector, err := statsd.New()
|
||||
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
|
||||
@ -46,13 +49,5 @@ var Migrate action.Action = func(ctx context.Context) error {
|
||||
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
|
||||
}
|
||||
|
@ -3,17 +3,23 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"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/internal/clock"
|
||||
"github.com/feditools/relay/internal/config"
|
||||
"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/activitypub"
|
||||
"github.com/feditools/relay/internal/logic"
|
||||
"github.com/feditools/relay/internal/metrics/statsd"
|
||||
"github.com/feditools/relay/internal/http/static"
|
||||
"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/token"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tyrm/go-util"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
@ -27,7 +33,10 @@ var Start action.Action = func(topCtx context.Context) error {
|
||||
ctx, cancel := context.WithCancel(topCtx)
|
||||
|
||||
// create metrics collector
|
||||
metricsCollector, err := statsd.New()
|
||||
metricsCollector, err := statsd.New(
|
||||
viper.GetString(config.Keys.MetricsStatsDAddress),
|
||||
viper.GetString(config.Keys.MetricsStatsDPrefix),
|
||||
)
|
||||
if err != nil {
|
||||
l.Errorf("metrics: %s", err.Error())
|
||||
cancel()
|
||||
@ -35,12 +44,17 @@ var Start action.Action = func(topCtx context.Context) error {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
l.Info("closing metrics collector")
|
||||
err := metricsCollector.Close()
|
||||
if err != nil {
|
||||
l.Errorf("closing metrics: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
// create clock module
|
||||
l.Debug("creating clock")
|
||||
clockMod := clock.NewClock()
|
||||
|
||||
// create database client
|
||||
l.Debug("creating database client")
|
||||
dbClient, err := bun.New(ctx, metricsCollector)
|
||||
@ -57,13 +71,51 @@ var Start action.Action = func(topCtx context.Context) error {
|
||||
}
|
||||
}()
|
||||
|
||||
// create clock module
|
||||
l.Debug("creating clock")
|
||||
clockMod := clock.NewClock()
|
||||
// create http client
|
||||
httpClient, err := http.NewClient(ctx)
|
||||
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
|
||||
l.Debug("creating logic module")
|
||||
logicMod, err := logic.New(ctx, clockMod, dbClient)
|
||||
logicMod, err := logic1.New(ctx, clockMod, dbClient, httpClient)
|
||||
if err != nil {
|
||||
l.Errorf("logic: %s", err.Error())
|
||||
cancel()
|
||||
@ -71,6 +123,16 @@ var Start action.Action = func(topCtx context.Context) error {
|
||||
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
|
||||
runnerMod, err := faktory.New(logicMod)
|
||||
if err != nil {
|
||||
@ -94,7 +156,7 @@ var Start action.Action = func(topCtx context.Context) error {
|
||||
|
||||
// create web modules
|
||||
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)
|
||||
apMod, err := activitypub.New(ctx, logicMod, runnerMod)
|
||||
if err != nil {
|
||||
@ -105,9 +167,42 @@ var Start action.Action = func(topCtx context.Context) error {
|
||||
}
|
||||
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
|
||||
for _, mod := range webModules {
|
||||
mod.SetServer(server)
|
||||
err := mod.Route(server)
|
||||
if err != nil {
|
||||
l.Errorf("loading %s module: %s", mod.Name(), err.Error())
|
||||
|
16
cmd/relay/flag/account.go
Normal file
16
cmd/relay/flag/account.go
Normal 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)
|
||||
}
|
@ -12,5 +12,4 @@ func Database(cmd *cobra.Command, values config.Values) {
|
||||
// DatabaseMigrate adds all flags for running the database migrate command.
|
||||
func DatabaseMigrate(cmd *cobra.Command, values config.Values) {
|
||||
Database(cmd, values)
|
||||
cmd.PersistentFlags().Bool(config.Keys.DbLoadTestData, values.DbLoadTestData, usage.DbLoadTestData)
|
||||
}
|
||||
|
@ -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.CachedActorLimit, values.CachedActorLimit, usage.CachedActorLimit)
|
||||
cmd.PersistentFlags().Int(config.Keys.CachedDigestLimit, values.CachedDigestLimit, usage.CachedDigestLimit)
|
||||
cmd.PersistentFlags().String(config.Keys.EncryptionKey, values.EncryptionKey, usage.EncryptionKey)
|
||||
|
||||
// database
|
||||
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.DbTLSMode, values.DbTLSMode, usage.DbTLSMode)
|
||||
cmd.PersistentFlags().String(config.Keys.DbTLSCACert, values.DbTLSCACert, usage.DbTLSCACert)
|
||||
cmd.PersistentFlags().String(config.Keys.DbEncryptionKey, values.DbEncryptionKey, usage.DbEncryptionKey)
|
||||
|
||||
// metrics
|
||||
cmd.PersistentFlags().String(config.Keys.MetricsStatsDAddress, values.MetricsStatsDAddress, usage.MetricsStatsDAddress)
|
||||
|
13
cmd/relay/flag/redis.go
Normal file
13
cmd/relay/flag/redis.go
Normal 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)
|
||||
}
|
@ -7,6 +7,8 @@ import (
|
||||
|
||||
// Server adds all flags for running the server.
|
||||
func Server(cmd *cobra.Command, values config.Values) {
|
||||
Redis(cmd, values)
|
||||
|
||||
// server
|
||||
cmd.PersistentFlags().String(config.Keys.ServerExternalHostname, values.ServerExternalHostname, usage.ServerExternalHostname)
|
||||
cmd.PersistentFlags().String(config.Keys.ServerHTTPBind, values.ServerHTTPBind, usage.ServerHTTPBind)
|
||||
@ -15,4 +17,14 @@ func Server(cmd *cobra.Command, values config.Values) {
|
||||
|
||||
// runner
|
||||
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)
|
||||
}
|
||||
|
@ -10,15 +10,14 @@ var usage = config.KeyNames{
|
||||
ApplicationName: "Name of the application, used in various places internally",
|
||||
|
||||
// database
|
||||
DbType: "Database type: eg., postgres",
|
||||
DbAddress: "Database ipv4 address, hostname, or filename",
|
||||
DbPort: "Database port",
|
||||
DbUser: "Database username",
|
||||
DbPassword: "Database password",
|
||||
DbDatabase: "Database name",
|
||||
DbTLSMode: "Database tls mode",
|
||||
DbTLSCACert: "Path to CA cert for db tls connection",
|
||||
DbLoadTestData: "Should test data be loaded into the database",
|
||||
DbType: "Database type: eg., postgres",
|
||||
DbAddress: "Database ipv4 address, hostname, or filename",
|
||||
DbPort: "Database port",
|
||||
DbUser: "Database username",
|
||||
DbPassword: "Database password",
|
||||
DbDatabase: "Database name",
|
||||
DbTLSMode: "Database tls mode",
|
||||
DbTLSCACert: "Path to CA cert for db tls connection",
|
||||
|
||||
// server
|
||||
ServerExternalHostname: "The external hostname used by the server",
|
||||
|
@ -48,6 +48,7 @@ func main() {
|
||||
}
|
||||
|
||||
// add commands
|
||||
rootCmd.AddCommand(accountCommands())
|
||||
rootCmd.AddCommand(serverCommands())
|
||||
rootCmd.AddCommand(databaseCommands())
|
||||
|
||||
|
@ -19,6 +19,12 @@ services:
|
||||
environment:
|
||||
- FAKTORY_PASSWORD=test
|
||||
restart: always
|
||||
redis:
|
||||
image: redis:6
|
||||
command: redis-server --requirepass test
|
||||
ports:
|
||||
- "127.0.0.1:6379:6379/tcp"
|
||||
restart: always
|
||||
|
||||
influxdb:
|
||||
image: influxdb:2.1
|
||||
|
58
go.mod
58
go.mod
@ -3,40 +3,50 @@ module github.com/feditools/relay
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/cactus/go-statsd-client/v5 v5.0.0
|
||||
github.com/contribsys/faktory v1.6.1
|
||||
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/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-redis/redis/v8 v8.3.3
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/jackc/pgconn v1.12.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/speps/go-hashids/v2 v2.0.1
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
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/dialect/pgdialect v1.1.6
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.1.6
|
||||
github.com/uptrace/bun/extra/bundebug v1.1.6
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
modernc.org/sqlite v1.17.0
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||
modernc.org/sqlite v1.17.1
|
||||
)
|
||||
|
||||
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/felixge/httpsnoop v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.7.7 // indirect
|
||||
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // 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/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/inconshreveable/mousetrap v1.0.0 // 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/pgtype v1.11.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/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 // 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/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.3.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.1 // indirect
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
go.opentelemetry.io/otel v0.13.0 // 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/term v0.0.0-20210927222741-03fcf44c2211 // 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
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.35.26 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.2 // indirect
|
||||
modernc.org/libc v1.15.0 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.36.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.6 // indirect
|
||||
modernc.org/libc v1.16.2 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.0.7 // indirect
|
||||
modernc.org/opt v0.1.1 // indirect
|
||||
modernc.org/memory v1.1.1 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.1 // indirect
|
||||
modernc.org/token v1.0.0 // indirect
|
||||
)
|
||||
|
307
go.sum
307
go.sum
@ -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=
|
||||
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 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/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/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/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY=
|
||||
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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
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/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/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/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/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/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
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/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
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.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/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/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/go.mod h1:v4QoPaAzjWZ8zN2VFVGL5ep9C02mst0hQYHUpQwso4Q=
|
||||
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/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-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-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/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/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/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/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-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
|
||||
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.2/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 v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
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.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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/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=
|
||||
@ -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/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
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.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/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
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/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-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
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/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/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.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
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/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
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.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.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/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.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.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
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/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/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.0 h1:MNXbyPvd141JJqlU6gJKrczThxJy+kdCNivxZpBQFkw=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.0/go.mod h1:4OtLfzqyAxsscyCb//3gfqSvBc81gImX91LrZzczN1o=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
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/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
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/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
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/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 v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
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.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
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/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
||||
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/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/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/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/tyrm/go-util v0.4.2 h1:SFcHyY39tzl2v5tr35vVvRbaTDkLNQeaHgnCf0214D8=
|
||||
github.com/tyrm/go-util v0.4.2/go.mod h1:DPIb77f7bqnK517jXiV86pBmdQYRiuTY09YOCPZrxUA=
|
||||
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/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
|
||||
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/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/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/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/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
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.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
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-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-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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.4.0/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.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
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-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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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-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-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-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-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-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-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-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-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-20190226205417-e64efc72b421/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-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-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-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-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-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-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-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-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-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-20191228213918-04cbcbbfeed8/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-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-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-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-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-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-20210104204734-6f8348627aad/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-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-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-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-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I=
|
||||
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-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-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.3.0/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-20210105154028-b0ab187a4818/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.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-20190513163551-3ee3066db522/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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
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 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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
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/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
|
||||
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
|
||||
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.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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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-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=
|
||||
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
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=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.36.0 h1:0kmRkTmqNidmu3c7BNDSdVHCxXCkWLmWmCIVX4LUboo=
|
||||
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
|
||||
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
|
||||
modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
|
||||
modernc.org/ccgo/v3 v3.16.6 h1:3l18poV+iUemQ98O3X5OMr97LOqlzis+ytivU4NqGhA=
|
||||
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
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/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
|
||||
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
|
||||
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
|
||||
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
|
||||
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/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
|
||||
modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
|
||||
modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=
|
||||
modernc.org/libc v1.16.2 h1:+0cTSYtYuX5PzWOcxc+U3LPRie63WJPLSmR/1yGokjg=
|
||||
modernc.org/libc v1.16.2/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
|
||||
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/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||
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/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU=
|
||||
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.17.0 h1:yF5JlxCzQOn2WzyfGAPvHbMNx98ifXLno7a97qggXjE=
|
||||
modernc.org/sqlite v1.17.0/go.mod h1:yMNaeEckF88G+PcfRcZRwGE+XnBkzWl/j15bPsDm4QM=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
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/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/tcl v1.12.0 h1:Mw2Ukszv5qZbwk/wC9HkDjxhPD4exnd/7/zVxqrB4rY=
|
||||
modernc.org/tcl v1.12.0/go.mod h1:9zyAWctRV6IAkMTBeGLyYYqcBrTlVy3ubqiY3dzMfwI=
|
||||
modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao=
|
||||
modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
|
||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||
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.4.0/go.mod h1:x6vxerH3hHCPGA3DAM5pERRzuyJEO4UGVfdQC4NZYl0=
|
||||
modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM=
|
||||
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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
@ -1,6 +1,10 @@
|
||||
package config
|
||||
|
||||
const (
|
||||
// ServerRoleActivityPub represents the activity pub server role
|
||||
// ServerRoleActivityPub represents the activity pub server role.
|
||||
ServerRoleActivityPub = "activity-pub"
|
||||
// ServerRoleStatic represents the static asset server role.
|
||||
ServerRoleStatic = "static"
|
||||
// ServerRoleWebapp represents the webapp server role.
|
||||
ServerRoleWebapp = "webapp"
|
||||
)
|
||||
|
@ -8,24 +8,30 @@ type KeyNames struct {
|
||||
// application
|
||||
ActorKeySize string
|
||||
ApplicationName string
|
||||
ApplicationWebsite string
|
||||
CachedActivityLimit string
|
||||
CachedActorLimit string
|
||||
CachedDigestLimit string
|
||||
EncryptionKey string
|
||||
SoftwareVersion string
|
||||
TokenSalt string
|
||||
|
||||
// database
|
||||
DbType string
|
||||
DbAddress string
|
||||
DbPort string
|
||||
DbUser string
|
||||
DbPassword string
|
||||
DbDatabase string
|
||||
DbTLSMode string
|
||||
DbTLSCACert string
|
||||
DbLoadTestData string
|
||||
DbEncryptionKey string
|
||||
DbType string
|
||||
DbAddress string
|
||||
DbPort string
|
||||
DbUser string
|
||||
DbPassword string
|
||||
DbDatabase string
|
||||
DbTLSMode string
|
||||
DbTLSCACert string
|
||||
|
||||
// running
|
||||
// redis
|
||||
RedisAddress string
|
||||
RedisDB string
|
||||
RedisPassword string
|
||||
|
||||
// runner
|
||||
RunnerConcurrency string
|
||||
|
||||
// server
|
||||
@ -34,6 +40,20 @@ type KeyNames struct {
|
||||
ServerMinifyHTML 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
|
||||
MetricsStatsDAddress string
|
||||
MetricsStatsDPrefix string
|
||||
@ -47,22 +67,28 @@ var Keys = KeyNames{
|
||||
// application
|
||||
ActorKeySize: "actor-key-size",
|
||||
ApplicationName: "application-name",
|
||||
ApplicationWebsite: "application-website",
|
||||
CachedActivityLimit: "cached-activity-limit",
|
||||
CachedActorLimit: "cached-actor-limit",
|
||||
CachedDigestLimit: "cached-digest-limit",
|
||||
EncryptionKey: "encryption-key",
|
||||
SoftwareVersion: "software-version", // Set at build
|
||||
TokenSalt: "token-salt",
|
||||
|
||||
// database
|
||||
DbType: "db-type",
|
||||
DbAddress: "db-address",
|
||||
DbPort: "db-port",
|
||||
DbUser: "db-user",
|
||||
DbPassword: "db-password",
|
||||
DbDatabase: "db-database",
|
||||
DbTLSMode: "db-tls-mode",
|
||||
DbTLSCACert: "db-tls-ca-cert",
|
||||
DbLoadTestData: "test-data", // CLI only
|
||||
DbEncryptionKey: "db-crypto-key",
|
||||
DbType: "db-type",
|
||||
DbAddress: "db-address",
|
||||
DbPort: "db-port",
|
||||
DbUser: "db-user",
|
||||
DbPassword: "db-password",
|
||||
DbDatabase: "db-database",
|
||||
DbTLSMode: "db-tls-mode",
|
||||
DbTLSCACert: "db-tls-ca-cert",
|
||||
|
||||
// redis
|
||||
RedisAddress: "redis-address",
|
||||
RedisDB: "redis-db",
|
||||
RedisPassword: "redis-password",
|
||||
|
||||
// runner
|
||||
RunnerConcurrency: "runner-concurrency",
|
||||
@ -73,6 +99,20 @@ var Keys = KeyNames{
|
||||
ServerMinifyHTML: "minify-html",
|
||||
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
|
||||
MetricsStatsDAddress: "statsd-addr",
|
||||
MetricsStatsDPrefix: "statsd-prefix",
|
||||
|
@ -8,24 +8,30 @@ type Values struct {
|
||||
// application
|
||||
ActorKeySize int
|
||||
ApplicationName string
|
||||
ApplicationWebsite string
|
||||
CachedActivityLimit int
|
||||
CachedActorLimit int
|
||||
CachedDigestLimit int
|
||||
EncryptionKey string
|
||||
SoftwareVersion string
|
||||
TokenSalt string
|
||||
|
||||
// database
|
||||
DbType string
|
||||
DbAddress string
|
||||
DbPort int
|
||||
DbUser string
|
||||
DbPassword string
|
||||
DbDatabase string
|
||||
DbTLSMode string
|
||||
DbTLSCACert string
|
||||
DbLoadTestData bool
|
||||
DbEncryptionKey string
|
||||
DbType string
|
||||
DbAddress string
|
||||
DbPort int
|
||||
DbUser string
|
||||
DbPassword string
|
||||
DbDatabase string
|
||||
DbTLSMode string
|
||||
DbTLSCACert string
|
||||
|
||||
// running
|
||||
// redis
|
||||
RedisAddress string
|
||||
RedisDB int
|
||||
RedisPassword string
|
||||
|
||||
// runner
|
||||
RunnerConcurrency int
|
||||
|
||||
// server
|
||||
@ -34,6 +40,20 @@ type Values struct {
|
||||
ServerMinifyHTML bool
|
||||
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
|
||||
MetricsStatsDAddress string
|
||||
MetricsStatsDPrefix string
|
||||
@ -47,20 +67,24 @@ var Defaults = Values{
|
||||
// application
|
||||
ActorKeySize: 2048,
|
||||
ApplicationName: "feditools-relay",
|
||||
ApplicationWebsite: "https://github.com/feditools/relay",
|
||||
CachedActivityLimit: 1024,
|
||||
CachedActorLimit: 1024,
|
||||
CachedDigestLimit: 1024,
|
||||
|
||||
// database
|
||||
DbType: "postgres",
|
||||
DbAddress: "",
|
||||
DbPort: 5432,
|
||||
DbUser: "",
|
||||
DbPassword: "",
|
||||
DbDatabase: "relay",
|
||||
DbTLSMode: "disable",
|
||||
DbTLSCACert: "",
|
||||
DbLoadTestData: false,
|
||||
DbType: "postgres",
|
||||
DbAddress: "",
|
||||
DbPort: 5432,
|
||||
DbUser: "",
|
||||
DbPassword: "",
|
||||
DbDatabase: "relay",
|
||||
DbTLSMode: "disable",
|
||||
DbTLSCACert: "",
|
||||
|
||||
// redis
|
||||
RedisAddress: "localhost:6379",
|
||||
RedisDB: 0,
|
||||
|
||||
// runner
|
||||
RunnerConcurrency: 4,
|
||||
@ -71,8 +95,20 @@ var Defaults = Values{
|
||||
ServerMinifyHTML: true,
|
||||
ServerRoles: []string{
|
||||
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
|
||||
MetricsStatsDAddress: "localhost:8125",
|
||||
MetricsStatsDPrefix: "relay",
|
||||
|
192
internal/db/bun/account.go
Normal file
192
internal/db/bun/account.go
Normal 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)
|
||||
}
|
@ -2,93 +2,186 @@ 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"
|
||||
)
|
||||
|
||||
// CreateBlock stores the domain block
|
||||
func (c *Client) CreateBlock(ctx context.Context, block *models.Block) db.Error {
|
||||
start := time.Now()
|
||||
// CountBlocks returns the number of domain blocks.
|
||||
func (c *Client) CountBlocks(ctx context.Context) (int64, db.Error) {
|
||||
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 {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "CreateBlock", true)
|
||||
go metric.Done(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)
|
||||
}
|
||||
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "CreateBlock", false)
|
||||
go metric.Done(false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadBlockByID returns one domain block
|
||||
func (c *Client) ReadBlockByID(ctx context.Context, id int64) (*models.Block, db.Error) {
|
||||
start := time.Now()
|
||||
// DeleteBlock deletes the stored domain block
|
||||
func (c *Client) DeleteBlock(ctx context.Context, blocks ...*models.Block) db.Error {
|
||||
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)
|
||||
if err == sql.ErrNoRows {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadBlockByID", false)
|
||||
return nil, c.bun.ProcessError(err)
|
||||
return c.bun.errProc(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 {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadBlockByID", true)
|
||||
return nil, c.bun.ProcessError(err)
|
||||
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
|
||||
}
|
||||
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadBlockByID", false)
|
||||
go metric.Done(false)
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// ReadBlockByDomain returns one domain block
|
||||
func (c *Client) ReadBlockByDomain(ctx context.Context, domain string) (*models.Block, db.Error) {
|
||||
start := time.Now()
|
||||
metric := c.metrics.NewDBQuery("ReadBlockByDomain")
|
||||
|
||||
block := &models.Block{}
|
||||
|
||||
err := c.newBlockQ(block).Where("lower(domain) = lower(?)", domain).Scan(ctx)
|
||||
if err == sql.ErrNoRows {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadBlockByDomain", false)
|
||||
return nil, c.bun.ProcessError(err)
|
||||
}
|
||||
block := new(models.Block)
|
||||
err := newBlockQ(c.bun, block).
|
||||
Where("lower(domain) = lower(?)", domain).
|
||||
Order("domain ASC").
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadBlockByDomain", true)
|
||||
return nil, c.bun.ProcessError(err)
|
||||
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
|
||||
}
|
||||
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadBlockByDomain", false)
|
||||
go metric.Done(false)
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// UpdateBlock updates the stored domain block
|
||||
func (c *Client) UpdateBlock(ctx context.Context, block *models.Block) db.Error {
|
||||
start := time.Now()
|
||||
// ReadBlocks returns all domain block
|
||||
func (c *Client) ReadBlocks(ctx context.Context) ([]*models.Block, db.Error) {
|
||||
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 {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "UpdateBlock", true)
|
||||
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 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)
|
||||
}
|
||||
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "UpdateBlock", false)
|
||||
go metric.Done(false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) newBlockQ(block *models.Block) *bun.SelectQuery {
|
||||
return c.bun.
|
||||
func newBlockQ(c bun.IDB, block *models.Block) *bun.SelectQuery {
|
||||
return c.
|
||||
NewSelect().
|
||||
Model(block)
|
||||
}
|
||||
|
||||
func newBlocksQ(c bun.IDB, blocks *[]*models.Block) *bun.SelectQuery {
|
||||
return c.
|
||||
NewSelect().
|
||||
Model(blocks)
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/feditools/go-lib/metrics"
|
||||
"github.com/feditools/relay/internal/config"
|
||||
"github.com/feditools/relay/internal/db"
|
||||
"github.com/feditools/relay/internal/metrics"
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/jackc/pgx/v4/stdlib"
|
||||
"github.com/spf13/viper"
|
||||
@ -48,8 +48,6 @@ type Client struct {
|
||||
metrics metrics.Collector
|
||||
}
|
||||
|
||||
var _ db.DB = (*Client)(nil)
|
||||
|
||||
// New creates a new bun database client
|
||||
func New(ctx context.Context, m metrics.Collector) (*Client, error) {
|
||||
var newBun *Bun
|
||||
|
@ -4,10 +4,9 @@ package bun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/feditools/go-lib/mock"
|
||||
"github.com/feditools/relay/internal/config"
|
||||
"github.com/feditools/relay/internal/db"
|
||||
"github.com/feditools/relay/internal/mock"
|
||||
"github.com/feditools/relay/internal/models/testdata"
|
||||
"github.com/spf13/viper"
|
||||
"testing"
|
||||
)
|
||||
@ -84,7 +83,6 @@ func testNewPostresClient() (db.DB, error) {
|
||||
viper.Set(config.Keys.DbPassword, "test")
|
||||
viper.Set(config.Keys.DbPort, 5432)
|
||||
viper.Set(config.Keys.DbUser, "test")
|
||||
viper.Set(config.Keys.DbEncryptionKey, testdata.TestEncryptionKey)
|
||||
|
||||
metricsCollector, _ := mock.NewMetricsCollector()
|
||||
|
||||
@ -98,10 +96,5 @@ func testNewPostresClient() (db.DB, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = client.LoadTestData(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
@ -5,10 +5,9 @@ import (
|
||||
"crypto/tls"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/feditools/go-lib/mock"
|
||||
"github.com/feditools/relay/internal/config"
|
||||
"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/spf13/viper"
|
||||
"testing"
|
||||
@ -377,7 +376,6 @@ func testNewSqliteClient() (db.DB, error) {
|
||||
|
||||
viper.Set(config.Keys.DbType, "sqlite")
|
||||
viper.Set(config.Keys.DbAddress, ":memory:")
|
||||
viper.Set(config.Keys.DbEncryptionKey, testdata.TestEncryptionKey)
|
||||
|
||||
metricsCollector, _ := mock.NewMetricsCollector()
|
||||
|
||||
@ -391,7 +389,6 @@ func testNewSqliteClient() (db.DB, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = client.LoadTestData(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -2,13 +2,9 @@ package bun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/feditools/relay/internal/db"
|
||||
"github.com/feditools/relay/internal/db/bun/migrations"
|
||||
"github.com/feditools/relay/internal/models"
|
||||
"github.com/feditools/relay/internal/models/testdata"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
@ -19,15 +15,6 @@ func (c *Client) Close(ctx context.Context) db.Error {
|
||||
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
|
||||
func (c *Client) DoMigration(ctx context.Context) db.Error {
|
||||
l := logger.WithField("func", "DoMigration")
|
||||
@ -55,76 +42,31 @@ func (c *Client) DoMigration(ctx context.Context) db.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadTestData adds test data to the database
|
||||
func (c *Client) LoadTestData(ctx context.Context) db.Error {
|
||||
l := logger.WithField("func", "LoadTestData")
|
||||
l.Debugf("adding test data")
|
||||
|
||||
// Truncate
|
||||
modelList := []interface{}{
|
||||
&models.Instance{},
|
||||
func create(ctx context.Context, c bun.IDB, i interface{}) error {
|
||||
_, err := c.NewInsert().Model(i).Exec(ctx)
|
||||
if err != nil {
|
||||
logger.WithField("func", "create").Error(err.Error())
|
||||
}
|
||||
|
||||
for _, m := range modelList {
|
||||
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
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadByID returns a model by its ID
|
||||
func (c *Client) ReadByID(ctx context.Context, id int64, i any) db.Error {
|
||||
q := c.bun.NewSelect().Model(i).Where("id = ?", id)
|
||||
func delete(ctx context.Context, c bun.IDB, i interface{}) error {
|
||||
_, err := c.NewDelete().Model(i).Exec(ctx)
|
||||
if err != nil {
|
||||
logger.WithField("func", "delete").Error(err.Error())
|
||||
}
|
||||
|
||||
err := q.Scan(ctx)
|
||||
return c.bun.ProcessError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update updates stored data
|
||||
func (c *Client) Update(ctx context.Context, i any) db.Error {
|
||||
q := c.bun.NewUpdate().Model(i).WherePK()
|
||||
func update(ctx context.Context, c bun.IDB, i interface{}) error {
|
||||
q := c.NewUpdate().Model(i).WherePK()
|
||||
|
||||
_, err := q.Exec(ctx)
|
||||
return c.bun.ProcessError(err)
|
||||
if err != nil {
|
||||
logger.WithField("func", "update").Error(err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -2,140 +2,248 @@ package bun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
libdatabase "github.com/feditools/go-lib/database"
|
||||
"github.com/feditools/relay/internal/db"
|
||||
"github.com/feditools/relay/internal/models"
|
||||
"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
|
||||
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)
|
||||
}
|
||||
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "CreateInstance", false)
|
||||
go metric.Done(false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadInstanceByID returns one federated social instance
|
||||
func (c *Client) ReadInstanceByID(ctx context.Context, id int64) (*models.Instance, db.Error) {
|
||||
start := time.Now()
|
||||
// ReadInstance returns one federated social instance
|
||||
func (c *Client) ReadInstance(ctx context.Context, id int64) (*models.Instance, db.Error) {
|
||||
metric := c.metrics.NewDBQuery("ReadInstance")
|
||||
|
||||
instance := &models.Instance{}
|
||||
|
||||
err := c.newInstanceQ(instance).Where("id = ?", id).Scan(ctx)
|
||||
if err == sql.ErrNoRows {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadInstanceByID", false)
|
||||
return nil, c.bun.ProcessError(err)
|
||||
}
|
||||
instance := new(models.Instance)
|
||||
err := newInstanceQ(c.bun, instance).
|
||||
Where("id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadInstanceByID", true)
|
||||
return nil, c.bun.ProcessError(err)
|
||||
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
|
||||
}
|
||||
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadInstanceByID", false)
|
||||
go metric.Done(false)
|
||||
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// ReadInstanceByActorIRI returns one federated social instance
|
||||
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)
|
||||
|
||||
err := c.newInstanceQ(instance).Where("actor_iri = ?", actorIRI).Scan(ctx)
|
||||
if err == sql.ErrNoRows {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadInstanceByActorIRI", false)
|
||||
return nil, c.bun.ProcessError(err)
|
||||
}
|
||||
err := newInstanceQ(c.bun, instance).
|
||||
Where("actor_iri = ?", actorIRI).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadInstanceByActorIRI", true)
|
||||
return nil, c.bun.ProcessError(err)
|
||||
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
|
||||
}
|
||||
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadInstanceByActorIRI", false)
|
||||
go metric.Done(false)
|
||||
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// ReadInstanceByDomain returns one federated social instance
|
||||
func (c *Client) ReadInstanceByDomain(ctx context.Context, domain string) (*models.Instance, db.Error) {
|
||||
start := time.Now()
|
||||
metric := c.metrics.NewDBQuery("ReadInstanceByDomain")
|
||||
|
||||
instance := &models.Instance{}
|
||||
|
||||
err := c.newInstanceQ(instance).Where("lower(domain) = lower(?)", domain).Scan(ctx)
|
||||
if err == sql.ErrNoRows {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadInstanceByDomain", false)
|
||||
return nil, c.bun.ProcessError(err)
|
||||
}
|
||||
instance := new(models.Instance)
|
||||
err := newInstanceQ(c.bun, instance).
|
||||
Where("lower(domain) = lower(?)", domain).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadInstanceByDomain", true)
|
||||
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 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)
|
||||
}
|
||||
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadInstanceByDomain", false)
|
||||
return instance, nil
|
||||
go metric.Done(false)
|
||||
|
||||
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
|
||||
func (c *Client) ReadInstancesWhereFollowing(ctx context.Context) ([]*models.Instance, db.Error) {
|
||||
start := time.Now()
|
||||
metric := c.metrics.NewDBQuery("ReadInstancesWhereFollowing")
|
||||
|
||||
var instances []*models.Instance
|
||||
|
||||
err := c.newInstancesQ(&instances).Where("followed = true").Scan(ctx)
|
||||
err := newInstancesQ(c.bun, &instances).
|
||||
Where("followed = true").
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadInstancesWhereFollowing", true)
|
||||
return nil, c.bun.ProcessError(err)
|
||||
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
|
||||
}
|
||||
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "ReadInstancesWhereFollowing", false)
|
||||
go metric.Done(false)
|
||||
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
// UpdateInstance updates the stored federated instance
|
||||
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)
|
||||
}
|
||||
|
||||
ended := time.Since(start)
|
||||
go c.metrics.DBQuery(ended, "UpdateInstance", false)
|
||||
go metric.Done(false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) newInstanceQ(instance *models.Instance) *bun.SelectQuery {
|
||||
return c.bun.
|
||||
func newInstanceQ(c bun.IDB, instance *models.Instance) *bun.SelectQuery {
|
||||
return c.
|
||||
NewSelect().
|
||||
Model(instance)
|
||||
}
|
||||
|
||||
func (c *Client) newInstancesQ(instances *[]*models.Instance) *bun.SelectQuery {
|
||||
return c.bun.
|
||||
func newInstancesQ(c bun.IDB, instances *[]*models.Instance) *bun.SelectQuery {
|
||||
return c.
|
||||
NewSelect().
|
||||
Model(instances)
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ func init() {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
modelList := []interface{}{
|
||||
&models.Instance{},
|
||||
&models.Account{},
|
||||
&models.Block{},
|
||||
}
|
||||
for _, i := range modelList {
|
||||
|
20
internal/db/bun/migrations/20220426162151_init/account.go
Normal file
20
internal/db/bun/migrations/20220426162151_init/account.go
Normal 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"`
|
||||
}
|
@ -4,9 +4,11 @@ import "time"
|
||||
|
||||
// Block represents a block of a domain
|
||||
type Block struct {
|
||||
ID int64 `validate:"-" bun:",pk,autoincrement,nullzero,notnull,unique"`
|
||||
CreatedAt 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"`
|
||||
BlockSubdomains bool `validate:"-" bun:",notnull,default:true"`
|
||||
ID int64 `validate:"-" bun:",pk,autoincrement,nullzero,notnull,unique"`
|
||||
CreatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"`
|
||||
MarkedForDeletionOn time.Time `validate:"-" bun:",nullzero"`
|
||||
Domain string `validate:"required,fqdn" bun:",nullzero,notnull,unique"`
|
||||
ObfuscatedDomain string `validate:"-" bun:",nullzero"`
|
||||
BlockSubdomains bool `validate:"-" bun:",notnull"`
|
||||
}
|
||||
|
@ -7,16 +7,17 @@ import (
|
||||
|
||||
// Instance represents a federated social instance
|
||||
type Instance struct {
|
||||
ID int64 `validate:"-" bun:",pk,autoincrement,nullzero,notnull,unique"`
|
||||
CreatedAt 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"`
|
||||
PublicKey *rsa.PublicKey `validate:"-"`
|
||||
PrivateKey *rsa.PrivateKey `validate:"-"`
|
||||
ActorIRI string `validate:"required,url" bun:",nullzero,notnull,unique"`
|
||||
InboxIRI string `validate:"required,url" bun:",nullzero,notnull,unique"`
|
||||
Followed bool `validate:"-" bun:",notnull,default:false"`
|
||||
BlockID int64 `validate:"-" bun:",nullzero"`
|
||||
Block *Block `validate:"-" bun:"rel:belongs-to"`
|
||||
ID int64 `validate:"-" bun:",pk,autoincrement,nullzero,notnull,unique"`
|
||||
CreatedAt 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"`
|
||||
ServerHostname string `validate:"required,fqdn" bun:",nullzero,notnull,unique"`
|
||||
Software string `validate:"-" bun:",nullzero"`
|
||||
PublicKey *rsa.PublicKey `validate:"-"`
|
||||
PrivateKey *rsa.PrivateKey `validate:"-"`
|
||||
ActorIRI string `validate:"required,url" bun:",nullzero,notnull,unique"`
|
||||
InboxIRI string `validate:"required,url" bun:",nullzero,notnull,unique"`
|
||||
Followed bool `validate:"-" bun:",notnull,default:false"`
|
||||
BlockID int64 `validate:"-" bun:",nullzero"`
|
||||
Block *Block `validate:"-" bun:"rel:belongs-to"`
|
||||
}
|
||||
|
@ -9,38 +9,65 @@ import (
|
||||
type DB interface {
|
||||
// Close closes the db connections
|
||||
Close(ctx context.Context) Error
|
||||
// Create stores the object
|
||||
Create(ctx context.Context, i any) Error
|
||||
// DoMigration runs database migrations
|
||||
DoMigration(ctx context.Context) Error
|
||||
// LoadTestData adds test data to the database
|
||||
LoadTestData(ctx context.Context) Error
|
||||
// ReadByID returns a model by its ID
|
||||
ReadByID(ctx context.Context, id int64, i any) Error
|
||||
// Update updates stored data
|
||||
Update(ctx context.Context, i any) Error
|
||||
|
||||
// Accounts
|
||||
|
||||
// CountAccounts returns the number of federated social account
|
||||
CountAccounts(ctx context.Context) (count int64, err 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
|
||||
|
||||
// CountBlocks returns the number of domain blocks
|
||||
CountBlocks(ctx context.Context) (count int64, err Error)
|
||||
// CreateBlock stores the domain block
|
||||
CreateBlock(ctx context.Context, block *models.Block) (err Error)
|
||||
// ReadBlockByID returns one domain block
|
||||
ReadBlockByID(ctx context.Context, id int64) (block *models.Block, err Error)
|
||||
CreateBlock(ctx context.Context, blocks ...*models.Block) (err Error)
|
||||
// DeleteBlock deletes a domain block
|
||||
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(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(ctx context.Context, block *models.Block) (err Error)
|
||||
UpdateBlock(ctx context.Context, blocks ...*models.Block) (err Error)
|
||||
|
||||
// Instance
|
||||
|
||||
// CountInstances returns the number of federated instance
|
||||
CountInstances(ctx context.Context) (count int64, err Error)
|
||||
// CreateInstance stores the federated instance
|
||||
CreateInstance(ctx context.Context, instance *models.Instance) (err Error)
|
||||
// ReadInstanceByID returns one federated social instance
|
||||
ReadInstanceByID(ctx context.Context, id int64) (instance *models.Instance, err Error)
|
||||
// ReadInstance returns one federated social instance
|
||||
ReadInstance(ctx context.Context, id int64) (instance *models.Instance, err Error)
|
||||
// ReadInstanceByActorIRI returns one federated social instance
|
||||
ReadInstanceByActorIRI(ctx context.Context, actorIRI string) (instance *models.Instance, err Error)
|
||||
// ReadInstanceByDomain returns one federated social instance
|
||||
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(ctx context.Context) (instances []*models.Instance, err Error)
|
||||
// UpdateInstance updates the stored federated instance
|
||||
|
5
internal/fedi/errors.go
Normal file
5
internal/fedi/errors.go
Normal 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
107
internal/fedi/fedi.go
Normal 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
40
internal/fedi/handler.go
Normal 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
|
||||
}
|
@ -3,6 +3,7 @@ package activitypub
|
||||
import (
|
||||
"context"
|
||||
"github.com/feditools/relay/internal/config"
|
||||
ihttp "github.com/feditools/relay/internal/http"
|
||||
"github.com/feditools/relay/internal/logic"
|
||||
"github.com/feditools/relay/internal/runner"
|
||||
"github.com/spf13/viper"
|
||||
@ -10,7 +11,7 @@ import (
|
||||
|
||||
// Module is an http module that handles activity pub activity
|
||||
type Module struct {
|
||||
logic *logic.Logic
|
||||
logic logic.Logic
|
||||
runner runner.Runner
|
||||
|
||||
appName string
|
||||
@ -19,7 +20,7 @@ type Module struct {
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
||||
module := &Module{
|
||||
@ -53,3 +54,6 @@ func New(ctx context.Context, l *logic.Logic, r runner.Runner) (*Module, error)
|
||||
func (m *Module) Name() string {
|
||||
return config.ServerRoleActivityPub
|
||||
}
|
||||
|
||||
// SetServer adds a reference to the server to the module.
|
||||
func (*Module) SetServer(_ *ihttp.Server) {}
|
||||
|
@ -2,9 +2,9 @@ package activitypub
|
||||
|
||||
import (
|
||||
"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/tyrm/go-util/mimetype"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -13,17 +13,17 @@ func (m *Module) actorGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
actor := m.genRelayActor()
|
||||
|
||||
w.Header().Set("Content-Type", mimetype.ApplicationJSON)
|
||||
w.Header().Set("Content-Type", libhttp.MimeAppJSON.String())
|
||||
err := json.NewEncoder(w).Encode(actor)
|
||||
if err != nil {
|
||||
l.Errorf("marshaling json: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) genRelayActor() *models.Actor {
|
||||
return &models.Actor{
|
||||
func (m *Module) genRelayActor() *fedihelper.Actor {
|
||||
return &fedihelper.Actor{
|
||||
Context: ContextActivityStreams,
|
||||
Endpoints: models.Endpoints{
|
||||
Endpoints: fedihelper.Endpoints{
|
||||
SharedInbox: path.GenInbox(m.logic.Domain()),
|
||||
},
|
||||
Followers: path.GenFollowers(m.logic.Domain()),
|
||||
@ -32,7 +32,7 @@ func (m *Module) genRelayActor() *models.Actor {
|
||||
Name: m.appName,
|
||||
Type: "Application",
|
||||
ID: path.GenActor(m.logic.Domain()),
|
||||
PublicKey: models.PublicKey{
|
||||
PublicKey: fedihelper.PublicKey{
|
||||
ID: path.GenPublicKey(m.logic.Domain()),
|
||||
Owner: path.GenActor(m.logic.Domain()),
|
||||
PublicKeyPEM: m.publicKeyPem,
|
||||
|
11
internal/http/activitypub/context_key.go
Normal file
11
internal/http/activitypub/context_key.go
Normal 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
|
||||
)
|
@ -3,6 +3,7 @@ package activitypub
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/feditools/go-lib/fedihelper"
|
||||
"github.com/feditools/relay/internal/db"
|
||||
"github.com/feditools/relay/internal/models"
|
||||
nethttp "net/http"
|
||||
@ -13,7 +14,7 @@ func (m *Module) inboxPostHandler(w nethttp.ResponseWriter, r *nethttp.Request)
|
||||
l := logger.WithField("func", "inboxPostHandler")
|
||||
|
||||
// parse activity
|
||||
var activity models.Activity
|
||||
var activity fedihelper.Activity
|
||||
err := json.NewDecoder(r.Body).Decode(&activity)
|
||||
if err != nil {
|
||||
l.Errorf("decoding activity: %+v", err)
|
||||
@ -35,6 +36,7 @@ func (m *Module) inboxPostHandler(w nethttp.ResponseWriter, r *nethttp.Request)
|
||||
if !ok {
|
||||
l.Debugf("activity actor isn't string: %+v", activity)
|
||||
nethttp.Error(w, nethttp.StatusText(nethttp.StatusBadRequest), nethttp.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
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)
|
||||
validated, actor := m.logic.ValidateRequest(r, actorIRI)
|
||||
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)
|
||||
|
||||
return
|
||||
@ -57,7 +59,7 @@ func (m *Module) inboxPostHandler(w nethttp.ResponseWriter, r *nethttp.Request)
|
||||
|
||||
var instance *models.Instance
|
||||
switch actor.Type {
|
||||
case models.TypeApplication:
|
||||
case fedihelper.TypeApplication:
|
||||
instance, err = m.logic.GetInstanceForActor(r.Context(), actorIRI)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNoEntries) {
|
||||
@ -70,7 +72,7 @@ func (m *Module) inboxPostHandler(w nethttp.ResponseWriter, r *nethttp.Request)
|
||||
|
||||
return
|
||||
}
|
||||
case models.TypePerson:
|
||||
case fedihelper.TypePerson:
|
||||
instance, err = m.logic.GetInstance(r.Context(), actorIRI.Host)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNoEntries) {
|
||||
@ -91,23 +93,16 @@ func (m *Module) inboxPostHandler(w nethttp.ResponseWriter, r *nethttp.Request)
|
||||
}
|
||||
|
||||
// get activity type
|
||||
activityTypeI, ok := activity["type"]
|
||||
if !ok {
|
||||
l.Debugf("activity missing type: %+v", activity)
|
||||
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)
|
||||
activityType, err := activity.Type()
|
||||
if err != nil {
|
||||
l.Debugf("can't get type: %s", err.Error())
|
||||
nethttp.Error(w, nethttp.StatusText(nethttp.StatusBadRequest), nethttp.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
nethttp.Error(w, nethttp.StatusText(nethttp.StatusUnauthorized), nethttp.StatusUnauthorized)
|
||||
|
||||
|
@ -2,9 +2,8 @@ package activitypub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/feditools/relay/internal/http"
|
||||
libhttp "github.com/feditools/go-lib/http"
|
||||
"github.com/go-fed/httpsig"
|
||||
"github.com/tyrm/go-util/mimetype"
|
||||
nethttp "net/http"
|
||||
"net/url"
|
||||
)
|
||||
@ -39,19 +38,19 @@ func (m *Module) middlewareCheckHTTPSig(next nethttp.Handler) nethttp.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), http.ContextKeyKeyVerifier, verifier)
|
||||
ctx := context.WithValue(r.Context(), ContextKeyKeyVerifier, verifier)
|
||||
|
||||
// check for domain block
|
||||
isBlocked, err := m.logic.IsDomainBlocked(ctx, KeyIDURI.Host)
|
||||
if err != nil {
|
||||
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)
|
||||
return
|
||||
}
|
||||
if isBlocked {
|
||||
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)
|
||||
return
|
||||
}
|
||||
@ -59,7 +58,7 @@ func (m *Module) middlewareCheckHTTPSig(next nethttp.Handler) nethttp.Handler {
|
||||
// get signature
|
||||
signature := r.Header.Get("signature")
|
||||
if signature != "" {
|
||||
ctx = context.WithValue(ctx, http.ContextKeyHTTPSignature, signature)
|
||||
ctx = context.WithValue(ctx, ContextKeyHTTPSignature, signature)
|
||||
}
|
||||
|
||||
// do request with verifier
|
||||
|
@ -2,9 +2,9 @@ package activitypub
|
||||
|
||||
import (
|
||||
"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/tyrm/go-util/mimetype"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -14,35 +14,35 @@ func (m *Module) nodeinfo20GetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
peers, err := m.logic.GetPeers(r.Context())
|
||||
if err != nil {
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
nodeinfo := models.NodeInfo{
|
||||
nodeinfo := fedihelper.NodeInfoV2{
|
||||
Metadata: map[string]interface{}{
|
||||
"peers": peers,
|
||||
},
|
||||
OpenRegistrations: true,
|
||||
Protocols: []string{"activitypub"},
|
||||
Services: models.Services{
|
||||
Services: fedihelper.Services{
|
||||
Inbound: []string{},
|
||||
Outbound: []string{},
|
||||
},
|
||||
Software: models.Software{
|
||||
Software: fedihelper.Software{
|
||||
Name: m.appName,
|
||||
Version: m.appVersion,
|
||||
},
|
||||
Usage: models.Usage{
|
||||
Usage: fedihelper.Usage{
|
||||
LocalPosts: 0,
|
||||
Users: models.UsageUsers{
|
||||
Users: fedihelper.UsageUsers{
|
||||
Total: 1,
|
||||
},
|
||||
},
|
||||
Version: "2.0",
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", mimetype.ApplicationJSON)
|
||||
w.Header().Set("Content-Type", libhttp.MimeAppJSON.String())
|
||||
err = json.NewEncoder(w).Encode(nodeinfo)
|
||||
if err != nil {
|
||||
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) {
|
||||
l := logger.WithField("func", "wellknownNodeInfoGetHandler")
|
||||
|
||||
wellknown := models.NodeInfoWellKnown{
|
||||
Links: []models.Link{
|
||||
wellknown := fedihelper.WellKnownNodeInfo{
|
||||
Links: []fedihelper.Link{
|
||||
{
|
||||
Rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
|
||||
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)
|
||||
if err != nil {
|
||||
l.Errorf("marshaling json: %s", err.Error())
|
||||
|
@ -7,12 +7,10 @@ import (
|
||||
|
||||
// Route attaches routes to the web server
|
||||
func (m *Module) Route(s *http.Server) error {
|
||||
ap := s.PathPrefix("/").Subrouter()
|
||||
ap.Use(m.middlewareCheckHTTPSig)
|
||||
ap.HandleFunc(path.APActor, m.actorGetHandler).Methods("GET")
|
||||
ap.HandleFunc(path.APInbox, m.inboxPostHandler).Methods("POST")
|
||||
ap.HandleFunc(path.APNodeInfo20, m.nodeinfo20GetHandler).Methods("GET")
|
||||
ap.HandleFunc(path.APWellKnownNodeInfo, m.wellknownNodeInfoGetHandler).Methods("GET")
|
||||
ap.HandleFunc(path.APWellKnownWebFinger, m.wellknownWebFingerGetHandler).Methods("GET")
|
||||
s.HandleFunc(path.APActor, m.actorGetHandler).Methods("GET")
|
||||
s.HandleFunc(path.APInbox, m.inboxPostHandler).Methods("POST")
|
||||
s.HandleFunc(path.APNodeInfo20, m.nodeinfo20GetHandler).Methods("GET")
|
||||
s.HandleFunc(path.APWellKnownNodeInfo, m.wellknownNodeInfoGetHandler).Methods("GET")
|
||||
s.HandleFunc(path.APWellKnownWebFinger, m.wellknownWebFingerGetHandler).Methods("GET")
|
||||
return nil
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ package activitypub
|
||||
import (
|
||||
"encoding/json"
|
||||
"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/tyrm/go-util/mimetype"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -14,29 +14,29 @@ func (m *Module) wellknownWebFingerGetHandler(w http.ResponseWriter, r *http.Req
|
||||
|
||||
subject := r.URL.Query().Get("resource")
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
webfinger := models.WebFinger{
|
||||
webfinger := fedihelper.WebFinger{
|
||||
Aliases: []string{path.GenActor(m.logic.Domain())},
|
||||
Links: []models.Link{
|
||||
Links: []fedihelper.Link{
|
||||
{
|
||||
Href: path.GenActor(m.logic.Domain()),
|
||||
Rel: "self",
|
||||
Type: mimetype.ApplicationActivityJSON,
|
||||
Type: libhttp.MimeAppActivityJSON.String(),
|
||||
},
|
||||
{
|
||||
Href: path.GenActor(m.logic.Domain()),
|
||||
Rel: "self",
|
||||
Type: mimetype.ApplicationLDJSONActivityStreams,
|
||||
Type: libhttp.MimeAppActivityLDJSON.String(),
|
||||
},
|
||||
},
|
||||
Subject: subject,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", mimetype.ApplicationJSON)
|
||||
w.Header().Set("Content-Type", libhttp.MimeAppJSON.String())
|
||||
err := json.NewEncoder(w).Encode(webfinger)
|
||||
if err != nil {
|
||||
l.Errorf("marshaling json: %s", err.Error())
|
||||
|
71
internal/http/client.go
Normal file
71
internal/http/client.go
Normal 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)
|
||||
}
|
@ -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
|
||||
)
|
@ -1,22 +1,50 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
libhttp "github.com/feditools/go-lib/http"
|
||||
"github.com/go-http-utils/etag"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/sirupsen/logrus"
|
||||
"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) {
|
||||
start := time.Now()
|
||||
l := logger.WithField("func", "middlewareMetrics")
|
||||
metric := s.metrics.NewHTTPRequest(r.Method, r.URL.Path)
|
||||
|
||||
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)
|
||||
|
||||
// Do Request
|
||||
next.ServeHTTP(wx, r)
|
||||
|
||||
ended := time.Since(start)
|
||||
l.Debugf("rendering %s took %d ms", r.URL.Path, ended.Milliseconds())
|
||||
go s.metrics.HTTPRequestTiming(ended, wx.Status(), r.Method, r.URL.Path)
|
||||
go func() {
|
||||
ended := metric.Done(wx.Status())
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -4,4 +4,5 @@ package http
|
||||
type Module interface {
|
||||
Name() string
|
||||
Route(s *Server) error
|
||||
SetServer(s *Server)
|
||||
}
|
||||
|
@ -3,13 +3,11 @@ package http
|
||||
import (
|
||||
"context"
|
||||
"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/metrics"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tyrm/go-util/middleware"
|
||||
"github.com/tyrm/go-util/mimetype"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
@ -39,9 +37,7 @@ func NewServer(_ context.Context, m metrics.Collector) (*Server, error) {
|
||||
}
|
||||
|
||||
// add global middlewares
|
||||
r.Use(server.middlewareMetrics)
|
||||
r.Use(handlers.CompressHandler)
|
||||
r.Use(middleware.BlockFlocMux)
|
||||
r.Use(server.WrapInMiddlewares)
|
||||
|
||||
r.NotFoundHandler = server.notFoundHandler()
|
||||
r.MethodNotAllowedHandler = server.methodNotAllowedHandler()
|
||||
@ -73,16 +69,16 @@ func (s *Server) Stop(ctx context.Context) error {
|
||||
|
||||
func (s *Server) methodNotAllowedHandler() http.Handler {
|
||||
// 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) {
|
||||
w.Header().Set("Content-Type", mimetype.TextPlain)
|
||||
return s.WrapInMiddlewares(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", libhttp.MimeTextPlain.String())
|
||||
w.Write([]byte(fmt.Sprintf("%d %s", http.StatusMethodNotAllowed, http.StatusText(http.StatusMethodNotAllowed))))
|
||||
})))
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *Server) notFoundHandler() http.Handler {
|
||||
// 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) {
|
||||
w.Header().Set("Content-Type", mimetype.TextPlain)
|
||||
return s.WrapInMiddlewares(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", libhttp.MimeTextPlain.String())
|
||||
w.Write([]byte(fmt.Sprintf("%d %s", http.StatusNotFound, http.StatusText(http.StatusNotFound))))
|
||||
})))
|
||||
}))
|
||||
}
|
||||
|
42
internal/http/static/static.go
Normal file
42
internal/http/static/static.go
Normal 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
|
||||
}
|
39
internal/http/template/admin_block.go
Normal file
39
internal/http/template/admin_block.go
Normal 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
|
||||
}
|
9
internal/http/template/admin_home.go
Normal file
9
internal/http/template/admin_home.go
Normal 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
|
||||
}
|
17
internal/http/template/admin_instance.go
Normal file
17
internal/http/template/admin_instance.go
Normal 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
|
||||
}
|
80
internal/http/template/common.go
Normal file
80
internal/http/template/common.go
Normal 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
|
||||
}
|
16
internal/http/template/error.go
Normal file
16
internal/http/template/error.go
Normal 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
|
||||
}
|
17
internal/http/template/form.go
Normal file
17
internal/http/template/form.go
Normal 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
|
||||
}
|
16
internal/http/template/home.go
Normal file
16
internal/http/template/home.go
Normal 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
|
||||
}
|
12
internal/http/template/login.go
Normal file
12
internal/http/template/login.go
Normal 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
|
||||
}
|
52
internal/http/template/navbar.go
Normal file
52
internal/http/template/navbar.go
Normal 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
|
||||
}
|
77
internal/http/template/template.go
Normal file
77
internal/http/template/template.go
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
33
internal/http/webapp/admin.go
Normal file
33
internal/http/webapp/admin.go
Normal 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
|
||||
}
|
275
internal/http/webapp/admin_block.go
Normal file
275
internal/http/webapp/admin_block.go
Normal 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())
|
||||
}
|
||||
}
|
121
internal/http/webapp/admin_block_add.go
Normal file
121
internal/http/webapp/admin_block_add.go
Normal 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.",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
63
internal/http/webapp/admin_block_delete.go
Normal file
63
internal/http/webapp/admin_block_delete.go
Normal 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.",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
105
internal/http/webapp/admin_block_edit.go
Normal file
105
internal/http/webapp/admin_block_edit.go
Normal 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.",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
78
internal/http/webapp/admin_block_export.go
Normal file
78
internal/http/webapp/admin_block_export.go
Normal 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())
|
||||
}
|
||||
}
|
396
internal/http/webapp/admin_block_import.go
Normal file
396
internal/http/webapp/admin_block_import.go
Normal 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)),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
33
internal/http/webapp/admin_home.go
Normal file
33
internal/http/webapp/admin_home.go
Normal 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())
|
||||
}
|
||||
}
|
79
internal/http/webapp/admin_instance.go
Normal file
79
internal/http/webapp/admin_instance.go
Normal 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())
|
||||
}
|
||||
}
|
144
internal/http/webapp/callback.go
Normal file
144
internal/http/webapp/callback.go
Normal 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
|
||||
}
|
||||
}
|
41
internal/http/webapp/const.go
Normal file
41
internal/http/webapp/const.go
Normal 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"
|
||||
)
|
17
internal/http/webapp/context_key.go
Normal file
17
internal/http/webapp/context_key.go
Normal 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
|
||||
)
|
82
internal/http/webapp/error.go
Normal file
82
internal/http/webapp/error.go
Normal 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,
|
||||
),
|
||||
)
|
||||
}
|
60
internal/http/webapp/home.go
Normal file
60
internal/http/webapp/home.go
Normal 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)
|
||||
}
|
83
internal/http/webapp/js.go
Normal file
83
internal/http/webapp/js.go
Normal 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
|
||||
}
|
9
internal/http/webapp/logger.go
Normal file
9
internal/http/webapp/logger.go
Normal file
@ -0,0 +1,9 @@
|
||||
package webapp
|
||||
|
||||
import (
|
||||
"github.com/feditools/relay/internal/log"
|
||||
)
|
||||
|
||||
type empty struct{}
|
||||
|
||||
var logger = log.WithPackageField(empty{})
|
119
internal/http/webapp/login.go
Normal file
119
internal/http/webapp/login.go
Normal 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())
|
||||
}
|
||||
}
|
25
internal/http/webapp/logout.go
Normal file
25
internal/http/webapp/logout.go
Normal 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)
|
||||
}
|
90
internal/http/webapp/middleware.go
Normal file
90
internal/http/webapp/middleware.go
Normal 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)
|
||||
})
|
||||
}
|
10
internal/http/webapp/public.go
Normal file
10
internal/http/webapp/public.go
Normal 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{}
|
||||
}
|
1
internal/http/webapp/redirect.go
Normal file
1
internal/http/webapp/redirect.go
Normal file
@ -0,0 +1 @@
|
||||
package webapp
|
39
internal/http/webapp/route.go
Normal file
39
internal/http/webapp/route.go
Normal 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
|
||||
}
|
11
internal/http/webapp/session_key.go
Normal file
11
internal/http/webapp/session_key.go
Normal 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
|
||||
)
|
101
internal/http/webapp/template.go
Normal file
101
internal/http/webapp/template.go
Normal 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
|
||||
}
|
98
internal/http/webapp/util.go
Normal file
98
internal/http/webapp/util.go
Normal 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()
|
||||
}
|
9
internal/http/webapp/validator.go
Normal file
9
internal/http/webapp/validator.go
Normal file
@ -0,0 +1,9 @@
|
||||
package webapp
|
||||
|
||||
import "github.com/go-playground/validator/v10"
|
||||
|
||||
var validate *validator.Validate
|
||||
|
||||
func init() {
|
||||
validate = validator.New()
|
||||
}
|
176
internal/http/webapp/webapp.go
Normal file
176
internal/http/webapp/webapp.go
Normal 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
26
internal/kv/error.go
Normal 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
38
internal/kv/keys.go
Normal 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
10
internal/kv/kv.go
Normal 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
|
||||
}
|
44
internal/kv/redis/access_token.go
Normal file
44
internal/kv/redis/access_token.go
Normal 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
|
||||
}
|
37
internal/kv/redis/actor.go
Normal file
37
internal/kv/redis/actor.go
Normal 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
|
||||
}
|
46
internal/kv/redis/client.go
Normal file
46
internal/kv/redis/client.go
Normal 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
|
||||
}
|
19
internal/kv/redis/error.go
Normal file
19
internal/kv/redis/error.go
Normal 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
39
internal/kv/redis/fedi.go
Normal 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
|
||||
}
|
37
internal/kv/redis/host_meta.go
Normal file
37
internal/kv/redis/host_meta.go
Normal 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
|
||||
}
|
66
internal/kv/redis/instance_oauth.go
Normal file
66
internal/kv/redis/instance_oauth.go
Normal 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
|
||||
}
|
7
internal/kv/redis/logger.go
Normal file
7
internal/kv/redis/logger.go
Normal 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
Loading…
Reference in New Issue
Block a user