[WIP] Add web client

Ignore .swp files

Rename serverCmd to apiCmd (the web client is technically a server too)

Add webCmd for html routes hosting

Add embedding and templating for web client

Add webhandlers

Fix some variables not being filled automatically by viper

Change flags and reorganize config structure
This commit is contained in:
Victor Lacasse-Beaudoin 2023-05-25 19:22:46 -04:00
parent 3e37e8ffef
commit 3aa7faa2f6
7 changed files with 201 additions and 32 deletions

3
.gitignore vendored
View file

@ -25,3 +25,6 @@
# env
.env
# .swp
*.swp

View file

@ -18,11 +18,22 @@ var (
apiKey string
)
// serverCmd represents the server command
var serverCmd = &cobra.Command{
Use: "server",
// 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")
connection := data.PostgresConnection{
User: viper.GetString("db.user"),
Password: viper.GetString("db.password"),
Host: viper.GetString("db.host"),
Database: viper.GetString("db.database"),
Port: viper.GetInt("db.port"),
}
e := echo.New()
@ -52,14 +63,6 @@ var serverCmd = &cobra.Command{
// Execution
connection := data.PostgresConnection{
User: viper.GetString("db.user"),
Password: viper.GetString("db.password"),
Host: viper.GetString("db.host"),
Database: viper.GetString("db.database"),
Port: viper.GetInt("db.port"),
}
client, err := data.NewDataClient(connection)
if err != nil {
log.Fatalf("Could not establish database connection.\n Error: %s\n", err)
@ -72,40 +75,45 @@ var serverCmd = &cobra.Command{
client.DB.Close()
log.Println("apiPort: ", apiPort)
log.Println("apiKey: ", apiKey)
e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", apiPort)))
},
}
func init() {
rootCmd.AddCommand(serverCmd)
rootCmd.AddCommand(apiCmd)
// api.key
serverCmd.Flags().StringVar(
&apiKey, "api-key", "bottin",
apiCmd.Flags().String(
"api-key", "bottin",
"API server key. Leave empty for no key auth. (config: 'api.key')")
viper.BindPFlag("api.key", apiCmd.Flags().Lookup("api-key"))
// api.port
serverCmd.Flags().IntVar(
&apiPort, "api-port", 1312,
apiCmd.Flags().Int(
"api-port", 1312,
"API server port (config:'api.port')")
viper.BindPFlag("api.port", apiCmd.Flags().Lookup("api-port"))
// db.database
serverCmd.Flags().String("db-database", "bottin", "Postgres database (config:'db.database')")
viper.BindPFlag("db.database", serverCmd.Flags().Lookup("db-database"))
apiCmd.Flags().String("db-database", "bottin", "Postgres database (config:'db.database')")
viper.BindPFlag("db.database", apiCmd.Flags().Lookup("db-database"))
// db.host
serverCmd.Flags().String("db-host", "postgres", "Postgres host (config:'db.host')")
viper.BindPFlag("db.host", serverCmd.Flags().Lookup("db-host"))
apiCmd.Flags().String("db-host", "db", "Postgres host (config:'db.host')")
viper.BindPFlag("db.host", apiCmd.Flags().Lookup("db-host"))
// db.password
serverCmd.Flags().String("db-password", "bottin", "Postgres password (config:'db.password')")
viper.BindPFlag("db.password", serverCmd.Flags().Lookup("db-password"))
apiCmd.Flags().String("db-password", "bottin", "Postgres password (config:'db.password')")
viper.BindPFlag("db.password", apiCmd.Flags().Lookup("db-password"))
// db.port
serverCmd.Flags().Int("db-port", 5432, "Postgres port (config:'db.port')")
viper.BindPFlag("db.port", serverCmd.Flags().Lookup("db-port"))
apiCmd.Flags().Int("db-port", 5432, "Postgres port (config:'db.port')")
viper.BindPFlag("db.port", apiCmd.Flags().Lookup("db-port"))
// db.user
serverCmd.Flags().String("db-user", "bottin", "Postgres user (config:'db.user')")
viper.BindPFlag("db.user", serverCmd.Flags().Lookup("db-user"))
apiCmd.Flags().String("db-user", "bottin", "Postgres user (config:'db.user')")
viper.BindPFlag("db.user", apiCmd.Flags().Lookup("db-user"))
}

View file

@ -1,20 +1,129 @@
package cmd
import (
"crypto/subtle"
"embed"
"fmt"
"html/template"
"io"
"log"
"git.agecem.com/agecem/bottin/v4/web"
"git.agecem.com/agecem/bottin/v4/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
)
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) {
fmt.Println("web called")
webApiHost = viper.GetString("web.api.host")
webApiKey = viper.GetString("web.api.key")
webApiPort = viper.GetInt("web.api.port")
webPassword = viper.GetString("web.password")
webPort = viper.GetInt("web.port")
webUser = viper.GetString("web.user")
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
e.GET("/", webhandlers.GetIndex)
// Execution
fmt.Println("webPort: ", webPort)
fmt.Println("web.port: ", viper.GetInt("web.port"))
webPortFlag, err := cmd.Flags().GetInt("web-port")
if err != nil {
log.Fatal(err)
}
fmt.Println("web-port: ", webPortFlag)
e.Logger.Fatal(e.Start(
fmt.Sprintf(":%d", webPort)))
},
}
func init() {
rootCmd.AddCommand(webCmd)
templatesFS = web.GetTemplates()
// web.api.host
webCmd.Flags().String(
"web-api-host", "api",
"Remote API server host (config:'web.api.host')")
viper.BindPFlag("web.api.host", webCmd.Flags().Lookup("web-api-host"))
// web.api.key
webCmd.Flags().String(
"web-api-key", "bottin",
"Remote API server key (config:'web.api.key')")
viper.BindPFlag("web.api.key", webCmd.Flags().Lookup("web-api-key"))
// web.api.port
webCmd.Flags().Int(
"web-api-port", 1312,
"Remote API server port (config:'web.api.port')")
viper.BindPFlag("web.api.port", webCmd.Flags().Lookup("web-api-port"))
// web.password
webCmd.Flags().String(
"web-password", "bottin",
"Web client password (config:'web.password')")
viper.BindPFlag("web.password", webCmd.Flags().Lookup("web-password"))
// web.port
webCmd.Flags().Int(
"web-port", 2312,
"Web client port (config:'web.port')")
viper.BindPFlag("web.port", webCmd.Flags().Lookup("web-port"))
// web.user
webCmd.Flags().String(
"web-user", "bottin",
"Web client user (config:'web.user')")
viper.BindPFlag("web.user", webCmd.Flags().Lookup("web-user"))
}

View file

@ -1,6 +1,6 @@
services:
postgres:
db:
image: postgres:latest
environment:
POSTGRES_DATABASE: "${BOTTIN_POSTGRES_DATABASE}"
@ -11,11 +11,19 @@ services:
volumes:
- 'pgdata:/var/lib/postgresql/data'
#api:
# depends_on: db
#web:
# depends_on: api
adminer:
image: adminer
restart: always
ports:
- 8088:8080
depends_on:
- db
volumes:
pgdata:

View file

@ -2,9 +2,9 @@ package web
import "embed"
//go:embed templates
var templates embed.FS
//go:embed templates/*
var templatesFS embed.FS
func GetTemplates() embed.FS {
return templates
return templatesFS
}

View file

@ -1 +1,31 @@
Hello world from bottin
{{ define "index-html" }}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>
AGECEM | Bottin
</title>
</head>
<body>
<h2>
Bottin des membres de l'AGECEM
</h2>
<p>
Scannez la carte étudiante d'unE membre<br>
-ou-<br>
Entrez manuellement le code à 7 chiffres
</p>
<form action="/membre/">
<label>#
<input type="text" name="num_etud" id="num_etud" autofocus>
</label>
<button formmethod="get" type="submit">Valider</button>
</form>
</body>
</html>
{{ end }}

View file

@ -0,0 +1,11 @@
package webhandlers
import (
"net/http"
"github.com/labstack/echo/v4"
)
func GetIndex(c echo.Context) error {
return c.Render(http.StatusOK, "index-html", nil)
}