Wire up events for creating new public reports

This involved moving a lot of stuff to the platform layer since I don't
want event interfaces leaking out.

Also this includes a fix to the user authentication which I had
previously broken by making a platform-layer user object independent of
the database layer.
This commit is contained in:
Eli Ribble 2026-03-13 17:33:39 +00:00
parent 9a5cc4cf97
commit e8d865d0ab
No known key found for this signature in database
24 changed files with 915 additions and 541 deletions

View file

@ -1,8 +1,6 @@
package rmo
import (
"context"
"fmt"
"net/http"
"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
@ -10,9 +8,7 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/html"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
)
type ContentDistrict struct {
@ -57,42 +53,6 @@ func getDistrictList(w http.ResponseWriter, r *http.Request) {
)
}
func matchDistrict(ctx context.Context, longitude, latitude *float64, images []ImageUpload) (*int32, error) {
var err error
var org *models.Organization
for _, image := range images {
if image.Exif == nil {
continue
}
if image.Exif.GPS == nil {
continue
}
org, err = platform.DistrictForLocation(ctx, image.Exif.GPS.Longitude, image.Exif.GPS.Latitude)
if err != nil {
log.Warn().Err(err).Msg("Failed to get district for location")
continue
}
if org != nil {
return &org.ID, nil
}
}
if longitude == nil || latitude == nil {
log.Debug().Msg("No location from images, no latlng for the report itself, cannot match")
return nil, nil
}
org, err = platform.DistrictForLocation(ctx, *longitude, *latitude)
if err != nil {
log.Warn().Err(err).Msg("Failed to get district for location")
return nil, fmt.Errorf("Failed to get district for location: %w", err)
}
if org == nil {
log.Debug().Err(err).Float64("lng", *longitude).Float64("lat", *latitude).Msg("No district match by report location")
return nil, nil
}
log.Debug().Err(err).Int32("org_id", org.ID).Float64("lng", *longitude).Float64("lat", *latitude).Msg("Found district match by report location")
return &org.ID, nil
}
func newContentDistrict(d *models.Organization) *ContentDistrict {
if d == nil {
return nil

View file

@ -1,70 +0,0 @@
package rmo
import (
"fmt"
"net/http"
"strconv"
"github.com/Gleipnir-Technology/nidus-sync/h3utils"
"github.com/rs/zerolog/log"
"github.com/uber/h3-go/v4"
)
type GeospatialData struct {
Cell h3.Cell
GeometryQuery string
Populated bool
}
func geospatialFromForm(r *http.Request) (GeospatialData, error) {
lat := r.FormValue("latitude")
lng := r.FormValue("longitude")
accuracy_type := r.FormValue("latlng-accuracy-type")
accuracy_value := r.FormValue("latlng-accuracy-value")
if lat == "" || lng == "" {
return GeospatialData{Populated: false}, nil
}
latitude, err := strconv.ParseFloat(lat, 64)
if err != nil {
return GeospatialData{Populated: false}, fmt.Errorf("Failed to create parse latitude: %v", err)
}
longitude, err := strconv.ParseFloat(lng, 64)
if err != nil {
return GeospatialData{Populated: false}, fmt.Errorf("Failed to create parse longitude: %v", err)
}
var resolution int
switch accuracy_type {
// These accuracy_type strings come from the Mapbox Geocoding API definition and
// are far from scientific
case "rooftop":
resolution = 14
case "parcel":
resolution = 13
case "point":
resolution = 13
case "interpolated":
resolution = 12
case "approximate":
resolution = 11
case "intersection":
resolution = 10
// This is a special indicator that we got our location from the browser measurements
case "meters":
case "browser":
accuracy_in_meters, err := strconv.ParseFloat(accuracy_value, 64)
if err != nil {
return GeospatialData{Populated: false}, fmt.Errorf("Failed to parse '%s' as an accuracy in meters: %v", accuracy_value, err)
}
resolution = h3utils.MeterAccuracyToH3Resolution(accuracy_in_meters)
default:
log.Warn().Str("accuracy-type", accuracy_type).Msg("unrecognized accuracy type, this indicates either a weird client or misbehaving web page. Defaulting to resolution 13")
resolution = 13
}
cell, err := h3utils.GetCell(longitude, latitude, resolution)
return GeospatialData{
Cell: cell,
GeometryQuery: fmt.Sprintf("ST_Point(%f, %f, 4326)", longitude, latitude),
Populated: true,
}, nil
}

View file

@ -2,9 +2,11 @@ package rmo
import (
"bytes"
"context"
"errors"
"fmt"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/Gleipnir-Technology/nidus-sync/platform/file"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"image"
_ "image/gif" // register GIF format
_ "image/jpeg" // register JPEG format
@ -12,84 +14,9 @@ import (
"io"
"mime/multipart"
"net/http"
"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/models"
"github.com/Gleipnir-Technology/nidus-sync/platform/file"
"github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"github.com/rwcarlsen/goexif/exif"
"github.com/rwcarlsen/goexif/tiff"
//exif "github.com/rwcarlsen/goexif/exif"
//"github.com/dsoprea/go-exif-extra/format"
)
type GPS struct {
Latitude float64
Longitude float64
}
type ExifCollection struct {
GPS *GPS
Tags map[string]string
}
type ImageUpload struct {
Bounds image.Rectangle
ContentType string
Exif *ExifCollection
Format string
UploadFilesize int
UploadFilename string
UUID uuid.UUID
}
func (e *ExifCollection) Walk(name exif.FieldName, tag *tiff.Tag) error {
e.Tags[string(name)] = tag.String()
return nil
}
func extractExif(content_type string, file_bytes []byte) (result *ExifCollection, err error) {
/*
Using "github.com/evanoberholster/imagemeta"
meta, err := imagemeta.Decode(bytes.NewReader(file_bytes))
if err != nil {
return result, fmt.Errorf("Failed to decode image meta: %w", err)
}
result.GPS = &GPS{
Latitude: meta.GPS.Latitude(),
Longitude: meta.GPS.Longitude(),
}
return result, err
*/
e, err := exif.Decode(bytes.NewReader(file_bytes))
if err != nil {
if err.Error() == "exif: failed to find exif intro marker" {
return nil, nil
} else if errors.Is(err, io.EOF) {
return nil, nil
}
return nil, fmt.Errorf("Failed to decode image meta: %w", err)
}
lat, lng, _ := e.LatLong()
result = &ExifCollection{
GPS: &GPS{
Latitude: lat,
Longitude: lng,
},
Tags: make(map[string]string, 0),
}
err = e.Walk(result)
return result, err
}
func extractImageUpload(headers *multipart.FileHeader) (upload 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)
@ -99,7 +26,7 @@ func extractImageUpload(headers *multipart.FileHeader) (upload ImageUpload, err
file_bytes, err := io.ReadAll(f)
content_type := http.DetectContentType(file_bytes)
exif, err := extractExif(content_type, file_bytes)
exif, err := platform.ImageExtractExif(content_type, file_bytes)
if err != nil {
return upload, fmt.Errorf("Failed to extract EXIF data: %w", err)
}
@ -117,7 +44,7 @@ func extractImageUpload(headers *multipart.FileHeader) (upload ImageUpload, err
return upload, fmt.Errorf("Failed to write image file to disk: %w", err)
}
log.Info().Int("size", len(file_bytes)).Str("uploaded_filename", headers.Filename).Str("content-type", content_type).Str("uuid", u.String()).Msg("Saved an uploaded file to disk")
return ImageUpload{
return platform.ImageUpload{
Bounds: i.Bounds(),
ContentType: content_type,
Exif: exif,
@ -128,77 +55,16 @@ func extractImageUpload(headers *multipart.FileHeader) (upload ImageUpload, err
}, nil
}
func extractImageUploads(r *http.Request) (uploads []ImageUpload, err error) {
uploads = make([]ImageUpload, 0)
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)
if err != nil {
return make([]ImageUpload, 0), fmt.Errorf("Failed to extract photo upload: %w", err)
return make([]platform.ImageUpload, 0), fmt.Errorf("Failed to extract photo upload: %w", err)
}
uploads = append(uploads, upload)
}
}
return uploads, nil
}
func saveImageUploads(ctx context.Context, tx bob.Tx, uploads []ImageUpload) (models.PublicreportImageSlice, error) {
images := make(models.PublicreportImageSlice, 0)
for _, u := range uploads {
image, err := models.PublicreportImages.Insert(&models.PublicreportImageSetter{
ContentType: omit.From(u.ContentType),
Created: omit.From(time.Now()),
//Location: psql.Raw("NULL"),
Location: omitnull.FromPtr[string](nil),
ResolutionX: omit.From(int32(u.Bounds.Max.X)),
ResolutionY: omit.From(int32(u.Bounds.Max.Y)),
StorageUUID: omit.From(u.UUID),
StorageSize: omit.From(int64(u.UploadFilesize)),
UploadedFilename: omit.From(u.UploadFilename),
}).One(ctx, tx)
if err != nil {
return images, fmt.Errorf("Failed to create photo records: %w", err)
}
// TODO: figure out how to do this via the setter...?
if u.Exif != nil {
if u.Exif.GPS != nil {
_, err = psql.Update(
um.Table("publicreport.image"),
um.SetCol("location").To(fmt.Sprintf("ST_Point(%f, %f, 4326)", u.Exif.GPS.Longitude, u.Exif.GPS.Latitude)),
um.Where(psql.Quote("id").EQ(psql.Arg(image.ID))),
).Exec(ctx, tx)
}
exif_setters := make([]*models.PublicreportImageExifSetter, 0)
for k, v := range u.Exif.Tags {
to_save := trimQuotes(v)
exif_setters = append(exif_setters, &models.PublicreportImageExifSetter{
ImageID: omit.From(image.ID),
Name: omit.From(k),
Value: omit.From(to_save),
})
}
if len(exif_setters) > 0 {
_, err = models.PublicreportImageExifs.Insert(bob.ToMods(exif_setters...)).Exec(ctx, tx)
if err != nil {
return images, fmt.Errorf("Failed to create photo exif records: %w", err)
}
}
log.Info().Int32("id", image.ID).Int("tags", len(u.Exif.Tags)).Msg("Saved an uploaded file to the database")
} else {
log.Info().Int32("id", image.ID).Int("tags", 0).Msg("Saved an uploaded file without EXIF data")
}
images = append(images, image)
}
return images, nil
}
// Given a string like "\"foo\"" return "foo".
func trimQuotes(s string) string {
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
return s[1 : len(s)-1]
}
return s
}

View file

@ -6,16 +6,11 @@ import (
"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"
"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"
@ -130,46 +125,6 @@ func postNuisance(w http.ResponseWriter, r *http.Request) {
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,
})
if err != nil {
respondError(w, "Failed to ensure address: %w", err, http.StatusInternalServerError)
return
}
}
uploads, err := extractImageUploads(r)
log.Info().Int("len", len(uploads)).Msg("extracted uploads")
@ -177,42 +132,41 @@ func postNuisance(w http.ResponseWriter, r *http.Request) {
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
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: "",
}
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),
//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()),
Duration: omit.From(duration),
//H3cell: omitnull.From(geospatial.Cell.String()),
//H3cell: omitnull.From(latlng.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),
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),
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),
@ -226,42 +180,6 @@ func postNuisance(w http.ResponseWriter, r *http.Request) {
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)
public_id, err := platform.NuisanceCreate(ctx, setter, latlng, address, uploads)
http.Redirect(w, r, fmt.Sprintf("/submit-complete?report=%s", public_id), http.StatusFound)
}

