Merge pull request 'Bump API à version 2' (#2) from bump/v2 into main
Reviewed-on: #2
This commit is contained in:
commit
9066a47893
23 changed files with 889 additions and 742 deletions
3
.env
Normal file
3
.env
Normal file
|
@ -0,0 +1,3 @@
|
|||
BOTTINAGENDA_POSTGRES_DATABASE=bottin-agenda
|
||||
BOTTINAGENDA_POSTGRES_PASSWORD=bottin-agenda
|
||||
BOTTINAGENDA_POSTGRES_USER=bottin-agenda
|
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
|
@ -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
|
26
Dockerfile
26
Dockerfile
|
@ -1,15 +1,25 @@
|
|||
FROM docker.io/library/golang:1.18
|
||||
FROM golang:1.20.4 as build
|
||||
|
||||
LABEL author="Victor Lacasse-Beaudoin <vlbeaudoin@agecem.org>"
|
||||
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"]
|
||||
|
|
22
LICENSE
22
LICENSE
|
@ -1,21 +1,9 @@
|
|||
The MIT License (MIT)
|
||||
MIT License
|
||||
|
||||
Copyright © 2022 AGECEM & Victor Lacasse-Beaudoin <vlbeaudoin@agecem.org>
|
||||
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:
|
||||
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 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.
|
||||
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.
|
||||
|
|
11
Makefile
11
Makefile
|
@ -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 .
|
|
@ -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).
|
||||
|
||||
|
||||
|
|
149
cmd/api.go
Normal file
149
cmd/api.go
Normal file
|
@ -0,0 +1,149 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"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"
|
||||
"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")
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
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/health/", handlers.GetHealth)
|
||||
|
||||
e.GET("/v2/membres/:membre_id/", handlers.GetMembre)
|
||||
|
||||
// Check bottin is ready
|
||||
|
||||
bottinHealthResponse, err := bottinConnection.GetHealth()
|
||||
if err != nil {
|
||||
log.Fatalf("[bottin] bottinConnection.GetHealth(): %s", err)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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"))
|
||||
}
|
58
cmd/root.go
58
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())
|
||||
}
|
||||
}
|
||||
|
|
131
cmd/scan.go
131
cmd/scan.go
|
@ -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
|
||||
}
|
259
cmd/server.go
259
cmd/server.go
|
@ -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)
|
||||
}
|
137
data/apiclient.go
Normal file
137
data/apiclient.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
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
|
||||
}
|
||||
|
||||
// BottinHealthResponse is the response type for GetBottinHealth
|
||||
type BottinHealthResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// 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 healthResponse.Message, err
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return healthResponse.Message, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &healthResponse); err != nil {
|
||||
return healthResponse.Message, err
|
||||
}
|
||||
|
||||
if healthResponse.Message == "" {
|
||||
return healthResponse.Message, errors.New("Could not confirm that API server is up, no response message")
|
||||
}
|
||||
|
||||
return healthResponse.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
|
||||
}
|
||||
*/
|
321
data/data.go
321
data/data.go
|
@ -1,97 +1,306 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"fmt"
|
||||
|
||||
"git.agecem.com/agecem/bottin-agenda/models"
|
||||
_ "github.com/jackc/pgx/stdlib"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
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 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"),
|
||||
})
|
||||
|
||||
switch t := viper.GetString("db.type"); t {
|
||||
case "sqlite":
|
||||
log.Println("Using driver gorm.io/driver/sqlite")
|
||||
return client, err
|
||||
}
|
||||
|
||||
db_sqlite_path := viper.GetString("db.sqlite.path")
|
||||
func NewDataClient(connection PostgresConnection) (*DataClient, error) {
|
||||
client := &DataClient{PostgresConnection: connection}
|
||||
|
||||
if db_sqlite_path == "" {
|
||||
log.Fatal("No valid database file found in `--db-sqlite-path` or `db.sqlite.path`.")
|
||||
connectionString := fmt.Sprintf("postgres://%s:%s@%s:%d/%s",
|
||||
client.PostgresConnection.User,
|
||||
client.PostgresConnection.Password,
|
||||
client.PostgresConnection.Host,
|
||||
client.PostgresConnection.Port,
|
||||
client.PostgresConnection.Database,
|
||||
)
|
||||
|
||||
db, err := sqlx.Connect("pgx", connectionString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.DB = *db
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (d *DataClient) Seed() (int64, error) {
|
||||
result, err := d.DB.Exec(models.Schema)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
rows, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return rows, err
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
/*
|
||||
// InsertMembres inserts a slice of Membre into a database, returning the amount inserted and any error encountered
|
||||
func (d *DataClient) InsertMembres(membres []models.Membre) (int64, error) {
|
||||
var rowsInserted int64
|
||||
tx, err := d.DB.Beginx()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return rowsInserted, err
|
||||
}
|
||||
|
||||
for _, membre := range membres {
|
||||
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
|
||||
}
|
||||
*/
|
||||
|
|
50
docker-compose.yaml
Normal file
50
docker-compose.yaml
Normal file
|
@ -0,0 +1,50 @@
|
|||
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}"
|
||||
ports:
|
||||
- '5433:5432'
|
||||
volumes:
|
||||
- 'db-data:/var/lib/postgresql/data'
|
||||
restart: 'unless-stopped'
|
||||
|
||||
api:
|
||||
depends_on:
|
||||
- db
|
||||
build: .
|
||||
image: 'git.agecem.com/agecem/bottin-agenda: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: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:
|
|
@ -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
|
|
@ -1,10 +0,0 @@
|
|||
package embed
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed html/index.html
|
||||
var Html_index string
|
||||
|
||||
func ReadHtml() string {
|
||||
return Html_index
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>
|
||||
AGECEM | Agendas
|
||||
</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>
|
||||
Distribution d'agendas
|
||||
</h2>
|
||||
<h4>
|
||||
Scannez la carte étudiante d'unE membre<br>
|
||||
-ou-<br>
|
||||
Entrez manuellement le code à 7 chiffres
|
||||
</h4>
|
||||
<form action="/">
|
||||
<label for="num_etud">#
|
||||
<input type="text" name="num_etud" id="num_etud" placeholder="Numéro étudiant"autofocus>
|
||||
</label>
|
||||
<button formmethod="post" type="submit">Scan</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -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'
|
53
go.mod
53
go.mod
|
@ -1,43 +1,42 @@
|
|||
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
|
||||
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
|
||||
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/fsnotify/fsnotify v1.6.0 // 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/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/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/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
125
go.sum
125
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/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=
|
||||
|
@ -46,7 +48,8 @@ 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/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 +60,14 @@ 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/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,13 @@ 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/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 +147,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 +178,37 @@ 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/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 +226,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 +292,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 +350,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 +361,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=
|
||||
|
@ -491,23 +508,17 @@ 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.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=
|
||||
|
|
56
handlers/health.go
Normal file
56
handlers/health.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
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 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 := bottindata.NewApiClient(
|
||||
bottinApiKey,
|
||||
bottinApiHost,
|
||||
bottinApiProtocol,
|
||||
bottinApiPort,
|
||||
)
|
||||
|
||||
var bottinStatus string
|
||||
|
||||
healthResponse, err := bottinConnection.GetHealth()
|
||||
if err != nil {
|
||||
bottinStatus = err.Error()
|
||||
} else {
|
||||
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,
|
||||
"database": databaseStatus,
|
||||
})
|
||||
}
|
56
handlers/membre.go
Normal file
56
handlers/membre.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"git.agecem.com/agecem/bottin-agenda/responses"
|
||||
bottindata "git.agecem.com/agecem/bottin/v5/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")
|
||||
|
||||
getMembreResponse := responses.GetMembreResponse{}
|
||||
|
||||
membre, err := bottinConnection.GetMembre(membreID)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
getMembreResponse.Data.Membre = membre
|
||||
|
||||
return c.JSON(statusCode, getMembreResponse)
|
||||
}
|
||||
|
||||
getMembreResponse.Data.Membre = membre
|
||||
|
||||
getMembreResponse.Message = "Read successful"
|
||||
|
||||
return c.JSON(http.StatusOK, getMembreResponse)
|
||||
}
|
19
models/models.go
Normal file
19
models/models.go
Normal file
|
@ -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"`
|
||||
}
|
10
responses/responses.go
Normal file
10
responses/responses.go
Normal file
|
@ -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"`
|
||||
}
|
Reference in a new issue