From 94f58024985d31d66d9e4e63b40cea7134cf3513 Mon Sep 17 00:00:00 2001 From: Tommy Date: Tue, 9 Sep 2025 07:03:32 -0400 Subject: [PATCH] Full API implementation --- internal/controllers/api.go | 54 ++++++++++++++++++------------- internal/controllers/guestbook.go | 21 ------------ pkg/router/request.go | 36 +++++++++++++-------- 3 files changed, 54 insertions(+), 57 deletions(-) delete mode 100644 internal/controllers/guestbook.go diff --git a/internal/controllers/api.go b/internal/controllers/api.go index 5d4a34d..df4fd52 100644 --- a/internal/controllers/api.go +++ b/internal/controllers/api.go @@ -3,43 +3,53 @@ package controllers import ( "addrss/pkg/postal" "addrss/pkg/router" - "fmt" ) type Api struct{} -type ParseRequest struct { - Address string `json:"address"` -} - func (a Api) AddRoutes() { - router.AddPost("/v1/expand", expandAddress).Anonymous() - router.AddPost("/v1/parse", parseAddress).Anonymous() + router.AddGet("/suggestions", suggestions).Anonymous() + router.AddGet("/parse", parse).Anonymous() } -func expandAddress(ctx *router.Context) { - expansions := postal.ExpandAddress("1080 Brayden Ct. Hebron KY 41048") - for i := 0; i < len(expansions); i++ { - fmt.Println(expansions[i]) - } - - ctx.Response.NoContent() -} - -func parseAddress(ctx *router.Context) { - pr := ParseRequest{} - if err := ctx.Request.Bind(&pr); err != nil { +func suggestions(ctx *router.Context) { + address, err := ctx.Request.Query("address") + if err != nil { ctx.Response.BadRequest(err) } - options := postal.ParserOptions{} + var parsedSlice []map[string]any - pa := postal.ParseAddressOptions(pr.Address, options) + expansions := postal.ExpandAddress(address.(string)) + for i := 0; i < len(expansions); i++ { + parsed := parseAddress(expansions[i]) + parsedSlice = append(parsedSlice, parsed) + } + + ctx.Response.OK(parsedSlice) +} + +func parse(ctx *router.Context) { + address, err := ctx.Request.Query("address") + if err != nil { + ctx.Response.BadRequest(err) + } + + parsed := parseAddress(address.(string)) + ctx.Response.OK(parsed) +} + +func parseAddress(address string) map[string]any { + pa := postal.ParseAddress(address) addr := map[string]any{} for i := 0; i < len(pa); i++ { + // This is hacky, but renaming in libpostal involves retraining the model. + if pa[i].Label == "postcode" { + pa[i].Label = "zip_code" + } addr[pa[i].Label] = pa[i].Value } - ctx.Response.OK(addr) + return addr } diff --git a/internal/controllers/guestbook.go b/internal/controllers/guestbook.go deleted file mode 100644 index 6f8feb2..0000000 --- a/internal/controllers/guestbook.go +++ /dev/null @@ -1,21 +0,0 @@ -package controllers - -import ( - "addrss/pkg/router" -) - -type Guestbook struct{} - -type guestbookError struct { - Fields []string `json:"fields"` -} - -func (g Guestbook) AddRoutes() { - router.AddPost("/guestbook", signGuestbook).Anonymous() -} - -func signGuestbook(ctx *router.Context) { - //gb := ctx.Request.Model.(repo.Guestbook) - - ctx.Response.NoContent() -} diff --git a/pkg/router/request.go b/pkg/router/request.go index 9b858d8..d76e6f4 100644 --- a/pkg/router/request.go +++ b/pkg/router/request.go @@ -6,7 +6,7 @@ import ( "fmt" "mime/multipart" "net/http" - "strconv" + "net/url" "strings" ) @@ -16,36 +16,44 @@ type Request struct { } func (req Request) Param(key string) (any, error) { - value, ok := req.params[key] - if ok { + if value, ok := req.params[key]; ok { return value, nil } return nil, fmt.Errorf("param %s not found", key) } -func (req Request) ParamInt64(key string) (int64, error) { - param, err := req.Param(key) +func (req Request) Query(key string) (any, error) { + values, err := url.ParseQuery(req.Request.URL.RawQuery) if err != nil { - return 0, err + return nil, err } - i, err := strconv.Atoi(param.(string)) - if err != nil { - return 0, err + value, ok := values[key] + if !ok { + return nil, fmt.Errorf("query string parameter %s not found", key) } - return int64(i), nil + if len(value) > 1 { + return value, nil + } + + return value[0], nil } -// Bind Binds the request body to the struct pointer v using the Content-Type header to select a binder. This function panics if v cannot be bound. +// Bind Binds the request body to the struct pointer v using the Content-Type header to select a binder. func (req Request) Bind(v any) error { + body, err := req.Request.GetBody() + if err != nil { + return err + } + mime := req.Header.Get("Content-Type") mime = strings.Split(mime, ";")[0] switch mime { case "application/json": - err := json.NewDecoder(req.Body).Decode(&v) + err := json.NewDecoder(body).Decode(&v) if err != nil { return fmt.Errorf("error while decoding http request body as json: %w", err) } @@ -57,13 +65,13 @@ func (req Request) Bind(v any) error { return nil } -func (req Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) { +func (req Request) File(key string) (multipart.File, *multipart.FileHeader, error) { maxMegs, err := config.GetInt64("uploads.maxMegabytes") if err != nil { maxMegs = 8000000 } - if err := req.ParseMultipartForm(maxMegs << 20); err != nil { + if err = req.ParseMultipartForm(maxMegs << 20); err != nil { return nil, nil, err }