View file

@ -11,6 +11,7 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
"github.com/Gleipnir-Technology/nidus-sync/db/sql"
"github.com/Gleipnir-Technology/nidus-sync/platform"
//"github.com/go-chi/chi/v5"
//"github.com/rs/zerolog/log"
)
@ -67,16 +68,8 @@ func getReportSuggestion(w http.ResponseWriter, r *http.Request) {
w.Write(jsonBody)
}
type LatLngForm struct {
Latitude *float64
Longitude *float64
MapZoom float32
AccuracyValue float32
AccuracyType enums.PublicreportAccuracytype
}
func parseLatLng(r *http.Request) (LatLngForm, error) {
result := LatLngForm{
func parseLatLng(r *http.Request) (platform.LatLng, error) {
result := platform.LatLng{
AccuracyType: enums.PublicreportAccuracytypeNone,
AccuracyValue: 0.0,
Latitude: nil,
@ -102,7 +95,7 @@ func parseLatLng(r *http.Request) (LatLngForm, error) {
if err != nil {
return result, fmt.Errorf("Failed to parse latlng_accuracy_value '%s': %w", latlng_accuracy_value_str, err)
}
result.AccuracyValue = float32(t)
result.AccuracyValue = float64(t)
}
if latitude_str != "" {

View file

@ -5,17 +5,11 @@ import (
"net/http"
"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/report"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull"
"github.com/rs/zerolog/log"
)
type ContentWater struct {
@ -65,7 +59,7 @@ func postWater(w http.ResponseWriter, r *http.Request) {
address_country := r.FormValue("address-country")
address_locality := r.FormValue("address-locality")
address_number := r.FormValue("address-number")
address_postalcode := r.FormValue("address-postalcode")
address_postal_code := r.FormValue("address-postalcode")
address_region := r.FormValue("address-region")
address_street := r.FormValue("address-street")
comments := r.FormValue("comments")
@ -85,42 +79,24 @@ func postWater(w http.ResponseWriter, r *http.Request) {
return
}
geospatial, err := geospatialFromForm(r)
if err != nil {
respondError(w, "Failed to handle geospatial data", err, http.StatusInternalServerError)
return
}
public_id, err := report.GenerateReportID()
if err != nil {
respondError(w, "Failed to create water report public ID", err, http.StatusInternalServerError)
return
}
ctx := r.Context()
tx, err := db.PGInstance.BobDB.BeginTx(ctx, nil)
if err != nil {
respondError(w, "Failed to create transaction", err, http.StatusInternalServerError)
return
}
defer tx.Rollback(ctx)
uploads, err := extractImageUploads(r)
if err != nil {
respondError(w, "Failed to extract image uploads", err, http.StatusInternalServerError)
return
}
images, err := saveImageUploads(r.Context(), tx, 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")
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 := models.PublicreportWaterSetter{
AccessComments: omit.From(access_comments),
AccessDog: omit.From(access_dog),
@ -132,7 +108,7 @@ func postWater(w http.ResponseWriter, r *http.Request) {
AddressCountry: omit.From(address_country),
AddressLocality: omit.From(address_locality),
AddressNumber: omit.From(address_number),
AddressPostalCode: omit.From(address_postalcode),
AddressPostalCode: omit.From(address_postal_code),
AddressStreet: omit.From(address_street),
AddressRegion: omit.From(address_region),
Comments: omit.From(comments),
@ -145,51 +121,22 @@ func postWater(w http.ResponseWriter, r *http.Request) {
IsReporterConfidential: omit.From(is_reporter_confidential),
IsReporterOwner: omit.From(is_reporter_owner),
//Location: add later
MapZoom: omit.From(latlng.MapZoom),
OrganizationID: omitnull.FromPtr(organization_id),
OwnerEmail: omit.From(owner_email),
OwnerName: omit.From(owner_name),
OwnerPhone: omit.From(owner_phone),
PublicID: omit.From(public_id),
ReporterEmail: omit.From(""),
ReporterName: omit.From(""),
ReporterPhone: omit.From(""),
Status: omit.From(enums.PublicreportReportstatustypeReported),
MapZoom: omit.From(latlng.MapZoom),
//OrganizationID: omitnull.FromPtr(organization_id),
OwnerEmail: omit.From(owner_email),
OwnerName: omit.From(owner_name),
OwnerPhone: omit.From(owner_phone),
//PublicID: omit.From(public_id),
ReporterEmail: omit.From(""),
ReporterName: omit.From(""),
ReporterPhone: omit.From(""),
Status: omit.From(enums.PublicreportReportstatustypeReported),
}
water, err := models.PublicreportWaters.Insert(&setter).One(ctx, tx)
public_id, err := platform.WaterCreate(ctx, setter, latlng, address, uploads)
if err != nil {
respondError(w, "Failed to create database record", err, http.StatusInternalServerError)
respondError(w, "Failed to save new report", err, http.StatusInternalServerError)
return
}
if geospatial.Populated {
_, err = psql.Update(
um.Table("publicreport.water"),
um.SetCol("h3cell").ToArg(geospatial.Cell),
um.SetCol("location").To(geospatial.GeometryQuery),
um.Where(psql.Quote("id").EQ(psql.Arg(water.ID))),
).Exec(ctx, tx)
if err != nil {
respondError(w, "Failed to update publicreport.water geospatial", err, http.StatusInternalServerError)
return
}
}
log.Info().Int32("id", water.ID).Str("public_id", water.PublicID).Msg("Created water report")
setters := make([]*models.PublicreportWaterImageSetter, 0)
for _, image := range images {
setters = append(setters, &models.PublicreportWaterImageSetter{
ImageID: omit.From(int32(image.ID)),
WaterID: omit.From(int32(water.ID)),
})
}
if len(setters) > 0 {
_, err = models.PublicreportWaterImages.Insert(bob.ToMods(setters...)).Exec(r.Context(), tx)
if err != nil {
respondError(w, "Failed to save upload relationships", err, http.StatusInternalServerError)
return
}
}
tx.Commit(ctx)
http.Redirect(w, r, fmt.Sprintf("/submit-complete?report=%s", public_id), http.StatusFound)
}
func postWaterDistrict(w http.ResponseWriter, r *http.Request) {