From 4303534396e2ff8de60d0a09aedd479a8c2418b0 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 9 Jan 2026 21:02:30 +0000 Subject: [PATCH] Add logic for searching for a report and getting the status. --- db/sql/publicreport_publicid_table.bob.go | 112 +++++++++ db/sql/publicreport_publicid_table.bob.sql | 25 ++ db/sql/publicreport_publicid_table.sql | 22 ++ public-report/status.go | 266 +++++++++++++-------- public-report/template/status.html | 50 +++- 5 files changed, 376 insertions(+), 99 deletions(-) create mode 100644 db/sql/publicreport_publicid_table.bob.go create mode 100644 db/sql/publicreport_publicid_table.bob.sql create mode 100644 db/sql/publicreport_publicid_table.sql diff --git a/db/sql/publicreport_publicid_table.bob.go b/db/sql/publicreport_publicid_table.bob.go new file mode 100644 index 00000000..9883cb40 --- /dev/null +++ b/db/sql/publicreport_publicid_table.bob.go @@ -0,0 +1,112 @@ +// 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 sql + +import ( + "context" + _ "embed" + "io" + "iter" + + "github.com/lib/pq" + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/dialect/psql" + "github.com/stephenafamo/bob/dialect/psql/dialect" + "github.com/stephenafamo/bob/orm" + "github.com/stephenafamo/scan" +) + +//go:embed publicreport_publicid_table.bob.sql +var formattedQueries_publicreport_publicid_table string + +var publicreportIDTableSQL = formattedQueries_publicreport_publicid_table[192:659] + +type PublicreportIDTableQuery = orm.ModQuery[*dialect.SelectQuery, publicreportIDTable, PublicreportIDTableRow, []PublicreportIDTableRow, publicreportIDTableTransformer] + +func PublicreportIDTable(PublicID string) *PublicreportIDTableQuery { + var expressionTypArgs publicreportIDTable + + expressionTypArgs.PublicID = psql.Arg(PublicID) + + return &PublicreportIDTableQuery{ + Query: orm.Query[publicreportIDTable, PublicreportIDTableRow, []PublicreportIDTableRow, publicreportIDTableTransformer]{ + ExecQuery: orm.ExecQuery[publicreportIDTable]{ + BaseQuery: bob.BaseQuery[publicreportIDTable]{ + Expression: expressionTypArgs, + Dialect: dialect.Dialect, + QueryType: bob.QueryTypeSelect, + }, + }, + Scanner: func(context.Context, []string) (func(*scan.Row) (any, error), func(any) (PublicreportIDTableRow, error)) { + return func(row *scan.Row) (any, error) { + var t PublicreportIDTableRow + row.ScheduleScanByIndex(0, &t.ExistsSomewhere) + row.ScheduleScanByIndex(1, &t.FoundInTables) + return &t, nil + }, func(v any) (PublicreportIDTableRow, error) { + return *(v.(*PublicreportIDTableRow)), nil + } + }, + }, + Mod: bob.ModFunc[*dialect.SelectQuery](func(q *dialect.SelectQuery) { + q.AppendCTE(expressionTypArgs.subExpr(5, 335)) + q.AppendSelect(expressionTypArgs.subExpr(348, 449)) + q.SetTable(expressionTypArgs.subExpr(455, 467)) + }), + } +} + +type PublicreportIDTableRow = struct { + ExistsSomewhere bool `db:"exists_somewhere"` + FoundInTables pq.StringArray `db:"found_in_tables"` +} + +type publicreportIDTableTransformer = bob.SliceTransformer[PublicreportIDTableRow, []PublicreportIDTableRow] + +type publicreportIDTable struct { + PublicID bob.Expression +} + +func (o publicreportIDTable) args() iter.Seq[orm.ArgWithPosition] { + return func(yield func(arg orm.ArgWithPosition) bool) { + if !yield(orm.ArgWithPosition{ + Name: "publicID", + Start: 112, + Stop: 114, + Expression: o.PublicID, + }) { + return + } + + if !yield(orm.ArgWithPosition{ + Name: "publicID", + Start: 221, + Stop: 223, + Expression: o.PublicID, + }) { + return + } + + if !yield(orm.ArgWithPosition{ + Name: "publicID", + Start: 331, + Stop: 333, + Expression: o.PublicID, + }) { + return + } + } +} + +func (o publicreportIDTable) raw(from, to int) string { + return publicreportIDTableSQL[from:to] +} + +func (o publicreportIDTable) subExpr(from, to int) bob.Expression { + return orm.ArgsToExpression(publicreportIDTableSQL, from, to, o.args()) +} + +func (o publicreportIDTable) WriteSQL(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return o.subExpr(0, len(publicreportIDTableSQL)).WriteSQL(ctx, w, d, start) +} diff --git a/db/sql/publicreport_publicid_table.bob.sql b/db/sql/publicreport_publicid_table.bob.sql new file mode 100644 index 00000000..8a6381d9 --- /dev/null +++ b/db/sql/publicreport_publicid_table.bob.sql @@ -0,0 +1,25 @@ +-- 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. + +-- PublicreportIDTable +WITH found_tables AS ( + SELECT 'nuisance' as table_name + FROM publicreport.nuisance + WHERE public_id = $1 + + UNION ALL + + SELECT 'pool' as table_name + FROM publicreport.pool + WHERE public_id = $2 + + UNION ALL + + SELECT 'quick' as table_name + FROM publicreport.quick + WHERE public_id = $3 +) +SELECT + EXISTS (SELECT 1 FROM found_tables) as exists_somewhere, + array_agg(table_name) as found_in_tables +FROM found_tables; diff --git a/db/sql/publicreport_publicid_table.sql b/db/sql/publicreport_publicid_table.sql new file mode 100644 index 00000000..6bf74029 --- /dev/null +++ b/db/sql/publicreport_publicid_table.sql @@ -0,0 +1,22 @@ +-- PublicreportIDTable +WITH found_tables AS ( + SELECT 'nuisance' as table_name + FROM publicreport.nuisance + WHERE public_id = $1 + + UNION ALL + + SELECT 'pool' as table_name + FROM publicreport.pool + WHERE public_id = $1 + + UNION ALL + + SELECT 'quick' as table_name + FROM publicreport.quick + WHERE public_id = $1 +) +SELECT + EXISTS (SELECT 1 FROM found_tables) as exists_somewhere, + array_agg(table_name) as found_in_tables +FROM found_tables; diff --git a/public-report/status.go b/public-report/status.go index 7b14a371..d00a8fc6 100644 --- a/public-report/status.go +++ b/public-report/status.go @@ -1,30 +1,36 @@ package publicreport import ( + "fmt" "net/http" + "strings" + "github.com/Gleipnir-Technology/nidus-sync/db" + "github.com/Gleipnir-Technology/nidus-sync/db/sql" "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/go-chi/chi/v5" + "github.com/rs/zerolog/log" /* - "fmt" - "strconv" - "time" + "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/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" + "github.com/Gleipnir-Technology/nidus-sync/db" + "github.com/Gleipnir-Technology/nidus-sync/h3utils" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/stephenafamo/bob/dialect/psql" + "github.com/stephenafamo/bob/dialect/psql/um" */) type Report struct { ID string } -type ContextStatus struct{} +type ContextStatus struct { + Error string + ReportID string +} type ContextStatusByID struct { Report Report } @@ -34,11 +40,69 @@ var ( StatusByID = buildTemplate("status-by-id", "base") ) +func formatReportID(s string) string { + // truncate down if too long + if len(s) > 12 { + s = s[:12] + } + + // If less than 4 characters, return as is + if len(s) < 4 { + return s + } + + // If at least 8 characters, add hyphens at positions 4 and 8 + if len(s) >= 8 { + return s[0:4] + "-" + s[4:8] + "-" + s[8:] + } + + // If at least 4 characters but less than 8, add hyphen only at position 4 + return s[0:4] + "-" + s[4:] +} + func getStatus(w http.ResponseWriter, r *http.Request) { + report_id_str := r.URL.Query().Get("report") + if report_id_str == "" { + htmlpage.RenderOrError( + w, + Status, + ContextStatus{ + Error: "", + ReportID: "", + }, + ) + return + } + report_id := sanitizeReportID(report_id_str) + report_id_str = formatReportID(report_id) + results, err := sql.PublicreportIDTable(report_id).All(r.Context(), db.PGInstance.BobDB) + if err != nil { + respondError(w, "Failed to query for report", err, http.StatusInternalServerError) + return + } + if len(results) != 1 { + log.Error().Int("count", len(results)).Str("report_id", report_id_str).Msg("Got too many results for report id. This is a programmer error.") + htmlpage.RenderOrError( + w, + Status, + ContextStatus{ + Error: "Sorry, server's confused", + ReportID: report_id_str, + }, + ) + } + result := results[0] + if result.ExistsSomewhere { + http.Redirect(w, r, fmt.Sprintf("/status/%s", report_id), http.StatusFound) + return + } htmlpage.RenderOrError( w, Status, - ContextStatus{}, + ContextStatus{ + Error: "Sorry, we can't find that report", + ReportID: report_id_str, + }, ) } func getStatusByID(w http.ResponseWriter, r *http.Request) { @@ -55,91 +119,103 @@ func getStatusByID(w http.ResponseWriter, r *http.Request) { } /* -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 + func getQuick(w http.ResponseWriter, r *http.Request) { + htmlpage.RenderOrError( + w, + Quick, + ContextQuick{}, + ) } - 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 + func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) { + report := r.URL.Query().Get("report") + htmlpage.RenderOrError( + w, + QuickSubmitComplete, + ContextQuickSubmitComplete{ + ReportID: report, + }, + ) } - longitude, err := strconv.ParseFloat(lng, 64) - if err != nil { - respondError(w, "Failed to create parse longitude", err, http.StatusBadRequest) - return + + 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) } - u, err := GenerateReportID() - if err != nil { - respondError(w, "Failed to create quick report public ID", err, http.StatusInternalServerError) - return +*/ +func sanitizeReportID(r string) string { + result := "" + for _, char := range r { + if char != '-' { + result += string(char) + } } - 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) -}*/ + return strings.ToUpper(result) +} diff --git a/public-report/template/status.html b/public-report/template/status.html index 8e8a0b52..b1dfcf18 100644 --- a/public-report/template/status.html +++ b/public-report/template/status.html @@ -35,6 +35,43 @@ } } + {{end}} {{define "content"}}
@@ -72,14 +109,19 @@ If you have a report ID from a previous request, enter it below to view the details and current status.

-
+
- - + +
Example: MMD-2023-12345
+ {{ if ne .Error "" }} + + {{ end }}
- View Report Details +