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
7
platform/address.go
Normal file
7
platform/address.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
||||
)
|
||||
|
||||
type Address = types.Address
|
||||
|
|
@ -34,3 +34,38 @@ func DistrictForLocation(ctx context.Context, lng float64, lat float64) (*models
|
|||
return nil, errors.New("too many organizations")
|
||||
}
|
||||
}
|
||||
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 = 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 = 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
|
||||
}
|
||||
|
|
|
|||
27
platform/event.go
Normal file
27
platform/event.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/event"
|
||||
)
|
||||
|
||||
type Envelope = event.Envelope
|
||||
type Event = event.Event
|
||||
|
||||
const EventTypeHeartbeat = event.EventTypeHeartbeat
|
||||
|
||||
func SetEventChannel(chan_events chan<- Envelope) {
|
||||
event.SetEventChannel(chan_events)
|
||||
}
|
||||
func SudoEvent(org_id int32, content string) {
|
||||
go event.Send(event.Envelope{
|
||||
Event: Event{
|
||||
Resource: "sudo",
|
||||
Time: time.Now(),
|
||||
Type: event.EventTypeSudo,
|
||||
URI: content,
|
||||
},
|
||||
OrganizationID: org_id,
|
||||
})
|
||||
}
|
||||
68
platform/event/event.go
Normal file
68
platform/event/event.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"time"
|
||||
)
|
||||
|
||||
var chanEvents chan<- Envelope
|
||||
|
||||
type Event struct {
|
||||
Resource string `json:"resource"`
|
||||
Time time.Time `json:"time"`
|
||||
Type EventType `json:"type"`
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
type Envelope struct {
|
||||
OrganizationID int32
|
||||
Event Event
|
||||
}
|
||||
|
||||
func SetEventChannel(chan_events chan<- Envelope) {
|
||||
chanEvents = chan_events
|
||||
}
|
||||
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
EventTypeCreated EventType = iota
|
||||
EventTypeDeleted
|
||||
EventTypeModified
|
||||
EventTypeHeartbeat
|
||||
EventTypeSudo
|
||||
)
|
||||
|
||||
type ResourceType int
|
||||
|
||||
const (
|
||||
TypeRMONuisance = iota
|
||||
TypeRMOWater
|
||||
)
|
||||
|
||||
func Created(type_ ResourceType, organization_id int32, uri_id string) {
|
||||
var resource string
|
||||
var uri string
|
||||
switch type_ {
|
||||
case TypeRMONuisance:
|
||||
resource = "rmo:nuisance"
|
||||
uri = config.MakeURLReport("/report/%s", uri_id)
|
||||
case TypeRMOWater:
|
||||
resource = "rmo:water"
|
||||
uri = config.MakeURLReport("/report/%s", uri_id)
|
||||
default:
|
||||
|
||||
}
|
||||
go Send(Envelope{
|
||||
Event: Event{
|
||||
Resource: resource,
|
||||
Time: time.Now(),
|
||||
Type: EventTypeCreated,
|
||||
URI: uri,
|
||||
},
|
||||
OrganizationID: organization_id,
|
||||
})
|
||||
}
|
||||
func Send(env Envelope) {
|
||||
chanEvents <- env
|
||||
|
||||
}
|
||||
148
platform/image.go
Normal file
148
platform/image.go
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif" // register GIF format
|
||||
_ "image/jpeg" // register JPEG format
|
||||
_ "image/png" // register PNG format
|
||||
"io"
|
||||
"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/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 ImageExtractExif(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 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
|
||||
}
|
||||
57
platform/latlng.go
Normal file
57
platform/latlng.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/h3utils"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/uber/h3-go/v4"
|
||||
)
|
||||
|
||||
type LatLng struct {
|
||||
Latitude *float64
|
||||
Longitude *float64
|
||||
MapZoom float32
|
||||
AccuracyValue float64
|
||||
AccuracyType enums.PublicreportAccuracytype
|
||||
}
|
||||
|
||||
func (l LatLng) Resolution() uint {
|
||||
switch l.AccuracyType {
|
||||
// These accuracy_type strings come from the Mapbox Geocoding API definition and
|
||||
// are far from scientific
|
||||
case enums.PublicreportAccuracytypeRooftop:
|
||||
return 14
|
||||
case enums.PublicreportAccuracytypeParcel:
|
||||
return 13
|
||||
case enums.PublicreportAccuracytypePoint:
|
||||
return 13
|
||||
case enums.PublicreportAccuracytypeInterpolated:
|
||||
return 12
|
||||
case enums.PublicreportAccuracytypeApproximate:
|
||||
return 11
|
||||
case enums.PublicreportAccuracytypeIntersection:
|
||||
return 10
|
||||
// This is a special indicator that we got our location from the browser measurements
|
||||
case enums.PublicreportAccuracytypeBrowser:
|
||||
return uint(h3utils.MeterAccuracyToH3Resolution(l.AccuracyValue))
|
||||
default:
|
||||
log.Warn().Str("accuracy-type", string(l.AccuracyType)).Msg("unrecognized accuracy type, this indicates either a weird client or misbehaving web page. Defaulting to resolution 13")
|
||||
return 13
|
||||
}
|
||||
}
|
||||
func (l LatLng) H3Cell() (*h3.Cell, error) {
|
||||
if l.Longitude == nil || l.Latitude == nil {
|
||||
return nil, errors.New("nil lat/lng")
|
||||
}
|
||||
result, err := h3utils.GetCell(*l.Longitude, *l.Latitude, int(l.Resolution()))
|
||||
return &result, err
|
||||
}
|
||||
func (l LatLng) GeometryQuery() (string, error) {
|
||||
if l.Longitude == nil || l.Latitude == nil {
|
||||
return "", errors.New("nil lat/lng")
|
||||
}
|
||||
return fmt.Sprintf("ST_Point(%f, %f, 4326)", *l.Longitude, *l.Latitude), nil
|
||||
}
|
||||
104
platform/nuisance.go
Normal file
104
platform/nuisance.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/event"
|
||||
"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"
|
||||
)
|
||||
|
||||
func NuisanceCreate(ctx context.Context, setter models.PublicreportNuisanceSetter, latlng LatLng, address Address, images []ImageUpload) (public_id string, err error) {
|
||||
txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create txn: %w", err)
|
||||
}
|
||||
defer txn.Rollback(ctx)
|
||||
|
||||
public_id, err = report.GenerateReportID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create public ID: %w", err)
|
||||
}
|
||||
setter.PublicID = omit.From(public_id)
|
||||
|
||||
// If we've got an locality value it was set by geocoding so we should save it
|
||||
var a *models.Address
|
||||
if address.Locality != "" && latlng.Latitude != nil && latlng.Longitude != nil {
|
||||
a, err = geocode.EnsureAddress(ctx, txn, address, types.Location{
|
||||
Latitude: *latlng.Latitude,
|
||||
Longitude: *latlng.Longitude,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to ensure address: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
saved_images, err := saveImageUploads(ctx, txn, images)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to save image uploads: %w", err)
|
||||
}
|
||||
var organization_id *int32
|
||||
organization_id, err = MatchDistrict(ctx, latlng.Longitude, latlng.Latitude, images)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to match district")
|
||||
}
|
||||
|
||||
if a != nil {
|
||||
setter.AddressID = omitnull.From(a.ID)
|
||||
}
|
||||
if organization_id != nil {
|
||||
setter.OrganizationID = omitnull.FromPtr(organization_id)
|
||||
}
|
||||
nuisance, err := models.PublicreportNuisances.Insert(&setter).One(ctx, txn)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to create database record: %w", err)
|
||||
}
|
||||
if latlng.Latitude != nil && latlng.Longitude != nil {
|
||||
h3cell, _ := latlng.H3Cell()
|
||||
geom_query, _ := latlng.GeometryQuery()
|
||||
_, err = psql.Update(
|
||||
um.Table("publicreport.nuisance"),
|
||||
um.SetCol("h3cell").ToArg(h3cell),
|
||||
um.SetCol("location").To(geom_query),
|
||||
um.Where(psql.Quote("id").EQ(psql.Arg(nuisance.ID))),
|
||||
).Exec(ctx, txn)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to insert publicreport.nuisance geospatial", err)
|
||||
}
|
||||
}
|
||||
log.Info().Str("public_id", public_id).Int32("id", nuisance.ID).Msg("Created nuisance report")
|
||||
if len(saved_images) > 0 {
|
||||
setters := make([]*models.PublicreportNuisanceImageSetter, 0)
|
||||
for _, image := range saved_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 {
|
||||
return "", fmt.Errorf("Failed to save reference to images: %w", err)
|
||||
}
|
||||
log.Info().Int("len", len(images)).Msg("saved uploaded images")
|
||||
}
|
||||
txn.Commit(ctx)
|
||||
|
||||
if organization_id != nil {
|
||||
event.Created(
|
||||
event.TypeRMONuisance,
|
||||
*organization_id,
|
||||
nuisance.PublicID,
|
||||
)
|
||||
}
|
||||
return nuisance.PublicID, nil
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ type Address struct {
|
|||
Locality string `db:"locality" json:"locality"`
|
||||
Number string `db:"number" json:"number"`
|
||||
PostalCode string `db:"postal_code" json:"postal_code"`
|
||||
Raw string `db:"-" json:"raw"`
|
||||
Region string `db:"region" json:"region"`
|
||||
Street string `db:"street" json:"street"`
|
||||
Unit string `db:"unit" json:"unit"`
|
||||
|
|
|
|||
|
|
@ -35,7 +35,22 @@ type User struct {
|
|||
}
|
||||
|
||||
func (u User) HasRoot() bool {
|
||||
return u.model.Role != enums.UserroleRoot
|
||||
return u.model.Role == enums.UserroleRoot
|
||||
}
|
||||
func newUser(org Organization, user *models.User) User {
|
||||
return User{
|
||||
DisplayName: user.DisplayName,
|
||||
ID: int(user.ID),
|
||||
Initials: extractInitials(user.DisplayName),
|
||||
Notifications: []Notification{},
|
||||
Organization: org,
|
||||
PasswordHash: user.PasswordHash,
|
||||
PasswordHashType: string(user.PasswordHashType),
|
||||
Role: user.Role.String(),
|
||||
Username: user.Username,
|
||||
|
||||
model: user,
|
||||
}
|
||||
}
|
||||
|
||||
func CreateUser(ctx context.Context, username string, name string, password_hash string) (*User, error) {
|
||||
|
|
@ -60,19 +75,11 @@ func CreateUser(ctx context.Context, username string, name string, password_hash
|
|||
return nil, fmt.Errorf("Failed to create user: %w", err)
|
||||
}
|
||||
log.Info().Int32("id", user.ID).Str("username", user.Username).Msg("Created user")
|
||||
return &User{
|
||||
DisplayName: user.DisplayName,
|
||||
Initials: extractInitials(user.DisplayName),
|
||||
Notifications: []Notification{},
|
||||
Organization: newOrganization(o),
|
||||
Role: user.Role.String(),
|
||||
Username: user.Username,
|
||||
|
||||
model: user,
|
||||
}, nil
|
||||
u := newUser(newOrganization(o), user)
|
||||
return &u, nil
|
||||
}
|
||||
func UserByID(ctx context.Context, user_id int) (*User, error) {
|
||||
return getUser(ctx, models.SelectWhere.Users.ID.EQ(int32(user_id)))
|
||||
func UserByID(ctx context.Context, user_id int32) (*User, error) {
|
||||
return getUser(ctx, models.SelectWhere.Users.ID.EQ(user_id))
|
||||
}
|
||||
func UserByUsername(ctx context.Context, username string) (*User, error) {
|
||||
return getUser(ctx, models.SelectWhere.Users.Username.EQ(username))
|
||||
|
|
@ -84,15 +91,8 @@ func UsersByOrg(ctx context.Context, org Organization) (map[int32]*User, error)
|
|||
}
|
||||
results := make(map[int32]*User, len(users))
|
||||
for _, user := range users {
|
||||
results[user.ID] = &User{
|
||||
DisplayName: user.DisplayName,
|
||||
Initials: "",
|
||||
Notifications: []Notification{},
|
||||
Organization: org,
|
||||
Role: user.Role.String(),
|
||||
Username: user.Username,
|
||||
model: user,
|
||||
}
|
||||
u := newUser(org, user)
|
||||
results[user.ID] = &u
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
|
@ -102,6 +102,7 @@ func getUser(ctx context.Context, where mods.Where[*dialect.SelectQuery]) (*User
|
|||
where,
|
||||
).One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("getUser failed")
|
||||
if err.Error() == "No such user" || err.Error() == "sql: no rows in result set" {
|
||||
return nil, &NoUserError{}
|
||||
} else {
|
||||
|
|
@ -112,14 +113,8 @@ func getUser(ctx context.Context, where mods.Where[*dialect.SelectQuery]) (*User
|
|||
}
|
||||
org := newOrganization(user.R.Organization)
|
||||
|
||||
return &User{
|
||||
DisplayName: user.DisplayName,
|
||||
Initials: extractInitials(user.DisplayName),
|
||||
Notifications: []Notification{},
|
||||
Organization: org,
|
||||
Role: user.Role.String(),
|
||||
Username: user.Username,
|
||||
}, nil
|
||||
u := newUser(org, user)
|
||||
return &u, nil
|
||||
}
|
||||
func extractInitials(name string) string {
|
||||
parts := strings.Fields(name)
|
||||
|
|
|
|||
104
platform/water.go
Normal file
104
platform/water.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/event"
|
||||
"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"
|
||||
)
|
||||
|
||||
func WaterCreate(ctx context.Context, setter models.PublicreportWaterSetter, latlng LatLng, address Address, images []ImageUpload) (public_id string, err error) {
|
||||
txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to create transaction: %w", err)
|
||||
}
|
||||
defer txn.Rollback(ctx)
|
||||
public_id, err = report.GenerateReportID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to create water report public ID", err)
|
||||
}
|
||||
setter.PublicID = omit.From(public_id)
|
||||
|
||||
// If we've got an locality value it was set by geocoding so we should save it
|
||||
var a *models.Address
|
||||
if address.Locality != "" && latlng.Latitude != nil && latlng.Longitude != nil {
|
||||
a, err = geocode.EnsureAddress(ctx, txn, address, types.Location{
|
||||
Latitude: *latlng.Latitude,
|
||||
Longitude: *latlng.Longitude,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to ensure address: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
saved_images, err := saveImageUploads(ctx, txn, images)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to save image uploads", err)
|
||||
}
|
||||
|
||||
var organization_id *int32
|
||||
organization_id, err = MatchDistrict(ctx, latlng.Longitude, latlng.Latitude, images)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to match district")
|
||||
}
|
||||
if a != nil {
|
||||
setter.AddressID = omitnull.From(a.ID)
|
||||
}
|
||||
if organization_id != nil {
|
||||
setter.OrganizationID = omitnull.FromPtr(organization_id)
|
||||
}
|
||||
|
||||
water, err := models.PublicreportWaters.Insert(&setter).One(ctx, txn)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to create database record", err)
|
||||
}
|
||||
|
||||
if latlng.Latitude != nil && latlng.Longitude != nil {
|
||||
h3cell, _ := latlng.H3Cell()
|
||||
geom_query, _ := latlng.GeometryQuery()
|
||||
_, err = psql.Update(
|
||||
um.Table("publicreport.water"),
|
||||
um.SetCol("h3cell").ToArg(h3cell),
|
||||
um.SetCol("location").To(geom_query),
|
||||
um.Where(psql.Quote("id").EQ(psql.Arg(water.ID))),
|
||||
).Exec(ctx, txn)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to update publicreport.water geospatial", err)
|
||||
}
|
||||
}
|
||||
log.Info().Int32("id", water.ID).Str("public_id", water.PublicID).Msg("Created water report")
|
||||
setters := make([]*models.PublicreportWaterImageSetter, 0)
|
||||
for _, image := range saved_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(ctx, txn)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to save upload relationships", err)
|
||||
}
|
||||
}
|
||||
txn.Commit(ctx)
|
||||
|
||||
if organization_id != nil {
|
||||
event.Created(
|
||||
event.TypeRMOWater,
|
||||
*organization_id,
|
||||
water.PublicID,
|
||||
)
|
||||
}
|
||||
return water.PublicID, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue