Victor Lacasse-Beaudoin
8a9decfe6c
Valeur n'avait aucun effet précédemment, permet maintenant de choisir sur quel hôte le serveur API est rejoignable
257 lines
5.9 KiB
Go
257 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/subtle"
|
|
"fmt"
|
|
"html/template"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
|
|
"codeberg.org/vlbeaudoin/voki/v3"
|
|
"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"
|
|
)
|
|
|
|
// 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 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 := &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 := addRoutes(e, db, cfg); err != nil {
|
|
log.Fatal("add routes:", err)
|
|
}
|
|
/*
|
|
h := handlers.New(client)
|
|
|
|
e.GET("/v8/health/", h.GetHealth)
|
|
|
|
e.POST("/v8/membres/", h.PostMembres)
|
|
|
|
e.GET("/v8/membres/", h.ListMembres)
|
|
|
|
e.GET("/v8/membres/:membre_id/", h.ReadMembre)
|
|
|
|
e.PUT("/v8/membres/:membre_id/prefered_name/", h.PutMembrePreferedName)
|
|
|
|
e.POST("/v8/programmes/", h.PostProgrammes)
|
|
|
|
e.POST("/v8/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 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 = &Template{
|
|
templates: template.Must(template.ParseFS(templatesFS, "templates/*.html")),
|
|
}
|
|
|
|
// API Client
|
|
apiClient := APIClient{voki.New(
|
|
http.DefaultClient,
|
|
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 !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
|
|
e.Logger.Fatal(e.Start(
|
|
fmt.Sprintf(":%d", cfg.Server.UI.Port)))
|
|
},
|
|
}
|