Compare commits
72 commits
Author | SHA1 | Date | |
---|---|---|---|
68074dcd85 | |||
1ad0d61477 | |||
e96033eb80 | |||
b419a5b260 | |||
a17d6bf06c | |||
0640395fd2 | |||
d29ef1b5ef | |||
545b38c0db | |||
882553521a | |||
c9a45f8db8 | |||
f874c449a7 | |||
bdff81c6b2 | |||
c0b8ceafa9 | |||
9072f7114a | |||
2b6c631d64 | |||
7ddf89a859 | |||
1a847209f4 | |||
f5aa25a12a | |||
61c4ef80f5 | |||
8a9decfe6c | |||
31bcdbac20 | |||
03c9ad5f3c | |||
537f1a8a1a | |||
e14ff3d04e | |||
6579ea45f9 | |||
98090c96ac | |||
eb1982898c | |||
8c074dd443 | |||
a9f1682634 | |||
4ce3d9f60b | |||
150782c42f | |||
d80c7675f9 | |||
1f2ba0576a | |||
14eb6c5d02 | |||
d0de811547 | |||
64ddfa96d6 | |||
8af11615dd | |||
6cc90b1a29 | |||
0321b1b2a0 | |||
244276905b | |||
7484bafc84 | |||
8cb2014f3b | |||
929704c6ff | |||
e4ff1013d0 | |||
e6103c6e6e | |||
78aafe0ce9 | |||
26b3134861 | |||
4d338f2b03 | |||
f6ffa03379 | |||
f8b5c72003 | |||
00aebc2ae3 | |||
c7c64674c7 | |||
e847f693e0 | |||
e1bce94d18 | |||
c5339bd45b | |||
be766f593d | |||
eca5ffa7fb | |||
1b04237c96 | |||
1125104280 | |||
780d493dc1 | |||
cdd526a6f3 | |||
0123d9d37c | |||
b67955ab28 | |||
6d98375adb | |||
369332db26 | |||
917aab7e01 | |||
522b2d7041 | |||
4a87daae79 | |||
1b5e0913a6 | |||
9367f0f4c0 | |||
3f0bf238e0 | |||
4c8e822324 |
48 changed files with 2771 additions and 1649 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -22,6 +22,8 @@
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
|
|
||||||
|
# cert files
|
||||||
|
*.pem
|
||||||
|
|
||||||
# env
|
# env
|
||||||
.env
|
.env
|
||||||
|
|
16
Dockerfile
16
Dockerfile
|
@ -1,23 +1,21 @@
|
||||||
FROM golang:1.21.1 as build
|
FROM golang:1.23.0 as build
|
||||||
|
|
||||||
LABEL author="vlbeaudoin"
|
LABEL author="vlbeaudoin"
|
||||||
|
|
||||||
WORKDIR /go/src/app
|
WORKDIR /go/src/app
|
||||||
|
|
||||||
COPY go.mod go.sum main.go ./
|
COPY go.mod go.sum LICENSE ./
|
||||||
|
|
||||||
ADD cmd/ cmd/
|
ADD cmd/ cmd/
|
||||||
ADD data/ data/
|
ADD pkg/ pkg/
|
||||||
ADD handlers/ handlers/
|
ADD queries/ queries/
|
||||||
ADD models/ models/
|
ADD templates/ templates/
|
||||||
ADD responses/ responses/
|
|
||||||
ADD web/ web/
|
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o bottin .
|
RUN CGO_ENABLED=0 go build -a -o bottin ./cmd/bottin
|
||||||
|
|
||||||
# Alpine
|
# Alpine
|
||||||
|
|
||||||
FROM alpine:3.18
|
FROM alpine:3.20.2
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
28
Makefile
Normal file
28
Makefile
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
## This Makefile uses the help target explained in the following blogpost:
|
||||||
|
##
|
||||||
|
## https://victoria.dev/blog/how-to-create-a-self-documenting-makefile/
|
||||||
|
|
||||||
|
.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: test-integration
|
||||||
|
test-integration: ## run integration tests through API client. Config is read from `~/.bottin.yaml`. WARNING: affects data in the database, do not run on production server
|
||||||
|
docker-compose down && docker-compose up -d --build && sleep 2 && go test
|
||||||
|
|
||||||
|
.PHONY: dev
|
||||||
|
dev: generate-self-signed-x509 compose-inject-x509 ## deploy development environment on docker-compose
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
.PHONY: generate-self-signed-x509
|
||||||
|
generate-self-signed-x509: ## Générer une paire de clés x509 self-signed pour utilisation avec un serveur de développement
|
||||||
|
./scripts/generate-self-signed-x509.sh
|
||||||
|
|
||||||
|
.PHONY: compose-inject-x509
|
||||||
|
compose-inject-x509: ## Copie la paire de clés x509 du current directory vers les containers orchestrés par docker-compose
|
||||||
|
./scripts/compose-inject-x509.sh
|
22
README.md
22
README.md
|
@ -20,17 +20,18 @@ https://git.agecem.com/agecem/bottin
|
||||||
|
|
||||||
Remplir .env avec les infos qui seront utilisées pour déployer le container
|
Remplir .env avec les infos qui seront utilisées pour déployer le container
|
||||||
|
|
||||||
(Remplacer `bottin` par quelque chose de plus sécuritaire)
|
Au minimum, il faut ces 3 entrées:
|
||||||
|
|
||||||
|
*Remplacer `bottin` par quelque chose de plus sécuritaire*
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
BOTTIN_API_KEY=bottin
|
BOTTIN_SERVER_DB_DATABASE=bottin
|
||||||
BOTTIN_POSTGRES_DATABASE=bottin
|
BOTTIN_SERVER_DB_PASSWORD=bottin
|
||||||
BOTTIN_POSTGRES_PASSWORD=bottin
|
BOTTIN_SERVER_DB_USER=bottin
|
||||||
BOTTIN_POSTGRES_USER=bottin
|
|
||||||
BOTTIN_WEB_PASSWORD=bottin
|
|
||||||
BOTTIN_WEB_USER=bottin
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
*D'autres entrées peuvent être ajoutées, voir `config.go` pour les options*
|
||||||
|
|
||||||
Déployer avec docker-compose
|
Déployer avec docker-compose
|
||||||
|
|
||||||
`$ docker-compose up -d`
|
`$ docker-compose up -d`
|
||||||
|
@ -43,14 +44,15 @@ Pour modifier la configuration du serveur API
|
||||||
|
|
||||||
`$ docker-compose exec -it api vi /etc/bottin/api.yaml`
|
`$ docker-compose exec -it api vi /etc/bottin/api.yaml`
|
||||||
|
|
||||||
*Y remplir au minimum le champs `api.key` (string)*
|
*Y remplir au minimum le champs `server.api.key` (string)*
|
||||||
|
|
||||||
Pour modifier la configuration du client web
|
Pour modifier la configuration du client web
|
||||||
|
|
||||||
`$ docker-compose exec -it web vi /etc/bottin/web.yaml`
|
`$ docker-compose exec -it ui vi /etc/bottin/ui.yaml`
|
||||||
|
|
||||||
*Y remplir au minimum les champs `web.api.key` (string), `web.user` (string) et `web.password` (string)*
|
*Y remplir au minimum les champs `server.ui.api.key` (string), `server.ui.user` (string) et `server.ui.password` (string)*
|
||||||
|
|
||||||
Redémarrer les containers une fois la configuration modifiée
|
Redémarrer les containers une fois la configuration modifiée
|
||||||
|
|
||||||
`$ docker-compose down && docker-compose up -d`
|
`$ docker-compose down && docker-compose up -d`
|
||||||
|
v
|
||||||
|
|
122
cmd/api.go
122
cmd/api.go
|
@ -1,122 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/subtle"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"codeberg.org/vlbeaudoin/serpents"
|
|
||||||
"git.agecem.com/agecem/bottin/v6/data"
|
|
||||||
"git.agecem.com/agecem/bottin/v6/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")
|
|
||||||
|
|
||||||
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
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataClient
|
|
||||||
|
|
||||||
client, err := data.NewDataClientFromViper()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not establish database connection.\n Error: %s\n", err)
|
|
||||||
}
|
|
||||||
defer client.DB.Close()
|
|
||||||
|
|
||||||
err = client.DB.Ping()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Database was supposed to be ready but Ping() failed.\n Error: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.Seed()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error during client.Seed(): %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := handlers.New(client)
|
|
||||||
|
|
||||||
// Routes
|
|
||||||
|
|
||||||
e.GET("/v6/health/", h.GetHealth)
|
|
||||||
|
|
||||||
e.POST("/v6/membres/", h.PostMembres)
|
|
||||||
|
|
||||||
e.GET("/v6/membres/", h.ListMembres)
|
|
||||||
|
|
||||||
e.GET("/v6/membres/:membre_id/", h.ReadMembre)
|
|
||||||
|
|
||||||
e.PUT("/v6/membres/:membre_id/prefered_name/", h.PutMembrePreferedName)
|
|
||||||
|
|
||||||
e.POST("/v6/programmes/", h.PostProgrammes)
|
|
||||||
|
|
||||||
e.POST("/v6/seed/", h.PostSeed)
|
|
||||||
|
|
||||||
// Execution
|
|
||||||
|
|
||||||
e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", apiPort)))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(apiCmd)
|
|
||||||
|
|
||||||
// api.key
|
|
||||||
serpents.String(apiCmd.Flags(),
|
|
||||||
"api.key", "api-key", "bottin",
|
|
||||||
"API server key. Leave empty for no key auth")
|
|
||||||
|
|
||||||
// api.port
|
|
||||||
serpents.Int(apiCmd.Flags(),
|
|
||||||
"api.port", "api-port", 1312,
|
|
||||||
"API server port")
|
|
||||||
|
|
||||||
// db.database
|
|
||||||
serpents.String(apiCmd.Flags(),
|
|
||||||
"db.database", "db-database", "bottin",
|
|
||||||
"Postgres database")
|
|
||||||
|
|
||||||
// db.host
|
|
||||||
serpents.String(apiCmd.Flags(),
|
|
||||||
"db.host", "db-host", "db",
|
|
||||||
"Postgres host")
|
|
||||||
|
|
||||||
// db.password
|
|
||||||
serpents.String(apiCmd.Flags(),
|
|
||||||
"db.password", "db-password", "bottin",
|
|
||||||
"Postgres password")
|
|
||||||
|
|
||||||
// db.port
|
|
||||||
serpents.Int(apiCmd.Flags(),
|
|
||||||
"db.port", "db-port", 5432,
|
|
||||||
"Postgres port")
|
|
||||||
|
|
||||||
// db.user
|
|
||||||
serpents.String(apiCmd.Flags(),
|
|
||||||
"db.user", "db-user", "bottin",
|
|
||||||
"Postgres user")
|
|
||||||
}
|
|
717
cmd/bottin/main.go
Normal file
717
cmd/bottin/main.go
Normal file
|
@ -0,0 +1,717 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/subtle"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"codeberg.org/vlbeaudoin/voki/v3"
|
||||||
|
"git.agecem.com/agecem/bottin/v9/pkg/bottin"
|
||||||
|
"git.agecem.com/agecem/bottin/v9/templates"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cfgFile string
|
||||||
|
|
||||||
|
// initConfig reads in config file and ENV variables if set.
|
||||||
|
func initConfig() {
|
||||||
|
if cfgFile != "" {
|
||||||
|
// Use config file from the flag.
|
||||||
|
viper.SetConfigFile(cfgFile)
|
||||||
|
} else {
|
||||||
|
// Find home directory.
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
cobra.CheckErr(err)
|
||||||
|
|
||||||
|
// Search config in home directory with name ".bottin" (without extension).
|
||||||
|
viper.AddConfigPath(home)
|
||||||
|
viper.SetConfigType("yaml")
|
||||||
|
viper.SetConfigName(".bottin")
|
||||||
|
}
|
||||||
|
|
||||||
|
viper.SetEnvPrefix("BOTTIN")
|
||||||
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
viper.AutomaticEnv() // read in environment variables that match
|
||||||
|
|
||||||
|
// If a config file is found, read it in.
|
||||||
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
/* TODO
|
||||||
|
if err := Run(context.Background(), Config{}, nil, os.Stdout); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Handle the command-line via cobra and viper
|
||||||
|
execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// rootCmd
|
||||||
|
|
||||||
|
cobra.OnInitialize(initConfig)
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)")
|
||||||
|
|
||||||
|
// client.api.host
|
||||||
|
rootCmd.PersistentFlags().String(
|
||||||
|
"client-api-host",
|
||||||
|
"api",
|
||||||
|
"API server host",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"client.api.host",
|
||||||
|
rootCmd.PersistentFlags().Lookup("client-api-host"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// client.api.key
|
||||||
|
rootCmd.PersistentFlags().String(
|
||||||
|
"client-api-key",
|
||||||
|
"bottin",
|
||||||
|
"API server key",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"client.api.key",
|
||||||
|
rootCmd.PersistentFlags().Lookup("client-api-key"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// client.api.port
|
||||||
|
rootCmd.PersistentFlags().Int(
|
||||||
|
"client-api-port",
|
||||||
|
1312,
|
||||||
|
"API server port",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"client.api.port",
|
||||||
|
rootCmd.PersistentFlags().Lookup("client-api-port"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// client.api.protocol
|
||||||
|
rootCmd.PersistentFlags().String(
|
||||||
|
"client-api-protocol",
|
||||||
|
"https",
|
||||||
|
"API server protocol",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"client.api.protocol",
|
||||||
|
rootCmd.PersistentFlags().Lookup("client-api-protocol"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server
|
||||||
|
rootCmd.AddCommand(serverCmd)
|
||||||
|
|
||||||
|
// server api
|
||||||
|
serverCmd.AddCommand(apiCmd)
|
||||||
|
|
||||||
|
// server api db
|
||||||
|
// server.api.db.database
|
||||||
|
apiCmd.PersistentFlags().String(
|
||||||
|
"server-api-db-database",
|
||||||
|
"bottin",
|
||||||
|
"Postgres database name",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.db.database",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-db-database"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.api.db.host
|
||||||
|
apiCmd.PersistentFlags().String(
|
||||||
|
"server-api-db-host",
|
||||||
|
"db",
|
||||||
|
"Postgres host name",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.db.host",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-db-host"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.api.db.password
|
||||||
|
apiCmd.PersistentFlags().String(
|
||||||
|
"server-api-db-password",
|
||||||
|
"bottin",
|
||||||
|
"Postgres password",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.db.password",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-db-password"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.api.db.port
|
||||||
|
apiCmd.PersistentFlags().Int(
|
||||||
|
"server-api-db-port",
|
||||||
|
5432,
|
||||||
|
"Postgres port",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.db.port",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-db-port"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.api.db.sslmode
|
||||||
|
apiCmd.PersistentFlags().String(
|
||||||
|
"server-api-db-sslmode",
|
||||||
|
"prefer",
|
||||||
|
"Postgres sslmode",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.db.sslmode",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-db-sslmode"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.api.db.user
|
||||||
|
apiCmd.PersistentFlags().String(
|
||||||
|
"server-api-db-user",
|
||||||
|
"bottin",
|
||||||
|
"Postgres user name",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.db.user",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-db-user"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.api.host
|
||||||
|
apiCmd.PersistentFlags().String(
|
||||||
|
"server-api-host",
|
||||||
|
"",
|
||||||
|
"API server hostname or IP to answer on (empty = any)",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.host",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-host"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.api.key
|
||||||
|
apiCmd.PersistentFlags().String(
|
||||||
|
"server-api-key",
|
||||||
|
"bottin",
|
||||||
|
"API server key",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.key",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-key"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.api.port
|
||||||
|
apiCmd.PersistentFlags().Int(
|
||||||
|
"server-api-port",
|
||||||
|
1312,
|
||||||
|
"API server port",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.port",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-port"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server api tls
|
||||||
|
// server.api.tls.enabled
|
||||||
|
apiCmd.PersistentFlags().Bool(
|
||||||
|
"server-api-tls-enabled",
|
||||||
|
true,
|
||||||
|
"Use TLS for API server connections (requires certfile and keyfile)",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.tls.enabled",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-tls-enabled"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.api.tls.certfile
|
||||||
|
apiCmd.PersistentFlags().String(
|
||||||
|
"server-api-tls-certfile",
|
||||||
|
"/etc/bottin/cert.pem",
|
||||||
|
"Path to certificate file",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.tls.certfile",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-tls-certfile"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.api.tls.keyfile
|
||||||
|
apiCmd.PersistentFlags().String(
|
||||||
|
"server-api-tls-keyfile",
|
||||||
|
"/etc/bottin/key.pem",
|
||||||
|
"Path to private key file",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.api.tls.keyfile",
|
||||||
|
apiCmd.PersistentFlags().Lookup("server-api-tls-keyfile"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server ui
|
||||||
|
serverCmd.AddCommand(uiCmd)
|
||||||
|
|
||||||
|
// server ui api
|
||||||
|
|
||||||
|
// server.ui.api.host
|
||||||
|
uiCmd.PersistentFlags().String(
|
||||||
|
"server-ui-api-host",
|
||||||
|
"api",
|
||||||
|
"Web UI backend API server host name",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.api.host",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-api-host"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ui.api.key
|
||||||
|
uiCmd.PersistentFlags().String(
|
||||||
|
"server-ui-api-key",
|
||||||
|
"bottin",
|
||||||
|
"Web UI backend API server key",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.api.key",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-api-key"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ui.api.port
|
||||||
|
uiCmd.PersistentFlags().Int(
|
||||||
|
"server-ui-api-port",
|
||||||
|
1312,
|
||||||
|
"Web UI backend API server port",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.api.port",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-api-port"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ui.api.protocol
|
||||||
|
uiCmd.PersistentFlags().String(
|
||||||
|
"server-ui-api-protocol",
|
||||||
|
"https",
|
||||||
|
"Web UI backend API server protocol",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.api.protocol",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-api-protocol"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ui.api.tls.skipverify
|
||||||
|
uiCmd.PersistentFlags().Bool(
|
||||||
|
"server-ui-api-tls-skipverify",
|
||||||
|
false,
|
||||||
|
"Skip API server TLS certificate verification",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.api.tls.skipverify",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-api-tls-skipverify"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ui.host
|
||||||
|
uiCmd.PersistentFlags().String(
|
||||||
|
"server-ui-host",
|
||||||
|
"",
|
||||||
|
"Web UI host",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.host",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-host"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ui.password
|
||||||
|
uiCmd.PersistentFlags().String(
|
||||||
|
"server-ui-password",
|
||||||
|
"bottin",
|
||||||
|
"Web UI password",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.password",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-password"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ui.port
|
||||||
|
uiCmd.PersistentFlags().Int(
|
||||||
|
"server-ui-port",
|
||||||
|
2312,
|
||||||
|
"Web UI port",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.port",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-port"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ui.user
|
||||||
|
uiCmd.PersistentFlags().String(
|
||||||
|
"server-ui-user",
|
||||||
|
"bottin",
|
||||||
|
"Web UI user",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.user",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-user"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server ui tls
|
||||||
|
// server.ui.tls.enabled
|
||||||
|
uiCmd.PersistentFlags().Bool(
|
||||||
|
"server-ui-tls-enabled",
|
||||||
|
true,
|
||||||
|
"Web UI enable TLS (requires certfile and keyfile)",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.tls.enabled",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-tls-enabled"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ui.tls.certfile
|
||||||
|
uiCmd.PersistentFlags().String(
|
||||||
|
"server-ui-tls-certfile",
|
||||||
|
"/etc/bottin/cert.pem",
|
||||||
|
"Path to Web UI TLS certificate file",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.tls.certfile",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-tls-certfile"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ui.tls.keyfile
|
||||||
|
uiCmd.PersistentFlags().String(
|
||||||
|
"server-ui-tls-keyfile",
|
||||||
|
"/etc/bottin/key.pem",
|
||||||
|
"Path to Web UI TLS private key file",
|
||||||
|
)
|
||||||
|
if err := viper.BindPFlag(
|
||||||
|
"server.ui.tls.keyfile",
|
||||||
|
uiCmd.PersistentFlags().Lookup("server-ui-tls-keyfile"),
|
||||||
|
); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO
|
||||||
|
func Run(ctx context.Context, config Config, args []string, stdout io.Writer) error {
|
||||||
|
return fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// rootCmd represents the base command when called without any subcommands
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "bottin",
|
||||||
|
Short: "Bottin étudiant de l'AGECEM",
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute adds all child commands to the root command and sets flags appropriately.
|
||||||
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||||
|
func execute() {
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverCmd = &cobra.Command{
|
||||||
|
Use: "server",
|
||||||
|
Short: "Démarrer serveurs (API ou Web UI)",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
var cfg bottin.Config
|
||||||
|
if err := viper.Unmarshal(&cfg); err != nil {
|
||||||
|
log.Fatal("parse config:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
// Middlewares
|
||||||
|
|
||||||
|
e.Pre(middleware.AddTrailingSlash())
|
||||||
|
|
||||||
|
if cfg.Server.API.Key != "" {
|
||||||
|
e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) {
|
||||||
|
return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.Server.API.Key)) == 1, nil
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
log.Println("Server started but no API key (server.api.key) was provided, using empty key (NOT RECOMMENDED FOR PRODUCTION)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataClient
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
//prep
|
||||||
|
pool, err := pgxpool.New(
|
||||||
|
ctx,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"user=%s password=%s database=%s host=%s port=%d sslmode=%s ",
|
||||||
|
cfg.Server.API.DB.User,
|
||||||
|
cfg.Server.API.DB.Password,
|
||||||
|
cfg.Server.API.DB.Database,
|
||||||
|
cfg.Server.API.DB.Host,
|
||||||
|
cfg.Server.API.DB.Port,
|
||||||
|
cfg.Server.API.DB.SSLMode,
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("init pgx pool:", err)
|
||||||
|
}
|
||||||
|
defer pool.Close()
|
||||||
|
|
||||||
|
db := &bottin.PostgresClient{
|
||||||
|
Ctx: ctx,
|
||||||
|
Pool: pool,
|
||||||
|
}
|
||||||
|
if err := db.Pool.Ping(ctx); err != nil {
|
||||||
|
log.Fatal("ping db:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.CreateOrReplaceSchema(); err != nil {
|
||||||
|
log.Fatal("create or replace schema:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.CreateOrReplaceViews(); err != nil {
|
||||||
|
log.Fatal("create or replace views:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes
|
||||||
|
if err := bottin.AddRoutes(e, db, cfg); err != nil {
|
||||||
|
log.Fatal("add routes:", err)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
h := handlers.New(client)
|
||||||
|
|
||||||
|
e.GET("/v9/health/", h.GetHealth)
|
||||||
|
|
||||||
|
e.POST("/v9/membres/", h.PostMembres)
|
||||||
|
|
||||||
|
e.GET("/v9/membres/", h.ListMembres)
|
||||||
|
|
||||||
|
e.GET("/v9/membres/:membre_id/", h.ReadMembre)
|
||||||
|
|
||||||
|
e.PUT("/v9/membres/:membre_id/prefered_name/", h.PutMembrePreferedName)
|
||||||
|
|
||||||
|
e.POST("/v9/programmes/", h.PostProgrammes)
|
||||||
|
|
||||||
|
e.POST("/v9/seed/", h.PostSeed)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
switch cfg.Server.API.TLS.Enabled {
|
||||||
|
case false:
|
||||||
|
e.Logger.Fatal(
|
||||||
|
e.Start(
|
||||||
|
fmt.Sprintf("%s:%d", cfg.Server.API.Host, cfg.Server.API.Port),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case true:
|
||||||
|
if cfg.Server.API.TLS.Certfile == "" {
|
||||||
|
log.Fatal("TLS enabled for API but no certificate file provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Server.API.TLS.Keyfile == "" {
|
||||||
|
log.Fatal("TLS enabled for UI but no private key file provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Logger.Fatal(
|
||||||
|
e.StartTLS(
|
||||||
|
fmt.Sprintf("%s:%d", cfg.Server.API.Host, cfg.Server.API.Port),
|
||||||
|
cfg.Server.API.TLS.Certfile,
|
||||||
|
cfg.Server.API.TLS.Keyfile,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// uiCmd represents the ui command
|
||||||
|
var uiCmd = &cobra.Command{
|
||||||
|
Use: "ui",
|
||||||
|
Aliases: []string{"web", "interface"},
|
||||||
|
Short: "Démarrer l'interface Web UI",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// Parse config
|
||||||
|
var cfg bottin.Config
|
||||||
|
if err := viper.Unmarshal(&cfg); err != nil {
|
||||||
|
log.Fatal("init config:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
// Middlewares
|
||||||
|
|
||||||
|
// Trailing slash
|
||||||
|
e.Pre(middleware.AddTrailingSlash())
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) {
|
||||||
|
usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Server.UI.User)) == 1
|
||||||
|
passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Server.UI.Password)) == 1
|
||||||
|
return usersMatch && passwordsMatch, nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Templating
|
||||||
|
e.Renderer = templates.NewTemplate()
|
||||||
|
|
||||||
|
// API Client
|
||||||
|
var httpClient = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: cfg.Server.UI.API.TLS.SkipVerify,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
apiClient := bottin.APIClient{
|
||||||
|
Voki: voki.New(
|
||||||
|
httpClient,
|
||||||
|
cfg.Server.UI.API.Host,
|
||||||
|
cfg.Server.UI.API.Key,
|
||||||
|
cfg.Server.UI.API.Port,
|
||||||
|
cfg.Server.UI.API.Protocol,
|
||||||
|
)}
|
||||||
|
defer apiClient.Voki.CloseIdleConnections()
|
||||||
|
|
||||||
|
// Routes
|
||||||
|
e.GET("/", func(c echo.Context) error {
|
||||||
|
pingResult, err := apiClient.GetHealth()
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(
|
||||||
|
http.StatusOK,
|
||||||
|
"index-html",
|
||||||
|
voki.MessageResponse{Message: fmt.Sprintf("impossible d'accéder au serveur API: %s", err)},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Render(
|
||||||
|
http.StatusOK,
|
||||||
|
"index-html",
|
||||||
|
voki.MessageResponse{Message: pingResult},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
e.GET("/membre/", func(c echo.Context) error {
|
||||||
|
membreID := c.QueryParam("membre_id")
|
||||||
|
switch {
|
||||||
|
case membreID == "":
|
||||||
|
return c.Render(
|
||||||
|
http.StatusOK,
|
||||||
|
"index-html",
|
||||||
|
voki.MessageResponse{Message: "❗Veuillez entrer un numéro étudiant à rechercher"},
|
||||||
|
)
|
||||||
|
case !bottin.IsMembreID(membreID):
|
||||||
|
return c.Render(
|
||||||
|
http.StatusOK,
|
||||||
|
"index-html",
|
||||||
|
voki.MessageResponse{Message: fmt.Sprintf("❗Numéro étudiant '%s' invalide", membreID)},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
membre, err := apiClient.GetMembreForDisplay(membreID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(
|
||||||
|
http.StatusOK,
|
||||||
|
"index-html",
|
||||||
|
voki.MessageResponse{Message: fmt.Sprintf("❗erreur: %s", err)},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Render(
|
||||||
|
http.StatusOK,
|
||||||
|
"index-html",
|
||||||
|
voki.MessageResponse{Message: fmt.Sprintf(`
|
||||||
|
Numéro étudiant: %s
|
||||||
|
Nom d'usage: %s
|
||||||
|
Programme: [%s] %s
|
||||||
|
`,
|
||||||
|
membre.ID,
|
||||||
|
membre.Name,
|
||||||
|
membre.ProgrammeID,
|
||||||
|
membre.ProgrammeName,
|
||||||
|
)},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
switch cfg.Server.UI.TLS.Enabled {
|
||||||
|
case false:
|
||||||
|
e.Logger.Fatal(e.Start(
|
||||||
|
fmt.Sprintf("%s:%d", cfg.Server.UI.Host, cfg.Server.UI.Port)))
|
||||||
|
case true:
|
||||||
|
if cfg.Server.UI.TLS.Certfile == "" {
|
||||||
|
log.Fatal("TLS enabled for UI but no certificate file provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Server.UI.TLS.Keyfile == "" {
|
||||||
|
log.Fatal("TLS enabled for UI but no private key file provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Logger.Fatal(
|
||||||
|
e.StartTLS(
|
||||||
|
fmt.Sprintf("%s:%d", cfg.Server.UI.Host, cfg.Server.UI.Port),
|
||||||
|
cfg.Server.UI.TLS.Certfile,
|
||||||
|
cfg.Server.UI.TLS.Keyfile,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
156
cmd/bottin/main_test.go
Normal file
156
cmd/bottin/main_test.go
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"codeberg.org/vlbeaudoin/voki/v3"
|
||||||
|
"git.agecem.com/agecem/bottin/v9/pkg/bottin"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
initConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPI(t *testing.T) {
|
||||||
|
var cfg bottin.Config
|
||||||
|
if err := viper.Unmarshal(&cfg); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//httpClient := http.DefaultClient
|
||||||
|
//defer httpClient.CloseIdleConnections()
|
||||||
|
|
||||||
|
transport := http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := http.Client{
|
||||||
|
Transport: &transport,
|
||||||
|
}
|
||||||
|
defer httpClient.CloseIdleConnections()
|
||||||
|
|
||||||
|
vokiClient := voki.New(&httpClient, "localhost", cfg.Client.API.Key, cfg.Client.API.Port, cfg.Client.API.Protocol)
|
||||||
|
apiClient := bottin.APIClient{Voki: vokiClient}
|
||||||
|
|
||||||
|
t.Run("get API health", func(t *testing.T) {
|
||||||
|
health, err := apiClient.GetHealth()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := "ok"
|
||||||
|
got := health
|
||||||
|
|
||||||
|
if want != got {
|
||||||
|
t.Errorf("want=%s got=%s", want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("insert programmes",
|
||||||
|
func(t *testing.T) {
|
||||||
|
programmes := []bottin.Programme{
|
||||||
|
{ID: "404.42", Name: "Cool programme"},
|
||||||
|
{ID: "200.10", Name: "Autre programme"},
|
||||||
|
}
|
||||||
|
t.Log("programmes:", programmes)
|
||||||
|
_, err := apiClient.InsertProgrammes(programmes...)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
testMembres := []bottin.Membre{
|
||||||
|
{
|
||||||
|
ID: "0000000",
|
||||||
|
FirstName: "Test",
|
||||||
|
LastName: "User",
|
||||||
|
ProgrammeID: "404.42",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "1234567",
|
||||||
|
FirstName: "Deadname",
|
||||||
|
LastName: "User",
|
||||||
|
PreferedName: "User, Test-Name",
|
||||||
|
ProgrammeID: "200.10",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("get programmes, max 50",
|
||||||
|
func(t *testing.T) {
|
||||||
|
programmes, err := apiClient.GetProgrammes(50)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(programmes)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("insert membres",
|
||||||
|
func(t *testing.T) {
|
||||||
|
_, err := apiClient.InsertMembres(testMembres...)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get membre",
|
||||||
|
func(t *testing.T) {
|
||||||
|
membre, err := apiClient.GetMembre(testMembres[0].ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := testMembres[0].LastName
|
||||||
|
got := membre.LastName
|
||||||
|
|
||||||
|
if want != got {
|
||||||
|
t.Errorf("want=%s got=%s", want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get invalid membre",
|
||||||
|
func(t *testing.T) {
|
||||||
|
_, err := apiClient.GetMembre("invalid")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("`invalid` should not have been accepted as value to GetMembre, but did")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("update membre prefered name",
|
||||||
|
func(t *testing.T) {
|
||||||
|
if err := apiClient.UpdateMembrePreferedName(testMembres[0].ID, "User, Galaxy"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("get membres, max 50",
|
||||||
|
func(t *testing.T) {
|
||||||
|
membres, err := apiClient.GetMembres(50)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(membres)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get membre for display",
|
||||||
|
func(t *testing.T) {
|
||||||
|
membre, err := apiClient.GetMembreForDisplay(testMembres[0].ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(membre)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get membres for display, max 5",
|
||||||
|
func(t *testing.T) {
|
||||||
|
membres, err := apiClient.GetMembresForDisplay(5)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(membres)
|
||||||
|
})
|
||||||
|
}
|
59
cmd/root.go
59
cmd/root.go
|
@ -1,59 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var cfgFile string
|
|
||||||
|
|
||||||
// rootCmd represents the base command when called without any subcommands
|
|
||||||
var rootCmd = &cobra.Command{
|
|
||||||
Use: "bottin",
|
|
||||||
Short: "Application de gestion de distribution d'agendas",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
|
||||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
|
||||||
func Execute() {
|
|
||||||
err := rootCmd.Execute()
|
|
||||||
if err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cobra.OnInitialize(initConfig)
|
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// initConfig reads in config file and ENV variables if set.
|
|
||||||
func initConfig() {
|
|
||||||
if cfgFile != "" {
|
|
||||||
// Use config file from the flag.
|
|
||||||
viper.SetConfigFile(cfgFile)
|
|
||||||
} else {
|
|
||||||
// Find home directory.
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
cobra.CheckErr(err)
|
|
||||||
|
|
||||||
// Search config in home directory with name ".bottin" (without extension).
|
|
||||||
viper.AddConfigPath(home)
|
|
||||||
viper.SetConfigType("yaml")
|
|
||||||
viper.SetConfigName(".bottin")
|
|
||||||
}
|
|
||||||
|
|
||||||
viper.SetEnvPrefix("BOTTIN")
|
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
||||||
viper.AutomaticEnv() // read in environment variables that match
|
|
||||||
|
|
||||||
// If a config file is found, read it in.
|
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
|
|
||||||
}
|
|
||||||
}
|
|
142
cmd/web.go
142
cmd/web.go
|
@ -1,142 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/subtle"
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"codeberg.org/vlbeaudoin/serpents"
|
|
||||||
"git.agecem.com/agecem/bottin/v6/data"
|
|
||||||
"git.agecem.com/agecem/bottin/v6/web"
|
|
||||||
"git.agecem.com/agecem/bottin/v6/web/webhandlers"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
"github.com/labstack/echo/v4/middleware"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
webUser string
|
|
||||||
webPassword string
|
|
||||||
webPort int
|
|
||||||
webApiHost string
|
|
||||||
webApiKey string
|
|
||||||
webApiPort int
|
|
||||||
webApiProtocol string
|
|
||||||
)
|
|
||||||
|
|
||||||
var templatesFS embed.FS
|
|
||||||
|
|
||||||
type Template struct {
|
|
||||||
templates *template.Template
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
|
||||||
return t.templates.ExecuteTemplate(w, name, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// webCmd represents the web command
|
|
||||||
var webCmd = &cobra.Command{
|
|
||||||
Use: "web",
|
|
||||||
Short: "Démarrer le client web",
|
|
||||||
Args: cobra.ExactArgs(0),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
webApiHost = viper.GetString("web.api.host")
|
|
||||||
webApiKey = viper.GetString("web.api.key")
|
|
||||||
webApiPort = viper.GetInt("web.api.port")
|
|
||||||
webApiProtocol = viper.GetString("web.api.protocol")
|
|
||||||
webPassword = viper.GetString("web.password")
|
|
||||||
webPort = viper.GetInt("web.port")
|
|
||||||
webUser = viper.GetString("web.user")
|
|
||||||
|
|
||||||
// Ping API server
|
|
||||||
|
|
||||||
client := http.DefaultClient
|
|
||||||
defer client.CloseIdleConnections()
|
|
||||||
|
|
||||||
apiClient := data.NewApiClient(client, webApiKey, webApiHost, webApiProtocol, webApiPort)
|
|
||||||
|
|
||||||
pingResult, err := apiClient.GetHealth()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println(pingResult)
|
|
||||||
|
|
||||||
e := echo.New()
|
|
||||||
|
|
||||||
// Middlewares
|
|
||||||
|
|
||||||
e.Pre(middleware.AddTrailingSlash())
|
|
||||||
|
|
||||||
e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) {
|
|
||||||
usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(webUser)) == 1
|
|
||||||
passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(webPassword)) == 1
|
|
||||||
return usersMatch && passwordsMatch, nil
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Template
|
|
||||||
|
|
||||||
t := &Template{
|
|
||||||
templates: template.Must(template.ParseFS(templatesFS, "templates/*.html")),
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Renderer = t
|
|
||||||
|
|
||||||
// Routes
|
|
||||||
|
|
||||||
handler := webhandlers.Handler{APIClient: apiClient}
|
|
||||||
|
|
||||||
e.GET("/", handler.GetIndex)
|
|
||||||
e.GET("/membre/", handler.GetMembre)
|
|
||||||
|
|
||||||
// Execution
|
|
||||||
|
|
||||||
e.Logger.Fatal(e.Start(
|
|
||||||
fmt.Sprintf(":%d", webPort)))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(webCmd)
|
|
||||||
templatesFS = web.GetTemplates()
|
|
||||||
|
|
||||||
// web.api.host
|
|
||||||
serpents.String(webCmd.Flags(),
|
|
||||||
"web.api.host", "web-api-host", "api",
|
|
||||||
"Remote API server host")
|
|
||||||
|
|
||||||
// web.api.key
|
|
||||||
serpents.String(webCmd.Flags(),
|
|
||||||
"web.api.key", "web-api-key", "bottin",
|
|
||||||
"Remote API server key")
|
|
||||||
|
|
||||||
// web.api.protocol
|
|
||||||
serpents.String(webCmd.Flags(),
|
|
||||||
"web.api.protocol", "web-api-protocol", "http",
|
|
||||||
"Remote API server protocol")
|
|
||||||
|
|
||||||
// web.api.port
|
|
||||||
serpents.Int(webCmd.Flags(),
|
|
||||||
"web.api.port", "web-api-port", 1312,
|
|
||||||
"Remote API server port")
|
|
||||||
|
|
||||||
// web.password
|
|
||||||
serpents.String(webCmd.Flags(),
|
|
||||||
"web.password", "web-password", "bottin",
|
|
||||||
"Web client password")
|
|
||||||
|
|
||||||
// web.port
|
|
||||||
serpents.Int(webCmd.Flags(),
|
|
||||||
"web.port", "web-port", 2312,
|
|
||||||
"Web client port")
|
|
||||||
|
|
||||||
// web.user
|
|
||||||
serpents.String(webCmd.Flags(),
|
|
||||||
"web.user", "web-user", "bottin",
|
|
||||||
"Web client user")
|
|
||||||
}
|
|
51
compose.yaml
Normal file
51
compose.yaml
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
name: 'bottin'
|
||||||
|
services:
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: 'docker.io/library/postgres:16'
|
||||||
|
environment:
|
||||||
|
POSTGRES_DATABASE: "${BOTTIN_SERVER_API_DB_DATABASE:?}"
|
||||||
|
POSTGRES_PASSWORD: "${BOTTIN_SERVER_API_DB_PASSWORD:?}"
|
||||||
|
POSTGRES_USER: "${BOTTIN_SERVER_API_DB_USER:?}"
|
||||||
|
volumes:
|
||||||
|
- 'db-data:/var/lib/postgresql/data'
|
||||||
|
restart: 'unless-stopped'
|
||||||
|
|
||||||
|
api:
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
build: ../..
|
||||||
|
image: 'git.agecem.com/agecem/bottin:latest'
|
||||||
|
env_file: '.env'
|
||||||
|
ports:
|
||||||
|
- '1312:1312'
|
||||||
|
volumes:
|
||||||
|
- 'api-config:/etc/bottin'
|
||||||
|
restart: 'unless-stopped'
|
||||||
|
command: ['bottin', '--config', '/etc/bottin/api.yaml', 'server', 'api']
|
||||||
|
|
||||||
|
ui:
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
build: ../..
|
||||||
|
image: 'git.agecem.com/agecem/bottin:latest'
|
||||||
|
env_file: '.env'
|
||||||
|
ports:
|
||||||
|
- '2312:2312'
|
||||||
|
volumes:
|
||||||
|
- 'ui-config:/etc/bottin'
|
||||||
|
restart: 'unless-stopped'
|
||||||
|
command: ['bottin', '--config', '/etc/bottin/ui.yaml', 'server', 'ui']
|
||||||
|
|
||||||
|
# adminer:
|
||||||
|
# image: adminer
|
||||||
|
# restart: always
|
||||||
|
# ports:
|
||||||
|
# - 8088:8080
|
||||||
|
# depends_on:
|
||||||
|
# - db
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db-data:
|
||||||
|
api-config:
|
||||||
|
ui-config:
|
|
@ -1,78 +0,0 @@
|
||||||
package data
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"codeberg.org/vlbeaudoin/voki"
|
|
||||||
"git.agecem.com/agecem/bottin/v6/models"
|
|
||||||
"git.agecem.com/agecem/bottin/v6/responses"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ApiClient struct {
|
|
||||||
Voki *voki.Voki
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewApiClientFromViper(client *http.Client) *ApiClient {
|
|
||||||
apiClientKey := viper.GetString("web.api.key")
|
|
||||||
apiClientHost := viper.GetString("web.api.host")
|
|
||||||
apiClientProtocol := viper.GetString("web.api.protocol")
|
|
||||||
apiClientPort := viper.GetInt("web.api.port")
|
|
||||||
|
|
||||||
return NewApiClient(client, apiClientKey, apiClientHost, apiClientProtocol, apiClientPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewApiClient(client *http.Client, key, host, protocol string, port int) *ApiClient {
|
|
||||||
return &ApiClient{
|
|
||||||
Voki: voki.New(client, host, key, port, protocol),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHealth allows checking for API server health
|
|
||||||
func (a *ApiClient) GetHealth() (string, error) {
|
|
||||||
var getHealthResponse responses.GetHealthResponse
|
|
||||||
err := a.Voki.Unmarshal(http.MethodGet, "/v6/health", nil, true, &getHealthResponse)
|
|
||||||
if err != nil {
|
|
||||||
return getHealthResponse.Message, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if getHealthResponse.Message == "" {
|
|
||||||
return getHealthResponse.Message, errors.New("Could not confirm that API server is up, no response message")
|
|
||||||
}
|
|
||||||
|
|
||||||
return getHealthResponse.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")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := a.Voki.Unmarshal(http.MethodGet, fmt.Sprintf("/v6/membres/%s", membreID), nil, true, &getMembreResponse)
|
|
||||||
if err != nil {
|
|
||||||
return getMembreResponse.Data.Membre, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if getMembreResponse.Data.Membre == *new(models.Membre) {
|
|
||||||
if getMembreResponse.Message != "" {
|
|
||||||
return getMembreResponse.Data.Membre, fmt.Errorf(getMembreResponse.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return getMembreResponse.Data.Membre, fmt.Errorf("Ce numéro étudiant ne correspond à aucunE membre")
|
|
||||||
}
|
|
||||||
|
|
||||||
return getMembreResponse.Data.Membre, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ApiClient) ListMembres() (r responses.ListMembresResponse, err error) {
|
|
||||||
return r, a.Voki.Unmarshal(http.MethodGet, "/v6/membres", nil, true, &r)
|
|
||||||
}
|
|
182
data/data.go
182
data/data.go
|
@ -1,182 +0,0 @@
|
||||||
package data
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.agecem.com/agecem/bottin/v6/models"
|
|
||||||
_ "github.com/jackc/pgx/stdlib"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DataClient is a postgres client based on sqlx
|
|
||||||
type DataClient struct {
|
|
||||||
PostgresConnection PostgresConnection
|
|
||||||
DB sqlx.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostgresConnection struct {
|
|
||||||
User string
|
|
||||||
Password string
|
|
||||||
Database string
|
|
||||||
Host string
|
|
||||||
Port int
|
|
||||||
SSL bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDataClientFromViper() (*DataClient, error) {
|
|
||||||
client, err := NewDataClient(
|
|
||||||
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"),
|
|
||||||
})
|
|
||||||
|
|
||||||
return client, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDataClient(connection PostgresConnection) (*DataClient, error) {
|
|
||||||
client := &DataClient{PostgresConnection: connection}
|
|
||||||
|
|
||||||
connectionString := fmt.Sprintf("postgres://%s:%s@%s:%d/%s",
|
|
||||||
client.PostgresConnection.User,
|
|
||||||
client.PostgresConnection.Password,
|
|
||||||
client.PostgresConnection.Host,
|
|
||||||
client.PostgresConnection.Port,
|
|
||||||
client.PostgresConnection.Database,
|
|
||||||
)
|
|
||||||
|
|
||||||
db, err := sqlx.Connect("pgx", connectionString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client.DB = *db
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DataClient) Seed() (int64, error) {
|
|
||||||
result, err := d.DB.Exec(models.Schema)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := result.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsertMembres inserts a slice of Membre into a database, returning the amount inserted and any error encountered
|
|
||||||
func (d *DataClient) InsertMembres(membres []models.Membre) (int64, error) {
|
|
||||||
var rowsInserted int64
|
|
||||||
tx, err := d.DB.Beginx()
|
|
||||||
if err != nil {
|
|
||||||
return rowsInserted, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
for _, membre := range membres {
|
|
||||||
if membre.ID == "" {
|
|
||||||
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) ON CONFLICT (id) DO NOTHING;", &membre)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := result.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
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 {
|
|
||||||
return rowsInserted, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
for _, programme := range programmes {
|
|
||||||
if programme.ID == "" {
|
|
||||||
return 0, errors.New("Cannot insert programme with no programme_id")
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := tx.NamedExec("INSERT INTO programmes (id, titre) VALUES (:id, :titre) ON CONFLICT DO NOTHING;", &programme)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := result.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
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 (d *DataClient) UpdateMembreName(membreID, newName string) (int64, error) {
|
|
||||||
result, err := d.DB.Exec("UPDATE membres SET prefered_name = $1 WHERE id = $2;", newName, membreID)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := result.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DataClient) GetMembres() (membres []models.Membre, err error) {
|
|
||||||
return membres, d.DB.Select(&membres, "SELECT * FROM membres;")
|
|
||||||
}
|
|
60
deployments/kubernetes/bottin-pod.yaml
Normal file
60
deployments/kubernetes/bottin-pod.yaml
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: bottin-pod
|
||||||
|
spec:
|
||||||
|
initContainers:
|
||||||
|
- name: clone
|
||||||
|
image: alpine:3.20
|
||||||
|
command: ['sh', '-c']
|
||||||
|
args:
|
||||||
|
- apk add git &&
|
||||||
|
git clone -- https://git.agecem.com/agecem/bottin /opt/bottin-src
|
||||||
|
volumeMounts:
|
||||||
|
- name: bottin-src
|
||||||
|
mountPath: /opt/bottin-src
|
||||||
|
- name: build
|
||||||
|
image: golang:1.23
|
||||||
|
env:
|
||||||
|
- name: CGO_ENABLED
|
||||||
|
value: '0'
|
||||||
|
command: ['sh', '-c']
|
||||||
|
args:
|
||||||
|
- cd /opt/bottin-src &&
|
||||||
|
go build -a -o /opt/bottin-executable/bottin
|
||||||
|
volumeMounts:
|
||||||
|
- name: bottin-src
|
||||||
|
mountPath: /opt/bottin-src
|
||||||
|
- name: bottin-executable
|
||||||
|
mountPath: /opt/bottin-executable
|
||||||
|
containers:
|
||||||
|
- name: api
|
||||||
|
image: alpine:3.20
|
||||||
|
command: ['sh', '-c']
|
||||||
|
args:
|
||||||
|
- ln -s /opt/bottin-executable/bottin /usr/bin/bottin
|
||||||
|
volumeMounts:
|
||||||
|
- name: bottin-executable
|
||||||
|
mountPath: /opt/bottin-executable
|
||||||
|
- name: bottin-secret
|
||||||
|
readOnly: true
|
||||||
|
mountPath: '/etc/bottin'
|
||||||
|
- name: ui
|
||||||
|
image: alpine:3.20
|
||||||
|
command: ['sh', '-c']
|
||||||
|
args:
|
||||||
|
- bottin --config /etc/bottin/ui.yaml server ui
|
||||||
|
volumeMounts:
|
||||||
|
- name: bottin-executable
|
||||||
|
mountPath: /opt/bottin-executable
|
||||||
|
- name: bottin-secret
|
||||||
|
readOnly: true
|
||||||
|
mountPath: '/etc/bottin'
|
||||||
|
volumes:
|
||||||
|
- name: bottin-src
|
||||||
|
emptyDir: {}
|
||||||
|
- name: bottin-executable
|
||||||
|
emptyDir: {}
|
||||||
|
- name: bottin-secret
|
||||||
|
secret:
|
||||||
|
secretName: bottin-secret
|
26
deployments/kubernetes/example-bottin-secret.yaml
Normal file
26
deployments/kubernetes/example-bottin-secret.yaml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: bottin-secret
|
||||||
|
stringData:
|
||||||
|
api.yaml: |
|
||||||
|
bottin:
|
||||||
|
server:
|
||||||
|
api:
|
||||||
|
db:
|
||||||
|
database: 'bottin'
|
||||||
|
host: 'db.example.com'
|
||||||
|
password: 'bottin'
|
||||||
|
sslmode: 'require'
|
||||||
|
user: 'bottin'
|
||||||
|
key: 'bottin'
|
||||||
|
ui.yaml: |
|
||||||
|
bottin:
|
||||||
|
server:
|
||||||
|
ui:
|
||||||
|
api:
|
||||||
|
tls:
|
||||||
|
skipverify: 'true'
|
||||||
|
key: 'bottin'
|
||||||
|
password: 'bottin'
|
||||||
|
user: 'bottin'
|
|
@ -1,57 +0,0 @@
|
||||||
services:
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: 'docker.io/library/postgres:16.1'
|
|
||||||
environment:
|
|
||||||
POSTGRES_DATABASE: "${BOTTIN_POSTGRES_DATABASE}"
|
|
||||||
POSTGRES_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}"
|
|
||||||
POSTGRES_USER: "${BOTTIN_POSTGRES_USER}"
|
|
||||||
volumes:
|
|
||||||
- 'db-data:/var/lib/postgresql/data'
|
|
||||||
restart: 'unless-stopped'
|
|
||||||
|
|
||||||
api:
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
build: .
|
|
||||||
image: 'git.agecem.com/agecem/bottin:latest'
|
|
||||||
environment:
|
|
||||||
BOTTIN_DB_DATABASE: "${BOTTIN_POSTGRES_DATABASE}"
|
|
||||||
BOTTIN_DB_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}"
|
|
||||||
BOTTIN_DB_USER: "${BOTTIN_POSTGRES_USER}"
|
|
||||||
BOTTIN_API_KEY: "${BOTTIN_API_KEY}"
|
|
||||||
ports:
|
|
||||||
- '1312:1312'
|
|
||||||
volumes:
|
|
||||||
- 'api-config:/etc/bottin'
|
|
||||||
restart: 'unless-stopped'
|
|
||||||
command: ['bottin', '--config', '/etc/bottin/api.yaml', 'api']
|
|
||||||
|
|
||||||
web:
|
|
||||||
depends_on:
|
|
||||||
- api
|
|
||||||
build: .
|
|
||||||
image: 'git.agecem.com/agecem/bottin:latest'
|
|
||||||
environment:
|
|
||||||
BOTTIN_WEB_API_KEY: "${BOTTIN_API_KEY}"
|
|
||||||
BOTTIN_WEB_PASSWORD: "${BOTTIN_WEB_PASSWORD}"
|
|
||||||
BOTTIN_WEB_USER: "${BOTTIN_WEB_USER}"
|
|
||||||
ports:
|
|
||||||
- '2312:2312'
|
|
||||||
volumes:
|
|
||||||
- 'web-config:/etc/bottin'
|
|
||||||
restart: 'unless-stopped'
|
|
||||||
command: ['bottin', '--config', '/etc/bottin/web.yaml', 'web']
|
|
||||||
|
|
||||||
# adminer:
|
|
||||||
# image: adminer
|
|
||||||
# restart: always
|
|
||||||
# ports:
|
|
||||||
# - 8088:8080
|
|
||||||
# depends_on:
|
|
||||||
# - db
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
db-data:
|
|
||||||
api-config:
|
|
||||||
web-config:
|
|
3
examples/example.csv
Normal file
3
examples/example.csv
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
programme_id;nom_programme;
|
||||||
|
000.00;test programme;
|
||||||
|
111.11;autre test programme;
|
|
58
go.mod
58
go.mod
|
@ -1,46 +1,48 @@
|
||||||
module git.agecem.com/agecem/bottin/v6
|
module git.agecem.com/agecem/bottin/v9
|
||||||
|
|
||||||
go 1.21.1
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
codeberg.org/vlbeaudoin/serpents v1.0.2
|
codeberg.org/vlbeaudoin/pave/v2 v2.0.0
|
||||||
codeberg.org/vlbeaudoin/voki v1.3.1
|
codeberg.org/vlbeaudoin/voki/v3 v3.0.0
|
||||||
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d
|
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
|
||||||
github.com/jackc/pgx v3.6.2+incompatible
|
github.com/jackc/pgx/v5 v5.6.0
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
github.com/labstack/echo/v4 v4.12.0
|
||||||
github.com/labstack/echo/v4 v4.10.2
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/viper v1.19.0
|
||||||
github.com/spf13/viper v1.16.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cockroachdb/apd v1.1.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.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/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/labstack/gommon v0.4.0 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
||||||
github.com/shopspring/decimal v1.3.1 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/spf13/afero v1.9.5 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/cast v1.5.1 // indirect
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/subosito/gotenv v1.4.2 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
golang.org/x/crypto v0.9.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/net v0.10.0 // indirect
|
golang.org/x/crypto v0.24.0 // indirect
|
||||||
golang.org/x/sys v0.8.0 // indirect
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/net v0.26.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
golang.org/x/time v0.5.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
577
go.sum
577
go.sum
|
@ -1,546 +1,115 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
codeberg.org/vlbeaudoin/pave/v2 v2.0.0 h1:hfB5KnqMMu17g5QBWgLvWOsqidrYaohRfu2LflmTrb0=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
codeberg.org/vlbeaudoin/pave/v2 v2.0.0/go.mod h1:TsTfP6IA+3Ph33vLZigeJWS5vgBPgkW1tfs3zFPfycU=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
codeberg.org/vlbeaudoin/voki/v3 v3.0.0 h1:XdF/UTe9YUNj3hYrAyEvdmIMDYLL8SkqTwPkqw1yJ2c=
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
codeberg.org/vlbeaudoin/voki/v3 v3.0.0/go.mod h1:+6LMXosAu2ijNKV04sMwkeujpH+cghZU1fydqj2y95g=
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
|
||||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
|
||||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
|
||||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
|
||||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
|
||||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
|
||||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
|
||||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
|
||||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
|
||||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
|
||||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
|
||||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
|
||||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
|
||||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
|
||||||
codeberg.org/vlbeaudoin/serpents v1.0.2 h1:mHuL+RBAMOGeiB5+ew1cRputEAnOIQNJW9o9a5Qjudo=
|
|
||||||
codeberg.org/vlbeaudoin/serpents v1.0.2/go.mod h1:3bE/R0ToABwcUJtS1VcGEBa86K5FYhrZGAbFl2qL8kQ=
|
|
||||||
codeberg.org/vlbeaudoin/voki v1.3.1 h1:TxJj3qmOys0Pbq1dPKnOEXMXKqQLQqrBYd4QqiWWXcw=
|
|
||||||
codeberg.org/vlbeaudoin/voki v1.3.1/go.mod h1:5XTLx/KiW/OfiupF3o7PAAAU/UhsPdKSrVMmtHbmkPI=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
|
||||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
|
||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
|
||||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
|
||||||
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/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d h1:KbPOUXFUDJxwZ04vbmDOc3yuruGvVO+LOa7cVER3yWw=
|
|
||||||
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
|
|
||||||
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 h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
|
||||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
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/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
|
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||||
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
|
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
|
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||||
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 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
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.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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
|
||||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
|
||||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
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/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
|
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||||
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
|
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
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/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
|
||||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
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=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
|
||||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
|
||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
|
||||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
|
||||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
|
||||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
|
||||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
|
||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
|
||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import "git.agecem.com/agecem/bottin/v6/data"
|
|
||||||
|
|
||||||
type Handler struct {
|
|
||||||
DataClient *data.DataClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(dataClient *data.DataClient) *Handler {
|
|
||||||
return &Handler{DataClient: dataClient}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.agecem.com/agecem/bottin/v6/data"
|
|
||||||
"git.agecem.com/agecem/bottin/v6/responses"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *Handler) GetHealth(c echo.Context) error {
|
|
||||||
var response responses.GetHealthResponse
|
|
||||||
|
|
||||||
dataClient, err := data.NewDataClientFromViper()
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusInternalServerError
|
|
||||||
response.Message = "Error during data.NewDataClientFromViper()"
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
defer dataClient.DB.Close()
|
|
||||||
|
|
||||||
if err = dataClient.DB.Ping(); err != nil {
|
|
||||||
response.StatusCode = http.StatusInternalServerError
|
|
||||||
response.Message = "Error during dataClient.DB.Ping()"
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
response.StatusCode = http.StatusOK
|
|
||||||
response.Message = "Bottin API v6 is ready"
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/csv"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.agecem.com/agecem/bottin/v6/models"
|
|
||||||
"git.agecem.com/agecem/bottin/v6/responses"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
|
|
||||||
"github.com/gocarina/gocsv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *Handler) PostMembres(c echo.Context) error {
|
|
||||||
var response responses.PostMembresResponse
|
|
||||||
|
|
||||||
var membres []models.Membre
|
|
||||||
|
|
||||||
switch c.Request().Header.Get("Content-Type") {
|
|
||||||
case "application/json":
|
|
||||||
if err := c.Bind(&membres); err != nil {
|
|
||||||
response.StatusCode = http.StatusBadRequest
|
|
||||||
response.Message = "Could not bind membres"
|
|
||||||
response.Error = err.Error()
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
case "text/csv":
|
|
||||||
body := c.Request().Body
|
|
||||||
if body == nil {
|
|
||||||
response.StatusCode = http.StatusBadRequest
|
|
||||||
response.Message = "Request body is empty"
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
defer body.Close()
|
|
||||||
|
|
||||||
// Parse the CSV data from the request body using gocsv.
|
|
||||||
if err := gocsv.Unmarshal(body, &membres); err != nil {
|
|
||||||
response.StatusCode = http.StatusBadRequest
|
|
||||||
response.Message = "Could not unmarshal into membres"
|
|
||||||
response.Error = err.Error()
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
response.StatusCode = http.StatusBadRequest
|
|
||||||
response.Message = "Invalid Content-Type: Please use application/json or text/csv"
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(membres) == 0 {
|
|
||||||
response.StatusCode = http.StatusOK
|
|
||||||
response.Message = "Nothing to do"
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
newMembres, err := h.DataClient.InsertMembres(membres)
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusInternalServerError
|
|
||||||
response.Message = "Could not insert membres"
|
|
||||||
response.Error = err.Error()
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
response.StatusCode = http.StatusCreated
|
|
||||||
response.Message = "Insert successful"
|
|
||||||
response.Data.MembresInserted = newMembres
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) PostProgrammes(c echo.Context) error {
|
|
||||||
var response responses.PostProgrammesResponse
|
|
||||||
|
|
||||||
var programmes []models.Programme
|
|
||||||
|
|
||||||
switch c.Request().Header.Get("Content-Type") {
|
|
||||||
case "application/json":
|
|
||||||
if err := c.Bind(&programmes); err != nil {
|
|
||||||
response.StatusCode = http.StatusBadRequest
|
|
||||||
response.Message = "Could not bind programmes"
|
|
||||||
response.Error = err.Error()
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
case "text/csv":
|
|
||||||
body := c.Request().Body
|
|
||||||
if body == nil {
|
|
||||||
response.StatusCode = http.StatusBadRequest
|
|
||||||
response.Message = "Request body is empty"
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
defer body.Close()
|
|
||||||
|
|
||||||
gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader {
|
|
||||||
r := csv.NewReader(in)
|
|
||||||
r.Comma = ';'
|
|
||||||
return r // Allows use ; as delimiter
|
|
||||||
})
|
|
||||||
|
|
||||||
// Parse the CSV data from the request body using gocsv.
|
|
||||||
if err := gocsv.Unmarshal(body, &programmes); err != nil {
|
|
||||||
response.StatusCode = http.StatusBadRequest
|
|
||||||
response.Message = "Could not unmarshal into programmes"
|
|
||||||
response.Error = err.Error()
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
response.StatusCode = http.StatusBadRequest
|
|
||||||
response.Message = "Invalid Content-Type"
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(programmes) == 0 {
|
|
||||||
response.StatusCode = http.StatusOK
|
|
||||||
response.Message = "Nothing to do"
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
newProgrammes, err := h.DataClient.InsertProgrammes(programmes)
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusInternalServerError
|
|
||||||
response.Message = "Could not insert programmes"
|
|
||||||
response.Error = err.Error()
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
response.StatusCode = http.StatusCreated
|
|
||||||
response.Message = "Insert successful"
|
|
||||||
response.Data.ProgrammesInserted = newProgrammes
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.agecem.com/agecem/bottin/v6/responses"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *Handler) ReadMembre(c echo.Context) error {
|
|
||||||
membreID := c.Param("membre_id")
|
|
||||||
|
|
||||||
membre, err := h.DataClient.GetMembre(membreID)
|
|
||||||
if err != nil {
|
|
||||||
if err.Error() == "No membre by that id was found" {
|
|
||||||
return c.JSON(http.StatusNotFound, map[string]string{
|
|
||||||
"message": "Not Found",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{
|
|
||||||
"message": "Unknown error during GetMembre",
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
|
||||||
"message": "Read successful",
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"membre": &membre,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) ListMembres(c echo.Context) error {
|
|
||||||
var r responses.ListMembresResponse
|
|
||||||
|
|
||||||
membres, err := h.DataClient.GetMembres()
|
|
||||||
if err != nil {
|
|
||||||
r.StatusCode = http.StatusInternalServerError
|
|
||||||
r.Error = err.Error()
|
|
||||||
r.Message = "Error during (*handlers.Handler).DataClient.GetMembres"
|
|
||||||
|
|
||||||
return c.JSON(r.StatusCode, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.StatusCode = http.StatusOK
|
|
||||||
|
|
||||||
switch membres := len(membres); membres {
|
|
||||||
case 0:
|
|
||||||
r.Message = "No membres returned from database"
|
|
||||||
case 1:
|
|
||||||
r.Message = "Membre returned from database"
|
|
||||||
default:
|
|
||||||
r.Message = fmt.Sprintf("%d membres returned from database", membres)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Data.Membres = membres
|
|
||||||
|
|
||||||
return c.JSON(r.StatusCode, r)
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *Handler) PostSeed(c echo.Context) error {
|
|
||||||
rows, err := h.DataClient.Seed()
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{
|
|
||||||
"message": "Seed failed",
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
|
||||||
"message": "Seed successful",
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"rows": rows,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *Handler) PutMembrePreferedName(c echo.Context) error {
|
|
||||||
membreID := c.Param("membre_id")
|
|
||||||
|
|
||||||
var newName string
|
|
||||||
|
|
||||||
err := c.Bind(&newName)
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusBadRequest, map[string]string{
|
|
||||||
"message": "Could not bind newName",
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := h.DataClient.UpdateMembreName(membreID, newName)
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{
|
|
||||||
"message": "Could not update membre name",
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if rows == 0 {
|
|
||||||
return c.JSON(http.StatusBadRequest, map[string]string{
|
|
||||||
"message": "No update was done, probably no membre by that id",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
|
||||||
"message": "Update successful",
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"rows": rows,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
7
main.go
7
main.go
|
@ -1,7 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import "git.agecem.com/agecem/bottin/v6/cmd"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cmd.Execute()
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
const Schema = `
|
|
||||||
CREATE TABLE IF NOT EXISTS programmes (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
titre TEXT
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS membres (
|
|
||||||
id VARCHAR(7) PRIMARY KEY,
|
|
||||||
last_name TEXT,
|
|
||||||
first_name TEXT,
|
|
||||||
prefered_name TEXT,
|
|
||||||
programme_id TEXT REFERENCES programmes(id)
|
|
||||||
);
|
|
||||||
`
|
|
||||||
|
|
||||||
type Programme struct {
|
|
||||||
ID string `db:"id" json:"programme_id" csv:"programme_id"`
|
|
||||||
Titre string `db:"titre" json:"nom_programme" csv:"nom_programme"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Membre struct {
|
|
||||||
ID string `db:"id" json:"membre_id" csv:"membre_id"`
|
|
||||||
LastName string `db:"last_name" json:"last_name" csv:"last_name"`
|
|
||||||
FirstName string `db:"first_name" json:"first_name" csv:"first_name"`
|
|
||||||
PreferedName string `db:"prefered_name" json:"prefered_name" csv:"prefered_name"`
|
|
||||||
ProgrammeID string `db:"programme_id" json:"programme_id" csv:"programme_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Entry interface {
|
|
||||||
Programme | Membre
|
|
||||||
}
|
|
170
pkg/bottin/client.go
Normal file
170
pkg/bottin/client.go
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
package bottin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"codeberg.org/vlbeaudoin/voki/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIClient struct {
|
||||||
|
Voki *voki.Voki
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c APIClient) GetHealth() (health string, err error) {
|
||||||
|
var request HealthGETRequest
|
||||||
|
response, err := request.Request(c.Voki)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, message := response.StatusCode(), response.Message; code >= 400 {
|
||||||
|
err = fmt.Errorf("%d: %s", code, message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Message, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c APIClient) InsertProgrammes(programmes ...Programme) (amountInserted int64, err error) {
|
||||||
|
var request ProgrammesPOSTRequest
|
||||||
|
request.Data.Programmes = programmes
|
||||||
|
|
||||||
|
response, err := request.Request(c.Voki)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, message := response.StatusCode(), response.Message; code >= 400 {
|
||||||
|
err = fmt.Errorf("%d: %s", code, message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.ProgrammesInserted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c APIClient) InsertMembres(membres ...Membre) (amountInserted int64, err error) {
|
||||||
|
var request MembresPOSTRequest
|
||||||
|
request.Data.Membres = membres
|
||||||
|
|
||||||
|
response, err := request.Request(c.Voki)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, message := response.StatusCode(), response.Message; code >= 400 {
|
||||||
|
err = fmt.Errorf("%d: %s", code, message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.MembresInserted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c APIClient) GetMembre(membreID string) (membre Membre, err error) {
|
||||||
|
var request MembreGETRequest
|
||||||
|
request.Param.MembreID = membreID
|
||||||
|
|
||||||
|
response, err := request.Request(c.Voki)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, message := response.StatusCode(), response.Message; code >= 400 {
|
||||||
|
err = fmt.Errorf("%d: %s", code, message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.Membre, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c APIClient) GetMembres(limit int) (membres []Membre, err error) {
|
||||||
|
var request MembresGETRequest
|
||||||
|
|
||||||
|
request.Query.Limit = limit
|
||||||
|
|
||||||
|
response, err := request.Request(c.Voki)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, message := response.StatusCode(), response.Message; code >= 400 {
|
||||||
|
err = fmt.Errorf("%d: %s", code, message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.Membres, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c APIClient) GetProgrammes(limit int) (programmes []Programme, err error) {
|
||||||
|
var request ProgrammesGETRequest
|
||||||
|
|
||||||
|
request.Query.Limit = limit
|
||||||
|
|
||||||
|
response, err := request.Request(c.Voki)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, message := response.StatusCode(), response.Message; code >= 400 {
|
||||||
|
err = fmt.Errorf("%d: %s", code, message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.Programmes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c APIClient) UpdateMembrePreferedName(membreID string, name string) (err error) {
|
||||||
|
var request MembrePreferedNamePUTRequest
|
||||||
|
|
||||||
|
if !IsMembreID(membreID) {
|
||||||
|
return fmt.Errorf("Numéro étudiant '%s' invalide", membreID)
|
||||||
|
}
|
||||||
|
request.Param.MembreID = membreID
|
||||||
|
request.Data.PreferedName = name
|
||||||
|
|
||||||
|
response, err := request.Request(c.Voki)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, message := response.StatusCode(), response.Message; code >= 400 {
|
||||||
|
err = fmt.Errorf("%d: %s", code, message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c APIClient) GetMembreForDisplay(membreID string) (membre MembreForDisplay, err error) {
|
||||||
|
var request MembreDisplayGETRequest
|
||||||
|
request.Param.MembreID = membreID
|
||||||
|
|
||||||
|
response, err := request.Request(c.Voki)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, message := response.StatusCode(), response.Message; code >= 400 {
|
||||||
|
err = fmt.Errorf("%d: %s", code, message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.Membre, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c APIClient) GetMembresForDisplay(limit int) (membres []MembreForDisplay, err error) {
|
||||||
|
var request MembresDisplayGETRequest
|
||||||
|
|
||||||
|
request.Query.Limit = limit
|
||||||
|
|
||||||
|
response, err := request.Request(c.Voki)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, message := response.StatusCode(), response.Message; code >= 400 {
|
||||||
|
err = fmt.Errorf("%d: %s", code, message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.Membres, nil
|
||||||
|
}
|
53
pkg/bottin/config.go
Normal file
53
pkg/bottin/config.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package bottin
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Client struct {
|
||||||
|
API struct {
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Key string `yaml:"key"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Protocol string `yaml:"protocol"`
|
||||||
|
} `yaml:"api"`
|
||||||
|
} `yaml:"client"`
|
||||||
|
|
||||||
|
Server struct {
|
||||||
|
API struct {
|
||||||
|
DB struct {
|
||||||
|
Database string `yaml:"database"`
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
SSLMode string `yaml:"sslmode"`
|
||||||
|
User string `yaml:"user"`
|
||||||
|
} `yaml:"db"`
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Key string `yaml:"key"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
TLS struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
Certfile string `yaml:"certfile"`
|
||||||
|
Keyfile string `yaml:"keyfile"`
|
||||||
|
} `yaml:"tls"`
|
||||||
|
} `yaml:"api"`
|
||||||
|
UI struct {
|
||||||
|
API struct {
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Key string `yaml:"key"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Protocol string `yaml:"protocol"`
|
||||||
|
TLS struct {
|
||||||
|
SkipVerify bool `yaml:"skipverify"`
|
||||||
|
} `yaml:"tls"`
|
||||||
|
} `yaml:"api"`
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
TLS struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
Certfile string `yaml:"certfile"`
|
||||||
|
Keyfile string `yaml:"keyfile"`
|
||||||
|
} `yaml:"tls"`
|
||||||
|
User string `yaml:"user"`
|
||||||
|
} `yaml:"ui"`
|
||||||
|
} `yaml:"server"`
|
||||||
|
}
|
353
pkg/bottin/db.go
Normal file
353
pkg/bottin/db.go
Normal file
|
@ -0,0 +1,353 @@
|
||||||
|
package bottin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.agecem.com/agecem/bottin/v9/queries"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostgresClient struct {
|
||||||
|
//TODO move context out of client
|
||||||
|
Ctx context.Context
|
||||||
|
Pool *pgxpool.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *PostgresClient) CreateOrReplaceSchema() error {
|
||||||
|
_, err := db.Pool.Exec(db.Ctx, queries.SQLSchema())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *PostgresClient) CreateOrReplaceViews() error {
|
||||||
|
_, err := db.Pool.Exec(db.Ctx, queries.SQLViews())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertMembres inserts a slice of Membre into a database, returning the amount inserted and any error encountered
|
||||||
|
func (d *PostgresClient) InsertMembres(membres ...Membre) (inserted int64, err error) {
|
||||||
|
select {
|
||||||
|
case <-d.Ctx.Done():
|
||||||
|
return inserted, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err())
|
||||||
|
default:
|
||||||
|
tx, err := d.Pool.Begin(d.Ctx)
|
||||||
|
if err != nil {
|
||||||
|
return inserted, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(d.Ctx)
|
||||||
|
|
||||||
|
for i, membre := range membres {
|
||||||
|
if membre.ID == "" {
|
||||||
|
return inserted, fmt.Errorf("insertion ligne %d: membre requiert numéro étudiant valide", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := tx.Exec(d.Ctx, `
|
||||||
|
INSERT INTO membres
|
||||||
|
(id, last_name, first_name, prefered_name, programme_id)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4, $5)
|
||||||
|
ON CONFLICT (id) DO NOTHING;`,
|
||||||
|
membre.ID,
|
||||||
|
membre.LastName,
|
||||||
|
membre.FirstName,
|
||||||
|
membre.PreferedName,
|
||||||
|
membre.ProgrammeID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inserted += result.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.Commit(d.Ctx); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return inserted, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *PostgresClient) InsertProgrammes(programmes ...Programme) (inserted int64, err error) {
|
||||||
|
select {
|
||||||
|
case <-d.Ctx.Done():
|
||||||
|
return inserted, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err())
|
||||||
|
default:
|
||||||
|
tx, err := d.Pool.Begin(d.Ctx)
|
||||||
|
if err != nil {
|
||||||
|
return inserted, err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(d.Ctx)
|
||||||
|
|
||||||
|
for _, programme := range programmes {
|
||||||
|
if programme.ID == "" {
|
||||||
|
return 0, fmt.Errorf("Cannot insert programme with no programme_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := tx.Exec(d.Ctx, `
|
||||||
|
INSERT INTO programmes
|
||||||
|
(id, name)
|
||||||
|
VALUES ($1, $2) ON CONFLICT DO NOTHING;`,
|
||||||
|
programme.ID,
|
||||||
|
programme.Name)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inserted += result.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(d.Ctx); err != nil {
|
||||||
|
return inserted, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return inserted, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *PostgresClient) GetMembre(membreID string) (membre Membre, err error) {
|
||||||
|
select {
|
||||||
|
case <-d.Ctx.Done():
|
||||||
|
err = fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err())
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
if err = d.Pool.QueryRow(d.Ctx, `
|
||||||
|
SELECT
|
||||||
|
"membres".id,
|
||||||
|
"membres".last_name,
|
||||||
|
"membres".first_name,
|
||||||
|
"membres".prefered_name,
|
||||||
|
"membres".programme_id
|
||||||
|
FROM
|
||||||
|
"membres"
|
||||||
|
WHERE
|
||||||
|
"membres".id = $1
|
||||||
|
LIMIT
|
||||||
|
1;
|
||||||
|
`, membreID).Scan(
|
||||||
|
&membre.ID,
|
||||||
|
&membre.LastName,
|
||||||
|
&membre.FirstName,
|
||||||
|
&membre.PreferedName,
|
||||||
|
&membre.ProgrammeID,
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if membre.ID == "" {
|
||||||
|
return membre, fmt.Errorf("Aucun membre trouvé avec numéro '%s'", membre.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return membre, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (d *PostgresClient) UpdateMembreName(membreID, newName string) (int64, error) {
|
||||||
|
result, err := d.Pool.Exec("UPDATE membres SET prefered_name = $1 WHERE id = $2;", newName, membreID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows, nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (d *PostgresClient) GetMembres(limit int) (membres []Membre, err error) {
|
||||||
|
select {
|
||||||
|
case <-d.Ctx.Done():
|
||||||
|
return nil, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err())
|
||||||
|
default:
|
||||||
|
rows, err := d.Pool.Query(d.Ctx, `
|
||||||
|
SELECT
|
||||||
|
"membres".id,
|
||||||
|
"membres".last_name,
|
||||||
|
"membres".first_name,
|
||||||
|
"membres".prefered_name,
|
||||||
|
"membres".programme_id
|
||||||
|
FROM
|
||||||
|
"membres"
|
||||||
|
ORDER BY
|
||||||
|
"membres".id
|
||||||
|
LIMIT
|
||||||
|
$1;
|
||||||
|
`, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var membre Membre
|
||||||
|
|
||||||
|
if err = rows.Scan(
|
||||||
|
&membre.ID,
|
||||||
|
&membre.LastName,
|
||||||
|
&membre.FirstName,
|
||||||
|
&membre.PreferedName,
|
||||||
|
&membre.ProgrammeID,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
membres = append(membres, membre)
|
||||||
|
}
|
||||||
|
if rows.Err() != nil {
|
||||||
|
return membres, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return membres, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *PostgresClient) GetProgrammes(limit int) (programmes []Programme, err error) {
|
||||||
|
select {
|
||||||
|
case <-d.Ctx.Done():
|
||||||
|
return nil, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err())
|
||||||
|
default:
|
||||||
|
rows, err := d.Pool.Query(d.Ctx, `
|
||||||
|
SELECT
|
||||||
|
"programmes".id,
|
||||||
|
"programmes".name
|
||||||
|
FROM
|
||||||
|
"programmes"
|
||||||
|
ORDER BY
|
||||||
|
"programmes".id
|
||||||
|
LIMIT
|
||||||
|
$1;
|
||||||
|
`, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var programme Programme
|
||||||
|
|
||||||
|
if err = rows.Scan(
|
||||||
|
&programme.ID,
|
||||||
|
&programme.Name,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
programmes = append(programmes, programme)
|
||||||
|
}
|
||||||
|
if rows.Err() != nil {
|
||||||
|
return programmes, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return programmes, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *PostgresClient) UpdateMembrePreferedName(membreID string, name string) (err error) {
|
||||||
|
select {
|
||||||
|
case <-d.Ctx.Done():
|
||||||
|
return fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err())
|
||||||
|
default:
|
||||||
|
if !IsMembreID(membreID) {
|
||||||
|
return fmt.Errorf("Numéro étudiant '%s' invalide", membreID)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = d.Pool.Exec(d.Ctx, `
|
||||||
|
UPDATE
|
||||||
|
"membres"
|
||||||
|
SET
|
||||||
|
prefered_name = $1
|
||||||
|
WHERE
|
||||||
|
"membres".id = $2;
|
||||||
|
`, name, membreID)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *PostgresClient) GetMembreForDisplay(membreID string) (membre MembreForDisplay, err error) {
|
||||||
|
select {
|
||||||
|
case <-d.Ctx.Done():
|
||||||
|
err = fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err())
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
if err = d.Pool.QueryRow(d.Ctx, `
|
||||||
|
SELECT
|
||||||
|
"membres_for_display".id,
|
||||||
|
"membres_for_display".name,
|
||||||
|
"membres_for_display".programme_id,
|
||||||
|
"membres_for_display".programme_name
|
||||||
|
FROM
|
||||||
|
"membres_for_display"
|
||||||
|
WHERE
|
||||||
|
"membres_for_display".id = $1
|
||||||
|
LIMIT
|
||||||
|
1;
|
||||||
|
`, membreID).Scan(
|
||||||
|
&membre.ID,
|
||||||
|
&membre.Name,
|
||||||
|
&membre.ProgrammeID,
|
||||||
|
&membre.ProgrammeName,
|
||||||
|
); err != nil {
|
||||||
|
if err == pgx.ErrNoRows {
|
||||||
|
err = fmt.Errorf("Numéro étudiant valide mais aucun·e membre trouvé·e")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if membre.ID == "" {
|
||||||
|
return membre, fmt.Errorf("Aucun membre trouvé avec numéro '%s'", membre.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return membre, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *PostgresClient) GetMembresForDisplay(limit int) (membres []MembreForDisplay, err error) {
|
||||||
|
select {
|
||||||
|
case <-d.Ctx.Done():
|
||||||
|
return nil, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err())
|
||||||
|
default:
|
||||||
|
rows, err := d.Pool.Query(d.Ctx, `
|
||||||
|
SELECT
|
||||||
|
"membres_for_display".id,
|
||||||
|
"membres_for_display".name,
|
||||||
|
"membres_for_display".programme_id,
|
||||||
|
"membres_for_display".programme_name
|
||||||
|
FROM
|
||||||
|
"membres_for_display"
|
||||||
|
ORDER BY
|
||||||
|
"membres_for_display".id
|
||||||
|
LIMIT
|
||||||
|
$1;
|
||||||
|
`, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var membre MembreForDisplay
|
||||||
|
|
||||||
|
if err = rows.Scan(
|
||||||
|
&membre.ID,
|
||||||
|
&membre.Name,
|
||||||
|
&membre.ProgrammeID,
|
||||||
|
&membre.ProgrammeName,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
membres = append(membres, membre)
|
||||||
|
}
|
||||||
|
if rows.Err() != nil {
|
||||||
|
return membres, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return membres, nil
|
||||||
|
}
|
||||||
|
}
|
38
pkg/bottin/entity.go
Normal file
38
pkg/bottin/entity.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package bottin
|
||||||
|
|
||||||
|
import "unicode"
|
||||||
|
|
||||||
|
type Programme struct {
|
||||||
|
ID string `db:"id" json:"programme_id" csv:"programme_id"`
|
||||||
|
Name string `db:"name" json:"nom_programme" csv:"nom_programme"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Membre struct {
|
||||||
|
ID string `db:"id" json:"membre_id" csv:"membre_id"`
|
||||||
|
LastName string `db:"last_name" json:"last_name" csv:"last_name"`
|
||||||
|
FirstName string `db:"first_name" json:"first_name" csv:"first_name"`
|
||||||
|
PreferedName string `db:"prefered_name" json:"prefered_name" csv:"prefered_name"`
|
||||||
|
ProgrammeID string `db:"programme_id" json:"programme_id" csv:"programme_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MembreForDisplay maps to the `membres_for_display` view declared in `sql/views.sql`
|
||||||
|
type MembreForDisplay struct {
|
||||||
|
ID string `db:"id" json:"membre_id" csv:"membre_id"`
|
||||||
|
Name string `db:"name" json:"name" csv:"name"`
|
||||||
|
ProgrammeID string `db:"programme_id" json:"programme_id" csv:"programme_id"`
|
||||||
|
ProgrammeName string `db:"programme_name" json:"programme_name" csv:"programme_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsMembreID(membre_id string) bool {
|
||||||
|
if len(membre_id) != 7 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, character := range membre_id {
|
||||||
|
if !unicode.IsDigit(character) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
350
pkg/bottin/request.go
Normal file
350
pkg/bottin/request.go
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
package bottin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/vlbeaudoin/voki/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ voki.Requester[HealthGETResponse] = HealthGETRequest{}
|
||||||
|
|
||||||
|
type HealthGETRequest struct{}
|
||||||
|
|
||||||
|
func (request HealthGETRequest) Complete() bool { return true }
|
||||||
|
|
||||||
|
func (request HealthGETRequest) Request(v *voki.Voki) (response HealthGETResponse, err error) {
|
||||||
|
if !request.Complete() {
|
||||||
|
err = fmt.Errorf("Incomplete HealthGET request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode, body, err := v.CallAndParse(
|
||||||
|
http.MethodGet,
|
||||||
|
"/api/v9/health/",
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("%d: %s", statusCode, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.SetStatusCode(statusCode)
|
||||||
|
|
||||||
|
if err = json.Unmarshal(body, &response); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ voki.Requester[ProgrammesPOSTResponse] = ProgrammesPOSTRequest{}
|
||||||
|
|
||||||
|
type ProgrammesPOSTRequest struct {
|
||||||
|
Data struct {
|
||||||
|
Programmes []Programme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request ProgrammesPOSTRequest) Complete() bool {
|
||||||
|
return len(request.Data.Programmes) != 0
|
||||||
|
}
|
||||||
|
func (request ProgrammesPOSTRequest) Request(v *voki.Voki) (response ProgrammesPOSTResponse, err error) {
|
||||||
|
if !request.Complete() {
|
||||||
|
err = fmt.Errorf("Incomplete ProgrammesPOSTRequest")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err = json.NewEncoder(&buf).Encode(request.Data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode, body, err := v.CallAndParse(
|
||||||
|
http.MethodPost,
|
||||||
|
"/api/v9/programme/",
|
||||||
|
&buf,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("code=%d err=%s", statusCode, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.SetStatusCode(statusCode)
|
||||||
|
if err = json.Unmarshal(body, &response); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ voki.Requester[MembresPOSTResponse] = MembresPOSTRequest{}
|
||||||
|
|
||||||
|
type MembresPOSTRequest struct {
|
||||||
|
Data struct {
|
||||||
|
Membres []Membre
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request MembresPOSTRequest) Complete() bool {
|
||||||
|
return len(request.Data.Membres) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request MembresPOSTRequest) Request(v *voki.Voki) (response MembresPOSTResponse, err error) {
|
||||||
|
if !request.Complete() {
|
||||||
|
err = fmt.Errorf("Incomplete MembresPOSTRequest")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err = json.NewEncoder(&buf).Encode(request.Data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode, body, err := v.CallAndParse(
|
||||||
|
http.MethodPost,
|
||||||
|
"/api/v9/membre/",
|
||||||
|
&buf,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("code=%d err=%s", statusCode, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.SetStatusCode(statusCode)
|
||||||
|
if err = json.Unmarshal(body, &response); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ voki.Requester[MembreGETResponse] = MembreGETRequest{}
|
||||||
|
|
||||||
|
type MembreGETRequest struct {
|
||||||
|
Param struct {
|
||||||
|
MembreID string `json:"membre_id" param:"membre_id"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request MembreGETRequest) Complete() bool {
|
||||||
|
return request.Param.MembreID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request MembreGETRequest) Request(v *voki.Voki) (response MembreGETResponse, err error) {
|
||||||
|
if !request.Complete() {
|
||||||
|
err = fmt.Errorf("Incomplete MembreGETRequest")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if id := request.Param.MembreID; !IsMembreID(id) {
|
||||||
|
err = fmt.Errorf("MembreID '%s' invalide", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode, body, err := v.CallAndParse(
|
||||||
|
http.MethodGet,
|
||||||
|
fmt.Sprintf("/api/v9/membre/%s/", request.Param.MembreID),
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("code=%d err=%s", statusCode, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.SetStatusCode(statusCode)
|
||||||
|
if err = json.Unmarshal(body, &response); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ voki.Requester[MembresGETResponse] = MembresGETRequest{}
|
||||||
|
|
||||||
|
type MembresGETRequest struct {
|
||||||
|
Query struct {
|
||||||
|
Limit int `json:"limit" query:"limit"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request MembresGETRequest) Complete() bool { return true }
|
||||||
|
|
||||||
|
func (request MembresGETRequest) Request(v *voki.Voki) (response MembresGETResponse, err error) {
|
||||||
|
if !request.Complete() {
|
||||||
|
err = fmt.Errorf("Incomplete MembresGETRequest")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode, body, err := v.CallAndParse(
|
||||||
|
http.MethodGet,
|
||||||
|
fmt.Sprintf("/api/v9/membre/?limit=%d", request.Query.Limit),
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("code=%d err=%s", statusCode, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.SetStatusCode(statusCode)
|
||||||
|
if err = json.Unmarshal(body, &response); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ voki.Requester[MembrePreferedNamePUTResponse] = MembrePreferedNamePUTRequest{}
|
||||||
|
|
||||||
|
type MembrePreferedNamePUTRequest struct {
|
||||||
|
Data struct {
|
||||||
|
PreferedName string `json:"prefered_name"`
|
||||||
|
} `json:"data"`
|
||||||
|
Param struct {
|
||||||
|
MembreID string `json:"membre_id" param:"membre_id"`
|
||||||
|
} `json:"param"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request MembrePreferedNamePUTRequest) Complete() bool {
|
||||||
|
return IsMembreID(request.Param.MembreID) && len(request.Data.PreferedName) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request MembrePreferedNamePUTRequest) Request(v *voki.Voki) (response MembrePreferedNamePUTResponse, err error) {
|
||||||
|
if !request.Complete() {
|
||||||
|
err = fmt.Errorf("Incomplete MembrePreferedNamePUTRequest")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err = json.NewEncoder(&buf).Encode(request.Data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode, body, err := v.CallAndParse(
|
||||||
|
http.MethodPut,
|
||||||
|
fmt.Sprintf("/api/v9/membre/%s/prefered_name/", request.Param.MembreID),
|
||||||
|
&buf,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("code=%d err=%s", statusCode, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.SetStatusCode(statusCode)
|
||||||
|
if err = json.Unmarshal(body, &response); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ voki.Requester[ProgrammesGETResponse] = ProgrammesGETRequest{}
|
||||||
|
|
||||||
|
type ProgrammesGETRequest struct {
|
||||||
|
Query struct {
|
||||||
|
Limit int `json:"limit" query:"limit"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request ProgrammesGETRequest) Complete() bool { return true }
|
||||||
|
|
||||||
|
func (request ProgrammesGETRequest) Request(v *voki.Voki) (response ProgrammesGETResponse, err error) {
|
||||||
|
if !request.Complete() {
|
||||||
|
err = fmt.Errorf("Incomplete ProgrammesGETRequest")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode, body, err := v.CallAndParse(
|
||||||
|
http.MethodGet,
|
||||||
|
fmt.Sprintf("/api/v9/programme/?limit=%d", request.Query.Limit),
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("code=%d err=%s", statusCode, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.SetStatusCode(statusCode)
|
||||||
|
if err = json.Unmarshal(body, &response); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ voki.Requester[MembresDisplayGETResponse] = MembresDisplayGETRequest{}
|
||||||
|
|
||||||
|
type MembresDisplayGETRequest struct {
|
||||||
|
Query struct {
|
||||||
|
Limit int `json:"limit" query:"limit"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request MembresDisplayGETRequest) Complete() bool { return true }
|
||||||
|
|
||||||
|
func (request MembresDisplayGETRequest) Request(v *voki.Voki) (response MembresDisplayGETResponse, err error) {
|
||||||
|
if !request.Complete() {
|
||||||
|
err = fmt.Errorf("Incomplete MembresDisplayGETRequest")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode, body, err := v.CallAndParse(
|
||||||
|
http.MethodGet,
|
||||||
|
fmt.Sprintf("/api/v9/membre/display/?limit=%d", request.Query.Limit),
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("code=%d err=%s", statusCode, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.SetStatusCode(statusCode)
|
||||||
|
if err = json.Unmarshal(body, &response); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ voki.Requester[MembreDisplayGETResponse] = MembreDisplayGETRequest{}
|
||||||
|
|
||||||
|
type MembreDisplayGETRequest struct {
|
||||||
|
Param struct {
|
||||||
|
MembreID string `json:"membre_id" param:"membre_id"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request MembreDisplayGETRequest) Complete() bool {
|
||||||
|
return request.Param.MembreID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (request MembreDisplayGETRequest) Request(v *voki.Voki) (response MembreDisplayGETResponse, err error) {
|
||||||
|
if !request.Complete() {
|
||||||
|
err = fmt.Errorf("Incomplete MembreDisplayGETRequest")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if id := request.Param.MembreID; !IsMembreID(id) {
|
||||||
|
err = fmt.Errorf("MembreID '%s' invalide", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode, body, err := v.CallAndParse(
|
||||||
|
http.MethodGet,
|
||||||
|
fmt.Sprintf("/api/v9/membre/%s/display/", request.Param.MembreID),
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("code=%d err=%s", statusCode, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.SetStatusCode(statusCode)
|
||||||
|
if err = json.Unmarshal(body, &response); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
91
pkg/bottin/response.go
Normal file
91
pkg/bottin/response.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package bottin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"codeberg.org/vlbeaudoin/voki/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIResponse struct {
|
||||||
|
voki.MessageResponse
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (R APIResponse) StatusCode() int { return R.statusCode }
|
||||||
|
|
||||||
|
func (R *APIResponse) SetStatusCode(code int) error {
|
||||||
|
if code <= 0 {
|
||||||
|
return fmt.Errorf("Cannot set status code to %d", code)
|
||||||
|
}
|
||||||
|
R.statusCode = code
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type HealthGETResponse struct {
|
||||||
|
APIResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type MembreGETResponse struct {
|
||||||
|
APIResponse
|
||||||
|
Data MembreGETResponseData `json:"data"`
|
||||||
|
}
|
||||||
|
type MembreGETResponseData struct {
|
||||||
|
Membre Membre `json:"membre"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MembrePreferedNamePUTResponse struct {
|
||||||
|
APIResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type MembresGETResponse struct {
|
||||||
|
APIResponse
|
||||||
|
Data MembresGETResponseData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MembresGETResponseData struct {
|
||||||
|
Membres []Membre `json:"membres"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MembreDisplayGETResponse struct {
|
||||||
|
APIResponse
|
||||||
|
Data MembreDisplayGETResponseData `json:"data"`
|
||||||
|
}
|
||||||
|
type MembreDisplayGETResponseData struct {
|
||||||
|
Membre MembreForDisplay `json:"membre_for_display"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MembresDisplayGETResponse struct {
|
||||||
|
APIResponse
|
||||||
|
Data MembresDisplayGETResponseData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MembresDisplayGETResponseData struct {
|
||||||
|
Membres []MembreForDisplay `json:"membres_for_display"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MembresPOSTResponse struct {
|
||||||
|
APIResponse
|
||||||
|
Data MembresPOSTResponseData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MembresPOSTResponseData struct {
|
||||||
|
MembresInserted int64 `json:"membres_inserted"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProgrammesPOSTResponse struct {
|
||||||
|
APIResponse
|
||||||
|
Data ProgrammesPOSTResponseData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProgrammesPOSTResponseData struct {
|
||||||
|
ProgrammesInserted int64 `json:"programmes_inserted"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProgrammesGETResponse struct {
|
||||||
|
APIResponse
|
||||||
|
Data ProgrammesGETResponseData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProgrammesGETResponseData struct {
|
||||||
|
Programmes []Programme `json:"programmes"`
|
||||||
|
}
|
465
pkg/bottin/routes.go
Normal file
465
pkg/bottin/routes.go
Normal file
|
@ -0,0 +1,465 @@
|
||||||
|
package bottin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"codeberg.org/vlbeaudoin/pave/v2"
|
||||||
|
"codeberg.org/vlbeaudoin/voki/v3"
|
||||||
|
"github.com/gocarina/gocsv"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error {
|
||||||
|
_ = db
|
||||||
|
_ = cfg
|
||||||
|
|
||||||
|
apiPath := "/api/v9"
|
||||||
|
apiGroup := e.Group(apiPath)
|
||||||
|
p := pave.New()
|
||||||
|
if err := pave.EchoRegister[HealthGETRequest](
|
||||||
|
apiGroup,
|
||||||
|
&p,
|
||||||
|
apiPath,
|
||||||
|
http.MethodGet,
|
||||||
|
"/health/",
|
||||||
|
"Get API server health",
|
||||||
|
"HealthGET", func(c echo.Context) error {
|
||||||
|
var request, response = HealthGETRequest{}, HealthGETResponse{}
|
||||||
|
if !request.Complete() {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = "Incomplete HealthGET request received"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
if err := response.SetStatusCode(http.StatusOK); err != nil {
|
||||||
|
var response voki.ResponseInternalServerError
|
||||||
|
response.Message = fmt.Sprintf("handler: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Message = "ok"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pave.EchoRegister[ProgrammesPOSTRequest](
|
||||||
|
apiGroup,
|
||||||
|
&p,
|
||||||
|
apiPath,
|
||||||
|
http.MethodPost,
|
||||||
|
"/programme/",
|
||||||
|
"Insert programmes",
|
||||||
|
"ProgrammesPOST", func(c echo.Context) error {
|
||||||
|
var request, response = ProgrammesPOSTRequest{}, ProgrammesPOSTResponse{}
|
||||||
|
|
||||||
|
switch contentType := c.Request().Header.Get("Content-Type"); contentType {
|
||||||
|
case "application/json":
|
||||||
|
if err := c.Bind(&request.Data); err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("parse request body: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
case "text/csv":
|
||||||
|
body := c.Request().Body
|
||||||
|
if body == nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = "empty request body cannot be parsed"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader {
|
||||||
|
r := csv.NewReader(in)
|
||||||
|
r.Comma = ';'
|
||||||
|
return r // Allows use ; as delimiter
|
||||||
|
})
|
||||||
|
|
||||||
|
// Parse CSV data using gocsv
|
||||||
|
if err := gocsv.Unmarshal(body, &request.Data.Programmes); err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("parse programmes from csv: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("cannot parse body with content-type: %s", contentType)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !request.Complete() {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = "Incomplete ProgrammesPOST request received"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
amountInserted, err := db.InsertProgrammes(request.Data.Programmes...)
|
||||||
|
if err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("db: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
response.Data.ProgrammesInserted = amountInserted
|
||||||
|
|
||||||
|
if err := response.SetStatusCode(http.StatusOK); err != nil {
|
||||||
|
var response voki.ResponseInternalServerError
|
||||||
|
response.Message = fmt.Sprintf("handler: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Message = "ok"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pave.EchoRegister[MembresPOSTRequest](
|
||||||
|
apiGroup,
|
||||||
|
&p,
|
||||||
|
apiPath,
|
||||||
|
http.MethodPost,
|
||||||
|
"/membre/",
|
||||||
|
"Insert membres",
|
||||||
|
"MembresPOST", func(c echo.Context) error {
|
||||||
|
var request, response = MembresPOSTRequest{}, MembresPOSTResponse{}
|
||||||
|
|
||||||
|
switch contentType := c.Request().Header.Get("Content-Type"); contentType {
|
||||||
|
case "application/json":
|
||||||
|
if err := c.Bind(&request.Data); err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("parse request body: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
case "text/csv":
|
||||||
|
body := c.Request().Body
|
||||||
|
if body == nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = "empty request body cannot be parsed"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
|
||||||
|
gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader {
|
||||||
|
r := csv.NewReader(in)
|
||||||
|
r.Comma = ';'
|
||||||
|
return r // Allows use ; as delimiter
|
||||||
|
})
|
||||||
|
|
||||||
|
// Parse CSV data using gocsv
|
||||||
|
if err := gocsv.Unmarshal(body, &request.Data.Membres); err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("parse membres from csv: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("cannot parse body with content-type: %s", contentType)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !request.Complete() {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = "Incomplete MembresPOST request received"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
amountInserted, err := db.InsertMembres(request.Data.Membres...)
|
||||||
|
if err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("db: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
response.Data.MembresInserted = amountInserted
|
||||||
|
|
||||||
|
if err := response.SetStatusCode(http.StatusOK); err != nil {
|
||||||
|
var response voki.ResponseInternalServerError
|
||||||
|
response.Message = fmt.Sprintf("handler: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Message = "ok"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pave.EchoRegister[MembreGETRequest](
|
||||||
|
apiGroup,
|
||||||
|
&p,
|
||||||
|
apiPath,
|
||||||
|
http.MethodGet,
|
||||||
|
"/membre/:membre_id/",
|
||||||
|
"Get membre",
|
||||||
|
"MembreGET", func(c echo.Context) error {
|
||||||
|
var request, response = MembreGETRequest{}, MembreGETResponse{}
|
||||||
|
|
||||||
|
request.Param.MembreID = c.Param("membre_id")
|
||||||
|
|
||||||
|
if !request.Complete() {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = "Incomplete MembreGET request received"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
membre, err := db.GetMembre(request.Param.MembreID)
|
||||||
|
if err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("db: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
response.Data.Membre = membre
|
||||||
|
|
||||||
|
if err := response.SetStatusCode(http.StatusOK); err != nil {
|
||||||
|
var response voki.ResponseInternalServerError
|
||||||
|
response.Message = fmt.Sprintf("handler: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Message = "ok"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pave.EchoRegister[MembresGETRequest](
|
||||||
|
apiGroup,
|
||||||
|
&p,
|
||||||
|
apiPath,
|
||||||
|
http.MethodGet,
|
||||||
|
"/membre/",
|
||||||
|
"Get membres",
|
||||||
|
"MembresGET", func(c echo.Context) (err error) {
|
||||||
|
var request, response = MembresGETRequest{}, MembresGETResponse{}
|
||||||
|
|
||||||
|
queryLimit := c.QueryParam("limit")
|
||||||
|
if queryLimit != "" {
|
||||||
|
request.Query.Limit, err = strconv.Atoi(queryLimit)
|
||||||
|
if err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("parsing limit: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//TODO cfg.Server.API.DefaultLimit
|
||||||
|
//TODO cfg.Client.API.Limit
|
||||||
|
request.Query.Limit = 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
if !request.Complete() {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = "Incomplete MembresGET request received"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Data.Membres, err = db.GetMembres(request.Query.Limit)
|
||||||
|
if err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("db: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := response.SetStatusCode(http.StatusOK); err != nil {
|
||||||
|
var response voki.ResponseInternalServerError
|
||||||
|
response.Message = fmt.Sprintf("handler: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Message = "ok"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pave.EchoRegister[ProgrammesGETRequest](
|
||||||
|
apiGroup,
|
||||||
|
&p,
|
||||||
|
apiPath,
|
||||||
|
http.MethodGet,
|
||||||
|
"/programme/",
|
||||||
|
"Get programmes",
|
||||||
|
"ProgrammesGET", func(c echo.Context) (err error) {
|
||||||
|
var request, response = ProgrammesGETRequest{}, ProgrammesGETResponse{}
|
||||||
|
|
||||||
|
queryLimit := c.QueryParam("limit")
|
||||||
|
if queryLimit != "" {
|
||||||
|
request.Query.Limit, err = strconv.Atoi(queryLimit)
|
||||||
|
if err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("parsing limit: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//TODO cfg.API.DefaultLimit
|
||||||
|
request.Query.Limit = 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
if !request.Complete() {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = "Incomplete ProgrammesGET request received"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Data.Programmes, err = db.GetProgrammes(request.Query.Limit)
|
||||||
|
if err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("db: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := response.SetStatusCode(http.StatusOK); err != nil {
|
||||||
|
var response voki.ResponseInternalServerError
|
||||||
|
response.Message = fmt.Sprintf("handler: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Message = "ok"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pave.EchoRegister[MembrePreferedNamePUTRequest](
|
||||||
|
apiGroup,
|
||||||
|
&p,
|
||||||
|
apiPath,
|
||||||
|
http.MethodPut,
|
||||||
|
"/membre/:membre_id/prefered_name/",
|
||||||
|
"Update membre prefered name, which is prioritized in the membres_for_display view",
|
||||||
|
"MembrePreferedNamePUT", func(c echo.Context) error {
|
||||||
|
var request, response = MembrePreferedNamePUTRequest{}, MembrePreferedNamePUTResponse{}
|
||||||
|
|
||||||
|
request.Param.MembreID = c.Param("membre_id")
|
||||||
|
|
||||||
|
if err := c.Bind(&request.Data); err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("parse request body: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !request.Complete() {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = "Incomplete MembrePreferedNamePUT request received"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.UpdateMembrePreferedName(request.Param.MembreID, request.Data.PreferedName); err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("db: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := response.SetStatusCode(http.StatusOK); err != nil {
|
||||||
|
var response voki.ResponseInternalServerError
|
||||||
|
response.Message = fmt.Sprintf("handler: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Message = fmt.Sprintf("Updated membre %s name to %s", request.Param.MembreID, request.Data.PreferedName)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pave.EchoRegister[MembresDisplayGETRequest](
|
||||||
|
apiGroup,
|
||||||
|
&p,
|
||||||
|
apiPath,
|
||||||
|
http.MethodGet,
|
||||||
|
"/membre/display/",
|
||||||
|
"Get membres",
|
||||||
|
"MembresDisplayGET", func(c echo.Context) (err error) {
|
||||||
|
var request, response = MembresDisplayGETRequest{}, MembresDisplayGETResponse{}
|
||||||
|
|
||||||
|
queryLimit := c.QueryParam("limit")
|
||||||
|
if queryLimit != "" {
|
||||||
|
request.Query.Limit, err = strconv.Atoi(queryLimit)
|
||||||
|
if err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("parsing limit: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//TODO cfg.API.DefaultLimit
|
||||||
|
request.Query.Limit = 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
if !request.Complete() {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = "Incomplete MembresDisplayGET request received"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Data.Membres, err = db.GetMembresForDisplay(request.Query.Limit)
|
||||||
|
if err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("db: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := response.SetStatusCode(http.StatusOK); err != nil {
|
||||||
|
var response voki.ResponseInternalServerError
|
||||||
|
response.Message = fmt.Sprintf("handler: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Message = "ok"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pave.EchoRegister[MembreDisplayGETRequest](
|
||||||
|
apiGroup,
|
||||||
|
&p,
|
||||||
|
apiPath,
|
||||||
|
http.MethodGet,
|
||||||
|
"/membre/:membre_id/display/",
|
||||||
|
"Get membre",
|
||||||
|
"MembreDisplayGET", func(c echo.Context) error {
|
||||||
|
var request, response = MembreDisplayGETRequest{}, MembreDisplayGETResponse{}
|
||||||
|
|
||||||
|
request.Param.MembreID = c.Param("membre_id")
|
||||||
|
|
||||||
|
if !request.Complete() {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = "Incomplete MembreDisplayGET request received"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
membre, err := db.GetMembreForDisplay(request.Param.MembreID)
|
||||||
|
if err != nil {
|
||||||
|
var response voki.ResponseBadRequest
|
||||||
|
response.Message = fmt.Sprintf("db: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
response.Data.Membre = membre
|
||||||
|
|
||||||
|
if err := response.SetStatusCode(http.StatusOK); err != nil {
|
||||||
|
var response voki.ResponseInternalServerError
|
||||||
|
response.Message = fmt.Sprintf("handler: %s", err)
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Message = "ok"
|
||||||
|
return c.JSON(response.StatusCode(), response)
|
||||||
|
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
14
queries/queries.go
Normal file
14
queries/queries.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed schema.sql
|
||||||
|
var sqlSchema string
|
||||||
|
|
||||||
|
//go:embed views.sql
|
||||||
|
var sqlViews string
|
||||||
|
|
||||||
|
func SQLSchema() string { return sqlSchema }
|
||||||
|
func SQLViews() string { return sqlViews }
|
12
queries/schema.sql
Normal file
12
queries/schema.sql
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS programmes (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS membres (
|
||||||
|
id VARCHAR(7) PRIMARY KEY,
|
||||||
|
last_name TEXT NOT NULL,
|
||||||
|
first_name TEXT NOT NULL,
|
||||||
|
prefered_name TEXT,
|
||||||
|
programme_id TEXT REFERENCES programmes(id) NOT NULL
|
||||||
|
);
|
23
queries/views.sql
Normal file
23
queries/views.sql
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
-- membres_for_display affiche le numéro étudiant, nom complet OU prefered_name, et titre du programme.
|
||||||
|
--
|
||||||
|
-- Utilisé par l'application web pour rechercher et afficher les informations des membres
|
||||||
|
CREATE OR REPLACE VIEW
|
||||||
|
"membres_for_display"
|
||||||
|
AS (
|
||||||
|
SELECT
|
||||||
|
"membres".id,
|
||||||
|
CASE
|
||||||
|
WHEN
|
||||||
|
"membres".prefered_name != '' AND "membres".prefered_name IS NOT NULL
|
||||||
|
THEN
|
||||||
|
"membres".prefered_name
|
||||||
|
ELSE
|
||||||
|
CONCAT("membres".last_name, ', ', "membres".first_name)
|
||||||
|
END AS name,
|
||||||
|
"programmes".id AS programme_id,
|
||||||
|
"programmes".name AS programme_name
|
||||||
|
FROM
|
||||||
|
"membres"
|
||||||
|
INNER JOIN
|
||||||
|
"programmes" ON "programmes".id = "membres".programme_id
|
||||||
|
);
|
|
@ -1,7 +0,0 @@
|
||||||
package responses
|
|
||||||
|
|
||||||
import "codeberg.org/vlbeaudoin/voki/response"
|
|
||||||
|
|
||||||
type GetHealthResponse struct {
|
|
||||||
response.ResponseWithError
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package responses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"codeberg.org/vlbeaudoin/voki/response"
|
|
||||||
"git.agecem.com/agecem/bottin/v6/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListMembresResponse struct {
|
|
||||||
response.ResponseWithError
|
|
||||||
Data struct {
|
|
||||||
Membres []models.Membre
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package responses
|
|
||||||
|
|
||||||
import "codeberg.org/vlbeaudoin/voki/response"
|
|
||||||
|
|
||||||
type PostMembresResponse struct {
|
|
||||||
response.ResponseWithError
|
|
||||||
Data struct {
|
|
||||||
MembresInserted int64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostProgrammesResponse struct {
|
|
||||||
response.ResponseWithError
|
|
||||||
Data struct {
|
|
||||||
ProgrammesInserted int64
|
|
||||||
}
|
|
||||||
}
|
|
6
scripts/compose-inject-x509.sh
Executable file
6
scripts/compose-inject-x509.sh
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
docker-compose cp cert.pem api:/etc/bottin/cert.pem
|
||||||
|
docker-compose cp key.pem api:/etc/bottin/key.pem
|
||||||
|
docker-compose cp cert.pem ui:/etc/bottin/cert.pem
|
||||||
|
docker-compose cp key.pem ui:/etc/bottin/key.pem
|
2
scripts/generate-self-signed-x509.sh
Executable file
2
scripts/generate-self-signed-x509.sh
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
|
|
@ -83,7 +83,7 @@ button {
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Scannez la carte étudiante d'unE membre<br>
|
Scannez la carte étudiante d'un·e membre<br>
|
||||||
-ou-<br>
|
-ou-<br>
|
||||||
Entrez manuellement le code à 7 chiffres
|
Entrez manuellement le code à 7 chiffres
|
||||||
</p>
|
</p>
|
||||||
|
@ -100,7 +100,7 @@ button {
|
||||||
</ul>
|
</ul>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p class="result">{{ .Result }}</p>
|
<p class="result">{{ .Message }}</p>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
27
templates/templates.go
Normal file
27
templates/templates.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed *.html
|
||||||
|
var templatesFS embed.FS
|
||||||
|
|
||||||
|
type Template struct {
|
||||||
|
templates *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||||
|
return t.templates.ExecuteTemplate(w, name, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTemplate returns a new Template instance with templates embedded from *.html
|
||||||
|
func NewTemplate() *Template {
|
||||||
|
return &Template{
|
||||||
|
templates: template.Must(template.ParseFS(templatesFS, "*.html")),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2021-2023 AGECEM
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
@ -1 +0,0 @@
|
||||||
deprecated, see git.agecem.com/agecem/bottin or git.agecem.com/agecem/bottin/v5
|
|
10
v4/go.mod
10
v4/go.mod
|
@ -1,10 +0,0 @@
|
||||||
module git.agecem.com/agecem/bottin/v4
|
|
||||||
|
|
||||||
go 1.20
|
|
||||||
|
|
||||||
//retract (
|
|
||||||
// v4.1.0
|
|
||||||
// v4.0.3
|
|
||||||
// v4.0.2
|
|
||||||
// v4.0.1
|
|
||||||
//)
|
|
10
web/embed.go
10
web/embed.go
|
@ -1,10 +0,0 @@
|
||||||
package web
|
|
||||||
|
|
||||||
import "embed"
|
|
||||||
|
|
||||||
//go:embed templates/*
|
|
||||||
var templatesFS embed.FS
|
|
||||||
|
|
||||||
func GetTemplates() embed.FS {
|
|
||||||
return templatesFS
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package webhandlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.agecem.com/agecem/bottin/v6/data"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Handler struct {
|
|
||||||
APIClient *data.ApiClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) GetIndex(c echo.Context) error {
|
|
||||||
return c.Render(http.StatusOK, "index-html", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) GetMembre(c echo.Context) error {
|
|
||||||
|
|
||||||
membreID := c.QueryParam("membre_id")
|
|
||||||
|
|
||||||
membre, err := h.APIClient.GetMembre(membreID)
|
|
||||||
if err != nil {
|
|
||||||
return c.Render(http.StatusBadRequest, "index-html", struct {
|
|
||||||
Result string
|
|
||||||
}{
|
|
||||||
Result: fmt.Sprintln("👎", err.Error()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
membreResult := fmt.Sprintf(`👍
|
|
||||||
Membre trouvéE: [%s]`, membre.ID)
|
|
||||||
|
|
||||||
if membre.PreferedName != "" {
|
|
||||||
membreResult = fmt.Sprintf("%s -> %s", membreResult, membre.PreferedName)
|
|
||||||
} else {
|
|
||||||
membreResult = fmt.Sprintf("%s -> %s, %s", membreResult, membre.LastName, membre.FirstName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "index-html", struct {
|
|
||||||
Result string
|
|
||||||
}{
|
|
||||||
Result: membreResult,
|
|
||||||
})
|
|
||||||
}
|
|
Reference in a new issue