Produit minimal viable #1
8 changed files with 46 additions and 57 deletions
|
@ -11,5 +11,6 @@ services:
|
||||||
- '5432:5432'
|
- '5432:5432'
|
||||||
volumes:
|
volumes:
|
||||||
- 'db-data:/var/lib/postgresql/data/'
|
- 'db-data:/var/lib/postgresql/data/'
|
||||||
|
- '/etc/localtime:/etc/localtime:ro'
|
||||||
volumes:
|
volumes:
|
||||||
db-data:
|
db-data:
|
||||||
|
|
11
db.go
11
db.go
|
@ -3,7 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.agecem.com/bottin/agendas/queries"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,8 +29,13 @@ func (d DBClient) Ping(ctx context.Context) error {
|
||||||
func (d DBClient) Init(ctx context.Context) error {
|
func (d DBClient) Init(ctx context.Context) error {
|
||||||
//TODO check context is not closed
|
//TODO check context is not closed
|
||||||
//TODO check *DB is not nil
|
//TODO check *DB is not nil
|
||||||
//TODO Init
|
log.Println("warning: DBClient [Init] not properly checked")
|
||||||
return fmt.Errorf("db: Init not implemented")
|
// Init
|
||||||
|
if _, err := d.Pool.Exec(ctx, queries.SQLSchema()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d DBClient) CreateTransaction(ctx context.Context, transaction Transaction) error {
|
func (d DBClient) CreateTransaction(ctx context.Context, transaction Transaction) error {
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -5,6 +5,7 @@ go 1.23.4
|
||||||
require (
|
require (
|
||||||
codeberg.org/vlbeaudoin/voki/v3 v3.0.1
|
codeberg.org/vlbeaudoin/voki/v3 v3.0.1
|
||||||
git.agecem.com/bottin/bottin/v10 v10.4.1
|
git.agecem.com/bottin/bottin/v10 v10.4.1
|
||||||
|
github.com/jackc/pgx/v5 v5.7.1
|
||||||
github.com/labstack/echo/v4 v4.13.3
|
github.com/labstack/echo/v4 v4.13.3
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
|
@ -18,7 +19,6 @@ require (
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/labstack/gommon v0.4.2 // 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
|
||||||
|
|
60
handler.go
60
handler.go
|
@ -17,6 +17,7 @@ func UIIndex(ctx context.Context, bottinClient *bottin.APIClient, dbClient *DBCl
|
||||||
Error string
|
Error string
|
||||||
BottinHealthResponse bottin.ReadHealthResponse
|
BottinHealthResponse bottin.ReadHealthResponse
|
||||||
IsDBUp bool
|
IsDBUp bool
|
||||||
|
Result string
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "index", func() (d data) {
|
return c.Render(http.StatusOK, "index", func() (d data) {
|
||||||
|
@ -62,47 +63,6 @@ func UIIndex(ctx context.Context, bottinClient *bottin.APIClient, dbClient *DBCl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UIReadMembre(ctx context.Context, bottinClient *bottin.APIClient) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
type data struct {
|
|
||||||
Error string
|
|
||||||
BottinMembreResponse bottin.ReadMembreResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "index", func() (d data) {
|
|
||||||
if err := func() error {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return fmt.Errorf("Impossible de contacter le serveur: %s", ctx.Err())
|
|
||||||
default:
|
|
||||||
// Check client
|
|
||||||
if bottinClient == nil {
|
|
||||||
return fmt.Errorf("Impossible de contacter le serveur, le client API est nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Check membre
|
|
||||||
d.BottinMembreResponse, err = bottinClient.ReadMembre(ctx, c.QueryParam("m"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// No errors
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}(); err != nil {
|
|
||||||
// Send error to user
|
|
||||||
d.Error = err.Error()
|
|
||||||
|
|
||||||
// Log error
|
|
||||||
log.Println("err:", d.Error)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
UICreateTransaction gère la création des transactions
|
UICreateTransaction gère la création des transactions
|
||||||
|
|
||||||
|
@ -113,9 +73,9 @@ TODO:
|
||||||
func UICreateTransaction(ctx context.Context, cfg Config, bottinClient *bottin.APIClient, dbClient *DBClient) echo.HandlerFunc {
|
func UICreateTransaction(ctx context.Context, cfg Config, bottinClient *bottin.APIClient, dbClient *DBClient) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
type data struct {
|
type data struct {
|
||||||
BottinHealth bottin.ReadHealthResponse
|
Error string
|
||||||
Error string
|
Result string
|
||||||
Result string
|
//BottinHealth bottin.ReadHealthResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "index", func() (d data) {
|
return c.Render(http.StatusOK, "index", func() (d data) {
|
||||||
|
@ -124,12 +84,14 @@ func UICreateTransaction(ctx context.Context, cfg Config, bottinClient *bottin.A
|
||||||
return fmt.Errorf("Cannot operate on nil *bottin.APIClient")
|
return fmt.Errorf("Cannot operate on nil *bottin.APIClient")
|
||||||
}
|
}
|
||||||
|
|
||||||
bottinReadHealthResponse, err := bottinClient.ReadHealth(ctx)
|
/*
|
||||||
if err != nil {
|
bottinReadHealthResponse, err := bottinClient.ReadHealth(ctx)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
d.BottinHealth = bottinReadHealthResponse
|
d.BottinHealth = bottinReadHealthResponse
|
||||||
|
*/
|
||||||
|
|
||||||
isPerpetual := c.FormValue("is_perpetual") == "on"
|
isPerpetual := c.FormValue("is_perpetual") == "on"
|
||||||
membreID := c.FormValue("membre_id")
|
membreID := c.FormValue("membre_id")
|
||||||
|
|
10
queries/queries.go
Normal file
10
queries/queries.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package queries
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed schema.sql
|
||||||
|
var sqlSchema string
|
||||||
|
|
||||||
|
func SQLSchema() string {
|
||||||
|
return sqlSchema
|
||||||
|
}
|
7
queries/schema.sql
Normal file
7
queries/schema.sql
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
-- Schema
|
||||||
|
CREATE TABLE IF NOT EXISTS transactions (
|
||||||
|
given_at TIMESTAMP DEFAULT current_timestamp,
|
||||||
|
membre_id VARCHAR(7) NOT NULL CHECK (length(membre_id) > 0),
|
||||||
|
is_perpetual BOOLEAN NOT NULL,
|
||||||
|
PRIMARY KEY (membre_id, is_perpetual)
|
||||||
|
);
|
|
@ -25,6 +25,10 @@ func RunServer(ctx context.Context, cfg Config, bottinClient *bottin.APIClient,
|
||||||
return fmt.Errorf("nil dbClient")
|
return fmt.Errorf("nil dbClient")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := dbClient.Init(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
|
|
||||||
r := ui.NewRenderer()
|
r := ui.NewRenderer()
|
||||||
|
@ -42,7 +46,7 @@ func RunServer(ctx context.Context, cfg Config, bottinClient *bottin.APIClient,
|
||||||
return fmt.Errorf("UI requires at least one credential (config key `Credentials` of type map[string]string)")
|
return fmt.Errorf("UI requires at least one credential (config key `Credentials` of type map[string]string)")
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Use(middleware.BasicAuth(
|
e.Pre(middleware.BasicAuth(
|
||||||
func(username, password string, c echo.Context) (bool, error) {
|
func(username, password string, c echo.Context) (bool, error) {
|
||||||
for validUser, validPass := range cfg.Credentials {
|
for validUser, validPass := range cfg.Credentials {
|
||||||
userOK := subtle.ConstantTimeCompare([]byte(username), []byte(validUser)) == 1
|
userOK := subtle.ConstantTimeCompare([]byte(username), []byte(validUser)) == 1
|
||||||
|
@ -62,8 +66,6 @@ func RunServer(ctx context.Context, cfg Config, bottinClient *bottin.APIClient,
|
||||||
//e.GET("/transaction/", UIReadTransaction
|
//e.GET("/transaction/", UIReadTransaction
|
||||||
e.POST("/transaction/", UICreateTransaction(ctx, cfg, bottinClient, dbClient))
|
e.POST("/transaction/", UICreateTransaction(ctx, cfg, bottinClient, dbClient))
|
||||||
|
|
||||||
//e.GET("/membre/", UIReadMembre(ctx, bottinClient))
|
|
||||||
|
|
||||||
address := fmt.Sprintf(":%d", cfg.Port)
|
address := fmt.Sprintf(":%d", cfg.Port)
|
||||||
|
|
||||||
if cfg.TLS.Enabled {
|
if cfg.TLS.Enabled {
|
||||||
|
|
|
@ -112,8 +112,8 @@ button {
|
||||||
</ul>
|
</ul>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{{ if .Error }}<p class="result">Erreur: {{ .Error }}</p> {{ end }}
|
{{ if .Error }}<p class="result">Erreur: {{ .Error }}</p>{{ end }}
|
||||||
<p class="result">{{ .Result }}</p>
|
{{ if .Result }}<p class="result">{{ .Result }}</p>{{ end }}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue