From 10e368c40373095738b466159f2146d16d8a6269 Mon Sep 17 00:00:00 2001
From: Eli Ribble
Date: Fri, 3 Apr 2026 22:04:22 +0000
Subject: [PATCH] Get initial nuisance and water resources working
This is a straight port of the form-encoded POST submission logic.
It is missing a bunch of data.
---
api/handler.go | 272 ++++++++++++++++------------------
api/routes.go | 5 +
{rmo => html}/form.go | 11 +-
{rmo => html}/image-upload.go | 8 +-
resource/nuisance.go | 194 ++++++++++++++++++++++++
resource/water.go | 125 ++++++++++++++++
rmo/nuisance.go | 127 +---------------
rmo/report.go | 63 --------
rmo/routes.go | 4 -
rmo/water.go | 111 --------------
ts/components/MapLocator.vue | 2 +-
ts/rmo/content/Nuisance.vue | 46 +++++-
12 files changed, 507 insertions(+), 461 deletions(-)
rename {rmo => html}/form.go (59%)
rename {rmo => html}/image-upload.go (91%)
create mode 100644 resource/nuisance.go
create mode 100644 resource/water.go
diff --git a/api/handler.go b/api/handler.go
index c5501f85..3d9d4e1c 100644
--- a/api/handler.go
+++ b/api/handler.go
@@ -8,7 +8,6 @@ import (
"net/http"
"github.com/Gleipnir-Technology/nidus-sync/auth"
- "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/file"
@@ -18,23 +17,28 @@ import (
"github.com/rs/zerolog/log"
)
+type ErrorAPI struct {
+ Message string `json:"message"`
+}
+
var decoder = schema.NewDecoder()
type handlerFunctionDelete func(context.Context, *http.Request, platform.User) *nhttp.ErrorWithStatus
+type handlerFunctionGet[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) (*T, *nhttp.ErrorWithStatus)
+type handlerFunctionGetImage func(context.Context, *http.Request, platform.User) (file.Collection, uuid.UUID, *nhttp.ErrorWithStatus)
+type handlerFunctionGetSlice[T any] func(context.Context, *http.Request, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus)
+type handlerFunctionGetSliceAuthenticated[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus)
+type handlerFunctionPost[RequestType any] func(context.Context, *http.Request, RequestType) (string, *nhttp.ErrorWithStatus)
+type handlerFunctionPostAuthenticated[RequestType any, ResponseType any] func(context.Context, *http.Request, platform.User, RequestType) (ResponseType, *nhttp.ErrorWithStatus)
+type handlerFunctionPostFormMultipart[RequestType any, ResponseType any] func(context.Context, *http.Request, RequestType) (*ResponseType, *nhttp.ErrorWithStatus)
+type handlerFunctionPutAuthenticated[RequestType any] func(context.Context, *http.Request, platform.User, RequestType) (string, *nhttp.ErrorWithStatus)
func authenticatedHandlerDelete(f handlerFunctionDelete) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
ctx := r.Context()
e := f(ctx, r, u)
if e != nil {
- log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
- body, err := json.Marshal(ErrorAPI{Message: e.Error()})
- if err != nil {
- log.Error().Err(err).Msg("failed to marshal error")
- http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
- return
- }
- http.Error(w, string(body), e.Status)
+ respondErrorStatus(w, e)
return
}
http.Error(w, "", http.StatusNoContent)
@@ -42,39 +46,18 @@ func authenticatedHandlerDelete(f handlerFunctionDelete) http.Handler {
})
}
-type handlerFunctionGetImage func(context.Context, *http.Request, platform.User) (file.Collection, uuid.UUID, *nhttp.ErrorWithStatus)
-
func authenticatedHandlerGetImage(f handlerFunctionGetImage) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
ctx := r.Context()
collection, uid, e := f(ctx, r, u)
if e != nil {
- log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
- body, err := json.Marshal(ErrorAPI{Message: e.Error()})
- if err != nil {
- log.Error().Err(err).Msg("failed to marshal error")
- http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
- return
- }
- http.Error(w, string(body), e.Status)
+ respondErrorStatus(w, e)
return
}
file.ImageFileToWriter(collection, uid, w)
})
}
-type handlerFunctionGet[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) (*T, *nhttp.ErrorWithStatus)
-type wrappedHandler func(http.ResponseWriter, *http.Request)
-type contentAuthenticated[T any] struct {
- C T
- Config html.ContentConfig
- User platform.User
-}
-
-type ErrorAPI struct {
- Message string `json:"message"`
-}
-
func authenticatedHandlerJSON[T any](f handlerFunctionGet[T]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
ctx := r.Context()
@@ -82,37 +65,25 @@ func authenticatedHandlerJSON[T any](f handlerFunctionGet[T]) http.Handler {
var params resource.QueryParams
err := decoder.Decode(¶ms, r.URL.Query())
if err != nil {
- log.Error().Err(err).Msg("decode query failure")
- http.Error(w, "failed to decode query", http.StatusInternalServerError)
+ respondErrorStatus(w, nhttp.NewBadRequest("failed to decode query: %w", err))
return
}
resp, e := f(ctx, r, u, params)
w.Header().Set("Content-Type", "application/json")
//log.Info().Str("template", template).Err(e).Msg("handler done")
if e != nil {
- log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
- body, err = json.Marshal(ErrorAPI{Message: e.Error()})
- if err != nil {
- log.Error().Err(err).Msg("failed to marshal error")
- http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
- return
- }
- http.Error(w, string(body), e.Status)
+ respondErrorStatus(w, e)
return
}
body, err = json.Marshal(resp)
if err != nil {
- log.Error().Err(err).Msg("failed to marshal json")
- http.Error(w, "{\"message\": \"failed to marshal json\"}", http.StatusInternalServerError)
+ respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err))
return
}
w.Write(body)
})
}
-type handlerFunctionGetSlice[T any] func(context.Context, *http.Request, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus)
-type handlerFunctionGetSliceAuthenticated[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus)
-
func authenticatedHandlerJSONSlice[T any](f handlerFunctionGetSliceAuthenticated[T]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
ctx := r.Context()
@@ -120,71 +91,24 @@ func authenticatedHandlerJSONSlice[T any](f handlerFunctionGetSliceAuthenticated
var params resource.QueryParams
err := decoder.Decode(¶ms, r.URL.Query())
if err != nil {
- log.Error().Err(err).Msg("decode query failure")
- http.Error(w, "failed to decode query", http.StatusInternalServerError)
+ respondErrorStatus(w, nhttp.NewBadRequest("failed to decode query: %w", err))
return
}
resp, e := f(ctx, r, u, params)
w.Header().Set("Content-Type", "application/json")
//log.Info().Str("template", template).Err(e).Msg("handler done")
if e != nil {
- log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
- body, err = json.Marshal(ErrorAPI{Message: e.Error()})
- if err != nil {
- log.Error().Err(err).Msg("failed to marshal error")
- http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
- return
- }
- http.Error(w, string(body), e.Status)
+ respondErrorStatus(w, e)
return
}
body, err = json.Marshal(resp)
if err != nil {
- log.Error().Err(err).Msg("failed to marshal json")
- http.Error(w, "{\"message\": \"failed to marshal json\"}", http.StatusInternalServerError)
+ respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err))
return
}
w.Write(body)
})
}
-func handlerJSONSlice[T any](f handlerFunctionGetSlice[T]) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
- var body []byte
- var params resource.QueryParams
- err := decoder.Decode(¶ms, r.URL.Query())
- if err != nil {
- log.Error().Err(err).Msg("decode query failure")
- http.Error(w, "failed to decode query", http.StatusInternalServerError)
- return
- }
- resp, e := f(ctx, r, params)
- w.Header().Set("Content-Type", "application/json")
- //log.Info().Str("template", template).Err(e).Msg("handler done")
- if e != nil {
- log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
- body, err = json.Marshal(ErrorAPI{Message: e.Error()})
- if err != nil {
- log.Error().Err(err).Msg("failed to marshal error")
- http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
- return
- }
- http.Error(w, string(body), e.Status)
- return
- }
- body, err = json.Marshal(resp)
- if err != nil {
- log.Error().Err(err).Msg("failed to marshal json")
- http.Error(w, "{\"message\": \"failed to marshal json\"}", http.StatusInternalServerError)
- return
- }
- w.Write(body)
- }
-}
-
-type handlerFunctionPost[ReqType any] func(context.Context, *http.Request, ReqType) (string, *nhttp.ErrorWithStatus)
-type handlerFunctionPostAuthenticated[RequestType any, ResponseType any] func(context.Context, *http.Request, platform.User, RequestType) (ResponseType, *nhttp.ErrorWithStatus)
-
func authenticatedHandlerJSONPost[RequestType any, ResponseType any](f handlerFunctionPostAuthenticated[RequestType, ResponseType]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
w.Header().Set("Content-Type", "application/json")
@@ -201,20 +125,17 @@ func authenticatedHandlerJSONPost[RequestType any, ResponseType any](f handlerFu
}
body, err := json.Marshal(resp)
if err != nil {
- log.Error().Err(err).Msg("failed to marshal json")
- http.Error(w, "{\"message\": \"failed to marshal json\"}", http.StatusInternalServerError)
+ respondErrorStatus(w, nhttp.NewError("failed to marshal json: %w", err))
return
}
w.Write(body)
})
}
-type handlerFunctionPutAuthenticated[ReqType any] func(context.Context, *http.Request, platform.User, ReqType) (string, *nhttp.ErrorWithStatus)
-
-func authenticatedHandlerJSONPut[ReqType any](f handlerFunctionPutAuthenticated[ReqType]) http.Handler {
+func authenticatedHandlerJSONPut[RequestType any](f handlerFunctionPutAuthenticated[RequestType]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
w.Header().Set("Content-Type", "application/json")
- req, e := parseRequest[ReqType](r)
+ req, e := parseRequest[RequestType](r)
if e != nil {
serializeError(w, e)
return
@@ -233,50 +154,6 @@ func authenticatedHandlerJSONPut[ReqType any](f handlerFunctionPutAuthenticated[
http.Redirect(w, r, path, http.StatusCreated)
})
}
-func parseRequest[ReqType any](r *http.Request) (*ReqType, *nhttp.ErrorWithStatus) {
- var req ReqType
- body, err := io.ReadAll(r.Body)
- if err != nil {
- return nil, nhttp.NewError("Failed to read body: %w", err)
- }
- err = json.Unmarshal(body, &req)
- if err != nil {
- return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "Failed to decode request: %w", err)
- }
- return &req, nil
-}
-func serializeError(w http.ResponseWriter, e *nhttp.ErrorWithStatus) {
- log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
- body, err := json.Marshal(ErrorAPI{Message: e.Error()})
- if err != nil {
- log.Error().Err(err).Msg("failed to marshal error")
- http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
- return
- }
- http.Error(w, string(body), e.Status)
- return
-}
-func handlerJSONPost[ReqType any](f handlerFunctionPost[ReqType]) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "application/json")
- req, e := parseRequest[ReqType](r)
- if e != nil {
- serializeError(w, e)
- return
- }
- ctx := r.Context()
- path, e := f(ctx, r, *req)
- if e != nil {
- return
- }
- http.Redirect(w, r, path, http.StatusFound)
- }
-}
-
-type postMultipartResponse struct {
- URI string `json:"uri"`
-}
-
func authenticatedHandlerPostMultipart[ResponseType any](f handlerFunctionPostAuthenticated[[]file.Upload, ResponseType], collection file.Collection) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
@@ -312,6 +189,99 @@ func authenticatedHandlerPostMultipart[ResponseType any](f handlerFunctionPostAu
w.Write(body)
})
}
+func handlerJSONSlice[T any](f handlerFunctionGetSlice[T]) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+ var body []byte
+ var params resource.QueryParams
+ err := decoder.Decode(¶ms, r.URL.Query())
+ if err != nil {
+ respondErrorStatus(w, nhttp.NewBadRequest("failed to decode query: %w", err))
+ return
+ }
+ resp, e := f(ctx, r, params)
+ w.Header().Set("Content-Type", "application/json")
+ //log.Info().Str("template", template).Err(e).Msg("handler done")
+ if e != nil {
+ respondErrorStatus(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 handlerJSONPost[RequestType any](f handlerFunctionPost[RequestType]) 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()
+ path, e := f(ctx, r, *req)
+ if e != nil {
+ return
+ }
+ http.Redirect(w, r, path, http.StatusFound)
+ }
+}
+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")
+ err := r.ParseMultipartForm(32 << 12) // 128 MB buffer
+ if err != nil {
+ respondErrorStatus(w, nhttp.NewBadRequest("bad form: %w", err))
+ return
+ }
+ var req RequestType
+ err = decoder.Decode(&req, r.PostForm)
+ if err != nil {
+ respondErrorStatus(w, nhttp.NewBadRequest("decode form: %w", err))
+ 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 parseRequest[RequestType any](r *http.Request) (*RequestType, *nhttp.ErrorWithStatus) {
+ var req RequestType
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ return nil, nhttp.NewError("Failed to read body: %w", err)
+ }
+ err = json.Unmarshal(body, &req)
+ if err != nil {
+ return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "Failed to decode request: %w", err)
+ }
+ return &req, nil
+}
+func serializeError(w http.ResponseWriter, e *nhttp.ErrorWithStatus) {
+ log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
+ body, err := json.Marshal(ErrorAPI{Message: e.Error()})
+ if err != nil {
+ log.Error().Err(err).Msg("failed to marshal error")
+ http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
+ return
+ }
+ http.Error(w, string(body), e.Status)
+ return
+}
func respondError(w http.ResponseWriter, status int, format string, args ...any) {
outer_err := fmt.Errorf(format, args...)
body, err := json.Marshal(ErrorAPI{
@@ -323,3 +293,13 @@ func respondError(w http.ResponseWriter, status int, format string, args ...any)
}
http.Error(w, string(body), status)
}
+func respondErrorStatus(w http.ResponseWriter, e *nhttp.ErrorWithStatus) {
+ log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
+ body, err := json.Marshal(ErrorAPI{Message: e.Error()})
+ if err != nil {
+ log.Error().Err(err).Msg("failed to marshal error")
+ http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
+ return
+ }
+ http.Error(w, string(body), e.Status)
+}
diff --git a/api/routes.go b/api/routes.go
index 4c3d3187..74263142 100644
--- a/api/routes.go
+++ b/api/routes.go
@@ -34,12 +34,17 @@ func AddRoutes(r *mux.Router) {
r.Handle("/leads", authenticatedHandlerJSON(lead.List)).Methods("GET")
r.Handle("/leads", authenticatedHandlerJSONPost(lead.Create)).Methods("POST")
r.Handle("/mosquito-source", auth.NewEnsureAuth(apiMosquitoSource)).Methods("GET")
+
r.Handle("/publicreport/invalid", authenticatedHandlerJSONPost(postPublicreportInvalid)).Methods("POST")
r.Handle("/publicreport/signal", authenticatedHandlerJSONPost(postPublicreportSignal)).Methods("POST")
r.Handle("/publicreport/message", authenticatedHandlerJSONPost(postPublicreportMessage)).Methods("POST")
r.Handle("/review/pool", authenticatedHandlerJSONPost(postReviewPool)).Methods("POST")
review_task := resource.ReviewTask(r)
r.Handle("/review-task", authenticatedHandlerJSON(review_task.List)).Methods("GET")
+ nuisance := resource.Nuisance(router)
+ r.HandleFunc("/rmo/nuisance", handlerFormPost(nuisance.Create)).Methods("POST")
+ water := resource.Water(router)
+ r.HandleFunc("/rmo/water", handlerFormPost(water.Create)).Methods("POST")
r.Handle("/service-request", auth.NewEnsureAuth(apiServiceRequest)).Methods("GET")
session := resource.Session(router)
r.Handle("/session", authenticatedHandlerJSON(session.Get)).Methods("GET").Name("session.get")
diff --git a/rmo/form.go b/html/form.go
similarity index 59%
rename from rmo/form.go
rename to html/form.go
index 66631f81..f5d1a19b 100644
--- a/rmo/form.go
+++ b/html/form.go
@@ -1,9 +1,16 @@
-package rmo
+package html
import (
"net/http"
)
+func BoolFromForm(r *http.Request, k string) bool {
+ s := r.PostFormValue(k)
+ if s == "on" {
+ return true
+ }
+ return false
+}
func postFormBool(r *http.Request, k string) *bool {
v := r.PostFormValue(k)
if v == "" {
@@ -17,7 +24,7 @@ func postFormBool(r *http.Request, k string) *bool {
return &result
}
-func postFormValueOrNone(r *http.Request, k string) string {
+func PostFormValueOrNone(r *http.Request, k string) string {
v := r.PostFormValue(k)
if v == "" {
return "none"
diff --git a/rmo/image-upload.go b/html/image-upload.go
similarity index 91%
rename from rmo/image-upload.go
rename to html/image-upload.go
index ef45e356..c145381f 100644
--- a/rmo/image-upload.go
+++ b/html/image-upload.go
@@ -1,4 +1,4 @@
-package rmo
+package html
import (
"bytes"
@@ -16,7 +16,7 @@ import (
"net/http"
)
-func extractImageUpload(headers *multipart.FileHeader) (upload platform.ImageUpload, err error) {
+func ExtractImageUpload(headers *multipart.FileHeader) (upload platform.ImageUpload, err error) {
f, err := headers.Open()
if err != nil {
return upload, fmt.Errorf("Failed to open header: %w", err)
@@ -55,11 +55,11 @@ func extractImageUpload(headers *multipart.FileHeader) (upload platform.ImageUpl
}, nil
}
-func extractImageUploads(r *http.Request) (uploads []platform.ImageUpload, err error) {
+func ExtractImageUploads(r *http.Request) (uploads []platform.ImageUpload, err error) {
uploads = make([]platform.ImageUpload, 0)
for _, fheaders := range r.MultipartForm.File {
for _, headers := range fheaders {
- upload, err := extractImageUpload(headers)
+ upload, err := ExtractImageUpload(headers)
if err != nil {
return make([]platform.ImageUpload, 0), fmt.Errorf("Failed to extract photo upload: %w", err)
}
diff --git a/resource/nuisance.go b/resource/nuisance.go
new file mode 100644
index 00000000..f46fe4a0
--- /dev/null
+++ b/resource/nuisance.go
@@ -0,0 +1,194 @@
+package resource
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "slices"
+ "strconv"
+ "time"
+
+ "github.com/Gleipnir-Technology/nidus-sync/db/enums"
+ "github.com/Gleipnir-Technology/nidus-sync/db/models"
+ "github.com/Gleipnir-Technology/nidus-sync/html"
+ nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
+ "github.com/Gleipnir-Technology/nidus-sync/platform"
+ "github.com/aarondl/opt/omit"
+ "github.com/aarondl/opt/omitnull"
+ "github.com/rs/zerolog/log"
+)
+
+func Nuisance(r *router) *nuisanceR {
+ return &nuisanceR{
+ router: r,
+ }
+}
+
+type nuisanceR struct {
+ router *router
+}
+type nuisance struct {
+ ID string `json:"id"`
+}
+type nuisanceForm struct {
+ AdditionalInfo string `schema:"additional-info"`
+ AddressRaw string `schema:"address"`
+ AddressCountry string `schema:"address-country"`
+ AddressLocality string `schema:"address-locality"`
+ AddressNumber string `schema:"address-number"`
+ AddressPostalCode string `schema:"address-postalcode"`
+ AddressRegion string `schema:"address-region"`
+ AddressStreet string `schema:"address-street"`
+ Duration string `schema:"duration"`
+ Latitude string `schema:"latitude"`
+ Longitude string `schema:"longitude"`
+ LatlngAccuracyType string `schema:"latlng-accuracy-type"`
+ LatlngAccuracyValue string `schema:"latlng-accuracy-value"`
+ MapZoom string `schema:"map-zoom"`
+ SourceStagnant bool `schema:"source-stagnant"`
+ SourceContainer bool `schema:"source-container"`
+ SourceDescription string `schema:"source-description"`
+ SourceGutters bool `schema:"source-gutters"`
+ SourceLocations []string `schema:"source-location"`
+ TODEarly bool `schema:"tod-early"`
+ TODDay bool `schema:"tod-day"`
+ TODEvening bool `schema:"tod-evening"`
+ TODNight bool `schema:"tod-night"`
+}
+
+func parseLatLng(r *http.Request) (platform.LatLng, error) {
+ result := platform.LatLng{
+ AccuracyType: enums.PublicreportAccuracytypeNone,
+ AccuracyValue: 0.0,
+ Latitude: nil,
+ Longitude: nil,
+ MapZoom: 0.0,
+ }
+ latitude_str := r.FormValue("latitude")
+ longitude_str := r.FormValue("longitude")
+ latlng_accuracy_type_str := r.PostFormValue("latlng-accuracy-type")
+ latlng_accuracy_value_str := r.PostFormValue("latlng-accuracy-value")
+ map_zoom_str := r.PostFormValue("map-zoom")
+
+ var err error
+ if latlng_accuracy_type_str != "" {
+ err := result.AccuracyType.Scan(latlng_accuracy_type_str)
+ if err != nil {
+ return result, fmt.Errorf("Failed to parse accuracy type '%s': %w", latlng_accuracy_type_str, err)
+ }
+ }
+ if latlng_accuracy_value_str != "" {
+ var t float64
+ t, err = strconv.ParseFloat(latlng_accuracy_value_str, 32)
+ if err != nil {
+ return result, fmt.Errorf("Failed to parse latlng_accuracy_value '%s': %w", latlng_accuracy_value_str, err)
+ }
+ result.AccuracyValue = float64(t)
+ }
+
+ if latitude_str != "" {
+ var t float64
+ t, err = strconv.ParseFloat(latitude_str, 64)
+ if err != nil {
+ return result, fmt.Errorf("Failed to parse latitude '%s': %w", latitude_str, err)
+ }
+ result.Latitude = &t
+ }
+ if longitude_str != "" {
+ var t float64
+ t, err := strconv.ParseFloat(longitude_str, 64)
+ if err != nil {
+ return result, fmt.Errorf("Failed to parse longitude '%s': %w", longitude_str, err)
+ }
+ result.Longitude = &t
+ }
+
+ if map_zoom_str != "" {
+ var t float64
+ t, err = strconv.ParseFloat(map_zoom_str, 32)
+ if err != nil {
+ return result, fmt.Errorf("Failed to parse map_zoom_str '%s': %w", map_zoom_str, err)
+ } else {
+ result.MapZoom = float32(t)
+ }
+ }
+ return result, nil
+}
+
+func (res *nuisanceR) Create(ctx context.Context, r *http.Request, n nuisanceForm) (*nuisance, *nhttp.ErrorWithStatus) {
+ duration := enums.PublicreportNuisancedurationtypeNone
+ is_location_frontyard := slices.Contains(n.SourceLocations, "frontyard")
+ is_location_backyard := slices.Contains(n.SourceLocations, "backyard")
+ is_location_garden := slices.Contains(n.SourceLocations, "garden")
+ is_location_pool := slices.Contains(n.SourceLocations, "pool-area")
+ is_location_other := slices.Contains(n.SourceLocations, "other")
+
+ latlng, err := parseLatLng(r)
+
+ err = duration.Scan(n.Duration)
+ if err != nil {
+ log.Warn().Err(err).Str("duration_str", n.Duration).Msg("Failed to interpret 'duration'")
+ }
+
+ uploads, err := html.ExtractImageUploads(r)
+ log.Info().Int("len", len(uploads)).Msg("extracted uploads")
+ if err != nil {
+ return nil, nhttp.NewError("Failed to extract image uploads: %w", err)
+ }
+ address := platform.Address{
+ Country: n.AddressCountry,
+ Locality: n.AddressLocality,
+ Number: n.AddressNumber,
+ PostalCode: n.AddressPostalCode,
+ Raw: n.AddressRaw,
+ Region: n.AddressRegion,
+ Street: n.AddressStreet,
+ Unit: "",
+ }
+ setter_report := models.PublicreportReportSetter{
+ //AddressID: omitnull.From(latlng.Cell.String()),
+ AddressRaw: omit.From(address.Raw),
+ AddressCountry: omit.From(address.Country),
+ AddressNumber: omit.From(address.Number),
+ AddressLocality: omit.From(address.Locality),
+ AddressPostalCode: omit.From(address.PostalCode),
+ AddressRegion: omit.From(address.Region),
+ AddressStreet: omit.From(address.Street),
+ Created: omit.From(time.Now()),
+ //H3cell: omitnull.From(latlng.Cell.String()),
+ LatlngAccuracyType: omit.From(latlng.AccuracyType),
+ LatlngAccuracyValue: omit.From(float32(latlng.AccuracyValue)),
+ //Location: omitnull.From(fmt.Sprintf("ST_GeometryFromText(Point(%s %s))", longitude, latitude)),
+ Location: omitnull.FromPtr[string](nil),
+ MapZoom: omit.From(latlng.MapZoom),
+ //OrganizationID: omitnull.FromPtr(organization_id),
+ //PublicID: omit.From(public_id),
+ ReporterEmail: omit.From(""),
+ ReporterName: omit.From(""),
+ ReporterPhone: omit.From(""),
+ ReportType: omit.From(enums.PublicreportReporttypeNuisance),
+ Status: omit.From(enums.PublicreportReportstatustypeReported),
+ }
+ setter_nuisance := models.PublicreportNuisanceSetter{
+ AdditionalInfo: omit.From(n.AdditionalInfo),
+ Duration: omit.From(duration),
+ IsLocationBackyard: omit.From(is_location_backyard),
+ IsLocationFrontyard: omit.From(is_location_frontyard),
+ IsLocationGarden: omit.From(is_location_garden),
+ IsLocationOther: omit.From(is_location_other),
+ IsLocationPool: omit.From(is_location_pool),
+ //ReportID omit.Val[int32]
+ SourceContainer: omit.From(n.SourceContainer),
+ SourceDescription: omit.From(n.SourceDescription),
+ SourceGutter: omit.From(n.SourceGutters),
+ SourceStagnant: omit.From(n.SourceStagnant),
+ TodDay: omit.From(n.TODDay),
+ TodEarly: omit.From(n.TODEarly),
+ TodEvening: omit.From(n.TODEvening),
+ TodNight: omit.From(n.TODNight),
+ }
+ report, err := platform.ReportNuisanceCreate(ctx, setter_report, setter_nuisance, latlng, address, uploads)
+ return &nuisance{
+ ID: report.PublicID,
+ }, nil
+}
diff --git a/resource/water.go b/resource/water.go
new file mode 100644
index 00000000..948b25e7
--- /dev/null
+++ b/resource/water.go
@@ -0,0 +1,125 @@
+package resource
+
+import (
+ "context"
+ "net/http"
+ "time"
+
+ "github.com/Gleipnir-Technology/nidus-sync/db/enums"
+ "github.com/Gleipnir-Technology/nidus-sync/db/models"
+ "github.com/Gleipnir-Technology/nidus-sync/html"
+ nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
+ "github.com/Gleipnir-Technology/nidus-sync/platform"
+ "github.com/aarondl/opt/omit"
+ //"github.com/aarondl/opt/omitnull"
+ //"github.com/rs/zerolog/log"
+)
+
+func Water(r *router) *waterR {
+ return &waterR{
+ router: r,
+ }
+}
+
+type waterR struct {
+ router *router
+}
+type water struct {
+ ID string `json:"id"`
+}
+type waterForm struct {
+ AccessComments string `schema:"access-comments"`
+ AccessDog bool `schema:"access-dog"`
+ AccessFence bool `schema:"access-fence"`
+ AccessGate bool `schema:"access-gate"`
+ AccessLocked bool `schema:"access-locked"`
+ AccessOther bool `schema:"access-other"`
+ AddressRaw string `schema:"address"`
+ AddressCountry string `schema:"address-country"`
+ AddressLocality string `schema:"address-locality"`
+ AddressNumber string `schema:"address-number"`
+ AddressPostalCode string `schema:"address-postalcode"`
+ AddressRegion string `schema:"address-region"`
+ AddressStreet string `schema:"address-street"`
+ Comments string `schema:"comments"`
+ HasAdult bool `schema:"has-adult"`
+ HasBackyardPermission bool `schema:"backyard-permission"`
+ HasLarvae bool `schema:"has-larvae"`
+ HasPupae bool `schema:"has-pupae"`
+ IsReporterConfidential bool `schema:"reporter-confidential"`
+ IsReporter_owner bool `schema:"property-ownership"`
+ OwnerEmail string `schema:"owner-email"`
+ OwnerName string `schema:"owner-name"`
+ OwnerPhone string `schema:"owner-phone"`
+}
+
+func (res *waterR) Create(ctx context.Context, r *http.Request, w waterForm) (*water, *nhttp.ErrorWithStatus) {
+ latlng, err := parseLatLng(r)
+ if err != nil {
+ return nil, nhttp.NewError("Failed to parse lat lng for water report: %w", err)
+ }
+
+ uploads, err := html.ExtractImageUploads(r)
+ if err != nil {
+ return nil, nhttp.NewError("Failed to extract image uploads: %w", err)
+ }
+
+ address := platform.Address{
+ Country: w.AddressCountry,
+ Locality: w.AddressLocality,
+ Number: w.AddressNumber,
+ PostalCode: w.AddressPostalCode,
+ Raw: w.AddressRaw,
+ Region: w.AddressRegion,
+ Street: w.AddressStreet,
+ Unit: "",
+ }
+ setter_report := models.PublicreportReportSetter{
+ AddressRaw: omit.From(address.Raw),
+ AddressCountry: omit.From(address.Country),
+ AddressNumber: omit.From(address.Number),
+ AddressLocality: omit.From(address.Locality),
+ AddressPostalCode: omit.From(address.PostalCode),
+ AddressRegion: omit.From(address.Region),
+ AddressStreet: omit.From(address.Street),
+ Created: omit.From(time.Now()),
+ //H3cell: omitnull.From(geospatial.Cell.String()),
+ LatlngAccuracyType: omit.From(latlng.AccuracyType),
+ LatlngAccuracyValue: omit.From(float32(latlng.AccuracyValue)),
+ //Location: add later
+ MapZoom: omit.From(latlng.MapZoom),
+ //OrganizationID: omitnull.FromPtr(organization_id),
+ //PublicID: omit.From(public_id),
+ ReporterEmail: omit.From(""),
+ ReporterName: omit.From(""),
+ ReporterPhone: omit.From(""),
+ ReportType: omit.From(enums.PublicreportReporttypeWater),
+ Status: omit.From(enums.PublicreportReportstatustypeReported),
+ }
+ setter_water := models.PublicreportWaterSetter{
+ AccessComments: omit.From(w.AccessComments),
+ AccessDog: omit.From(w.AccessDog),
+ AccessFence: omit.From(w.AccessFence),
+ AccessGate: omit.From(w.AccessGate),
+ AccessLocked: omit.From(w.AccessLocked),
+ AccessOther: omit.From(w.AccessOther),
+ Comments: omit.From(w.Comments),
+ HasAdult: omit.From(w.HasAdult),
+ HasBackyardPermission: omit.From(w.HasBackyardPermission),
+ HasLarvae: omit.From(w.HasLarvae),
+ HasPupae: omit.From(w.HasPupae),
+ IsReporterConfidential: omit.From(w.IsReporterConfidential),
+ IsReporterOwner: omit.From(w.IsReporter_owner),
+ OwnerEmail: omit.From(w.OwnerEmail),
+ OwnerName: omit.From(w.OwnerName),
+ OwnerPhone: omit.From(w.OwnerPhone),
+ //ReportID omit.Val[int32]
+ }
+ report, err := platform.ReportWaterCreate(ctx, setter_report, setter_water, latlng, address, uploads)
+ if err != nil {
+ return nil, nhttp.NewError("Failed to save new report: %w", err)
+ }
+ return &water{
+ ID: report.PublicID,
+ }, nil
+}
diff --git a/rmo/nuisance.go b/rmo/nuisance.go
index 4ae5e349..6673c291 100644
--- a/rmo/nuisance.go
+++ b/rmo/nuisance.go
@@ -3,17 +3,10 @@ package rmo
import (
"fmt"
"net/http"
- "slices"
- "time"
- "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"
"github.com/Gleipnir-Technology/nidus-sync/platform/report"
- "github.com/aarondl/opt/omit"
- "github.com/aarondl/opt/omitnull"
- "github.com/rs/zerolog/log"
+ //"github.com/rs/zerolog/log"
)
type ContentNuisance struct {
@@ -69,121 +62,3 @@ func getSubmitComplete(w http.ResponseWriter, r *http.Request) {
},
)
}
-func postNuisance(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
- err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
- if err != nil {
- respondError(w, "Failed to parse form", err, http.StatusBadRequest)
- return
- }
- additional_info := r.PostFormValue("additional-info")
- address_raw := r.PostFormValue("address")
- address_country := r.PostFormValue("address-country")
- address_locality := r.PostFormValue("address-locality")
- address_number := r.PostFormValue("address-number")
- address_postal_code := r.PostFormValue("address-postalcode")
- address_region := r.PostFormValue("address-region")
- address_street := r.PostFormValue("address-street")
- duration_str := postFormValueOrNone(r, "duration")
- source_stagnant := boolFromForm(r, "source-stagnant")
- source_container := boolFromForm(r, "source-container")
- source_description := r.PostFormValue("source-description")
- source_gutters := boolFromForm(r, "source-gutters")
- source_locations := r.Form["source-location"]
- tod_early := boolFromForm(r, "tod-early")
- tod_day := boolFromForm(r, "tod-day")
- tod_evening := boolFromForm(r, "tod-evening")
- tod_night := boolFromForm(r, "tod-night")
-
- duration := enums.PublicreportNuisancedurationtypeNone
- is_location_frontyard := false
- is_location_backyard := false
- is_location_garden := false
- is_location_pool := false
- is_location_other := false
-
- latlng, err := parseLatLng(r)
-
- err = duration.Scan(duration_str)
- if err != nil {
- log.Warn().Err(err).Str("duration_str", duration_str).Msg("Failed to interpret 'duration'")
- }
-
- //log.Debug().Strs("source_locations", source_locations).Msg("parsing")
- if slices.Contains(source_locations, "backyard") {
- is_location_backyard = true
- }
- if slices.Contains(source_locations, "frontyard") {
- is_location_frontyard = true
- }
- if slices.Contains(source_locations, "garden") {
- is_location_garden = true
- }
- if slices.Contains(source_locations, "other") {
- is_location_other = true
- }
- if slices.Contains(source_locations, "pool-area") {
- is_location_pool = true
- }
-
- uploads, err := extractImageUploads(r)
- log.Info().Int("len", len(uploads)).Msg("extracted uploads")
- if err != nil {
- respondError(w, "Failed to extract image uploads", err, http.StatusInternalServerError)
- return
- }
- address := platform.Address{
- Country: address_country,
- Locality: address_locality,
- Number: address_number,
- PostalCode: address_postal_code,
- Raw: address_raw,
- Region: address_region,
- Street: address_street,
- Unit: "",
- }
- setter_report := models.PublicreportReportSetter{
- //AddressID: omitnull.From(latlng.Cell.String()),
- AddressRaw: omit.From(address.Raw),
- AddressCountry: omit.From(address.Country),
- AddressNumber: omit.From(address.Number),
- AddressLocality: omit.From(address.Locality),
- AddressPostalCode: omit.From(address.PostalCode),
- AddressRegion: omit.From(address.Region),
- AddressStreet: omit.From(address.Street),
- Created: omit.From(time.Now()),
- //H3cell: omitnull.From(latlng.Cell.String()),
- LatlngAccuracyType: omit.From(latlng.AccuracyType),
- LatlngAccuracyValue: omit.From(float32(latlng.AccuracyValue)),
- //Location: omitnull.From(fmt.Sprintf("ST_GeometryFromText(Point(%s %s))", longitude, latitude)),
- Location: omitnull.FromPtr[string](nil),
- MapZoom: omit.From(latlng.MapZoom),
- //OrganizationID: omitnull.FromPtr(organization_id),
- //PublicID: omit.From(public_id),
- ReporterEmail: omit.From(""),
- ReporterName: omit.From(""),
- ReporterPhone: omit.From(""),
- ReportType: omit.From(enums.PublicreportReporttypeNuisance),
- Status: omit.From(enums.PublicreportReportstatustypeReported),
- }
- setter_nuisance := models.PublicreportNuisanceSetter{
- AdditionalInfo: omit.From(additional_info),
- Duration: omit.From(duration),
- IsLocationBackyard: omit.From(is_location_backyard),
- IsLocationFrontyard: omit.From(is_location_frontyard),
- IsLocationGarden: omit.From(is_location_garden),
- IsLocationOther: omit.From(is_location_other),
- IsLocationPool: omit.From(is_location_pool),
- //ReportID omit.Val[int32]
- SourceContainer: omit.From(source_container),
- SourceDescription: omit.From(source_description),
- SourceGutter: omit.From(source_gutters),
- SourceStagnant: omit.From(source_stagnant),
- TodDay: omit.From(tod_day),
- TodEarly: omit.From(tod_early),
- TodEvening: omit.From(tod_evening),
- TodNight: omit.From(tod_night),
- }
- report, err := platform.ReportNuisanceCreate(ctx, setter_report, setter_nuisance, latlng, address, uploads)
- http.Redirect(w, r, fmt.Sprintf("/submit-complete?report=%s", report.PublicID), http.StatusFound)
-}
diff --git a/rmo/report.go b/rmo/report.go
index 207b994e..ca9ad8db 100644
--- a/rmo/report.go
+++ b/rmo/report.go
@@ -2,9 +2,7 @@ package rmo
import (
"encoding/json"
- "fmt"
"net/http"
- "strconv"
"strings"
//"github.com/Gleipnir-Technology/nidus-sync/config"
@@ -12,8 +10,6 @@ import (
"github.com/Gleipnir-Technology/bob/dialect/psql"
"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
"github.com/Gleipnir-Technology/nidus-sync/db"
- "github.com/Gleipnir-Technology/nidus-sync/db/enums"
- "github.com/Gleipnir-Technology/nidus-sync/platform"
//"github.com/gorilla/mux"
"github.com/stephenafamo/scan"
//"github.com/rs/zerolog/log"
@@ -85,65 +81,6 @@ func getReportSuggestion(w http.ResponseWriter, r *http.Request) {
w.Write(jsonBody)
}
-func parseLatLng(r *http.Request) (platform.LatLng, error) {
- result := platform.LatLng{
- AccuracyType: enums.PublicreportAccuracytypeNone,
- AccuracyValue: 0.0,
- Latitude: nil,
- Longitude: nil,
- MapZoom: 0.0,
- }
- latitude_str := r.FormValue("latitude")
- longitude_str := r.FormValue("longitude")
- latlng_accuracy_type_str := r.PostFormValue("latlng-accuracy-type")
- latlng_accuracy_value_str := r.PostFormValue("latlng-accuracy-value")
- map_zoom_str := r.PostFormValue("map-zoom")
-
- var err error
- if latlng_accuracy_type_str != "" {
- err := result.AccuracyType.Scan(latlng_accuracy_type_str)
- if err != nil {
- return result, fmt.Errorf("Failed to parse accuracy type '%s': %w", latlng_accuracy_type_str, err)
- }
- }
- if latlng_accuracy_value_str != "" {
- var t float64
- t, err = strconv.ParseFloat(latlng_accuracy_value_str, 32)
- if err != nil {
- return result, fmt.Errorf("Failed to parse latlng_accuracy_value '%s': %w", latlng_accuracy_value_str, err)
- }
- result.AccuracyValue = float64(t)
- }
-
- if latitude_str != "" {
- var t float64
- t, err = strconv.ParseFloat(latitude_str, 64)
- if err != nil {
- return result, fmt.Errorf("Failed to parse latitude '%s': %w", latitude_str, err)
- }
- result.Latitude = &t
- }
- if longitude_str != "" {
- var t float64
- t, err := strconv.ParseFloat(longitude_str, 64)
- if err != nil {
- return result, fmt.Errorf("Failed to parse longitude '%s': %w", longitude_str, err)
- }
- result.Longitude = &t
- }
-
- if map_zoom_str != "" {
- var t float64
- t, err = strconv.ParseFloat(map_zoom_str, 32)
- if err != nil {
- return result, fmt.Errorf("Failed to parse map_zoom_str '%s': %w", map_zoom_str, err)
- } else {
- result.MapZoom = float32(t)
- }
- }
- return result, nil
-}
-
func partialSearchParam(p string) string {
result := strings.ReplaceAll(p, "-", "")
result = strings.ToUpper(result)
diff --git a/rmo/routes.go b/rmo/routes.go
index 5ed7d44d..170a98b1 100644
--- a/rmo/routes.go
+++ b/rmo/routes.go
@@ -8,11 +8,7 @@ import (
func Router(r *mux.Router) {
r.HandleFunc("/", getRoot).Methods("GET")
- r.HandleFunc("/nuisance", getNuisance).Methods("GET")
- r.HandleFunc("/nuisance", postNuisance).Methods("POST")
r.HandleFunc("/submit-complete", getSubmitComplete).Methods("GET")
- r.HandleFunc("/water", getWater).Methods("GET")
- r.HandleFunc("/water", postWater).Methods("POST")
r.HandleFunc("/district", getDistrictList).Methods("GET")
r.HandleFunc("/district/{slug}", getRootDistrict).Methods("GET")
diff --git a/rmo/water.go b/rmo/water.go
index bbed67cf..0a9bee57 100644
--- a/rmo/water.go
+++ b/rmo/water.go
@@ -1,15 +1,9 @@
package rmo
import (
- "fmt"
"net/http"
- "time"
- "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"
- "github.com/aarondl/opt/omit"
)
type ContentWater struct {
@@ -42,108 +36,3 @@ func getWaterDistrict(w http.ResponseWriter, r *http.Request) {
},
)
}
-func postWater(w http.ResponseWriter, r *http.Request) {
- err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
- if err != nil {
- respondError(w, "Failed to parse form", err, http.StatusBadRequest)
- return
- }
-
- access_comments := r.FormValue("access-comments")
- access_dog := boolFromForm(r, "access-dog")
- access_fence := boolFromForm(r, "access-fence")
- access_gate := boolFromForm(r, "access-gate")
- access_locked := boolFromForm(r, "access-locked")
- access_other := boolFromForm(r, "access-other")
- address_raw := r.FormValue("address")
- address_country := r.FormValue("address-country")
- address_locality := r.FormValue("address-locality")
- address_number := r.FormValue("address-number")
- address_postal_code := r.FormValue("address-postalcode")
- address_region := r.FormValue("address-region")
- address_street := r.FormValue("address-street")
- comments := r.FormValue("comments")
- has_adult := boolFromForm(r, "has-adult")
- has_backyard_permission := boolFromForm(r, "backyard-permission")
- has_larvae := boolFromForm(r, "has-larvae")
- has_pupae := boolFromForm(r, "has-pupae")
- is_reporter_confidential := boolFromForm(r, "reporter-confidential")
- is_reporter_owner := boolFromForm(r, "property-ownership")
- owner_email := r.FormValue("owner-email")
- owner_name := r.FormValue("owner-name")
- owner_phone := r.FormValue("owner-phone")
-
- latlng, err := parseLatLng(r)
- if err != nil {
- respondError(w, "Failed to parse lat lng for water report", err, http.StatusInternalServerError)
- return
- }
-
- ctx := r.Context()
-
- uploads, err := extractImageUploads(r)
- if err != nil {
- respondError(w, "Failed to extract image uploads", err, http.StatusInternalServerError)
- return
- }
-
- address := platform.Address{
- Country: address_country,
- Locality: address_locality,
- Number: address_number,
- PostalCode: address_postal_code,
- Raw: address_raw,
- Region: address_region,
- Street: address_street,
- Unit: "",
- }
- setter_report := models.PublicreportReportSetter{
- AddressRaw: omit.From(address_raw),
- AddressCountry: omit.From(address_country),
- AddressLocality: omit.From(address_locality),
- AddressNumber: omit.From(address_number),
- AddressPostalCode: omit.From(address_postal_code),
- AddressStreet: omit.From(address_street),
- AddressRegion: omit.From(address_region),
- Created: omit.From(time.Now()),
- //H3cell: omitnull.From(geospatial.Cell.String()),
- LatlngAccuracyType: omit.From(latlng.AccuracyType),
- LatlngAccuracyValue: omit.From(float32(latlng.AccuracyValue)),
- //Location: add later
- MapZoom: omit.From(latlng.MapZoom),
- //OrganizationID: omitnull.FromPtr(organization_id),
- //PublicID: omit.From(public_id),
- ReporterEmail: omit.From(""),
- ReporterName: omit.From(""),
- ReporterPhone: omit.From(""),
- ReportType: omit.From(enums.PublicreportReporttypeWater),
- Status: omit.From(enums.PublicreportReportstatustypeReported),
- }
- setter_water := models.PublicreportWaterSetter{
- AccessComments: omit.From(access_comments),
- AccessDog: omit.From(access_dog),
- AccessFence: omit.From(access_fence),
- AccessGate: omit.From(access_gate),
- AccessLocked: omit.From(access_locked),
- AccessOther: omit.From(access_other),
- Comments: omit.From(comments),
- HasAdult: omit.From(has_adult),
- HasBackyardPermission: omit.From(has_backyard_permission),
- HasLarvae: omit.From(has_larvae),
- HasPupae: omit.From(has_pupae),
- IsReporterConfidential: omit.From(is_reporter_confidential),
- IsReporterOwner: omit.From(is_reporter_owner),
- OwnerEmail: omit.From(owner_email),
- OwnerName: omit.From(owner_name),
- OwnerPhone: omit.From(owner_phone),
- //ReportID omit.Val[int32]
- }
- report, err := platform.ReportWaterCreate(ctx, setter_report, setter_water, latlng, address, uploads)
- if err != nil {
- respondError(w, "Failed to save new report", err, http.StatusInternalServerError)
- return
- }
- http.Redirect(w, r, fmt.Sprintf("/submit-complete?report=%s", report.PublicID), http.StatusFound)
-}
-func postWaterDistrict(w http.ResponseWriter, r *http.Request) {
-}
diff --git a/ts/components/MapLocator.vue b/ts/components/MapLocator.vue
index 85dcfc7e..9f3fc239 100644
--- a/ts/components/MapLocator.vue
+++ b/ts/components/MapLocator.vue
@@ -128,7 +128,7 @@ const frameMarkers = () => {
if (props.markers.length === 1) {
// Single marker: pan to it
- map.value.panTo(props.markers[0].location, { duration: 1000, zoom: 15 });
+ map.value.panTo(props.markers[0].location, { duration: 1000 });
} else {
// Multiple markers: fit bounds
const bounds = new maplibregl.LngLatBounds();
diff --git a/ts/rmo/content/Nuisance.vue b/ts/rmo/content/Nuisance.vue
index fc069bcd..189bf734 100644
--- a/ts/rmo/content/Nuisance.vue
+++ b/ts/rmo/content/Nuisance.vue
@@ -120,10 +120,9 @@ select.tall {
-
@@ -517,7 +530,10 @@ import type { Location, Marker } from "@/types";
import type { Address } from "@/type/stadia";
const currentLocation = ref(null);
+const errorMessage = ref("");
+const formElement = ref(null);
const images = ref([]);
+const isSubmitting = ref(false);
const marker = ref(null);
const showMore = ref(false);
@@ -562,4 +578,26 @@ function doMapMarkerDragEnd(location: Location) {
location: location,
};
}
+async function doSubmit() {
+ if (!formElement.value) return;
+
+ isSubmitting.value = true;
+ errorMessage.value = "";
+ try {
+ const formData = new FormData(formElement.value);
+ images.value.forEach((image, index) => {
+ formData.append(`image[${index}]`, image.file, image.name);
+ });
+ await fetch("/api/rmo/nuisance", {
+ method: "POST",
+ body: formData,
+ // Don't set Content-Type, the borwser should do it
+ });
+ } catch (error) {
+ errorMessage.value =
+ error instanceof Error ? error.message : "Upload failed";
+ } finally {
+ isSubmitting.value = false;
+ }
+}