From 96f8dfa35e20d63e1dfd879c17f2ca6e52a8d5cb Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Mon, 29 May 2023 17:58:23 -0400 Subject: [PATCH 1/9] Migrate to version 2 --- .env | 3 + .gitignore | 24 +++ Dockerfile | 26 ++- LICENSE | 21 --- Makefile | 11 -- README.md | 8 - cmd/api.go | 142 +++++++++++++++++ cmd/root.go | 58 +------ cmd/scan.go | 131 --------------- cmd/server.go | 259 ------------------------------ data/apiclient.go | 134 ++++++++++++++++ data/data.go | 310 +++++++++++++++++++++++++++++------- docker-compose.yaml | 48 ++++++ docker-compose.yml | 17 -- embed/embed.go | 10 -- embed/html/index.html | 25 --- examples/bottin-agenda.yaml | 61 ------- go.mod | 55 +++---- go.sum | 124 ++++++++------- handlers/v2.go | 37 +++++ models/models.go | 19 +++ 21 files changed, 777 insertions(+), 746 deletions(-) create mode 100644 .env create mode 100644 .gitignore delete mode 100644 Makefile create mode 100644 cmd/api.go delete mode 100644 cmd/scan.go delete mode 100644 cmd/server.go create mode 100644 data/apiclient.go create mode 100644 docker-compose.yaml delete mode 100644 docker-compose.yml delete mode 100644 embed/embed.go delete mode 100644 embed/html/index.html delete mode 100644 examples/bottin-agenda.yaml create mode 100644 handlers/v2.go create mode 100644 models/models.go diff --git a/.env b/.env new file mode 100644 index 0000000..d4ab811 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +BOTTINAGENDA_POSTGRES_DATABASE=bottin-agenda +BOTTINAGENDA_POSTGRES_PASSWORD=bottin-agenda +BOTTINAGENDA_POSTGRES_USER=bottin-agenda diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40de0a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# ---> Go +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.x86_64 + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + + +# env +#.env + +# .swp +*.swp diff --git a/Dockerfile b/Dockerfile index 701497e..485c5fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,25 @@ -FROM docker.io/library/golang:1.18 +FROM golang:1.20.2 as build -LABEL author="Victor Lacasse-Beaudoin " -LABEL repo="https://git.agecem.com/agecem/bottin-agenda" +LABEL author="vlbeaudoin" WORKDIR /go/src/app -COPY . . +COPY go.mod go.sum main.go ./ -ENV PATH=/go/src/app:$PATH +ADD cmd/ cmd/ +ADD data/ data/ +ADD handlers/ handlers/ +ADD models/ models/ +#ADD web/ web/ -RUN go get -d -v . && \ - go install -v . +RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o bottin-agenda . -CMD bottin-agenda -h +# Alpine + +FROM alpine:latest + +WORKDIR /app + +COPY --from=build /go/src/app/bottin-agenda /usr/bin/bottin-agenda + +CMD ["bottin-agenda", "--help"] diff --git a/LICENSE b/LICENSE index e6168c7..e69de29 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright © 2022 AGECEM & Victor Lacasse-Beaudoin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/Makefile b/Makefile deleted file mode 100644 index d3a9760..0000000 --- a/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -# SHELL = /bin/sh - -.DEFAULT_GOAL := help - -.PHONY: help -help: ## Show this help - @egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' - -.PHONY: build -build: ## Build une image latest selon ./Dockerfile - docker build -t localhost/agecem/bottin-agenda:latest . diff --git a/README.md b/README.md index 5686f2d..80284cd 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,6 @@ Application web de distribution d'agendas pour le Centre Multi-Services. -## Utilisation - -Tiens une liste de `data.Membre{}` qui ont reçuEs leur agenda. Utilise une serveur [agecem/bottin](https://git.agecem.com/agecem/bottin) - -L'application doit exposer un API specification sur `/v1` pour son utilisation, ainsi que des commandes CLI pour les manipulations de database. - ## Prérequis Nécessite une installation fonctionnelle et accessible de [agecem/bottin](https://git.agecem.com/agecem/bottin). - - diff --git a/cmd/api.go b/cmd/api.go new file mode 100644 index 0000000..e3b87ae --- /dev/null +++ b/cmd/api.go @@ -0,0 +1,142 @@ +package cmd + +import ( + "crypto/subtle" + "fmt" + "log" + + "git.agecem.com/agecem/bottin-agenda/data" + "git.agecem.com/agecem/bottin-agenda/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 +) + +// 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") + + /* + dbConnection := 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"), + } + */ + + bottinApiKey := viper.GetString("bottin.api.key") + bottinApiHost := viper.GetString("bottin.api.host") + bottinApiProtocol := viper.GetString("bottin.api.protocol") + bottinApiPort := viper.GetInt("bottin.api.port") + + bottinConnection := data.NewApiClient( + bottinApiKey, + bottinApiHost, + bottinApiProtocol, + bottinApiPort, + ) + + 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("/v2/", handlers.GetV2) + + // Check bottin is ready + + message, err := bottinConnection.GetV4() + if err != nil { + log.Fatal(err) + } + + //TODO + log.Println(message) + + // Execution + + e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", apiPort))) + }, +} + +func init() { + rootCmd.AddCommand(apiCmd) + + // api.key + apiCmd.Flags().String( + "api-key", "bottin-agenda", + "API server key. Leave empty for no key auth. (config: 'api.key')") + viper.BindPFlag("api.key", apiCmd.Flags().Lookup("api-key")) + + // api.port + apiCmd.Flags().Int( + "api-port", 1313, + "API server port (config:'api.port')") + viper.BindPFlag("api.port", apiCmd.Flags().Lookup("api-port")) + + // bottin.api.host + apiCmd.Flags().String( + "bottin-api-host", "api", + "Remote bottin API server host (config:'bottin.api.host')") + viper.BindPFlag("bottin.api.host", apiCmd.Flags().Lookup("bottin-api-host")) + + // bottin.api.key + apiCmd.Flags().String( + "bottin-api-key", "bottin", + "Remote bottin API server key (config:'bottin.api.key')") + viper.BindPFlag("bottin.api.key", apiCmd.Flags().Lookup("bottin-api-key")) + + // bottin.api.protocol + apiCmd.Flags().String( + "bottin-api-protocol", "http", + "Remote bottin API server protocol (config:'bottin.api.protocol')") + viper.BindPFlag("bottin.api.protocol", apiCmd.Flags().Lookup("bottin-api-protocol")) + + // bottin.api.port + apiCmd.Flags().Int( + "bottin-api-port", 1312, + "Remote bottin API server port (config:'bottin.api.port')") + viper.BindPFlag("bottin.api.port", apiCmd.Flags().Lookup("bottin-api-port")) + + // db.database + apiCmd.Flags().String("db-database", "bottin-agenda", "Postgres database (config:'db.database')") + viper.BindPFlag("db.database", apiCmd.Flags().Lookup("db-database")) + + // db.host + apiCmd.Flags().String("db-host", "db", "Postgres host (config:'db.host')") + viper.BindPFlag("db.host", apiCmd.Flags().Lookup("db-host")) + + // db.password + apiCmd.Flags().String("db-password", "bottin-agenda", "Postgres password (config:'db.password')") + viper.BindPFlag("db.password", apiCmd.Flags().Lookup("db-password")) + + // db.port + apiCmd.Flags().Int("db-port", 5432, "Postgres port (config:'db.port')") + viper.BindPFlag("db.port", apiCmd.Flags().Lookup("db-port")) + + // db.user + apiCmd.Flags().String("db-user", "bottin-agenda", "Postgres user (config:'db.user')") + viper.BindPFlag("db.user", apiCmd.Flags().Lookup("db-user")) +} diff --git a/cmd/root.go b/cmd/root.go index f699a63..7fa76ca 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,7 +1,7 @@ package cmd import ( - "log" + "fmt" "os" "github.com/spf13/cobra" @@ -13,10 +13,7 @@ var cfgFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "bottin-agenda", - Short: "Application de distribution d'agendas", - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, + Short: "Application de gestion de distribution d'agendas", } // Execute adds all child commands to the root command and sets flags appropriately. @@ -31,56 +28,7 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin-agenda.yaml)") - - // bottin.host - rootCmd.PersistentFlags().String( - "bottin-host", "", - "The host of the bottin instance (config: 'bottin.host')") - viper.BindPFlag( - "bottin.host", - rootCmd.PersistentFlags().Lookup("bottin-host")) - rootCmd.MarkPersistentFlagRequired("bottin.host") - - // bottin.protocol - rootCmd.PersistentFlags().String( - "bottin-protocol", "http", - "The protocol of the bottin instance (config: 'bottin.protocol')") - viper.BindPFlag( - "bottin.protocol", - rootCmd.PersistentFlags().Lookup("bottin-protocol")) - rootCmd.MarkPersistentFlagRequired("bottin.protocol") - - // bottin.port - rootCmd.PersistentFlags().Int( - "bottin-port", 1312, - "The port to the bottin instance (config: 'bottin.port')") - viper.BindPFlag( - "bottin.port", - rootCmd.PersistentFlags().Lookup("bottin-port")) - rootCmd.MarkPersistentFlagRequired("bottin.port") - - // bottin.username - rootCmd.PersistentFlags().String( - "bottin-username", "", - "The username to the bottin instance (config: 'bottin.username')") - viper.BindPFlag( - "bottin.username", - rootCmd.PersistentFlags().Lookup("bottin-username")) - rootCmd.MarkPersistentFlagRequired("bottin.username") - - // bottin.password - rootCmd.PersistentFlags().String( - "bottin-password", "", - "The password to the bottin instance (config: 'bottin.password')") - viper.BindPFlag( - "bottin.password", - rootCmd.PersistentFlags().Lookup("bottin-password")) - rootCmd.MarkPersistentFlagRequired("bottin.password") } // initConfig reads in config file and ENV variables if set. @@ -103,6 +51,6 @@ func initConfig() { // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { - log.Printf("Using config file: %s\n", viper.ConfigFileUsed()) + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } } diff --git a/cmd/scan.go b/cmd/scan.go deleted file mode 100644 index 8a15dbb..0000000 --- a/cmd/scan.go +++ /dev/null @@ -1,131 +0,0 @@ -package cmd - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/http" - "time" - - "git.agecem.com/agecem/bottin-agenda/data" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var ( - protocol, host, username, password string - port int - check bool -) - -// scanCmd represents the scan command -var scanCmd = &cobra.Command{ - Use: "scan", - Short: "Check a bottin instance for existing membre", - Run: func(cmd *cobra.Command, args []string) { - args_length := len(args) - - if args_length != 1 { - log.Fatalf("[e] Wrong number of arguments, got '%d' needs 1", args_length) - } else { - - // Search locally for corresponding Membre - membre, err := readMembre(args[0]) - if err != nil { - log.Printf("[e] %s", err) - } - - membre_exists := (membre.ID != 0) - - if membre_exists { - log.Fatal("[e] Membre is already in the bottin-agenda database") - } else { - log.Println("[i] Membre not already in bottin-agenda, confirming existence with bottin") - - // Search in bottin for corresponding Membre - membre_bottin, err := readMembreBottin(args[0]) - if err != nil { - log.Fatalf("[e] %s", err) - } - - membre_bottin_exists := (membre_bottin.ID != 0) - - if membre_bottin_exists { - - log.Println("[ok] Membre found in bottin") - - if !check { - log.Println("[i] Adding to bottin-agenda") - - membre_bottin.CreatedAt = time.Now() - membre_bottin.UpdatedAt = time.Now() - - // Add to bottin-agenda database - data.InsertMembre(&membre_bottin) - } else { - log.Fatal("[i] Skipping insert because of --check flag") - } - } else { - log.Fatal("[e] Membre does not exist in bottin database") - } - } - } - }, -} - -func init() { - rootCmd.AddCommand(scanCmd) - - // check - scanCmd.PersistentFlags().BoolVar( - &check, "check", false, - "Do not write anything to database.") -} - -func updateFlagsBottin() { - protocol = viper.GetString("bottin.protocol") - host = viper.GetString("bottin.host") - port = viper.GetInt("bottin.port") - username = viper.GetString("bottin.username") - password = viper.GetString("bottin.password") -} - -func readMembre(num_etud string) (data.Membre, error) { - data.OpenDatabase() - data.MigrateDatabase() - - membre, err := data.ReadMembre(num_etud) - if err != nil { - return membre, err - } - - return membre, nil -} - -func readMembreBottin(num_etud string) (data.Membre, error) { - updateFlagsBottin() - - membre := data.Membre{} - - request := fmt.Sprintf("%s://%s:%s@%s:%d/v1/membre/%s", protocol, username, password, host, port, num_etud) - - response, err := http.Get(request) - if err != nil { - return membre, err - } - - defer response.Body.Close() - - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return membre, err - } - - err = json.Unmarshal([]byte(body), &membre) - if err != nil { - return membre, err - } - - return membre, nil -} diff --git a/cmd/server.go b/cmd/server.go deleted file mode 100644 index 5c35b75..0000000 --- a/cmd/server.go +++ /dev/null @@ -1,259 +0,0 @@ -package cmd - -import ( - "crypto/subtle" - _ "embed" - "errors" - "fmt" - "net/http" - "time" - - "git.agecem.com/agecem/bottin-agenda/data" - "git.agecem.com/agecem/bottin-agenda/embed" - "github.com/labstack/echo/v4" - "github.com/labstack/echo/v4/middleware" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var ( - html string -) - -// serverCmd represents the server command -var serverCmd = &cobra.Command{ - Use: "server", - Short: "Run the bottin-agenda server", - Run: func(cmd *cobra.Command, args []string) { - data.OpenDatabase() - data.MigrateDatabase() - - runServer() - }, -} - -func init() { - declareFlags() - rootCmd.AddCommand(serverCmd) - - html = embed.ReadHtml() -} - -func declareFlags() { - // db.type - serverCmd.PersistentFlags().String( - "db-type", "", - "Database type (config: 'db.type')") - viper.BindPFlag( - "db.type", - serverCmd.PersistentFlags().Lookup("db-type")) - serverCmd.MarkPersistentFlagRequired("db.type") - - // db.sqlite.path - serverCmd.PersistentFlags().String( - "db-sqlite-path", "", - "Path to sqlite database (config: 'db.sqlite.path')") - viper.BindPFlag( - "db.sqlite.path", - serverCmd.PersistentFlags().Lookup("db-sqlite-path")) - - // server.port - serverCmd.PersistentFlags().Int( - "server-port", 1313, - "The port on which the web application will server content (config: 'server.port')") - viper.BindPFlag( - "server.port", - serverCmd.PersistentFlags().Lookup("server-port")) - - // server.static_dir - serverCmd.PersistentFlags().String( - "static-dir", "/var/lib/bottin-agenda/static", - "DEPRECATED The directory containing static assets (config: 'server.static_dir')") - viper.BindPFlag( - "server.static_dir", - serverCmd.PersistentFlags().Lookup("static-dir")) - - // login.username - serverCmd.PersistentFlags().String( - "login-username", "agenda", - "The username to login to the web ui. (config: 'login.username')") - viper.BindPFlag( - "login.username", - serverCmd.PersistentFlags().Lookup("login-username")) - - // login.password - serverCmd.PersistentFlags().String( - "login-password", "agenda", - "The password to login to the web ui. (config: 'login.password')") - viper.BindPFlag( - "login.password", - serverCmd.PersistentFlags().Lookup("login-password")) - - // import.insert-batch-size - serverCmd.PersistentFlags().Int( - "insert-batch-size", 500, - "The amount of inserts to do per batch (config: 'import.insert_batch_size')") - viper.BindPFlag( - "import.insert_batch_size", - serverCmd.PersistentFlags().Lookup("insert-batch-size")) -} - -func runServer() { - port := fmt.Sprintf(":%d", viper.GetInt("server.port")) - - e := echo.New() - g := e.Group("") - - e.Pre(middleware.RemoveTrailingSlash()) - g.Use(middleware.BasicAuth(basicAuther)) - - g.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ - Format: "${time_rfc3339_nano} method=${method}, uri=${uri}, status=${status}" + "\n", - })) - - // v1 routes - g.GET("/v1", showAPISpec) - g.GET("/v1/scan/:num_etud", showScan) - g.POST("/v1/scan/:num_etud", postScan) - - // html routes - registerHTMLRoutes(g) - - e.Logger.Fatal(e.Start(port)) -} - -func basicAuther(username, password string, context echo.Context) (bool, error) { - if subtle.ConstantTimeCompare([]byte(username), []byte(viper.GetString("login.username"))) == 1 && - subtle.ConstantTimeCompare([]byte(password), []byte(viper.GetString("login.password"))) == 1 { - return true, nil - } - return false, nil -} - -func showAPISpec(c echo.Context) error { - return c.String(http.StatusOK, `agecem/bottin-agenda -API Spec ------ -"/v1" | GET | Show API specifications -"/v1/scan/:num_etud" | GET | Show JSON representing a membre with :num_etud -"/v1/scan/:num_etud" | POST | Scan bottin for membre with :num_etud and add to local db if not already present ------ -`) -} - -// Handler for "-X GET /v1/scan/:num_etud" -func showScan(c echo.Context) error { - num_etud := c.Param("num_etud") - membre, err := readMembre(num_etud) - if err != nil { - return c.JSON(http.StatusOK, map[string]error{ - "message": err, - }) - } - - return c.JSON(200, membre) -} - -// Handler for "-X POST /v1/scan/:num_etud" -func postScan(c echo.Context) error { - num_etud := c.Param("num_etud") - - membre, err := scanMembre(num_etud) - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{ - "message": fmt.Sprintf("Erreur lors de l'insertion de Membre '%s'", num_etud), - }) - } - - return c.JSON(http.StatusOK, map[string]string{ - "message": fmt.Sprintf("Membre '%s' scannéE avec succès et peut reçevoir son agenda.", membre.NumEtud), - }) -} - -// Tries to insert a membre into the local db and return it with any error encountered -func scanMembre(num_etud string) (data.Membre, error) { - membre, err := readMembre(num_etud) - if num_etud == "" { - - } - - if err != nil { - return membre, err - } - membre_exists := (membre.ID != 0) - - membre_bottin, err := readMembreBottin(num_etud) - if err != nil { - return membre, err - } - membre_bottin_exists := (membre_bottin.ID != 0) - - if membre_exists { - return membre, errors.New(fmt.Sprintf("Membre '%s' a déjà reçuE son agenda", num_etud)) - } - - if !membre_bottin_exists { - return membre, errors.New(fmt.Sprintf("Membre '%s' non-trouvéE dans le bottin. Est-ce bien entré?", num_etud)) - } - - // This should happen if membre is not scanned and is present in bottin - membre_bottin.CreatedAt = time.Now() - membre_bottin.UpdatedAt = time.Now() - err, _ = data.InsertMembre(&membre_bottin) - if err != nil { - return membre_bottin, err - } - - return membre_bottin, nil -} - -func registerHTMLRoutes(g *echo.Group) { - g.GET("/", showScanHTML) - g.POST("/", postScanHTML) -} - -func showScanHTML(c echo.Context) error { - num_etud := c.QueryParam("num_etud") - - if num_etud == "" { - return c.HTML(http.StatusOK, html) - } - - html_filled := html - - if num_etud != "" { - html_filled = fmt.Sprintf(`%s -Numéro étudiant: %s`, html, num_etud) - } - - return c.HTML(http.StatusOK, html_filled) -} - -func postScanHTML(c echo.Context) error { - num_etud := c.FormValue("num_etud") - - if num_etud == "" { - return c.HTML(http.StatusOK, html) - } - - membre, err := scanMembre(num_etud) - if err != nil { - return c.HTML(http.StatusInternalServerError, fmt.Sprintf(`%s - Erreur: %s`, html, err)) - } - - html_filled := html - - if num_etud != "" { - if membre.ID == 0 { - html_filled = fmt.Sprintf(`%s -Erreur lors de l'insertion de membre %s. Veuillez SVP signaler cette erreur.`, html, num_etud) - } else { - - html_filled = fmt.Sprintf(`%s -Membre '%s' scannéE avec succès et peut recevoir son agenda.`, html, num_etud) - } - } - - return c.HTML(http.StatusOK, html_filled) -} diff --git a/data/apiclient.go b/data/apiclient.go new file mode 100644 index 0000000..5f36780 --- /dev/null +++ b/data/apiclient.go @@ -0,0 +1,134 @@ +package data + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" +) + +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 +} + +// GetV4 allows checking for API v4 server health +func (a *ApiClient) GetV4() (string, error) { + var getV4Response struct { + Message string `json:"message"` + } + + response, err := a.Call(http.MethodGet, "/v4", nil, true) + if err != nil { + return getV4Response.Message, err + } + + defer response.Body.Close() + + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return getV4Response.Message, err + } + + if err := json.Unmarshal(body, &getV4Response); err != nil { + return getV4Response.Message, err + } + + if getV4Response.Message == "" { + return getV4Response.Message, errors.New("Could not confirm that API server is up, no response message") + } + + return getV4Response.Message, 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") + } + + 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.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/data/data.go b/data/data.go index c7776a1..f71ff65 100644 --- a/data/data.go +++ b/data/data.go @@ -1,97 +1,293 @@ package data import ( - "errors" - "log" + "fmt" - "github.com/spf13/viper" - "gorm.io/driver/sqlite" - "gorm.io/gorm" + "git.agecem.com/agecem/bottin-agenda/models" + _ "github.com/jackc/pgx/stdlib" + "github.com/jmoiron/sqlx" ) -var db *gorm.DB - -type Membre struct { - gorm.Model - NumEtud string `mapper:"num_etud" json:"num_etud"` - Nom string `mapper:"-" json:"-"` +// DataClient is a postgres client based on sqlx +type DataClient struct { + PostgresConnection PostgresConnection + DB sqlx.DB } -func OpenDatabase() error { - var err error +type PostgresConnection struct { + User string + Password string + Database string + Host string + Port int + SSL bool +} - var dialector gorm.Dialector +func NewDataClient(connection PostgresConnection) (*DataClient, error) { + client := &DataClient{PostgresConnection: connection} - switch t := viper.GetString("db.type"); t { - case "sqlite": - log.Println("Using driver gorm.io/driver/sqlite") + 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_sqlite_path := viper.GetString("db.sqlite.path") + db, err := sqlx.Connect("pgx", connectionString) + if err != nil { + return nil, err + } - if db_sqlite_path == "" { - log.Fatal("No valid database file found in `--db-sqlite-path` or `db.sqlite.path`.") + 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 { + 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 0, err } - log.Println("Using database file:", db_sqlite_path) + rows, err := result.RowsAffected() + if err != nil { + tx.Rollback() + return 0, err + } - dialector = sqlite.Open(db_sqlite_path) - default: - log.Fatalf("Unrecognized database driver requested (%s).\n", t) + rowsInserted += rows } - db, err = gorm.Open(dialector, &gorm.Config{}) + err = tx.Commit() if err != nil { - return err + return rowsInserted, err } - sqlDB, err := db.DB() - if err != nil { - return err - } - - return sqlDB.Ping() + return rowsInserted, nil } -func MigrateDatabase() error { - err := db.AutoMigrate(&Membre{}) - return err +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 { + 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 0, err + } + + rows, err := result.RowsAffected() + if err != nil { + tx.Rollback() + return 0, err + } + + rowsInserted += rows + } + + err = tx.Commit() + if err != nil { + return rowsInserted, err + } + + return rowsInserted, nil } -func ReadMembre(num_etud string) (Membre, error) { - var membre Membre - result := db.Limit(1).Find(&membre, "num_etud = ?", num_etud) +func (d *DataClient) GetMembre(membreID string) (models.Membre, error) { + var membre models.Membre - if result.Error != nil { - return membre, result.Error + 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 InsertMembre(membre *Membre) (error, uint) { - // Reset ID before insertion - membre.ID = 0 - - // Insert membre into database - result := db.Create(&membre) - - if result.Error != nil { - return result.Error, 0 +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 } - return nil, membre.ID + 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 + 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 InsertMembres(membres []*Membre, batch_size int) error { - if len(membres) == 0 { - return errors.New("Cannot insert empty batch of membres.") +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 } - for _, membre := range membres { - membre.ID = 0 - } + return assets, nil +} - db.CreateInBatches(&membres, batch_size) +// 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/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..133796d --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,48 @@ +services: + + db: + image: 'docker.io/library/postgres:14.8' + environment: + POSTGRES_DATABASE: "${BOTTINAGENDA_POSTGRES_DATABASE}" + POSTGRES_PASSWORD: "${BOTTINAGENDA_POSTGRES_PASSWORD}" + POSTGRES_USER: "${BOTTINAGENDA_POSTGRES_USER}" + volumes: + - 'db-data:/var/lib/postgresql/data' + restart: 'unless-stopped' + + api: + depends_on: + - db + build: . + image: 'git.agecem.com/agecem/bottin-agenda/bottin-agenda-v2:latest' + ports: + - '1313:1313' + volumes: + - 'api-config:/etc/bottin-agenda' + restart: 'unless-stopped' + command: ['bottin-agenda', '--config', '/etc/bottin-agenda/api.yaml', 'api'] + + web: + depends_on: + - api + build: . + image: 'git.agecem.com/agecem/bottin-agenda/bottin-agenda-v2:latest' + ports: + - '2313:2313' + volumes: + - 'web-config:/etc/bottin-agenda' + restart: 'unless-stopped' + command: ['bottin-agenda', '--config', '/etc/bottin-agenda/web.yaml', 'web'] + +# adminer: +# image: adminer +# restart: always +# ports: +# - 8088:8080 +# depends_on: +# - db + +volumes: + db-data: + api-config: + web-config: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 77588ca..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: "3.9" -services: - bottin-agenda: - image: localhost/agecem/bottin-agenda:latest - ports: - - "1313:1313" # http - volumes: - - "bottin-agenda-config:/etc/bottin-agenda" - - "bottin-agenda-data:/var/lib/bottin-agenda" - command: bottin-agenda server --config /etc/bottin-agenda/bottin-agenda.yaml - restart: "always" -volumes: - bottin-agenda-config: - bottin-agenda-data: -networks: - default: - name: bottin diff --git a/embed/embed.go b/embed/embed.go deleted file mode 100644 index 53b166a..0000000 --- a/embed/embed.go +++ /dev/null @@ -1,10 +0,0 @@ -package embed - -import _ "embed" - -//go:embed html/index.html -var Html_index string - -func ReadHtml() string { - return Html_index -} diff --git a/embed/html/index.html b/embed/html/index.html deleted file mode 100644 index a178f70..0000000 --- a/embed/html/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - AGECEM | Agendas - - - -

- Distribution d'agendas -

-

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

-
- - -
- - diff --git a/examples/bottin-agenda.yaml b/examples/bottin-agenda.yaml deleted file mode 100644 index 4a36f2b..0000000 --- a/examples/bottin-agenda.yaml +++ /dev/null @@ -1,61 +0,0 @@ -# bottin instance configurations -bottin: - - # protocol - # - # possible values: - # - # - http - # - https - protocol: 'http' - - # bottin instance hostname - host: 'localhost' - - # bottin instance port - port: '1312' - - # bottin instance username - username: 'bottin' - - # bottin instance password - password: 'bottin' - - -# database configurations -db: - - # database type - # - # possible values: - # - # - sqlite - type: 'sqlite' - - # sqlite configurations - sqlite: - - # sqlite database path - # - # doesn't need to exist, but directory must be writable - # for bottin-agenda to create it. - path: '/var/lib/bottin-agenda/bottin-agenda.db' - -# data import -import: - - # batch size for database imports - insert_batch_size: 500 - -# authentication -login: - - # credentials - username: 'agenda' - password: 'agenda' - -# bottin-agenda server options -server: - - # bottin-agenda instance port - port: '1313' diff --git a/go.mod b/go.mod index 355525f..0794e17 100644 --- a/go.mod +++ b/go.mod @@ -1,43 +1,44 @@ module git.agecem.com/agecem/bottin-agenda -go 1.18 +go 1.20 require ( - github.com/labstack/echo/v4 v4.7.2 - github.com/spf13/cobra v1.4.0 - github.com/spf13/viper v1.12.0 - gorm.io/driver/sqlite v1.3.4 - gorm.io/gorm v1.23.6 + 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/fsnotify/fsnotify v1.5.4 // indirect + 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.0.0 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - github.com/labstack/gommon v0.3.1 // indirect - github.com/magiconair/properties v1.8.6 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/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/mattn/go-sqlite3 v1.14.12 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.1 // indirect - github.com/spf13/afero v1.8.2 // 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.3.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.1 // indirect - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect - golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect - golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect - gopkg.in/ini.v1 v1.66.4 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.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/go.sum b/go.sum index d699e76..802f7fa 100644 --- a/go.sum +++ b/go.sum @@ -46,7 +46,9 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk 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/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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= @@ -57,11 +59,15 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y 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.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +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= @@ -100,7 +106,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +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= @@ -125,13 +131,14 @@ 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.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +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= @@ -141,25 +148,29 @@ 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.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI= -github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= -github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= -github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +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.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +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/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/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 v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +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= @@ -168,31 +179,38 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: 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/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +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.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +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.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +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 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= -github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= +github.com/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 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= 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= @@ -210,8 +228,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U 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.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.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= @@ -276,8 +294,8 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y= -golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +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= @@ -334,9 +352,10 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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= @@ -344,13 +363,13 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 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.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +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.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/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= @@ -494,20 +513,13 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 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.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= -gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/sqlite v1.3.4 h1:NnFOPVfzi4CPsJPH4wXr6rMkPb4ElHEqKMvrsx9c9Fk= -gorm.io/driver/sqlite v1.3.4/go.mod h1:B+8GyC9K7VgzJAcrcXMRPdnMcck+8FgJynEehEPM16U= -gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.23.6 h1:KFLdNgri4ExFFGTRGGFWON2P1ZN28+9SJRN8voOoYe0= -gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +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= diff --git a/handlers/v2.go b/handlers/v2.go new file mode 100644 index 0000000..9a02ca0 --- /dev/null +++ b/handlers/v2.go @@ -0,0 +1,37 @@ +package handlers + +import ( + "net/http" + + "git.agecem.com/agecem/bottin-agenda/data" + "github.com/labstack/echo/v4" + "github.com/spf13/viper" +) + +func GetV2(c echo.Context) error { + bottinApiKey := viper.GetString("bottin.api.key") + bottinApiHost := viper.GetString("bottin.api.host") + bottinApiProtocol := viper.GetString("bottin.api.protocol") + bottinApiPort := viper.GetInt("bottin.api.port") + + bottinConnection := data.NewApiClient( + bottinApiKey, + bottinApiHost, + bottinApiProtocol, + bottinApiPort, + ) + + var bottinStatus string + + message, err := bottinConnection.GetV4() + if err != nil { + bottinStatus = err.Error() + } else { + bottinStatus = message + } + + return c.JSON(http.StatusOK, map[string]string{ + "message": "Bottin-agenda API v2 is ready", + "bottin": bottinStatus, + }) +} diff --git a/models/models.go b/models/models.go new file mode 100644 index 0000000..49678d0 --- /dev/null +++ b/models/models.go @@ -0,0 +1,19 @@ +package models + +import "time" + +var Schema = ` +CREATE TABLE transactions ( + id PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + membre_id VARCHAR(7), + given_at TIMESTAMP, + is_perpetual BOOLEAN +); +` + +type Transaction struct { + ID string `db:"id" json:"id"` + MembreID string `db:"membre_id" json:"membre_id"` + GivenAt *time.Time `db:"given_at" json:"given_at"` + IsPerpetual bool `db:"is_perpetual" json:"is_perpetual"` +} From da0c6489b49117b8074a7bd81b982c906fd2e9f2 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Mon, 29 May 2023 17:58:48 -0400 Subject: [PATCH 2/9] Ignore .env files --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 40de0a6..62b0c86 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ # env -#.env +.env # .swp *.swp From c0879a2a675e5173aa5ab7e491a84b5b6343ce9a Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Mon, 29 May 2023 18:28:27 -0400 Subject: [PATCH 3/9] Add missing MIT license --- LICENSE | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/LICENSE b/LICENSE index e69de29..f166f48 100644 --- a/LICENSE +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2023 AGECEM + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From b2832217286c6299367413ba67364b4ae39dbc3c Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 30 May 2023 19:03:28 -0400 Subject: [PATCH 4/9] Ajuster nom d'image --- docker-compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 133796d..d0ae628 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -14,7 +14,7 @@ services: depends_on: - db build: . - image: 'git.agecem.com/agecem/bottin-agenda/bottin-agenda-v2:latest' + image: 'git.agecem.com/agecem/bottin-agenda:latest' ports: - '1313:1313' volumes: @@ -26,7 +26,7 @@ services: depends_on: - api build: . - image: 'git.agecem.com/agecem/bottin-agenda/bottin-agenda-v2:latest' + image: 'git.agecem.com/agecem/bottin-agenda:latest' ports: - '2313:2313' volumes: From bcd12582f15ee0e2c2f040def2a611275bf2077d Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 30 May 2023 19:03:55 -0400 Subject: [PATCH 5/9] Ajouter obtention de membre par API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Utiliser client API de bottin version 4 Ajouter handler GetMembre à `GET /v2/membres/{membre_id} HTTP/1.1` --- cmd/api.go | 7 +++++-- go.mod | 6 ++---- go.sum | 9 ++++----- handlers/membre.go | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 handlers/membre.go diff --git a/cmd/api.go b/cmd/api.go index e3b87ae..f36c18b 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -5,8 +5,8 @@ import ( "fmt" "log" - "git.agecem.com/agecem/bottin-agenda/data" "git.agecem.com/agecem/bottin-agenda/handlers" + bottindata "git.agecem.com/agecem/bottin/v4/data" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/spf13/cobra" @@ -42,7 +42,8 @@ var apiCmd = &cobra.Command{ bottinApiProtocol := viper.GetString("bottin.api.protocol") bottinApiPort := viper.GetInt("bottin.api.port") - bottinConnection := data.NewApiClient( + // Using bottin's API client + bottinConnection := bottindata.NewApiClient( bottinApiKey, bottinApiHost, bottinApiProtocol, @@ -65,6 +66,8 @@ var apiCmd = &cobra.Command{ e.GET("/v2/", handlers.GetV2) + e.GET("/v2/membres/:membre_id", handlers.GetMembre) + // Check bottin is ready message, err := bottinConnection.GetV4() diff --git a/go.mod b/go.mod index 0794e17..e361a00 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module git.agecem.com/agecem/bottin-agenda go 1.20 require ( + git.agecem.com/agecem/bottin/v4 v4.1.0 github.com/jackc/pgx v3.6.2+incompatible github.com/jmoiron/sqlx v1.3.5 github.com/labstack/echo/v4 v4.10.2 @@ -11,13 +12,10 @@ require ( ) 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 @@ -26,7 +24,6 @@ require ( 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 @@ -39,6 +36,7 @@ require ( 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/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 802f7fa..24872b2 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX 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= +git.agecem.com/agecem/bottin/v4 v4.1.0 h1:S8Mrh/pJaH5b0LbJkV7edFNyF+BJ1OZ7pYAccUUcv28= +git.agecem.com/agecem/bottin/v4 v4.1.0/go.mod h1:U7jxx83B4D7ST1GP+0tvCrAkMtC0s1Qgvv1NvCIg8CQ= 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= @@ -47,7 +49,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX 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= @@ -67,7 +68,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 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= @@ -134,7 +134,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: 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= @@ -180,7 +179,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR 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= @@ -510,8 +508,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD 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/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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= diff --git a/handlers/membre.go b/handlers/membre.go new file mode 100644 index 0000000..5ef8c08 --- /dev/null +++ b/handlers/membre.go @@ -0,0 +1,47 @@ +package handlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" + "github.com/spf13/viper" + + bottindata "git.agecem.com/agecem/bottin/v4/data" +) + +func GetMembre(c echo.Context) error { + bottinApiKey := viper.GetString("bottin.api.key") + bottinApiHost := viper.GetString("bottin.api.host") + bottinApiProtocol := viper.GetString("bottin.api.protocol") + bottinApiPort := viper.GetInt("bottin.api.port") + + // Using bottin's API client + bottinConnection := bottindata.NewApiClient( + bottinApiKey, + bottinApiHost, + bottinApiProtocol, + bottinApiPort, + ) + + membreID := c.Param("membre_id") + + membre, err := bottinConnection.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, + }, + }) +} From 8adb253f47952768835c610d0d46318e9d52546b Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Sat, 3 Jun 2023 19:48:37 -0400 Subject: [PATCH 6/9] =?UTF-8?q?Mettre=20=C3=A0=20jour=20avec=20bottin=20ap?= =?UTF-8?q?i=20v5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/api.go | 13 ++++++------- data/apiclient.go | 29 ++++++++++++++++------------- go.mod | 2 +- go.sum | 4 ++-- handlers/{v2.go => health.go} | 10 +++++----- handlers/membre.go | 2 +- 6 files changed, 31 insertions(+), 29 deletions(-) rename handlers/{v2.go => health.go} (73%) diff --git a/cmd/api.go b/cmd/api.go index f36c18b..397d2cc 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -6,7 +6,7 @@ import ( "log" "git.agecem.com/agecem/bottin-agenda/handlers" - bottindata "git.agecem.com/agecem/bottin/v4/data" + bottindata "git.agecem.com/agecem/bottin/v5/data" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/spf13/cobra" @@ -64,19 +64,18 @@ var apiCmd = &cobra.Command{ // Routes - e.GET("/v2/", handlers.GetV2) + e.GET("/v2/health/", handlers.GetHealth) - e.GET("/v2/membres/:membre_id", handlers.GetMembre) + e.GET("/v2/membres/:membre_id/", handlers.GetMembre) // Check bottin is ready - message, err := bottinConnection.GetV4() + bottinHealthResponse, err := bottinConnection.GetHealth() if err != nil { - log.Fatal(err) + log.Fatalf("bottinConnection.GetHealth(): %s", err) } - //TODO - log.Println(message) + log.Println(bottinHealthResponse) // Execution diff --git a/data/apiclient.go b/data/apiclient.go index 5f36780..f0f1453 100644 --- a/data/apiclient.go +++ b/data/apiclient.go @@ -67,33 +67,36 @@ func (a *ApiClient) Call(method, route string, requestBody io.Reader, useKey boo return response, nil } -// GetV4 allows checking for API v4 server health -func (a *ApiClient) GetV4() (string, error) { - var getV4Response struct { - Message string `json:"message"` - } +// BottinHealthResponse is the response type for GetBottinHealth +type BottinHealthResponse struct { + Message string `json:"message"` +} - response, err := a.Call(http.MethodGet, "/v4", nil, true) +// GetHealth allows checking for API server health +func (a *ApiClient) GetBottinHealth() (string, error) { + var healthResponse BottinHealthResponse + + response, err := a.Call(http.MethodGet, "/v4/health", nil, true) if err != nil { - return getV4Response.Message, err + return healthResponse.Message, err } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { - return getV4Response.Message, err + return healthResponse.Message, err } - if err := json.Unmarshal(body, &getV4Response); err != nil { - return getV4Response.Message, err + if err := json.Unmarshal(body, &healthResponse); err != nil { + return healthResponse.Message, err } - if getV4Response.Message == "" { - return getV4Response.Message, errors.New("Could not confirm that API server is up, no response message") + if healthResponse.Message == "" { + return healthResponse.Message, errors.New("Could not confirm that API server is up, no response message") } - return getV4Response.Message, nil + return healthResponse.Message, nil } /* diff --git a/go.mod b/go.mod index e361a00..4ea90d2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.agecem.com/agecem/bottin-agenda go 1.20 require ( - git.agecem.com/agecem/bottin/v4 v4.1.0 + git.agecem.com/agecem/bottin/v5 v5.0.4 github.com/jackc/pgx v3.6.2+incompatible github.com/jmoiron/sqlx v1.3.5 github.com/labstack/echo/v4 v4.10.2 diff --git a/go.sum b/go.sum index 24872b2..77145a2 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX 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= -git.agecem.com/agecem/bottin/v4 v4.1.0 h1:S8Mrh/pJaH5b0LbJkV7edFNyF+BJ1OZ7pYAccUUcv28= -git.agecem.com/agecem/bottin/v4 v4.1.0/go.mod h1:U7jxx83B4D7ST1GP+0tvCrAkMtC0s1Qgvv1NvCIg8CQ= +git.agecem.com/agecem/bottin/v5 v5.0.4 h1:19xQjnc9cZU1vBtk9mjhQ8QDg4nlpYLShmkbUeptVbM= +git.agecem.com/agecem/bottin/v5 v5.0.4/go.mod h1:OGqwTvEtIEOg/AHZgaJPzsMCy94dWygq89rMJlCFLuU= 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= diff --git a/handlers/v2.go b/handlers/health.go similarity index 73% rename from handlers/v2.go rename to handlers/health.go index 9a02ca0..9364932 100644 --- a/handlers/v2.go +++ b/handlers/health.go @@ -3,18 +3,18 @@ package handlers import ( "net/http" - "git.agecem.com/agecem/bottin-agenda/data" + bottindata "git.agecem.com/agecem/bottin/v5/data" "github.com/labstack/echo/v4" "github.com/spf13/viper" ) -func GetV2(c echo.Context) error { +func GetHealth(c echo.Context) error { bottinApiKey := viper.GetString("bottin.api.key") bottinApiHost := viper.GetString("bottin.api.host") bottinApiProtocol := viper.GetString("bottin.api.protocol") bottinApiPort := viper.GetInt("bottin.api.port") - bottinConnection := data.NewApiClient( + bottinConnection := bottindata.NewApiClient( bottinApiKey, bottinApiHost, bottinApiProtocol, @@ -23,11 +23,11 @@ func GetV2(c echo.Context) error { var bottinStatus string - message, err := bottinConnection.GetV4() + healthResponse, err := bottinConnection.GetHealth() if err != nil { bottinStatus = err.Error() } else { - bottinStatus = message + bottinStatus = healthResponse } return c.JSON(http.StatusOK, map[string]string{ diff --git a/handlers/membre.go b/handlers/membre.go index 5ef8c08..3b8286c 100644 --- a/handlers/membre.go +++ b/handlers/membre.go @@ -6,7 +6,7 @@ import ( "github.com/labstack/echo/v4" "github.com/spf13/viper" - bottindata "git.agecem.com/agecem/bottin/v4/data" + bottindata "git.agecem.com/agecem/bottin/v5/data" ) func GetMembre(c echo.Context) error { From 36c04e656d0025fb81fe52943beb4507f7838a8b Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Sat, 3 Jun 2023 19:49:15 -0400 Subject: [PATCH 7/9] Bump golang:1.20.2 -> 1.20.4 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 485c5fc..6a98ef7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20.2 as build +FROM golang:1.20.4 as build LABEL author="vlbeaudoin" From f7981715db2ea1b1a6e9f084403ac1639b7cefff Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Sat, 3 Jun 2023 20:16:41 -0400 Subject: [PATCH 8/9] Fix healthcheck --- cmd/api.go | 29 +++++++++++++++++------------ data/data.go | 13 +++++++++++++ docker-compose.yaml | 2 ++ handlers/health.go | 23 +++++++++++++++++++++-- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/cmd/api.go b/cmd/api.go index 397d2cc..e7cefd4 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "git.agecem.com/agecem/bottin-agenda/data" "git.agecem.com/agecem/bottin-agenda/handlers" bottindata "git.agecem.com/agecem/bottin/v5/data" "github.com/labstack/echo/v4" @@ -27,16 +28,6 @@ var apiCmd = &cobra.Command{ apiKey = viper.GetString("api.key") apiPort = viper.GetInt("api.port") - /* - dbConnection := 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"), - } - */ - bottinApiKey := viper.GetString("bottin.api.key") bottinApiHost := viper.GetString("bottin.api.host") bottinApiProtocol := viper.GetString("bottin.api.protocol") @@ -72,10 +63,24 @@ var apiCmd = &cobra.Command{ bottinHealthResponse, err := bottinConnection.GetHealth() if err != nil { - log.Fatalf("bottinConnection.GetHealth(): %s", err) + log.Fatalf("[bottin] bottinConnection.GetHealth(): %s", err) } - log.Println(bottinHealthResponse) + log.Println("[bottin] ok: ", bottinHealthResponse) + + // Check database is ready + + dataClient, err := data.NewDataClientFromViper() + if err != nil { + log.Fatalf("[bottin-agenda db] data.NewDataclientFromViper(): %s", err) + } + defer dataClient.DB.Close() + + if err := dataClient.DB.Ping(); err != nil { + log.Fatalf("[bottin-agenda db] dataClient.DB.Ping(): %s", err) + } else { + log.Println("[bottin-agenda db] ok") + } // Execution diff --git a/data/data.go b/data/data.go index f71ff65..26dacbd 100644 --- a/data/data.go +++ b/data/data.go @@ -6,6 +6,7 @@ import ( "git.agecem.com/agecem/bottin-agenda/models" _ "github.com/jackc/pgx/stdlib" "github.com/jmoiron/sqlx" + "github.com/spf13/viper" ) // DataClient is a postgres client based on sqlx @@ -23,6 +24,18 @@ type PostgresConnection struct { SSL bool } +func NewDataClientFromViper() (*DataClient, error) { + client, err := NewDataClient(PostgresConnection{ + User: viper.GetString("db.user"), + Password: viper.GetString("db.password"), + Host: viper.GetString("db.host"), + Port: viper.GetInt("db.port"), + Database: viper.GetString("db.database"), + }) + + return client, err +} + func NewDataClient(connection PostgresConnection) (*DataClient, error) { client := &DataClient{PostgresConnection: connection} diff --git a/docker-compose.yaml b/docker-compose.yaml index d0ae628..635d0c8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -6,6 +6,8 @@ services: POSTGRES_DATABASE: "${BOTTINAGENDA_POSTGRES_DATABASE}" POSTGRES_PASSWORD: "${BOTTINAGENDA_POSTGRES_PASSWORD}" POSTGRES_USER: "${BOTTINAGENDA_POSTGRES_USER}" + ports: + - '5433:5432' volumes: - 'db-data:/var/lib/postgresql/data' restart: 'unless-stopped' diff --git a/handlers/health.go b/handlers/health.go index 9364932..47d8177 100644 --- a/handlers/health.go +++ b/handlers/health.go @@ -3,6 +3,7 @@ package handlers import ( "net/http" + "git.agecem.com/agecem/bottin-agenda/data" bottindata "git.agecem.com/agecem/bottin/v5/data" "github.com/labstack/echo/v4" "github.com/spf13/viper" @@ -30,8 +31,26 @@ func GetHealth(c echo.Context) error { bottinStatus = healthResponse } + var databaseStatus string + + // Check database is ready + + dataClient, err := data.NewDataClientFromViper() + if err != nil { + databaseStatus = err.Error() + } else { + defer dataClient.DB.Close() + + if err := dataClient.DB.Ping(); err != nil { + databaseStatus = err.Error() + } else { + databaseStatus = "Bottin-agenda database is ready" + } + } + return c.JSON(http.StatusOK, map[string]string{ - "message": "Bottin-agenda API v2 is ready", - "bottin": bottinStatus, + "message": "Bottin-agenda API v2 is ready", + "bottin": bottinStatus, + "database": databaseStatus, }) } From d6573287f412ed8b0fc622d65347c4b269b61b78 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Sat, 3 Jun 2023 20:41:13 -0400 Subject: [PATCH 9/9] Ajouter GetMembreResponse --- handlers/membre.go | 37 +++++++++++++++++++++++-------------- responses/responses.go | 10 ++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 responses/responses.go diff --git a/handlers/membre.go b/handlers/membre.go index 3b8286c..67b5215 100644 --- a/handlers/membre.go +++ b/handlers/membre.go @@ -6,6 +6,7 @@ import ( "github.com/labstack/echo/v4" "github.com/spf13/viper" + "git.agecem.com/agecem/bottin-agenda/responses" bottindata "git.agecem.com/agecem/bottin/v5/data" ) @@ -25,23 +26,31 @@ func GetMembre(c echo.Context) error { membreID := c.Param("membre_id") + getMembreResponse := responses.GetMembreResponse{} + membre, err := bottinConnection.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", - }) + getMembreResponse.Message = err.Error() + + var statusCode int + + switch err.Error() { + case "Veuillez fournir un numéro étudiant à rechercher": + statusCode = http.StatusBadRequest + case "Ce numéro étudiant ne correspond à aucunE membre": + statusCode = http.StatusNotFound + default: + statusCode = http.StatusInternalServerError } - return c.JSON(http.StatusInternalServerError, map[string]string{ - "message": "Unknown error during GetMembre", - "error": err.Error(), - }) + + getMembreResponse.Data.Membre = membre + + return c.JSON(statusCode, getMembreResponse) } - return c.JSON(http.StatusOK, map[string]interface{}{ - "message": "Read successful", - "data": map[string]interface{}{ - "membre": &membre, - }, - }) + getMembreResponse.Data.Membre = membre + + getMembreResponse.Message = "Read successful" + + return c.JSON(http.StatusOK, getMembreResponse) } diff --git a/responses/responses.go b/responses/responses.go new file mode 100644 index 0000000..64406af --- /dev/null +++ b/responses/responses.go @@ -0,0 +1,10 @@ +package responses + +import bottinmodels "git.agecem.com/agecem/bottin/v5/models" + +type GetMembreResponse struct { + Message string `json:"message"` + Data struct { + Membre bottinmodels.Membre + } `json:"data"` +}