2026-01-29 23:55:41 +00:00
|
|
|
package rmo
|
2026-01-09 19:45:33 +00:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"net/http"
|
2026-01-31 22:14:46 +00:00
|
|
|
"strconv"
|
2026-01-09 19:45:33 +00:00
|
|
|
"time"
|
|
|
|
|
|
2026-01-31 22:14:46 +00:00
|
|
|
"github.com/Gleipnir-Technology/bob"
|
|
|
|
|
"github.com/Gleipnir-Technology/bob/dialect/psql"
|
|
|
|
|
"github.com/Gleipnir-Technology/bob/dialect/psql/um"
|
2026-01-30 20:41:02 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/config"
|
2026-01-09 19:45:33 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/db"
|
|
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
|
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
2026-01-31 22:14:46 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/h3utils"
|
2026-01-30 18:21:27 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/html"
|
2026-01-31 20:08:08 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/platform/report"
|
2026-01-09 19:45:33 +00:00
|
|
|
"github.com/aarondl/opt/omit"
|
2026-01-13 19:52:25 +00:00
|
|
|
"github.com/aarondl/opt/omitnull"
|
2026-01-09 19:45:33 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
|
)
|
|
|
|
|
|
2026-01-30 20:41:02 +00:00
|
|
|
type ContentNuisance struct {
|
|
|
|
|
District *ContentDistrict
|
|
|
|
|
MapboxToken string
|
|
|
|
|
URL ContentURL
|
|
|
|
|
}
|
|
|
|
|
type ContentNuisanceSubmitComplete struct {
|
2026-01-31 16:44:41 +00:00
|
|
|
District *ContentDistrict
|
2026-01-09 19:45:33 +00:00
|
|
|
ReportID string
|
2026-01-31 16:44:41 +00:00
|
|
|
URL ContentURL
|
2026-01-09 19:45:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
2026-01-31 16:44:41 +00:00
|
|
|
NuisanceT = buildTemplate("nuisance", "base")
|
|
|
|
|
SubmitCompleteT = buildTemplate("submit-complete", "base")
|
2026-01-09 19:45:33 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func getNuisance(w http.ResponseWriter, r *http.Request) {
|
2026-01-30 18:21:27 +00:00
|
|
|
html.RenderOrError(
|
2026-01-09 19:45:33 +00:00
|
|
|
w,
|
2026-01-31 16:44:41 +00:00
|
|
|
NuisanceT,
|
2026-01-30 20:41:02 +00:00
|
|
|
ContentNuisance{
|
|
|
|
|
District: nil,
|
|
|
|
|
MapboxToken: config.MapboxToken,
|
2026-02-01 03:07:46 +00:00
|
|
|
URL: makeContentURL(nil),
|
2026-01-30 20:41:02 +00:00
|
|
|
},
|
2026-01-09 19:45:33 +00:00
|
|
|
)
|
|
|
|
|
}
|
2026-02-01 03:14:36 +00:00
|
|
|
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,
|
|
|
|
|
NuisanceT,
|
|
|
|
|
ContentNuisance{
|
|
|
|
|
District: newContentDistrict(district),
|
|
|
|
|
MapboxToken: config.MapboxToken,
|
|
|
|
|
URL: makeContentURL(nil),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-01-31 16:44:41 +00:00
|
|
|
func getSubmitComplete(w http.ResponseWriter, r *http.Request) {
|
2026-02-01 02:37:35 +00:00
|
|
|
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
|
|
|
|
|
}
|
2026-01-30 18:21:27 +00:00
|
|
|
html.RenderOrError(
|
2026-01-09 19:45:33 +00:00
|
|
|
w,
|
2026-01-31 16:44:41 +00:00
|
|
|
SubmitCompleteT,
|
2026-01-30 20:41:02 +00:00
|
|
|
ContentNuisanceSubmitComplete{
|
2026-02-01 02:37:35 +00:00
|
|
|
District: newContentDistrict(district),
|
|
|
|
|
ReportID: report_id,
|
2026-02-01 03:07:46 +00:00
|
|
|
URL: makeContentURL(nil),
|
2026-01-09 19:45:33 +00:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
func postNuisance(w http.ResponseWriter, r *http.Request) {
|
2026-01-31 22:14:46 +00:00
|
|
|
ctx := r.Context()
|
|
|
|
|
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
|
2026-01-09 19:45:33 +00:00
|
|
|
if err != nil {
|
|
|
|
|
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-01-31 16:14:19 +00:00
|
|
|
address := r.PostFormValue("address")
|
2026-01-31 22:14:46 +00:00
|
|
|
lat := r.FormValue("latitude")
|
|
|
|
|
lng := r.FormValue("longitude")
|
2026-01-09 19:45:33 +00:00
|
|
|
source_stagnant := boolFromForm(r, "source-stagnant")
|
|
|
|
|
source_container := boolFromForm(r, "source-container")
|
2026-01-31 15:39:14 +00:00
|
|
|
source_gutters := boolFromForm(r, "source-gutters")
|
2026-01-09 19:45:33 +00:00
|
|
|
|
|
|
|
|
duration_str := postFormValueOrNone(r, "duration")
|
|
|
|
|
var duration enums.PublicreportNuisancedurationtype
|
|
|
|
|
err = duration.Scan(duration_str)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondError(w, fmt.Sprintf("Failed to interpret 'duration' of '%s'", duration_str), err, http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 19:52:25 +00:00
|
|
|
source_location_str := postFormValueOrNone(r, "source-location")
|
|
|
|
|
var source_location enums.PublicreportNuisancelocationtype
|
|
|
|
|
err = source_location.Scan(source_location_str)
|
2026-01-09 19:45:33 +00:00
|
|
|
if err != nil {
|
2026-01-13 19:52:25 +00:00
|
|
|
respondError(w, fmt.Sprintf("Failed to interpret 'source-location' of '%s'", source_location_str), err, http.StatusBadRequest)
|
2026-01-09 19:45:33 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
source_description := r.PostFormValue("source-description")
|
|
|
|
|
additional_info := r.PostFormValue("additional-info")
|
|
|
|
|
|
2026-01-31 22:14:46 +00:00
|
|
|
latitude, err := strconv.ParseFloat(lat, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondError(w, "Failed to create parse latitude", err, http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
longitude, err := strconv.ParseFloat(lng, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondError(w, "Failed to create parse longitude", err, http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-01-31 20:08:08 +00:00
|
|
|
public_id, err := report.GenerateReportID()
|
2026-01-09 19:45:33 +00:00
|
|
|
if err != nil {
|
|
|
|
|
respondError(w, "Failed to create quick report public ID", err, http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 22:14:46 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
organization_id, err := matchDistrict(ctx, longitude, latitude, uploads)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Warn().Err(err).Msg("Failed to match district")
|
|
|
|
|
}
|
|
|
|
|
c, err := h3utils.GetCell(longitude, latitude, 15)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondError(w, "Failedt o get h3 cell", err, http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 19:45:33 +00:00
|
|
|
setter := models.PublicreportNuisanceSetter{
|
2026-01-31 22:14:46 +00:00
|
|
|
AdditionalInfo: omit.From(additional_info),
|
|
|
|
|
Address: omit.From(address),
|
|
|
|
|
Created: omit.From(time.Now()),
|
|
|
|
|
Duration: omit.From(duration),
|
|
|
|
|
H3cell: omitnull.From(c.String()),
|
|
|
|
|
//Location: omitnull.From(fmt.Sprintf("ST_GeometryFromText(Point(%s %s))", longitude, latitude)),
|
2026-01-31 15:39:14 +00:00
|
|
|
Location: omitnull.FromPtr[string](nil),
|
2026-01-31 22:14:46 +00:00
|
|
|
OrganizationID: omitnull.FromPtr(organization_id),
|
2026-01-31 15:39:14 +00:00
|
|
|
PublicID: omit.From(public_id),
|
2026-01-31 22:14:46 +00:00
|
|
|
ReporterEmail: omitnull.FromPtr[string](nil),
|
|
|
|
|
ReporterName: omitnull.FromPtr[string](nil),
|
|
|
|
|
ReporterPhone: omitnull.FromPtr[string](nil),
|
2026-01-31 15:39:14 +00:00
|
|
|
SourceContainer: omit.From(source_container),
|
|
|
|
|
SourceDescription: omit.From(source_description),
|
|
|
|
|
SourceGutter: omit.From(source_gutters),
|
|
|
|
|
SourceLocation: omit.From(source_location),
|
|
|
|
|
SourceStagnant: omit.From(source_stagnant),
|
|
|
|
|
Status: omit.From(enums.PublicreportReportstatustypeReported),
|
2026-01-09 19:45:33 +00:00
|
|
|
}
|
2026-01-31 22:14:46 +00:00
|
|
|
nuisance, err := models.PublicreportNuisances.Insert(&setter).One(ctx, txn)
|
2026-01-09 19:45:33 +00:00
|
|
|
if err != nil {
|
|
|
|
|
respondError(w, "Failed to create database record", err, http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-01-31 22:14:46 +00:00
|
|
|
_, err = psql.Update(
|
|
|
|
|
um.Table("publicreport.nuisance"),
|
|
|
|
|
um.SetCol("location").To(fmt.Sprintf("ST_GeometryFromText('Point(%f %f)')", longitude, latitude)),
|
|
|
|
|
um.Where(psql.Quote("id").EQ(psql.Arg(nuisance.ID))),
|
|
|
|
|
).Exec(ctx, txn)
|
|
|
|
|
if err != nil {
|
|
|
|
|
respondError(w, "Failed to insert publicreport", err, http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-01-09 19:45:33 +00:00
|
|
|
log.Info().Str("public_id", public_id).Int32("id", nuisance.ID).Msg("Created nuisance report")
|
2026-01-31 22:14:46 +00:00
|
|
|
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)
|
2026-01-31 20:57:34 +00:00
|
|
|
http.Redirect(w, r, fmt.Sprintf("/submit-complete?report=%s", public_id), http.StatusFound)
|
2026-01-09 19:45:33 +00:00
|
|
|
}
|