From d80ae7c739c0fd6e87c2baaf062b513915f337c1 Mon Sep 17 00:00:00 2001 From: Everest Date: Mon, 15 Jan 2024 02:08:37 -0300 Subject: [PATCH 1/9] create basic backend --- .gitignore | 305 +++++++++++ Makefile | 24 + cmd/migrator.go | 59 ++ cmd/root.go | 38 ++ docker-compose.yml | 9 + env.example | 5 + go.mod | 48 ++ go.sum | 504 ++++++++++++++++++ internal/attendee/fx.go | 7 + internal/attendee/model.go | 20 + internal/event/fx.go | 7 + internal/event/model.go | 21 + internal/event/service.go | 8 + internal/user/fx.go | 24 + internal/user/handler.go | 36 ++ internal/user/handler_oauth.go | 93 ++++ internal/user/models.go | 32 ++ internal/user/models_enum.go | 279 ++++++++++ internal/user/oauth_providers.go | 27 + internal/user/router.go | 15 + internal/user/service.go | 93 ++++ lib/config/config.go | 10 + lib/config/database.go | 10 + lib/config/initializer.go | 28 + lib/config/providers.go | 10 + lib/database/database.go | 50 ++ lib/logger/log.go | 40 ++ lib/server/http.go | 26 + main.go | 7 + .../20240113234311_create_user_table.go | 68 +++ carousel.js => public/carousel.js | 0 eventos.json => public/eventos.json | 0 {img => public/img}/conferencia.jpg | Bin {img => public/img}/forma1.png | Bin {img => public/img}/github-mark.png | Bin {img => public/img}/gopherconbr/logo.png | Bin {img => public/img}/lead-img.jpg | Bin {img => public/img}/meetups.jpg | Bin {img => public/img}/menu-mobile.png | Bin {img => public/img}/palestras.jpg | Bin {img => public/img}/slide-default.png | Bin {img => public/img}/slide-first.png | Bin {img => public/img}/slide-second.png | Bin {img => public/img}/slide-third.png | Bin {img => public/img}/workshops.jpg | Bin index.html => public/index.html | 0 map-rendering.js => public/map-rendering.js | 0 style.css => public/style.css | 0 48 files changed, 1903 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 cmd/migrator.go create mode 100644 cmd/root.go create mode 100644 docker-compose.yml create mode 100644 env.example create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/attendee/fx.go create mode 100644 internal/attendee/model.go create mode 100644 internal/event/fx.go create mode 100644 internal/event/model.go create mode 100644 internal/event/service.go create mode 100644 internal/user/fx.go create mode 100644 internal/user/handler.go create mode 100644 internal/user/handler_oauth.go create mode 100644 internal/user/models.go create mode 100644 internal/user/models_enum.go create mode 100644 internal/user/oauth_providers.go create mode 100644 internal/user/router.go create mode 100644 internal/user/service.go create mode 100644 lib/config/config.go create mode 100644 lib/config/database.go create mode 100644 lib/config/initializer.go create mode 100644 lib/config/providers.go create mode 100644 lib/database/database.go create mode 100644 lib/logger/log.go create mode 100644 lib/server/http.go create mode 100644 main.go create mode 100644 migrations/20240113234311_create_user_table.go rename carousel.js => public/carousel.js (100%) rename eventos.json => public/eventos.json (100%) rename {img => public/img}/conferencia.jpg (100%) rename {img => public/img}/forma1.png (100%) rename {img => public/img}/github-mark.png (100%) rename {img => public/img}/gopherconbr/logo.png (100%) rename {img => public/img}/lead-img.jpg (100%) rename {img => public/img}/meetups.jpg (100%) rename {img => public/img}/menu-mobile.png (100%) rename {img => public/img}/palestras.jpg (100%) rename {img => public/img}/slide-default.png (100%) rename {img => public/img}/slide-first.png (100%) rename {img => public/img}/slide-second.png (100%) rename {img => public/img}/slide-third.png (100%) rename {img => public/img}/workshops.jpg (100%) rename index.html => public/index.html (100%) rename map-rendering.js => public/map-rendering.js (100%) rename style.css => public/style.css (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71b5288 --- /dev/null +++ b/.gitignore @@ -0,0 +1,305 @@ +### Project basic ignorable files ### +\.env.* +bin/ + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data + + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### GoLand ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### GoLand Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +#Go Nvim +.gonvim/ + +# Temporary +.netrwhist +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..36c29b6 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +osArch:=$(shell uname -a) +envfile:=.env.local +ifeq ($(shell test ! -f .env.loical && echo -n yes),yes) + envfile=env.example +endif + +include $(envfile) +export $(shell sed 's/=.*//' $(envfile)) +export BRANCH=$(shell git branch --show-current | cut -d '/' -f2) +version:=BRANCH + +install-deps: + @go install github.com/pressly/goose/v3/cmd/goose@latest + @curl -fsSL "https://github.com/abice/go-enum/releases/download/v0.6.0/go-enum_$(uname -s)_$(uname -m)" -o $(shell go env GOPATH)/bin/go-enum + +run: + @go run main.go + +migrate-status: + @go run main.go migrator status + +migrate-up: + @go run main.go migrator status + @go run main.go migrator up diff --git a/cmd/migrator.go b/cmd/migrator.go new file mode 100644 index 0000000..699c920 --- /dev/null +++ b/cmd/migrator.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "context" + "database/sql" + "log" + "log/slog" + + _ "github.com/lib/pq" + "github.com/pressly/goose/v3" + "github.com/spf13/cobra" + + "github.com/marcopollivier/techagenda/lib/config" + "github.com/marcopollivier/techagenda/lib/database" + _ "github.com/marcopollivier/techagenda/lib/logger" + _ "github.com/marcopollivier/techagenda/migrations" +) + +func init() { + rootCmd.AddCommand(migratorCmd) + migratorCmd.Flags().StringVarP(&dir, "dir", "d", "./migrations", "directory with migration files") +} + +var ( + dir string + migratorCmd = &cobra.Command{ + Use: "migrator", + Short: "migrator uses the goose migrator application under the hood", + RunE: func(cmd *cobra.Command, args []string) (err error) { + var ( + cfg = config.Get() + dsn = database.BuildDSN(cfg) + db *sql.DB + arguments = []string{} + command = args[0] + ) + slog.Info("Running migrator command!", "cmd", cmd.Short, "args", args) + if db, err = goose.OpenDBWithDriver("postgres", dsn); err != nil { + slog.Error("goose: failed to open DB", "error", err.Error()) + return err + } + + defer func() { + if err = db.Close(); err != nil { + log.Fatalf("goose: failed to close DB: %v\n", err) + } + }() + + if len(args) > 3 { + arguments = append(arguments, args[3:]...) + } + + if err = goose.RunContext(context.Background(), command, db, dir, arguments...); err != nil { + log.Fatalf("goose %v: %v", command, err) + } + return + }, + } +) diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..e016532 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "fmt" + "log/slog" + "os" + + "github.com/spf13/cobra" + "go.uber.org/fx" + + "github.com/marcopollivier/techagenda/internal/user" + "github.com/marcopollivier/techagenda/lib/database" + _ "github.com/marcopollivier/techagenda/lib/logger" + "github.com/marcopollivier/techagenda/lib/server" +) + +var rootCmd = &cobra.Command{ + Use: "run", + Short: "Run is the command that run tech agenda service", + Run: func(_ *cobra.Command, _ []string) { + slog.Info("Starting tech agenda service!") + + fx.New( + fx.Provide(database.NewDB), + fx.Provide(server.NewHTTPServer), + user.Module(), + // event.Module(), + // attendee.Module(), + ).Run() + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2b0f4c0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3.8' + +services: + postgres: + image: postgres + environment: + - POSTGRES_HOST_AUTH_METHOD=trust + ports: + - "5432:5432" diff --git a/env.example b/env.example new file mode 100644 index 0000000..c2cea32 --- /dev/null +++ b/env.example @@ -0,0 +1,5 @@ +GOOSE_DRIVER=postgres +GOOSE_DBSTRING="user=postgres dbname=postgres sslmode=disable" + +ENVIRONMENT=development +DB_HOST=localhost diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c9672a3 --- /dev/null +++ b/go.mod @@ -0,0 +1,48 @@ +module github.com/marcopollivier/techagenda + +go 1.21.5 + +require ( + github.com/caarlos0/env/v10 v10.0.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/mux v1.6.2 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/gorilla/sessions v1.1.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect + github.com/jackc/pgx/v5 v5.5.2 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/labstack/echo/v4 v4.11.4 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/markbates/goth v1.78.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pressly/goose/v3 v3.17.0 // indirect + github.com/samber/lo v1.39.0 // indirect + github.com/sethvargo/go-retry v0.2.4 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.1 // indirect + go.uber.org/fx v1.20.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gorm.io/driver/postgres v1.5.4 // indirect + gorm.io/gorm v1.25.5 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6bd2d9a --- /dev/null +++ b/go.sum @@ -0,0 +1,504 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.67.0/go.mod h1:YNan/mUhNZFrYUor0vqrsQ0Ffl7Xtm/ACOy/vsTS858= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +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/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= +github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +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/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/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= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY= +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.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE= +github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.2 h1:iLlpgp4Cp/gC9Xuscl7lFL1PhhW+ZLtXZcrfCt4C3tA= +github.com/jackc/pgx/v5 v5.5.2/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= +github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.2.21/go.mod h1:9cfxnOH7G1gN75CaJP2hKGcxFEx5sPh1abRIA/ZJVh4= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= +github.com/markbates/goth v1.78.0 h1:7VEIFDycJp9deyVv3YraGBPdD0ZYQW93Y3Aw1eVP3BY= +github.com/markbates/goth v1.78.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose/v3 v3.17.0 h1:fT4CL3LRm4kfyLuPWzDFAoxjR5ZHjeJ6uQhibQtBaIs= +github.com/pressly/goose/v3 v3.17.0/go.mod h1:22aw7NpnCPlS86oqkO/+3+o9FuCaJg4ZVWRUO3oGzHQ= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= +github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +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.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc= +go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= +go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= +go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +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= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +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-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= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/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-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +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= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/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= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +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= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= +gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +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= +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= diff --git a/internal/attendee/fx.go b/internal/attendee/fx.go new file mode 100644 index 0000000..9dcda88 --- /dev/null +++ b/internal/attendee/fx.go @@ -0,0 +1,7 @@ +package attendee + +import "go.uber.org/fx" + +func Module() fx.Option { + return fx.Module("attendee") +} diff --git a/internal/attendee/model.go b/internal/attendee/model.go new file mode 100644 index 0000000..e76fd5b --- /dev/null +++ b/internal/attendee/model.go @@ -0,0 +1,20 @@ +package attendee + +import ( + "gorm.io/gorm" + + "github.com/marcopollivier/techagenda/internal/event" + "github.com/marcopollivier/techagenda/internal/user" +) + +type Attendee struct { + gorm.Model + FullName string + ContactInfo string + Metadata any + EventID string + UserID string + + event.Event + user.User +} diff --git a/internal/event/fx.go b/internal/event/fx.go new file mode 100644 index 0000000..3d6aee2 --- /dev/null +++ b/internal/event/fx.go @@ -0,0 +1,7 @@ +package event + +import "go.uber.org/fx" + +func Module() fx.Option { + return fx.Module("event") +} diff --git a/internal/event/model.go b/internal/event/model.go new file mode 100644 index 0000000..9f55688 --- /dev/null +++ b/internal/event/model.go @@ -0,0 +1,21 @@ +package event + +import ( + "time" + + "gorm.io/gorm" + + "github.com/marcopollivier/techagenda/internal/user" +) + +type Event struct { + gorm.Model + Name string + Tags []string + Location string + ScheduleDate time.Time + OwnerUserID uint + ExternalLink string + + user.User +} diff --git a/internal/event/service.go b/internal/event/service.go new file mode 100644 index 0000000..2ca474a --- /dev/null +++ b/internal/event/service.go @@ -0,0 +1,8 @@ +package event + +type Service interface { + Create() + Read() + Update() + Delete() +} diff --git a/internal/user/fx.go b/internal/user/fx.go new file mode 100644 index 0000000..095c0ba --- /dev/null +++ b/internal/user/fx.go @@ -0,0 +1,24 @@ +package user + +import "go.uber.org/fx" + +func Module() fx.Option { + return fx.Module("user", + fx.Provide(NewUserService), + fx.Provide(NewUserHandler), + fx.Invoke(SetUserHandlerRoutes), + ) +} + +// var Module = fx.Module("server", +// fx.Provide( +// New, +// ), +// fx.Provide( +// fx.Private, +// parseConfig, +// ), +// fx.Invoke(startServer), +// fx.Decorate(wrapLogger), +// +// ) diff --git a/internal/user/handler.go b/internal/user/handler.go new file mode 100644 index 0000000..7b49528 --- /dev/null +++ b/internal/user/handler.go @@ -0,0 +1,36 @@ +package user + +import ( + "log/slog" + + "github.com/labstack/echo/v4" +) + +type UserHandler struct { + service Service +} + +func NewUserHandler(service Service) *UserHandler { + return &UserHandler{service: service} +} + +func (h *UserHandler) ListAll(c echo.Context) (err error) { + var ( + ctx = c.Request().Context() + roleSTR = c.QueryParam("role") + role Role + users []User + ) + + if role, err = ParseRole(roleSTR); err != nil { + slog.ErrorContext(ctx, err.Error()) + return err + } + + if users, err = h.service.ListAll(ctx, role); err != nil { + slog.ErrorContext(ctx, err.Error()) + return err + } + + return c.JSON(200, users) +} diff --git a/internal/user/handler_oauth.go b/internal/user/handler_oauth.go new file mode 100644 index 0000000..5274587 --- /dev/null +++ b/internal/user/handler_oauth.go @@ -0,0 +1,93 @@ +package user + +import ( + "context" + "fmt" + "log/slog" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/markbates/goth" + "github.com/markbates/goth/gothic" +) + +func (h *UserHandler) AuthLogin(c echo.Context) (err error) { + var ( + ctx = c.Request().Context() + providerRaw = c.Param("provider") + authUser goth.User + user User + ) + + if _, err = ParseProvider(providerRaw); err != nil { + slog.ErrorContext(ctx, err.Error()) + return c.JSON(404, nil) + } + ctx = context.WithValue(ctx, "provider", providerRaw) + c.SetRequest(c.Request().WithContext(ctx)) + + if authUser, err = gothic.CompleteUserAuth(c.Response(), c.Request()); err != nil { + slog.ErrorContext(ctx, "Fail to complete user auth", "error", err.Error()) + gothic.BeginAuthHandler(c.Response(), c.Request()) + return nil + } + if user, err = h.service.Auth(ctx, authUser); err != nil { + slog.ErrorContext(ctx, "Fail to get user information from database", "error", err.Error()) + return c.JSON(500, map[string]any{ + "error": err.Error(), + }) + } + + return c.JSON(200, user) +} + +func (h *UserHandler) AuthLogout(c echo.Context) (err error) { + var ( + ctx = c.Request().Context() + res = c.Response() + req = c.Request() + providerRaw = c.Param("provider") + ) + if _, err = ParseProvider(providerRaw); err != nil { + slog.ErrorContext(ctx, err.Error()) + return c.JSON(404, nil) + } + ctx = context.WithValue(ctx, "provider", providerRaw) + c.SetRequest(c.Request().WithContext(ctx)) + + gothic.Logout(res, req) + res.Header().Set("Location", "/") + res.WriteHeader(http.StatusTemporaryRedirect) + return +} + +func (h *UserHandler) AuthCallback(c echo.Context) (err error) { + var ( + ctx = c.Request().Context() + res = c.Response() + req = c.Request() + providerRaw = c.Param("provider") + authUser goth.User + user User + ) + if _, err = ParseProvider(providerRaw); err != nil { + slog.ErrorContext(ctx, err.Error()) + return c.JSON(404, nil) + } + ctx = context.WithValue(ctx, "provider", providerRaw) + c.SetRequest(c.Request().WithContext(ctx)) + + if authUser, err = gothic.CompleteUserAuth(res, req); err != nil { + slog.ErrorContext(ctx, "Fail to complete user auth", "error", err.Error()) + fmt.Fprintln(res, err) + return + } + if user, err = h.service.Auth(ctx, authUser); err != nil { + slog.ErrorContext(ctx, "Fail to get user information from database", "error", err.Error()) + return c.JSON(500, map[string]any{ + "error": err.Error(), + }) + } + + return c.JSON(200, user) +} diff --git a/internal/user/models.go b/internal/user/models.go new file mode 100644 index 0000000..b3c11d6 --- /dev/null +++ b/internal/user/models.go @@ -0,0 +1,32 @@ +package user + +import "gorm.io/gorm" + +//go:generate go-enum --marshal --sql -f models.go + +type User struct { + gorm.Model + Email string + Name string + Role Role +} + +func (u *User) IsAdmin() bool { return u.Role == RoleAdmin } +func (u *User) IsMod() bool { return u.Role == RoleMod } + +type OAuth struct { + gorm.Model + UserID uint + Provider Provider + Identifier string + + User User +} + +func (OAuth) TableName() string { return "oauths" } + +// ENUM(user, mod, admin) +type Role int + +// ENUM(github) +type Provider int diff --git a/internal/user/models_enum.go b/internal/user/models_enum.go new file mode 100644 index 0000000..bf75044 --- /dev/null +++ b/internal/user/models_enum.go @@ -0,0 +1,279 @@ +// Code generated by go-enum DO NOT EDIT. +// Version: +// Revision: +// Build Date: +// Built By: + +package user + +import ( + "database/sql/driver" + "errors" + "fmt" +) + +const ( + // ProviderGithub is a Provider of type Github. + ProviderGithub Provider = iota +) + +var ErrInvalidProvider = errors.New("not a valid Provider") + +const _ProviderName = "github" + +var _ProviderMap = map[Provider]string{ + ProviderGithub: _ProviderName[0:6], +} + +// String implements the Stringer interface. +func (x Provider) String() string { + if str, ok := _ProviderMap[x]; ok { + return str + } + return fmt.Sprintf("Provider(%d)", x) +} + +// IsValid provides a quick way to determine if the typed value is +// part of the allowed enumerated values +func (x Provider) IsValid() bool { + _, ok := _ProviderMap[x] + return ok +} + +var _ProviderValue = map[string]Provider{ + _ProviderName[0:6]: ProviderGithub, +} + +// ParseProvider attempts to convert a string to a Provider. +func ParseProvider(name string) (Provider, error) { + if x, ok := _ProviderValue[name]; ok { + return x, nil + } + return Provider(0), fmt.Errorf("%s is %w", name, ErrInvalidProvider) +} + +// MarshalText implements the text marshaller method. +func (x Provider) MarshalText() ([]byte, error) { + return []byte(x.String()), nil +} + +// UnmarshalText implements the text unmarshaller method. +func (x *Provider) UnmarshalText(text []byte) error { + name := string(text) + tmp, err := ParseProvider(name) + if err != nil { + return err + } + *x = tmp + return nil +} + +var errProviderNilPtr = errors.New("value pointer is nil") // one per type for package clashes + +// Scan implements the Scanner interface. +func (x *Provider) Scan(value interface{}) (err error) { + if value == nil { + *x = Provider(0) + return + } + + // A wider range of scannable types. + // driver.Value values at the top of the list for expediency + switch v := value.(type) { + case int64: + *x = Provider(v) + case string: + *x, err = ParseProvider(v) + case []byte: + *x, err = ParseProvider(string(v)) + case Provider: + *x = v + case int: + *x = Provider(v) + case *Provider: + if v == nil { + return errProviderNilPtr + } + *x = *v + case uint: + *x = Provider(v) + case uint64: + *x = Provider(v) + case *int: + if v == nil { + return errProviderNilPtr + } + *x = Provider(*v) + case *int64: + if v == nil { + return errProviderNilPtr + } + *x = Provider(*v) + case float64: // json marshals everything as a float64 if it's a number + *x = Provider(v) + case *float64: // json marshals everything as a float64 if it's a number + if v == nil { + return errProviderNilPtr + } + *x = Provider(*v) + case *uint: + if v == nil { + return errProviderNilPtr + } + *x = Provider(*v) + case *uint64: + if v == nil { + return errProviderNilPtr + } + *x = Provider(*v) + case *string: + if v == nil { + return errProviderNilPtr + } + *x, err = ParseProvider(*v) + } + + return +} + +// Value implements the driver Valuer interface. +func (x Provider) Value() (driver.Value, error) { + return x.String(), nil +} + +const ( + // RoleUser is a Role of type User. + RoleUser Role = iota + // RoleMod is a Role of type Mod. + RoleMod + // RoleAdmin is a Role of type Admin. + RoleAdmin +) + +var ErrInvalidRole = errors.New("not a valid Role") + +const _RoleName = "usermodadmin" + +var _RoleMap = map[Role]string{ + RoleUser: _RoleName[0:4], + RoleMod: _RoleName[4:7], + RoleAdmin: _RoleName[7:12], +} + +// String implements the Stringer interface. +func (x Role) String() string { + if str, ok := _RoleMap[x]; ok { + return str + } + return fmt.Sprintf("Role(%d)", x) +} + +// IsValid provides a quick way to determine if the typed value is +// part of the allowed enumerated values +func (x Role) IsValid() bool { + _, ok := _RoleMap[x] + return ok +} + +var _RoleValue = map[string]Role{ + _RoleName[0:4]: RoleUser, + _RoleName[4:7]: RoleMod, + _RoleName[7:12]: RoleAdmin, +} + +// ParseRole attempts to convert a string to a Role. +func ParseRole(name string) (Role, error) { + if x, ok := _RoleValue[name]; ok { + return x, nil + } + return Role(0), fmt.Errorf("%s is %w", name, ErrInvalidRole) +} + +// MarshalText implements the text marshaller method. +func (x Role) MarshalText() ([]byte, error) { + return []byte(x.String()), nil +} + +// UnmarshalText implements the text unmarshaller method. +func (x *Role) UnmarshalText(text []byte) error { + name := string(text) + tmp, err := ParseRole(name) + if err != nil { + return err + } + *x = tmp + return nil +} + +var errRoleNilPtr = errors.New("value pointer is nil") // one per type for package clashes + +// Scan implements the Scanner interface. +func (x *Role) Scan(value interface{}) (err error) { + if value == nil { + *x = Role(0) + return + } + + // A wider range of scannable types. + // driver.Value values at the top of the list for expediency + switch v := value.(type) { + case int64: + *x = Role(v) + case string: + *x, err = ParseRole(v) + case []byte: + *x, err = ParseRole(string(v)) + case Role: + *x = v + case int: + *x = Role(v) + case *Role: + if v == nil { + return errRoleNilPtr + } + *x = *v + case uint: + *x = Role(v) + case uint64: + *x = Role(v) + case *int: + if v == nil { + return errRoleNilPtr + } + *x = Role(*v) + case *int64: + if v == nil { + return errRoleNilPtr + } + *x = Role(*v) + case float64: // json marshals everything as a float64 if it's a number + *x = Role(v) + case *float64: // json marshals everything as a float64 if it's a number + if v == nil { + return errRoleNilPtr + } + *x = Role(*v) + case *uint: + if v == nil { + return errRoleNilPtr + } + *x = Role(*v) + case *uint64: + if v == nil { + return errRoleNilPtr + } + *x = Role(*v) + case *string: + if v == nil { + return errRoleNilPtr + } + *x, err = ParseRole(*v) + } + + return +} + +// Value implements the driver Valuer interface. +func (x Role) Value() (driver.Value, error) { + return x.String(), nil +} diff --git a/internal/user/oauth_providers.go b/internal/user/oauth_providers.go new file mode 100644 index 0000000..53f6f00 --- /dev/null +++ b/internal/user/oauth_providers.go @@ -0,0 +1,27 @@ +package user + +import ( + "log/slog" + + "github.com/markbates/goth" + "github.com/markbates/goth/providers/github" + "github.com/samber/lo" + + "github.com/marcopollivier/techagenda/lib/config" +) + +func registerProviders() { + cfg := config.Get() + + goth.UseProviders( + github.New( + cfg.Providers.Github.Key, + cfg.Providers.Github.Secret, + "http://localhost:8000/auth/github/callback", + "user", + ), + ) + + pp := lo.Keys(goth.GetProviders()) + slog.Info("set providers", "providers", pp) +} diff --git a/internal/user/router.go b/internal/user/router.go new file mode 100644 index 0000000..e58a68e --- /dev/null +++ b/internal/user/router.go @@ -0,0 +1,15 @@ +package user + +import "github.com/labstack/echo/v4" + +func SetUserHandlerRoutes(server *echo.Echo, handler *UserHandler) { + registerProviders() + auth := server.Group("/auth/:provider") + + auth.GET("", handler.AuthLogin) + auth.GET("/logout", handler.AuthLogout) + auth.GET("/callback", handler.AuthCallback) + + grp := server.Group("/users") + grp.GET("", handler.ListAll) +} diff --git a/internal/user/service.go b/internal/user/service.go new file mode 100644 index 0000000..b7260c9 --- /dev/null +++ b/internal/user/service.go @@ -0,0 +1,93 @@ +package user + +import ( + "context" + "fmt" + "log/slog" + + "github.com/markbates/goth" + "gorm.io/gorm" +) + +type Service interface { + Auth(ctx context.Context, oauthUser goth.User) (user User, err error) + Get(ctx context.Context, userID uint) (user User, err error) + ListAll(ctx context.Context, role Role) (users []User, err error) +} + +type UserService struct { + db *gorm.DB +} + +func NewUserService(db *gorm.DB) Service { + return &UserService{ + db: db, + } +} + +func (s *UserService) Auth(ctx context.Context, oauthUser goth.User) (user User, err error) { + var oauth OAuth + if err = s.db.WithContext(ctx). + Where("provider = ?", oauthUser.Provider). + Where("identifier = ?", oauthUser.UserID). + First(&oauth).Error; err != nil && err != gorm.ErrRecordNotFound { + slog.ErrorContext(ctx, "Unexpected error searching for oauth link", "provider", oauthUser.Provider, "error", err.Error()) + return user, err + } + + // If the provider and id does not match to any one already on the database, we need to link with an user if the email already exists, if not we need to create a new user and link it. + if err == gorm.ErrRecordNotFound { + var provider Provider + slog.WarnContext(ctx, fmt.Sprintf("We didn't found a oauth link for email %s and provider %s", oauthUser.Email, oauthUser.Provider)) + if err = s.db.WithContext(ctx).Where("email = ?", oauthUser.Email).First(&user).Error; err != nil { + if err != gorm.ErrRecordNotFound { + slog.ErrorContext(ctx, "Unexpected error searching for user", "error", err.Error()) + return user, err + } + + slog.WarnContext(ctx, "No user found to this oauth link, creating a new one") + user = User{ + Email: oauthUser.Email, + Name: oauthUser.Name, + } + if err = s.db.WithContext(ctx).Create(&user).Error; err != nil { + slog.ErrorContext(ctx, "Fail to create new user", "error", err.Error()) + return user, err + } + } + + if provider, err = ParseProvider(oauthUser.Provider); err != nil { + slog.ErrorContext(ctx, fmt.Sprintf("Unexpected provider %s", oauthUser.Provider), "error", err.Error()) + return user, err + } + + slog.InfoContext(ctx, fmt.Sprintf("Linking user %d to oauth provider %s of identifier %s", user.ID, oauthUser.Provider, oauthUser.UserID)) + oauth = OAuth{ + UserID: user.ID, + Provider: provider, + Identifier: oauthUser.UserID, + } + if err = s.db.WithContext(ctx).Create(&oauth).Error; err != nil { + slog.ErrorContext(ctx, "Fail to create link of oauth user", "user", user.ID, "error", err.Error()) + return user, err + } + return user, err + } + + // If oauth is linked with a user just return the user + return s.Get(ctx, oauth.UserID) +} + +func (s *UserService) Get(ctx context.Context, userID uint) (user User, err error) { + if err = s.db.WithContext(ctx).Where("id = ?", userID).First(&user).Error; err != nil { + slog.ErrorContext(ctx, "Unable to find user!", "user", userID) + } + return user, err +} + +func (s *UserService) ListAll(ctx context.Context, role Role) (users []User, err error) { + if err = s.db.WithContext(ctx).Model(new(User)).Where("role = ?", role.String()).Scan(&users).Error; err != nil { + slog.ErrorContext(ctx, fmt.Sprintf("Fail to list users of role %s", role)) + } + return users, err +} diff --git a/lib/config/config.go b/lib/config/config.go new file mode 100644 index 0000000..c947086 --- /dev/null +++ b/lib/config/config.go @@ -0,0 +1,10 @@ +package config + +type Config struct { + HTTPPort int `env:"HTTP_PORT" envDefault:"8000"` + Environment string `env:"ENVIRONMENT" envDefault:"unknown"` + LogLevel string `env:"LOG_LEVEL" envDefault:"debug"` + LogFormat string `env:"LOG_FORMAT" envDefault:"text"` + DB Database `envPrefix:"DB_"` + Providers Providers `envPrefix:"PROVIDER_"` +} diff --git a/lib/config/database.go b/lib/config/database.go new file mode 100644 index 0000000..4d92aa3 --- /dev/null +++ b/lib/config/database.go @@ -0,0 +1,10 @@ +package config + +type Database struct { + Host string `env:"HOST" envDefault:"127.0.0.1"` + Port int `env:"PORT" envDefault:"5432"` + Name string `env:"NAME" envDefault:"postgres"` + User string `env:"USER" envDefault:"postgres"` + Pass string `env:"PASSWORD,unset"` + SSLMode string `env:"SSL_MODE" envDefault:"disable"` +} diff --git a/lib/config/initializer.go b/lib/config/initializer.go new file mode 100644 index 0000000..393f300 --- /dev/null +++ b/lib/config/initializer.go @@ -0,0 +1,28 @@ +package config + +import ( + "github.com/caarlos0/env/v10" + "github.com/samber/lo" + "go.uber.org/atomic" +) + +var ( + version string // HACK: This variable is filled at compiling time though a build argument `-ldflags "-X config.version=$VERSION"` + defaultConfig atomic.Value +) + +func init() { + defaultConfig.Store(load()) +} + +func Version() string { return lo.Ternary(lo.IsNotEmpty(version), version, "0.0.0-unknown") } + +func Get() Config { return lo.FromPtr(defaultConfig.Load().(*Config)) } + +func load() *Config { + cfg := Config{} + if err := env.Parse(&cfg); err != nil { + panic(err) + } + return &cfg +} diff --git a/lib/config/providers.go b/lib/config/providers.go new file mode 100644 index 0000000..0a05f27 --- /dev/null +++ b/lib/config/providers.go @@ -0,0 +1,10 @@ +package config + +type Providers struct { + Github Provider `envPrefix:"GITHUB_"` +} + +type Provider struct { + Key string `env:"KEY,unset"` + Secret string `env:"SECRET,unset"` +} diff --git a/lib/database/database.go b/lib/database/database.go new file mode 100644 index 0000000..6e33511 --- /dev/null +++ b/lib/database/database.go @@ -0,0 +1,50 @@ +package database + +import ( + "fmt" + "log" + "log/slog" + "os" + "time" + + "github.com/samber/lo" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" + + "github.com/marcopollivier/techagenda/lib/config" +) + +func NewDB() *gorm.DB { + cfg := config.Get() + db, err := gorm.Open(postgres.Open(BuildDSN(cfg)), &gorm.Config{ + Logger: gormlogger(), + }) + if err != nil { + slog.Error(fmt.Sprintf("Fail to connect db %s", cfg.DB.Host), "error", err.Error()) + panic(err) + } + return db +} + +func BuildDSN(cfg config.Config) string { + baseDSN := fmt.Sprintf("host=%s port=%d dbname=%s user=%s sslmode=%s TimeZone=UTC", cfg.DB.Host, cfg.DB.Port, cfg.DB.Name, cfg.DB.User, cfg.DB.SSLMode) + if lo.IsNotEmpty(cfg.DB.Pass) { + baseDSN += fmt.Sprintf(" password=%s", cfg.DB.Pass) + } + return baseDSN +} + +func gormlogger() logger.Interface { + newLogger := logger.New( + log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer + logger.Config{ + SlowThreshold: time.Second, // Slow SQL threshold + LogLevel: logger.Info, // Log level + IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger + ParameterizedQueries: true, // Don't include params in the SQL log + Colorful: true, // Disable color + }, + ) + return newLogger +} diff --git a/lib/logger/log.go b/lib/logger/log.go new file mode 100644 index 0000000..c6650c8 --- /dev/null +++ b/lib/logger/log.go @@ -0,0 +1,40 @@ +package logger + +import ( + "context" + "log/slog" + "os" + "sync" + + "github.com/samber/lo" + + "github.com/marcopollivier/techagenda/lib/config" +) + +var once sync.Once + +func init() { + once.Do(func() { + cfg := config.Get() + + handler := lo.Switch[string, slog.Handler](cfg.LogFormat). + Case("json", slog.NewJSONHandler(os.Stdout, nil)). + Default(slog.NewTextHandler(os.Stdout, nil)) + + l := slog.New(handler). + With( + "env", cfg.Environment, + "version", config.Version(), + ) + + level := lo.Switch[string, slog.Level](cfg.LogLevel). + Case("info", slog.LevelInfo). + Case("warn", slog.LevelWarn). + Case("error", slog.LevelError). + Default(slog.LevelDebug) + + l.Enabled(context.Background(), level) + + slog.SetDefault(l) + }) +} diff --git a/lib/server/http.go b/lib/server/http.go new file mode 100644 index 0000000..ba25be4 --- /dev/null +++ b/lib/server/http.go @@ -0,0 +1,26 @@ +package server + +import ( + "context" + "fmt" + "log/slog" + + "github.com/labstack/echo/v4" + "go.uber.org/fx" + + "github.com/marcopollivier/techagenda/lib/config" +) + +func NewHTTPServer(lc fx.Lifecycle) *echo.Echo { + srv := echo.New() + cfg := config.Get() + lc.Append(fx.Hook{ + OnStart: func(_ context.Context) error { + slog.Info(fmt.Sprintf("Starting HTTP server at %d", cfg.HTTPPort)) + go srv.Start(fmt.Sprintf(":%d", cfg.HTTPPort)) + return nil + }, + OnStop: srv.Shutdown, + }) + return srv +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..8c9d69d --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/marcopollivier/techagenda/cmd" + +func main() { + cmd.Execute() +} diff --git a/migrations/20240113234311_create_user_table.go b/migrations/20240113234311_create_user_table.go new file mode 100644 index 0000000..15929ad --- /dev/null +++ b/migrations/20240113234311_create_user_table.go @@ -0,0 +1,68 @@ +package migrations + +import ( + "context" + "database/sql" + + "github.com/pressly/goose/v3" +) + +func init() { + goose.AddMigrationContext(upCreateUsersTable, downCreateUsersTable) +} + +func upCreateUsersTable(ctx context.Context, tx *sql.Tx) error { + // This code is executed when the migration is applied. + + // Users table + if _, err := tx.ExecContext(ctx, ` + + CREATE TYPE role AS ENUM ('user', 'mod', 'admin'); + + CREATE TABLE IF NOT EXISTS users ( + id BIGSERIAL PRIMARY KEY, + email TEXT NOT NULL, + name TEXT, + role ROLE DEFAULT 'user', + created_at TIMESTAMP DEFAULT now(), + updated_at TIMESTAMP DEFAULT now(), + deleted_at TIMESTAMP, + + UNIQUE(email) + ); + + CREATE INDEX idx_users_role on users (role); + `); err != nil { + return err + } + + // OAuth table + if _, err := tx.ExecContext(ctx, ` + CREATE TYPE provider AS ENUM ('github'); + + CREATE TABLE IF NOT EXISTS oauths ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + provider PROVIDER NOT NULL, + identifier TEXT NOT NULL, + created_at TIMESTAMP DEFAULT now(), + updated_at TIMESTAMP DEFAULT now(), + deleted_at TIMESTAMP, + + CONSTRAINT fk_oauths_user_id FOREIGN KEY (user_id) REFERENCES users(id), + UNIQUE(user_id, provider, identifier) + ); + `); err != nil { + return err + } + return nil +} + +func downCreateUsersTable(ctx context.Context, tx *sql.Tx) error { + _, err := tx.ExecContext(ctx, ` + DROP TYPE role + DROP TABLE users + DROP TABLE oauths + `) + return err +} diff --git a/carousel.js b/public/carousel.js similarity index 100% rename from carousel.js rename to public/carousel.js diff --git a/eventos.json b/public/eventos.json similarity index 100% rename from eventos.json rename to public/eventos.json diff --git a/img/conferencia.jpg b/public/img/conferencia.jpg similarity index 100% rename from img/conferencia.jpg rename to public/img/conferencia.jpg diff --git a/img/forma1.png b/public/img/forma1.png similarity index 100% rename from img/forma1.png rename to public/img/forma1.png diff --git a/img/github-mark.png b/public/img/github-mark.png similarity index 100% rename from img/github-mark.png rename to public/img/github-mark.png diff --git a/img/gopherconbr/logo.png b/public/img/gopherconbr/logo.png similarity index 100% rename from img/gopherconbr/logo.png rename to public/img/gopherconbr/logo.png diff --git a/img/lead-img.jpg b/public/img/lead-img.jpg similarity index 100% rename from img/lead-img.jpg rename to public/img/lead-img.jpg diff --git a/img/meetups.jpg b/public/img/meetups.jpg similarity index 100% rename from img/meetups.jpg rename to public/img/meetups.jpg diff --git a/img/menu-mobile.png b/public/img/menu-mobile.png similarity index 100% rename from img/menu-mobile.png rename to public/img/menu-mobile.png diff --git a/img/palestras.jpg b/public/img/palestras.jpg similarity index 100% rename from img/palestras.jpg rename to public/img/palestras.jpg diff --git a/img/slide-default.png b/public/img/slide-default.png similarity index 100% rename from img/slide-default.png rename to public/img/slide-default.png diff --git a/img/slide-first.png b/public/img/slide-first.png similarity index 100% rename from img/slide-first.png rename to public/img/slide-first.png diff --git a/img/slide-second.png b/public/img/slide-second.png similarity index 100% rename from img/slide-second.png rename to public/img/slide-second.png diff --git a/img/slide-third.png b/public/img/slide-third.png similarity index 100% rename from img/slide-third.png rename to public/img/slide-third.png diff --git a/img/workshops.jpg b/public/img/workshops.jpg similarity index 100% rename from img/workshops.jpg rename to public/img/workshops.jpg diff --git a/index.html b/public/index.html similarity index 100% rename from index.html rename to public/index.html diff --git a/map-rendering.js b/public/map-rendering.js similarity index 100% rename from map-rendering.js rename to public/map-rendering.js diff --git a/style.css b/public/style.css similarity index 100% rename from style.css rename to public/style.css From d0858d4d6195b0ff6147e7a2b07652ebdefe81dc Mon Sep 17 00:00:00 2001 From: Everest Date: Tue, 16 Jan 2024 01:29:37 -0300 Subject: [PATCH 2/9] build base pipelines --- .github/workflows/on_merge_main.yaml | 25 +++++++++++++ .github/workflows/on_push.yaml | 54 ++++++++++++++++++++++++++++ .github/workflows/on_release.yaml | 28 +++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 .github/workflows/on_merge_main.yaml create mode 100644 .github/workflows/on_push.yaml create mode 100644 .github/workflows/on_release.yaml diff --git a/.github/workflows/on_merge_main.yaml b/.github/workflows/on_merge_main.yaml new file mode 100644 index 0000000..034f5b1 --- /dev/null +++ b/.github/workflows/on_merge_main.yaml @@ -0,0 +1,25 @@ +name: On merge with main + +on: + push: + branches: + - 'main' + tags-ignore: + - '*.*' + +env: + GIT_USERNAME: ${{ github.actor }} + GIT_EMAIL: "github-actions@github.com" + TODOCHECK_AUTH_TOKEN: ${{ secrets.GHA_PAT }} + +jobs: + create-issues: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Download todocheck + run: | + curl -fsSL "https://github.com/preslavmihaylov/todocheck/releases/download/v0.6.1/todocheck-v0.6.1-linux-x86_64" -o todocheck + chmod +x todocheck + - name: Run todocheck + run: ./todocheck --basepath . diff --git a/.github/workflows/on_push.yaml b/.github/workflows/on_push.yaml new file mode 100644 index 0000000..e551691 --- /dev/null +++ b/.github/workflows/on_push.yaml @@ -0,0 +1,54 @@ +name: on push + +on: + push: + branches: + - '**' + tags-ignore: + - '*.*' + paths-ignore: + - README.md + - .github/* + - docs/* + +env: + GIT_USERNAME: '${{ github.actor }}' + GIT_EMAIL: github-actions@github.com + PA_TOKEN: '${{ secrets.GHA_PAT }}' + +jobs: + lint: + permissions: + contents: read + id-token: write + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v4.1.0 + with: + go-version: 1.21.5 + cache: false + - uses: actions/checkout@v3 + + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + install-mode: goinstall + skip-pkg-cache: true + + test: + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4.1.0 + with: + go-version: 1.21.5 + - name: Test + run: go test ./... diff --git a/.github/workflows/on_release.yaml b/.github/workflows/on_release.yaml new file mode 100644 index 0000000..7a3efd0 --- /dev/null +++ b/.github/workflows/on_release.yaml @@ -0,0 +1,28 @@ +name: On release push + +on: + push: + tags: + - "v*" + +env: + ENVIRONMENT: "prod" + GIT_USERNAME: ${{ github.actor }} + GIT_EMAIL: "github-actions@github.com" + PA_TOKEN: ${{ secrets.GHA_PAT }} + HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set output + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + - name: Login to Heroku Container registry + run: heroku container:login + - name: Build and push + run: heroku container:push -a ${{ secrets.HEROKU_APP_NAME }} web --arg version=${{ steps.vars.outputs.tag }} + - name: Release + run: heroku container:release -a ${{ secrets.HEROKU_APP_NAME }} web From eb8b73e32e758c523aefab7a34a788b05602ed94 Mon Sep 17 00:00:00 2001 From: Everest Date: Tue, 16 Jan 2024 01:29:56 -0300 Subject: [PATCH 3/9] add static handler --- cmd/root.go | 2 ++ internal/static/fx.go | 9 +++++++++ internal/static/static.go | 7 +++++++ 3 files changed, 18 insertions(+) create mode 100644 internal/static/fx.go create mode 100644 internal/static/static.go diff --git a/cmd/root.go b/cmd/root.go index e016532..02ccbba 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/cobra" "go.uber.org/fx" + "github.com/marcopollivier/techagenda/internal/static" "github.com/marcopollivier/techagenda/internal/user" "github.com/marcopollivier/techagenda/lib/database" _ "github.com/marcopollivier/techagenda/lib/logger" @@ -23,6 +24,7 @@ var rootCmd = &cobra.Command{ fx.New( fx.Provide(database.NewDB), fx.Provide(server.NewHTTPServer), + static.Module(), user.Module(), // event.Module(), // attendee.Module(), diff --git a/internal/static/fx.go b/internal/static/fx.go new file mode 100644 index 0000000..f75d155 --- /dev/null +++ b/internal/static/fx.go @@ -0,0 +1,9 @@ +package static + +import "go.uber.org/fx" + +func Module() fx.Option { + return fx.Module("static_server", + fx.Invoke(Router), + ) +} diff --git a/internal/static/static.go b/internal/static/static.go new file mode 100644 index 0000000..ee85034 --- /dev/null +++ b/internal/static/static.go @@ -0,0 +1,7 @@ +package static + +import "github.com/labstack/echo/v4" + +func Router(server *echo.Echo) { + server.Static("", "public") +} From 4fbc7e03f3014e0f3d18a919a5696be8d9d17c33 Mon Sep 17 00:00:00 2001 From: Everest Date: Tue, 16 Jan 2024 01:30:13 -0300 Subject: [PATCH 4/9] rename http port variable to match heroko spec --- lib/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/config/config.go b/lib/config/config.go index c947086..016a2c5 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -1,7 +1,7 @@ package config type Config struct { - HTTPPort int `env:"HTTP_PORT" envDefault:"8000"` + HTTPPort int `env:"PORT" envDefault:"8000"` Environment string `env:"ENVIRONMENT" envDefault:"unknown"` LogLevel string `env:"LOG_LEVEL" envDefault:"debug"` LogFormat string `env:"LOG_FORMAT" envDefault:"text"` From 238ffddef66e79766d412f811baf19de02eab666 Mon Sep 17 00:00:00 2001 From: Everest Date: Tue, 16 Jan 2024 01:31:18 -0300 Subject: [PATCH 5/9] add todo check config Todocheck is a CLI that map the code for todo comments and convert them into issues --- .todocheck.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .todocheck.yml diff --git a/.todocheck.yml b/.todocheck.yml new file mode 100644 index 0000000..b4f3ce8 --- /dev/null +++ b/.todocheck.yml @@ -0,0 +1,4 @@ +origin: github.com/marcopollivier/techagenda +issue_tracker: GITHUB +auth: + type: apitoken From ab472cedbd97b6db2ac0b757e2f300a0d9b96e72 Mon Sep 17 00:00:00 2001 From: Everest Date: Tue, 16 Jan 2024 01:45:41 -0300 Subject: [PATCH 6/9] change arg to upper-case --- .github/workflows/on_release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/on_release.yaml b/.github/workflows/on_release.yaml index 7a3efd0..1ca903b 100644 --- a/.github/workflows/on_release.yaml +++ b/.github/workflows/on_release.yaml @@ -23,6 +23,6 @@ jobs: - name: Login to Heroku Container registry run: heroku container:login - name: Build and push - run: heroku container:push -a ${{ secrets.HEROKU_APP_NAME }} web --arg version=${{ steps.vars.outputs.tag }} + run: heroku container:push -a ${{ secrets.HEROKU_APP_NAME }} web --arg VERSION=${{ steps.vars.outputs.tag }} - name: Release run: heroku container:release -a ${{ secrets.HEROKU_APP_NAME }} web From f7283fc8cf530d89754700d4bda4a5c180d9c8cd Mon Sep 17 00:00:00 2001 From: Everest Date: Tue, 16 Jan 2024 01:45:54 -0300 Subject: [PATCH 7/9] create Dockerfile --- Dockerfile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..15456a8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +# Build image layer +FROM golang:1.21.5 AS builder +ARG VERSION +ADD . /app +WORKDIR /app +RUN go mod download +ENV CGO_ENABLED=0 GO111MODULE=on GOOS=linux +RUN go build -a -ldflags "-s -w -X 'github.com/marcopollivier/techagenda/lib/config.version=$VERSION'" -o techagenda . + +# Release image layer +# TODO: Find a way to send a scratch image to heroko +# FROM scratch +# COPY --from=alpine:latest /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +FROM alpine:latest +COPY --from=builder /app/techagenda /techagenda +COPY --from=builder /app/public /public +CMD ./techagenda + From 7b9a3b48024892bce156ff73dcb96b5d20b655ec Mon Sep 17 00:00:00 2001 From: Everest Date: Tue, 16 Jan 2024 02:06:43 -0300 Subject: [PATCH 8/9] add app host env var --- internal/user/oauth_providers.go | 3 ++- lib/config/config.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/user/oauth_providers.go b/internal/user/oauth_providers.go index 53f6f00..35839ab 100644 --- a/internal/user/oauth_providers.go +++ b/internal/user/oauth_providers.go @@ -1,6 +1,7 @@ package user import ( + "fmt" "log/slog" "github.com/markbates/goth" @@ -17,7 +18,7 @@ func registerProviders() { github.New( cfg.Providers.Github.Key, cfg.Providers.Github.Secret, - "http://localhost:8000/auth/github/callback", + fmt.Sprintf("%s/auth/github/callback", cfg.AppHost), "user", ), ) diff --git a/lib/config/config.go b/lib/config/config.go index 016a2c5..4f1fac9 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -1,6 +1,7 @@ package config type Config struct { + AppHost string `env:"HOST" envDefault:"http://localhost:8000"` HTTPPort int `env:"PORT" envDefault:"8000"` Environment string `env:"ENVIRONMENT" envDefault:"unknown"` LogLevel string `env:"LOG_LEVEL" envDefault:"debug"` From ffdc3e44daaba47fb4a13c69c35387c632c992f8 Mon Sep 17 00:00:00 2001 From: Everest Date: Wed, 17 Jan 2024 20:15:15 -0300 Subject: [PATCH 9/9] fix lint --- .golangci.yml | 4 ++++ internal/user/handler_oauth.go | 5 ++++- lib/server/http.go | 7 ++++++- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..e1f6225 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,4 @@ +issues: + exclude-rules: + - path: internal/user/handler_oauth.go + text: 'SA1029: should not use built-in type string as key for value; define your own type to avoid collisions' diff --git a/internal/user/handler_oauth.go b/internal/user/handler_oauth.go index 5274587..cc0038e 100644 --- a/internal/user/handler_oauth.go +++ b/internal/user/handler_oauth.go @@ -55,7 +55,10 @@ func (h *UserHandler) AuthLogout(c echo.Context) (err error) { ctx = context.WithValue(ctx, "provider", providerRaw) c.SetRequest(c.Request().WithContext(ctx)) - gothic.Logout(res, req) + if err = gothic.Logout(res, req); err != nil { + slog.Error("Fail to execute logout", "error", err.Error()) + return + } res.Header().Set("Location", "/") res.WriteHeader(http.StatusTemporaryRedirect) return diff --git a/lib/server/http.go b/lib/server/http.go index ba25be4..a5474d2 100644 --- a/lib/server/http.go +++ b/lib/server/http.go @@ -17,7 +17,12 @@ func NewHTTPServer(lc fx.Lifecycle) *echo.Echo { lc.Append(fx.Hook{ OnStart: func(_ context.Context) error { slog.Info(fmt.Sprintf("Starting HTTP server at %d", cfg.HTTPPort)) - go srv.Start(fmt.Sprintf(":%d", cfg.HTTPPort)) + go func() { + if err := srv.Start(fmt.Sprintf(":%d", cfg.HTTPPort)); err != nil { + slog.Error("Fail to start http server", "error", err) + panic(err) + } + }() return nil }, OnStop: srv.Shutdown,