From 3ccebd8cfba6e35dd9dac8743be2c1356a178f6c Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 02:21:09 -0400 Subject: [PATCH 01/16] Refactor app sous v4/ --- v4/.cobra.yaml | 3 + v4/.env | 3 + v4/LICENSE | 0 v4/README.md | 36 +++ v4/cmd/root.go | 56 ++++ v4/cmd/server.go | 106 +++++++ v4/cmd/web.go | 20 ++ v4/data/data.go | 254 +++++++++++++++++ v4/docker-compose.yaml | 21 ++ v4/go.mod | 43 +++ v4/go.sum | 531 ++++++++++++++++++++++++++++++++++++ v4/handlers/insert.go | 76 ++++++ v4/handlers/read.go | 117 ++++++++ v4/handlers/seed.go | 42 +++ v4/handlers/v4.go | 13 + v4/main.go | 7 + v4/models/models.go | 33 +++ v4/web/embed.go | 10 + v4/web/templates/index.html | 1 + 19 files changed, 1372 insertions(+) create mode 100644 v4/.cobra.yaml create mode 100644 v4/.env create mode 100644 v4/LICENSE create mode 100644 v4/README.md create mode 100644 v4/cmd/root.go create mode 100644 v4/cmd/server.go create mode 100644 v4/cmd/web.go create mode 100644 v4/data/data.go create mode 100644 v4/docker-compose.yaml create mode 100644 v4/go.mod create mode 100644 v4/go.sum create mode 100644 v4/handlers/insert.go create mode 100644 v4/handlers/read.go create mode 100644 v4/handlers/seed.go create mode 100644 v4/handlers/v4.go create mode 100644 v4/main.go create mode 100644 v4/models/models.go create mode 100644 v4/web/embed.go create mode 100644 v4/web/templates/index.html diff --git a/v4/.cobra.yaml b/v4/.cobra.yaml new file mode 100644 index 0000000..9213962 --- /dev/null +++ b/v4/.cobra.yaml @@ -0,0 +1,3 @@ +author: AGECEM +license: none +useViper: true diff --git a/v4/.env b/v4/.env new file mode 100644 index 0000000..2e8a756 --- /dev/null +++ b/v4/.env @@ -0,0 +1,3 @@ +BOTTIN_POSTGRES_DATABASE=bottin +BOTTIN_POSTGRES_PASSWORD=bottin +BOTTIN_POSTGRES_USER=bottin diff --git a/v4/LICENSE b/v4/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/v4/README.md b/v4/README.md new file mode 100644 index 0000000..77ca460 --- /dev/null +++ b/v4/README.md @@ -0,0 +1,36 @@ +## usage + +### Base de données + +`docker-compose.yaml` est fourni pour facilité le développement avec une base de données postgres sur docker. + +En production, des efforts de high-availability devraient être mis. + + +Remplir .env avec les infos qui seront utilisées pour déployer le container: + +(Remplacer `bottin` par quelque chose de plus sécuritaire) + +```sh +BOTTIN_POSTGRES_DATABASE=bottin +BOTTIN_POSTGRES_PASSWORD=bottin +BOTTIN_POSTGRES_USER=bottin +``` + +Déployer avec docker-compose: + +`$ docker-compose up -d` + +### Configuration + +Remplir le fichier de config `~/.bottin.yaml`. + +## Build + +Build l'exécutable du serveur. (TODO) + +### API + +Démarrer le serveur web: + +`$ bottin server` diff --git a/v4/cmd/root.go b/v4/cmd/root.go new file mode 100644 index 0000000..94350ca --- /dev/null +++ b/v4/cmd/root.go @@ -0,0 +1,56 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "bottin", + Short: "Application de gestion de distribution d'agendas", +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".bottin" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".bottin") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/v4/cmd/server.go b/v4/cmd/server.go new file mode 100644 index 0000000..a2c8799 --- /dev/null +++ b/v4/cmd/server.go @@ -0,0 +1,106 @@ +package cmd + +import ( + "crypto/subtle" + "fmt" + "log" + + "git.agecem.com/agecem/bottin/v4/data" + "git.agecem.com/agecem/bottin/v4/handlers" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + apiPort int + apiKey string +) + +// serverCmd represents the server command +var serverCmd = &cobra.Command{ + Use: "server", + Short: "Démarrer le serveur API", + Run: func(cmd *cobra.Command, args []string) { + + e := echo.New() + + // Middlewares + + e.Pre(middleware.AddTrailingSlash()) + + if apiKey != "" { + e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { + return subtle.ConstantTimeCompare([]byte(key), []byte(apiKey)) == 1, nil + })) + } + + // Routes + + e.GET("/v4/", handlers.GetV4) + + e.POST("/v4/seed/", handlers.PostSeed) + + e.GET("/v4/membres/:membre_id/", handlers.ReadMembre) + e.POST("/v4/membres/", handlers.PostMembres) + + // Execution + + connection := data.PostgresConnection{ + User: viper.GetString("db.user"), + Password: viper.GetString("db.password"), + Host: viper.GetString("db.host"), + Database: viper.GetString("db.database"), + Port: viper.GetInt("db.port"), + } + + client, err := data.NewDataClient(connection) + if err != nil { + log.Fatalf("Could not establish database connection.\n Error: %s\n", err) + } + + err = client.DB.Ping() + if err != nil { + log.Fatalf("Database was supposed to be ready but Ping() failed.\n Error: %s\n", err) + } + + client.DB.Close() + + e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", apiPort))) + }, +} + +func init() { + rootCmd.AddCommand(serverCmd) + + // api.key + serverCmd.Flags().StringVar( + &apiKey, "api-key", "bottin", + "API server key. Leave empty for no key auth. (config: 'api.key')") + + // api.port + serverCmd.Flags().IntVar( + &apiPort, "api-port", 1312, + "API server port (config:'api.port')") + + // db.database + serverCmd.Flags().String("db-database", "bottin", "Postgres database (config:'db.database')") + viper.BindPFlag("db.database", serverCmd.Flags().Lookup("db-database")) + + // db.host + serverCmd.Flags().String("db-host", "", "Postgres host (config:'db.host')") + viper.BindPFlag("db.host", serverCmd.Flags().Lookup("db-host")) + + // db.password + serverCmd.Flags().String("db-password", "", "Postgres password (config:'db.password')") + viper.BindPFlag("db.password", serverCmd.Flags().Lookup("db-password")) + + // db.port + serverCmd.Flags().Int("db-port", 5432, "Postgres port (config:'db.port')") + viper.BindPFlag("db.port", serverCmd.Flags().Lookup("db-port")) + + // db.user + serverCmd.Flags().String("db-user", "", "Postgres user (config:'db.user')") + viper.BindPFlag("db.user", serverCmd.Flags().Lookup("db-user")) +} diff --git a/v4/cmd/web.go b/v4/cmd/web.go new file mode 100644 index 0000000..ab10ba0 --- /dev/null +++ b/v4/cmd/web.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// webCmd represents the web command +var webCmd = &cobra.Command{ + Use: "web", + Short: "Démarrer le client web", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("web called") + }, +} + +func init() { + rootCmd.AddCommand(webCmd) +} diff --git a/v4/data/data.go b/v4/data/data.go new file mode 100644 index 0000000..4817e7a --- /dev/null +++ b/v4/data/data.go @@ -0,0 +1,254 @@ +package data + +import ( + "fmt" + + "git.agecem.com/agecem/bottin/v4/models" + _ "github.com/jackc/pgx/stdlib" + "github.com/jmoiron/sqlx" +) + +// DataClient is a postgres client based on sqlx +type DataClient struct { + PostgresConnection PostgresConnection + DB sqlx.DB +} + +type PostgresConnection struct { + User string + Password string + Database string + Host string + Port int + SSL bool +} + +func NewDataClient(connection PostgresConnection) (*DataClient, error) { + client := &DataClient{PostgresConnection: connection} + + connectionString := fmt.Sprintf("postgres://%s:%s@%s:%d/%s", + client.PostgresConnection.User, + client.PostgresConnection.Password, + client.PostgresConnection.Host, + client.PostgresConnection.Port, + client.PostgresConnection.Database, + ) + + db, err := sqlx.Connect("pgx", connectionString) + if err != nil { + return nil, err + } + + client.DB = *db + + return client, nil +} + +func (d *DataClient) Seed() (int64, error) { + result, err := d.DB.Exec(models.Schema) + if err != nil { + return 0, err + } + + rows, err := result.RowsAffected() + if err != nil { + return rows, err + } + + return rows, nil +} + +// InsertMembres inserts a slice of Membre into a database, returning the amount inserted and any error encountered +func (d *DataClient) InsertMembres(membres []models.Membre) (int64, error) { + var rowsInserted int64 + tx, err := d.DB.Beginx() + if err != nil { + tx.Rollback() + return rowsInserted, err + } + + for _, membre := range membres { + _, err := tx.NamedExec("INSERT INTO membres (id, last_name, first_name, prefered_name, programme_id) VALUES (:id, :last_name, :first_name, :prefered_name, :programme_id);", &membre) + if err != nil { + tx.Rollback() + return rowsInserted, err + } + rowsInserted++ + } + + err = tx.Commit() + if err != nil { + return rowsInserted, err + } + + return rowsInserted, nil +} + +func (d *DataClient) InsertProgrammes(programmes []models.Programme) (int64, error) { + var rowsInserted int64 + tx, err := d.DB.Beginx() + if err != nil { + tx.Rollback() + return rowsInserted, err + } + + for _, programme := range programmes { + _, err := tx.NamedExec("INSERT INTO programmes (id, titre) VALUES (:id, :titre);", &programme) + if err != nil { + tx.Rollback() + return rowsInserted, err + } + rowsInserted++ + } + + err = tx.Commit() + if err != nil { + return rowsInserted, err + } + + return rowsInserted, nil +} + +func (d *DataClient) GetMembre(membreID string) (models.Membre, error) { + var membre models.Membre + + rows, err := d.DB.Queryx("SELECT * FROM membres WHERE id = $1 LIMIT 1;", membreID) + if err != nil { + return membre, err + } + + for rows.Next() { + err := rows.StructScan(&membre) + if err != nil { + return membre, err + } + } + + if membre.ID == "" { + return membre, fmt.Errorf("No membre by that id was found") + } + + return membre, nil +} + +/* +func (d *DataClient) Insert(assets []models.Asset) (id int64, err error) { + // Check for minimal required info + for _, asset := range assets { + if asset.Description == "" { + err = errors.New("Cannot insert: At least one asset has no `description` set.") + return + } + } + + tx := d.DB.MustBegin() + + for _, asset := range assets { + _, err = tx.NamedExec("INSERT INTO assets (description, status, created_at) VALUES (:description, :status, current_timestamp)", asset) + if err != nil { + return + } + } + + err = tx.Commit() + + return +} + +func (d *DataClient) List() ([]models.Asset, error) { + // Query the database, storing results in a []Person (wrapped in []interface{}) + assets := []models.Asset{} + + err := d.DB.Select(&assets, "SELECT * FROM assets WHERE deleted_at IS NULL LIMIT 1000") + if err != nil { + return nil, err + } + + return assets, nil +} + +// RecordEvent allows inserting into events when an asset or a tag is modified +// or deleted. +func (d *DataClient) RecordEvent(assetID, tagID int64, content string) error { + event := models.Event{ + AssetID: assetID, + TagID: tagID, + Content: content, + } + _, err := d.DB.NamedExec("INSERT INTO events (asset_id, tag_id, at, content) VALUES (:asset_id, :tag_id, current_timestamp, :content);", event) + if err != nil { + return err + } + + return nil +} + +func (d *DataClient) Delete(assetIDs []int64) ([]int64, error) { + var rows []int64 + + tx := d.DB.MustBegin() + + for _, assetID := range assetIDs { + result, err := d.DB.Exec("UPDATE assets SET deleted_at = current_timestamp WHERE id = $1 AND deleted_at IS NULL;", assetID) + if err != nil { + return rows, err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return rows, err + } + + if rowsAffected != 0 { + rows = append(rows, assetID) + } + } + + err := tx.Commit() + if err != nil { + return rows, err + } + + for _, assetID := range assetIDs { + d.RecordEvent(assetID, -1, fmt.Sprintf("Asset %d deleted.", assetID)) + } + + return rows, nil +} + +func (d *DataClient) UpdateAssetDescription(assetID int64, description string) (int64, error) { + result, err := d.DB.Exec("UPDATE assets SET description = $1 WHERE id = $2", description, assetID) + if err != nil { + return 0, err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return 0, err + } + + if rowsAffected != 0 { + return 0, errors.New("Nothing to do") + } + + return rowsAffected, nil +} + +func (d *DataClient) UpdateAssetStatus(assetID int64, status string) (int64, error) { + result, err := d.DB.Exec("UPDATE assets SET status = $1 WHERE id = $2", status, assetID) + if err != nil { + return 0, err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return 0, err + } + + if rowsAffected != 0 { + return 0, errors.New("Nothing to do") + } + + return rowsAffected, nil +} +*/ diff --git a/v4/docker-compose.yaml b/v4/docker-compose.yaml new file mode 100644 index 0000000..bfb46f1 --- /dev/null +++ b/v4/docker-compose.yaml @@ -0,0 +1,21 @@ +services: + + postgres: + image: postgres:latest + environment: + POSTGRES_DATABASE: "${BOTTIN_POSTGRES_DATABASE}" + POSTGRES_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}" + POSTGRES_USER: "${BOTTIN_POSTGRES_USER}" + ports: + - '5432:5432' + volumes: + - 'pgdata:/var/lib/postgresql/data' + + adminer: + image: adminer + restart: always + ports: + - 8088:8080 + +volumes: + pgdata: diff --git a/v4/go.mod b/v4/go.mod new file mode 100644 index 0000000..b6a59fc --- /dev/null +++ b/v4/go.mod @@ -0,0 +1,43 @@ +module git.agecem.com/agecem/bottin/v4 + +go 1.20 + +require ( + github.com/jackc/pgx v3.6.2+incompatible + github.com/jmoiron/sqlx v1.3.5 + github.com/labstack/echo/v4 v4.10.2 + github.com/spf13/cobra v1.7.0 + github.com/spf13/viper v1.15.0 +) + +require ( + github.com/cockroachdb/apd v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect + github.com/labstack/gommon v0.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + golang.org/x/crypto v0.6.0 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect + golang.org/x/time v0.3.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/v4/go.sum b/v4/go.sum new file mode 100644 index 0000000..03ed3fe --- /dev/null +++ b/v4/go.sum @@ -0,0 +1,531 @@ +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.44.3/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.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +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= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +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/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cpuguy83/go-md2man/v2 v2.0.2/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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +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.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +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-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= +github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= +github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +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/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +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.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +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= +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.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +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/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/lint v0.0.0-20201208152925-83fdc39ff7b5/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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +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/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/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-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +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.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +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.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +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-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/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.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +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= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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/v4/handlers/insert.go b/v4/handlers/insert.go new file mode 100644 index 0000000..dda92ee --- /dev/null +++ b/v4/handlers/insert.go @@ -0,0 +1,76 @@ +package handlers + +import ( + "net/http" + + "git.agecem.com/agecem/bottin/v4/data" + "git.agecem.com/agecem/bottin/v4/models" + "github.com/labstack/echo/v4" + "github.com/spf13/viper" +) + +func PostMembres(c echo.Context) error { + connection := data.PostgresConnection{ + User: viper.GetString("db.user"), + Password: viper.GetString("db.password"), + Host: viper.GetString("db.host"), + Database: viper.GetString("db.database"), + Port: viper.GetInt("db.port"), + } + + client, err := data.NewDataClient(connection) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not establish database connection", + "error": err.Error(), + }) + } + + programmes := []models.Programme{ + { + ID: "foo", + Titre: "Foo", + }, + { + ID: "bar", + Titre: "Bar", + }, + } + + newProgrammes, err := client.InsertProgrammes(programmes) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not insert programmes", + "error": err.Error(), + }) + } + + membres := []models.Membre{ + { + ID: "1327163", + PreferedName: "victor", + ProgrammeID: "foo", + }, + { + ID: "0000000", + PreferedName: "test user", + ProgrammeID: "bar", + }, + } + + newMembres, err := client.InsertMembres(membres) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not insert membres", + "error": err.Error(), + }) + } + + return c.JSON(http.StatusOK, map[string]interface{}{ + "message": "Insert successful", + "data": map[string]interface{}{ + "membres": newMembres, + "programmes": newProgrammes, + }, + }) +} diff --git a/v4/handlers/read.go b/v4/handlers/read.go new file mode 100644 index 0000000..8136e21 --- /dev/null +++ b/v4/handlers/read.go @@ -0,0 +1,117 @@ +package handlers + +import ( + "net/http" + + "git.agecem.com/agecem/bottin/v4/data" + "github.com/labstack/echo/v4" + "github.com/spf13/viper" +) + +func ReadMembre(c echo.Context) error { + connection := data.PostgresConnection{ + User: viper.GetString("db.user"), + Password: viper.GetString("db.password"), + Host: viper.GetString("db.host"), + Database: viper.GetString("db.database"), + Port: viper.GetInt("db.port"), + } + + client, err := data.NewDataClient(connection) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not establish database connection", + "error": err.Error(), + }) + } + + membreID := c.Param("membre_id") + + membre, err := client.GetMembre(membreID) + if err != nil { + if err.Error() == "No membre by that id was found" { + return c.JSON(http.StatusNotFound, map[string]string{ + "message": "Not Found", + }) + } + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Unknown error during GetMembre", + "error": err.Error(), + }) + } + + return c.JSON(http.StatusOK, map[string]interface{}{ + "message": "Read successful", + "data": map[string]interface{}{ + "membre": &membre, + }, + }) +} + +/* +func PostMembres(c echo.Context) error { + connection := data.PostgresConnection{ + User: viper.GetString("db.user"), + Password: viper.GetString("db.password"), + Host: viper.GetString("db.host"), + Database: viper.GetString("db.database"), + Port: viper.GetInt("db.port"), + } + + client, err := data.NewDataClient(connection) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not establish database connection", + "error": err.Error(), + }) + } + + programmes := []models.Programme{ + { + ID: "foo", + Titre: "Foo", + }, + { + ID: "bar", + Titre: "Bar", + }, + } + + newProgrammes, err := client.InsertProgrammes(programmes) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not insert programmes", + "error": err.Error(), + }) + } + + membres := []models.Membre{ + { + ID: "1327163", + PreferedName: "victor", + ProgrammeID: "foo", + }, + { + ID: "0000000", + PreferedName: "test user", + ProgrammeID: "bar", + }, + } + + newMembres, err := client.InsertMembres(membres) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not insert membres", + "error": err.Error(), + }) + } + + return c.JSON(http.StatusOK, map[string]interface{}{ + "message": "Insert successful", + "data": map[string]interface{}{ + "membres": newMembres, + "programmes": newProgrammes, + }, + }) +} +*/ diff --git a/v4/handlers/seed.go b/v4/handlers/seed.go new file mode 100644 index 0000000..91d5037 --- /dev/null +++ b/v4/handlers/seed.go @@ -0,0 +1,42 @@ +package handlers + +import ( + "net/http" + + "git.agecem.com/agecem/bottin/v4/data" + "github.com/labstack/echo/v4" + "github.com/spf13/viper" +) + +func PostSeed(c echo.Context) error { + connection := data.PostgresConnection{ + User: viper.GetString("db.user"), + Password: viper.GetString("db.password"), + Host: viper.GetString("db.host"), + Database: viper.GetString("db.database"), + Port: viper.GetInt("db.port"), + } + + client, err := data.NewDataClient(connection) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not establish database connection", + "error": err.Error(), + }) + } + + rows, err := client.Seed() + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Seed failed", + "error": err.Error(), + }) + } + + return c.JSON(http.StatusOK, map[string]interface{}{ + "message": "Seed successful", + "data": map[string]interface{}{ + "rows": rows, + }, + }) +} diff --git a/v4/handlers/v4.go b/v4/handlers/v4.go new file mode 100644 index 0000000..facbdde --- /dev/null +++ b/v4/handlers/v4.go @@ -0,0 +1,13 @@ +package handlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +func GetV4(c echo.Context) error { + return c.JSON(http.StatusOK, map[string]string{ + "message": "Bottin API v4 is ready", + }) +} diff --git a/v4/main.go b/v4/main.go new file mode 100644 index 0000000..6ceb837 --- /dev/null +++ b/v4/main.go @@ -0,0 +1,7 @@ +package main + +import "git.agecem.com/agecem/bottin/v4/cmd" + +func main() { + cmd.Execute() +} diff --git a/v4/models/models.go b/v4/models/models.go new file mode 100644 index 0000000..e781aff --- /dev/null +++ b/v4/models/models.go @@ -0,0 +1,33 @@ +package models + +const Schema = ` +CREATE TABLE programmes ( + id TEXT PRIMARY KEY, + titre TEXT +); + +CREATE TABLE membres ( + id VARCHAR(7) PRIMARY KEY, + last_name TEXT, + first_name TEXT, + prefered_name TEXT, + programme_id TEXT REFERENCES programmes(id) +); +` + +type Programme struct { + ID string `db:"id"` + Titre string `db:"titre"` +} + +type Membre struct { + ID string `db:"id"` + LastName string `db:"last_name"` + FirstName string `db:"first_name"` + PreferedName string `db:"prefered_name"` + ProgrammeID string `db:"programme_id"` +} + +type Entry interface { + Programme | Membre +} diff --git a/v4/web/embed.go b/v4/web/embed.go new file mode 100644 index 0000000..cae74d2 --- /dev/null +++ b/v4/web/embed.go @@ -0,0 +1,10 @@ +package web + +import "embed" + +//go:embed templates +var templates embed.FS + +func GetTemplates() embed.FS { + return templates +} diff --git a/v4/web/templates/index.html b/v4/web/templates/index.html new file mode 100644 index 0000000..7df8015 --- /dev/null +++ b/v4/web/templates/index.html @@ -0,0 +1 @@ +Hello world from bottin From cb51ada4b6dec1d07abe4b228825e5e5ed250d12 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 03:23:07 -0400 Subject: [PATCH 02/16] Ajouter upload par json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fonctionne avec json dans request body ou en fichier formatté json Cleanup commentaires Ajouter vérification d'insertion vide --- v4/handlers/insert.go | 37 ++++++++++--------------------------- v4/models/models.go | 14 +++++++------- 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/v4/handlers/insert.go b/v4/handlers/insert.go index dda92ee..4d07443 100644 --- a/v4/handlers/insert.go +++ b/v4/handlers/insert.go @@ -26,36 +26,20 @@ func PostMembres(c echo.Context) error { }) } - programmes := []models.Programme{ - { - ID: "foo", - Titre: "Foo", - }, - { - ID: "bar", - Titre: "Bar", - }, - } + var membres []models.Membre - newProgrammes, err := client.InsertProgrammes(programmes) - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{ - "message": "Could not insert programmes", + if err := c.Bind(&membres); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{ + "message": "Could not bind membres", "error": err.Error(), }) } - membres := []models.Membre{ - { - ID: "1327163", - PreferedName: "victor", - ProgrammeID: "foo", - }, - { - ID: "0000000", - PreferedName: "test user", - ProgrammeID: "bar", - }, + if len(membres) == 0 { + return c.JSON(http.StatusBadRequest, map[string]string{ + "message": "Nothing to do", + "error": "No valid membres to insert were found", + }) } newMembres, err := client.InsertMembres(membres) @@ -69,8 +53,7 @@ func PostMembres(c echo.Context) error { return c.JSON(http.StatusOK, map[string]interface{}{ "message": "Insert successful", "data": map[string]interface{}{ - "membres": newMembres, - "programmes": newProgrammes, + "membres": newMembres, }, }) } diff --git a/v4/models/models.go b/v4/models/models.go index e781aff..2c29a82 100644 --- a/v4/models/models.go +++ b/v4/models/models.go @@ -16,16 +16,16 @@ CREATE TABLE membres ( ` type Programme struct { - ID string `db:"id"` - Titre string `db:"titre"` + ID string `db:"id" json:"programme_id"` + Titre string `db:"titre" json:"titre"` } type Membre struct { - ID string `db:"id"` - LastName string `db:"last_name"` - FirstName string `db:"first_name"` - PreferedName string `db:"prefered_name"` - ProgrammeID string `db:"programme_id"` + ID string `db:"id" json:"membre_id"` + LastName string `db:"last_name" json:"last_name"` + FirstName string `db:"first_name" json:"first_name"` + PreferedName string `db:"prefered_name" json:"prefered_name"` + ProgrammeID string `db:"programme_id" json:"programme_id"` } type Entry interface { From f1a4d190dfc1d4935e32fa22e52bd2701dbec22e Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 03:44:41 -0400 Subject: [PATCH 03/16] Ajouter insertion de programmes --- v4/cmd/server.go | 2 ++ v4/handlers/insert.go | 49 +++++++++++++++++++++++++++++++++++++++++++ v4/models/models.go | 2 +- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/v4/cmd/server.go b/v4/cmd/server.go index a2c8799..e2c4c48 100644 --- a/v4/cmd/server.go +++ b/v4/cmd/server.go @@ -45,6 +45,8 @@ var serverCmd = &cobra.Command{ e.GET("/v4/membres/:membre_id/", handlers.ReadMembre) e.POST("/v4/membres/", handlers.PostMembres) + e.POST("/v4/programmes/", handlers.PostProgrammes) + // Execution connection := data.PostgresConnection{ diff --git a/v4/handlers/insert.go b/v4/handlers/insert.go index 4d07443..f534d9c 100644 --- a/v4/handlers/insert.go +++ b/v4/handlers/insert.go @@ -57,3 +57,52 @@ func PostMembres(c echo.Context) error { }, }) } + +func PostProgrammes(c echo.Context) error { + connection := data.PostgresConnection{ + User: viper.GetString("db.user"), + Password: viper.GetString("db.password"), + Host: viper.GetString("db.host"), + Database: viper.GetString("db.database"), + Port: viper.GetInt("db.port"), + } + + client, err := data.NewDataClient(connection) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not establish database connection", + "error": err.Error(), + }) + } + + var programmes []models.Programme + + if err := c.Bind(&programmes); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{ + "message": "Could not bind programmes", + "error": err.Error(), + }) + } + + if len(programmes) == 0 { + return c.JSON(http.StatusBadRequest, map[string]string{ + "message": "Nothing to do", + "error": "No valid programmes to insert were found", + }) + } + + newProgrammes, err := client.InsertProgrammes(programmes) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not insert programmes", + "error": err.Error(), + }) + } + + return c.JSON(http.StatusOK, map[string]interface{}{ + "message": "Insert successful", + "data": map[string]interface{}{ + "programmes": newProgrammes, + }, + }) +} diff --git a/v4/models/models.go b/v4/models/models.go index 2c29a82..c3f4af8 100644 --- a/v4/models/models.go +++ b/v4/models/models.go @@ -17,7 +17,7 @@ CREATE TABLE membres ( type Programme struct { ID string `db:"id" json:"programme_id"` - Titre string `db:"titre" json:"titre"` + Titre string `db:"titre" json:"nom_programme"` } type Membre struct { From b9044fa08d23da86c6f426f50ac6a1df73ea0852 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 04:31:29 -0400 Subject: [PATCH 04/16] Ajouter update de membre.prefered_name --- v4/cmd/server.go | 1 + v4/data/data.go | 14 ++++++++++ v4/handlers/insert.go | 10 +++----- v4/handlers/update.go | 60 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 v4/handlers/update.go diff --git a/v4/cmd/server.go b/v4/cmd/server.go index e2c4c48..58a0912 100644 --- a/v4/cmd/server.go +++ b/v4/cmd/server.go @@ -44,6 +44,7 @@ var serverCmd = &cobra.Command{ e.GET("/v4/membres/:membre_id/", handlers.ReadMembre) e.POST("/v4/membres/", handlers.PostMembres) + e.PUT("/v4/membres/:membre_id/prefered_name/", handlers.PutMembrePreferedName) e.POST("/v4/programmes/", handlers.PostProgrammes) diff --git a/v4/data/data.go b/v4/data/data.go index 4817e7a..db8addb 100644 --- a/v4/data/data.go +++ b/v4/data/data.go @@ -131,6 +131,20 @@ func (d *DataClient) GetMembre(membreID string) (models.Membre, error) { return membre, nil } +func (d *DataClient) UpdateMembreName(membreID, newName string) (int64, error) { + result, err := d.DB.Exec("UPDATE membres SET prefered_name = $1 WHERE id = $2;", newName, membreID) + if err != nil { + return 0, err + } + + rows, err := result.RowsAffected() + if err != nil { + return rows, err + } + + return rows, nil +} + /* func (d *DataClient) Insert(assets []models.Asset) (id int64, err error) { // Check for minimal required info diff --git a/v4/handlers/insert.go b/v4/handlers/insert.go index f534d9c..629d6e7 100644 --- a/v4/handlers/insert.go +++ b/v4/handlers/insert.go @@ -36,9 +36,8 @@ func PostMembres(c echo.Context) error { } if len(membres) == 0 { - return c.JSON(http.StatusBadRequest, map[string]string{ + return c.JSON(http.StatusOK, map[string]string{ "message": "Nothing to do", - "error": "No valid membres to insert were found", }) } @@ -50,7 +49,7 @@ func PostMembres(c echo.Context) error { }) } - return c.JSON(http.StatusOK, map[string]interface{}{ + return c.JSON(http.StatusCreated, map[string]interface{}{ "message": "Insert successful", "data": map[string]interface{}{ "membres": newMembres, @@ -85,9 +84,8 @@ func PostProgrammes(c echo.Context) error { } if len(programmes) == 0 { - return c.JSON(http.StatusBadRequest, map[string]string{ + return c.JSON(http.StatusOK, map[string]string{ "message": "Nothing to do", - "error": "No valid programmes to insert were found", }) } @@ -99,7 +97,7 @@ func PostProgrammes(c echo.Context) error { }) } - return c.JSON(http.StatusOK, map[string]interface{}{ + return c.JSON(http.StatusCreated, map[string]interface{}{ "message": "Insert successful", "data": map[string]interface{}{ "programmes": newProgrammes, diff --git a/v4/handlers/update.go b/v4/handlers/update.go new file mode 100644 index 0000000..8a8cf0a --- /dev/null +++ b/v4/handlers/update.go @@ -0,0 +1,60 @@ +package handlers + +import ( + "net/http" + + "git.agecem.com/agecem/bottin/v4/data" + "github.com/labstack/echo/v4" + "github.com/spf13/viper" +) + +func PutMembrePreferedName(c echo.Context) error { + connection := data.PostgresConnection{ + User: viper.GetString("db.user"), + Password: viper.GetString("db.password"), + Host: viper.GetString("db.host"), + Database: viper.GetString("db.database"), + Port: viper.GetInt("db.port"), + } + + client, err := data.NewDataClient(connection) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not establish database connection", + "error": err.Error(), + }) + } + + membreID := c.Param("membre_id") + + var newName string + + err = c.Bind(&newName) + if err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{ + "message": "Could not bind newName", + "error": err.Error(), + }) + } + + rows, err := client.UpdateMembreName(membreID, newName) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Could not update membre name", + "error": err.Error(), + }) + } + + if rows == 0 { + return c.JSON(http.StatusBadRequest, map[string]string{ + "message": "No update was done, probably no membre by that id", + }) + } + + return c.JSON(http.StatusOK, map[string]interface{}{ + "message": "Update successful", + "data": map[string]interface{}{ + "rows": rows, + }, + }) +} From 1a8fce6361456e26bfbb0496428c7355bad2e928 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 04:35:00 -0400 Subject: [PATCH 05/16] Ignore fichiers .env --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 5808d0c..ab3b38e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +# env +.env From 9e7a8b3e46249ff4e3805ad4ea0d8e0485d7b5f8 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 04:40:13 -0400 Subject: [PATCH 06/16] =?UTF-8?q?Trier=20d=C3=A9clarations=20de=20routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- v4/cmd/server.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/v4/cmd/server.go b/v4/cmd/server.go index 58a0912..28ba0c4 100644 --- a/v4/cmd/server.go +++ b/v4/cmd/server.go @@ -40,14 +40,16 @@ var serverCmd = &cobra.Command{ e.GET("/v4/", handlers.GetV4) - e.POST("/v4/seed/", handlers.PostSeed) + e.POST("/v4/membres/", handlers.PostMembres) e.GET("/v4/membres/:membre_id/", handlers.ReadMembre) - e.POST("/v4/membres/", handlers.PostMembres) + e.PUT("/v4/membres/:membre_id/prefered_name/", handlers.PutMembrePreferedName) e.POST("/v4/programmes/", handlers.PostProgrammes) + e.POST("/v4/seed/", handlers.PostSeed) + // Execution connection := data.PostgresConnection{ From ef4e9f70503839bf2fb55d7c4c191bc6a218f2d0 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 04:44:06 -0400 Subject: [PATCH 07/16] Set sensible flags pour server --- v4/cmd/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/v4/cmd/server.go b/v4/cmd/server.go index 28ba0c4..411eb13 100644 --- a/v4/cmd/server.go +++ b/v4/cmd/server.go @@ -94,11 +94,11 @@ func init() { viper.BindPFlag("db.database", serverCmd.Flags().Lookup("db-database")) // db.host - serverCmd.Flags().String("db-host", "", "Postgres host (config:'db.host')") + serverCmd.Flags().String("db-host", "postgres", "Postgres host (config:'db.host')") viper.BindPFlag("db.host", serverCmd.Flags().Lookup("db-host")) // db.password - serverCmd.Flags().String("db-password", "", "Postgres password (config:'db.password')") + serverCmd.Flags().String("db-password", "bottin", "Postgres password (config:'db.password')") viper.BindPFlag("db.password", serverCmd.Flags().Lookup("db-password")) // db.port @@ -106,6 +106,6 @@ func init() { viper.BindPFlag("db.port", serverCmd.Flags().Lookup("db-port")) // db.user - serverCmd.Flags().String("db-user", "", "Postgres user (config:'db.user')") + serverCmd.Flags().String("db-user", "bottin", "Postgres user (config:'db.user')") viper.BindPFlag("db.user", serverCmd.Flags().Lookup("db-user")) } From 3e37e8ffef67f50817cc1788b627a2fce9a82653 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 05:03:46 -0400 Subject: [PATCH 08/16] =?UTF-8?q?Am=C3=A9liorer=20insert=20error=20managem?= =?UTF-8?q?ent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- v4/data/data.go | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/v4/data/data.go b/v4/data/data.go index db8addb..4572dea 100644 --- a/v4/data/data.go +++ b/v4/data/data.go @@ -1,6 +1,7 @@ package data import ( + "errors" "fmt" "git.agecem.com/agecem/bottin/v4/models" @@ -68,12 +69,23 @@ func (d *DataClient) InsertMembres(membres []models.Membre) (int64, error) { } for _, membre := range membres { - _, err := tx.NamedExec("INSERT INTO membres (id, last_name, first_name, prefered_name, programme_id) VALUES (:id, :last_name, :first_name, :prefered_name, :programme_id);", &membre) + if membre.ID == "" { + tx.Rollback() + return 0, errors.New("Cannot insert membre with no membre_id") + } + result, err := tx.NamedExec("INSERT INTO membres (id, last_name, first_name, prefered_name, programme_id) VALUES (:id, :last_name, :first_name, :prefered_name, :programme_id);", &membre) if err != nil { tx.Rollback() - return rowsInserted, err + return 0, err } - rowsInserted++ + + rows, err := result.RowsAffected() + if err != nil { + tx.Rollback() + return 0, err + } + + rowsInserted += rows } err = tx.Commit() @@ -93,12 +105,24 @@ func (d *DataClient) InsertProgrammes(programmes []models.Programme) (int64, err } for _, programme := range programmes { - _, err := tx.NamedExec("INSERT INTO programmes (id, titre) VALUES (:id, :titre);", &programme) + if programme.ID == "" { + tx.Rollback() + return 0, errors.New("Cannot insert programme with no programme_id") + } + + result, err := tx.NamedExec("INSERT INTO programmes (id, titre) VALUES (:id, :titre);", &programme) if err != nil { tx.Rollback() - return rowsInserted, err + return 0, err } - rowsInserted++ + + rows, err := result.RowsAffected() + if err != nil { + tx.Rollback() + return 0, err + } + + rowsInserted += rows } err = tx.Commit() From 3aa7faa2f607fd59b7de7690b6b42c2ce32d239e Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 19:22:46 -0400 Subject: [PATCH 09/16] [WIP] Add web client Ignore .swp files Rename serverCmd to apiCmd (the web client is technically a server too) Add webCmd for html routes hosting Add embedding and templating for web client Add webhandlers Fix some variables not being filled automatically by viper Change flags and reorganize config structure --- .gitignore | 3 + v4/cmd/{server.go => api.go} | 60 ++++++++++-------- v4/cmd/web.go | 111 ++++++++++++++++++++++++++++++++- v4/docker-compose.yaml | 10 ++- v4/web/embed.go | 6 +- v4/web/templates/index.html | 32 +++++++++- v4/web/webhandlers/handlers.go | 11 ++++ 7 files changed, 201 insertions(+), 32 deletions(-) rename v4/cmd/{server.go => api.go} (59%) create mode 100644 v4/web/webhandlers/handlers.go diff --git a/.gitignore b/.gitignore index ab3b38e..e2f7237 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ # env .env + +# .swp +*.swp diff --git a/v4/cmd/server.go b/v4/cmd/api.go similarity index 59% rename from v4/cmd/server.go rename to v4/cmd/api.go index 411eb13..a800734 100644 --- a/v4/cmd/server.go +++ b/v4/cmd/api.go @@ -18,11 +18,22 @@ var ( apiKey string ) -// serverCmd represents the server command -var serverCmd = &cobra.Command{ - Use: "server", +// apiCmd represents the api command +var apiCmd = &cobra.Command{ + Use: "api", Short: "Démarrer le serveur API", + Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { + apiKey = viper.GetString("api.key") + apiPort = viper.GetInt("api.port") + + connection := data.PostgresConnection{ + User: viper.GetString("db.user"), + Password: viper.GetString("db.password"), + Host: viper.GetString("db.host"), + Database: viper.GetString("db.database"), + Port: viper.GetInt("db.port"), + } e := echo.New() @@ -52,14 +63,6 @@ var serverCmd = &cobra.Command{ // Execution - connection := data.PostgresConnection{ - User: viper.GetString("db.user"), - Password: viper.GetString("db.password"), - Host: viper.GetString("db.host"), - Database: viper.GetString("db.database"), - Port: viper.GetInt("db.port"), - } - client, err := data.NewDataClient(connection) if err != nil { log.Fatalf("Could not establish database connection.\n Error: %s\n", err) @@ -72,40 +75,45 @@ var serverCmd = &cobra.Command{ client.DB.Close() + log.Println("apiPort: ", apiPort) + log.Println("apiKey: ", apiKey) + e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", apiPort))) }, } func init() { - rootCmd.AddCommand(serverCmd) + rootCmd.AddCommand(apiCmd) // api.key - serverCmd.Flags().StringVar( - &apiKey, "api-key", "bottin", + apiCmd.Flags().String( + "api-key", "bottin", "API server key. Leave empty for no key auth. (config: 'api.key')") + viper.BindPFlag("api.key", apiCmd.Flags().Lookup("api-key")) // api.port - serverCmd.Flags().IntVar( - &apiPort, "api-port", 1312, + apiCmd.Flags().Int( + "api-port", 1312, "API server port (config:'api.port')") + viper.BindPFlag("api.port", apiCmd.Flags().Lookup("api-port")) // db.database - serverCmd.Flags().String("db-database", "bottin", "Postgres database (config:'db.database')") - viper.BindPFlag("db.database", serverCmd.Flags().Lookup("db-database")) + apiCmd.Flags().String("db-database", "bottin", "Postgres database (config:'db.database')") + viper.BindPFlag("db.database", apiCmd.Flags().Lookup("db-database")) // db.host - serverCmd.Flags().String("db-host", "postgres", "Postgres host (config:'db.host')") - viper.BindPFlag("db.host", serverCmd.Flags().Lookup("db-host")) + apiCmd.Flags().String("db-host", "db", "Postgres host (config:'db.host')") + viper.BindPFlag("db.host", apiCmd.Flags().Lookup("db-host")) // db.password - serverCmd.Flags().String("db-password", "bottin", "Postgres password (config:'db.password')") - viper.BindPFlag("db.password", serverCmd.Flags().Lookup("db-password")) + apiCmd.Flags().String("db-password", "bottin", "Postgres password (config:'db.password')") + viper.BindPFlag("db.password", apiCmd.Flags().Lookup("db-password")) // db.port - serverCmd.Flags().Int("db-port", 5432, "Postgres port (config:'db.port')") - viper.BindPFlag("db.port", serverCmd.Flags().Lookup("db-port")) + apiCmd.Flags().Int("db-port", 5432, "Postgres port (config:'db.port')") + viper.BindPFlag("db.port", apiCmd.Flags().Lookup("db-port")) // db.user - serverCmd.Flags().String("db-user", "bottin", "Postgres user (config:'db.user')") - viper.BindPFlag("db.user", serverCmd.Flags().Lookup("db-user")) + apiCmd.Flags().String("db-user", "bottin", "Postgres user (config:'db.user')") + viper.BindPFlag("db.user", apiCmd.Flags().Lookup("db-user")) } diff --git a/v4/cmd/web.go b/v4/cmd/web.go index ab10ba0..6e812f7 100644 --- a/v4/cmd/web.go +++ b/v4/cmd/web.go @@ -1,20 +1,129 @@ package cmd import ( + "crypto/subtle" + "embed" "fmt" + "html/template" + "io" + "log" + "git.agecem.com/agecem/bottin/v4/web" + "git.agecem.com/agecem/bottin/v4/web/webhandlers" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" "github.com/spf13/cobra" + "github.com/spf13/viper" ) +var ( + webUser string + webPassword string + webPort int + webApiHost string + webApiKey string + webApiPort int +) + +var templatesFS embed.FS + +type Template struct { + templates *template.Template +} + +func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { + return t.templates.ExecuteTemplate(w, name, data) +} + // webCmd represents the web command var webCmd = &cobra.Command{ Use: "web", Short: "Démarrer le client web", + Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - fmt.Println("web called") + webApiHost = viper.GetString("web.api.host") + webApiKey = viper.GetString("web.api.key") + webApiPort = viper.GetInt("web.api.port") + webPassword = viper.GetString("web.password") + webPort = viper.GetInt("web.port") + webUser = viper.GetString("web.user") + + e := echo.New() + + // Middlewares + + e.Pre(middleware.AddTrailingSlash()) + + e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) { + usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(webUser)) == 1 + passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(webPassword)) == 1 + return usersMatch && passwordsMatch, nil + })) + + // Template + + t := &Template{ + templates: template.Must(template.ParseFS(templatesFS, "templates/*.html")), + } + + e.Renderer = t + + // Routes + + e.GET("/", webhandlers.GetIndex) + + // Execution + + fmt.Println("webPort: ", webPort) + fmt.Println("web.port: ", viper.GetInt("web.port")) + webPortFlag, err := cmd.Flags().GetInt("web-port") + if err != nil { + log.Fatal(err) + } + fmt.Println("web-port: ", webPortFlag) + + e.Logger.Fatal(e.Start( + fmt.Sprintf(":%d", webPort))) }, } func init() { rootCmd.AddCommand(webCmd) + templatesFS = web.GetTemplates() + + // web.api.host + webCmd.Flags().String( + "web-api-host", "api", + "Remote API server host (config:'web.api.host')") + viper.BindPFlag("web.api.host", webCmd.Flags().Lookup("web-api-host")) + + // web.api.key + webCmd.Flags().String( + "web-api-key", "bottin", + "Remote API server key (config:'web.api.key')") + viper.BindPFlag("web.api.key", webCmd.Flags().Lookup("web-api-key")) + + // web.api.port + webCmd.Flags().Int( + "web-api-port", 1312, + "Remote API server port (config:'web.api.port')") + viper.BindPFlag("web.api.port", webCmd.Flags().Lookup("web-api-port")) + + // web.password + webCmd.Flags().String( + "web-password", "bottin", + "Web client password (config:'web.password')") + viper.BindPFlag("web.password", webCmd.Flags().Lookup("web-password")) + + // web.port + webCmd.Flags().Int( + "web-port", 2312, + "Web client port (config:'web.port')") + viper.BindPFlag("web.port", webCmd.Flags().Lookup("web-port")) + + // web.user + webCmd.Flags().String( + "web-user", "bottin", + "Web client user (config:'web.user')") + viper.BindPFlag("web.user", webCmd.Flags().Lookup("web-user")) } diff --git a/v4/docker-compose.yaml b/v4/docker-compose.yaml index bfb46f1..53b726c 100644 --- a/v4/docker-compose.yaml +++ b/v4/docker-compose.yaml @@ -1,6 +1,6 @@ services: - postgres: + db: image: postgres:latest environment: POSTGRES_DATABASE: "${BOTTIN_POSTGRES_DATABASE}" @@ -11,11 +11,19 @@ services: volumes: - 'pgdata:/var/lib/postgresql/data' + #api: + # depends_on: db + + #web: + # depends_on: api + adminer: image: adminer restart: always ports: - 8088:8080 + depends_on: + - db volumes: pgdata: diff --git a/v4/web/embed.go b/v4/web/embed.go index cae74d2..465e5ec 100644 --- a/v4/web/embed.go +++ b/v4/web/embed.go @@ -2,9 +2,9 @@ package web import "embed" -//go:embed templates -var templates embed.FS +//go:embed templates/* +var templatesFS embed.FS func GetTemplates() embed.FS { - return templates + return templatesFS } diff --git a/v4/web/templates/index.html b/v4/web/templates/index.html index 7df8015..e318199 100644 --- a/v4/web/templates/index.html +++ b/v4/web/templates/index.html @@ -1 +1,31 @@ -Hello world from bottin +{{ define "index-html" }} + + + + + + AGECEM | Bottin + + + + +

