nidus-sync/rmo/nuisance.go

263 lines
9.1 KiB
Go

package rmo
import (
"fmt"
"net/http"
"slices"
"time"
"github.com/Gleipnir-Technology/bob"
"github.com/Gleipnir-Technology/bob/dialect/psql"
"github.com/Gleipnir-Technology/bob/dialect/psql/um"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
"github.com/Gleipnir-Technology/nidus-sync/platform/geocode"
"github.com/Gleipnir-Technology/nidus-sync/platform/report"
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
"github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull"
"github.com/rs/zerolog/log"
)
type ContentNuisance struct {
District *ContentDistrict
MapboxToken string
URL ContentURL
}
type ContentNuisanceSubmitComplete struct {
District *ContentDistrict
ReportID string
URL ContentURL
}
func getNuisance(w http.ResponseWriter, r *http.Request) {
html.RenderOrError(
w,
"rmo/nuisance.html",
ContentNuisance{
District: nil,
URL: makeContentURL(nil),
},
)
}
func getNuisanceDistrict(w http.ResponseWriter, r *http.Request) {
district, err := districtBySlug(r)
if err != nil {
respondError(w, "Failed to lookup organization", err, http.StatusBadRequest)
return
}
html.RenderOrError(
w,
"rmo/nuisance.html",
ContentNuisance{
District: newContentDistrict(district),
URL: makeContentURL(nil),
},
)
}
func getSubmitComplete(w http.ResponseWriter, r *http.Request) {
report_id := r.URL.Query().Get("report")
district, err := report.DistrictForReport(r.Context(), report_id)
if err != nil {
respondError(w, fmt.Sprintf("Failed to get district for report '%s'", report_id, err), err, http.StatusInternalServerError)
return
}
html.RenderOrError(
w,
"rmo/submit-complete.html",
ContentNuisanceSubmitComplete{
District: newContentDistrict(district),
ReportID: report_id,
URL: makeContentURL(nil),
},
)
}
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
}
//log.Debug().Bool("is_location_backyard", is_location_backyard).Bool("is_location_frontyard", is_location_frontyard).Bool("is_location_garden", is_location_garden).Bool("is_location_other", is_location_other).Bool("is_location_pool", is_location_pool).Msg("parsed")
public_id, err := report.GenerateReportID()
if err != nil {
respondError(w, "Failed to create report public ID", err, http.StatusInternalServerError)
return
}
geospatial, err := geospatialFromForm(r)
if err != nil {
respondError(w, "Failed to handle geospatial data", err, http.StatusInternalServerError)
return
}
txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil)
if err != nil {
respondError(w, "Failed to create transaction", err, http.StatusInternalServerError)
return
}
defer txn.Rollback(ctx)
// If we've got an address_country value it was set by geocoding so we should save it
var address *models.Address
if address_country != "" && latlng.Latitude != nil && latlng.Longitude != nil {
address, err = geocode.EnsureAddress(ctx, txn, types.Address{
Country: address_country,
Locality: address_locality,
Number: address_number,
PostalCode: address_postal_code,
Region: address_region,
Street: address_street,
Unit: "",
}, types.Location{
Latitude: *latlng.Latitude,
Longitude: *latlng.Longitude,
})
}
uploads, err := extractImageUploads(r)
log.Info().Int("len", len(uploads)).Msg("extracted uploads")
if err != nil {
respondError(w, "Failed to extract image uploads", err, http.StatusInternalServerError)
return
}
images, err := saveImageUploads(ctx, txn, uploads)
if err != nil {
respondError(w, "Failed to save image uploads", err, http.StatusInternalServerError)
return
}
var organization_id *int32
organization_id, err = matchDistrict(ctx, latlng.Longitude, latlng.Latitude, uploads)
if err != nil {
log.Warn().Err(err).Msg("Failed to match district")
}
setter := models.PublicreportNuisanceSetter{
AdditionalInfo: omit.From(additional_info),
//AddressID: omitnull.From(geospatial.Cell.String()),
AddressRaw: omit.From(address_raw),
AddressCountry: omit.From(address_country),
AddressNumber: omit.From(address_number),
AddressLocality: omit.From(address_locality),
AddressPostalCode: omit.From(address_postal_code),
AddressRegion: omit.From(address_region),
AddressStreet: omit.From(address_street),
Created: omit.From(time.Now()),
Duration: omit.From(duration),
//H3cell: omitnull.From(geospatial.Cell.String()),
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),
LatlngAccuracyType: omit.From(latlng.AccuracyType),
LatlngAccuracyValue: omit.From(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: omitnull.FromPtr[string](nil),
ReporterName: omitnull.FromPtr[string](nil),
ReporterPhone: omitnull.FromPtr[string](nil),
SourceContainer: omit.From(source_container),
SourceDescription: omit.From(source_description),
SourceGutter: omit.From(source_gutters),
SourceStagnant: omit.From(source_stagnant),
Status: omit.From(enums.PublicreportReportstatustypeReported),
TodEarly: omit.From(tod_early),
TodDay: omit.From(tod_day),
TodEvening: omit.From(tod_evening),
TodNight: omit.From(tod_night),
}
if address != nil {
setter.AddressID = omitnull.From(address.ID)
}
nuisance, err := models.PublicreportNuisances.Insert(&setter).One(ctx, txn)
if err != nil {
respondError(w, "Failed to create database record", err, http.StatusInternalServerError)
return
}
if geospatial.Populated {
_, err = psql.Update(
um.Table("publicreport.nuisance"),
um.SetCol("h3cell").ToArg(geospatial.Cell),
um.SetCol("location").To(geospatial.GeometryQuery),
um.Where(psql.Quote("id").EQ(psql.Arg(nuisance.ID))),
).Exec(ctx, txn)
if err != nil {
respondError(w, "Failed to insert publicreport.nuisance geospatial", err, http.StatusInternalServerError)
return
}
}
log.Info().Str("public_id", public_id).Int32("id", nuisance.ID).Msg("Created nuisance report")
if len(images) > 0 {
setters := make([]*models.PublicreportNuisanceImageSetter, 0)
for _, image := range images {
setters = append(setters, &models.PublicreportNuisanceImageSetter{
ImageID: omit.From(int32(image.ID)),
NuisanceID: omit.From(int32(nuisance.ID)),
})
}
_, err = models.PublicreportNuisanceImages.Insert(bob.ToMods(setters...)).Exec(ctx, txn)
if err != nil {
respondError(w, "Failed to save reference to images", err, http.StatusInternalServerError)
return
}
log.Info().Int("len", len(images)).Msg("saved uploads")
}
txn.Commit(ctx)
http.Redirect(w, r, fmt.Sprintf("/submit-complete?report=%s", public_id), http.StatusFound)
}