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.
This commit is contained in:
parent
597aedc2af
commit
10e368c403
12 changed files with 507 additions and 461 deletions
272
api/handler.go
272
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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)
|
||||
}
|
||||
194
resource/nuisance.go
Normal file
194
resource/nuisance.go
Normal file
|
|
@ -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
|
||||
}
|
||||
125
resource/water.go
Normal file
125
resource/water.go
Normal file
|
|
@ -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
|
||||
}
|
||||
127
rmo/nuisance.go
127
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
111
rmo/water.go
111
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) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -120,10 +120,9 @@ select.tall {
|
|||
|
||||
<!-- Report Form -->
|
||||
<form
|
||||
id="mosquitoNuisanceForm"
|
||||
action="/api/rmo/nuisance"
|
||||
method="POST"
|
||||
@submit.prevent="doSubmit"
|
||||
enctype="multipart/form-data"
|
||||
ref="formElement"
|
||||
>
|
||||
<!-- Location Section -->
|
||||
<div class="form-section">
|
||||
|
|
@ -456,6 +455,13 @@ select.tall {
|
|||
rows="2"
|
||||
placeholder="Describe any other potential breeding sites you've noticed..."
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label for="image" class="form-label"
|
||||
>Please provide a photo or two of the breeding source</label
|
||||
>
|
||||
<ImageUpload v-model="images" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -497,7 +503,14 @@ select.tall {
|
|||
</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end mt-3 mt-md-0">
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
<div v-if="errorMessage" class="error-message">
|
||||
✗ {{ errorMessage }}
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary btn-lg"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Submit Report
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -517,7 +530,10 @@ import type { Location, Marker } from "@/types";
|
|||
import type { Address } from "@/type/stadia";
|
||||
|
||||
const currentLocation = ref<Location | null>(null);
|
||||
const errorMessage = ref("");
|
||||
const formElement = ref<HTMLFormElement | null>(null);
|
||||
const images = ref<Image[]>([]);
|
||||
const isSubmitting = ref(false);
|
||||
const marker = ref<Marker | null>(null);
|
||||
|
||||
const showMore = ref<boolean>(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;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue