Version 7 #53
9 changed files with 168 additions and 11 deletions
17
client.go
17
client.go
|
@ -41,3 +41,20 @@ func (c APIClient) InsertProgrammes(programmes ...Programme) (amountInserted int
|
||||||
|
|
||||||
return response.Data.ProgrammesInserted, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -40,7 +40,6 @@ func TestAPI(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
//TODO create or replace schema
|
//TODO create or replace schema
|
||||||
//TODO insert programmes
|
|
||||||
t.Run("insert programmes",
|
t.Run("insert programmes",
|
||||||
func(t *testing.T) {
|
func(t *testing.T) {
|
||||||
programmes := []Programme{
|
programmes := []Programme{
|
||||||
|
@ -54,6 +53,28 @@ func TestAPI(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
//TODO insert membres
|
//TODO insert membres
|
||||||
|
t.Run("insert membres",
|
||||||
|
func(t *testing.T) {
|
||||||
|
membres := []Membre{
|
||||||
|
{
|
||||||
|
ID: "0000000",
|
||||||
|
FirstName: "Test",
|
||||||
|
LastName: "User",
|
||||||
|
ProgrammeID: "404.42",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "1234567",
|
||||||
|
FirstName: "Deadname",
|
||||||
|
LastName: "User",
|
||||||
|
PreferedName: "User, Test-Name",
|
||||||
|
ProgrammeID: "200.10",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := apiClient.InsertMembres(membres...)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
//TODO get membre
|
//TODO get membre
|
||||||
//TODO update membre prefered name
|
//TODO update membre prefered name
|
||||||
//TODO get membres
|
//TODO get membres
|
||||||
|
|
4
cmd.go
4
cmd.go
|
@ -85,6 +85,10 @@ var apiCmd = &cobra.Command{
|
||||||
log.Fatal("create or replace schema:", err)
|
log.Fatal("create or replace schema:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := db.CreateOrReplaceViews(); err != nil {
|
||||||
|
log.Fatal("create or replace views:", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
if err := addRoutes(e, db); err != nil {
|
if err := addRoutes(e, db); err != nil {
|
||||||
log.Fatal("add routes:", err)
|
log.Fatal("add routes:", err)
|
||||||
|
|
14
db.go
14
db.go
|
@ -11,6 +11,9 @@ import (
|
||||||
//go:embed sql/schema.sql
|
//go:embed sql/schema.sql
|
||||||
var sqlSchema string
|
var sqlSchema string
|
||||||
|
|
||||||
|
//go:embed sql/views.sql
|
||||||
|
var sqlViews string
|
||||||
|
|
||||||
type PostgresClient struct {
|
type PostgresClient struct {
|
||||||
//TODO move context out of client
|
//TODO move context out of client
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
|
@ -22,8 +25,13 @@ func (db *PostgresClient) CreateOrReplaceSchema() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *PostgresClient) CreateOrReplaceViews() error {
|
||||||
|
_, err := db.Pool.Exec(db.Ctx, sqlViews)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// InsertMembres inserts a slice of Membre into a database, returning the amount inserted and any error encountered
|
// 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) {
|
func (d *PostgresClient) InsertMembres(membres ...Membre) (inserted int64, err error) {
|
||||||
select {
|
select {
|
||||||
case <-d.Ctx.Done():
|
case <-d.Ctx.Done():
|
||||||
return inserted, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err())
|
return inserted, fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err())
|
||||||
|
@ -84,10 +92,10 @@ func (d *PostgresClient) InsertProgrammes(programmes ...Programme) (inserted int
|
||||||
|
|
||||||
result, err := tx.Exec(d.Ctx, `
|
result, err := tx.Exec(d.Ctx, `
|
||||||
INSERT INTO programmes
|
INSERT INTO programmes
|
||||||
(id, titre)
|
(id, name)
|
||||||
VALUES ($1, $2) ON CONFLICT DO NOTHING;`,
|
VALUES ($1, $2) ON CONFLICT DO NOTHING;`,
|
||||||
programme.ID,
|
programme.ID,
|
||||||
programme.Titre)
|
programme.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package main
|
||||||
|
|
||||||
type Programme struct {
|
type Programme struct {
|
||||||
ID string `db:"id" json:"programme_id" csv:"programme_id"`
|
ID string `db:"id" json:"programme_id" csv:"programme_id"`
|
||||||
Titre string `db:"titre" json:"nom_programme" csv:"nom_programme"`
|
Name string `db:"name" json:"nom_programme" csv:"nom_programme"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Membre struct {
|
type Membre struct {
|
||||||
|
|
41
request.go
41
request.go
|
@ -79,3 +79,44 @@ func (request ProgrammesPOSTRequest) Request(v *voki.Voki) (response ProgrammesP
|
||||||
|
|
||||||
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/membres/",
|
||||||
|
&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
|
||||||
|
}
|
||||||
|
|
45
routes.go
45
routes.go
|
@ -48,7 +48,7 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error {
|
||||||
apiPath,
|
apiPath,
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
"/programmes/",
|
"/programmes/",
|
||||||
"Get registered programmes",
|
"Insert programmes",
|
||||||
"ProgrammesPOST", func(c echo.Context) error {
|
"ProgrammesPOST", func(c echo.Context) error {
|
||||||
var request, response = ProgrammesPOSTRequest{}, ProgrammesPOSTResponse{}
|
var request, response = ProgrammesPOSTRequest{}, ProgrammesPOSTResponse{}
|
||||||
|
|
||||||
|
@ -84,5 +84,48 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error {
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := pave.EchoRegister[MembresPOSTRequest](
|
||||||
|
apiGroup,
|
||||||
|
&p,
|
||||||
|
apiPath,
|
||||||
|
http.MethodPost,
|
||||||
|
"/membres/",
|
||||||
|
"Insert membres",
|
||||||
|
"MembresPOST", func(c echo.Context) error {
|
||||||
|
var request, response = MembresPOSTRequest{}, MembresPOSTResponse{}
|
||||||
|
|
||||||
|
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 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
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
CREATE TABLE IF NOT EXISTS programmes (
|
CREATE TABLE IF NOT EXISTS programmes (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
titre TEXT
|
name TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS membres (
|
CREATE TABLE IF NOT EXISTS membres (
|
||||||
id VARCHAR(7) PRIMARY KEY,
|
id VARCHAR(7) PRIMARY KEY,
|
||||||
last_name TEXT,
|
last_name TEXT NOT NULL,
|
||||||
first_name TEXT,
|
first_name TEXT NOT NULL,
|
||||||
prefered_name TEXT,
|
prefered_name TEXT,
|
||||||
programme_id TEXT REFERENCES programmes(id)
|
programme_id TEXT REFERENCES programmes(id) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
23
sql/views.sql
Normal file
23
sql/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
|
||||||
|
);
|
Loading…
Reference in a new issue