From bac55774f8d662a82c6f66480033cf4769be9f5c Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 10 Apr 2026 16:59:29 +0000 Subject: [PATCH] Switch address to contain an embedded location, start saving compliance --- api/handler.go | 23 ++++ api/routes.go | 2 +- platform/publicreport.go | 62 +++++++--- platform/types/address.go | 19 +-- platform/types/report.go | 1 - resource/compliance.go | 25 +--- resource/publicreport.go | 57 ++++++++- ts/rmo/components/AddressAndMapLocator.vue | 57 +++++---- ts/rmo/content/Nuisance.vue | 50 +++----- ts/rmo/content/Water.vue | 129 ++++++--------------- ts/rmo/content/compliance/Address.vue | 8 +- ts/rmo/content/compliance/Submit.vue | 4 +- ts/rmo/view/Compliance.vue | 67 ++++++++--- ts/store/local.ts | 8 ++ ts/type/api.ts | 1 + ts/type/map.ts | 6 - 16 files changed, 281 insertions(+), 238 deletions(-) diff --git a/api/handler.go b/api/handler.go index 88ee655b..6bfd15eb 100644 --- a/api/handler.go +++ b/api/handler.go @@ -263,6 +263,29 @@ func handlerJSONPost[RequestType any, ResponseType any](f handlerFunctionPost[Re w.Write(body) } } + +func handlerJSONPut[RequestType any, ResponseType any](f handlerFunctionPost[RequestType, ResponseType]) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + req, e := parseRequest[RequestType](r) + if e != nil { + serializeError(w, e) + return + } + ctx := r.Context() + resp, e := f(ctx, r, *req) + if e != nil { + serializeError(w, e) + return + } + body, err := json.Marshal(resp) + if err != nil { + respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err)) + return + } + w.Write(body) + } +} func handlerFormPost[RequestType any, ResponseType any](f handlerFunctionPostFormMultipart[RequestType, ResponseType]) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") diff --git a/api/routes.go b/api/routes.go index 2be78a2c..48ba55b8 100644 --- a/api/routes.go +++ b/api/routes.go @@ -43,7 +43,6 @@ func AddRoutes(r *mux.Router) { r.Handle("/review-task", authenticatedHandlerJSON(review_task.List)).Methods("GET") compliance := resource.Compliance(router) r.HandleFunc("/rmo/compliance", handlerFormPost(compliance.Create)).Methods("POST") - r.HandleFunc("/rmo/compliance/{public_id}", handlerFormPost(compliance.Update)).Methods("PUT") nuisance := resource.Nuisance(router) r.HandleFunc("/rmo/nuisance", handlerFormPost(nuisance.Create)).Methods("POST") water := resource.Water(router) @@ -83,6 +82,7 @@ func AddRoutes(r *mux.Router) { r.Handle("/geocode/suggestion", handlerJSONSlice(geocode.SuggestionList)).Methods("GET") publicreport := resource.Publicreport(router) r.Handle("/publicreport/{id}", handlerJSON(publicreport.ByID)).Methods("GET").Name("publicreport.ByIDGet") + r.Handle("/publicreport/{id}", handlerJSONPut(publicreport.Update)).Methods("PUT") publicreport_notification := resource.PublicreportNotification(router) r.Handle("/publicreport-notification", handlerJSONPost(publicreport_notification.Create)).Methods("POST") diff --git a/platform/publicreport.go b/platform/publicreport.go index 9c01bb0b..77a18e75 100644 --- a/platform/publicreport.go +++ b/platform/publicreport.go @@ -29,10 +29,13 @@ func PublicreportByID(ctx context.Context, report_id string) (*types.Report, err return publicreport.Report(ctx, report_id) } func PublicreportInvalid(ctx context.Context, user User, report_id string) error { - report, err := reportFromID(ctx, user, report_id) + report, err := reportFromID(ctx, report_id) if err != nil { return fmt.Errorf("query report existence: %w", err) } + if report.OrganizationID != user.Organization.ID { + return fmt.Errorf("user is from a different organization") + } err = report.Update(ctx, db.PGInstance.BobDB, &models.PublicreportReportSetter{ Reviewed: omitnull.From(time.Now()), @@ -52,10 +55,13 @@ func PublicReportMessageCreate(ctx context.Context, user User, report_id, messag } defer txn.Rollback(ctx) - report, err := reportFromID(ctx, user, report_id) + report, err := reportFromID(ctx, report_id) if err != nil { return nil, fmt.Errorf("query report existence: %w", err) } + if report.OrganizationID != user.Organization.ID { + return nil, fmt.Errorf("user is from a different organization") + } if report.ReporterPhone != "" { log.Debug().Str("report_id", report_id).Msg("contacting via phone") p, err := text.ParsePhoneNumber(report.ReporterPhone) @@ -81,6 +87,29 @@ func PublicReportMessageCreate(ctx context.Context, user User, report_id, messag return nil, errors.New("no contact methods available") } } +func PublicReportUpdate(ctx context.Context, report_id string, report_setter models.PublicreportReportSetter, location *types.Location) (*types.Report, error) { + txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil) + if err != nil { + return nil, fmt.Errorf("create txn: %w", err) + } + defer txn.Rollback(ctx) + report, err := reportFromID(ctx, report_id) + if err != nil { + return nil, fmt.Errorf("query report existence: %w", err) + } + err = report.Update(ctx, txn, &report_setter) + if err != nil { + return nil, fmt.Errorf("update report: %w", err) + } + if location != nil { + err = reportUpdateLocation(ctx, txn, report.ID, *location) + if err != nil { + return nil, fmt.Errorf("update location: %w", err) + } + } + txn.Commit(ctx) + return publicreport.Report(ctx, report_id) +} func PublicReportReporterUpdated(ctx context.Context, org_id int32, report_id string) { event.Updated(event.TypeRMOReport, org_id, report_id) } @@ -170,17 +199,7 @@ func reportCreate(ctx context.Context, setter_report models.PublicreportReportSe if location != nil { l := *location if l.Latitude != 0 && l.Longitude != 0 { - h3cell, _ := location.H3Cell() - geom_query, _ := location.GeometryQuery() - _, err = psql.Update( - um.Table("publicreport.report"), - um.SetCol("h3cell").ToArg(h3cell), - um.SetCol("location").To(geom_query), - um.Where(psql.Quote("id").EQ(psql.Arg(result.ID))), - ).Exec(ctx, txn) - if err != nil { - return nil, fmt.Errorf("Failed to insert publicreport.report geospatial", err) - } + reportUpdateLocation(ctx, txn, result.ID, l) } } log.Info().Str("public_id", public_id).Int32("id", result.ID).Msg("Created base report") @@ -226,13 +245,26 @@ func reportCreate(ctx context.Context, setter_report models.PublicreportReportSe } return result, nil } -func reportFromID(ctx context.Context, user User, report_id string) (*models.PublicreportReport, error) { +func reportFromID(ctx context.Context, report_id string) (*models.PublicreportReport, error) { report, err := models.PublicreportReports.Query( models.SelectWhere.PublicreportReports.PublicID.EQ(report_id), - models.SelectWhere.PublicreportReports.OrganizationID.EQ(user.Organization.ID), ).One(ctx, db.PGInstance.BobDB) if err != nil { return nil, err } return report, nil } +func reportUpdateLocation(ctx context.Context, txn bob.Executor, id int32, location types.Location) error { + h3cell, _ := location.H3Cell() + geom_query, _ := location.GeometryQuery() + _, err := psql.Update( + um.Table("publicreport.report"), + um.SetCol("h3cell").ToArg(h3cell), + um.SetCol("location").To(geom_query), + um.Where(psql.Quote("id").EQ(psql.Arg(id))), + ).Exec(ctx, txn) + if err != nil { + return fmt.Errorf("Failed to insert publicreport.report geospatial", err) + } + return nil +} diff --git a/platform/types/address.go b/platform/types/address.go index a6e3fb67..3d0b5f19 100644 --- a/platform/types/address.go +++ b/platform/types/address.go @@ -7,15 +7,16 @@ import ( ) type Address struct { - Country string `db:"country" json:"country"` - GID string `db:"gid" json:"gid" schema:"gid"` - Locality string `db:"locality" json:"locality"` - Number string `db:"number" json:"number"` - PostalCode string `db:"postal_code" json:"postal_code"` - Raw string `db:"raw" json:"raw" schema:"raw"` - Region string `db:"region" json:"region"` - Street string `db:"street" json:"street"` - Unit string `db:"unit" json:"unit"` + Country string `db:"country" json:"country"` + GID string `db:"gid" json:"gid" schema:"gid"` + Locality string `db:"locality" json:"locality"` + Location *Location `db:"location" json:"location" schema:"location"` + Number string `db:"number" json:"number"` + PostalCode string `db:"postal_code" json:"postal_code"` + Raw string `db:"raw" json:"raw" schema:"raw"` + Region string `db:"region" json:"region"` + Street string `db:"street" json:"street"` + Unit string `db:"unit" json:"unit"` } func (a Address) String() string { diff --git a/platform/types/report.go b/platform/types/report.go index ed8b2fd9..a065dce9 100644 --- a/platform/types/report.go +++ b/platform/types/report.go @@ -6,7 +6,6 @@ import ( type Report struct { Address Address `db:"address" json:"address"` - AddressRaw string `db:"address_raw" json:"address_raw"` Created time.Time `db:"created" json:"created"` ID int32 `db:"id" json:"-"` Images []Image `db:"images" json:"images"` diff --git a/resource/compliance.go b/resource/compliance.go index c3891170..0932534d 100644 --- a/resource/compliance.go +++ b/resource/compliance.go @@ -7,13 +7,13 @@ 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/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + //"github.com/Gleipnir-Technology/nidus-sync/html" nhttp "github.com/Gleipnir-Technology/nidus-sync/http" "github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/platform/types" - "github.com/aarondl/opt/omit" - "github.com/aarondl/opt/omitnull" - "github.com/rs/zerolog/log" + //"github.com/rs/zerolog/log" ) func Compliance(r *router) *complianceR { @@ -92,20 +92,3 @@ func (res *complianceR) Create(ctx context.Context, r *http.Request, n complianc URI: uri, }, nil } -func (res *complianceR) Update(ctx context.Context, r *http.Request, n complianceForm) (*compliance, *nhttp.ErrorWithStatus) { - uploads, err := html.ExtractImageUploads(r) - log.Info().Int("len", len(uploads)).Msg("extracted compliance uploads") - if err != nil { - return nil, nhttp.NewError("Failed to extract image uploads: %w", err) - } - address := platform.Address{ - GID: n.Locator.Address.GID, - Raw: n.Locator.Address.Raw, - } - accuracy := float32(0.0) - if n.Location.Accuracy != nil { - accuracy = *n.Location.Accuracy - } - log.Info().Str("address.raw", address.Raw).Str("address.gid", address.GID).Float32("accuracy", accuracy).Msg("making compliance") - return nil, nil -} diff --git a/resource/publicreport.go b/resource/publicreport.go index 4e5e5183..f8d1db9b 100644 --- a/resource/publicreport.go +++ b/resource/publicreport.go @@ -2,11 +2,14 @@ package resource import ( "context" + "net/http" + "github.com/aarondl/opt/omit" + //"github.com/aarondl/opt/omitnull" + "github.com/Gleipnir-Technology/nidus-sync/db/models" nhttp "github.com/Gleipnir-Technology/nidus-sync/http" "github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/platform/types" - "net/http" //"github.com/rs/zerolog/log" "github.com/gorilla/mux" ) @@ -46,3 +49,55 @@ func (res *publicreportR) ByID(ctx context.Context, r *http.Request, query Query report.URI = uri return report, nil } + +type publicreportForm struct { + Address *types.Address `schema:"address"` + ClientID string `schema:"client_id"` + DistrictID string `schema:"district"` + Location *types.Location `schema:"location"` + Locator *Locator `schema:"locator"` + Reporter *types.Contact `schema:"reporter"` +} + +func (res *publicreportR) Update(ctx context.Context, r *http.Request, prf publicreportForm) (*types.Report, *nhttp.ErrorWithStatus) { + /* + uploads, err := html.ExtractImageUploads(r) + log.Info().Int("len", len(uploads)).Msg("extracted compliance uploads") + if err != nil { + return nil, nhttp.NewError("Failed to extract image uploads: %w", err) + } + */ + vars := mux.Vars(r) + public_id := vars["id"] + if public_id == "" { + return nil, nhttp.NewBadRequest("You must provide an ID") + } + report_setter := models.PublicreportReportSetter{} + if prf.Address != nil { + report_setter.AddressGid = omit.From(prf.Address.GID) + report_setter.AddressRaw = omit.From(prf.Address.Raw) + } + if prf.Location != nil { + //report_setter.Latitude = omit.From(prf.Location.Latitude) + //report_setter.Longitude = omit.From(prf.Location.Longitude) + if prf.Location.Accuracy != nil { + report_setter.LatlngAccuracyValue = omit.From(*prf.Location.Accuracy) + } + } + if prf.Reporter != nil { + if prf.Reporter.Email != nil { + report_setter.ReporterEmail = omit.From(*prf.Reporter.Email) + } + if prf.Reporter.Name != nil { + report_setter.ReporterName = omit.From(*prf.Reporter.Name) + } + if prf.Reporter.Phone != nil { + report_setter.ReporterPhone = omit.From(*prf.Reporter.Phone) + } + } + report, err := platform.PublicReportUpdate(ctx, public_id, report_setter, prf.Location) + if err != nil { + return nil, nhttp.NewError("update report: %w", err) + } + return report, nil +} diff --git a/ts/rmo/components/AddressAndMapLocator.vue b/ts/rmo/components/AddressAndMapLocator.vue index a0309ecd..e3fdef88 100644 --- a/ts/rmo/components/AddressAndMapLocator.vue +++ b/ts/rmo/components/AddressAndMapLocator.vue @@ -38,7 +38,7 @@