+ Bottin des membres de l'AGECEM +

+ +

+ Scannez la carte étudiante d'unE membre
+ -ou-
+ Entrez manuellement le code à 7 chiffres +

+ +
+ + +
+ + + +{{ end }} diff --git a/v4/web/webhandlers/handlers.go b/v4/web/webhandlers/handlers.go new file mode 100644 index 0000000..8376dd8 --- /dev/null +++ b/v4/web/webhandlers/handlers.go @@ -0,0 +1,11 @@ +package webhandlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +func GetIndex(c echo.Context) error { + return c.Render(http.StatusOK, "index-html", nil) +} From 719c92a6521862b32932787148b5649722a28a80 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 19:26:37 -0400 Subject: [PATCH 10/16] Remove debug prints --- v4/cmd/api.go | 3 --- v4/cmd/web.go | 9 --------- 2 files changed, 12 deletions(-) diff --git a/v4/cmd/api.go b/v4/cmd/api.go index a800734..e44632c 100644 --- a/v4/cmd/api.go +++ b/v4/cmd/api.go @@ -75,9 +75,6 @@ var apiCmd = &cobra.Command{ client.DB.Close() - log.Println("apiPort: ", apiPort) - log.Println("apiKey: ", apiKey) - e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", apiPort))) }, } diff --git a/v4/cmd/web.go b/v4/cmd/web.go index 6e812f7..34f6bc0 100644 --- a/v4/cmd/web.go +++ b/v4/cmd/web.go @@ -6,7 +6,6 @@ import ( "fmt" "html/template" "io" - "log" "git.agecem.com/agecem/bottin/v4/web" "git.agecem.com/agecem/bottin/v4/web/webhandlers" @@ -74,14 +73,6 @@ var webCmd = &cobra.Command{ // Execution - fmt.Println("webPort: ", webPort) - fmt.Println("web.port: ", viper.GetInt("web.port")) - webPortFlag, err := cmd.Flags().GetInt("web-port") - if err != nil { - log.Fatal(err) - } - fmt.Println("web-port: ", webPortFlag) - e.Logger.Fatal(e.Start( fmt.Sprintf(":%d", webPort))) }, From e585b65565d82248edc520374de81cd88b0b2b40 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 22:07:53 -0400 Subject: [PATCH 11/16] Permettre au client web de rechercher unE membre --- v4/cmd/web.go | 7 ++ v4/data/apiclient.go | 116 +++++++++++++++++++++++++++++++++ v4/web/templates/index.html | 17 +++-- v4/web/webhandlers/handlers.go | 53 +++++++++++++++ 4 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 v4/data/apiclient.go diff --git a/v4/cmd/web.go b/v4/cmd/web.go index 34f6bc0..c108012 100644 --- a/v4/cmd/web.go +++ b/v4/cmd/web.go @@ -70,6 +70,7 @@ var webCmd = &cobra.Command{ // Routes e.GET("/", webhandlers.GetIndex) + e.GET("/membre/", webhandlers.GetMembre) // Execution @@ -94,6 +95,12 @@ func init() { "Remote API server key (config:'web.api.key')") viper.BindPFlag("web.api.key", webCmd.Flags().Lookup("web-api-key")) + // web.api.protocol + webCmd.Flags().String( + "web-api-protocol", "http", + "Remote API server protocol (config:'web.api.protocol')") + viper.BindPFlag("web.api.protocol", webCmd.Flags().Lookup("web-api-protocol")) + // web.api.port webCmd.Flags().Int( "web-api-port", 1312, diff --git a/v4/data/apiclient.go b/v4/data/apiclient.go new file mode 100644 index 0000000..c805463 --- /dev/null +++ b/v4/data/apiclient.go @@ -0,0 +1,116 @@ +package data + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + + "git.agecem.com/agecem/bottin/v4/models" +) + +type ApiClient struct { + Key string + Host string + Port int + Protocol string +} + +func NewApiClient(key, host, protocol string, port int) *ApiClient { + return &ApiClient{ + Key: key, + Host: host, + Port: port, + Protocol: protocol, + } +} + +func (a *ApiClient) Call(method, route string, requestBody io.Reader, useKey bool) (*http.Response, error) { + var response *http.Response + + endpoint := fmt.Sprintf("%s://%s:%d%s", + a.Protocol, a.Host, a.Port, route, + ) + + /* + //TODO + log.Println("endpoint: ", endpoint) + */ + + // Create client + client := &http.Client{} + + // Create request + request, err := http.NewRequest(method, endpoint, requestBody) + if err != nil { + return response, err + } + + if useKey { + if a.Key == "" { + return response, fmt.Errorf("Call to API required a key but none was provided. See --help for instructions on providing an API key.") + } + + request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", a.Key)) + } + + if requestBody != nil { + request.Header.Add("Content-Type", "application/json") + } + + // Fetch Request + response, err = client.Do(request) + if err != nil { + return response, err + } + + return response, nil +} + +func (a *ApiClient) GetMembre(membreID string) (models.Membre, error) { + var getMembreResponse struct { + Message string `json:"message"` + Data struct { + Membre models.Membre `json:"membre"` + } `json:"data"` + } + + if membreID == "" { + return getMembreResponse.Data.Membre, errors.New("Veuillez fournir un numéro étudiant à rechercher") + } + + //TODO + /* + log.Println("ApiClient.GetMembre received membreID: ", membreID) + */ + + response, err := a.Call(http.MethodGet, fmt.Sprintf("/v4/membres/%s", membreID), nil, true) + if err != nil { + return getMembreResponse.Data.Membre, err + } + + defer response.Body.Close() + + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return getMembreResponse.Data.Membre, err + } + + if err := json.Unmarshal(body, &getMembreResponse); err != nil { + return getMembreResponse.Data.Membre, err + } + + /* + if getMembreResponse.Message != "Read successful" { + return getMembreResponse.Data.Membre, errors.New(getMembreResponse.Message) + } + */ + + if getMembreResponse.Data.Membre == *new(models.Membre) { + return getMembreResponse.Data.Membre, fmt.Errorf("Ce numéro étudiant ne correspond à aucunE membre") + } + + return getMembreResponse.Data.Membre, nil +} diff --git a/v4/web/templates/index.html b/v4/web/templates/index.html index e318199..b5a94a8 100644 --- a/v4/web/templates/index.html +++ b/v4/web/templates/index.html @@ -19,12 +19,19 @@ Entrez manuellement le code à 7 chiffres

-
- - + +
    +
  • + + +
  • +
  • + +
  • +
+ +

{{ .Result }}

diff --git a/v4/web/webhandlers/handlers.go b/v4/web/webhandlers/handlers.go index 8376dd8..34724f9 100644 --- a/v4/web/webhandlers/handlers.go +++ b/v4/web/webhandlers/handlers.go @@ -1,11 +1,64 @@ package webhandlers import ( + "fmt" "net/http" + "git.agecem.com/agecem/bottin/v4/data" "github.com/labstack/echo/v4" + "github.com/spf13/viper" ) func GetIndex(c echo.Context) error { return c.Render(http.StatusOK, "index-html", nil) } + +func GetMembre(c echo.Context) error { + apiClientKey := viper.GetString("web.api.key") + apiClientHost := viper.GetString("web.api.host") + apiClientProtocol := viper.GetString("web.api.protocol") + apiClientPort := viper.GetInt("web.api.port") + + /* + log.Printf(` + apiClientKey: %s + apiClientHost: %s + apiClientProtocol: %s + apiClientPort: %d`, + apiClientKey, apiClientHost, apiClientProtocol, apiClientPort, + ) + */ + + apiClient := data.NewApiClient(apiClientKey, apiClientHost, apiClientProtocol, apiClientPort) + + membreID := c.QueryParam("membre_id") + + /* + // TODO + log.Printf("Requesting membreID: [%s]", membreID) + */ + + membre, err := apiClient.GetMembre(membreID) + if err != nil { + return c.Render(http.StatusBadRequest, "index-html", struct { + Result string + }{ + Result: fmt.Sprintln("👎", err.Error()), + }) + } + + membreResult := fmt.Sprintf(`👍 + Membre trouvéE: [%s]`, membre.ID) + + if membre.PreferedName != "" { + membreResult = fmt.Sprintf("%s -> %s", membreResult, membre.PreferedName) + } else { + membreResult = fmt.Sprintf("%s -> %s, %s", membreResult, membre.LastName, membre.FirstName) + } + + return c.Render(http.StatusOK, "index-html", struct { + Result string + }{ + Result: membreResult, + }) +} From 7aca281a3c8fdf08caeefd91aff81aadf52f5193 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 23:03:28 -0400 Subject: [PATCH 12/16] Ajouter multistage build docker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Valeurs par défaut devraient fonctionner pour évaluer l'application hors-production --- v4/Dockerfile | 35 +++++++++++++++++++++++++++++ v4/docker-compose.yaml | 51 +++++++++++++++++++++++++++++------------- 2 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 v4/Dockerfile diff --git a/v4/Dockerfile b/v4/Dockerfile new file mode 100644 index 0000000..26c5238 --- /dev/null +++ b/v4/Dockerfile @@ -0,0 +1,35 @@ +FROM golang:1.20.2 as build + +LABEL author="vlbeaudoin" + +WORKDIR /go/src/app + +COPY go.mod go.sum main.go ./ + +ADD cmd/ cmd/ +ADD data/ data/ +ADD handlers/ handlers/ +ADD models/ models/ +ADD web/ web/ + +RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o bottin . + +# Alpine + +FROM alpine:latest + +WORKDIR /app + +COPY --from=build /go/src/app/bottin /usr/bin/bottin + +CMD ["bottin", "--help"] + +# Debian + +#FROM debian:stable-20230502 + +#WORKDIR /app + +#COPY --from=build /go/src/app/haul /usr/bin/haul + +#CMD ["haul", "--help"] diff --git a/v4/docker-compose.yaml b/v4/docker-compose.yaml index 53b726c..09ab9be 100644 --- a/v4/docker-compose.yaml +++ b/v4/docker-compose.yaml @@ -1,29 +1,48 @@ services: db: - image: postgres:latest + image: 'docker.io/library/postgres:14.8' environment: POSTGRES_DATABASE: "${BOTTIN_POSTGRES_DATABASE}" POSTGRES_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}" POSTGRES_USER: "${BOTTIN_POSTGRES_USER}" - ports: - - '5432:5432' volumes: - - 'pgdata:/var/lib/postgresql/data' + - 'db-data:/var/lib/postgresql/data' + restart: 'unless-stopped' - #api: - # depends_on: db - - #web: - # depends_on: api - - adminer: - image: adminer - restart: always - ports: - - 8088:8080 + api: depends_on: - db + build: . + image: 'git.agecem.com/agecem/bottin/v4:latest' + ports: + - '1312:1312' + volumes: + - 'api-config:/etc/bottin' + restart: 'unless-stopped' + command: ['bottin', '--config', '/etc/bottin/api.yaml', 'api'] + + web: + depends_on: + - api + build: . + image: 'git.agecem.com/agecem/bottin/v4:latest' + ports: + - '2312:2312' + volumes: + - 'web-config:/etc/bottin' + restart: 'unless-stopped' + command: ['bottin', '--config', '/etc/bottin/api.yaml', 'web'] + +# adminer: +# image: adminer +# restart: always +# ports: +# - 8088:8080 +# depends_on: +# - db volumes: - pgdata: + db-data: + api-config: + web-config: From 641a144019430fc1581784ce7091a7b8b336cece Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 23:15:57 -0400 Subject: [PATCH 13/16] Ajouter autofocus sur text input --- v4/web/templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v4/web/templates/index.html b/v4/web/templates/index.html index b5a94a8..0bc6eeb 100644 --- a/v4/web/templates/index.html +++ b/v4/web/templates/index.html @@ -23,7 +23,7 @@
  • - +
  • From 97ee9458bb2f9541e855ae12c61625cfc20173f2 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 23:19:35 -0400 Subject: [PATCH 14/16] Fix typo --- v4/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v4/docker-compose.yaml b/v4/docker-compose.yaml index 09ab9be..3c4c744 100644 --- a/v4/docker-compose.yaml +++ b/v4/docker-compose.yaml @@ -32,7 +32,7 @@ services: volumes: - 'web-config:/etc/bottin' restart: 'unless-stopped' - command: ['bottin', '--config', '/etc/bottin/api.yaml', 'web'] + command: ['bottin', '--config', '/etc/bottin/web.yaml', 'web'] # adminer: # image: adminer From 801df2c522dae5a754cfe57ea07ab03244923cc7 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 23:20:50 -0400 Subject: [PATCH 15/16] Retirer commentaires --- v4/Dockerfile | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/v4/Dockerfile b/v4/Dockerfile index 26c5238..805727f 100644 --- a/v4/Dockerfile +++ b/v4/Dockerfile @@ -23,13 +23,3 @@ WORKDIR /app COPY --from=build /go/src/app/bottin /usr/bin/bottin CMD ["bottin", "--help"] - -# Debian - -#FROM debian:stable-20230502 - -#WORKDIR /app - -#COPY --from=build /go/src/app/haul /usr/bin/haul - -#CMD ["haul", "--help"] From 7887be18385a8de3b880b20bf138bdf669abbf78 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Thu, 25 May 2023 23:29:54 -0400 Subject: [PATCH 16/16] =?UTF-8?q?Mettre=20=C3=A0=20jour=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- v4/README.md | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/v4/README.md b/v4/README.md index 77ca460..c32ebe5 100644 --- a/v4/README.md +++ b/v4/README.md @@ -1,13 +1,24 @@ +# agecem/bottin/v4 + +Version 4 du bottin de la masse étudiante, en Go + +https://git.agecem.com/agecem/bottin + +## fonctionalités + +### Serveur API + +- Insertion de membre et programme +- Lecture de membre +- Modification du nom d'usage de membre + +### Client web + +- Lecture de membre par requête au serveur API + ## usage -### Base de données - -`docker-compose.yaml` est fourni pour facilité le développement avec une base de données postgres sur docker. - -En production, des efforts de high-availability devraient être mis. - - -Remplir .env avec les infos qui seront utilisées pour déployer le container: +Remplir .env avec les infos qui seront utilisées pour déployer le container (Remplacer `bottin` par quelque chose de plus sécuritaire) @@ -17,20 +28,22 @@ BOTTIN_POSTGRES_PASSWORD=bottin BOTTIN_POSTGRES_USER=bottin ``` -Déployer avec docker-compose: +Déployer avec docker-compose `$ docker-compose up -d` -### Configuration +Pour modifier la configuration du serveur API -Remplir le fichier de config `~/.bottin.yaml`. +`$ docker-compose exec -it api vi /etc/bottin/api.yaml` -## Build +*Y remplir au minimum le champs `api.key` (string)* -Build l'exécutable du serveur. (TODO) +Pour modifier la configuration du client web -### API +`$ docker-compose exec -it web vi /etc/bottin/web.yaml` -Démarrer le serveur web: +*Y remplir au minimum les champs `web.api.key` (string), `web.user` (string) et `web.password` (string)* -`$ bottin server` +Redémarrer les containers une fois la configuration modifiée + +`$ docker-compose down && docker-compose up -d`