diff --git a/platform/geocode/geocode.go b/platform/geocode/geocode.go index 72a17171..8dcfc4b4 100644 --- a/platform/geocode/geocode.go +++ b/platform/geocode/geocode.go @@ -32,6 +32,63 @@ func InitializeStadia(key string) { client = stadia.NewStadiaMaps(key) } +// Ensure the provided address exists. If it doesn't add it to the database. +func EnsureAddress(ctx context.Context, txn bob.Tx, a types.Address, l types.Location) (*models.Address, error) { + address, err := models.Addresses.Query( + models.SelectWhere.Addresses.Country.EQ(a.CountryEnum()), + models.SelectWhere.Addresses.Locality.EQ(a.Locality), + models.SelectWhere.Addresses.Number.EQ(a.Number), + models.SelectWhere.Addresses.PostalCode.EQ(a.PostalCode), + models.SelectWhere.Addresses.Region.EQ(a.Region), + models.SelectWhere.Addresses.Street.EQ(a.Street), + models.SelectWhere.Addresses.Unit.EQ(a.Unit), + ).One(ctx, txn) + if err == nil { + return address, nil + } + cell, err := h3utils.GetCell(l.Longitude, l.Latitude, 15) + if err != nil { + return nil, fmt.Errorf("failed to convert lat %f lng %f to h3 cell", l.Longitude, l.Latitude) + } + type _row struct { + ID int32 `db:"id"` + } + created := time.Now() + row, err := bob.One(ctx, txn, psql.Insert( + im.Into("address", "country", "created", "h3cell", "id", "locality", "location", "number_", "postal_code", "region", "street", "unit"), + im.Values( + psql.Arg(a.Country), + psql.Arg(created), + psql.Arg(cell), + psql.Raw("DEFAULT"), + psql.Arg(a.Locality), + psql.F("ST_Point", l.Longitude, l.Latitude, 4326), + psql.Arg(a.Number), + psql.Arg(a.PostalCode), + psql.Arg(a.Region), + psql.Arg(a.Street), + psql.Raw("''"), + ), + im.Returning("id"), + ), scan.StructMapper[_row]()) + if err != nil { + return nil, fmt.Errorf("insert: %w", err) + } + return &models.Address{ + Country: a.CountryEnum(), + Created: created, + H3cell: "", + ID: row.ID, + Locality: a.Locality, + Location: "", + PostalCode: a.PostalCode, + Street: a.Street, + Unit: a.Unit, + Region: a.Region, + Number: a.Number, + }, nil +} + // Either get an address that matches, or create a new address. Either way, return an address // This will make a call to a structured geocode service, so it's slow. func EnsureAddressWithGeocode(ctx context.Context, txn bob.Tx, org *models.Organization, a types.Address) (*models.Address, error) { diff --git a/rmo/nuisance.go b/rmo/nuisance.go index 733561ba..00dc789a 100644 --- a/rmo/nuisance.go +++ b/rmo/nuisance.go @@ -13,7 +13,9 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/html" + "github.com/Gleipnir-Technology/nidus-sync/platform/geocode" "github.com/Gleipnir-Technology/nidus-sync/platform/report" + "github.com/Gleipnir-Technology/nidus-sync/platform/types" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" @@ -80,7 +82,7 @@ func postNuisance(w http.ResponseWriter, r *http.Request) { return } additional_info := r.PostFormValue("additional-info") - address := r.PostFormValue("address") + address_raw := r.PostFormValue("address") address_country := r.PostFormValue("address-country") address_locality := r.PostFormValue("address-locality") address_number := r.PostFormValue("address-number") @@ -129,7 +131,6 @@ func postNuisance(w http.ResponseWriter, r *http.Request) { is_location_pool = true } //log.Debug().Bool("is_location_backyard", is_location_backyard).Bool("is_location_frontyard", is_location_frontyard).Bool("is_location_garden", is_location_garden).Bool("is_location_other", is_location_other).Bool("is_location_pool", is_location_pool).Msg("parsed") - public_id, err := report.GenerateReportID() if err != nil { respondError(w, "Failed to create report public ID", err, http.StatusInternalServerError) @@ -149,6 +150,23 @@ func postNuisance(w http.ResponseWriter, r *http.Request) { } defer txn.Rollback(ctx) + // If we've got an address_country value it was set by geocoding so we should save it + var address *models.Address + if address_country != "" && latlng.Latitude != nil && latlng.Longitude != nil { + address, err = geocode.EnsureAddress(ctx, txn, types.Address{ + Country: address_country, + Locality: address_locality, + Number: address_number, + PostalCode: address_postal_code, + Region: address_region, + Street: address_street, + Unit: "", + }, types.Location{ + Latitude: *latlng.Latitude, + Longitude: *latlng.Longitude, + }) + } + uploads, err := extractImageUploads(r) log.Info().Int("len", len(uploads)).Msg("extracted uploads") if err != nil { @@ -167,8 +185,9 @@ func postNuisance(w http.ResponseWriter, r *http.Request) { } setter := models.PublicreportNuisanceSetter{ - AdditionalInfo: omit.From(additional_info), - AddressRaw: omit.From(address), + AdditionalInfo: omit.From(additional_info), + //AddressID: omitnull.From(geospatial.Cell.String()), + AddressRaw: omit.From(address_raw), AddressCountry: omit.From(address_country), AddressNumber: omit.From(address_number), AddressLocality: omit.From(address_locality), @@ -203,6 +222,9 @@ func postNuisance(w http.ResponseWriter, r *http.Request) { TodEvening: omit.From(tod_evening), TodNight: omit.From(tod_night), } + if address != nil { + setter.AddressID = omitnull.From(address.ID) + } nuisance, err := models.PublicreportNuisances.Insert(&setter).One(ctx, txn) if err != nil { respondError(w, "Failed to create database record", err, http.StatusInternalServerError)