rework: change api prefix to /api/v7/

- add and test GetMembre
- add `IsMembreID(string) bool` function

BREAKING: Rename routes to `/api/v7/...` scheme
This commit is contained in:
Victor Lacasse-Beaudoin 2024-06-18 19:44:20 -04:00
parent e847f693e0
commit c7c64674c7
6 changed files with 212 additions and 45 deletions

View file

@ -58,3 +58,20 @@ func (c APIClient) InsertMembres(membres ...Membre) (amountInserted int64, err e
return response.Data.MembresInserted, nil 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
}

View file

@ -40,6 +40,7 @@ func TestAPI(t *testing.T) {
}) })
//TODO create or replace schema //TODO create or replace schema
t.Run("insert programmes", t.Run("insert programmes",
func(t *testing.T) { func(t *testing.T) {
programmes := []Programme{ programmes := []Programme{
@ -52,10 +53,8 @@ func TestAPI(t *testing.T) {
t.Error(err) t.Error(err)
} }
}) })
//TODO insert membres
t.Run("insert membres", testMembres := []Membre{
func(t *testing.T) {
membres := []Membre{
{ {
ID: "0000000", ID: "0000000",
FirstName: "Test", FirstName: "Test",
@ -70,13 +69,55 @@ func TestAPI(t *testing.T) {
ProgrammeID: "200.10", ProgrammeID: "200.10",
}, },
} }
_, err := apiClient.InsertMembres(membres...)
t.Run("insert membres",
func(t *testing.T) {
_, err := apiClient.InsertMembres(testMembres...)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
}) })
//TODO get membre
//TODO update membre prefered name
//TODO get membres
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")
}
})
//TODO update membre prefered name
/*
t.Run("",
func(t *testing.T) {
if err := apiClient.UpdateMembrePreferedName(testMembres[0].ID, "User, Galaxy"); err != nil {
t.Error(err)
}
})
*/
//TODO get membres
/*
t.Run("get membres, max 50",
func(t *testing.T) {
membres, err := apiClient.GetMembres(50)
if err != nil {
t.Error(err)
}
})
*/
} }

45
db.go
View file

@ -111,29 +111,42 @@ VALUES ($1, $2) ON CONFLICT DO NOTHING;`,
} }
} }
/* func (d *PostgresClient) GetMembre(membreID string) (membre Membre, err error) {
func (d *PostgresClient) GetMembre(membreID string) (Membre, error) { select {
var membre Membre case <-d.Ctx.Done():
err = fmt.Errorf("PostgresClient.Ctx closed: %s", d.Ctx.Err())
rows, err := d.Pool.Queryx("SELECT * FROM membres WHERE id = $1 LIMIT 1;", membreID) return
if err != nil { default:
return membre, err if err = d.Pool.QueryRow(d.Ctx, `
} SELECT
"membres".id,
for rows.Next() { "membres".last_name,
err := rows.StructScan(&membre) "membres".first_name,
if err != nil { "membres".prefered_name,
return membre, err "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 == "" { if membre.ID == "" {
return membre, fmt.Errorf("No membre by that id was found") return membre, fmt.Errorf("Aucun membre trouvé avec numéro '%s'", membre.ID)
} }
return membre, nil return membre, nil
}
} }
*/
/* /*
func (d *PostgresClient) UpdateMembreName(membreID, newName string) (int64, error) { func (d *PostgresClient) UpdateMembreName(membreID, newName string) (int64, error) {

View file

@ -1,5 +1,7 @@
package main package main
import "unicode"
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"`
Name string `db:"name" json:"nom_programme" csv:"nom_programme"` Name string `db:"name" json:"nom_programme" csv:"nom_programme"`
@ -12,3 +14,17 @@ type Membre struct {
PreferedName string `db:"prefered_name" json:"prefered_name" csv:"prefered_name"` PreferedName string `db:"prefered_name" json:"prefered_name" csv:"prefered_name"`
ProgrammeID string `db:"programme_id" json:"programme_id" csv:"programme_id"` ProgrammeID string `db:"programme_id" json:"programme_id" csv:"programme_id"`
} }
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
}

View file

@ -23,7 +23,7 @@ func (request HealthGETRequest) Request(v *voki.Voki) (response HealthGETRespons
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodGet, http.MethodGet,
"/api/health/", "/api/v7/health/",
nil, nil,
true, true,
) )
@ -64,7 +64,7 @@ func (request ProgrammesPOSTRequest) Request(v *voki.Voki) (response ProgrammesP
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodPost, http.MethodPost,
"/api/programmes/", "/api/v7/programme/",
&buf, &buf,
true, true,
) )
@ -105,7 +105,7 @@ func (request MembresPOSTRequest) Request(v *voki.Voki) (response MembresPOSTRes
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodPost, http.MethodPost,
"/api/membres/", "/api/v7/membre/",
&buf, &buf,
true, true,
) )
@ -120,3 +120,44 @@ func (request MembresPOSTRequest) Request(v *voki.Voki) (response MembresPOSTRes
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/v7/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
}

View file

@ -12,7 +12,7 @@ import (
func addRoutes(e *echo.Echo, db *PostgresClient) error { func addRoutes(e *echo.Echo, db *PostgresClient) error {
_ = db _ = db
apiPath := "/api" apiPath := "/api/v7"
apiGroup := e.Group(apiPath) apiGroup := e.Group(apiPath)
p := pave.New() p := pave.New()
if err := pave.EchoRegister[HealthGETRequest]( if err := pave.EchoRegister[HealthGETRequest](
@ -47,7 +47,7 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error {
&p, &p,
apiPath, apiPath,
http.MethodPost, http.MethodPost,
"/programmes/", "/programme/",
"Insert 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{}
@ -90,7 +90,7 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error {
&p, &p,
apiPath, apiPath,
http.MethodPost, http.MethodPost,
"/membres/", "/membre/",
"Insert membres", "Insert membres",
"MembresPOST", func(c echo.Context) error { "MembresPOST", func(c echo.Context) error {
var request, response = MembresPOSTRequest{}, MembresPOSTResponse{} var request, response = MembresPOSTRequest{}, MembresPOSTResponse{}
@ -127,5 +127,44 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error {
}); err != nil { }); err != nil {
return err 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
}
return nil return nil
} }