Finish green pool report submission

Also start the pattern of breaking out pool pages together in their own
file. I think its easier to read this way.
This commit is contained in:
Eli Ribble 2026-01-09 19:43:19 +00:00
parent 9680fb6a68
commit 01ed2d6086
No known key found for this signature in database
31 changed files with 5925 additions and 375 deletions

View file

@ -0,0 +1,26 @@
// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT.
// This file is meant to be re-generated in place and/or deleted at any time.
package dberrors
var PublicreportPoolErrors = &publicreportPoolErrors{
ErrUniquePoolPkey: &UniqueConstraintError{
schema: "publicreport",
table: "pool",
columns: []string{"id"},
s: "pool_pkey",
},
ErrUniquePoolPublicIdKey: &UniqueConstraintError{
schema: "publicreport",
table: "pool",
columns: []string{"public_id"},
s: "pool_public_id_key",
},
}
type publicreportPoolErrors struct {
ErrUniquePoolPkey *UniqueConstraintError
ErrUniquePoolPublicIdKey *UniqueConstraintError
}

View file

@ -0,0 +1,17 @@
// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT.
// This file is meant to be re-generated in place and/or deleted at any time.
package dberrors
var PublicreportPoolPhotoErrors = &publicreportPoolPhotoErrors{
ErrUniquePoolPhotoPkey: &UniqueConstraintError{
schema: "publicreport",
table: "pool_photo",
columns: []string{"id"},
s: "pool_photo_pkey",
},
}
type publicreportPoolPhotoErrors struct {
ErrUniquePoolPhotoPkey *UniqueConstraintError
}

View file

@ -0,0 +1,402 @@
// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT.
// This file is meant to be re-generated in place and/or deleted at any time.
package dbinfo
import "github.com/aarondl/opt/null"
var PublicreportPools = Table[
publicreportPoolColumns,
publicreportPoolIndexes,
publicreportPoolForeignKeys,
publicreportPoolUniques,
publicreportPoolChecks,
]{
Schema: "publicreport",
Name: "pool",
Columns: publicreportPoolColumns{
ID: column{
Name: "id",
DBType: "integer",
Default: "nextval('publicreport.pool_id_seq'::regclass)",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
AccessComments: column{
Name: "access_comments",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
AccessGate: column{
Name: "access_gate",
DBType: "boolean",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
AccessFence: column{
Name: "access_fence",
DBType: "boolean",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
AccessLocked: column{
Name: "access_locked",
DBType: "boolean",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
AccessDog: column{
Name: "access_dog",
DBType: "boolean",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
AccessOther: column{
Name: "access_other",
DBType: "boolean",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
Address: column{
Name: "address",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
AddressCountry: column{
Name: "address_country",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
AddressPostCode: column{
Name: "address_post_code",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
AddressPlace: column{
Name: "address_place",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
AddressStreet: column{
Name: "address_street",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
AddressRegion: column{
Name: "address_region",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
Comments: column{
Name: "comments",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
Created: column{
Name: "created",
DBType: "timestamp without time zone",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
H3cell: column{
Name: "h3cell",
DBType: "h3index",
Default: "NULL",
Comment: "",
Nullable: true,
Generated: false,
AutoIncr: false,
},
HasAdult: column{
Name: "has_adult",
DBType: "boolean",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
HasLarvae: column{
Name: "has_larvae",
DBType: "boolean",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
HasPupae: column{
Name: "has_pupae",
DBType: "boolean",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
Location: column{
Name: "location",
DBType: "geography",
Default: "NULL",
Comment: "",
Nullable: true,
Generated: false,
AutoIncr: false,
},
MapZoom: column{
Name: "map_zoom",
DBType: "double precision",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
OwnerEmail: column{
Name: "owner_email",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
OwnerName: column{
Name: "owner_name",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
OwnerPhone: column{
Name: "owner_phone",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
PublicID: column{
Name: "public_id",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
ReporterEmail: column{
Name: "reporter_email",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
ReporterName: column{
Name: "reporter_name",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
ReporterPhone: column{
Name: "reporter_phone",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
Subscribe: column{
Name: "subscribe",
DBType: "boolean",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
},
Indexes: publicreportPoolIndexes{
PoolPkey: index{
Type: "btree",
Name: "pool_pkey",
Columns: []indexColumn{
{
Name: "id",
Desc: null.FromCond(false, true),
IsExpression: false,
},
},
Unique: true,
Comment: "",
NullsFirst: []bool{false},
NullsDistinct: false,
Where: "",
Include: []string{},
},
PoolPublicIDKey: index{
Type: "btree",
Name: "pool_public_id_key",
Columns: []indexColumn{
{
Name: "public_id",
Desc: null.FromCond(false, true),
IsExpression: false,
},
},
Unique: true,
Comment: "",
NullsFirst: []bool{false},
NullsDistinct: false,
Where: "",
Include: []string{},
},
},
PrimaryKey: &constraint{
Name: "pool_pkey",
Columns: []string{"id"},
Comment: "",
},
Uniques: publicreportPoolUniques{
PoolPublicIDKey: constraint{
Name: "pool_public_id_key",
Columns: []string{"public_id"},
Comment: "",
},
},
Comment: "",
}
type publicreportPoolColumns struct {
ID column
AccessComments column
AccessGate column
AccessFence column
AccessLocked column
AccessDog column
AccessOther column
Address column
AddressCountry column
AddressPostCode column
AddressPlace column
AddressStreet column
AddressRegion column
Comments column
Created column
H3cell column
HasAdult column
HasLarvae column
HasPupae column
Location column
MapZoom column
OwnerEmail column
OwnerName column
OwnerPhone column
PublicID column
ReporterEmail column
ReporterName column
ReporterPhone column
Subscribe column
}
func (c publicreportPoolColumns) AsSlice() []column {
return []column{
c.ID, c.AccessComments, c.AccessGate, c.AccessFence, c.AccessLocked, c.AccessDog, c.AccessOther, c.Address, c.AddressCountry, c.AddressPostCode, c.AddressPlace, c.AddressStreet, c.AddressRegion, c.Comments, c.Created, c.H3cell, c.HasAdult, c.HasLarvae, c.HasPupae, c.Location, c.MapZoom, c.OwnerEmail, c.OwnerName, c.OwnerPhone, c.PublicID, c.ReporterEmail, c.ReporterName, c.ReporterPhone, c.Subscribe,
}
}
type publicreportPoolIndexes struct {
PoolPkey index
PoolPublicIDKey index
}
func (i publicreportPoolIndexes) AsSlice() []index {
return []index{
i.PoolPkey, i.PoolPublicIDKey,
}
}
type publicreportPoolForeignKeys struct{}
func (f publicreportPoolForeignKeys) AsSlice() []foreignKey {
return []foreignKey{}
}
type publicreportPoolUniques struct {
PoolPublicIDKey constraint
}
func (u publicreportPoolUniques) AsSlice() []constraint {
return []constraint{
u.PoolPublicIDKey,
}
}
type publicreportPoolChecks struct{}
func (c publicreportPoolChecks) AsSlice() []check {
return []check{}
}

View file

@ -0,0 +1,147 @@
// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT.
// This file is meant to be re-generated in place and/or deleted at any time.
package dbinfo
import "github.com/aarondl/opt/null"
var PublicreportPoolPhotos = Table[
publicreportPoolPhotoColumns,
publicreportPoolPhotoIndexes,
publicreportPoolPhotoForeignKeys,
publicreportPoolPhotoUniques,
publicreportPoolPhotoChecks,
]{
Schema: "publicreport",
Name: "pool_photo",
Columns: publicreportPoolPhotoColumns{
ID: column{
Name: "id",
DBType: "integer",
Default: "nextval('publicreport.pool_photo_id_seq'::regclass)",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
Size: column{
Name: "size",
DBType: "bigint",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
Filename: column{
Name: "filename",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
PoolID: column{
Name: "pool_id",
DBType: "integer",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
UUID: column{
Name: "uuid",
DBType: "uuid",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
},
Indexes: publicreportPoolPhotoIndexes{
PoolPhotoPkey: index{
Type: "btree",
Name: "pool_photo_pkey",
Columns: []indexColumn{
{
Name: "id",
Desc: null.FromCond(false, true),
IsExpression: false,
},
},
Unique: true,
Comment: "",
NullsFirst: []bool{false},
NullsDistinct: false,
Where: "",
Include: []string{},
},
},
PrimaryKey: &constraint{
Name: "pool_photo_pkey",
Columns: []string{"id"},
Comment: "",
},
ForeignKeys: publicreportPoolPhotoForeignKeys{
PublicreportPoolPhotoPoolPhotoPoolIDFkey: foreignKey{
constraint: constraint{
Name: "publicreport.pool_photo.pool_photo_pool_id_fkey",
Columns: []string{"pool_id"},
Comment: "",
},
ForeignTable: "publicreport.pool",
ForeignColumns: []string{"id"},
},
},
Comment: "",
}
type publicreportPoolPhotoColumns struct {
ID column
Size column
Filename column
PoolID column
UUID column
}
func (c publicreportPoolPhotoColumns) AsSlice() []column {
return []column{
c.ID, c.Size, c.Filename, c.PoolID, c.UUID,
}
}
type publicreportPoolPhotoIndexes struct {
PoolPhotoPkey index
}
func (i publicreportPoolPhotoIndexes) AsSlice() []index {
return []index{
i.PoolPhotoPkey,
}
}
type publicreportPoolPhotoForeignKeys struct {
PublicreportPoolPhotoPoolPhotoPoolIDFkey foreignKey
}
func (f publicreportPoolPhotoForeignKeys) AsSlice() []foreignKey {
return []foreignKey{
f.PublicreportPoolPhotoPoolPhotoPoolIDFkey,
}
}
type publicreportPoolPhotoUniques struct{}
func (u publicreportPoolPhotoUniques) AsSlice() []constraint {
return []constraint{}
}
type publicreportPoolPhotoChecks struct{}
func (c publicreportPoolPhotoChecks) AsSlice() []check {
return []check{}
}

View file

@ -890,3 +890,88 @@ func (e *PublicreportNuisancepreferredtimetype) Scan(value any) error {
return nil
}
// Enum values for PublicreportPoolsourceduration
const (
PublicreportPoolsourcedurationNone PublicreportPoolsourceduration = "none"
PublicreportPoolsourcedurationLessThanWeek PublicreportPoolsourceduration = "less-than-week"
PublicreportPoolsourceduration12Weeks PublicreportPoolsourceduration = "1-2-weeks"
PublicreportPoolsourceduration24Weeks PublicreportPoolsourceduration = "2-4-weeks"
PublicreportPoolsourceduration13Months PublicreportPoolsourceduration = "1-3-months"
PublicreportPoolsourcedurationMoreThan3Months PublicreportPoolsourceduration = "more-than-3-months"
)
func AllPublicreportPoolsourceduration() []PublicreportPoolsourceduration {
return []PublicreportPoolsourceduration{
PublicreportPoolsourcedurationNone,
PublicreportPoolsourcedurationLessThanWeek,
PublicreportPoolsourceduration12Weeks,
PublicreportPoolsourceduration24Weeks,
PublicreportPoolsourceduration13Months,
PublicreportPoolsourcedurationMoreThan3Months,
}
}
type PublicreportPoolsourceduration string
func (e PublicreportPoolsourceduration) String() string {
return string(e)
}
func (e PublicreportPoolsourceduration) Valid() bool {
switch e {
case PublicreportPoolsourcedurationNone,
PublicreportPoolsourcedurationLessThanWeek,
PublicreportPoolsourceduration12Weeks,
PublicreportPoolsourceduration24Weeks,
PublicreportPoolsourceduration13Months,
PublicreportPoolsourcedurationMoreThan3Months:
return true
default:
return false
}
}
// useful when testing in other packages
func (e PublicreportPoolsourceduration) All() []PublicreportPoolsourceduration {
return AllPublicreportPoolsourceduration()
}
func (e PublicreportPoolsourceduration) MarshalText() ([]byte, error) {
return []byte(e), nil
}
func (e *PublicreportPoolsourceduration) UnmarshalText(text []byte) error {
return e.Scan(text)
}
func (e PublicreportPoolsourceduration) MarshalBinary() ([]byte, error) {
return []byte(e), nil
}
func (e *PublicreportPoolsourceduration) UnmarshalBinary(data []byte) error {
return e.Scan(data)
}
func (e PublicreportPoolsourceduration) Value() (driver.Value, error) {
return string(e), nil
}
func (e *PublicreportPoolsourceduration) Scan(value any) error {
switch x := value.(type) {
case string:
*e = PublicreportPoolsourceduration(x)
case []byte:
*e = PublicreportPoolsourceduration(x)
case nil:
return fmt.Errorf("cannot nil into PublicreportPoolsourceduration")
default:
return fmt.Errorf("cannot scan type %T: %v", value, value)
}
if !e.Valid() {
return fmt.Errorf("invalid PublicreportPoolsourceduration value: %s", *e)
}
return nil
}

View file

@ -211,6 +211,14 @@ var (
// Relationship Contexts for publicreport.nuisance
publicreportNuisanceWithParentsCascadingCtx = newContextual[bool]("publicreportNuisanceWithParentsCascading")
// Relationship Contexts for publicreport.pool
publicreportPoolWithParentsCascadingCtx = newContextual[bool]("publicreportPoolWithParentsCascading")
publicreportPoolRelPoolPhotosCtx = newContextual[bool]("publicreport.pool.publicreport.pool_photo.publicreport.pool_photo.pool_photo_pool_id_fkey")
// Relationship Contexts for publicreport.pool_photo
publicreportPoolPhotoWithParentsCascadingCtx = newContextual[bool]("publicreportPoolPhotoWithParentsCascading")
publicreportPoolPhotoRelPoolCtx = newContextual[bool]("publicreport.pool.publicreport.pool_photo.publicreport.pool_photo.pool_photo_pool_id_fkey")
// Relationship Contexts for publicreport.quick
publicreportQuickWithParentsCascadingCtx = newContextual[bool]("publicreportQuickWithParentsCascading")
publicreportQuickRelQuickPhotosCtx = newContextual[bool]("publicreport.quick.publicreport.quick_photo.publicreport.quick_photo.quick_photo_quick_id_fkey")

View file

@ -59,6 +59,8 @@ type Factory struct {
baseOauthTokenMods OauthTokenModSlice
baseOrganizationMods OrganizationModSlice
basePublicreportNuisanceMods PublicreportNuisanceModSlice
basePublicreportPoolMods PublicreportPoolModSlice
basePublicreportPoolPhotoMods PublicreportPoolPhotoModSlice
basePublicreportQuickMods PublicreportQuickModSlice
basePublicreportQuickPhotoMods PublicreportQuickPhotoModSlice
baseRasterColumnMods RasterColumnModSlice
@ -2420,6 +2422,96 @@ func (f *Factory) FromExistingPublicreportNuisance(m *models.PublicreportNuisanc
return o
}
func (f *Factory) NewPublicreportPool(mods ...PublicreportPoolMod) *PublicreportPoolTemplate {
return f.NewPublicreportPoolWithContext(context.Background(), mods...)
}
func (f *Factory) NewPublicreportPoolWithContext(ctx context.Context, mods ...PublicreportPoolMod) *PublicreportPoolTemplate {
o := &PublicreportPoolTemplate{f: f}
if f != nil {
f.basePublicreportPoolMods.Apply(ctx, o)
}
PublicreportPoolModSlice(mods).Apply(ctx, o)
return o
}
func (f *Factory) FromExistingPublicreportPool(m *models.PublicreportPool) *PublicreportPoolTemplate {
o := &PublicreportPoolTemplate{f: f, alreadyPersisted: true}
o.ID = func() int32 { return m.ID }
o.AccessComments = func() string { return m.AccessComments }
o.AccessGate = func() bool { return m.AccessGate }
o.AccessFence = func() bool { return m.AccessFence }
o.AccessLocked = func() bool { return m.AccessLocked }
o.AccessDog = func() bool { return m.AccessDog }
o.AccessOther = func() bool { return m.AccessOther }
o.Address = func() string { return m.Address }
o.AddressCountry = func() string { return m.AddressCountry }
o.AddressPostCode = func() string { return m.AddressPostCode }
o.AddressPlace = func() string { return m.AddressPlace }
o.AddressStreet = func() string { return m.AddressStreet }
o.AddressRegion = func() string { return m.AddressRegion }
o.Comments = func() string { return m.Comments }
o.Created = func() time.Time { return m.Created }
o.H3cell = func() null.Val[string] { return m.H3cell }
o.HasAdult = func() bool { return m.HasAdult }
o.HasLarvae = func() bool { return m.HasLarvae }
o.HasPupae = func() bool { return m.HasPupae }
o.Location = func() null.Val[string] { return m.Location }
o.MapZoom = func() float64 { return m.MapZoom }
o.OwnerEmail = func() string { return m.OwnerEmail }
o.OwnerName = func() string { return m.OwnerName }
o.OwnerPhone = func() string { return m.OwnerPhone }
o.PublicID = func() string { return m.PublicID }
o.ReporterEmail = func() string { return m.ReporterEmail }
o.ReporterName = func() string { return m.ReporterName }
o.ReporterPhone = func() string { return m.ReporterPhone }
o.Subscribe = func() bool { return m.Subscribe }
ctx := context.Background()
if len(m.R.PoolPhotos) > 0 {
PublicreportPoolMods.AddExistingPoolPhotos(m.R.PoolPhotos...).Apply(ctx, o)
}
return o
}
func (f *Factory) NewPublicreportPoolPhoto(mods ...PublicreportPoolPhotoMod) *PublicreportPoolPhotoTemplate {
return f.NewPublicreportPoolPhotoWithContext(context.Background(), mods...)
}
func (f *Factory) NewPublicreportPoolPhotoWithContext(ctx context.Context, mods ...PublicreportPoolPhotoMod) *PublicreportPoolPhotoTemplate {
o := &PublicreportPoolPhotoTemplate{f: f}
if f != nil {
f.basePublicreportPoolPhotoMods.Apply(ctx, o)
}
PublicreportPoolPhotoModSlice(mods).Apply(ctx, o)
return o
}
func (f *Factory) FromExistingPublicreportPoolPhoto(m *models.PublicreportPoolPhoto) *PublicreportPoolPhotoTemplate {
o := &PublicreportPoolPhotoTemplate{f: f, alreadyPersisted: true}
o.ID = func() int32 { return m.ID }
o.Size = func() int64 { return m.Size }
o.Filename = func() string { return m.Filename }
o.PoolID = func() int32 { return m.PoolID }
o.UUID = func() uuid.UUID { return m.UUID }
ctx := context.Background()
if m.R.Pool != nil {
PublicreportPoolPhotoMods.WithExistingPool(m.R.Pool).Apply(ctx, o)
}
return o
}
func (f *Factory) NewPublicreportQuick(mods ...PublicreportQuickMod) *PublicreportQuickTemplate {
return f.NewPublicreportQuickWithContext(context.Background(), mods...)
}
@ -3009,6 +3101,22 @@ func (f *Factory) AddBasePublicreportNuisanceMod(mods ...PublicreportNuisanceMod
f.basePublicreportNuisanceMods = append(f.basePublicreportNuisanceMods, mods...)
}
func (f *Factory) ClearBasePublicreportPoolMods() {
f.basePublicreportPoolMods = nil
}
func (f *Factory) AddBasePublicreportPoolMod(mods ...PublicreportPoolMod) {
f.basePublicreportPoolMods = append(f.basePublicreportPoolMods, mods...)
}
func (f *Factory) ClearBasePublicreportPoolPhotoMods() {
f.basePublicreportPoolPhotoMods = nil
}
func (f *Factory) AddBasePublicreportPoolPhotoMod(mods ...PublicreportPoolPhotoMod) {
f.basePublicreportPoolPhotoMods = append(f.basePublicreportPoolPhotoMods, mods...)
}
func (f *Factory) ClearBasePublicreportQuickMods() {
f.basePublicreportQuickMods = nil
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,498 @@
// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT.
// This file is meant to be re-generated in place and/or deleted at any time.
package factory
import (
"context"
"testing"
models "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/aarondl/opt/omit"
"github.com/google/uuid"
"github.com/jaswdr/faker/v2"
"github.com/stephenafamo/bob"
)
type PublicreportPoolPhotoMod interface {
Apply(context.Context, *PublicreportPoolPhotoTemplate)
}
type PublicreportPoolPhotoModFunc func(context.Context, *PublicreportPoolPhotoTemplate)
func (f PublicreportPoolPhotoModFunc) Apply(ctx context.Context, n *PublicreportPoolPhotoTemplate) {
f(ctx, n)
}
type PublicreportPoolPhotoModSlice []PublicreportPoolPhotoMod
func (mods PublicreportPoolPhotoModSlice) Apply(ctx context.Context, n *PublicreportPoolPhotoTemplate) {
for _, f := range mods {
f.Apply(ctx, n)
}
}
// PublicreportPoolPhotoTemplate is an object representing the database table.
// all columns are optional and should be set by mods
type PublicreportPoolPhotoTemplate struct {
ID func() int32
Size func() int64
Filename func() string
PoolID func() int32
UUID func() uuid.UUID
r publicreportPoolPhotoR
f *Factory
alreadyPersisted bool
}
type publicreportPoolPhotoR struct {
Pool *publicreportPoolPhotoRPoolR
}
type publicreportPoolPhotoRPoolR struct {
o *PublicreportPoolTemplate
}
// Apply mods to the PublicreportPoolPhotoTemplate
func (o *PublicreportPoolPhotoTemplate) Apply(ctx context.Context, mods ...PublicreportPoolPhotoMod) {
for _, mod := range mods {
mod.Apply(ctx, o)
}
}
// setModelRels creates and sets the relationships on *models.PublicreportPoolPhoto
// according to the relationships in the template. Nothing is inserted into the db
func (t PublicreportPoolPhotoTemplate) setModelRels(o *models.PublicreportPoolPhoto) {
if t.r.Pool != nil {
rel := t.r.Pool.o.Build()
rel.R.PoolPhotos = append(rel.R.PoolPhotos, o)
o.PoolID = rel.ID // h2
o.R.Pool = rel
}
}
// BuildSetter returns an *models.PublicreportPoolPhotoSetter
// this does nothing with the relationship templates
func (o PublicreportPoolPhotoTemplate) BuildSetter() *models.PublicreportPoolPhotoSetter {
m := &models.PublicreportPoolPhotoSetter{}
if o.ID != nil {
val := o.ID()
m.ID = omit.From(val)
}
if o.Size != nil {
val := o.Size()
m.Size = omit.From(val)
}
if o.Filename != nil {
val := o.Filename()
m.Filename = omit.From(val)
}
if o.PoolID != nil {
val := o.PoolID()
m.PoolID = omit.From(val)
}
if o.UUID != nil {
val := o.UUID()
m.UUID = omit.From(val)
}
return m
}
// BuildManySetter returns an []*models.PublicreportPoolPhotoSetter
// this does nothing with the relationship templates
func (o PublicreportPoolPhotoTemplate) BuildManySetter(number int) []*models.PublicreportPoolPhotoSetter {
m := make([]*models.PublicreportPoolPhotoSetter, number)
for i := range m {
m[i] = o.BuildSetter()
}
return m
}
// Build returns an *models.PublicreportPoolPhoto
// Related objects are also created and placed in the .R field
// NOTE: Objects are not inserted into the database. Use PublicreportPoolPhotoTemplate.Create
func (o PublicreportPoolPhotoTemplate) Build() *models.PublicreportPoolPhoto {
m := &models.PublicreportPoolPhoto{}
if o.ID != nil {
m.ID = o.ID()
}
if o.Size != nil {
m.Size = o.Size()
}
if o.Filename != nil {
m.Filename = o.Filename()
}
if o.PoolID != nil {
m.PoolID = o.PoolID()
}
if o.UUID != nil {
m.UUID = o.UUID()
}
o.setModelRels(m)
return m
}
// BuildMany returns an models.PublicreportPoolPhotoSlice
// Related objects are also created and placed in the .R field
// NOTE: Objects are not inserted into the database. Use PublicreportPoolPhotoTemplate.CreateMany
func (o PublicreportPoolPhotoTemplate) BuildMany(number int) models.PublicreportPoolPhotoSlice {
m := make(models.PublicreportPoolPhotoSlice, number)
for i := range m {
m[i] = o.Build()
}
return m
}
func ensureCreatablePublicreportPoolPhoto(m *models.PublicreportPoolPhotoSetter) {
if !(m.Size.IsValue()) {
val := random_int64(nil)
m.Size = omit.From(val)
}
if !(m.Filename.IsValue()) {
val := random_string(nil)
m.Filename = omit.From(val)
}
if !(m.PoolID.IsValue()) {
val := random_int32(nil)
m.PoolID = omit.From(val)
}
if !(m.UUID.IsValue()) {
val := random_uuid_UUID(nil)
m.UUID = omit.From(val)
}
}
// insertOptRels creates and inserts any optional the relationships on *models.PublicreportPoolPhoto
// according to the relationships in the template.
// any required relationship should have already exist on the model
func (o *PublicreportPoolPhotoTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.PublicreportPoolPhoto) error {
var err error
return err
}
// Create builds a publicreportPoolPhoto and inserts it into the database
// Relations objects are also inserted and placed in the .R field
func (o *PublicreportPoolPhotoTemplate) Create(ctx context.Context, exec bob.Executor) (*models.PublicreportPoolPhoto, error) {
var err error
opt := o.BuildSetter()
ensureCreatablePublicreportPoolPhoto(opt)
if o.r.Pool == nil {
PublicreportPoolPhotoMods.WithNewPool().Apply(ctx, o)
}
var rel0 *models.PublicreportPool
if o.r.Pool.o.alreadyPersisted {
rel0 = o.r.Pool.o.Build()
} else {
rel0, err = o.r.Pool.o.Create(ctx, exec)
if err != nil {
return nil, err
}
}
opt.PoolID = omit.From(rel0.ID)
m, err := models.PublicreportPoolPhotos.Insert(opt).One(ctx, exec)
if err != nil {
return nil, err
}
m.R.Pool = rel0
if err := o.insertOptRels(ctx, exec, m); err != nil {
return nil, err
}
return m, err
}
// MustCreate builds a publicreportPoolPhoto and inserts it into the database
// Relations objects are also inserted and placed in the .R field
// panics if an error occurs
func (o *PublicreportPoolPhotoTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.PublicreportPoolPhoto {
m, err := o.Create(ctx, exec)
if err != nil {
panic(err)
}
return m
}
// CreateOrFail builds a publicreportPoolPhoto and inserts it into the database
// Relations objects are also inserted and placed in the .R field
// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs
func (o *PublicreportPoolPhotoTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.PublicreportPoolPhoto {
tb.Helper()
m, err := o.Create(ctx, exec)
if err != nil {
tb.Fatal(err)
return nil
}
return m
}
// CreateMany builds multiple publicreportPoolPhotos and inserts them into the database
// Relations objects are also inserted and placed in the .R field
func (o PublicreportPoolPhotoTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.PublicreportPoolPhotoSlice, error) {
var err error
m := make(models.PublicreportPoolPhotoSlice, number)
for i := range m {
m[i], err = o.Create(ctx, exec)
if err != nil {
return nil, err
}
}
return m, nil
}
// MustCreateMany builds multiple publicreportPoolPhotos and inserts them into the database
// Relations objects are also inserted and placed in the .R field
// panics if an error occurs
func (o PublicreportPoolPhotoTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.PublicreportPoolPhotoSlice {
m, err := o.CreateMany(ctx, exec, number)
if err != nil {
panic(err)
}
return m
}
// CreateManyOrFail builds multiple publicreportPoolPhotos and inserts them into the database
// Relations objects are also inserted and placed in the .R field
// It calls `tb.Fatal(err)` on the test/benchmark if an error occurs
func (o PublicreportPoolPhotoTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.PublicreportPoolPhotoSlice {
tb.Helper()
m, err := o.CreateMany(ctx, exec, number)
if err != nil {
tb.Fatal(err)
return nil
}
return m
}
// PublicreportPoolPhoto has methods that act as mods for the PublicreportPoolPhotoTemplate
var PublicreportPoolPhotoMods publicreportPoolPhotoMods
type publicreportPoolPhotoMods struct{}
func (m publicreportPoolPhotoMods) RandomizeAllColumns(f *faker.Faker) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModSlice{
PublicreportPoolPhotoMods.RandomID(f),
PublicreportPoolPhotoMods.RandomSize(f),
PublicreportPoolPhotoMods.RandomFilename(f),
PublicreportPoolPhotoMods.RandomPoolID(f),
PublicreportPoolPhotoMods.RandomUUID(f),
}
}
// Set the model columns to this value
func (m publicreportPoolPhotoMods) ID(val int32) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.ID = func() int32 { return val }
})
}
// Set the Column from the function
func (m publicreportPoolPhotoMods) IDFunc(f func() int32) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.ID = f
})
}
// Clear any values for the column
func (m publicreportPoolPhotoMods) UnsetID() PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.ID = nil
})
}
// Generates a random value for the column using the given faker
// if faker is nil, a default faker is used
func (m publicreportPoolPhotoMods) RandomID(f *faker.Faker) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.ID = func() int32 {
return random_int32(f)
}
})
}
// Set the model columns to this value
func (m publicreportPoolPhotoMods) Size(val int64) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.Size = func() int64 { return val }
})
}
// Set the Column from the function
func (m publicreportPoolPhotoMods) SizeFunc(f func() int64) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.Size = f
})
}
// Clear any values for the column
func (m publicreportPoolPhotoMods) UnsetSize() PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.Size = nil
})
}
// Generates a random value for the column using the given faker
// if faker is nil, a default faker is used
func (m publicreportPoolPhotoMods) RandomSize(f *faker.Faker) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.Size = func() int64 {
return random_int64(f)
}
})
}
// Set the model columns to this value
func (m publicreportPoolPhotoMods) Filename(val string) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.Filename = func() string { return val }
})
}
// Set the Column from the function
func (m publicreportPoolPhotoMods) FilenameFunc(f func() string) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.Filename = f
})
}
// Clear any values for the column
func (m publicreportPoolPhotoMods) UnsetFilename() PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.Filename = nil
})
}
// Generates a random value for the column using the given faker
// if faker is nil, a default faker is used
func (m publicreportPoolPhotoMods) RandomFilename(f *faker.Faker) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.Filename = func() string {
return random_string(f)
}
})
}
// Set the model columns to this value
func (m publicreportPoolPhotoMods) PoolID(val int32) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.PoolID = func() int32 { return val }
})
}
// Set the Column from the function
func (m publicreportPoolPhotoMods) PoolIDFunc(f func() int32) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.PoolID = f
})
}
// Clear any values for the column
func (m publicreportPoolPhotoMods) UnsetPoolID() PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.PoolID = nil
})
}
// Generates a random value for the column using the given faker
// if faker is nil, a default faker is used
func (m publicreportPoolPhotoMods) RandomPoolID(f *faker.Faker) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.PoolID = func() int32 {
return random_int32(f)
}
})
}
// Set the model columns to this value
func (m publicreportPoolPhotoMods) UUID(val uuid.UUID) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.UUID = func() uuid.UUID { return val }
})
}
// Set the Column from the function
func (m publicreportPoolPhotoMods) UUIDFunc(f func() uuid.UUID) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.UUID = f
})
}
// Clear any values for the column
func (m publicreportPoolPhotoMods) UnsetUUID() PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.UUID = nil
})
}
// Generates a random value for the column using the given faker
// if faker is nil, a default faker is used
func (m publicreportPoolPhotoMods) RandomUUID(f *faker.Faker) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(_ context.Context, o *PublicreportPoolPhotoTemplate) {
o.UUID = func() uuid.UUID {
return random_uuid_UUID(f)
}
})
}
func (m publicreportPoolPhotoMods) WithParentsCascading() PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(ctx context.Context, o *PublicreportPoolPhotoTemplate) {
if isDone, _ := publicreportPoolPhotoWithParentsCascadingCtx.Value(ctx); isDone {
return
}
ctx = publicreportPoolPhotoWithParentsCascadingCtx.WithValue(ctx, true)
{
related := o.f.NewPublicreportPoolWithContext(ctx, PublicreportPoolMods.WithParentsCascading())
m.WithPool(related).Apply(ctx, o)
}
})
}
func (m publicreportPoolPhotoMods) WithPool(rel *PublicreportPoolTemplate) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(ctx context.Context, o *PublicreportPoolPhotoTemplate) {
o.r.Pool = &publicreportPoolPhotoRPoolR{
o: rel,
}
})
}
func (m publicreportPoolPhotoMods) WithNewPool(mods ...PublicreportPoolMod) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(ctx context.Context, o *PublicreportPoolPhotoTemplate) {
related := o.f.NewPublicreportPoolWithContext(ctx, mods...)
m.WithPool(related).Apply(ctx, o)
})
}
func (m publicreportPoolPhotoMods) WithExistingPool(em *models.PublicreportPool) PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(ctx context.Context, o *PublicreportPoolPhotoTemplate) {
o.r.Pool = &publicreportPoolPhotoRPoolR{
o: o.f.FromExistingPublicreportPool(em),
}
})
}
func (m publicreportPoolPhotoMods) WithoutPool() PublicreportPoolPhotoMod {
return PublicreportPoolPhotoModFunc(func(ctx context.Context, o *PublicreportPoolPhotoTemplate) {
o.r.Pool = nil
})
}

View file

@ -0,0 +1,53 @@
-- +goose Up
CREATE TYPE publicreport.PoolSourceDuration AS ENUM (
'none',
'less-than-week',
'1-2-weeks',
'2-4-weeks',
'1-3-months',
'more-than-3-months'
);
CREATE TABLE publicreport.pool (
id SERIAL PRIMARY KEY,
access_comments TEXT NOT NULL,
access_gate BOOLEAN NOT NULL,
access_fence BOOLEAN NOT NULL,
access_locked BOOLEAN NOT NULL,
access_dog BOOLEAN NOT NULL,
access_other BOOLEAN NOT NULL,
address TEXT NOT NULL,
address_country TEXT NOT NULL,
address_post_code TEXT NOT NULL,
address_place TEXT NOT NULL,
address_street TEXT NOT NULL,
address_region TEXT NOT NULL,
comments TEXT NOT NULL,
created TIMESTAMP WITHOUT TIME ZONE NOT NULL,
h3cell h3index,
has_adult BOOLEAN NOT NULL,
has_larvae BOOLEAN NOT NULL,
has_pupae BOOLEAN NOT NULL,
location GEOGRAPHY,
map_zoom FLOAT NOT NULL,
owner_email TEXT NOT NULL,
owner_name TEXT NOT NULL,
owner_phone TEXT NOT NULL,
public_id TEXT NOT NULL UNIQUE,
reporter_email TEXT NOT NULL,
reporter_name TEXT NOT NULL,
reporter_phone TEXT NOT NULL,
subscribe BOOLEAN NOT NULL
);
CREATE TABLE publicreport.pool_photo (
id SERIAL PRIMARY KEY,
size BIGINT NOT NULL,
filename TEXT NOT NULL,
pool_id INT NOT NULL REFERENCES publicreport.pool(id),
uuid UUID NOT NULL
);
-- +goose Down
DROP TABLE publicreport.pool_photo;
DROP TABLE publicreport.pool;
DROP TYPE publicreport.PoolSourceDuration;

View file

