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:
parent
9a5cc4cf97
commit
e8d865d0ab
24 changed files with 915 additions and 541 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
132
rmo/nuisance.go
132
rmo/nuisance.go
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 != "" {
|
||||
|
|
|
|||
101
rmo/water.go
101
rmo/water.go
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue