Add the ability to register for updates on quick reports
At this point it also appears that I'm correctly capturing the GPS location as both PostGIS data and as an H3 cell.
This commit is contained in:
parent
3c8436fe10
commit
b35c9496b6
13 changed files with 617 additions and 117 deletions
|
|
@ -10,8 +10,17 @@ var PublicreportQuickErrors = &publicreportQuickErrors{
|
||||||
columns: []string{"id"},
|
columns: []string{"id"},
|
||||||
s: "quick_pkey",
|
s: "quick_pkey",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ErrUniqueQuickPublicIdKey: &UniqueConstraintError{
|
||||||
|
schema: "publicreport",
|
||||||
|
table: "quick",
|
||||||
|
columns: []string{"public_id"},
|
||||||
|
s: "quick_public_id_key",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type publicreportQuickErrors struct {
|
type publicreportQuickErrors struct {
|
||||||
ErrUniqueQuickPkey *UniqueConstraintError
|
ErrUniqueQuickPkey *UniqueConstraintError
|
||||||
|
|
||||||
|
ErrUniqueQuickPublicIdKey *UniqueConstraintError
|
||||||
}
|
}
|
||||||
|
|
|
||||||
108
db/dberrors/publicreport.quick.bob_test.go
Normal file
108
db/dberrors/publicreport.quick.bob_test.go
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
factory "github.com/Gleipnir-Technology/nidus-sync/db/factory"
|
||||||
|
models "github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||||
|
"github.com/stephenafamo/bob"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPublicreportQuickUniqueConstraintErrors(t *testing.T) {
|
||||||
|
if testDB == nil {
|
||||||
|
t.Skip("No database connection provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
f := factory.New()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expectedErr *UniqueConstraintError
|
||||||
|
conflictMods func(context.Context, *testing.T, bob.Executor, *models.PublicreportQuick) factory.PublicreportQuickModSlice
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ErrUniqueQuickPkey",
|
||||||
|
expectedErr: PublicreportQuickErrors.ErrUniqueQuickPkey,
|
||||||
|
conflictMods: func(ctx context.Context, t *testing.T, exec bob.Executor, obj *models.PublicreportQuick) factory.PublicreportQuickModSlice {
|
||||||
|
shouldUpdate := false
|
||||||
|
updateMods := make(factory.PublicreportQuickModSlice, 0, 1)
|
||||||
|
|
||||||
|
if shouldUpdate {
|
||||||
|
if err := obj.Update(ctx, exec, f.NewPublicreportQuickWithContext(ctx, updateMods...).BuildSetter()); err != nil {
|
||||||
|
t.Fatalf("Error updating object: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return factory.PublicreportQuickModSlice{
|
||||||
|
factory.PublicreportQuickMods.ID(obj.ID),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ErrUniqueQuickPublicIdKey",
|
||||||
|
expectedErr: PublicreportQuickErrors.ErrUniqueQuickPublicIdKey,
|
||||||
|
conflictMods: func(ctx context.Context, t *testing.T, exec bob.Executor, obj *models.PublicreportQuick) factory.PublicreportQuickModSlice {
|
||||||
|
shouldUpdate := false
|
||||||
|
updateMods := make(factory.PublicreportQuickModSlice, 0, 1)
|
||||||
|
|
||||||
|
if shouldUpdate {
|
||||||
|
if err := obj.Update(ctx, exec, f.NewPublicreportQuickWithContext(ctx, updateMods...).BuildSetter()); err != nil {
|
||||||
|
t.Fatalf("Error updating object: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return factory.PublicreportQuickModSlice{
|
||||||
|
factory.PublicreportQuickMods.PublicID(obj.PublicID),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
t.Cleanup(cancel)
|
||||||
|
|
||||||
|
tx, err := testDB.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't start database transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := tx.Rollback(ctx); err != nil {
|
||||||
|
t.Fatalf("Error rolling back transaction: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var exec bob.Executor = tx
|
||||||
|
|
||||||
|
obj, err := f.NewPublicreportQuickWithContext(ctx, factory.PublicreportQuickMods.WithParentsCascading()).Create(ctx, exec)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj2, err := f.NewPublicreportQuickWithContext(ctx).Create(ctx, exec)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = obj2.Update(ctx, exec, f.NewPublicreportQuickWithContext(ctx, tt.conflictMods(ctx, t, exec, obj)...).BuildSetter())
|
||||||
|
if !errors.Is(ErrUniqueConstraint, err) {
|
||||||
|
t.Fatalf("Expected: %s, Got: %v", tt.name, err)
|
||||||
|
}
|
||||||
|
if !errors.Is(tt.expectedErr, err) {
|
||||||
|
t.Fatalf("Expected: %s, Got: %v", tt.expectedErr.Error(), err)
|
||||||
|
}
|
||||||
|
if !ErrUniqueConstraint.Is(err) {
|
||||||
|
t.Fatalf("Expected: %s, Got: %v", tt.name, err)
|
||||||
|
}
|
||||||
|
if !tt.expectedErr.Is(err) {
|
||||||
|
t.Fatalf("Expected: %s, Got: %v", tt.expectedErr.Error(), err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -60,9 +60,27 @@ var PublicreportQuicks = Table[
|
||||||
Generated: false,
|
Generated: false,
|
||||||
AutoIncr: false,
|
AutoIncr: false,
|
||||||
},
|
},
|
||||||
UUID: column{
|
PublicID: column{
|
||||||
Name: "uuid",
|
Name: "public_id",
|
||||||
DBType: "uuid",
|
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,
|
||||||
|
},
|
||||||
|
ReporterPhone: column{
|
||||||
|
Name: "reporter_phone",
|
||||||
|
DBType: "text",
|
||||||
Default: "",
|
Default: "",
|
||||||
Comment: "",
|
Comment: "",
|
||||||
Nullable: false,
|
Nullable: false,
|
||||||
|
|
@ -88,6 +106,23 @@ var PublicreportQuicks = Table[
|
||||||
Where: "",
|
Where: "",
|
||||||
Include: []string{},
|
Include: []string{},
|
||||||
},
|
},
|
||||||
|
QuickPublicIDKey: index{
|
||||||
|
Type: "btree",
|
||||||
|
Name: "quick_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{
|
PrimaryKey: &constraint{
|
||||||
Name: "quick_pkey",
|
Name: "quick_pkey",
|
||||||
|
|
@ -95,31 +130,42 @@ var PublicreportQuicks = Table[
|
||||||
Comment: "",
|
Comment: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Uniques: publicreportQuickUniques{
|
||||||
|
QuickPublicIDKey: constraint{
|
||||||
|
Name: "quick_public_id_key",
|
||||||
|
Columns: []string{"public_id"},
|
||||||
|
Comment: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
Comment: "",
|
Comment: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
type publicreportQuickColumns struct {
|
type publicreportQuickColumns struct {
|
||||||
ID column
|
ID column
|
||||||
Created column
|
Created column
|
||||||
Comments column
|
Comments column
|
||||||
Location column
|
Location column
|
||||||
H3cell column
|
H3cell column
|
||||||
UUID column
|
PublicID column
|
||||||
|
ReporterEmail column
|
||||||
|
ReporterPhone column
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c publicreportQuickColumns) AsSlice() []column {
|
func (c publicreportQuickColumns) AsSlice() []column {
|
||||||
return []column{
|
return []column{
|
||||||
c.ID, c.Created, c.Comments, c.Location, c.H3cell, c.UUID,
|
c.ID, c.Created, c.Comments, c.Location, c.H3cell, c.PublicID, c.ReporterEmail, c.ReporterPhone,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type publicreportQuickIndexes struct {
|
type publicreportQuickIndexes struct {
|
||||||
QuickPkey index
|
QuickPkey index
|
||||||
|
QuickPublicIDKey index
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i publicreportQuickIndexes) AsSlice() []index {
|
func (i publicreportQuickIndexes) AsSlice() []index {
|
||||||
return []index{
|
return []index{
|
||||||
i.QuickPkey,
|
i.QuickPkey, i.QuickPublicIDKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,10 +175,14 @@ func (f publicreportQuickForeignKeys) AsSlice() []foreignKey {
|
||||||
return []foreignKey{}
|
return []foreignKey{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type publicreportQuickUniques struct{}
|
type publicreportQuickUniques struct {
|
||||||
|
QuickPublicIDKey constraint
|
||||||
|
}
|
||||||
|
|
||||||
func (u publicreportQuickUniques) AsSlice() []constraint {
|
func (u publicreportQuickUniques) AsSlice() []constraint {
|
||||||
return []constraint{}
|
return []constraint{
|
||||||
|
u.QuickPublicIDKey,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type publicreportQuickChecks struct{}
|
type publicreportQuickChecks struct{}
|
||||||
|
|
|
||||||
|
|
@ -2396,7 +2396,9 @@ func (f *Factory) FromExistingPublicreportQuick(m *models.PublicreportQuick) *Pu
|
||||||
o.Comments = func() string { return m.Comments }
|
o.Comments = func() string { return m.Comments }
|
||||||
o.Location = func() null.Val[string] { return m.Location }
|
o.Location = func() null.Val[string] { return m.Location }
|
||||||
o.H3cell = func() null.Val[string] { return m.H3cell }
|
o.H3cell = func() null.Val[string] { return m.H3cell }
|
||||||
o.UUID = func() uuid.UUID { return m.UUID }
|
o.PublicID = func() string { return m.PublicID }
|
||||||
|
o.ReporterEmail = func() string { return m.ReporterEmail }
|
||||||
|
o.ReporterPhone = func() string { return m.ReporterPhone }
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if len(m.R.QuickPhotos) > 0 {
|
if len(m.R.QuickPhotos) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/aarondl/opt/null"
|
"github.com/aarondl/opt/null"
|
||||||
"github.com/aarondl/opt/omit"
|
"github.com/aarondl/opt/omit"
|
||||||
"github.com/aarondl/opt/omitnull"
|
"github.com/aarondl/opt/omitnull"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/jaswdr/faker/v2"
|
"github.com/jaswdr/faker/v2"
|
||||||
"github.com/stephenafamo/bob"
|
"github.com/stephenafamo/bob"
|
||||||
)
|
)
|
||||||
|
|
@ -38,12 +37,14 @@ func (mods PublicreportQuickModSlice) Apply(ctx context.Context, n *Publicreport
|
||||||
// PublicreportQuickTemplate is an object representing the database table.
|
// PublicreportQuickTemplate is an object representing the database table.
|
||||||
// all columns are optional and should be set by mods
|
// all columns are optional and should be set by mods
|
||||||
type PublicreportQuickTemplate struct {
|
type PublicreportQuickTemplate struct {
|
||||||
ID func() int32
|
ID func() int32
|
||||||
Created func() time.Time
|
Created func() time.Time
|
||||||
Comments func() string
|
Comments func() string
|
||||||
Location func() null.Val[string]
|
Location func() null.Val[string]
|
||||||
H3cell func() null.Val[string]
|
H3cell func() null.Val[string]
|
||||||
UUID func() uuid.UUID
|
PublicID func() string
|
||||||
|
ReporterEmail func() string
|
||||||
|
ReporterPhone func() string
|
||||||
|
|
||||||
r publicreportQuickR
|
r publicreportQuickR
|
||||||
f *Factory
|
f *Factory
|
||||||
|
|
@ -109,9 +110,17 @@ func (o PublicreportQuickTemplate) BuildSetter() *models.PublicreportQuickSetter
|
||||||
val := o.H3cell()
|
val := o.H3cell()
|
||||||
m.H3cell = omitnull.FromNull(val)
|
m.H3cell = omitnull.FromNull(val)
|
||||||
}
|
}
|
||||||
if o.UUID != nil {
|
if o.PublicID != nil {
|
||||||
val := o.UUID()
|
val := o.PublicID()
|
||||||
m.UUID = omit.From(val)
|
m.PublicID = omit.From(val)
|
||||||
|
}
|
||||||
|
if o.ReporterEmail != nil {
|
||||||
|
val := o.ReporterEmail()
|
||||||
|
m.ReporterEmail = omit.From(val)
|
||||||
|
}
|
||||||
|
if o.ReporterPhone != nil {
|
||||||
|
val := o.ReporterPhone()
|
||||||
|
m.ReporterPhone = omit.From(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|
@ -150,8 +159,14 @@ func (o PublicreportQuickTemplate) Build() *models.PublicreportQuick {
|
||||||
if o.H3cell != nil {
|
if o.H3cell != nil {
|
||||||
m.H3cell = o.H3cell()
|
m.H3cell = o.H3cell()
|
||||||
}
|
}
|
||||||
if o.UUID != nil {
|
if o.PublicID != nil {
|
||||||
m.UUID = o.UUID()
|
m.PublicID = o.PublicID()
|
||||||
|
}
|
||||||
|
if o.ReporterEmail != nil {
|
||||||
|
m.ReporterEmail = o.ReporterEmail()
|
||||||
|
}
|
||||||
|
if o.ReporterPhone != nil {
|
||||||
|
m.ReporterPhone = o.ReporterPhone()
|
||||||
}
|
}
|
||||||
|
|
||||||
o.setModelRels(m)
|
o.setModelRels(m)
|
||||||
|
|
@ -181,9 +196,17 @@ func ensureCreatablePublicreportQuick(m *models.PublicreportQuickSetter) {
|
||||||
val := random_string(nil)
|
val := random_string(nil)
|
||||||
m.Comments = omit.From(val)
|
m.Comments = omit.From(val)
|
||||||
}
|
}
|
||||||
if !(m.UUID.IsValue()) {
|
if !(m.PublicID.IsValue()) {
|
||||||
val := random_uuid_UUID(nil)
|
val := random_string(nil)
|
||||||
m.UUID = omit.From(val)
|
m.PublicID = omit.From(val)
|
||||||
|
}
|
||||||
|
if !(m.ReporterEmail.IsValue()) {
|
||||||
|
val := random_string(nil)
|
||||||
|
m.ReporterEmail = omit.From(val)
|
||||||
|
}
|
||||||
|
if !(m.ReporterPhone.IsValue()) {
|
||||||
|
val := random_string(nil)
|
||||||
|
m.ReporterPhone = omit.From(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,7 +333,9 @@ func (m publicreportQuickMods) RandomizeAllColumns(f *faker.Faker) PublicreportQ
|
||||||
PublicreportQuickMods.RandomComments(f),
|
PublicreportQuickMods.RandomComments(f),
|
||||||
PublicreportQuickMods.RandomLocation(f),
|
PublicreportQuickMods.RandomLocation(f),
|
||||||
PublicreportQuickMods.RandomH3cell(f),
|
PublicreportQuickMods.RandomH3cell(f),
|
||||||
PublicreportQuickMods.RandomUUID(f),
|
PublicreportQuickMods.RandomPublicID(f),
|
||||||
|
PublicreportQuickMods.RandomReporterEmail(f),
|
||||||
|
PublicreportQuickMods.RandomReporterPhone(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -514,32 +539,94 @@ func (m publicreportQuickMods) RandomH3cellNotNull(f *faker.Faker) PublicreportQ
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the model columns to this value
|
// Set the model columns to this value
|
||||||
func (m publicreportQuickMods) UUID(val uuid.UUID) PublicreportQuickMod {
|
func (m publicreportQuickMods) PublicID(val string) PublicreportQuickMod {
|
||||||
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
o.UUID = func() uuid.UUID { return val }
|
o.PublicID = func() string { return val }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the Column from the function
|
// Set the Column from the function
|
||||||
func (m publicreportQuickMods) UUIDFunc(f func() uuid.UUID) PublicreportQuickMod {
|
func (m publicreportQuickMods) PublicIDFunc(f func() string) PublicreportQuickMod {
|
||||||
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
o.UUID = f
|
o.PublicID = f
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear any values for the column
|
// Clear any values for the column
|
||||||
func (m publicreportQuickMods) UnsetUUID() PublicreportQuickMod {
|
func (m publicreportQuickMods) UnsetPublicID() PublicreportQuickMod {
|
||||||
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
o.UUID = nil
|
o.PublicID = nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates a random value for the column using the given faker
|
// Generates a random value for the column using the given faker
|
||||||
// if faker is nil, a default faker is used
|
// if faker is nil, a default faker is used
|
||||||
func (m publicreportQuickMods) RandomUUID(f *faker.Faker) PublicreportQuickMod {
|
func (m publicreportQuickMods) RandomPublicID(f *faker.Faker) PublicreportQuickMod {
|
||||||
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
o.UUID = func() uuid.UUID {
|
o.PublicID = func() string {
|
||||||
return random_uuid_UUID(f)
|
return random_string(f)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the model columns to this value
|
||||||
|
func (m publicreportQuickMods) ReporterEmail(val string) PublicreportQuickMod {
|
||||||
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
|
o.ReporterEmail = func() string { return val }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Column from the function
|
||||||
|
func (m publicreportQuickMods) ReporterEmailFunc(f func() string) PublicreportQuickMod {
|
||||||
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
|
o.ReporterEmail = f
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear any values for the column
|
||||||
|
func (m publicreportQuickMods) UnsetReporterEmail() PublicreportQuickMod {
|
||||||
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
|
o.ReporterEmail = nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a random value for the column using the given faker
|
||||||
|
// if faker is nil, a default faker is used
|
||||||
|
func (m publicreportQuickMods) RandomReporterEmail(f *faker.Faker) PublicreportQuickMod {
|
||||||
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
|
o.ReporterEmail = func() string {
|
||||||
|
return random_string(f)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the model columns to this value
|
||||||
|
func (m publicreportQuickMods) ReporterPhone(val string) PublicreportQuickMod {
|
||||||
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
|
o.ReporterPhone = func() string { return val }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Column from the function
|
||||||
|
func (m publicreportQuickMods) ReporterPhoneFunc(f func() string) PublicreportQuickMod {
|
||||||
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
|
o.ReporterPhone = f
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear any values for the column
|
||||||
|
func (m publicreportQuickMods) UnsetReporterPhone() PublicreportQuickMod {
|
||||||
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
|
o.ReporterPhone = nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a random value for the column using the given faker
|
||||||
|
// if faker is nil, a default faker is used
|
||||||
|
func (m publicreportQuickMods) RandomReporterPhone(f *faker.Faker) PublicreportQuickMod {
|
||||||
|
return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) {
|
||||||
|
o.ReporterPhone = func() string {
|
||||||
|
return random_string(f)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ CREATE TABLE publicreport.quick (
|
||||||
comments TEXT NOT NULL,
|
comments TEXT NOT NULL,
|
||||||
location GEOGRAPHY,
|
location GEOGRAPHY,
|
||||||
h3cell h3index,
|
h3cell h3index,
|
||||||
uuid UUID NOT NULL
|
public_id TEXT NOT NULL UNIQUE,
|
||||||
|
reporter_email TEXT NOT NULL,
|
||||||
|
reporter_phone TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE publicreport.quick_photo (
|
CREATE TABLE publicreport.quick_photo (
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/aarondl/opt/null"
|
"github.com/aarondl/opt/null"
|
||||||
"github.com/aarondl/opt/omit"
|
"github.com/aarondl/opt/omit"
|
||||||
"github.com/aarondl/opt/omitnull"
|
"github.com/aarondl/opt/omitnull"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stephenafamo/bob"
|
"github.com/stephenafamo/bob"
|
||||||
"github.com/stephenafamo/bob/dialect/psql"
|
"github.com/stephenafamo/bob/dialect/psql"
|
||||||
"github.com/stephenafamo/bob/dialect/psql/dialect"
|
"github.com/stephenafamo/bob/dialect/psql/dialect"
|
||||||
|
|
@ -27,12 +26,14 @@ import (
|
||||||
|
|
||||||
// PublicreportQuick is an object representing the database table.
|
// PublicreportQuick is an object representing the database table.
|
||||||
type PublicreportQuick struct {
|
type PublicreportQuick struct {
|
||||||
ID int32 `db:"id,pk" `
|
ID int32 `db:"id,pk" `
|
||||||
Created time.Time `db:"created" `
|
Created time.Time `db:"created" `
|
||||||
Comments string `db:"comments" `
|
Comments string `db:"comments" `
|
||||||
Location null.Val[string] `db:"location" `
|
Location null.Val[string] `db:"location" `
|
||||||
H3cell null.Val[string] `db:"h3cell" `
|
H3cell null.Val[string] `db:"h3cell" `
|
||||||
UUID uuid.UUID `db:"uuid" `
|
PublicID string `db:"public_id" `
|
||||||
|
ReporterEmail string `db:"reporter_email" `
|
||||||
|
ReporterPhone string `db:"reporter_phone" `
|
||||||
|
|
||||||
R publicreportQuickR `db:"-" `
|
R publicreportQuickR `db:"-" `
|
||||||
}
|
}
|
||||||
|
|
@ -55,27 +56,31 @@ type publicreportQuickR struct {
|
||||||
func buildPublicreportQuickColumns(alias string) publicreportQuickColumns {
|
func buildPublicreportQuickColumns(alias string) publicreportQuickColumns {
|
||||||
return publicreportQuickColumns{
|
return publicreportQuickColumns{
|
||||||
ColumnsExpr: expr.NewColumnsExpr(
|
ColumnsExpr: expr.NewColumnsExpr(
|
||||||
"id", "created", "comments", "location", "h3cell", "uuid",
|
"id", "created", "comments", "location", "h3cell", "public_id", "reporter_email", "reporter_phone",
|
||||||
).WithParent("publicreport.quick"),
|
).WithParent("publicreport.quick"),
|
||||||
tableAlias: alias,
|
tableAlias: alias,
|
||||||
ID: psql.Quote(alias, "id"),
|
ID: psql.Quote(alias, "id"),
|
||||||
Created: psql.Quote(alias, "created"),
|
Created: psql.Quote(alias, "created"),
|
||||||
Comments: psql.Quote(alias, "comments"),
|
Comments: psql.Quote(alias, "comments"),
|
||||||
Location: psql.Quote(alias, "location"),
|
Location: psql.Quote(alias, "location"),
|
||||||
H3cell: psql.Quote(alias, "h3cell"),
|
H3cell: psql.Quote(alias, "h3cell"),
|
||||||
UUID: psql.Quote(alias, "uuid"),
|
PublicID: psql.Quote(alias, "public_id"),
|
||||||
|
ReporterEmail: psql.Quote(alias, "reporter_email"),
|
||||||
|
ReporterPhone: psql.Quote(alias, "reporter_phone"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type publicreportQuickColumns struct {
|
type publicreportQuickColumns struct {
|
||||||
expr.ColumnsExpr
|
expr.ColumnsExpr
|
||||||
tableAlias string
|
tableAlias string
|
||||||
ID psql.Expression
|
ID psql.Expression
|
||||||
Created psql.Expression
|
Created psql.Expression
|
||||||
Comments psql.Expression
|
Comments psql.Expression
|
||||||
Location psql.Expression
|
Location psql.Expression
|
||||||
H3cell psql.Expression
|
H3cell psql.Expression
|
||||||
UUID psql.Expression
|
PublicID psql.Expression
|
||||||
|
ReporterEmail psql.Expression
|
||||||
|
ReporterPhone psql.Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c publicreportQuickColumns) Alias() string {
|
func (c publicreportQuickColumns) Alias() string {
|
||||||
|
|
@ -90,16 +95,18 @@ func (publicreportQuickColumns) AliasedAs(alias string) publicreportQuickColumns
|
||||||
// All values are optional, and do not have to be set
|
// All values are optional, and do not have to be set
|
||||||
// Generated columns are not included
|
// Generated columns are not included
|
||||||
type PublicreportQuickSetter struct {
|
type PublicreportQuickSetter struct {
|
||||||
ID omit.Val[int32] `db:"id,pk" `
|
ID omit.Val[int32] `db:"id,pk" `
|
||||||
Created omit.Val[time.Time] `db:"created" `
|
Created omit.Val[time.Time] `db:"created" `
|
||||||
Comments omit.Val[string] `db:"comments" `
|
Comments omit.Val[string] `db:"comments" `
|
||||||
Location omitnull.Val[string] `db:"location" `
|
Location omitnull.Val[string] `db:"location" `
|
||||||
H3cell omitnull.Val[string] `db:"h3cell" `
|
H3cell omitnull.Val[string] `db:"h3cell" `
|
||||||
UUID omit.Val[uuid.UUID] `db:"uuid" `
|
PublicID omit.Val[string] `db:"public_id" `
|
||||||
|
ReporterEmail omit.Val[string] `db:"reporter_email" `
|
||||||
|
ReporterPhone omit.Val[string] `db:"reporter_phone" `
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s PublicreportQuickSetter) SetColumns() []string {
|
func (s PublicreportQuickSetter) SetColumns() []string {
|
||||||
vals := make([]string, 0, 6)
|
vals := make([]string, 0, 8)
|
||||||
if s.ID.IsValue() {
|
if s.ID.IsValue() {
|
||||||
vals = append(vals, "id")
|
vals = append(vals, "id")
|
||||||
}
|
}
|
||||||
|
|
@ -115,8 +122,14 @@ func (s PublicreportQuickSetter) SetColumns() []string {
|
||||||
if !s.H3cell.IsUnset() {
|
if !s.H3cell.IsUnset() {
|
||||||
vals = append(vals, "h3cell")
|
vals = append(vals, "h3cell")
|
||||||
}
|
}
|
||||||
if s.UUID.IsValue() {
|
if s.PublicID.IsValue() {
|
||||||
vals = append(vals, "uuid")
|
vals = append(vals, "public_id")
|
||||||
|
}
|
||||||
|
if s.ReporterEmail.IsValue() {
|
||||||
|
vals = append(vals, "reporter_email")
|
||||||
|
}
|
||||||
|
if s.ReporterPhone.IsValue() {
|
||||||
|
vals = append(vals, "reporter_phone")
|
||||||
}
|
}
|
||||||
return vals
|
return vals
|
||||||
}
|
}
|
||||||
|
|
@ -137,8 +150,14 @@ func (s PublicreportQuickSetter) Overwrite(t *PublicreportQuick) {
|
||||||
if !s.H3cell.IsUnset() {
|
if !s.H3cell.IsUnset() {
|
||||||
t.H3cell = s.H3cell.MustGetNull()
|
t.H3cell = s.H3cell.MustGetNull()
|
||||||
}
|
}
|
||||||
if s.UUID.IsValue() {
|
if s.PublicID.IsValue() {
|
||||||
t.UUID = s.UUID.MustGet()
|
t.PublicID = s.PublicID.MustGet()
|
||||||
|
}
|
||||||
|
if s.ReporterEmail.IsValue() {
|
||||||
|
t.ReporterEmail = s.ReporterEmail.MustGet()
|
||||||
|
}
|
||||||
|
if s.ReporterPhone.IsValue() {
|
||||||
|
t.ReporterPhone = s.ReporterPhone.MustGet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,7 +167,7 @@ func (s *PublicreportQuickSetter) Apply(q *dialect.InsertQuery) {
|
||||||
})
|
})
|
||||||
|
|
||||||
q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
|
q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
|
||||||
vals := make([]bob.Expression, 6)
|
vals := make([]bob.Expression, 8)
|
||||||
if s.ID.IsValue() {
|
if s.ID.IsValue() {
|
||||||
vals[0] = psql.Arg(s.ID.MustGet())
|
vals[0] = psql.Arg(s.ID.MustGet())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -179,12 +198,24 @@ func (s *PublicreportQuickSetter) Apply(q *dialect.InsertQuery) {
|
||||||
vals[4] = psql.Raw("DEFAULT")
|
vals[4] = psql.Raw("DEFAULT")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.UUID.IsValue() {
|
if s.PublicID.IsValue() {
|
||||||
vals[5] = psql.Arg(s.UUID.MustGet())
|
vals[5] = psql.Arg(s.PublicID.MustGet())
|
||||||
} else {
|
} else {
|
||||||
vals[5] = psql.Raw("DEFAULT")
|
vals[5] = psql.Raw("DEFAULT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.ReporterEmail.IsValue() {
|
||||||
|
vals[6] = psql.Arg(s.ReporterEmail.MustGet())
|
||||||
|
} else {
|
||||||
|
vals[6] = psql.Raw("DEFAULT")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.ReporterPhone.IsValue() {
|
||||||
|
vals[7] = psql.Arg(s.ReporterPhone.MustGet())
|
||||||
|
} else {
|
||||||
|
vals[7] = psql.Raw("DEFAULT")
|
||||||
|
}
|
||||||
|
|
||||||
return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "")
|
return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "")
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +225,7 @@ func (s PublicreportQuickSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s PublicreportQuickSetter) Expressions(prefix ...string) []bob.Expression {
|
func (s PublicreportQuickSetter) Expressions(prefix ...string) []bob.Expression {
|
||||||
exprs := make([]bob.Expression, 0, 6)
|
exprs := make([]bob.Expression, 0, 8)
|
||||||
|
|
||||||
if s.ID.IsValue() {
|
if s.ID.IsValue() {
|
||||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||||
|
|
@ -231,10 +262,24 @@ func (s PublicreportQuickSetter) Expressions(prefix ...string) []bob.Expression
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.UUID.IsValue() {
|
if s.PublicID.IsValue() {
|
||||||
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||||
psql.Quote(append(prefix, "uuid")...),
|
psql.Quote(append(prefix, "public_id")...),
|
||||||
psql.Arg(s.UUID),
|
psql.Arg(s.PublicID),
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.ReporterEmail.IsValue() {
|
||||||
|
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||||
|
psql.Quote(append(prefix, "reporter_email")...),
|
||||||
|
psql.Arg(s.ReporterEmail),
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.ReporterPhone.IsValue() {
|
||||||
|
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
|
||||||
|
psql.Quote(append(prefix, "reporter_phone")...),
|
||||||
|
psql.Arg(s.ReporterPhone),
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -557,12 +602,14 @@ func (publicreportQuick0 *PublicreportQuick) AttachQuickPhotos(ctx context.Conte
|
||||||
}
|
}
|
||||||
|
|
||||||
type publicreportQuickWhere[Q psql.Filterable] struct {
|
type publicreportQuickWhere[Q psql.Filterable] struct {
|
||||||
ID psql.WhereMod[Q, int32]
|
ID psql.WhereMod[Q, int32]
|
||||||
Created psql.WhereMod[Q, time.Time]
|
Created psql.WhereMod[Q, time.Time]
|
||||||
Comments psql.WhereMod[Q, string]
|
Comments psql.WhereMod[Q, string]
|
||||||
Location psql.WhereNullMod[Q, string]
|
Location psql.WhereNullMod[Q, string]
|
||||||
H3cell psql.WhereNullMod[Q, string]
|
H3cell psql.WhereNullMod[Q, string]
|
||||||
UUID psql.WhereMod[Q, uuid.UUID]
|
PublicID psql.WhereMod[Q, string]
|
||||||
|
ReporterEmail psql.WhereMod[Q, string]
|
||||||
|
ReporterPhone psql.WhereMod[Q, string]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (publicreportQuickWhere[Q]) AliasedAs(alias string) publicreportQuickWhere[Q] {
|
func (publicreportQuickWhere[Q]) AliasedAs(alias string) publicreportQuickWhere[Q] {
|
||||||
|
|
@ -571,12 +618,14 @@ func (publicreportQuickWhere[Q]) AliasedAs(alias string) publicreportQuickWhere[
|
||||||
|
|
||||||
func buildPublicreportQuickWhere[Q psql.Filterable](cols publicreportQuickColumns) publicreportQuickWhere[Q] {
|
func buildPublicreportQuickWhere[Q psql.Filterable](cols publicreportQuickColumns) publicreportQuickWhere[Q] {
|
||||||
return publicreportQuickWhere[Q]{
|
return publicreportQuickWhere[Q]{
|
||||||
ID: psql.Where[Q, int32](cols.ID),
|
ID: psql.Where[Q, int32](cols.ID),
|
||||||
Created: psql.Where[Q, time.Time](cols.Created),
|
Created: psql.Where[Q, time.Time](cols.Created),
|
||||||
Comments: psql.Where[Q, string](cols.Comments),
|
Comments: psql.Where[Q, string](cols.Comments),
|
||||||
Location: psql.WhereNull[Q, string](cols.Location),
|
Location: psql.WhereNull[Q, string](cols.Location),
|
||||||
H3cell: psql.WhereNull[Q, string](cols.H3cell),
|
H3cell: psql.WhereNull[Q, string](cols.H3cell),
|
||||||
UUID: psql.Where[Q, uuid.UUID](cols.UUID),
|
PublicID: psql.Where[Q, string](cols.PublicID),
|
||||||
|
ReporterEmail: psql.Where[Q, string](cols.ReporterEmail),
|
||||||
|
ReporterPhone: psql.Where[Q, string](cols.ReporterPhone),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,20 @@ type ContextQuick struct{}
|
||||||
type ContextQuickSubmitComplete struct {
|
type ContextQuickSubmitComplete struct {
|
||||||
ReportID string
|
ReportID string
|
||||||
}
|
}
|
||||||
|
type ContextRegisterNotificationsComplete struct {
|
||||||
|
ReportID string
|
||||||
|
}
|
||||||
type ContextRoot struct{}
|
type ContextRoot struct{}
|
||||||
type ContextStatus struct{}
|
type ContextStatus struct{}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Nuisance = buildTemplate("nuisance", "base")
|
Nuisance = buildTemplate("nuisance", "base")
|
||||||
Pool = buildTemplate("pool", "base")
|
Pool = buildTemplate("pool", "base")
|
||||||
Quick = buildTemplate("quick", "base")
|
Quick = buildTemplate("quick", "base")
|
||||||
QuickSubmitComplete = buildTemplate("quick-submit-complete", "base")
|
QuickSubmitComplete = buildTemplate("quick-submit-complete", "base")
|
||||||
Root = buildTemplate("root", "base")
|
RegisterNotificationsComplete = buildTemplate("register-notifications-complete", "base")
|
||||||
Status = buildTemplate("status", "base")
|
Root = buildTemplate("root", "base")
|
||||||
|
Status = buildTemplate("status", "base")
|
||||||
)
|
)
|
||||||
|
|
||||||
var components = [...]string{"footer"}
|
var components = [...]string{"footer"}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
<p>Provide your contact information to receive updates about your report.</p>
|
<p>Provide your contact information to receive updates about your report.</p>
|
||||||
|
|
||||||
<form id="notificationForm" action="/register-notifications" method="post" class="needs-validation" novalidate>
|
<form id="notificationForm" action="/register-notifications" method="post" class="needs-validation" novalidate>
|
||||||
<input type="hidden" name="reportId" value="{{.ReportID}}">
|
<input type="hidden" name="report_id" value="{{.ReportID}}">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="email" class="form-label">Email Address</label>
|
<label for="email" class="form-label">Email Address</label>
|
||||||
|
|
|
||||||
|
|
@ -60,16 +60,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
const locationStatus = document.getElementById('locationStatus');
|
const locationStatus = document.getElementById('locationStatus');
|
||||||
const latitudeInput = document.getElementById('latitude');
|
const latitudeInput = document.getElementById('latitude');
|
||||||
const longitudeInput = document.getElementById('longitude');
|
const longitudeInput = document.getElementById('longitude');
|
||||||
const createdInput = document.getElementById('created');
|
|
||||||
const submitButton = document.getElementById('submitButton');
|
const submitButton = document.getElementById('submitButton');
|
||||||
const loadingOverlay = document.getElementById('loadingOverlay');
|
const loadingOverlay = document.getElementById('loadingOverlay');
|
||||||
|
|
||||||
// Get current location
|
// Get current location
|
||||||
requestLocation();
|
requestLocation();
|
||||||
|
|
||||||
// Set current time
|
|
||||||
createdInput.value = new Date().toISOString();
|
|
||||||
|
|
||||||
// Handle photo selection
|
// Handle photo selection
|
||||||
photoInput.addEventListener('change', handlePhotoSelection);
|
photoInput.addEventListener('change', handlePhotoSelection);
|
||||||
|
|
||||||
|
|
@ -251,7 +247,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<!-- Hidden fields for location data -->
|
<!-- Hidden fields for location data -->
|
||||||
<input type="hidden" id="latitude" name="latitude">
|
<input type="hidden" id="latitude" name="latitude">
|
||||||
<input type="hidden" id="longitude" name="longitude">
|
<input type="hidden" id="longitude" name="longitude">
|
||||||
<input type="hidden" id="created" name="created">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Photo Upload -->
|
<!-- Photo Upload -->
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
{{template "base.html" .}}
|
||||||
|
|
||||||
|
{{define "title"}}Dash{{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>
|
||||||
|
Notifications Registered
|
||||||
|
</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 contact information has been successfully registered for report updates.</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}}
|
||||||
|
|
@ -20,8 +20,6 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/stephenafamo/bob/dialect/psql"
|
"github.com/stephenafamo/bob/dialect/psql"
|
||||||
//"github.com/stephenafamo/bob/dialect/psql/dialect"
|
|
||||||
//"github.com/stephenafamo/bob/dialect/psql/im"
|
|
||||||
"github.com/stephenafamo/bob/dialect/psql/um"
|
"github.com/stephenafamo/bob/dialect/psql/um"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -33,6 +31,8 @@ func Router() chi.Router {
|
||||||
r.Get("/quick", getQuick)
|
r.Get("/quick", getQuick)
|
||||||
r.Post("/quick-submit", postQuick)
|
r.Post("/quick-submit", postQuick)
|
||||||
r.Get("/quick-submit-complete", getQuickSubmitComplete)
|
r.Get("/quick-submit-complete", getQuickSubmitComplete)
|
||||||
|
r.Post("/register-notifications", postRegisterNotifications)
|
||||||
|
r.Get("/register-notifications-complete", getRegisterNotificationsComplete)
|
||||||
r.Get("/status", getStatus)
|
r.Get("/status", getStatus)
|
||||||
localFS := http.Dir("./static")
|
localFS := http.Dir("./static")
|
||||||
htmlpage.FileServer(r, "/static", localFS, publicreports.EmbeddedStaticFS, "static")
|
htmlpage.FileServer(r, "/static", localFS, publicreports.EmbeddedStaticFS, "static")
|
||||||
|
|
@ -78,6 +78,16 @@ func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
func getRegisterNotificationsComplete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
report := r.URL.Query().Get("report")
|
||||||
|
htmlpage.RenderOrError(
|
||||||
|
w,
|
||||||
|
publicreports.RegisterNotificationsComplete,
|
||||||
|
publicreports.ContextRegisterNotificationsComplete{
|
||||||
|
ReportID: report,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
func getStatus(w http.ResponseWriter, r *http.Request) {
|
func getStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
htmlpage.RenderOrError(
|
htmlpage.RenderOrError(
|
||||||
w,
|
w,
|
||||||
|
|
@ -88,13 +98,11 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
func postQuick(w http.ResponseWriter, r *http.Request) {
|
func postQuick(w http.ResponseWriter, r *http.Request) {
|
||||||
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
|
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to parse form")
|
|
||||||
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
|
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lat := r.FormValue("latitude")
|
lat := r.FormValue("latitude")
|
||||||
lng := r.FormValue("longitude")
|
lng := r.FormValue("longitude")
|
||||||
created := r.FormValue("created")
|
|
||||||
comments := r.FormValue("comments")
|
comments := r.FormValue("comments")
|
||||||
//photos := r.FormValue("photos")
|
//photos := r.FormValue("photos")
|
||||||
|
|
||||||
|
|
@ -108,9 +116,9 @@ func postQuick(w http.ResponseWriter, r *http.Request) {
|
||||||
respondError(w, "Failed to create parse longitude", err, http.StatusBadRequest)
|
respondError(w, "Failed to create parse longitude", err, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
u, err := uuid.NewUUID()
|
u, err := GenerateReportID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, "Failed to create quick report uuid", err, http.StatusInternalServerError)
|
respondError(w, "Failed to create quick report public ID", err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c, err := h3utils.GetCell(longitude, latitude, 15)
|
c, err := h3utils.GetCell(longitude, latitude, 15)
|
||||||
|
|
@ -118,8 +126,10 @@ func postQuick(w http.ResponseWriter, r *http.Request) {
|
||||||
Created: omit.From(time.Now()),
|
Created: omit.From(time.Now()),
|
||||||
Comments: omit.From(comments),
|
Comments: omit.From(comments),
|
||||||
//Location: omitnull.From(fmt.Sprintf("ST_GeometryFromText(Point(%s %s))", longitude, latitude)),
|
//Location: omitnull.From(fmt.Sprintf("ST_GeometryFromText(Point(%s %s))", longitude, latitude)),
|
||||||
H3cell: omitnull.From(c.String()),
|
H3cell: omitnull.From(c.String()),
|
||||||
UUID: omit.From(u),
|
PublicID: omit.From(u),
|
||||||
|
ReporterEmail: omit.From(""),
|
||||||
|
ReporterPhone: omit.From(""),
|
||||||
}
|
}
|
||||||
quick, err := models.PublicreportQuicks.Insert(&setter).One(r.Context(), db.PGInstance.BobDB)
|
quick, err := models.PublicreportQuicks.Insert(&setter).One(r.Context(), db.PGInstance.BobDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -135,7 +145,7 @@ func postQuick(w http.ResponseWriter, r *http.Request) {
|
||||||
respondError(w, "Failed to insert publicreport", err, http.StatusInternalServerError)
|
respondError(w, "Failed to insert publicreport", err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info().Float64("latitude", latitude).Float64("longitude", longitude).Str("created", created).Msg("Got upload")
|
log.Info().Float64("latitude", latitude).Float64("longitude", longitude).Msg("Got upload")
|
||||||
photoSetters := make([]*models.PublicreportQuickPhotoSetter, 0)
|
photoSetters := make([]*models.PublicreportQuickPhotoSetter, 0)
|
||||||
for _, fheaders := range r.MultipartForm.File {
|
for _, fheaders := range r.MultipartForm.File {
|
||||||
for _, headers := range fheaders {
|
for _, headers := range fheaders {
|
||||||
|
|
@ -179,12 +189,48 @@ func postQuick(w http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*err = quick.InsertQuickPhotos(r.Context(), db.PGInstance.BobDB, photoSetters...)
|
err = quick.InsertQuickPhotos(r.Context(), db.PGInstance.BobDB, photoSetters...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, "Failed to create photo records", err, http.StatusInternalServerError)
|
respondError(w, "Failed to create photo records", err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}*/
|
}
|
||||||
http.Redirect(w, r, "/quick-submit-complete?report=123", http.StatusFound)
|
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 {
|
||||||
|
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
consent := r.PostFormValue("consent")
|
||||||
|
email := r.PostFormValue("email")
|
||||||
|
phone := r.PostFormValue("phone")
|
||||||
|
report_id := r.PostFormValue("report_id")
|
||||||
|
if consent != "on" {
|
||||||
|
respondError(w, "You must consent", nil, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, err := psql.Update(
|
||||||
|
um.Table("publicreport.quick"),
|
||||||
|
um.SetCol("reporter_email").ToArg(email),
|
||||||
|
um.SetCol("reporter_phone").ToArg(phone),
|
||||||
|
um.Where(psql.Quote("public_id").EQ(psql.Arg(report_id))),
|
||||||
|
).Exec(r.Context(), db.PGInstance.BobDB)
|
||||||
|
if err != nil {
|
||||||
|
respondError(w, "Failed to update report", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rowcount, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
respondError(w, "Failed to get rows affected", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if rowcount == 0 {
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("/error?code=no-rows-affected&report=%s", report_id), http.StatusFound)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("/register-notifications-complete?report=%s", report_id), http.StatusFound)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond with an error that is visible to the user
|
// Respond with an error that is visible to the user
|
||||||
|
|
|
||||||
33
public-report/report.go
Normal file
33
public-report/report.go
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package publicreport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateReportID creates a 12-character random string using only unambiguous
|
||||||
|
// capital letters and numbers
|
||||||
|
func GenerateReportID() (string, error) {
|
||||||
|
// Define character set (no O, I, Z to avoid confusion)
|
||||||
|
const charset = "ABCDEFGHJKLMNPQRSTUVWXY0123456789"
|
||||||
|
const length = 12
|
||||||
|
|
||||||
|
var builder strings.Builder
|
||||||
|
builder.Grow(length)
|
||||||
|
|
||||||
|
// Use crypto/rand for secure randomness
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
// Generate a random index within our charset
|
||||||
|
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to generate random number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the randomly selected character to our ID
|
||||||
|
builder.WriteByte(charset[n.Int64()])
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String(), nil
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue