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"` +}