@ -70,6 +70,8 @@ type joins[Q dialect.Joinable] struct {
Notifications joinSet[notificationJoins[Q]]
OauthTokens joinSet[oauthTokenJoins[Q]]
Organizations joinSet[organizationJoins[Q]]
PublicreportPools joinSet[publicreportPoolJoins[Q]]
PublicreportPoolPhotos joinSet[publicreportPoolPhotoJoins[Q]]
PublicreportQuicks joinSet[publicreportQuickJoins[Q]]
PublicreportQuickPhotos joinSet[publicreportQuickPhotoJoins[Q]]
Users joinSet[userJoins[Q]]
@ -123,6 +125,8 @@ func getJoins[Q dialect.Joinable]() joins[Q] {
Notifications: buildJoinSet[notificationJoins[Q]](Notifications.Columns, buildNotificationJoins),
OauthTokens: buildJoinSet[oauthTokenJoins[Q]](OauthTokens.Columns, buildOauthTokenJoins),
Organizations: buildJoinSet[organizationJoins[Q]](Organizations.Columns, buildOrganizationJoins),
PublicreportPools: buildJoinSet[publicreportPoolJoins[Q]](PublicreportPools.Columns, buildPublicreportPoolJoins),
PublicreportPoolPhotos: buildJoinSet[publicreportPoolPhotoJoins[Q]](PublicreportPoolPhotos.Columns, buildPublicreportPoolPhotoJoins),
PublicreportQuicks: buildJoinSet[publicreportQuickJoins[Q]](PublicreportQuicks.Columns, buildPublicreportQuickJoins),
PublicreportQuickPhotos: buildJoinSet[publicreportQuickPhotoJoins[Q]](PublicreportQuickPhotos.Columns, buildPublicreportQuickPhotoJoins),
Users: buildJoinSet[userJoins[Q]](Users.Columns, buildUserJoins),

View file

@ -55,6 +55,8 @@ type preloaders struct {
Notification notificationPreloader
OauthToken oauthTokenPreloader
Organization organizationPreloader
PublicreportPool publicreportPoolPreloader
PublicreportPoolPhoto publicreportPoolPhotoPreloader
PublicreportQuick publicreportQuickPreloader
PublicreportQuickPhoto publicreportQuickPhotoPreloader
User userPreloader
@ -100,6 +102,8 @@ func getPreloaders() preloaders {
Notification: buildNotificationPreloader(),
OauthToken: buildOauthTokenPreloader(),
Organization: buildOrganizationPreloader(),
PublicreportPool: buildPublicreportPoolPreloader(),
PublicreportPoolPhoto: buildPublicreportPoolPhotoPreloader(),
PublicreportQuick: buildPublicreportQuickPreloader(),
PublicreportQuickPhoto: buildPublicreportQuickPhotoPreloader(),
User: buildUserPreloader(),
@ -151,6 +155,8 @@ type thenLoaders[Q orm.Loadable] struct {
Notification notificationThenLoader[Q]
OauthToken oauthTokenThenLoader[Q]
Organization organizationThenLoader[Q]
PublicreportPool publicreportPoolThenLoader[Q]
PublicreportPoolPhoto publicreportPoolPhotoThenLoader[Q]
PublicreportQuick publicreportQuickThenLoader[Q]
PublicreportQuickPhoto publicreportQuickPhotoThenLoader[Q]
User userThenLoader[Q]
@ -196,6 +202,8 @@ func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] {
Notification: buildNotificationThenLoader[Q](),
OauthToken: buildOauthTokenThenLoader[Q](),
Organization: buildOrganizationThenLoader[Q](),
PublicreportPool: buildPublicreportPoolThenLoader[Q](),
PublicreportPoolPhoto: buildPublicreportPoolPhotoThenLoader[Q](),
PublicreportQuick: buildPublicreportQuickThenLoader[Q](),
PublicreportQuickPhoto: buildPublicreportQuickPhotoThenLoader[Q](),
User: buildUserThenLoader[Q](),

View file

@ -59,6 +59,8 @@ func Where[Q psql.Filterable]() struct {
OauthTokens oauthTokenWhere[Q]
Organizations organizationWhere[Q]
PublicreportNuisances publicreportNuisanceWhere[Q]
PublicreportPools publicreportPoolWhere[Q]
PublicreportPoolPhotos publicreportPoolPhotoWhere[Q]
PublicreportQuicks publicreportQuickWhere[Q]
PublicreportQuickPhotos publicreportQuickPhotoWhere[Q]
RasterColumns rasterColumnWhere[Q]
@ -110,6 +112,8 @@ func Where[Q psql.Filterable]() struct {
OauthTokens oauthTokenWhere[Q]
Organizations organizationWhere[Q]
PublicreportNuisances publicreportNuisanceWhere[Q]
PublicreportPools publicreportPoolWhere[Q]
PublicreportPoolPhotos publicreportPoolPhotoWhere[Q]
PublicreportQuicks publicreportQuickWhere[Q]
PublicreportQuickPhotos publicreportQuickPhotoWhere[Q]
RasterColumns rasterColumnWhere[Q]
@ -160,6 +164,8 @@ func Where[Q psql.Filterable]() struct {
OauthTokens: buildOauthTokenWhere[Q](OauthTokens.Columns),
Organizations: buildOrganizationWhere[Q](Organizations.Columns),
PublicreportNuisances: buildPublicreportNuisanceWhere[Q](PublicreportNuisances.Columns),
PublicreportPools: buildPublicreportPoolWhere[Q](PublicreportPools.Columns),
PublicreportPoolPhotos: buildPublicreportPoolPhotoWhere[Q](PublicreportPoolPhotos.Columns),
PublicreportQuicks: buildPublicreportQuickWhere[Q](PublicreportQuicks.Columns),
PublicreportQuickPhotos: buildPublicreportQuickPhotoWhere[Q](PublicreportQuickPhotos.Columns),
RasterColumns: buildRasterColumnWhere[Q](RasterColumns.Columns),

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,678 @@
// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT.
// This file is meant to be re-generated in place and/or deleted at any time.
package models
import (
"context"
"fmt"
"io"
"github.com/aarondl/opt/omit"
"github.com/google/uuid"
"github.com/stephenafamo/bob"
"github.com/stephenafamo/bob/dialect/psql"
"github.com/stephenafamo/bob/dialect/psql/dialect"
"github.com/stephenafamo/bob/dialect/psql/dm"
"github.com/stephenafamo/bob/dialect/psql/sm"
"github.com/stephenafamo/bob/dialect/psql/um"
"github.com/stephenafamo/bob/expr"
"github.com/stephenafamo/bob/mods"
"github.com/stephenafamo/bob/orm"
"github.com/stephenafamo/bob/types/pgtypes"
)
// PublicreportPoolPhoto is an object representing the database table.
type PublicreportPoolPhoto struct {
ID int32 `db:"id,pk" `
Size int64 `db:"size" `
Filename string `db:"filename" `
PoolID int32 `db:"pool_id" `
UUID uuid.UUID `db:"uuid" `
R publicreportPoolPhotoR `db:"-" `
}
// PublicreportPoolPhotoSlice is an alias for a slice of pointers to PublicreportPoolPhoto.
// This should almost always be used instead of []*PublicreportPoolPhoto.
type PublicreportPoolPhotoSlice []*PublicreportPoolPhoto
// PublicreportPoolPhotos contains methods to work with the pool_photo table
var PublicreportPoolPhotos = psql.NewTablex[*PublicreportPoolPhoto, PublicreportPoolPhotoSlice, *PublicreportPoolPhotoSetter]("publicreport", "pool_photo", buildPublicreportPoolPhotoColumns("publicreport.pool_photo"))
// PublicreportPoolPhotosQuery is a query on the pool_photo table
type PublicreportPoolPhotosQuery = *psql.ViewQuery[*PublicreportPoolPhoto, PublicreportPoolPhotoSlice]
// publicreportPoolPhotoR is where relationships are stored.
type publicreportPoolPhotoR struct {
Pool *PublicreportPool // publicreport.pool_photo.pool_photo_pool_id_fkey
}
func buildPublicreportPoolPhotoColumns(alias string) publicreportPoolPhotoColumns {
return publicreportPoolPhotoColumns{
ColumnsExpr: expr.NewColumnsExpr(
"id", "size", "filename", "pool_id", "uuid",
).WithParent("publicreport.pool_photo"),
tableAlias: alias,
ID: psql.Quote(alias, "id"),
Size: psql.Quote(alias, "size"),
Filename: psql.Quote(alias, "filename"),
PoolID: psql.Quote(alias, "pool_id"),
UUID: psql.Quote(alias, "uuid"),
}
}
type publicreportPoolPhotoColumns struct {
expr.ColumnsExpr
tableAlias string
ID psql.Expression
Size psql.Expression
Filename psql.Expression
PoolID psql.Expression
UUID psql.Expression
}
func (c publicreportPoolPhotoColumns) Alias() string {
return c.tableAlias
}
func (publicreportPoolPhotoColumns) AliasedAs(alias string) publicreportPoolPhotoColumns {
return buildPublicreportPoolPhotoColumns(alias)
}
// PublicreportPoolPhotoSetter is used for insert/upsert/update operations
// All values are optional, and do not have to be set
// Generated columns are not included
type PublicreportPoolPhotoSetter struct {
ID omit.Val[int32] `db:"id,pk" `
Size omit.Val[int64] `db:"size" `
Filename omit.Val[string] `db:"filename" `
PoolID omit.Val[int32] `db:"pool_id" `
UUID omit.Val[uuid.UUID] `db:"uuid" `
}
func (s PublicreportPoolPhotoSetter) SetColumns() []string {
vals := make([]string, 0, 5)
if s.ID.IsValue() {
vals = append(vals, "id")
}
if s.Size.IsValue() {
vals = append(vals, "size")
}
if s.Filename.IsValue() {
vals = append(vals, "filename")
}
if s.PoolID.IsValue() {
vals = append(vals, "pool_id")
}
if s.UUID.IsValue() {
vals = append(vals, "uuid")
}
return vals
}
func (s PublicreportPoolPhotoSetter) Overwrite(t *PublicreportPoolPhoto) {
if s.ID.IsValue() {
t.ID = s.ID.MustGet()
}
if s.Size.IsValue() {
t.Size = s.Size.MustGet()
}
if s.Filename.IsValue() {
t.Filename = s.Filename.MustGet()
}
if s.PoolID.IsValue() {
t.PoolID = s.PoolID.MustGet()
}
if s.UUID.IsValue() {
t.UUID = s.UUID.MustGet()
}
}
func (s *PublicreportPoolPhotoSetter) Apply(q *dialect.InsertQuery) {
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
return PublicreportPoolPhotos.BeforeInsertHooks.RunHooks(ctx, exec, s)
})
q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
vals := make([]bob.Expression, 5)
if s.ID.IsValue() {
vals[0] = psql.Arg(s.ID.MustGet())
} else {
vals[0] = psql.Raw("DEFAULT")
}
if s.Size.IsValue() {
vals[1] = psql.Arg(s.Size.MustGet())
} else {
vals[1] = psql.Raw("DEFAULT")
}
if s.Filename.IsValue() {
vals[2] = psql.Arg(s.Filename.MustGet())
} else {
vals[2] = psql.Raw("DEFAULT")
}
if s.PoolID.IsValue() {
vals[3] = psql.Arg(s.PoolID.MustGet())
} else {
vals[3] = psql.Raw("DEFAULT")
}
if s.UUID.IsValue() {
vals[4] = psql.Arg(s.UUID.MustGet())
} else {
vals[4] = psql.Raw("DEFAULT")
}
return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "")
}))
}
func (s PublicreportPoolPhotoSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
return um.Set(s.Expressions()...)
}
func (s PublicreportPoolPhotoSetter) Expressions(prefix ...string) []bob.Expression {
exprs := make([]bob.Expression, 0, 5)
if s.ID.IsValue() {
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
psql.Quote(append(prefix, "id")...),
psql.Arg(s.ID),
}})
}
if s.Size.IsValue() {
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
psql.Quote(append(prefix, "size")...),
psql.Arg(s.Size),
}})
}
if s.Filename.IsValue() {
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
psql.Quote(append(prefix, "filename")...),
psql.Arg(s.Filename),
}})
}
if s.PoolID.IsValue() {
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
psql.Quote(append(prefix, "pool_id")...),
psql.Arg(s.PoolID),
}})
}
if s.UUID.IsValue() {
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
psql.Quote(append(prefix, "uuid")...),
psql.Arg(s.UUID),
}})
}
return exprs
}
// FindPublicreportPoolPhoto retrieves a single record by primary key
// If cols is empty Find will return all columns.
func FindPublicreportPoolPhoto(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*PublicreportPoolPhoto, error) {
if len(cols) == 0 {
return PublicreportPoolPhotos.Query(
sm.Where(PublicreportPoolPhotos.Columns.ID.EQ(psql.Arg(IDPK))),
).One(ctx, exec)
}
return PublicreportPoolPhotos.Query(
sm.Where(PublicreportPoolPhotos.Columns.ID.EQ(psql.Arg(IDPK))),
sm.Columns(PublicreportPoolPhotos.Columns.Only(cols...)),
).One(ctx, exec)
}
// PublicreportPoolPhotoExists checks the presence of a single record by primary key
func PublicreportPoolPhotoExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) {
return PublicreportPoolPhotos.Query(
sm.Where(PublicreportPoolPhotos.Columns.ID.EQ(psql.Arg(IDPK))),
).Exists(ctx, exec)
}
// AfterQueryHook is called after PublicreportPoolPhoto is retrieved from the database
func (o *PublicreportPoolPhoto) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error {
var err error
switch queryType {
case bob.QueryTypeSelect:
ctx, err = PublicreportPoolPhotos.AfterSelectHooks.RunHooks(ctx, exec, PublicreportPoolPhotoSlice{o})
case bob.QueryTypeInsert:
ctx, err = PublicreportPoolPhotos.AfterInsertHooks.RunHooks(ctx, exec, PublicreportPoolPhotoSlice{o})
case bob.QueryTypeUpdate:
ctx, err = PublicreportPoolPhotos.AfterUpdateHooks.RunHooks(ctx, exec, PublicreportPoolPhotoSlice{o})
case bob.QueryTypeDelete:
ctx, err = PublicreportPoolPhotos.AfterDeleteHooks.RunHooks(ctx, exec, PublicreportPoolPhotoSlice{o})
}
return err
}
// primaryKeyVals returns the primary key values of the PublicreportPoolPhoto
func (o *PublicreportPoolPhoto) primaryKeyVals() bob.Expression {
return psql.Arg(o.ID)
}
func (o *PublicreportPoolPhoto) pkEQ() dialect.Expression {
return psql.Quote("publicreport.pool_photo", "id").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
return o.primaryKeyVals().WriteSQL(ctx, w, d, start)
}))
}
// Update uses an executor to update the PublicreportPoolPhoto
func (o *PublicreportPoolPhoto) Update(ctx context.Context, exec bob.Executor, s *PublicreportPoolPhotoSetter) error {
v, err := PublicreportPoolPhotos.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec)
if err != nil {
return err
}
o.R = v.R
*o = *v
return nil
}
// Delete deletes a single PublicreportPoolPhoto record with an executor
func (o *PublicreportPoolPhoto) Delete(ctx context.Context, exec bob.Executor) error {
_, err := PublicreportPoolPhotos.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec)
return err
}
// Reload refreshes the PublicreportPoolPhoto using the executor
func (o *PublicreportPoolPhoto) Reload(ctx context.Context, exec bob.Executor) error {
o2, err := PublicreportPoolPhotos.Query(
sm.Where(PublicreportPoolPhotos.Columns.ID.EQ(psql.Arg(o.ID))),
).One(ctx, exec)
if err != nil {
return err
}
o2.R = o.R
*o = *o2
return nil
}
// AfterQueryHook is called after PublicreportPoolPhotoSlice is retrieved from the database
func (o PublicreportPoolPhotoSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error {
var err error
switch queryType {
case bob.QueryTypeSelect:
ctx, err = PublicreportPoolPhotos.AfterSelectHooks.RunHooks(ctx, exec, o)
case bob.QueryTypeInsert:
ctx, err = PublicreportPoolPhotos.AfterInsertHooks.RunHooks(ctx, exec, o)
case bob.QueryTypeUpdate:
ctx, err = PublicreportPoolPhotos.AfterUpdateHooks.RunHooks(ctx, exec, o)
case bob.QueryTypeDelete:
ctx, err = PublicreportPoolPhotos.AfterDeleteHooks.RunHooks(ctx, exec, o)
}
return err
}
func (o PublicreportPoolPhotoSlice) pkIN() dialect.Expression {
if len(o) == 0 {
return psql.Raw("NULL")
}
return psql.Quote("publicreport.pool_photo", "id").In(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
pkPairs := make([]bob.Expression, len(o))
for i, row := range o {
pkPairs[i] = row.primaryKeyVals()
}
return bob.ExpressSlice(ctx, w, d, start, pkPairs, "", ", ", "")
}))
}
// copyMatchingRows finds models in the given slice that have the same primary key
// then it first copies the existing relationships from the old model to the new model
// and then replaces the old model in the slice with the new model
func (o PublicreportPoolPhotoSlice) copyMatchingRows(from ...*PublicreportPoolPhoto) {
for i, old := range o {
for _, new := range from {
if new.ID != old.ID {
continue
}
new.R = old.R
o[i] = new
break
}
}
}
// UpdateMod modifies an update query with "WHERE primary_key IN (o...)"
func (o PublicreportPoolPhotoSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
return bob.ModFunc[*dialect.UpdateQuery](func(q *dialect.UpdateQuery) {
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
return PublicreportPoolPhotos.BeforeUpdateHooks.RunHooks(ctx, exec, o)
})
q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error {
var err error
switch retrieved := retrieved.(type) {
case *PublicreportPoolPhoto:
o.copyMatchingRows(retrieved)
case []*PublicreportPoolPhoto:
o.copyMatchingRows(retrieved...)
case PublicreportPoolPhotoSlice:
o.copyMatchingRows(retrieved...)
default:
// If the retrieved value is not a PublicreportPoolPhoto or a slice of PublicreportPoolPhoto
// then run the AfterUpdateHooks on the slice
_, err = PublicreportPoolPhotos.AfterUpdateHooks.RunHooks(ctx, exec, o)
}
return err
}))
q.AppendWhere(o.pkIN())
})
}
// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)"
func (o PublicreportPoolPhotoSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] {
return bob.ModFunc[*dialect.DeleteQuery](func(q *dialect.DeleteQuery) {
q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) {
return PublicreportPoolPhotos.BeforeDeleteHooks.RunHooks(ctx, exec, o)
})
q.AppendLoader(bob.LoaderFunc(func(ctx context.Context, exec bob.Executor, retrieved any) error {
var err error
switch retrieved := retrieved.(type) {
case *PublicreportPoolPhoto:
o.copyMatchingRows(retrieved)
case []*PublicreportPoolPhoto:
o.copyMatchingRows(retrieved...)
case PublicreportPoolPhotoSlice:
o.copyMatchingRows(retrieved...)
default:
// If the retrieved value is not a PublicreportPoolPhoto or a slice of PublicreportPoolPhoto
// then run the AfterDeleteHooks on the slice
_, err = PublicreportPoolPhotos.AfterDeleteHooks.RunHooks(ctx, exec, o)
}
return err
}))
q.AppendWhere(o.pkIN())
})
}
func (o PublicreportPoolPhotoSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals PublicreportPoolPhotoSetter) error {
if len(o) == 0 {
return nil
}
_, err := PublicreportPoolPhotos.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec)
return err
}
func (o PublicreportPoolPhotoSlice) DeleteAll(ctx context.Context, exec bob.Executor) error {
if len(o) == 0 {
return nil
}
_, err := PublicreportPoolPhotos.Delete(o.DeleteMod()).Exec(ctx, exec)
return err
}
func (o PublicreportPoolPhotoSlice) ReloadAll(ctx context.Context, exec bob.Executor) error {
if len(o) == 0 {
return nil
}
o2, err := PublicreportPoolPhotos.Query(sm.Where(o.pkIN())).All(ctx, exec)
if err != nil {
return err
}
o.copyMatchingRows(o2...)
return nil
}
// Pool starts a query for related objects on publicreport.pool
func (o *PublicreportPoolPhoto) Pool(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportPoolsQuery {
return PublicreportPools.Query(append(mods,
sm.Where(PublicreportPools.Columns.ID.EQ(psql.Arg(o.PoolID))),
)...)
}
func (os PublicreportPoolPhotoSlice) Pool(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportPoolsQuery {
pkPoolID := make(pgtypes.Array[int32], 0, len(os))
for _, o := range os {
if o == nil {
continue
}
pkPoolID = append(pkPoolID, o.PoolID)
}
PKArgExpr := psql.Select(sm.Columns(
psql.F("unnest", psql.Cast(psql.Arg(pkPoolID), "integer[]")),
))
return PublicreportPools.Query(append(mods,
sm.Where(psql.Group(PublicreportPools.Columns.ID).OP("IN", PKArgExpr)),
)...)
}
func attachPublicreportPoolPhotoPool0(ctx context.Context, exec bob.Executor, count int, publicreportPoolPhoto0 *PublicreportPoolPhoto, publicreportPool1 *PublicreportPool) (*PublicreportPoolPhoto, error) {
setter := &PublicreportPoolPhotoSetter{
PoolID: omit.From(publicreportPool1.ID),
}
err := publicreportPoolPhoto0.Update(ctx, exec, setter)
if err != nil {
return nil, fmt.Errorf("attachPublicreportPoolPhotoPool0: %w", err)
}
return publicreportPoolPhoto0, nil
}
func (publicreportPoolPhoto0 *PublicreportPoolPhoto) InsertPool(ctx context.Context, exec bob.Executor, related *PublicreportPoolSetter) error {
var err error
publicreportPool1, err := PublicreportPools.Insert(related).One(ctx, exec)
if err != nil {
return fmt.Errorf("inserting related objects: %w", err)
}
_, err = attachPublicreportPoolPhotoPool0(ctx, exec, 1, publicreportPoolPhoto0, publicreportPool1)
if err != nil {
return err
}
publicreportPoolPhoto0.R.Pool = publicreportPool1
publicreportPool1.R.PoolPhotos = append(publicreportPool1.R.PoolPhotos, publicreportPoolPhoto0)
return nil
}
func (publicreportPoolPhoto0 *PublicreportPoolPhoto) AttachPool(ctx context.Context, exec bob.Executor, publicreportPool1 *PublicreportPool) error {
var err error
_, err = attachPublicreportPoolPhotoPool0(ctx, exec, 1, publicreportPoolPhoto0, publicreportPool1)
if err != nil {
return err
}
publicreportPoolPhoto0.R.Pool = publicreportPool1
publicreportPool1.R.PoolPhotos = append(publicreportPool1.R.PoolPhotos, publicreportPoolPhoto0)
return nil
}
type publicreportPoolPhotoWhere[Q psql.Filterable] struct {
ID psql.WhereMod[Q, int32]
Size psql.WhereMod[Q, int64]
Filename psql.WhereMod[Q, string]
PoolID psql.WhereMod[Q, int32]
UUID psql.WhereMod[Q, uuid.UUID]
}
func (publicreportPoolPhotoWhere[Q]) AliasedAs(alias string) publicreportPoolPhotoWhere[Q] {
return buildPublicreportPoolPhotoWhere[Q](buildPublicreportPoolPhotoColumns(alias))
}
func buildPublicreportPoolPhotoWhere[Q psql.Filterable](cols publicreportPoolPhotoColumns) publicreportPoolPhotoWhere[Q] {
return publicreportPoolPhotoWhere[Q]{
ID: psql.Where[Q, int32](cols.ID),
Size: psql.Where[Q, int64](cols.Size),
Filename: psql.Where[Q, string](cols.Filename),
PoolID: psql.Where[Q, int32](cols.PoolID),
UUID: psql.Where[Q, uuid.UUID](cols.UUID),
}
}
func (o *PublicreportPoolPhoto) Preload(name string, retrieved any) error {
if o == nil {
return nil
}
switch name {
case "Pool":
rel, ok := retrieved.(*PublicreportPool)
if !ok {
return fmt.Errorf("publicreportPoolPhoto cannot load %T as %q", retrieved, name)
}
o.R.Pool = rel
if rel != nil {
rel.R.PoolPhotos = PublicreportPoolPhotoSlice{o}
}
return nil
default:
return fmt.Errorf("publicreportPoolPhoto has no relationship %q", name)
}
}
type publicreportPoolPhotoPreloader struct {
Pool func(...psql.PreloadOption) psql.Preloader
}
func buildPublicreportPoolPhotoPreloader() publicreportPoolPhotoPreloader {
return publicreportPoolPhotoPreloader{
Pool: func(opts ...psql.PreloadOption) psql.Preloader {
return psql.Preload[*PublicreportPool, PublicreportPoolSlice](psql.PreloadRel{
Name: "Pool",
Sides: []psql.PreloadSide{
{
From: PublicreportPoolPhotos,
To: PublicreportPools,
FromColumns: []string{"pool_id"},
ToColumns: []string{"id"},
},
},
}, PublicreportPools.Columns.Names(), opts...)
},
}
}
type publicreportPoolPhotoThenLoader[Q orm.Loadable] struct {
Pool func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
}
func buildPublicreportPoolPhotoThenLoader[Q orm.Loadable]() publicreportPoolPhotoThenLoader[Q] {
type PoolLoadInterface interface {
LoadPool(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error
}
return publicreportPoolPhotoThenLoader[Q]{
Pool: thenLoadBuilder[Q](
"Pool",
func(ctx context.Context, exec bob.Executor, retrieved PoolLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error {
return retrieved.LoadPool(ctx, exec, mods...)
},
),
}
}
// LoadPool loads the publicreportPoolPhoto's Pool into the .R struct
func (o *PublicreportPoolPhoto) LoadPool(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error {
if o == nil {
return nil
}
// Reset the relationship
o.R.Pool = nil
related, err := o.Pool(mods...).One(ctx, exec)
if err != nil {
return err
}
related.R.PoolPhotos = PublicreportPoolPhotoSlice{o}
o.R.Pool = related
return nil
}
// LoadPool loads the publicreportPoolPhoto's Pool into the .R struct
func (os PublicreportPoolPhotoSlice) LoadPool(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error {
if len(os) == 0 {
return nil
}
publicreportPools, err := os.Pool(mods...).All(ctx, exec)
if err != nil {
return err
}
for _, o := range os {
if o == nil {
continue
}
for _, rel := range publicreportPools {
if !(o.PoolID == rel.ID) {
continue
}
rel.R.PoolPhotos = append(rel.R.PoolPhotos, o)
o.R.Pool = rel
break
}
}
return nil
}
type publicreportPoolPhotoJoins[Q dialect.Joinable] struct {
typ string
Pool modAs[Q, publicreportPoolColumns]
}
func (j publicreportPoolPhotoJoins[Q]) aliasedAs(alias string) publicreportPoolPhotoJoins[Q] {
return buildPublicreportPoolPhotoJoins[Q](buildPublicreportPoolPhotoColumns(alias), j.typ)
}
func buildPublicreportPoolPhotoJoins[Q dialect.Joinable](cols publicreportPoolPhotoColumns, typ string) publicreportPoolPhotoJoins[Q] {
return publicreportPoolPhotoJoins[Q]{
typ: typ,
Pool: modAs[Q, publicreportPoolColumns]{
c: PublicreportPools.Columns,
f: func(to publicreportPoolColumns) bob.Mod[Q] {
mods := make(mods.QueryMods[Q], 0, 1)
{
mods = append(mods, dialect.Join[Q](typ, PublicreportPools.Name().As(to.Alias())).On(
to.ID.EQ(cols.PoolID),
))
}
return mods
},
},
}
}

View file

@ -101,3 +101,44 @@ func CellToPostgisGeometry(c h3.Cell) (string, error) {
return fmt.Sprintf("POLYGON((%s))", sb.String()), nil
}
// Convert from an accuracy in meters of GPS coordinates to the H3 Resolution that has at least
// the same area. In other words, for a GPS coordinate accuracy of 2m you have pi*(2m)^2 or ~12.5m^2
// of area which corresponds to resolution 13 (average area of 43.87^2) vs resolution 14 (average area 6.26m^2)
// See https://h3geo.org/docs/core-library/restable
func MeterAccuracyToH3Resolution(accuracy_in_meters float64) int {
area := accuracy_in_meters * accuracy_in_meters * 3.1415
if area < 0.895 {
return 15
} else if area < 6.267 {
return 14
} else if area < 43.87 {
return 13
} else if area < 307.092 {
return 12
} else if area < 2149.643 {
return 11
} else if area < 15_047.502 {
return 10
} else if area < 105_332.513 {
return 9
} else if area < 737_327.598 {
return 8
} else if area < 5_161_293.360 {
return 7
} else if area < 36_129_062.164 {
return 6
} else if area < 252_903_858.182 {
return 5
} else if area < 1_770_347_654.491 {
return 4
} else if area < 12_393_434_655.088 {
return 3
} else if area < 86_801_780_398.997 {
return 2
} else if area < 609_788_441_794.134 {
return 1
} else {
return 0
}
}

View file

@ -1,47 +1,21 @@
package publicreport
import (
"bytes"
"fmt"
"io"
"net/http"
"strconv"
"time"
"github.com/Gleipnir-Technology/nidus-sync/config"
"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/h3utils"
"github.com/Gleipnir-Technology/nidus-sync/htmlpage"
"github.com/Gleipnir-Technology/nidus-sync/userfile"
"github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"github.com/stephenafamo/bob/dialect/psql"
"github.com/stephenafamo/bob/dialect/psql/um"
)
func Router() chi.Router {
r := chi.NewRouter()
r.Get("/", getRoot)
r.Get("/nuisance", getNuisance)
r.Post("/nuisance-submit", postNuisance)
r.Get("/nuisance-submit-complete", getNuisanceSubmitComplete)
r.Get("/pool", getPool)
r.Get("/quick", getQuick)
r.Post("/quick-submit", postQuick)
r.Get("/quick-submit-complete", getQuickSubmitComplete)
r.Post("/register-notifications", postRegisterNotifications)
r.Get("/register-notifications-complete", getRegisterNotificationsComplete)
r.Get("/status", getStatus)
localFS := http.Dir("./static")
htmlpage.FileServer(r, "/static", localFS, EmbeddedStaticFS, "static")
return r
}
func getRoot(w http.ResponseWriter, r *http.Request) {
htmlpage.RenderOrError(
w,
@ -67,32 +41,6 @@ func getNuisanceSubmitComplete(w http.ResponseWriter, r *http.Request) {
},
)
}
func getPool(w http.ResponseWriter, r *http.Request) {
htmlpage.RenderOrError(
w,
Pool,
ContextPool{
MapboxToken: config.MapboxToken,
},
)
}
func getQuick(w http.ResponseWriter, r *http.Request) {
htmlpage.RenderOrError(
w,
Quick,
ContextQuick{},
)
}
func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) {
report := r.URL.Query().Get("report")
htmlpage.RenderOrError(
w,
QuickSubmitComplete,
ContextQuickSubmitComplete{
ReportID: report,
},
)
}
func getRegisterNotificationsComplete(w http.ResponseWriter, r *http.Request) {
report := r.URL.Query().Get("report")
htmlpage.RenderOrError(
@ -135,7 +83,6 @@ func postNuisance(w http.ResponseWriter, r *http.Request) {
return
}
inspection_type_str := postFormValueOrNone(r, "inspection-type")
var inspection_type enums.PublicreportNuisanceinspectiontype
err = inspection_type.Scan(inspection_type_str)
@ -188,29 +135,29 @@ func postNuisance(w http.ResponseWriter, r *http.Request) {
log.Info().Str("address", address).Str("name", name).Msg("Got report")
setter := models.PublicreportNuisanceSetter{
AdditionalInfo: omit.From(additional_info),
Created: omit.From(time.Now()),
Duration: omit.From(duration),
Email: omit.From(email),
InspectionType: omit.From(inspection_type),
Location: omit.From(location),
AdditionalInfo: omit.From(additional_info),
Created: omit.From(time.Now()),
Duration: omit.From(duration),
Email: omit.From(email),
InspectionType: omit.From(inspection_type),
Location: omit.From(location),
PreferredDateRange: omit.From(preferred_date_range),
PreferredTime: omit.From(preferred_time),
PublicID: omit.From(public_id),
RequestCall: omit.From(request_call),
Severity: omit.From(int16(severity)),
SourceContainer: omit.From(source_container),
SourceDescription: omit.From(source_description),
SourceRoof: omit.From(source_roof),
SourceStagnant: omit.From(source_stagnant),
TimeOfDayDay: omit.From(tod_day),
TimeOfDayEarly: omit.From(tod_early),
TimeOfDayEvening: omit.From(tod_evening),
TimeOfDayNight: omit.From(tod_night),
ReporterAddress: omit.From(address),
ReporterEmail: omit.From(email),
ReporterName: omit.From(name),
ReporterPhone: omit.From(phone),
PreferredTime: omit.From(preferred_time),
PublicID: omit.From(public_id),
RequestCall: omit.From(request_call),
Severity: omit.From(int16(severity)),
SourceContainer: omit.From(source_container),
SourceDescription: omit.From(source_description),
SourceRoof: omit.From(source_roof),
SourceStagnant: omit.From(source_stagnant),
TimeOfDayDay: omit.From(tod_day),
TimeOfDayEarly: omit.From(tod_early),
TimeOfDayEvening: omit.From(tod_evening),
TimeOfDayNight: omit.From(tod_night),
ReporterAddress: omit.From(address),
ReporterEmail: omit.From(email),
ReporterName: omit.From(name),
ReporterPhone: omit.From(phone),
}
nuisance, err := models.PublicreportNuisances.Insert(&setter).One(r.Context(), db.PGInstance.BobDB)
if err != nil {
@ -221,108 +168,6 @@ func postNuisance(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, fmt.Sprintf("/nuisance-submit-complete?report=%s", public_id), http.StatusFound)
}
func postQuick(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
if err != nil {
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
return
}
lat := r.FormValue("latitude")
lng := r.FormValue("longitude")
comments := r.FormValue("comments")
//photos := r.FormValue("photos")
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
}
u, err := GenerateReportID()
if err != nil {
respondError(w, "Failed to create quick report public ID", err, http.StatusInternalServerError)
return
}
c, err := h3utils.GetCell(longitude, latitude, 15)
setter := models.PublicreportQuickSetter{
Created: omit.From(time.Now()),
Comments: omit.From(comments),
//Location: omitnull.From(fmt.Sprintf("ST_GeometryFromText(Point(%s %s))", longitude, latitude)),
H3cell: omitnull.From(c.String()),
PublicID: omit.From(u),
ReporterEmail: omit.From(""),
ReporterPhone: omit.From(""),
}
quick, err := models.PublicreportQuicks.Insert(&setter).One(r.Context(), db.PGInstance.BobDB)
if err != nil {
respondError(w, "Failed to create database record", err, http.StatusInternalServerError)
return
}
_, err = psql.Update(
um.Table("publicreport.quick"),
um.SetCol("location").To(fmt.Sprintf("ST_GeometryFromText('Point(%f %f)')", longitude, latitude)),
um.Where(psql.Quote("id").EQ(psql.Arg(quick.ID))),
).Exec(r.Context(), db.PGInstance.BobDB)
if err != nil {
respondError(w, "Failed to insert publicreport", err, http.StatusInternalServerError)
return
}
log.Info().Float64("latitude", latitude).Float64("longitude", longitude).Msg("Got upload")
photoSetters := make([]*models.PublicreportQuickPhotoSetter, 0)
for _, fheaders := range r.MultipartForm.File {
for _, headers := range fheaders {
file, err := headers.Open()
if err != nil {
respondError(w, "Failed to open header", err, http.StatusInternalServerError)
return
}
defer file.Close()
buff := make([]byte, 512)
file.Read(buff)
file.Seek(0, 0)
contentType := http.DetectContentType(buff)
var sizeBuff bytes.Buffer
fileSize, err := sizeBuff.ReadFrom(file)
if err != nil {
respondError(w, "Failed to read file", err, http.StatusInternalServerError)
return
}
file.Seek(0, 0)
contentBuf := bytes.NewBuffer(nil)
if _, err := io.Copy(contentBuf, file); err != nil {
respondError(w, "Failed to save file", err, http.StatusInternalServerError)
return
}
log.Info().Int64("size", fileSize).Str("filename", headers.Filename).Str("content-type", contentType).Msg("Got an uploaded file")
u, err := uuid.NewUUID()
if err != nil {
respondError(w, "Failed to create quick report photo uuid", err, http.StatusInternalServerError)
continue
}
err = userfile.PublicImageFileContentWrite(u, file)
photoSetters = append(photoSetters, &models.PublicreportQuickPhotoSetter{
Size: omit.From(fileSize),
Filename: omit.From(headers.Filename),
UUID: omit.From(u),
})
}
}
err = quick.InsertQuickPhotos(r.Context(), db.PGInstance.BobDB, photoSetters...)
if err != nil {
respondError(w, "Failed to create photo records", err, http.StatusInternalServerError)
return
}
http.Redirect(w, r, fmt.Sprintf("/quick-submit-complete?report=%s", u), http.StatusFound)
}
func postRegisterNotifications(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {

View file

@ -0,0 +1,69 @@
package publicreport
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":
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_GeometryFromText('Point(%f %f)')", longitude, latitude),
Populated: true,
}, nil
}

View file

@ -17,13 +17,6 @@ type ContextNuisance struct{}
type ContextNuisanceSubmitComplete struct {
ReportID string
}
type ContextPool struct{
MapboxToken string
}
type ContextQuick struct{}
type ContextQuickSubmitComplete struct {
ReportID string
}
type ContextRegisterNotificationsComplete struct {
ReportID string
}
@ -33,15 +26,12 @@ type ContextStatus struct{}
var (
Nuisance = buildTemplate("nuisance", "base")
NuisanceSubmitComplete = buildTemplate("nuisance-submit-complete", "base")
Pool = buildTemplate("pool", "base")
Quick = buildTemplate("quick", "base")
QuickSubmitComplete = buildTemplate("quick-submit-complete", "base")
RegisterNotificationsComplete = buildTemplate("register-notifications-complete", "base")
Root = buildTemplate("root", "base")
Status = buildTemplate("status", "base")
)
var components = [...]string{"footer", "location-geocode", "location-geocode-header", "map", "map-header", "photo-upload", "photo-upload-header"}
var components = [...]string{"footer", "location-geocode", "location-geocode-header", "photo-upload", "photo-upload-header"}
func buildTemplate(files ...string) *htmlpage.BuiltTemplate {
subdir := "public-report"

View file

@ -0,0 +1,64 @@
package publicreport
import (
"bytes"
"fmt"
"io"
"net/http"
"github.com/Gleipnir-Technology/nidus-sync/userfile"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
)
type PhotoUpload struct {
Filename string
Size int64
UUID uuid.UUID
}
func extractPhotoUploads(r *http.Request) (uploads []PhotoUpload, err error) {
uploads = make([]PhotoUpload, 0)
for _, fheaders := range r.MultipartForm.File {
for _, headers := range fheaders {
file, err := headers.Open()
if err != nil {
return uploads, fmt.Errorf("Failed to open header: %v", err)
}
defer file.Close()
buff := make([]byte, 512)
file.Read(buff)
file.Seek(0, 0)
contentType := http.DetectContentType(buff)
var sizeBuff bytes.Buffer
fileSize, err := sizeBuff.ReadFrom(file)
if err != nil {
return uploads, fmt.Errorf("Failed to read file: %v", err)
}
file.Seek(0, 0)
contentBuf := bytes.NewBuffer(nil)
if _, err := io.Copy(contentBuf, file); err != nil {
return uploads, fmt.Errorf("Failed to save file: %v", err)
}
log.Info().Int64("size", fileSize).Str("filename", headers.Filename).Str("content-type", contentType).Msg("Got an uploaded file")
u, err := uuid.NewUUID()
if err != nil {
return uploads, fmt.Errorf("Failed to create quick report photo uuid", err)
}
err = userfile.PublicImageFileContentWrite(u, file)
if err != nil {
return uploads, fmt.Errorf("Failed to write image file to disk: %v", err)
}
uploads = append(uploads, PhotoUpload{
Size: fileSize,
Filename: headers.Filename,
UUID: u,
})
}
}
return uploads, nil
}

165
public-report/pool.go Normal file
View file

@ -0,0 +1,165 @@
package publicreport
import (
"fmt"
"net/http"
"strconv"
"time"
"github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/htmlpage"
"github.com/aarondl/opt/omit"
"github.com/rs/zerolog/log"
"github.com/stephenafamo/bob/dialect/psql"
"github.com/stephenafamo/bob/dialect/psql/um"
)
type ContextPool struct {
MapboxToken string
}
type ContextPoolSubmitComplete struct {
ReportID string
}
var (
Pool = buildTemplate("pool", "base")
PoolSubmitComplete = buildTemplate("pool-submit-complete", "base")
)
func getPool(w http.ResponseWriter, r *http.Request) {
htmlpage.RenderOrError(
w,
Pool,
ContextPool{
MapboxToken: config.MapboxToken,
},
)
}
func getPoolSubmitComplete(w http.ResponseWriter, r *http.Request) {
report := r.URL.Query().Get("report")
htmlpage.RenderOrError(
w,
PoolSubmitComplete,
ContextPoolSubmitComplete{
ReportID: report,
},
)
}
func postPool(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
if err != nil {
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
return
}
access_comments := r.FormValue("access-comments")
access_gate := boolFromForm(r, "access-gate")
access_fence := boolFromForm(r, "access-fence")
access_locked := boolFromForm(r, "access-locked")
access_dog := boolFromForm(r, "access-dog")
access_other := boolFromForm(r, "access-other")
address := r.FormValue("address")
address_country := r.FormValue("address-country")
address_postcode := r.FormValue("address-postcode")
address_place := r.FormValue("address-place")
address_region := r.FormValue("address-region")
address_street := r.FormValue("address-street")
comments := r.FormValue("comments")
has_adult := boolFromForm(r, "has-adult")
has_larvae := boolFromForm(r, "has-larvae")
has_pupae := boolFromForm(r, "has-pupae")
map_zoom_str := r.FormValue("map-zoom")
owner_email := r.FormValue("owner-email")
owner_name := r.FormValue("owner-name")
owner_phone := r.FormValue("owner-phone")
reporter_email := r.FormValue("reporter-email")
reporter_name := r.FormValue("reporter-name")
reporter_phone := r.FormValue("reporter-phone")
subscribe := boolFromForm(r, "subscribe")
map_zoom, err := strconv.ParseFloat(map_zoom_str, 32)
if err != nil {
respondError(w, "Failed to parse zoom level", err, http.StatusBadRequest)
return
}
public_id, err := GenerateReportID()
if err != nil {
respondError(w, "Failed to create pool report public ID", err, http.StatusInternalServerError)
return
}
setter := models.PublicreportPoolSetter{
AccessComments: omit.From(access_comments),
AccessGate: omit.From(access_gate),
AccessFence: omit.From(access_fence),
AccessLocked: omit.From(access_locked),
AccessDog: omit.From(access_dog),
AccessOther: omit.From(access_other),
Address: omit.From(address),
AddressCountry: omit.From(address_country),
AddressPostCode: omit.From(address_postcode),
AddressPlace: omit.From(address_place),
AddressStreet: omit.From(address_street),
AddressRegion: omit.From(address_region),
Comments: omit.From(comments),
Created: omit.From(time.Now()),
//H3cell: add later
HasAdult: omit.From(has_adult),
HasLarvae: omit.From(has_larvae),
HasPupae: omit.From(has_pupae),
//Location: add later
MapZoom: omit.From(map_zoom),
OwnerEmail: omit.From(owner_email),
OwnerName: omit.From(owner_name),
OwnerPhone: omit.From(owner_phone),
PublicID: omit.From(public_id),
ReporterEmail: omit.From(reporter_email),
ReporterName: omit.From(reporter_name),
ReporterPhone: omit.From(reporter_phone),
Subscribe: omit.From(subscribe),
}
pool, err := models.PublicreportPools.Insert(&setter).One(r.Context(), db.PGInstance.BobDB)
if err != nil {
respondError(w, "Failed to create database record", err, http.StatusInternalServerError)
return
}
geospatial, err := geospatialFromForm(r)
if err != nil {
respondError(w, "Failed to handle geospatial data", err, http.StatusInternalServerError)
return
}
if geospatial.Populated {
_, err = psql.Update(
um.Table("publicreport.pool"),
um.SetCol("h3cell").ToArg(geospatial.Cell),
um.SetCol("location").To(geospatial.GeometryQuery),
um.Where(psql.Quote("id").EQ(psql.Arg(pool.ID))),
).Exec(r.Context(), db.PGInstance.BobDB)
if err != nil {
respondError(w, "Failed to insert publicreport.pool", err, http.StatusInternalServerError)
return
}
}
log.Info().Int32("id", pool.ID).Str("public_id", pool.PublicID).Msg("Created pool report")
photoSetters := make([]*models.PublicreportPoolPhotoSetter, 0)
uploads, err := extractPhotoUploads(r)
if err != nil {
respondError(w, "Failed to extract photo uploads", err, http.StatusInternalServerError)
return
}
for _, u := range uploads {
photoSetters = append(photoSetters, &models.PublicreportPoolPhotoSetter{
Filename: omit.From(u.Filename),
Size: omit.From(u.Size),
UUID: omit.From(u.UUID),
})
}
err = pool.InsertPoolPhotos(r.Context(), db.PGInstance.BobDB, photoSetters...)
if err != nil {
respondError(w, "Failed to create photo records", err, http.StatusInternalServerError)
return
}
http.Redirect(w, r, fmt.Sprintf("/pool-submit-complete?report=%s", public_id), http.StatusFound)
}

117
public-report/quick.go Normal file
View file

@ -0,0 +1,117 @@
package publicreport
import (
"fmt"
"net/http"
"strconv"
"time"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/h3utils"
"github.com/Gleipnir-Technology/nidus-sync/htmlpage"
"github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull"
"github.com/rs/zerolog/log"
"github.com/stephenafamo/bob/dialect/psql"
"github.com/stephenafamo/bob/dialect/psql/um"
)
type ContextQuick struct{}
type ContextQuickSubmitComplete struct {
ReportID string
}
var (
Quick = buildTemplate("quick", "base")
QuickSubmitComplete = buildTemplate("quick-submit-complete", "base")
)
func getQuick(w http.ResponseWriter, r *http.Request) {
htmlpage.RenderOrError(
w,
Quick,
ContextQuick{},
)
}
func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) {
report := r.URL.Query().Get("report")
htmlpage.RenderOrError(
w,
QuickSubmitComplete,
ContextQuickSubmitComplete{
ReportID: report,
},
)
}
func postQuick(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
if err != nil {
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
return
}
lat := r.FormValue("latitude")
lng := r.FormValue("longitude")
comments := r.FormValue("comments")
//photos := r.FormValue("photos")
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
}
u, err := GenerateReportID()
if err != nil {
respondError(w, "Failed to create quick report public ID", err, http.StatusInternalServerError)
return
}
c, err := h3utils.GetCell(longitude, latitude, 15)
setter := models.PublicreportQuickSetter{
Created: omit.From(time.Now()),
Comments: omit.From(comments),
//Location: omitnull.From(fmt.Sprintf("ST_GeometryFromText(Point(%s %s))", longitude, latitude)),
H3cell: omitnull.From(c.String()),
PublicID: omit.From(u),
ReporterEmail: omit.From(""),
ReporterPhone: omit.From(""),
}
quick, err := models.PublicreportQuicks.Insert(&setter).One(r.Context(), db.PGInstance.BobDB)
if err != nil {
respondError(w, "Failed to create database record", err, http.StatusInternalServerError)
return
}
_, err = psql.Update(
um.Table("publicreport.quick"),
um.SetCol("location").To(fmt.Sprintf("ST_GeometryFromText('Point(%f %f)')", longitude, latitude)),
um.Where(psql.Quote("id").EQ(psql.Arg(quick.ID))),
).Exec(r.Context(), db.PGInstance.BobDB)
if err != nil {
respondError(w, "Failed to insert publicreport", err, http.StatusInternalServerError)
return
}
log.Info().Float64("latitude", latitude).Float64("longitude", longitude).Msg("Got upload")
photoSetters := make([]*models.PublicreportQuickPhotoSetter, 0)
uploads, err := extractPhotoUploads(r)
if err != nil {
respondError(w, "Failed to extract photo uploads", err, http.StatusInternalServerError)
return
}
for _, u := range uploads {
photoSetters = append(photoSetters, &models.PublicreportQuickPhotoSetter{
Filename: omit.From(u.Filename),
Size: omit.From(u.Size),
UUID: omit.From(u.UUID),
})
}
err = quick.InsertQuickPhotos(r.Context(), db.PGInstance.BobDB, photoSetters...)
if err != nil {
respondError(w, "Failed to create photo records", err, http.StatusInternalServerError)
return
}
http.Redirect(w, r, fmt.Sprintf("/quick-submit-complete?report=%s", u), http.StatusFound)
}

28
public-report/routes.go Normal file
View file

@ -0,0 +1,28 @@
package publicreport
import (
"net/http"
"github.com/Gleipnir-Technology/nidus-sync/htmlpage"
"github.com/go-chi/chi/v5"
)
func Router() chi.Router {
r := chi.NewRouter()
r.Get("/", getRoot)
r.Get("/nuisance", getNuisance)
r.Post("/nuisance-submit", postNuisance)
r.Get("/nuisance-submit-complete", getNuisanceSubmitComplete)
r.Get("/pool", getPool)
r.Post("/pool-submit", postPool)
r.Get("/pool-submit-complete", getPoolSubmitComplete)
r.Get("/quick", getQuick)
r.Post("/quick-submit", postQuick)
r.Get("/quick-submit-complete", getQuickSubmitComplete)
r.Post("/register-notifications", postRegisterNotifications)
r.Get("/register-notifications-complete", getRegisterNotificationsComplete)
r.Get("/status", getStatus)
localFS := http.Dir("./static")
htmlpage.FileServer(r, "/static", localFS, EmbeddedStaticFS, "static")
return r
}

View file

@ -66,12 +66,16 @@ function displaySuggestions(suggestions) {
}
// Handle click on a suggestion
item.addEventListener('click', function() {
addressInput.value = suggestion.properties.name || suggestion.properties.full_address;
// Hide the suggestions container
setLocationInputs(suggestion);
suggestionsContainer.classList.add('d-none');
// Display the selected location details
displaySelectedLocation(suggestion);
setMapMarker(suggestion.geometry.coordinates);
const locationSelected = new CustomEvent("locationselected", {
detail: {
coordinates: suggestion.geometry.coordinates,
},
});
suggestionsContainer.dispatchEvent(locationSelected);
});
suggestionsContainer.appendChild(item);
@ -155,18 +159,45 @@ function onAddressInput() {
}, 300);
}
function setLocationInputs(suggestion) {
let address = document.getElementById('address');
let country = document.getElementById('address-country');
let latitude = document.getElementById('latitude');
let longitude = document.getElementById('longitude');
let latlngAccuracyType = document.getElementById('latlng-accuracy-type');
let postcode = document.getElementById('address-postcode');
let place = document.getElementById('address-place');
let region = document.getElementById('address-region');
let street = document.getElementById('address-street');
// Extract context data from properties
const props = suggestion.properties;
const context = props.context || {};
// Populate structured fields
address.value = props.full_address;
country.value = context.country.name;
latitude.value = props.coordinates.latitude;
longitude.value = props.coordinates.longitude;
latlngAccuracyType.value = props.coordinates.accuracy;
postcode.value = context.postcode.name;
place.value = context.place.name;
region.value = context.region.name;
street.value = context.country.name;
}
document.addEventListener('DOMContentLoaded', function() {
const addressInput = document.getElementById('addressInput');
const address = document.getElementById('address');
const suggestionsContainer = document.getElementById('suggestions');
const locationDetails = document.getElementById('locationDetails');
// Listen for input changes
addressInput.addEventListener('input', onAddressInput);
address.addEventListener('input', onAddressInput);
// Close suggestions when clicking outside
document.addEventListener('click', function(event) {
if (!addressInput.contains(event.target) && !suggestionsContainer.contains(event.target)) {
if (!address.contains(event.target) && !suggestionsContainer.contains(event.target)) {
suggestionsContainer.classList.add('d-none');
}
});

View file

@ -1,9 +1,18 @@
{{define "location-geocode"}}
<!-- Hidden fields for location data -->
<input type="hidden" id="address-country" name="address-country"/>
<input type="hidden" id="address-postcode" name="address-postcode"/>
<input type="hidden" id="address-place" name="address-place"/>
<input type="hidden" id="address-region" name="address-region"/>
<input type="hidden" id="address-street" name="address-street"/>
<input type="hidden" id="latitude" name="latitude"/>
<input type="hidden" id="longitude" name="longitude"/>
<input type="hidden" id="latlng-accuracy-type" name="latlng-accuracy-type"/>
<input type="hidden" id="latlng-accuracy-value" name="latlng-accuracy-value"/>
<div class="col-md-6">
<h2 class="mb-4">Address Search</h2>
<div class="mb-3 position-relative">
<label for="addressInput" class="form-label">Enter address</label>
<input type="text" class="form-control" id="addressInput"
<input type="text" class="form-control" id="address" name="address"
placeholder="Start typing an address (min 3 characters)">
<div id="suggestions" class="suggestions-container list-group d-none"></div>
</div>

View file

@ -1,144 +1,2 @@
{{define "map-header"}}
<script src='https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.css' rel='stylesheet' />
<script>
var map = null;
var markers = [];
function setMapMarker(coords) {
console.log("Setting map marker", coords);
map.jumpTo({
center: coords,
zoom: 14,
});
markers.forEach((marker) => marker.remove());
addMarker(coords);
}
function addMarker(coords) {
const marker = new mapboxgl.Marker({
color: "#FF0000",
draggable: true
}).setLngLat(coords).addTo(map);
marker.on('dragend', onMapMarkerDragEnd(marker));
markers.push(marker);
}
function onMapMarkerDragEnd(marker) {
return function() {
const lngLat = marker.getLngLat();
displaySelectedCoordinates(lngLat);
reverseGeocode(lngLat);
}
}
function displaySelectedCoordinates(lngLat) {
const gpsDisplay = document.getElementById("gps-display");
gpsDisplay.classList.remove('d-none');
longitude.textContent = lngLat.lng;
latitude.textContent = lngLat.lat;
}
async function reverseGeocode(lngLat) {
const MAPBOX_ACCESS_TOKEN = '{{.MapboxToken}}';
const url = `https://api.mapbox.com/search/geocode/v6/reverse?longitude=${lngLat.lng}&latitude=${lngLat.lat}&access_token=${MAPBOX_ACCESS_TOKEN}`
const response = await fetch(url);
const data = await response.json();
console.log("reverse geocoded to", data);
if (data.features.length == 0) {
console.warn("No results for reverse geocode");
return;
}
const match = data.features[0];
displaySelectedLocation(match);
}
function onLoadMap() {
const MAPBOX_ACCESS_TOKEN = '{{.MapboxToken}}';
console.log("Setting up the map...");
mapboxgl.accessToken = MAPBOX_ACCESS_TOKEN;
map = new mapboxgl.Map({
container: "map",
center: {
lat: 36.2,
lng: -119.2
},
style: 'mapbox://styles/mapbox/streets-v12', // style URL
zoom: 15,
});
map.addControl(new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true,
showUserHeading: true
}));
map.on("load", function() {
console.log("Map post-load...");
updateMapWithLocation(map);
console.log("Map post-load done.");
});
console.log("Map init done.");
}
function updateMapWithLocation(map) {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
// on success
function(position) {
console.log("Got location", position);
map.jumpTo({
center: {
lng: position.coords.longitude,
lat: position.coords.latitude,
},
zoom: 14,
});
addMarker([
position.coords.longitude,
position.coords.latitude,
]);
},
// on error
function(error) {
switch (error.code) {
case error.PERMISSION_DENIED:
console.log("permission denied");
break;
case error.POSITION_UNAVAILABLE:
console.log("location unavailable");
break;
case error.TIMEOUT:
console.log("request timed out");
break;
}
},
// options
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
} else {
console.log("location is not supported");
}
}
window.addEventListener("load", onLoadMap);
</script>
<style>
.map-container {
background-color: #e9ecef;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
height: 500px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
#map {
height: 500px;
width:100%;
margin-bottom: 10px;
}
#map img {
max-width: none;
min-width: 0px;
height: auto;
}
</style>
{{end}}

View file

@ -1,5 +1,4 @@
{{define "photo-upload"}}
<label for="photos" class="form-label fw-bold">Photos (Optional)</label>
<div class="photo-upload-area">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-camera mb-2" viewBox="0 0 16 16">
<path d="M15 12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.172a3 3 0 0 0 2.12-.879l.83-.828A1 1 0 0 1 6.827 3h2.344a1 1 0 0 1 .707.293l.828.828A3 3 0 0 0 12.828 5H14a1 1 0 0 1 1 1v6zM2 4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1.172a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 9.172 2H6.828a2 2 0 0 0-1.414.586l-.828.828A2 2 0 0 1 3.172 4H2z"/>

View file

@ -0,0 +1,115 @@
{{template "base.html" .}}
{{define "title"}}Nuisance Submission Complete{{end}}
{{define "extraheader"}}
<style>
</style>
<script>
</script>
{{end}}
{{define "content"}}
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-lg-7">
<!-- Confirmation Card -->
<div class="card shadow-sm border-info mb-4">
<div class="card-header bg-info text-white">
<h3 class="my-2">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-bell-fill me-2" viewBox="0 0 16 16">
<path d="M8 16a2 2 0 0 0 2-2H6a2 2 0 0 0 2 2zm.995-14.901a1 1 0 1 0-1.99 0A5.002 5.002 0 0 0 3 6c0 1.098-.5 6-2 7h14c-1.5-1-2-5.902-2-7 0-2.42-1.72-4.44-4.005-4.901z"/>
</svg>
Pool Report Complete
</h3>
</div>
<div class="card-body p-4 text-center">
<div class="mb-4">
<div class="display-1 text-info mb-3">
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" fill="currentColor" class="bi bi-check-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
</svg>
</div>
<h4 class="mb-3">Thank You!</h4>
<p class="lead">Your report has been successfully submitted.</p>
<div class="alert alert-secondary py-3 mt-3">
<strong>Report ID:</strong>
<span class="fs-5">{{.ReportID}}</span>
</div>
</div>
<hr class="my-4">
<!-- What to Expect Section -->
<div class="text-start mb-4">
<h5>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-info-circle me-2" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
</svg>
What to Expect
</h5>
<ul class="list-group list-group-flush">
<li class="list-group-item bg-transparent">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-envelope-check me-2 text-success" viewBox="0 0 16 16">
<path d="M2 2a2 2 0 0 0-2 2v8.01A2 2 0 0 0 2 14h5.5a.5.5 0 0 0 0-1H2a1 1 0 0 1-.966-.741l5.64-3.471L8 9.583l7-4.2V8.5a.5.5 0 0 0 1 0V4a2 2 0 0 0-2-2H2Zm3.708 6.208L1 11.105V5.383l4.708 2.825ZM1 4.217V4a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v.217l-7 4.2-7-4.2Z"/>
<path d="M16 12.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Zm-1.993-1.679a.5.5 0 0 0-.686.172l-1.17 1.95-.547-.547a.5.5 0 0 0-.708.708l.774.773a.75.75 0 0 0 1.174-.144l1.335-2.226a.5.5 0 0 0-.172-.686Z"/>
</svg>
A confirmation message has been sent to your contact information.
</li>
<li class="list-group-item bg-transparent">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard-check me-2 text-success" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M10.854 7.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 9.793l2.646-2.647a.5.5 0 0 1 .708 0z"/>
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
</svg>
You will receive updates when:
<ul class="mt-2">
<li>Your report is assigned to a specialist</li>
<li>A site visit is scheduled</li>
<li>Treatment or remediation is completed</li>
<li>The case is resolved</li>
</ul>
</li>
<li class="list-group-item bg-transparent">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search me-2 text-success" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>
You can check your report status anytime using your Report ID.
</li>
</ul>
</div>
<!-- Navigation Buttons -->
<div class="mt-4">
<a href="/check-report-status" class="btn btn-outline-primary me-2">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search me-1" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>
Check Report Status
</a>
<a href="/" class="btn btn-outline-secondary">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-house me-1" viewBox="0 0 16 16">
<path d="M8.707 1.5a1 1 0 0 0-1.414 0L.646 8.146a.5.5 0 0 0 .708.708L2 8.207V13.5A1.5 1.5 0 0 0 3.5 15h9a1.5 1.5 0 0 0 1.5-1.5V8.207l.646.647a.5.5 0 0 0 .708-.708L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.707 1.5ZM13 7.207V13.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V7.207l5-5 5 5Z"/>
</svg>
Return to Home
</a>
</div>
</div>
</div>
<!-- Optional: Additional Information Section -->
<div class="card shadow-sm">
<div class="card-body">
<h5 class="card-title">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-question-circle me-2" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"/>
</svg>
Need Help?
</h5>
<p>If you need to update your contact information or have questions about your report, please contact our Mosquito Control Unit at <strong>(123) 456-7890</strong> or <a href="mailto:mosquito@example.gov">mosquito@example.gov</a> and reference your Report ID.</p>
</div>
</div>
</div>
</div>
</div>
{{end}}

View file

@ -3,7 +3,8 @@
{{define "title"}}Green Pool{{end}}
{{define "extraheader"}}
{{template "location-geocode-header" .}}
{{template "map-header" .}}
<script src='https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.css' rel='stylesheet' />
{{template "photo-upload-header"}}
<style>
.district-logo {
@ -30,6 +31,48 @@
margin-bottom: 1rem;
padding-bottom: 0;
}
.map-container {
background-color: #e9ecef;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
height: 500px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
#map {
height: 500px;
width:100%;
margin-bottom: 10px;
}
#map img {
max-width: none;
min-width: 0px;
height: auto;
}
.photo-upload-area {
border: 2px dashed #ccc;
border-radius: 8px;
padding: 20px;
text-align: center;
margin-bottom: 20px;
background-color: #f9f9f9;
}
.photo-preview {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 15px;
}
.photo-preview img {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 4px;
}
.section-heading {
margin-bottom: 1.5rem;
display: flex;
@ -53,6 +96,232 @@
margin-top: 2rem;
}
</style>
<script>
function handlePhotoSelection() {
const photoInput = document.getElementById('photos');
const photoPreviewContainer = document.getElementById('photoPreviewContainer');
// Clear previous previews
photoPreviewContainer.innerHTML = '';
// Check if files were selected
if (photoInput.files && photoInput.files.length > 0) {
// Loop through selected files
Array.from(photoInput.files).forEach((file, index) => {
console.log("Handling", index, file);
if (!file.type.match('image.*')) {
console.log("Skipping non-image file", file.type);
return; // Skip non-image files
}
// Create preview container
const previewContainer = document.createElement('div');
previewContainer.className = 'position-relative m-1';
// Create image preview
const img = document.createElement('img');
img.className = 'img-thumbnail';
img.style.width = '100px';
img.style.height = '100px';
img.style.objectFit = 'cover';
// Read file and set preview
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target.result;
};
reader.readAsDataURL(file);
// Create remove button
const removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'btn btn-sm btn-danger position-absolute top-0 end-0';
removeBtn.innerHTML = '&times;';
removeBtn.style.fontSize = '10px';
removeBtn.style.padding = '0 5px';
// Handle remove button click
removeBtn.addEventListener('click', function() {
// Create a new FileList without this file
// Since FileList is immutable, we need to reset the input
// This is a bit tricky and requires recreating the input
previewContainer.remove();
// If this was the last image, clear the input entirely
if (photoPreviewContainer.children.length === 0) {
photoInput.value = '';
}
// Note: Unfortunately, selectively removing files from a FileList isn't straightforward
// In a real implementation, we might track selected files in an array and recreate the input
});
// Add elements to the preview container
previewContainer.appendChild(img);
previewContainer.appendChild(removeBtn);
photoPreviewContainer.appendChild(previewContainer);
});
}
}
document.addEventListener('DOMContentLoaded', function() {
// Elements
const photoInput = document.getElementById('photos');
// Handle photo selection
photoInput.addEventListener('change', handlePhotoSelection);
// Handle drag and drop
const photoDropArea = document.getElementById('photoDropArea');
photoDropArea.addEventListener('dragover', function(e) {
e.preventDefault();
photoDropArea.style.backgroundColor = '#e9ecef';
});
photoDropArea.addEventListener('dragleave', function() {
photoDropArea.style.backgroundColor = '#f8f9fa';
});
photoDropArea.addEventListener('drop', function(e) {
e.preventDefault();
photoDropArea.style.backgroundColor = '#f8f9fa';
if (e.dataTransfer.files.length) {
handleFiles(e.dataTransfer.files);
}
});
onLoadMap();
const suggestionsContainer = document.getElementById('suggestions');
suggestionsContainer.addEventListener("locationselected", (e) => {
setMapMarker(e.detail.coordinates);
});
});
var map = null;
var markers = [];
function setMapMarker(coords) {
console.log("Setting map marker", coords);
map.jumpTo({
center: coords,
zoom: 14,
});
markers.forEach((marker) => marker.remove());
addMarker(coords);
}
function addMarker(coords) {
const marker = new mapboxgl.Marker({
color: "#FF0000",
draggable: true
}).setLngLat(coords).addTo(map);
marker.on('dragend', onMapMarkerDragEnd(marker));
markers.push(marker);
}
function onMapMarkerDragEnd(marker) {
return function() {
const lngLat = marker.getLngLat();
//displaySelectedCoordinates(lngLat);
reverseGeocode(lngLat);
}
}
function displaySelectedCoordinates(lngLat) {
const gpsDisplay = document.getElementById("gps-display");
gpsDisplay.classList.remove('d-none');
longitude.textContent = lngLat.lng;
latitude.textContent = lngLat.lat;
}
async function reverseGeocode(lngLat) {
const MAPBOX_ACCESS_TOKEN = '{{.MapboxToken}}';
const url = `https://api.mapbox.com/search/geocode/v6/reverse?longitude=${lngLat.lng}&latitude=${lngLat.lat}&access_token=${MAPBOX_ACCESS_TOKEN}`
const response = await fetch(url);
const data = await response.json();
console.log("reverse geocoded to", data);
if (data.features.length == 0) {
console.warn("No results for reverse geocode");
return;
}
const match = data.features[0];
displaySelectedLocation(match);
setLocationInputs(match);
}
function onLoadMap() {
const MAPBOX_ACCESS_TOKEN = '{{.MapboxToken}}';
let mapZoom = document.getElementById('map-zoom');
console.log("Setting up the map...");
mapboxgl.accessToken = MAPBOX_ACCESS_TOKEN;
map = new mapboxgl.Map({
container: "map",
center: {
lat: 36.2,
lng: -119.2
},
style: 'mapbox://styles/mapbox/streets-v12', // style URL
zoom: 15,
});
map.addControl(new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true,
showUserHeading: true
}));
map.addControl(new mapboxgl.NavigationControl());
map.on("load", function() {
console.log("Map post-load...");
updateMapWithLocation(map);
console.log("Map post-load done.");
});
map.on("zoomend", function(e) {
mapZoom.value = e.target.getZoom();
});
console.log("Map init done.");
}
function updateMapWithLocation(map) {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
// on success
function(position) {
console.log("Got location", position);
map.jumpTo({
center: {
lng: position.coords.longitude,
lat: position.coords.latitude,
},
zoom: 14,
});
addMarker([
position.coords.longitude,
position.coords.latitude,
]);
reverseGeocode({
lat: position.coords.latitude,
lng: position.coords.longitude,
});
},
// on error
function(error) {
switch (error.code) {
case error.PERMISSION_DENIED:
console.log("permission denied");
break;
case error.POSITION_UNAVAILABLE:
console.log("location unavailable");
break;
case error.TIMEOUT:
console.log("request timed out");
break;
}
},
// options
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
} else {
console.log("location is not supported");
}
}
</script>
{{end}}
{{define "content"}}
<!-- Main Content -->
@ -77,7 +346,7 @@
</div>
<!-- Report Form -->
<form id="greenPoolForm">
<form id="greenPoolForm" action="/pool-submit" method="POST" enctype="multipart/form-data">
<!-- Photo Upload Section -->
<div class="form-section">
<div class="section-heading">
@ -86,8 +355,9 @@
<span class="optional-label">optional</span>
</div>
<p class="mb-3">Photos help us identify the severity of the issue and may contain location data that can help us find the source.</p>
{{template "photo-upload"}}
<div class="mb-4">
{{template "photo-upload"}}
</div>
</div>
<!-- Location Section -->
@ -119,6 +389,7 @@
<div class="map-container">
<div id="map"></div>
</div>
<input type="hidden" id="map-zoom" name="map-zoom"/>
</div>
<!-- Source Details Section -->
@ -132,8 +403,8 @@
<div class="row mb-4">
<div class="col-md-6">
<label for="duration" class="form-label">How long has this source been present?</label>
<select class="form-select" id="duration">
<option value="">I don't know</option>
<select class="form-select" id="duration" name="source-duration">
<option value="none">I don't know</option>
<option value="less-than-week">Less than a week</option>
<option value="1-2-weeks">1-2 weeks</option>
<option value="2-4-weeks">2-4 weeks</option>
@ -145,20 +416,20 @@
<div class="col-md-6">
<label class="form-label d-block">Have you observed any of the following? <a href="#" data-bs-toggle="modal" data-bs-target="#larvaeInfoModal"><i class="bi bi-question-circle small ms-1"></i></a></label>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="larvae">
<input class="form-check-input" type="checkbox" id="larvae" name="has-larvae">
<label class="form-check-label" for="larvae">
Larvae (wigglers) in water
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="pupae">
<input class="form-check-input" type="checkbox" id="pupae" name="has-pupae">
<label class="form-check-label" for="pupae">
Pupae (tumblers) in water
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="adultMosquitoes">
<label class="form-check-label" for="adultMosquitoes">
<input class="form-check-input" type="checkbox" id="adult" name="has-adult">
<label class="form-check-label" for="adult">
Adult mosquitoes near the source
</label>
</div>
@ -177,8 +448,8 @@
<div class="row mb-3">
<div class="col-md-12">
<label for="accessInfo" class="form-label">How can the source be accessed?</label>
<textarea class="form-control" id="accessInfo" rows="3" placeholder="Example: The pool is in the backyard, which can be accessed through a side gate on the right side of the house."></textarea>
<label for="access-comments" class="form-label">How can the source be accessed?</label>
<textarea class="form-control" id="access-comments" name="access-comments" rows="3" placeholder="Example: The pool is in the backyard, which can be accessed through a side gate on the right side of the house."></textarea>
</div>
</div>
@ -188,28 +459,28 @@
<div class="row">
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="gate">
<input class="form-check-input" type="checkbox" id="gate" name="access-gate">
<label class="form-check-label" for="gate">Gate</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="fence">
<input class="form-check-input" type="checkbox" id="fence" name="access-fence">
<label class="form-check-label" for="fence">Fence</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="lockedEntrance">
<label class="form-check-label" for="lockedEntrance">Locked entrance</label>
<input class="form-check-input" type="checkbox" id="locked" name="access-locked">
<label class="form-check-label" for="locked">Locked entrance</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="dogs">
<input class="form-check-input" type="checkbox" id="dogs" name="access-dog">
<label class="form-check-label" for="dogs">Dogs/pets</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="otherObstacle">
<label class="form-check-label" for="otherObstacle">Other obstacle</label>
<input class="form-check-input" type="checkbox" id="access-other" name="access-other">
<label class="form-check-label" for="access-other">Other obstacle</label>
</div>
</div>
</div>
@ -229,16 +500,16 @@
<h5 class="mb-3">Property Owner Information (if known)</h5>
<div class="row mb-4">
<div class="col-md-6 mb-3">
<label for="ownerName" class="form-label">Owner Name</label>
<input type="text" class="form-control" id="ownerName">
<label for="owner-name" class="form-label">Owner Name</label>
<input type="text" class="form-control" id="owner-name" name="owner-name">
</div>
<div class="col-md-6 mb-3">
<label for="ownerPhone" class="form-label">Owner Phone</label>
<input type="tel" class="form-control" id="ownerPhone">
<label for="owner-phone" class="form-label">Owner Phone</label>
<input type="tel" class="form-control" id="owner-phone" name="owner-phone">
</div>
<div class="col-md-12">
<label for="ownerEmail" class="form-label">Owner Email</label>
<input type="email" class="form-control" id="ownerEmail">
<label for="owner-email" class="form-label">Owner Email</label>
<input type="email" class="form-control" id="owner-email" name="owner-email">
</div>
</div>
@ -246,21 +517,21 @@
<h5 class="mb-3">Your Contact Information (for updates)</h5>
<div class="row mb-3">
<div class="col-md-6 mb-3">
<label for="reporterName" class="form-label">Your Name</label>
<input type="text" class="form-control" id="reporterName">
<label for="reporter-name" class="form-label">Your Name</label>
<input type="text" class="form-control" id="reporter-name" name="reporter-name">
</div>
<div class="col-md-6 mb-3">
<label for="reporterPhone" class="form-label">Your Phone</label>
<input type="tel" class="form-control" id="reporterPhone">
<label for="reporter-phone" class="form-label">Your Phone</label>
<input type="tel" class="form-control" id="reporter-phone" name="reporter-phone">
</div>
<div class="col-md-12 mb-3">
<label for="reporterEmail" class="form-label">Your Email</label>
<input type="email" class="form-control" id="reporterEmail">
<label for="reporter-email" class="form-label">Your Email</label>
<input type="email" class="form-control" id="reporter-email" name="reporter-email">
</div>
<div class="col-md-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="receiveUpdates" checked>
<label class="form-check-label" for="receiveUpdates">
<input class="form-check-input" type="checkbox" id="subscribe" name="subscribe" checked>
<label class="form-check-label" for="subscribe">
I would like to receive updates on this report
</label>
</div>
@ -279,8 +550,8 @@
<div class="row">
<div class="col-md-12">
<label for="additionalInfo" class="form-label">Additional Details</label>
<textarea class="form-control" id="additionalInfo" rows="4" placeholder="Example: The house appears to be vacant. There is algae growth in the pool. I've noticed increased mosquito activity in the evenings."></textarea>
<label for="comments" class="form-label">Additional Details</label>
<textarea class="form-control" id="comments" name="comments" rows="4" placeholder="Example: The house appears to be vacant. There is algae growth in the pool. I've noticed increased mosquito activity in the evenings."></textarea>
</div>
</div>
</div>