Bump API à version 2 #2
21 changed files with 777 additions and 746 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.2 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"]
|
||||
|
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2022 AGECEM & Victor Lacasse-Beaudoin <vlbeaudoin@agecem.org>
|
||||
|
||||
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.
|
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).
|
||||
|
||||
|
||||
|
|
142
cmd/api.go
Normal file
142
cmd/api.go
Normal file
|
@ -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"))
|
||||
}
|
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)
|
||||
}
|
134
data/apiclient.go
Normal file
134
data/apiclient.go
Normal file
|
@ -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
|
||||
}
|
||||
*/
|
312
data/data.go
312
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
|
||||
|
||||
var dialector gorm.Dialector
|
||||
|
||||
switch t := viper.GetString("db.type"); t {
|
||||
case "sqlite":
|
||||
log.Println("Using driver gorm.io/driver/sqlite")
|
||||
|
||||
db_sqlite_path := viper.GetString("db.sqlite.path")
|
||||
|
||||
if db_sqlite_path == "" {
|
||||
log.Fatal("No valid database file found in `--db-sqlite-path` or `db.sqlite.path`.")
|
||||
type PostgresConnection struct {
|
||||
User string
|
||||
Password string
|
||||
Database string
|
||||
Host string
|
||||
Port int
|
||||
SSL bool
|
||||
}
|
||||
|
||||
log.Println("Using database file:", db_sqlite_path)
|
||||
func NewDataClient(connection PostgresConnection) (*DataClient, error) {
|
||||
client := &DataClient{PostgresConnection: connection}
|
||||
|
||||
dialector = sqlite.Open(db_sqlite_path)
|
||||
default:
|
||||
log.Fatalf("Unrecognized database driver requested (%s).\n", t)
|
||||
}
|
||||
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 = gorm.Open(dialector, &gorm.Config{})
|
||||
db, err := sqlx.Connect("pgx", connectionString)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
client.DB = *db
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (d *DataClient) Seed() (int64, error) {
|
||||
result, err := d.DB.Exec(models.Schema)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return sqlDB.Ping()
|
||||
rows, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func MigrateDatabase() error {
|
||||
err := db.AutoMigrate(&Membre{})
|
||||
return err
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func ReadMembre(num_etud string) (Membre, error) {
|
||||
var membre Membre
|
||||
result := db.Limit(1).Find(&membre, "num_etud = ?", num_etud)
|
||||
/*
|
||||
// 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
|
||||
}
|
||||
|
||||
if result.Error != nil {
|
||||
return membre, result.Error
|
||||
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
|
||||
}
|
||||
|
||||
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 (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 (d *DataClient) GetMembre(membreID string) (models.Membre, error) {
|
||||
var membre models.Membre
|
||||
|
||||
rows, err := d.DB.Queryx("SELECT * FROM membres WHERE id = $1 LIMIT 1;", membreID)
|
||||
if err != nil {
|
||||
return membre, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.StructScan(&membre)
|
||||
if err != nil {
|
||||
return membre, err
|
||||
}
|
||||
}
|
||||
|
||||
if membre.ID == "" {
|
||||
return membre, fmt.Errorf("No membre by that id was found")
|
||||
}
|
||||
|
||||
return membre, nil
|
||||
}
|
||||
|
||||
func 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
|
||||
}
|
||||
|
||||
func InsertMembres(membres []*Membre, batch_size int) error {
|
||||
if len(membres) == 0 {
|
||||
return errors.New("Cannot insert empty batch of membres.")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
for _, membre := range membres {
|
||||
membre.ID = 0
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
db.CreateInBatches(&membres, batch_size)
|
||||
err = tx.Commit()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *DataClient) List() ([]models.Asset, error) {
|
||||
// Query the database, storing results in a []Person (wrapped in []interface{})
|
||||
assets := []models.Asset{}
|
||||
|
||||
err := d.DB.Select(&assets, "SELECT * FROM assets WHERE deleted_at IS NULL LIMIT 1000")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return assets, nil
|
||||
}
|
||||
|
||||
// RecordEvent allows inserting into events when an asset or a tag is modified
|
||||
// or deleted.
|
||||
func (d *DataClient) RecordEvent(assetID, tagID int64, content string) error {
|
||||
event := models.Event{
|
||||
AssetID: assetID,
|
||||
TagID: tagID,
|
||||
Content: content,
|
||||
}
|
||||
_, err := d.DB.NamedExec("INSERT INTO events (asset_id, tag_id, at, content) VALUES (:asset_id, :tag_id, current_timestamp, :content);", event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DataClient) Delete(assetIDs []int64) ([]int64, error) {
|
||||
var rows []int64
|
||||
|
||||
tx := d.DB.MustBegin()
|
||||
|
||||
for _, assetID := range assetIDs {
|
||||
result, err := d.DB.Exec("UPDATE assets SET deleted_at = current_timestamp WHERE id = $1 AND deleted_at IS NULL;", assetID)
|
||||
if err != nil {
|
||||
return rows, err
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return rows, err
|
||||
}
|
||||
|
||||
if rowsAffected != 0 {
|
||||
rows = append(rows, assetID)
|
||||
}
|
||||
}
|
||||
|
||||
err := tx.Commit()
|
||||
if err != nil {
|
||||
return rows, err
|
||||
}
|
||||
|
||||
for _, assetID := range assetIDs {
|
||||
d.RecordEvent(assetID, -1, fmt.Sprintf("Asset %d deleted.", assetID))
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
func (d *DataClient) UpdateAssetDescription(assetID int64, description string) (int64, error) {
|
||||
result, err := d.DB.Exec("UPDATE assets SET description = $1 WHERE id = $2", description, assetID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if rowsAffected != 0 {
|
||||
return 0, errors.New("Nothing to do")
|
||||
}
|
||||
|
||||
return rowsAffected, nil
|
||||
}
|
||||
|
||||
func (d *DataClient) UpdateAssetStatus(assetID int64, status string) (int64, error) {
|
||||
result, err := d.DB.Exec("UPDATE assets SET status = $1 WHERE id = $2", status, assetID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if rowsAffected != 0 {
|
||||
return 0, errors.New("Nothing to do")
|
||||
}
|
||||
|
||||
return rowsAffected, nil
|
||||
}
|
||||
*/
|
||||
|
|
48
docker-compose.yaml
Normal file
48
docker-compose.yaml
Normal file
|
@ -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:
|
|
@ -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'
|
55
go.mod
55
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
|
||||
)
|
||||
|
|
124
go.sum
124
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=
|
||||
|
|
37
handlers/v2.go
Normal file
37
handlers/v2.go
Normal file
|
@ -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,
|
||||
})
|
||||
}
|
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"`
|
||||
}
|
Loading…
Reference in a new issue