package main import ( "encoding/csv" "fmt" "io" "net/http" "strconv" "codeberg.org/vlbeaudoin/pave/v2" "codeberg.org/vlbeaudoin/voki/v3" "github.com/gocarina/gocsv" "github.com/labstack/echo/v4" ) func addRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error { _ = db _ = cfg apiPath := "/api/v7" apiGroup := e.Group(apiPath) p := pave.New() if err := pave.EchoRegister[HealthGETRequest]( apiGroup, &p, apiPath, http.MethodGet, "/health/", "Get API server health", "HealthGET", func(c echo.Context) error { var request, response = HealthGETRequest{}, HealthGETResponse{} if !request.Complete() { var response voki.ResponseBadRequest response.Message = "Incomplete HealthGET request received" return c.JSON(response.StatusCode(), response) } 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 } if err := pave.EchoRegister[ProgrammesPOSTRequest]( apiGroup, &p, apiPath, http.MethodPost, "/programme/", "Insert programmes", "ProgrammesPOST", func(c echo.Context) error { var request, response = ProgrammesPOSTRequest{}, ProgrammesPOSTResponse{} switch contentType := c.Request().Header.Get("Content-Type"); contentType { case "application/json": 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) } case "text/csv": body := c.Request().Body if body == nil { var response voki.ResponseBadRequest response.Message = "empty request body cannot be parsed" return c.JSON(response.StatusCode(), response) } defer body.Close() gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { r := csv.NewReader(in) r.Comma = ';' return r // Allows use ; as delimiter }) // Parse CSV data using gocsv if err := gocsv.Unmarshal(body, &request.Data.Programmes); err != nil { var response voki.ResponseBadRequest response.Message = fmt.Sprintf("parse programmes from csv: %s", err) return c.JSON(response.StatusCode(), response) } default: var response voki.ResponseBadRequest response.Message = fmt.Sprintf("cannot parse body with content-type: %s", contentType) return c.JSON(response.StatusCode(), response) } if !request.Complete() { var response voki.ResponseBadRequest response.Message = "Incomplete ProgrammesPOST request received" return c.JSON(response.StatusCode(), response) } amountInserted, err := db.InsertProgrammes(request.Data.Programmes...) if err != nil { var response voki.ResponseBadRequest response.Message = fmt.Sprintf("db: %s", err) return c.JSON(response.StatusCode(), response) } response.Data.ProgrammesInserted = 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 } if err := pave.EchoRegister[MembresPOSTRequest]( apiGroup, &p, apiPath, http.MethodPost, "/membre/", "Insert membres", "MembresPOST", func(c echo.Context) error { var request, response = MembresPOSTRequest{}, MembresPOSTResponse{} switch contentType := c.Request().Header.Get("Content-Type"); contentType { case "application/json": 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) } case "text/csv": body := c.Request().Body if body == nil { var response voki.ResponseBadRequest response.Message = "empty request body cannot be parsed" return c.JSON(response.StatusCode(), response) } defer body.Close() gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { r := csv.NewReader(in) r.Comma = ';' return r // Allows use ; as delimiter }) // Parse CSV data using gocsv if err := gocsv.Unmarshal(body, &request.Data.Membres); err != nil { var response voki.ResponseBadRequest response.Message = fmt.Sprintf("parse membres from csv: %s", err) return c.JSON(response.StatusCode(), response) } default: var response voki.ResponseBadRequest response.Message = fmt.Sprintf("cannot parse body with content-type: %s", contentType) 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 } 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 } if err := pave.EchoRegister[MembresGETRequest]( apiGroup, &p, apiPath, http.MethodGet, "/membre/", "Get membres", "MembresGET", func(c echo.Context) (err error) { var request, response = MembresGETRequest{}, MembresGETResponse{} queryLimit := c.QueryParam("limit") if queryLimit != "" { request.Query.Limit, err = strconv.Atoi(queryLimit) if err != nil { var response voki.ResponseBadRequest response.Message = fmt.Sprintf("parsing limit: %s", err) return c.JSON(response.StatusCode(), response) } } else { //TODO cfg.API.DefaultLimit request.Query.Limit = 1000 } if !request.Complete() { var response voki.ResponseBadRequest response.Message = "Incomplete MembresGET request received" return c.JSON(response.StatusCode(), response) } response.Data.Membres, err = db.GetMembres(request.Query.Limit) if err != nil { var response voki.ResponseBadRequest response.Message = fmt.Sprintf("db: %s", err) return c.JSON(response.StatusCode(), response) } 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 } if err := pave.EchoRegister[ProgrammesGETRequest]( apiGroup, &p, apiPath, http.MethodGet, "/programme/", "Get programmes", "ProgrammesGET", func(c echo.Context) (err error) { var request, response = ProgrammesGETRequest{}, ProgrammesGETResponse{} queryLimit := c.QueryParam("limit") if queryLimit != "" { request.Query.Limit, err = strconv.Atoi(queryLimit) if err != nil { var response voki.ResponseBadRequest response.Message = fmt.Sprintf("parsing limit: %s", err) return c.JSON(response.StatusCode(), response) } } else { //TODO cfg.API.DefaultLimit request.Query.Limit = 1000 } if !request.Complete() { var response voki.ResponseBadRequest response.Message = "Incomplete ProgrammesGET request received" return c.JSON(response.StatusCode(), response) } response.Data.Programmes, err = db.GetProgrammes(request.Query.Limit) if err != nil { var response voki.ResponseBadRequest response.Message = fmt.Sprintf("db: %s", err) return c.JSON(response.StatusCode(), response) } 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 } if err := pave.EchoRegister[MembrePreferedNamePUTRequest]( apiGroup, &p, apiPath, http.MethodPut, "/membre/:membre_id/prefered_name/", "Update membre prefered name, which is prioritized in the membres_for_display view", "MembrePreferedNamePUT", func(c echo.Context) error { var request, response = MembrePreferedNamePUTRequest{}, MembrePreferedNamePUTResponse{} request.Param.MembreID = c.Param("membre_id") 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 MembrePreferedNamePUT request received" return c.JSON(response.StatusCode(), response) } if err := db.UpdateMembrePreferedName(request.Param.MembreID, request.Data.PreferedName); err != nil { var response voki.ResponseBadRequest response.Message = fmt.Sprintf("db: %s", err) return c.JSON(response.StatusCode(), response) } 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 = fmt.Sprintf("Updated membre %s name to %s", request.Param.MembreID, request.Data.PreferedName) return c.JSON(response.StatusCode(), response) }); err != nil { return err } if err := pave.EchoRegister[MembresDisplayGETRequest]( apiGroup, &p, apiPath, http.MethodGet, "/membre/display/", "Get membres", "MembresDisplayGET", func(c echo.Context) (err error) { var request, response = MembresDisplayGETRequest{}, MembresDisplayGETResponse{} queryLimit := c.QueryParam("limit") if queryLimit != "" { request.Query.Limit, err = strconv.Atoi(queryLimit) if err != nil { var response voki.ResponseBadRequest response.Message = fmt.Sprintf("parsing limit: %s", err) return c.JSON(response.StatusCode(), response) } } else { //TODO cfg.API.DefaultLimit request.Query.Limit = 1000 } if !request.Complete() { var response voki.ResponseBadRequest response.Message = "Incomplete MembresDisplayGET request received" return c.JSON(response.StatusCode(), response) } response.Data.Membres, err = db.GetMembresForDisplay(request.Query.Limit) if err != nil { var response voki.ResponseBadRequest response.Message = fmt.Sprintf("db: %s", err) return c.JSON(response.StatusCode(), response) } 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 } if err := pave.EchoRegister[MembreDisplayGETRequest]( apiGroup, &p, apiPath, http.MethodGet, "/membre/:membre_id/display/", "Get membre", "MembreDisplayGET", func(c echo.Context) error { var request, response = MembreDisplayGETRequest{}, MembreDisplayGETResponse{} request.Param.MembreID = c.Param("membre_id") if !request.Complete() { var response voki.ResponseBadRequest response.Message = "Incomplete MembreDisplayGET request received" return c.JSON(response.StatusCode(), response) } membre, err := db.GetMembreForDisplay(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 }