From 9e8ad7707c850cba42d27e9883e74250c349a918 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 9 Jan 2026 20:11:57 +0000 Subject: [PATCH 0001/1453] Bump vendor hash --- default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default.nix b/default.nix index 43f460e0..bdfcb5c6 100644 --- a/default.nix +++ b/default.nix @@ -9,5 +9,5 @@ pkgs.buildGoModule rec { subPackages = []; version = "0.0.11"; # Needs to be updated after every modification of go.mod/go.sum - vendorHash = "sha256-5E5gQJh2cr/XwDg+XRQEdXW7mkObZMoyqQnfToVuZ10="; + vendorHash = "sha256-fU0FPvuDC3rwQ4ygQYA3sH48o8PaK5VqqowwdUsVNaA="; } From f6127af058cbdb850b689736966d049c8d30db18 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 9 Jan 2026 20:25:13 +0000 Subject: [PATCH 0002/1453] Correctly copy uploaded files --- public-report/photo-upload.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/public-report/photo-upload.go b/public-report/photo-upload.go index 58846e09..6d40946f 100644 --- a/public-report/photo-upload.go +++ b/public-report/photo-upload.go @@ -3,7 +3,6 @@ package publicreport import ( "bytes" "fmt" - "io" "net/http" "github.com/Gleipnir-Technology/nidus-sync/userfile" @@ -40,10 +39,6 @@ func extractPhotoUploads(r *http.Request) (uploads []PhotoUpload, err error) { 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 { From 4303534396e2ff8de60d0a09aedd479a8c2418b0 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 9 Jan 2026 21:02:30 +0000 Subject: [PATCH 0003/1453] 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 +
From 75454834f470619986bd9d2c6139c7e70cc04880 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 9 Jan 2026 21:31:45 +0000 Subject: [PATCH 0004/1453] Add mock of report search page --- public-report/routes.go | 1 + public-report/search.go | 21 +++ public-report/template/search.html | 190 +++++++++++++++++++++++ public-report/template/status-by-id.html | 162 +++++++++---------- public-report/template/status.html | 4 +- 5 files changed, 296 insertions(+), 82 deletions(-) create mode 100644 public-report/search.go create mode 100644 public-report/template/search.html diff --git a/public-report/routes.go b/public-report/routes.go index a0a55bb6..cb46b135 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -21,6 +21,7 @@ func Router() chi.Router { r.Get("/quick-submit-complete", getQuickSubmitComplete) r.Post("/register-notifications", postRegisterNotifications) r.Get("/register-notifications-complete", getRegisterNotificationsComplete) + r.Get("/search", getSearch) r.Get("/status", getStatus) r.Get("/status/{report_id}", getStatusByID) localFS := http.Dir("./static") diff --git a/public-report/search.go b/public-report/search.go new file mode 100644 index 00000000..ad8497f3 --- /dev/null +++ b/public-report/search.go @@ -0,0 +1,21 @@ +package publicreport + +import ( + "net/http" + + "github.com/Gleipnir-Technology/nidus-sync/htmlpage" +) + +type ContextSearch struct{} + +var ( + Search = buildTemplate("search", "base") +) + +func getSearch(w http.ResponseWriter, r *http.Request) { + htmlpage.RenderOrError( + w, + Search, + ContextSearch{}, + ) +} diff --git a/public-report/template/search.html b/public-report/template/search.html new file mode 100644 index 00000000..825ad946 --- /dev/null +++ b/public-report/template/search.html @@ -0,0 +1,190 @@ +{{template "base.html" .}} + +{{define "title"}}Status{{end}} +{{define "extraheader"}} + + +{{end}} +{{define "content"}} +
+ + + + +
+
+
Reports Map
+
+
+
+ + +
+
+
+ + +
+
+
Reports Near You
+ 15 Reports Found +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Report IDReportedTypeAddressStatus
#123452 days agoMosquito Nuisance456 Elm Street, Anytown, USAScheduled
#123463 days agoGreen Pool789 Oak Avenue, Anytown, USATreated
#123471 week agoMosquito Nuisance123 Pine Road, Anytown, USAVisited
#123481 day agoQuick Report567 Maple Lane, Anytown, USAIn Review
#123492 weeks agoGreen Pool890 Cedar Court, Anytown, USATreated
#123505 days agoMosquito Nuisance234 Birch Blvd, Anytown, USAScheduled
#123513 hours agoQuick Report678 Spruce Street, Anytown, USAIn Review
+
+
+ +
+ + +
+ +
+ + +
+ +
+
+{{end}} diff --git a/public-report/template/status-by-id.html b/public-report/template/status-by-id.html index 73bf8dac..d42d65cd 100644 --- a/public-report/template/status-by-id.html +++ b/public-report/template/status-by-id.html @@ -40,98 +40,100 @@ {{end}} {{define "content"}} - -
-
-
Report {{.Report.ID|publicReportID}}
- Scheduled -
-
-
-
- Created: - July 15, 2023 - 9:30 AM -
-
- Last Updated: - July 17, 2023 - 2:45 PM -
-
- Next Step: - July 19, 2023 (Estimated) +
+ +
+
+
Report {{.Report.ID|publicReportID}}
+ Scheduled +
+
+
+
+ Created: + July 15, 2023 - 9:30 AM +
+
+ Last Updated: + July 17, 2023 - 2:45 PM +
+
+ Next Step: + July 19, 2023 (Estimated) +
-
- -
-
-
-
-
Reporter Information
+ +
+
+
+
+
Reporter Information
+
+
+

Name: Jane Doe

+

Phone: (555) 123-4567

+

Address: 123 Main Street, Anytown, USA 12345

+
-
-

Name: Jane Doe

-

Phone: (555) 123-4567

-

Address: 123 Main Street, Anytown, USA 12345

+
+
+
+
+
Nuisance Property Information
+
+
+

Owner: John Smith

+

Address: 456 Elm Street, Anytown, USA 12345

+

Description: Standing water in abandoned pool

+
-
-
-
-
Nuisance Property Information
-
-
-

Owner: John Smith

-

Address: 456 Elm Street, Anytown, USA 12345

-

Description: Standing water in abandoned pool

-
-
-
-
- -
-
-
Location Map
-
-
-
- - + +
+
+
Location Map
+
+
+
+ + +
-
- -
-
-
Request History
-
-
-
-
-
July 17, 2023 - 2:45 PM
-
Scheduled for Treatment
-

Site visit scheduled for July 19. Technician: Michael Johnson

-
-
-
July 16, 2023 - 10:30 AM
-
Assessment Complete
-

Initial assessment completed. Property requires treatment for mosquito larvae.

-
-
-
July 15, 2023 - 1:15 PM
-
In Review
-

Report assigned to field supervisor for initial assessment.

-
-
-
July 15, 2023 - 9:30 AM
-
Report Created
-

New mosquito nuisance report submitted by Jane Doe.

+ +
+
+
Request History
+
+
+
+
+
July 17, 2023 - 2:45 PM
+
Scheduled for Treatment
+

Site visit scheduled for July 19. Technician: Michael Johnson

+
+
+
July 16, 2023 - 10:30 AM
+
Assessment Complete
+

Initial assessment completed. Property requires treatment for mosquito larvae.

+
+
+
July 15, 2023 - 1:15 PM
+
In Review
+

Report assigned to field supervisor for initial assessment.

+
+
+
July 15, 2023 - 9:30 AM
+
Report Created
+

New mosquito nuisance report submitted by Jane Doe.

+
diff --git a/public-report/template/status.html b/public-report/template/status.html index b1dfcf18..5348849d 100644 --- a/public-report/template/status.html +++ b/public-report/template/status.html @@ -113,7 +113,7 @@ document.addEventListener('DOMContentLoaded', function() {
-
Example: MMD-2023-12345
+
Example: ABCD-1234-5678
{{ if ne .Error "" }}
From 653e3473b36546030cbdb9f469701c8de2c066f4 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 9 Jan 2026 22:21:12 +0000 Subject: [PATCH 0005/1453] Fix fast load of static pages in development Makes it much easier to iterate on JavaScript --- htmlpage/fileserver.go | 19 +++++++++++-------- public-report/routes.go | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/htmlpage/fileserver.go b/htmlpage/fileserver.go index ace4f21d..d84647bf 100644 --- a/htmlpage/fileserver.go +++ b/htmlpage/fileserver.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/go-chi/chi/v5" ) @@ -33,16 +34,11 @@ func FileServer(r chi.Router, path string, root http.FileSystem, embeddedFS embe // Determine the actual file path requestedPath := strings.TrimPrefix(r.URL.Path, pathPrefix) - // Try to open from local filesystem first for development - localFile, localErr := root.Open(requestedPath) - + var err error var fileToServe http.File - if localErr == nil { - // File found in local filesystem - fileToServe = localFile - } else { - // If not found locall, try embedded filesystem + if config.IsProductionEnvironment() { + // For production use the embedded filesystem embeddedFilePath := filepath.Join(embeddedPath, requestedPath) embeddedFile, err := embeddedFS.Open(embeddedFilePath) @@ -54,6 +50,13 @@ func FileServer(r chi.Router, path string, root http.FileSystem, embeddedFS embe // Wrap the embedded file to implement http.File interface fileToServe = &embeddedFileWrapper{embeddedFile} + } else { + // Try to open from local filesystem for development + fileToServe, err = root.Open(requestedPath) + if err != nil { + respondError(w, "Failed to open file", err, http.StatusNotFound) + return + } } // Create a custom ResponseWriter that allows us to modify headers diff --git a/public-report/routes.go b/public-report/routes.go index cb46b135..17290814 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -24,7 +24,7 @@ func Router() chi.Router { r.Get("/search", getSearch) r.Get("/status", getStatus) r.Get("/status/{report_id}", getStatusByID) - localFS := http.Dir("./static") + localFS := http.Dir("./public-report/static") htmlpage.FileServer(r, "/static", localFS, EmbeddedStaticFS, "static") return r } From 28e4e88794b5dc64984f34e899a2af33231d96e1 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 9 Jan 2026 22:29:56 +0000 Subject: [PATCH 0006/1453] Fix sync to follow new pattern of function grouping. --- public-report/endpoint.go | 10 ++++++ public-report/page.go | 10 ------ sync/endpoint.go | 31 +++++++----------- {htmlpage/sync => sync}/model_conversion.go | 0 {htmlpage/sync => sync}/page.go | 4 +-- {htmlpage/sync => sync}/static/favicon.ico | Bin .../static/img/nidus-logo-256-transparent.png | Bin .../static/vendor/css/bootstrap.min.css | 0 .../static/vendor/js/bootstrap.bundle.min.js | 0 .../static/vendor/js/bootstrap.min.js | 0 {htmlpage/sync => sync}/template/admin.html | 0 .../sync => sync}/template/authenticated.html | 0 {htmlpage/sync => sync}/template/base.html | 0 {htmlpage/sync => sync}/template/cell.html | 0 .../template/components/header.html | 0 .../template/components/map.html | 0 .../sync => sync}/template/dashboard.html | 0 .../template/data-entry-bad.html | 0 .../template/data-entry-good.html | 0 .../sync => sync}/template/data-entry.html | 0 .../template/dispatch-results.html | 0 .../sync => sync}/template/dispatch.html | 0 .../sync => sync}/template/empty-auth.html | 0 {htmlpage/sync => sync}/template/empty.html | 0 .../sync => sync}/template/mock-root.html | 0 .../sync => sync}/template/oauth-prompt.html | 0 .../template/report-confirmation.html | 0 .../template/report-contribute.html | 0 .../sync => sync}/template/report-detail.html | 0 .../template/report-evidence.html | 0 .../template/report-schedule.html | 0 .../sync => sync}/template/report-update.html | 0 {htmlpage/sync => sync}/template/report.html | 0 .../template/service-request-detail.html | 0 .../template/service-request-location.html | 0 .../template/service-request-mosquito.html | 0 .../template/service-request-pool.html | 0 .../service-request-quick-confirmation.html | 0 .../template/service-request-quick.html | 0 .../template/service-request-updates.html | 0 .../template/service-request.html | 0 .../template/setting-integration.html | 0 .../sync => sync}/template/setting-mock.html | 0 .../template/setting-pesticide-add.html | 0 .../template/setting-pesticide.html | 0 .../template/setting-user-add.html | 0 .../sync => sync}/template/setting-user.html | 0 .../sync => sync}/template/settings.html | 0 {htmlpage/sync => sync}/template/signin.html | 0 {htmlpage/sync => sync}/template/signup.html | 0 {htmlpage/sync => sync}/template/source.html | 0 {htmlpage/sync => sync}/time.go | 0 {htmlpage/sync => sync}/types.go | 0 {htmlpage/sync => sync}/utils.go | 0 54 files changed, 24 insertions(+), 31 deletions(-) rename {htmlpage/sync => sync}/model_conversion.go (100%) rename {htmlpage/sync => sync}/page.go (99%) rename {htmlpage/sync => sync}/static/favicon.ico (100%) rename {htmlpage/sync => sync}/static/img/nidus-logo-256-transparent.png (100%) rename {htmlpage/sync => sync}/static/vendor/css/bootstrap.min.css (100%) rename {htmlpage/sync => sync}/static/vendor/js/bootstrap.bundle.min.js (100%) rename {htmlpage/sync => sync}/static/vendor/js/bootstrap.min.js (100%) rename {htmlpage/sync => sync}/template/admin.html (100%) rename {htmlpage/sync => sync}/template/authenticated.html (100%) rename {htmlpage/sync => sync}/template/base.html (100%) rename {htmlpage/sync => sync}/template/cell.html (100%) rename {htmlpage/sync => sync}/template/components/header.html (100%) rename {htmlpage/sync => sync}/template/components/map.html (100%) rename {htmlpage/sync => sync}/template/dashboard.html (100%) rename {htmlpage/sync => sync}/template/data-entry-bad.html (100%) rename {htmlpage/sync => sync}/template/data-entry-good.html (100%) rename {htmlpage/sync => sync}/template/data-entry.html (100%) rename {htmlpage/sync => sync}/template/dispatch-results.html (100%) rename {htmlpage/sync => sync}/template/dispatch.html (100%) rename {htmlpage/sync => sync}/template/empty-auth.html (100%) rename {htmlpage/sync => sync}/template/empty.html (100%) rename {htmlpage/sync => sync}/template/mock-root.html (100%) rename {htmlpage/sync => sync}/template/oauth-prompt.html (100%) rename {htmlpage/sync => sync}/template/report-confirmation.html (100%) rename {htmlpage/sync => sync}/template/report-contribute.html (100%) rename {htmlpage/sync => sync}/template/report-detail.html (100%) rename {htmlpage/sync => sync}/template/report-evidence.html (100%) rename {htmlpage/sync => sync}/template/report-schedule.html (100%) rename {htmlpage/sync => sync}/template/report-update.html (100%) rename {htmlpage/sync => sync}/template/report.html (100%) rename {htmlpage/sync => sync}/template/service-request-detail.html (100%) rename {htmlpage/sync => sync}/template/service-request-location.html (100%) rename {htmlpage/sync => sync}/template/service-request-mosquito.html (100%) rename {htmlpage/sync => sync}/template/service-request-pool.html (100%) rename {htmlpage/sync => sync}/template/service-request-quick-confirmation.html (100%) rename {htmlpage/sync => sync}/template/service-request-quick.html (100%) rename {htmlpage/sync => sync}/template/service-request-updates.html (100%) rename {htmlpage/sync => sync}/template/service-request.html (100%) rename {htmlpage/sync => sync}/template/setting-integration.html (100%) rename {htmlpage/sync => sync}/template/setting-mock.html (100%) rename {htmlpage/sync => sync}/template/setting-pesticide-add.html (100%) rename {htmlpage/sync => sync}/template/setting-pesticide.html (100%) rename {htmlpage/sync => sync}/template/setting-user-add.html (100%) rename {htmlpage/sync => sync}/template/setting-user.html (100%) rename {htmlpage/sync => sync}/template/settings.html (100%) rename {htmlpage/sync => sync}/template/signin.html (100%) rename {htmlpage/sync => sync}/template/signup.html (100%) rename {htmlpage/sync => sync}/template/source.html (100%) rename {htmlpage/sync => sync}/time.go (100%) rename {htmlpage/sync => sync}/types.go (100%) rename {htmlpage/sync => sync}/utils.go (100%) diff --git a/public-report/endpoint.go b/public-report/endpoint.go index 0eb77bb7..35bff222 100644 --- a/public-report/endpoint.go +++ b/public-report/endpoint.go @@ -11,6 +11,16 @@ import ( "github.com/stephenafamo/bob/dialect/psql/um" ) +type ContextRegisterNotificationsComplete struct { + ReportID string +} +type ContextRoot struct{} + +var ( + RegisterNotificationsComplete = buildTemplate("register-notifications-complete", "base") + Root = buildTemplate("root", "base") +) + func getRoot(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, diff --git a/public-report/page.go b/public-report/page.go index 1f03ef15..84c18982 100644 --- a/public-report/page.go +++ b/public-report/page.go @@ -13,16 +13,6 @@ var embeddedFiles embed.FS //go:embed static/* var EmbeddedStaticFS embed.FS -type ContextRegisterNotificationsComplete struct { - ReportID string -} -type ContextRoot struct{} - -var ( - RegisterNotificationsComplete = buildTemplate("register-notifications-complete", "base") - Root = buildTemplate("root", "base") -) - var components = [...]string{"footer", "location-geocode", "location-geocode-header", "photo-upload", "photo-upload-header"} func buildTemplate(files ...string) *htmlpage.BuiltTemplate { diff --git a/sync/endpoint.go b/sync/endpoint.go index 6a55d94c..4e289f92 100644 --- a/sync/endpoint.go +++ b/sync/endpoint.go @@ -15,7 +15,6 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/htmlpage" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage/sync" "github.com/go-chi/chi/v5" "github.com/google/uuid" "github.com/rs/zerolog/log" @@ -82,8 +81,8 @@ func Router() chi.Router { r.Method("GET", "/source/{globalid}", auth.NewEnsureAuth(getSource)) //r.Method("GET", "/vector-tiles/{org_id}/{tileset_id}/{zoom}/{x}/{y}.{format}", auth.NewEnsureAuth(getVectorTiles)) - localFS := http.Dir("./static") - htmlpage.FileServer(r, "/static", localFS, sync.EmbeddedStaticFS, "static") + localFS := http.Dir("./sync/static") + htmlpage.FileServer(r, "/static", localFS, EmbeddedStaticFS, "static") return r } @@ -123,7 +122,7 @@ func getCellDetails(w http.ResponseWriter, r *http.Request, user *models.User) { respondError(w, "Cannot convert provided cell to uint64", err, http.StatusBadRequest) return } - sync.Cell(r.Context(), w, user, cell) + Cell(r.Context(), w, user, cell) } func getFavicon(w http.ResponseWriter, r *http.Request) { @@ -138,7 +137,7 @@ func getOAuthRefresh(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/?next=/oauth/refresh", http.StatusFound) return } - sync.OauthPrompt(w, user) + OauthPrompt(w, user) } func getQRCodeReport(w http.ResponseWriter, r *http.Request) { @@ -215,7 +214,7 @@ func getRoot(w http.ResponseWriter, r *http.Request) { } if user == nil { errorCode := r.URL.Query().Get("error") - sync.Signin(w, errorCode) + Signin(w, errorCode) return } else { has, err := background.HasFieldseekerConnection(r.Context(), user) @@ -224,10 +223,10 @@ func getRoot(w http.ResponseWriter, r *http.Request) { return } if has { - sync.Dashboard(r.Context(), w, user) + Dashboard(r.Context(), w, user) return } else { - sync.OauthPrompt(w, user) + OauthPrompt(w, user) return } } @@ -237,16 +236,16 @@ func getRoot(w http.ResponseWriter, r *http.Request) { } func getSettings(w http.ResponseWriter, r *http.Request, u *models.User) { - sync.Settings(w, r, u) + Settings(w, r, u) } func getSignin(w http.ResponseWriter, r *http.Request) { errorCode := r.URL.Query().Get("error") - sync.Signin(w, errorCode) + Signin(w, errorCode) } func getSignup(w http.ResponseWriter, r *http.Request) { - sync.Signup(w, r.URL.Path) + Signup(w, r.URL.Path) } func getSource(w http.ResponseWriter, r *http.Request, u *models.User) { @@ -260,7 +259,7 @@ func getSource(w http.ResponseWriter, r *http.Request, u *models.User) { respondError(w, "globalid is not a UUID", nil, http.StatusBadRequest) return } - sync.Source(w, r, u, globalid) + Source(w, r, u, globalid) } func postSMS(w http.ResponseWriter, r *http.Request) { @@ -324,12 +323,6 @@ func getVectorTiles(w http.ResponseWriter, r *http.Request, u *models.User) { } -// Respond with an error that is visible to the user -func respondError(w http.ResponseWriter, m string, e error, s int) { - log.Warn().Int("status", s).Err(e).Str("user message", m).Msg("Responding with an error") - http.Error(w, m, s) -} - func postSignin(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { respondError(w, "Could not parse form", err, http.StatusBadRequest) @@ -394,6 +387,6 @@ func renderMock(templateName string) http.HandlerFunc { if code == "" { code = "abc-123" } - sync.Mock(templateName, w, code) + Mock(templateName, w, code) } } diff --git a/htmlpage/sync/model_conversion.go b/sync/model_conversion.go similarity index 100% rename from htmlpage/sync/model_conversion.go rename to sync/model_conversion.go diff --git a/htmlpage/sync/page.go b/sync/page.go similarity index 99% rename from htmlpage/sync/page.go rename to sync/page.go index 7866c9b6..0e35bd02 100644 --- a/htmlpage/sync/page.go +++ b/sync/page.go @@ -82,7 +82,7 @@ var ( var components = [...]string{"header", "map"} func buildTemplate(files ...string) *htmlpage.BuiltTemplate { - subdir := "htmlpage/sync" + subdir := "sync" full_files := make([]string, 0) for _, f := range files { full_files = append(full_files, fmt.Sprintf("%s/template/%s.html", subdir, f)) @@ -90,7 +90,7 @@ func buildTemplate(files ...string) *htmlpage.BuiltTemplate { for _, c := range components { full_files = append(full_files, fmt.Sprintf("%s/template/components/%s.html", subdir, c)) } - return htmlpage.NewBuiltTemplate(embeddedFiles, "htmlpage/sync/", full_files...) + return htmlpage.NewBuiltTemplate(embeddedFiles, "sync/", full_files...) } func Cell(ctx context.Context, w http.ResponseWriter, user *models.User, c int64) { diff --git a/htmlpage/sync/static/favicon.ico b/sync/static/favicon.ico similarity index 100% rename from htmlpage/sync/static/favicon.ico rename to sync/static/favicon.ico diff --git a/htmlpage/sync/static/img/nidus-logo-256-transparent.png b/sync/static/img/nidus-logo-256-transparent.png similarity index 100% rename from htmlpage/sync/static/img/nidus-logo-256-transparent.png rename to sync/static/img/nidus-logo-256-transparent.png diff --git a/htmlpage/sync/static/vendor/css/bootstrap.min.css b/sync/static/vendor/css/bootstrap.min.css similarity index 100% rename from htmlpage/sync/static/vendor/css/bootstrap.min.css rename to sync/static/vendor/css/bootstrap.min.css diff --git a/htmlpage/sync/static/vendor/js/bootstrap.bundle.min.js b/sync/static/vendor/js/bootstrap.bundle.min.js similarity index 100% rename from htmlpage/sync/static/vendor/js/bootstrap.bundle.min.js rename to sync/static/vendor/js/bootstrap.bundle.min.js diff --git a/htmlpage/sync/static/vendor/js/bootstrap.min.js b/sync/static/vendor/js/bootstrap.min.js similarity index 100% rename from htmlpage/sync/static/vendor/js/bootstrap.min.js rename to sync/static/vendor/js/bootstrap.min.js diff --git a/htmlpage/sync/template/admin.html b/sync/template/admin.html similarity index 100% rename from htmlpage/sync/template/admin.html rename to sync/template/admin.html diff --git a/htmlpage/sync/template/authenticated.html b/sync/template/authenticated.html similarity index 100% rename from htmlpage/sync/template/authenticated.html rename to sync/template/authenticated.html diff --git a/htmlpage/sync/template/base.html b/sync/template/base.html similarity index 100% rename from htmlpage/sync/template/base.html rename to sync/template/base.html diff --git a/htmlpage/sync/template/cell.html b/sync/template/cell.html similarity index 100% rename from htmlpage/sync/template/cell.html rename to sync/template/cell.html diff --git a/htmlpage/sync/template/components/header.html b/sync/template/components/header.html similarity index 100% rename from htmlpage/sync/template/components/header.html rename to sync/template/components/header.html diff --git a/htmlpage/sync/template/components/map.html b/sync/template/components/map.html similarity index 100% rename from htmlpage/sync/template/components/map.html rename to sync/template/components/map.html diff --git a/htmlpage/sync/template/dashboard.html b/sync/template/dashboard.html similarity index 100% rename from htmlpage/sync/template/dashboard.html rename to sync/template/dashboard.html diff --git a/htmlpage/sync/template/data-entry-bad.html b/sync/template/data-entry-bad.html similarity index 100% rename from htmlpage/sync/template/data-entry-bad.html rename to sync/template/data-entry-bad.html diff --git a/htmlpage/sync/template/data-entry-good.html b/sync/template/data-entry-good.html similarity index 100% rename from htmlpage/sync/template/data-entry-good.html rename to sync/template/data-entry-good.html diff --git a/htmlpage/sync/template/data-entry.html b/sync/template/data-entry.html similarity index 100% rename from htmlpage/sync/template/data-entry.html rename to sync/template/data-entry.html diff --git a/htmlpage/sync/template/dispatch-results.html b/sync/template/dispatch-results.html similarity index 100% rename from htmlpage/sync/template/dispatch-results.html rename to sync/template/dispatch-results.html diff --git a/htmlpage/sync/template/dispatch.html b/sync/template/dispatch.html similarity index 100% rename from htmlpage/sync/template/dispatch.html rename to sync/template/dispatch.html diff --git a/htmlpage/sync/template/empty-auth.html b/sync/template/empty-auth.html similarity index 100% rename from htmlpage/sync/template/empty-auth.html rename to sync/template/empty-auth.html diff --git a/htmlpage/sync/template/empty.html b/sync/template/empty.html similarity index 100% rename from htmlpage/sync/template/empty.html rename to sync/template/empty.html diff --git a/htmlpage/sync/template/mock-root.html b/sync/template/mock-root.html similarity index 100% rename from htmlpage/sync/template/mock-root.html rename to sync/template/mock-root.html diff --git a/htmlpage/sync/template/oauth-prompt.html b/sync/template/oauth-prompt.html similarity index 100% rename from htmlpage/sync/template/oauth-prompt.html rename to sync/template/oauth-prompt.html diff --git a/htmlpage/sync/template/report-confirmation.html b/sync/template/report-confirmation.html similarity index 100% rename from htmlpage/sync/template/report-confirmation.html rename to sync/template/report-confirmation.html diff --git a/htmlpage/sync/template/report-contribute.html b/sync/template/report-contribute.html similarity index 100% rename from htmlpage/sync/template/report-contribute.html rename to sync/template/report-contribute.html diff --git a/htmlpage/sync/template/report-detail.html b/sync/template/report-detail.html similarity index 100% rename from htmlpage/sync/template/report-detail.html rename to sync/template/report-detail.html diff --git a/htmlpage/sync/template/report-evidence.html b/sync/template/report-evidence.html similarity index 100% rename from htmlpage/sync/template/report-evidence.html rename to sync/template/report-evidence.html diff --git a/htmlpage/sync/template/report-schedule.html b/sync/template/report-schedule.html similarity index 100% rename from htmlpage/sync/template/report-schedule.html rename to sync/template/report-schedule.html diff --git a/htmlpage/sync/template/report-update.html b/sync/template/report-update.html similarity index 100% rename from htmlpage/sync/template/report-update.html rename to sync/template/report-update.html diff --git a/htmlpage/sync/template/report.html b/sync/template/report.html similarity index 100% rename from htmlpage/sync/template/report.html rename to sync/template/report.html diff --git a/htmlpage/sync/template/service-request-detail.html b/sync/template/service-request-detail.html similarity index 100% rename from htmlpage/sync/template/service-request-detail.html rename to sync/template/service-request-detail.html diff --git a/htmlpage/sync/template/service-request-location.html b/sync/template/service-request-location.html similarity index 100% rename from htmlpage/sync/template/service-request-location.html rename to sync/template/service-request-location.html diff --git a/htmlpage/sync/template/service-request-mosquito.html b/sync/template/service-request-mosquito.html similarity index 100% rename from htmlpage/sync/template/service-request-mosquito.html rename to sync/template/service-request-mosquito.html diff --git a/htmlpage/sync/template/service-request-pool.html b/sync/template/service-request-pool.html similarity index 100% rename from htmlpage/sync/template/service-request-pool.html rename to sync/template/service-request-pool.html diff --git a/htmlpage/sync/template/service-request-quick-confirmation.html b/sync/template/service-request-quick-confirmation.html similarity index 100% rename from htmlpage/sync/template/service-request-quick-confirmation.html rename to sync/template/service-request-quick-confirmation.html diff --git a/htmlpage/sync/template/service-request-quick.html b/sync/template/service-request-quick.html similarity index 100% rename from htmlpage/sync/template/service-request-quick.html rename to sync/template/service-request-quick.html diff --git a/htmlpage/sync/template/service-request-updates.html b/sync/template/service-request-updates.html similarity index 100% rename from htmlpage/sync/template/service-request-updates.html rename to sync/template/service-request-updates.html diff --git a/htmlpage/sync/template/service-request.html b/sync/template/service-request.html similarity index 100% rename from htmlpage/sync/template/service-request.html rename to sync/template/service-request.html diff --git a/htmlpage/sync/template/setting-integration.html b/sync/template/setting-integration.html similarity index 100% rename from htmlpage/sync/template/setting-integration.html rename to sync/template/setting-integration.html diff --git a/htmlpage/sync/template/setting-mock.html b/sync/template/setting-mock.html similarity index 100% rename from htmlpage/sync/template/setting-mock.html rename to sync/template/setting-mock.html diff --git a/htmlpage/sync/template/setting-pesticide-add.html b/sync/template/setting-pesticide-add.html similarity index 100% rename from htmlpage/sync/template/setting-pesticide-add.html rename to sync/template/setting-pesticide-add.html diff --git a/htmlpage/sync/template/setting-pesticide.html b/sync/template/setting-pesticide.html similarity index 100% rename from htmlpage/sync/template/setting-pesticide.html rename to sync/template/setting-pesticide.html diff --git a/htmlpage/sync/template/setting-user-add.html b/sync/template/setting-user-add.html similarity index 100% rename from htmlpage/sync/template/setting-user-add.html rename to sync/template/setting-user-add.html diff --git a/htmlpage/sync/template/setting-user.html b/sync/template/setting-user.html similarity index 100% rename from htmlpage/sync/template/setting-user.html rename to sync/template/setting-user.html diff --git a/htmlpage/sync/template/settings.html b/sync/template/settings.html similarity index 100% rename from htmlpage/sync/template/settings.html rename to sync/template/settings.html diff --git a/htmlpage/sync/template/signin.html b/sync/template/signin.html similarity index 100% rename from htmlpage/sync/template/signin.html rename to sync/template/signin.html diff --git a/htmlpage/sync/template/signup.html b/sync/template/signup.html similarity index 100% rename from htmlpage/sync/template/signup.html rename to sync/template/signup.html diff --git a/htmlpage/sync/template/source.html b/sync/template/source.html similarity index 100% rename from htmlpage/sync/template/source.html rename to sync/template/source.html diff --git a/htmlpage/sync/time.go b/sync/time.go similarity index 100% rename from htmlpage/sync/time.go rename to sync/time.go diff --git a/htmlpage/sync/types.go b/sync/types.go similarity index 100% rename from htmlpage/sync/types.go rename to sync/types.go diff --git a/htmlpage/sync/utils.go b/sync/utils.go similarity index 100% rename from htmlpage/sync/utils.go rename to sync/utils.go From acaeb2129e94a21f384bdb667e1e0c8384eb336b Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 9 Jan 2026 23:32:39 +0000 Subject: [PATCH 0007/1453] Begin sharing code with search page This includes code for geocoding, building a map, and getting the user's location. --- public-report/search.go | 9 +- public-report/static/js/geocode.js | 13 ++ public-report/static/js/location.js | 23 ++++ public-report/static/js/map.js | 62 ++++++++++ public-report/template/pool.html | 178 +++++++--------------------- public-report/template/search.html | 94 ++++++++++----- 6 files changed, 216 insertions(+), 163 deletions(-) create mode 100644 public-report/static/js/geocode.js create mode 100644 public-report/static/js/location.js create mode 100644 public-report/static/js/map.js diff --git a/public-report/search.go b/public-report/search.go index ad8497f3..d92748e0 100644 --- a/public-report/search.go +++ b/public-report/search.go @@ -3,10 +3,13 @@ package publicreport import ( "net/http" + "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/htmlpage" ) -type ContextSearch struct{} +type ContextSearch struct { + MapboxToken string +} var ( Search = buildTemplate("search", "base") @@ -16,6 +19,8 @@ func getSearch(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, Search, - ContextSearch{}, + ContextSearch{ + MapboxToken: config.MapboxToken, + }, ) } diff --git a/public-report/static/js/geocode.js b/public-report/static/js/geocode.js new file mode 100644 index 00000000..763854d5 --- /dev/null +++ b/public-report/static/js/geocode.js @@ -0,0 +1,13 @@ +async function geocodeReverse(MAPBOX_ACCESS_TOKEN, lngLat) { + 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); +} diff --git a/public-report/static/js/location.js b/public-report/static/js/location.js new file mode 100644 index 00000000..cb3acbf3 --- /dev/null +++ b/public-report/static/js/location.js @@ -0,0 +1,23 @@ +function getGeolocation(options) { + return new Promise((resolve, reject) => { + // Check if geolocation is supported by the browser + if (!navigator.geolocation) { + reject(new Error("Geolocation is not supported by your browser")); + return; + } + + // Default options if none provided + const geolocationOptions = options || { + enableHighAccuracy: true, + timeout: 5000, + maximumAge: 0 + }; + + // Call the geolocation API + navigator.geolocation.getCurrentPosition( + position => resolve(position), + error => reject(error), + geolocationOptions + ); + }); +} diff --git a/public-report/static/js/map.js b/public-report/static/js/map.js new file mode 100644 index 00000000..3bdf7ace --- /dev/null +++ b/public-report/static/js/map.js @@ -0,0 +1,62 @@ +var map = null; +var markers = []; + +function mapAddMarker(coords) { + const mapContainer = document.getElementById("map-container"); + const marker = new mapboxgl.Marker({ + color: "#FF0000", + draggable: true + }).setLngLat(coords).addTo(map); + marker.on('dragend', function(e) { + const markerDraggedEvent = new CustomEvent("markerdragend", { + detail: { + marker: marker + } + }); + mapContainer.dispatchEvent(markerDraggedEvent); + }); + markers.push(marker); +} + +function mapLoad(MAPBOX_ACCESS_TOKEN) { + return new Promise((resolve, reject) => { + 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 loaded."); + resolve(map); + }); + }); +} + +function mapJumpTo(args) { + map.jumpTo(args); +} + +function mapSetMarker(coords) { + console.log("Setting map marker", coords); + map.jumpTo({ + center: coords, + zoom: 14, + }); + markers.forEach((marker) => marker.remove()); + mapAddMarker(coords); +} + diff --git a/public-report/template/pool.html b/public-report/template/pool.html index 8d9f96bc..487de068 100644 --- a/public-report/template/pool.html +++ b/public-report/template/pool.html @@ -5,22 +5,15 @@ {{template "location-geocode-header" .}} + + + {{template "photo-upload-header"}} {{end}} {{define "content"}} @@ -386,7 +300,7 @@ function updateMapWithLocation(map) {

You can also click on the map to mark the location precisely

-
+
diff --git a/public-report/template/search.html b/public-report/template/search.html index 825ad946..60ee08b9 100644 --- a/public-report/template/search.html +++ b/public-report/template/search.html @@ -2,35 +2,73 @@ {{define "title"}}Status{{end}} {{define "extraheader"}} + + + + + {{end}} @@ -75,10 +113,8 @@ document.addEventListener('DOMContentLoaded', function() {
Reports Map
-
- - +
+
From eb24d871d052061777cd4e4d0e7c424b5cc319b7 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 9 Jan 2026 23:52:35 +0000 Subject: [PATCH 0008/1453] Bastardize mapbox example to query points This is just to get the structure of what I need to integrate with Mapbox to query a tile server and try to find point locations. In this case, I'm getting the airport locations and barely showing them. This saves me time understanding the pertinent APIs. I need to adapt this to get our mosquito reports instead. --- public-report/template/search.html | 123 ++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) diff --git a/public-report/template/search.html b/public-report/template/search.html index 60ee08b9..e674567d 100644 --- a/public-report/template/search.html +++ b/public-report/template/search.html @@ -51,8 +51,121 @@ + + + +{{end}} +{{define "content"}} +

District page placeholder

+{{end}} From 81dabdf097a3158bbd23b3261686dff6cf11f307 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 13 Jan 2026 23:36:09 +0000 Subject: [PATCH 0016/1453] Update readme with how I created the working district table --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 06c3f16a..b10a53ba 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,9 @@ You'll need a number of environment variables for configuring things; There's a table containing district information in the database, `public.district`. It was created with: ``` -shp2pgsql -s 4326 -c -D -I CA_districts.shp public.district | psql -d nidus-sync +shp2pgsql -s 3857 -c -D -I CA_districts.shp public.district | psql -d nidus-sync +psql +ALTER TABLE district ADD COLUMN geom_4326 geometry(MultiPolygon,4326) GENERATED ALWAYS AS (ST_Transform(geom, 4326)) STORED; ``` ## Hacking From d60db93bf20fe41c6e50c64d5d36d2759213b9eb Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 00:39:46 +0000 Subject: [PATCH 0017/1453] Make public report and sync share static assets It just seems useful --- {sync => htmlpage}/static/favicon.ico | Bin .../static/img/nidus-logo-256-transparent.png | Bin {public-report => htmlpage}/static/js/geocode.js | 0 {public-report => htmlpage}/static/js/location.js | 0 {public-report => htmlpage}/static/js/map.js | 0 .../static/vendor/css/bootstrap.min.css | 0 .../static/vendor/js/bootstrap.bundle.min.js | 0 .../static/vendor/js/bootstrap.min.js | 0 public-report/routes.go | 5 +---- sync/page.go | 3 --- sync/routes.go | 6 +----- 11 files changed, 2 insertions(+), 12 deletions(-) rename {sync => htmlpage}/static/favicon.ico (100%) rename {sync => htmlpage}/static/img/nidus-logo-256-transparent.png (100%) rename {public-report => htmlpage}/static/js/geocode.js (100%) rename {public-report => htmlpage}/static/js/location.js (100%) rename {public-report => htmlpage}/static/js/map.js (100%) rename {sync => htmlpage}/static/vendor/css/bootstrap.min.css (100%) rename {sync => htmlpage}/static/vendor/js/bootstrap.bundle.min.js (100%) rename {sync => htmlpage}/static/vendor/js/bootstrap.min.js (100%) diff --git a/sync/static/favicon.ico b/htmlpage/static/favicon.ico similarity index 100% rename from sync/static/favicon.ico rename to htmlpage/static/favicon.ico diff --git a/sync/static/img/nidus-logo-256-transparent.png b/htmlpage/static/img/nidus-logo-256-transparent.png similarity index 100% rename from sync/static/img/nidus-logo-256-transparent.png rename to htmlpage/static/img/nidus-logo-256-transparent.png diff --git a/public-report/static/js/geocode.js b/htmlpage/static/js/geocode.js similarity index 100% rename from public-report/static/js/geocode.js rename to htmlpage/static/js/geocode.js diff --git a/public-report/static/js/location.js b/htmlpage/static/js/location.js similarity index 100% rename from public-report/static/js/location.js rename to htmlpage/static/js/location.js diff --git a/public-report/static/js/map.js b/htmlpage/static/js/map.js similarity index 100% rename from public-report/static/js/map.js rename to htmlpage/static/js/map.js diff --git a/sync/static/vendor/css/bootstrap.min.css b/htmlpage/static/vendor/css/bootstrap.min.css similarity index 100% rename from sync/static/vendor/css/bootstrap.min.css rename to htmlpage/static/vendor/css/bootstrap.min.css diff --git a/sync/static/vendor/js/bootstrap.bundle.min.js b/htmlpage/static/vendor/js/bootstrap.bundle.min.js similarity index 100% rename from sync/static/vendor/js/bootstrap.bundle.min.js rename to htmlpage/static/vendor/js/bootstrap.bundle.min.js diff --git a/sync/static/vendor/js/bootstrap.min.js b/htmlpage/static/vendor/js/bootstrap.min.js similarity index 100% rename from sync/static/vendor/js/bootstrap.min.js rename to htmlpage/static/vendor/js/bootstrap.min.js diff --git a/public-report/routes.go b/public-report/routes.go index 17290814..67188698 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -1,8 +1,6 @@ package publicreport import ( - "net/http" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/go-chi/chi/v5" ) @@ -24,7 +22,6 @@ func Router() chi.Router { r.Get("/search", getSearch) r.Get("/status", getStatus) r.Get("/status/{report_id}", getStatusByID) - localFS := http.Dir("./public-report/static") - htmlpage.FileServer(r, "/static", localFS, EmbeddedStaticFS, "static") + htmlpage.AddStaticRoute(r, "/static") return r } diff --git a/sync/page.go b/sync/page.go index c0d0e3de..1738e5b7 100644 --- a/sync/page.go +++ b/sync/page.go @@ -12,9 +12,6 @@ import ( //go:embed template/* var embeddedFiles embed.FS -//go:embed static/* -var EmbeddedStaticFS embed.FS - var components = [...]string{"header", "map"} func buildTemplate(files ...string) *htmlpage.BuiltTemplate { diff --git a/sync/routes.go b/sync/routes.go index 0a24a87f..369bedd2 100644 --- a/sync/routes.go +++ b/sync/routes.go @@ -1,8 +1,6 @@ package sync import ( - "net/http" - "github.com/Gleipnir-Technology/nidus-sync/api" "github.com/Gleipnir-Technology/nidus-sync/auth" "github.com/Gleipnir-Technology/nidus-sync/htmlpage" @@ -68,9 +66,7 @@ func Router() chi.Router { r.Method("GET", "/cell/{cell}", auth.NewEnsureAuth(getCellDetails)) r.Method("GET", "/settings", auth.NewEnsureAuth(getSettings)) r.Method("GET", "/source/{globalid}", auth.NewEnsureAuth(getSource)) - //r.Method("GET", "/vector-tiles/{org_id}/{tileset_id}/{zoom}/{x}/{y}.{format}", auth.NewEnsureAuth(getVectorTiles)) - localFS := http.Dir("./sync/static") - htmlpage.FileServer(r, "/static", localFS, EmbeddedStaticFS, "static") + htmlpage.AddStaticRoute(r, "/static") return r } From 53f88577955106fa199e66a31606700d516834ac Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 00:40:01 +0000 Subject: [PATCH 0018/1453] Show the district overlay map on the district page I can event get the district name in the properties. --- sync/template/district.html | 61 ++++++++++++++----------------------- 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/sync/template/district.html b/sync/template/district.html index 3540a5f7..f4b792c0 100644 --- a/sync/template/district.html +++ b/sync/template/district.html @@ -1,13 +1,17 @@ -{{template "authenticated.html" .}} +{{template "base.html" .}} {{define "title"}}Dash{{end}} {{define "extraheader"}} + + + {{end}} {{define "content"}} -

District page placeholder

+
+
+
+
+
+
+
{{end}} From b91718cd7c88195e9e3d9d5161d09fcf24c50f51 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 15:52:14 +0000 Subject: [PATCH 0019/1453] Color districts by regionid The colors jump as you zoom, but they still technically work, so I'm committing it. --- sync/template/district.html | 38 +++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/sync/template/district.html b/sync/template/district.html index f4b792c0..dbfed900 100644 --- a/sync/template/district.html +++ b/sync/template/district.html @@ -9,12 +9,37 @@ -{{end}} diff --git a/public-report/template/component/location-geocode.html b/public-report/template/component/location-geocode.html deleted file mode 100644 index 3198e9f9..00000000 --- a/public-report/template/component/location-geocode.html +++ /dev/null @@ -1,52 +0,0 @@ -{{define "location-geocode"}} - - - - - - - - - - -
-
- - -
-
-
- -
-
Location Details
-
-
-
Street Address
-
-
-
- -
-
Post Code
-
-
-
- -
-
District/Place
-
-
-
- -
-
Region/State
-
-
-
- -
-
Country
-
-
-
-
-
-
-
-{{end}} diff --git a/public-report/template/pool.html b/public-report/template/pool.html index 487de068..70390fdf 100644 --- a/public-report/template/pool.html +++ b/public-report/template/pool.html @@ -2,9 +2,10 @@ {{define "title"}}Green Pool{{end}} {{define "extraheader"}} -{{template "location-geocode-header" .}} + + @@ -163,6 +164,31 @@ function onMapMarkerDragEnd(marker) { geocodeReverse(MAPBOX_ACCESS_TOKEN, lngLat); } +function setLocationInputs(location) { + 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 = location.properties; + const context = props.context || {}; + + // Populate structured fields + 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() { // Elements const photoInput = document.getElementById('photos'); @@ -224,9 +250,21 @@ document.addEventListener('DOMContentLoaded', function() { onMapMarkerDragEnd(e.detail.marker); }); - const suggestionsContainer = document.getElementById('suggestions'); - suggestionsContainer.addEventListener("locationselected", (e) => { - mapSetMarker(e.detail.coordinates); + const addressDisplay = document.querySelector("address-display"); + const addressInput = document.querySelector("address-input"); + const mapComponent = document.querySelector("map-component"); + addressInput.addEventListener("address-selected", (event) => { + const l = event.detail.location; + console.log("Address selected", l); + // Center map on selected address + mapSetMarker(l.geometry.coordinates); + mapJumpTo({ + center: l.geometry.coordinates, + zoom: 14, + }); + + addressDisplay.show(l); + setLocationInputs(l); }); }); function displaySelectedCoordinates(lngLat) { @@ -284,18 +322,26 @@ function displaySelectedCoordinates(lngLat) {

Please provide the location of the potential mosquito breeding source. We may be able to extract this information from your photos if they contain location data.

-
- {{template "location-geocode"}} + + + + + + + + + + +
+
+ + +
-
-
-
Longitude
-
-
-
-
-
Latitude
-
-
-
+
+
diff --git a/sync/template/district.html b/sync/template/district.html index dbfed900..a75253cd 100644 --- a/sync/template/district.html +++ b/sync/template/district.html @@ -4,6 +4,8 @@ {{define "extraheader"}} + + @@ -11,7 +13,7 @@ const MAPBOX_ACCESS_TOKEN = '{{.MapboxToken}}'; function setDistrictColors(map) { const features = map.querySourceFeatures('tegola-mosquito', {sourceLayer: 'district'}); - console.log("features", features); + //console.log("features", features); const regionIds = [...new Set(features.map(f => f.properties.regionid))]; map.setPaintProperty('districts', 'fill-opacity', 0.4); if (regionIds.length > 0) { @@ -26,11 +28,11 @@ function setDistrictColors(map) { matchExpression.push(id, colorScale[id]); }); matchExpression.push('#cccccc'); // Default color - console.log("using district coloring", matchExpression); + //console.log("using district coloring", matchExpression); map.setPaintProperty('districts', 'fill-color', matchExpression); } else { map.setPaintProperty('districts', 'fill-color', 'rgb(250, 100, 100)'); - console.log("using fallback district coloring"); + //console.log("using fallback district coloring"); } } function onLoad() { @@ -88,7 +90,6 @@ function onLoad() { } }); map.on('sourcedata', (e) => { - console.log("source loaded", e); if (e.sourceId == 'tegola-mosquito' && e.isSourceLoaded) { setDistrictColors(map) } @@ -98,6 +99,26 @@ function onLoad() { map.addControl(new mapboxgl.NavigationControl()); console.log("Map init done."); + + /*const address = document.getElementById('address'); + const suggestionsContainer = document.getElementById('suggestions'); + addressSuggestionEnable(address, suggestionsContainer);*/ + const addressDisplay = document.querySelector("address-display"); + const addressInput = document.querySelector("address-input"); + const mapComponent = document.querySelector("map-component"); + addressInput.addEventListener("address-selected", (event) => { + const l = event.detail.location; + // Center map on selected address + + //mapComponent.jumpTo(coordinates); + + // Add marker for selected address + /*mapComponent.addMarker(coordinates, { + title: event.detail.address + });*/ + + addressDisplay.show(l); + }); } document.addEventListener("DOMContentLoaded", onLoad); @@ -176,10 +197,23 @@ body { {{end}} {{define "content"}}
+
+
+ + +
+
+
+
+
+ +
{{end}} From ea48364d958a00e971cdc62d4054ef3c1c90d060 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 18:59:25 +0000 Subject: [PATCH 0021/1453] Set status when creating reports Because we added this as a non-null required field a migration or two ago. --- public-report/nuisance.go | 1 + public-report/pool.go | 2 ++ public-report/quick.go | 2 ++ 3 files changed, 5 insertions(+) diff --git a/public-report/nuisance.go b/public-report/nuisance.go index a2eae7c4..e262c64d 100644 --- a/public-report/nuisance.go +++ b/public-report/nuisance.go @@ -135,6 +135,7 @@ func postNuisance(w http.ResponseWriter, r *http.Request) { SourceRoof: omit.From(source_roof), SourceLocation: omit.From(source_location), SourceStagnant: omit.From(source_stagnant), + Status: omit.From(enums.PublicreportReportstatustypeReported), TimeOfDayDay: omit.From(tod_day), TimeOfDayEarly: omit.From(tod_early), TimeOfDayEvening: omit.From(tod_evening), diff --git a/public-report/pool.go b/public-report/pool.go index 8954f24e..5f5d5004 100644 --- a/public-report/pool.go +++ b/public-report/pool.go @@ -8,6 +8,7 @@ import ( "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/htmlpage" "github.com/aarondl/opt/omit" @@ -117,6 +118,7 @@ func postPool(w http.ResponseWriter, r *http.Request) { ReporterEmail: omit.From(reporter_email), ReporterName: omit.From(reporter_name), ReporterPhone: omit.From(reporter_phone), + Status: omit.From(enums.PublicreportReportstatustypeReported), Subscribe: omit.From(subscribe), } pool, err := models.PublicreportPools.Insert(&setter).One(r.Context(), db.PGInstance.BobDB) diff --git a/public-report/quick.go b/public-report/quick.go index 9f38e362..c6498920 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -7,6 +7,7 @@ import ( "time" "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" @@ -79,6 +80,7 @@ func postQuick(w http.ResponseWriter, r *http.Request) { PublicID: omit.From(u), ReporterEmail: omit.From(""), ReporterPhone: omit.From(""), + Status: omit.From(enums.PublicreportReportstatustypeReported), } quick, err := models.PublicreportQuicks.Insert(&setter).One(r.Context(), db.PGInstance.BobDB) if err != nil { From 749f8aaec79154e279583e7d2d9f5562bc3b4527 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 20:14:58 +0000 Subject: [PATCH 0022/1453] Initial work on creating custom map component This isn't done, I'm just shifting gears. --- htmlpage/static/js/address-suggestion.js | 27 --- htmlpage/static/js/map-locator.js | 215 +++++++++++++++++++++++ htmlpage/static/js/map.js | 62 ------- public-report/template/pool.html | 50 ++---- 4 files changed, 227 insertions(+), 127 deletions(-) create mode 100644 htmlpage/static/js/map-locator.js delete mode 100644 htmlpage/static/js/map.js diff --git a/htmlpage/static/js/address-suggestion.js b/htmlpage/static/js/address-suggestion.js index 8b63b3df..29a1ce91 100644 --- a/htmlpage/static/js/address-suggestion.js +++ b/htmlpage/static/js/address-suggestion.js @@ -197,30 +197,3 @@ class AddressInput extends HTMLElement { } customElements.define('address-input', AddressInput); - -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; -} diff --git a/htmlpage/static/js/map-locator.js b/htmlpage/static/js/map-locator.js new file mode 100644 index 00000000..d9c362b2 --- /dev/null +++ b/htmlpage/static/js/map-locator.js @@ -0,0 +1,215 @@ +var map = null; +// A map that can be used to locate a single point by setting its location explicitly +// or by allowing the user to move a marker. +class MapLocator extends HTMLElement { + constructor() { + super(); + + // Create a shadow DOM + this.attachShadow({mode: "open" }); + + // Initial render + this.render(); + + // markers shown on the map. Should be none or 1, generally. + this._markers = null; + } + + // Lifecycle: when element is added to the DOM + connectedCallback() { + // Initialize the map when the element is added to the DOM + setTimeout(() => this._initializeMap(), 0); + } + + disconnectedCallback() { + if (this._map) { + this._map.remove(); + } + } + + // Lifecycle: watch these attributes for changes + static get observedAttributes() { + return ['api-key', 'latitude', 'longitude', 'zoom']; + } + + // Lifecycle: respond to attribute changes + attributeChangedCallback(name, oldValue, newValue) { + // Only handle if map exists and values actually changed + if (!this._map || oldValue === newValue) return; + + if (name === 'api-key') { + this._apiKey = newValue; + } + + if (name === 'latitude' || name === 'longitude') { + if (this.hasAttribute('latitude') && this.hasAttribute('longitude')) { + const lat = Number(this.getAttribute('latitude')); + const lng = Number(this.getAttribute('longitude')); + this._map.setCenter([lat, lng]); + } + } + + if (name === 'zoom') { + this._map.setZoom(Number(newValue)); + } + } + + _initializeMap() { + console.log("Setting up the map..."); + const lat = Number(this.getAttribute('latitude') || 36.2); + const lng = Number(this.getAttribute('longitude') || -119.2); + const zoom = Number(this.getAttribute('zoom') || 15); + + mapboxgl.accessToken = this._apiKey; + map = new mapboxgl.Map({ + container: "map", + center: { + lat: lat, + lng: lng, + }, + style: 'mapbox://styles/mapbox/streets-v12', // style URL + zoom: zoom, + }); + map.addControl(new mapboxgl.GeolocateControl({ + positionOptions: { + enableHighAccuracy: true + }, + trackUserLocation: true, + showUserHeading: true + })); + map.addControl(new mapboxgl.NavigationControl()); + map.on("load", function() { + this.dispatchEvent(new CustomEvent('load') { + bubbles: true, + composed: true, // Allows event to cross shadow DOM boundary + detail: { + map: this + } + }); + }); + } + + async _fetchAddressSuggestions(text) { + try { + const url = `https://api.mapbox.com/search/geocode/v6/forward?q=${encodeURIComponent(text)}&access_token=${this._apiKey}`; + + const response = await fetch(url); + const data = await response.json(); + return data; + } catch (error) { + console.error('Error fetching geocoding suggestions:', error); + } + } + + _renderSuggestions(suggestions) { + console.log("Rendering suggestions", suggestions); + this._suggestions.innerHTML = suggestions.map((item, index) => { + if (item.properties.place_formatted != "") { + return ` +
+
${item.properties.name || item.properties.full_address}
+
${item.properties.place_formatted}
+
` + } else { + return ` +
+
${item.properties.name || item.properties.full_address}
+
${item.properties.place_formatted}
+
` + } + }).join(''); + + // Add click listeners to suggestions + this.shadowRoot.querySelectorAll('.suggestion-item').forEach(el => { + el.addEventListener('click', e => { + const index = parseInt(el.dataset.index); + const suggestion = suggestions[index]; + this.value = suggestion.properties.full_address; + this._suggestions.innerHTML = ''; + + // Dispatch custom event + this.dispatchEvent(new CustomEvent('address-selected', { + bubbles: true, + composed: true, // Allows event to cross shadow DOM boundary + detail: { + location: suggestion + } + })); + }); + }); + } + + // Initial render of component + render() { + this.shadowRoot.innerHTML = ` + + +
+
+
+ `; + } + + jumpTo(args) { + this._map.jumpTo(args); + } + + setMarker(coords) { + console.log("Setting map marker", coords); + this._map.jumpTo({ + center: coords, + zoom: 14, + }); + this._markers.forEach((marker) => marker.remove()); + + const marker = new mapboxgl.Marker({ + color: "#FF0000", + draggable: true + }).setLngLat(coords).addTo(map); + marker.on('dragend', function(e) { + const markerDraggedEvent = new CustomEvent("markerdragend", { + detail: { + marker: marker + } + }); + mapContainer.dispatchEvent(markerDraggedEvent); + }); + this._markers = [marker]; + } +} + +customElements.define('map-locator', MapLocator); + + + +function mapLoad(MAPBOX_ACCESS_TOKEN) { + return new Promise((resolve, reject) => { + }); +} diff --git a/htmlpage/static/js/map.js b/htmlpage/static/js/map.js deleted file mode 100644 index 3bdf7ace..00000000 --- a/htmlpage/static/js/map.js +++ /dev/null @@ -1,62 +0,0 @@ -var map = null; -var markers = []; - -function mapAddMarker(coords) { - const mapContainer = document.getElementById("map-container"); - const marker = new mapboxgl.Marker({ - color: "#FF0000", - draggable: true - }).setLngLat(coords).addTo(map); - marker.on('dragend', function(e) { - const markerDraggedEvent = new CustomEvent("markerdragend", { - detail: { - marker: marker - } - }); - mapContainer.dispatchEvent(markerDraggedEvent); - }); - markers.push(marker); -} - -function mapLoad(MAPBOX_ACCESS_TOKEN) { - return new Promise((resolve, reject) => { - 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 loaded."); - resolve(map); - }); - }); -} - -function mapJumpTo(args) { - map.jumpTo(args); -} - -function mapSetMarker(coords) { - console.log("Setting map marker", coords); - map.jumpTo({ - center: coords, - zoom: 14, - }); - markers.forEach((marker) => marker.remove()); - mapAddMarker(coords); -} - diff --git a/public-report/template/pool.html b/public-report/template/pool.html index 70390fdf..d97f2423 100644 --- a/public-report/template/pool.html +++ b/public-report/template/pool.html @@ -8,7 +8,7 @@ - + {{template "photo-upload-header"}} {{end}} {{define "content"}}
- -
-
- Your Logo -
-
-
@@ -90,8 +73,9 @@
-

Account Information

-

What you need to know

+
+ +
Who should register?
From e1684ce8f1b995d433df28bdb8c6172702d71001 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 20:53:46 +0000 Subject: [PATCH 0025/1453] Remove "since last week" placeholders from dashboard Also add the cute "...?" when syncing. --- sync/template/dashboard.html | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/sync/template/dashboard.html b/sync/template/dashboard.html index d43918e8..36347afd 100644 --- a/sync/template/dashboard.html +++ b/sync/template/dashboard.html @@ -202,7 +202,7 @@ body {
Last Data Refresh

{{ .LastSync | timeSince }}

-

Last sync: 12:45 PM

+
@@ -215,12 +215,16 @@ body {
Service Requests
-

{{ .CountServiceRequests | bigNumber }}

-

+ {{ if .IsSyncOngoing }} +

{{ .CountServiceRequests | bigNumber }}...?

+ {{ else }} +

{{ .CountServiceRequests | bigNumber }}

+ {{ end }} +
@@ -233,12 +237,16 @@ body {
Mosquito Sources
-

{{ .CountMosquitoSources | bigNumber }}

-

+ {{ if .IsSyncOngoing }} +

{{ .CountMosquitoSources | bigNumber }}..?

+ {{ else }} +

{{ .CountMosquitoSources | bigNumber }}

+ {{ end }} +
@@ -251,12 +259,16 @@ body {
Inspections
-

{{ .CountInspections | bigNumber }}

-

+ {{ if .IsSyncOngoing }} +

{{ .CountInspections | bigNumber }}...?

+ {{ else }} +

{{ .CountInspections | bigNumber }}

+ {{ end }} +
From 21a8f9622a572e584965393a9aacb70b9612e50c Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 21:36:58 +0000 Subject: [PATCH 0026/1453] Avoid empty insert statement when there are no aggregations --- background/arcgis.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/background/arcgis.go b/background/arcgis.go index 6237ccef..6997a2ec 100644 --- a/background/arcgis.go +++ b/background/arcgis.go @@ -1075,6 +1075,10 @@ func updateSummaryTables(ctx context.Context, org *models.Organization) { im.SetCol("count_").To(psql.Raw("EXCLUDED.count_")), )) //log.Info().Str("sql", insertQueryToString(psql.Insert(to_insert...))).Msg("Updating...") + if len(to_insert) == 0 { + log.Info().Int("resolution", i).Msg("No updates to perform") + continue + } _, err := psql.Insert(to_insert...).Exec(ctx, db.PGInstance.BobDB) if err != nil { log.Error().Err(err).Msg("Faild to add h3 aggregation") From 4f0b73c769a2dce7bbdc11704606f84cf6f9fde0 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 21:39:58 +0000 Subject: [PATCH 0027/1453] Add URL for Tegola to configuration Avoid cross-environment pollution. --- config/config.go | 6 +++++- sync/dash.go | 8 ++++++++ sync/template/dashboard.html | 6 ++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/config/config.go b/config/config.go index 94184e6a..1b6ac507 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,7 @@ import ( "strconv" ) -var Bind, ClientID, ClientSecret, Environment, FieldseekerSchemaDirectory, MapboxToken, PGDSN, URLReport, URLSync, FilesDirectoryPublic, FilesDirectoryUser string +var Bind, ClientID, ClientSecret, Environment, FilesDirectoryPublic, FilesDirectoryUser, FieldseekerSchemaDirectory, MapboxToken, PGDSN, URLReport, URLSync, URLTegola string // Build the ArcGIS authorization URL with PKCE func BuildArcGISAuthURL(clientID string) string { @@ -59,6 +59,10 @@ func Parse() error { if URLSync == "" { return fmt.Errorf("You must specify a non-empty URL_SYNC") } + URLTegola = os.Getenv("URL_TEGOLA") + if URLTegola == "" { + return fmt.Errorf("You must specify a non-empty URL_TEGOLA") + } Bind = os.Getenv("BIND") if Bind == "" { Bind = ":9001" diff --git a/sync/dash.go b/sync/dash.go index ae855655..e66a2fe0 100644 --- a/sync/dash.go +++ b/sync/dash.go @@ -29,7 +29,12 @@ var ( sourceT = buildTemplate("source", "authenticated") ) +type Config struct { + URLTegola string +} + type ContextDashboard struct { + Config Config CountInspections int CountMosquitoSources int CountServiceRequests int @@ -233,6 +238,9 @@ func dashboard(ctx context.Context, w http.ResponseWriter, user *models.User) { return } data := ContextDashboard{ + Config: Config{ + URLTegola: config.URLTegola, + }, CountInspections: int(inspectionCount), CountMosquitoSources: int(sourceCount), CountServiceRequests: int(serviceCount), diff --git a/sync/template/dashboard.html b/sync/template/dashboard.html index 36347afd..4dbb7ceb 100644 --- a/sync/template/dashboard.html +++ b/sync/template/dashboard.html @@ -20,8 +20,7 @@ function onLoad() { map.addSource('tegola-bonn', { 'type': 'vector', 'tiles': [ - //'https://tiles.mapillary.com/maps/vtp/mly1_public/2/{z}/{x}/{y}?access_token=MLY|4142433049200173|72206abe5035850d6743b23a49c41333' - 'https://tegola.nidus.cloud/maps/bonn/{z}/{x}/{y}' + 'https://{{.Config.URLTegola}}/maps/bonn/{z}/{x}/{y}' ] //'minzoom': 6, //'maxzoom': 14 @@ -29,8 +28,7 @@ function onLoad() { map.addSource('tegola-nidus', { 'type': 'vector', 'tiles': [ - //'https://tiles.mapillary.com/maps/vtp/mly1_public/2/{z}/{x}/{y}?access_token=MLY|4142433049200173|72206abe5035850d6743b23a49c41333' - 'https://tegola.nidus.cloud/maps/nidus/{z}/{x}/{y}?organization_id=1' + 'https://{{.Config.URLTegola}}/maps/nidus/{z}/{x}/{y}?organization_id={{.User.OrganizationID}}' ] //'minzoom': 6, //'maxzoom': 14 From a4c0e367a8bba90ca7a0251a7a08f5f94b6f7ffb Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 21:49:12 +0000 Subject: [PATCH 0028/1453] Make organization.name not-nullable, consolidate org in dash context --- auth/auth.go | 2 +- db/dbinfo/district.bob.go | 12 +++- db/dbinfo/organization.bob.go | 4 +- db/factory/bobfactory_main.bob.go | 3 +- db/factory/district.bob.go | 58 +++++++++++++++++++ db/factory/organization.bob.go | 40 ++++--------- .../00030_organization_name_not_null.sql | 5 ++ db/models/district.bob.go | 7 ++- db/models/organization.bob.go | 20 +++---- sync/dash.go | 2 - sync/template/dashboard.html | 4 +- sync/types.go | 6 +- sync/utils.go | 5 ++ 13 files changed, 118 insertions(+), 50 deletions(-) create mode 100644 db/migrations/00030_organization_name_not_null.sql diff --git a/auth/auth.go b/auth/auth.go index 24a372fc..83a6feee 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -131,7 +131,7 @@ func SignupUser(ctx context.Context, username string, name string, password stri return nil, fmt.Errorf("Cannot signup user, failed to create hashed password: %w", err) } o_setter := models.OrganizationSetter{ - Name: omitnull.From(fmt.Sprintf("%s's organization", username)), + Name: omit.From(fmt.Sprintf("%s's organization", username)), ArcgisID: omitnull.From(""), ArcgisName: omitnull.From(""), FieldseekerURL: omitnull.From(""), diff --git a/db/dbinfo/district.bob.go b/db/dbinfo/district.bob.go index bedd3bf0..11976f13 100644 --- a/db/dbinfo/district.bob.go +++ b/db/dbinfo/district.bob.go @@ -213,6 +213,15 @@ var Districts = Table[ Generated: false, AutoIncr: false, }, + Geom4326: column{ + Name: "geom_4326", + DBType: "geometry", + Default: "GENERATED", + Comment: "", + Nullable: true, + Generated: true, + AutoIncr: false, + }, }, Indexes: districtIndexes{ DistrictPkey: index{ @@ -282,11 +291,12 @@ type districtColumns struct { ShapeLe1 column ShapeArea column Geom column + Geom4326 column } func (c districtColumns) AsSlice() []column { return []column{ - c.Gid, c.ID, c.Website, c.Contact, c.Address, c.Regionid, c.PostalCod, c.Phone1, c.Fax1, c.Agency, c.Code1, c.City1, c.ShapeLeng, c.Address2, c.GeneralMG, c.City2, c.PostalC1, c.Fax2, c.Phone2, c.ShapeLe1, c.ShapeArea, c.Geom, + c.Gid, c.ID, c.Website, c.Contact, c.Address, c.Regionid, c.PostalCod, c.Phone1, c.Fax1, c.Agency, c.Code1, c.City1, c.ShapeLeng, c.Address2, c.GeneralMG, c.City2, c.PostalC1, c.Fax2, c.Phone2, c.ShapeLe1, c.ShapeArea, c.Geom, c.Geom4326, } } diff --git a/db/dbinfo/organization.bob.go b/db/dbinfo/organization.bob.go index 7b707d42..c65f22a4 100644 --- a/db/dbinfo/organization.bob.go +++ b/db/dbinfo/organization.bob.go @@ -27,9 +27,9 @@ var Organizations = Table[ Name: column{ Name: "name", DBType: "text", - Default: "NULL", + Default: "", Comment: "", - Nullable: true, + Nullable: false, Generated: false, AutoIncr: false, }, diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index 02a1eb5f..cae5658a 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -118,6 +118,7 @@ func (f *Factory) FromExistingDistrict(m *models.District) *DistrictTemplate { o.ShapeLe1 = func() null.Val[decimal.Decimal] { return m.ShapeLe1 } o.ShapeArea = func() null.Val[decimal.Decimal] { return m.ShapeArea } o.Geom = func() null.Val[string] { return m.Geom } + o.Geom4326 = func() null.Val[string] { return m.Geom4326 } return o } @@ -2317,7 +2318,7 @@ func (f *Factory) FromExistingOrganization(m *models.Organization) *Organization o := &OrganizationTemplate{f: f, alreadyPersisted: true} o.ID = func() int32 { return m.ID } - o.Name = func() null.Val[string] { return m.Name } + o.Name = func() string { return m.Name } o.ArcgisID = func() null.Val[string] { return m.ArcgisID } o.ArcgisName = func() null.Val[string] { return m.ArcgisName } o.FieldseekerURL = func() null.Val[string] { return m.FieldseekerURL } diff --git a/db/factory/district.bob.go b/db/factory/district.bob.go index 5f8f5c01..4a25435f 100644 --- a/db/factory/district.bob.go +++ b/db/factory/district.bob.go @@ -59,6 +59,7 @@ type DistrictTemplate struct { ShapeLe1 func() null.Val[decimal.Decimal] ShapeArea func() null.Val[decimal.Decimal] Geom func() null.Val[string] + Geom4326 func() null.Val[string] f *Factory @@ -257,6 +258,9 @@ func (o DistrictTemplate) Build() *models.District { if o.Geom != nil { m.Geom = o.Geom() } + if o.Geom4326 != nil { + m.Geom4326 = o.Geom4326() + } o.setModelRels(m) @@ -399,6 +403,7 @@ func (m districtMods) RandomizeAllColumns(f *faker.Faker) DistrictMod { DistrictMods.RandomShapeLe1(f), DistrictMods.RandomShapeArea(f), DistrictMods.RandomGeom(f), + DistrictMods.RandomGeom4326(f), } } @@ -1546,6 +1551,59 @@ func (m districtMods) RandomGeomNotNull(f *faker.Faker) DistrictMod { }) } +// Set the model columns to this value +func (m districtMods) Geom4326(val null.Val[string]) DistrictMod { + return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { + o.Geom4326 = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m districtMods) Geom4326Func(f func() null.Val[string]) DistrictMod { + return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { + o.Geom4326 = f + }) +} + +// Clear any values for the column +func (m districtMods) UnsetGeom4326() DistrictMod { + return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { + o.Geom4326 = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m districtMods) RandomGeom4326(f *faker.Faker) DistrictMod { + return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { + o.Geom4326 = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m districtMods) RandomGeom4326NotNull(f *faker.Faker) DistrictMod { + return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { + o.Geom4326 = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + func (m districtMods) WithParentsCascading() DistrictMod { return DistrictModFunc(func(ctx context.Context, o *DistrictTemplate) { if isDone, _ := districtWithParentsCascadingCtx.Value(ctx); isDone { diff --git a/db/factory/organization.bob.go b/db/factory/organization.bob.go index 79f6a071..f7567bd7 100644 --- a/db/factory/organization.bob.go +++ b/db/factory/organization.bob.go @@ -37,7 +37,7 @@ func (mods OrganizationModSlice) Apply(ctx context.Context, n *OrganizationTempl // all columns are optional and should be set by mods type OrganizationTemplate struct { ID func() int32 - Name func() null.Val[string] + Name func() string ArcgisID func() null.Val[string] ArcgisName func() null.Val[string] FieldseekerURL func() null.Val[string] @@ -650,7 +650,7 @@ func (o OrganizationTemplate) BuildSetter() *models.OrganizationSetter { } if o.Name != nil { val := o.Name() - m.Name = omitnull.FromNull(val) + m.Name = omit.From(val) } if o.ArcgisID != nil { val := o.ArcgisID() @@ -721,6 +721,10 @@ func (o OrganizationTemplate) BuildMany(number int) models.OrganizationSlice { } func ensureCreatableOrganization(m *models.OrganizationSetter) { + if !(m.Name.IsValue()) { + val := random_string(nil) + m.Name = omit.From(val) + } } // insertOptRels creates and inserts any optional the relationships on *models.Organization @@ -1501,14 +1505,14 @@ func (m organizationMods) RandomID(f *faker.Faker) OrganizationMod { } // Set the model columns to this value -func (m organizationMods) Name(val null.Val[string]) OrganizationMod { +func (m organizationMods) Name(val string) OrganizationMod { return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { - o.Name = func() null.Val[string] { return val } + o.Name = func() string { return val } }) } // Set the Column from the function -func (m organizationMods) NameFunc(f func() null.Val[string]) OrganizationMod { +func (m organizationMods) NameFunc(f func() string) OrganizationMod { return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { o.Name = f }) @@ -1523,32 +1527,10 @@ func (m organizationMods) UnsetName() OrganizationMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used -// The generated value is sometimes null func (m organizationMods) RandomName(f *faker.Faker) OrganizationMod { return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { - o.Name = func() null.Val[string] { - if f == nil { - f = &defaultFaker - } - - val := random_string(f) - return null.From(val) - } - }) -} - -// Generates a random value for the column using the given faker -// if faker is nil, a default faker is used -// The generated value is never null -func (m organizationMods) RandomNameNotNull(f *faker.Faker) OrganizationMod { - return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { - o.Name = func() null.Val[string] { - if f == nil { - f = &defaultFaker - } - - val := random_string(f) - return null.From(val) + o.Name = func() string { + return random_string(f) } }) } diff --git a/db/migrations/00030_organization_name_not_null.sql b/db/migrations/00030_organization_name_not_null.sql new file mode 100644 index 00000000..eecf7b98 --- /dev/null +++ b/db/migrations/00030_organization_name_not_null.sql @@ -0,0 +1,5 @@ +-- +goose Up +ALTER TABLE organization ALTER COLUMN name SET NOT NULL; + +-- +goose Down +ALTER TABLE organization ALTER COLUMN name DROP NOT NULL; diff --git a/db/models/district.bob.go b/db/models/district.bob.go index 851dfcd2..026f4c47 100644 --- a/db/models/district.bob.go +++ b/db/models/district.bob.go @@ -44,6 +44,7 @@ type District struct { ShapeLe1 null.Val[decimal.Decimal] `db:"shape_le_1" ` ShapeArea null.Val[decimal.Decimal] `db:"shape_area" ` Geom null.Val[string] `db:"geom" ` + Geom4326 null.Val[string] `db:"geom_4326,generated" ` } // DistrictSlice is an alias for a slice of pointers to District. @@ -59,7 +60,7 @@ type DistrictsQuery = *psql.ViewQuery[*District, DistrictSlice] func buildDistrictColumns(alias string) districtColumns { return districtColumns{ ColumnsExpr: expr.NewColumnsExpr( - "gid", "id", "website", "contact", "address", "regionid", "postal_cod", "phone1", "fax1", "agency", "code1", "city1", "shape_leng", "address2", "general_mg", "city2", "postal_c_1", "fax2", "phone2", "shape_le_1", "shape_area", "geom", + "gid", "id", "website", "contact", "address", "regionid", "postal_cod", "phone1", "fax1", "agency", "code1", "city1", "shape_leng", "address2", "general_mg", "city2", "postal_c_1", "fax2", "phone2", "shape_le_1", "shape_area", "geom", "geom_4326", ).WithParent("district"), tableAlias: alias, Gid: psql.Quote(alias, "gid"), @@ -84,6 +85,7 @@ func buildDistrictColumns(alias string) districtColumns { ShapeLe1: psql.Quote(alias, "shape_le_1"), ShapeArea: psql.Quote(alias, "shape_area"), Geom: psql.Quote(alias, "geom"), + Geom4326: psql.Quote(alias, "geom_4326"), } } @@ -112,6 +114,7 @@ type districtColumns struct { ShapeLe1 psql.Expression ShapeArea psql.Expression Geom psql.Expression + Geom4326 psql.Expression } func (c districtColumns) Alias() string { @@ -842,6 +845,7 @@ type districtWhere[Q psql.Filterable] struct { ShapeLe1 psql.WhereNullMod[Q, decimal.Decimal] ShapeArea psql.WhereNullMod[Q, decimal.Decimal] Geom psql.WhereNullMod[Q, string] + Geom4326 psql.WhereNullMod[Q, string] } func (districtWhere[Q]) AliasedAs(alias string) districtWhere[Q] { @@ -872,5 +876,6 @@ func buildDistrictWhere[Q psql.Filterable](cols districtColumns) districtWhere[Q ShapeLe1: psql.WhereNull[Q, decimal.Decimal](cols.ShapeLe1), ShapeArea: psql.WhereNull[Q, decimal.Decimal](cols.ShapeArea), Geom: psql.WhereNull[Q, string](cols.Geom), + Geom4326: psql.WhereNull[Q, string](cols.Geom4326), } } diff --git a/db/models/organization.bob.go b/db/models/organization.bob.go index 1139d02c..f9bfcc28 100644 --- a/db/models/organization.bob.go +++ b/db/models/organization.bob.go @@ -26,7 +26,7 @@ import ( // Organization is an object representing the database table. type Organization struct { ID int32 `db:"id,pk" ` - Name null.Val[string] `db:"name" ` + Name string `db:"name" ` ArcgisID null.Val[string] `db:"arcgis_id" ` ArcgisName null.Val[string] `db:"arcgis_name" ` FieldseekerURL null.Val[string] `db:"fieldseeker_url" ` @@ -117,7 +117,7 @@ func (organizationColumns) AliasedAs(alias string) organizationColumns { // Generated columns are not included type OrganizationSetter struct { ID omit.Val[int32] `db:"id,pk" ` - Name omitnull.Val[string] `db:"name" ` + Name omit.Val[string] `db:"name" ` ArcgisID omitnull.Val[string] `db:"arcgis_id" ` ArcgisName omitnull.Val[string] `db:"arcgis_name" ` FieldseekerURL omitnull.Val[string] `db:"fieldseeker_url" ` @@ -128,7 +128,7 @@ func (s OrganizationSetter) SetColumns() []string { if s.ID.IsValue() { vals = append(vals, "id") } - if !s.Name.IsUnset() { + if s.Name.IsValue() { vals = append(vals, "name") } if !s.ArcgisID.IsUnset() { @@ -147,8 +147,8 @@ func (s OrganizationSetter) Overwrite(t *Organization) { if s.ID.IsValue() { t.ID = s.ID.MustGet() } - if !s.Name.IsUnset() { - t.Name = s.Name.MustGetNull() + if s.Name.IsValue() { + t.Name = s.Name.MustGet() } if !s.ArcgisID.IsUnset() { t.ArcgisID = s.ArcgisID.MustGetNull() @@ -174,8 +174,8 @@ func (s *OrganizationSetter) Apply(q *dialect.InsertQuery) { vals[0] = psql.Raw("DEFAULT") } - if !s.Name.IsUnset() { - vals[1] = psql.Arg(s.Name.MustGetNull()) + if s.Name.IsValue() { + vals[1] = psql.Arg(s.Name.MustGet()) } else { vals[1] = psql.Raw("DEFAULT") } @@ -216,7 +216,7 @@ func (s OrganizationSetter) Expressions(prefix ...string) []bob.Expression { }}) } - if !s.Name.IsUnset() { + if s.Name.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "name")...), psql.Arg(s.Name), @@ -3416,7 +3416,7 @@ func (organization0 *Organization) AttachUser(ctx context.Context, exec bob.Exec type organizationWhere[Q psql.Filterable] struct { ID psql.WhereMod[Q, int32] - Name psql.WhereNullMod[Q, string] + Name psql.WhereMod[Q, string] ArcgisID psql.WhereNullMod[Q, string] ArcgisName psql.WhereNullMod[Q, string] FieldseekerURL psql.WhereNullMod[Q, string] @@ -3429,7 +3429,7 @@ func (organizationWhere[Q]) AliasedAs(alias string) organizationWhere[Q] { func buildOrganizationWhere[Q psql.Filterable](cols organizationColumns) organizationWhere[Q] { return organizationWhere[Q]{ ID: psql.Where[Q, int32](cols.ID), - Name: psql.WhereNull[Q, string](cols.Name), + Name: psql.Where[Q, string](cols.Name), ArcgisID: psql.WhereNull[Q, string](cols.ArcgisID), ArcgisName: psql.WhereNull[Q, string](cols.ArcgisName), FieldseekerURL: psql.WhereNull[Q, string](cols.FieldseekerURL), diff --git a/sync/dash.go b/sync/dash.go index e66a2fe0..227f4b19 100644 --- a/sync/dash.go +++ b/sync/dash.go @@ -42,7 +42,6 @@ type ContextDashboard struct { IsSyncOngoing bool LastSync *time.Time MapData ComponentMap - Org string RecentRequests []ServiceRequestSummary User User } @@ -249,7 +248,6 @@ func dashboard(ctx context.Context, w http.ResponseWriter, user *models.User) { MapData: ComponentMap{ MapboxToken: config.MapboxToken, }, - Org: org.Name.MustGet(), RecentRequests: requests, User: userContent, } diff --git a/sync/template/dashboard.html b/sync/template/dashboard.html index 4dbb7ceb..60920964 100644 --- a/sync/template/dashboard.html +++ b/sync/template/dashboard.html @@ -28,7 +28,7 @@ function onLoad() { map.addSource('tegola-nidus', { 'type': 'vector', 'tiles': [ - 'https://{{.Config.URLTegola}}/maps/nidus/{z}/{x}/{y}?organization_id={{.User.OrganizationID}}' + 'https://{{.Config.URLTegola}}/maps/nidus/{z}/{x}/{y}?organization_id={{.User.Organization.ID}}' ] //'minzoom': 6, //'maxzoom': 14 @@ -172,7 +172,7 @@ body {
-

{{ .Org }} Dashboard

+

{{ .User.Organization.Name }} Dashboard

Overview of mosquito control activities in your district

diff --git a/sync/types.go b/sync/types.go index 8bcd0300..b8435495 100644 --- a/sync/types.go +++ b/sync/types.go @@ -94,6 +94,10 @@ type Link struct { Href string Title string } +type Organization struct { + ID int + Name string +} type ServiceRequestSummary struct { Date time.Time Location string @@ -103,6 +107,6 @@ type User struct { DisplayName string Initials string Notifications []notification.Notification - OrganizationID int + Organization Organization Username string } diff --git a/sync/utils.go b/sync/utils.go index 827bfe44..45c0d23b 100644 --- a/sync/utils.go +++ b/sync/utils.go @@ -95,10 +95,15 @@ func contentForUser(ctx context.Context, user *models.User) (User, error) { if err != nil { return User{}, err } + org := user.R.Organization return User{ DisplayName: user.DisplayName, Initials: extractInitials(user.DisplayName), Notifications: notifications, + Organization: Organization { + ID: int(org.ID), + Name: org.Name, + }, Username: user.Username, }, nil From cd22818240455cde8acc3010ce03f724bc3e2c7e Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 21:52:36 +0000 Subject: [PATCH 0029/1453] Actually exit early on empty aggregate Previous attempt was subtly wrong. --- background/arcgis.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/background/arcgis.go b/background/arcgis.go index 6997a2ec..6a32ec53 100644 --- a/background/arcgis.go +++ b/background/arcgis.go @@ -1039,6 +1039,10 @@ func updateSummaryTables(ctx context.Context, org *models.Organization) { log.Error().Err(err).Msg("Failed to get organization") return } + if len(point_locations) == 0 { + log.Info().Int("org_id", int(org.ID)).Msg("No updates to perform") + return + } log.Info().Int("count", len(point_locations)).Msg("Summarizing point locations") for i := range 16 { @@ -1075,10 +1079,6 @@ func updateSummaryTables(ctx context.Context, org *models.Organization) { im.SetCol("count_").To(psql.Raw("EXCLUDED.count_")), )) //log.Info().Str("sql", insertQueryToString(psql.Insert(to_insert...))).Msg("Updating...") - if len(to_insert) == 0 { - log.Info().Int("resolution", i).Msg("No updates to perform") - continue - } _, err := psql.Insert(to_insert...).Exec(ctx, db.PGInstance.BobDB) if err != nil { log.Error().Err(err).Msg("Faild to add h3 aggregation") From c44fe26cdf226a8c8e112159238bc96dfcc92f78 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 21:52:59 +0000 Subject: [PATCH 0030/1453] Remove debugging bonn layers --- sync/template/dashboard.html | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/sync/template/dashboard.html b/sync/template/dashboard.html index 60920964..420b521d 100644 --- a/sync/template/dashboard.html +++ b/sync/template/dashboard.html @@ -17,14 +17,6 @@ function onLoad() { }); map.on("load", function() { console.log("Map post-load..."); - map.addSource('tegola-bonn', { - 'type': 'vector', - 'tiles': [ - 'https://{{.Config.URLTegola}}/maps/bonn/{z}/{x}/{y}' - ] - //'minzoom': 6, - //'maxzoom': 14 - }); map.addSource('tegola-nidus', { 'type': 'vector', 'tiles': [ @@ -33,17 +25,6 @@ function onLoad() { //'minzoom': 6, //'maxzoom': 14 }); - map.addLayer({ - 'id': 'bonn', // Layer ID - 'type': 'fill', - 'source': 'tegola-bonn', // ID of the tile source created above - 'source-layer': 'lakes', - 'paint': { - 'fill-opacity': 0.1, - 'fill-color': 'rgb(100, 50, 20)' - } - //slot: 'middle' // middle slot in Mapbox Standard style - }); map.addLayer({ 'id': 'nidus', // Layer ID 'type': 'fill', From 35721e7fa678e01281cff8cb588a760bd6ae9c8b Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 14 Jan 2026 22:18:11 +0000 Subject: [PATCH 0031/1453] Add some handy tools --- tools/README.md | 7 ++++++ tools/delete-all-fieldseeker.sql | 26 ++++++++++++++++++++ tools/delete-org.sql | 41 ++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 tools/README.md create mode 100644 tools/delete-all-fieldseeker.sql create mode 100644 tools/delete-org.sql diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 00000000..a6adf6c9 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,7 @@ +# Tools + +Useful for doing one-off developer types of work. Can be run with: + +``` +psql -d nidus-sync -v org_id=3 -f tools/delete-org.sql +``` diff --git a/tools/delete-all-fieldseeker.sql b/tools/delete-all-fieldseeker.sql new file mode 100644 index 00000000..0f7a5391 --- /dev/null +++ b/tools/delete-all-fieldseeker.sql @@ -0,0 +1,26 @@ +TRUNCATE fieldseeker.containerrelate; +TRUNCATE fieldseeker.fieldscoutinglog; +TRUNCATE fieldseeker.habitatrelate; +TRUNCATE fieldseeker.inspectionsample; +TRUNCATE fieldseeker.inspectionsampledetail; +TRUNCATE fieldseeker.linelocation; +TRUNCATE fieldseeker.locationtracking; +TRUNCATE fieldseeker.mosquitoinspection; +TRUNCATE fieldseeker.pointlocation; +TRUNCATE fieldseeker.polygonlocation; +TRUNCATE fieldseeker.pool; +TRUNCATE fieldseeker.pooldetail; +TRUNCATE fieldseeker.proposedtreatmentarea; +TRUNCATE fieldseeker.qamosquitoinspection; +TRUNCATE fieldseeker.rodentlocation; +TRUNCATE fieldseeker.samplecollection; +TRUNCATE fieldseeker.samplelocation; +TRUNCATE fieldseeker.servicerequest; +TRUNCATE fieldseeker.speciesabundance; +TRUNCATE fieldseeker.stormdrain; +TRUNCATE fieldseeker.timecard; +TRUNCATE fieldseeker.trapdata; +TRUNCATE fieldseeker.traplocation; +TRUNCATE fieldseeker.treatment; +TRUNCATE fieldseeker.treatmentarea; +TRUNCATE fieldseeker.zones; diff --git a/tools/delete-org.sql b/tools/delete-org.sql new file mode 100644 index 00000000..6bc09998 --- /dev/null +++ b/tools/delete-org.sql @@ -0,0 +1,41 @@ +-- delete-org.sql +BEGIN; + DELETE FROM public.oauth_token WHERE user_id IN (SELECT id FROM public.user_ WHERE organization_id = :org_id); + DELETE FROM public.notification WHERE user_id IN (SELECT id FROM public.user_ WHERE organization_id = :org_id); + DELETE FROM public.note_audio WHERE creator_id IN (SELECT id FROM public.user_ WHERE organization_id = :org_id); + DELETE FROM public.note_audio WHERE deletor_id IN (SELECT id FROM public.user_ WHERE organization_id = :org_id); + DELETE FROM public.note_image WHERE creator_id IN (SELECT id FROM public.user_ WHERE organization_id = :org_id); + DELETE FROM public.note_image WHERE deletor_id IN (SELECT id FROM public.user_ WHERE organization_id = :org_id); + DELETE FROM public.user_ WHERE organization_id = :org_id; + DELETE FROM public.fieldseeker_sync WHERE organization_id = :org_id; + DELETE FROM public.h3_aggregation WHERE organization_id = :org_id; + DELETE FROM public.note_audio WHERE organization_id = :org_id; + DELETE FROM public.note_image WHERE organization_id = :org_id; + DELETE FROM fieldseeker.containerrelate WHERE organization_id = :org_id; + DELETE FROM fieldseeker.fieldscoutinglog WHERE organization_id = :org_id; + DELETE FROM fieldseeker.habitatrelate WHERE organization_id = :org_id; + DELETE FROM fieldseeker.inspectionsample WHERE organization_id = :org_id; + DELETE FROM fieldseeker.inspectionsampledetail WHERE organization_id = :org_id; + DELETE FROM fieldseeker.linelocation WHERE organization_id = :org_id; + DELETE FROM fieldseeker.locationtracking WHERE organization_id = :org_id; + DELETE FROM fieldseeker.mosquitoinspection WHERE organization_id = :org_id; + DELETE FROM fieldseeker.pointlocation WHERE organization_id = :org_id; + DELETE FROM fieldseeker.polygonlocation WHERE organization_id = :org_id; + DELETE FROM fieldseeker.pool WHERE organization_id = :org_id; + DELETE FROM fieldseeker.pooldetail WHERE organization_id = :org_id; + DELETE FROM fieldseeker.proposedtreatmentarea WHERE organization_id = :org_id; + DELETE FROM fieldseeker.qamosquitoinspection WHERE organization_id = :org_id; + DELETE FROM fieldseeker.rodentlocation WHERE organization_id = :org_id; + DELETE FROM fieldseeker.samplecollection WHERE organization_id = :org_id; + DELETE FROM fieldseeker.samplelocation WHERE organization_id = :org_id; + DELETE FROM fieldseeker.servicerequest WHERE organization_id = :org_id; + DELETE FROM fieldseeker.speciesabundance WHERE organization_id = :org_id; + DELETE FROM fieldseeker.stormdrain WHERE organization_id = :org_id; + DELETE FROM fieldseeker.timecard WHERE organization_id = :org_id; + DELETE FROM fieldseeker.trapdata WHERE organization_id = :org_id; + DELETE FROM fieldseeker.traplocation WHERE organization_id = :org_id; + DELETE FROM fieldseeker.treatment WHERE organization_id = :org_id; + DELETE FROM fieldseeker.treatmentarea WHERE organization_id = :org_id; + DELETE FROM fieldseeker.zones WHERE organization_id = :org_id; + DELETE FROM fieldseeker.zones2 WHERE organization_id = :org_id; +COMMIT; From 92294e5b16ee4fb2573f2337dd51c9d9c328200b Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 15 Jan 2026 00:20:19 +0000 Subject: [PATCH 0032/1453] Creat signout logic, make links use it. --- auth/auth.go | 6 ++++++ sync/routes.go | 1 + sync/signin.go | 6 ++++++ sync/template/components/header.html | 2 +- sync/template/signup.html | 2 +- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 83a6feee..7548ecca 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -125,6 +125,12 @@ func SigninUser(r *http.Request, username string, password string) (*models.User return user, nil } +func SignoutUser(r *http.Request, user *models.User) { + sessionManager.Put(r.Context(), "user_id", "") + sessionManager.Put(r.Context(), "username", "") + log.Info().Str("username", user.Username).Int32("user_id", user.ID).Msg("Ended user session") +} + func SignupUser(ctx context.Context, username string, name string, password string) (*models.User, error) { passwordHash, err := hashPassword(password) if err != nil { diff --git a/sync/routes.go b/sync/routes.go index 8c57a09b..89cf6444 100644 --- a/sync/routes.go +++ b/sync/routes.go @@ -52,6 +52,7 @@ func Router() chi.Router { r.Get("/qr-code/report/{code}", getQRCodeReport) r.Get("/signin", getSignin) r.Post("/signin", postSignin) + r.Method("GET", "/signout", auth.NewEnsureAuth(getSignout)) r.Get("/signup", getSignup) r.Post("/signup", postSignup) r.Get("/sms", getSMS) diff --git a/sync/signin.go b/sync/signin.go index 180b119c..b1f9b63f 100644 --- a/sync/signin.go +++ b/sync/signin.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/Gleipnir-Technology/nidus-sync/auth" + "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/rs/zerolog/log" ) @@ -20,6 +21,11 @@ func getSignin(w http.ResponseWriter, r *http.Request) { signin(w, errorCode) } +func getSignout(w http.ResponseWriter, r *http.Request, user *models.User) { + auth.SignoutUser(r, user) + http.Redirect(w, r, "/signin", http.StatusFound) +} + func getSignup(w http.ResponseWriter, r *http.Request) { signup(w, r.URL.Path) } diff --git a/sync/template/components/header.html b/sync/template/components/header.html index 93e806df..00f43dad 100644 --- a/sync/template/components/header.html +++ b/sync/template/components/header.html @@ -70,7 +70,7 @@
diff --git a/sync/template/signup.html b/sync/template/signup.html index a44e93da..57d2b072 100644 --- a/sync/template/signup.html +++ b/sync/template/signup.html @@ -65,7 +65,7 @@
-

Already have an account? Sign in

+

Already have an account? Sign in

From 248cffd323cc92d82f02c1506aa6aa8d7dddaa9e Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 15 Jan 2026 01:05:21 +0000 Subject: [PATCH 0033/1453] Rework arcgis oauth flow logic This is several changes after the demo with Ben * When a user adds their oauth and they get an arcgis ID for an organization that exits they are added to that organization. The previous org isn't removed * All layer processing is done in a single large pool. This makes it much faster in aggregate * Some queries are done more directly instead of through custom sql --- background/arcgis.go | 173 ++++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 85 deletions(-) diff --git a/background/arcgis.go b/background/arcgis.go index 6a32ec53..cf6836a1 100644 --- a/background/arcgis.go +++ b/background/arcgis.go @@ -39,6 +39,8 @@ import ( "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/sm" + "github.com/stephenafamo/bob/dialect/psql/um" "github.com/uber/h3-go/v4" ) @@ -245,18 +247,49 @@ func updateArcgisUserData(ctx context.Context, user *models.User, access_token s } log.Info().Str("Username", portal.User.Username).Str("user_id", portal.User.ID).Str("org_id", portal.User.OrgID).Str("org_name", portal.Name).Str("license_type_id", portal.User.UserLicenseTypeID).Msg("Got portals data") - _, err = sql.UpdateOauthTokenOrg(portal.User.ID, portal.User.UserLicenseTypeID, refresh_token).Exec(ctx, db.PGInstance.BobDB) + _, err = models.OauthTokens.Update( + //um.SetCol(string(models.OauthTokens.Columns.ArcgisID)).ToArg(portal.User.ID), + //um.SetCol(string(models.OauthTokens.Columns.ArcgisLicenseTypeID)).ToArg(portal.User.UserLicenseTypeID), + um.SetCol("arcgis_id").ToArg(portal.User.ID), + um.SetCol("arcgis_license_type_id").ToArg(portal.User.UserLicenseTypeID), + um.Where(models.OauthTokens.Columns.RefreshToken.EQ(psql.Arg(refresh_token))), + ).Exec(ctx, db.PGInstance.BobDB) + //_, err = sql.UpdateOauthTokenOrg(portal.User.ID, portal.User.UserLicenseTypeID, refresh_token).Exec(ctx, db.PGInstance.BobDB) if err != nil { log.Error().Err(err).Msg("Failed to update oauth token portal data") return } - org := user.R.Organization - err = org.Update(ctx, db.PGInstance.BobDB, &models.OrganizationSetter{ - ArcgisID: omitnull.From(portal.User.OrgID), - ArcgisName: omitnull.From(portal.Name), - }) + // At this point we have the arcgis ID. If the ID matches an existing ID, join it with the users organization + orgs, err := models.Organizations.Query( + sm.Where( + psql.Quote("arcgis_id").EQ(psql.Arg(portal.User.OrgID)), + ), + ).All(ctx, db.PGInstance.BobDB) if err != nil { - log.Error().Err(err).Int32("id", user.R.Organization.ID).Msg("Failed to update organization's arcgis info") + log.Error().Err(err).Str("arcgis_id", portal.User.OrgID).Msg("Failed to search for organization with arcgis id") + return + } + var org *models.Organization + switch len(orgs) { + case 0: + org = user.R.Organization + err = org.Update(ctx, db.PGInstance.BobDB, &models.OrganizationSetter{ + ArcgisID: omitnull.From(portal.User.OrgID), + ArcgisName: omitnull.From(portal.Name), + }) + if err != nil { + log.Error().Err(err).Int32("id", user.R.Organization.ID).Msg("Failed to update organization's arcgis info") + return + } + log.Info().Int32("org_id", org.ID).Str("arcgis_id", portal.User.OrgID).Msg("Updated org arcgis ID") + case 1: + org = orgs[0] + user.Update(ctx, db.PGInstance.BobDB, &models.UserSetter{ + OrganizationID: omit.From(org.ID), + }) + log.Info().Int32("org_id", org.ID).Int32("user_id", user.ID).Msg("Moved user into organization") + default: + log.Warn().Int("orgs", len(orgs)).Msg("Got too many orgs, programmer error.") return } @@ -313,67 +346,6 @@ func maybeCreateWebhook(ctx context.Context, client *fieldseeker.FieldSeeker) { } } -func downloadAllRecords(ctx context.Context, fssync *fieldseeker.FieldSeeker, layer arcgis.LayerFeature, org_id int32) (SyncStats, error) { - var stats SyncStats - count, err := fssync.QueryCount(layer.ID) - if err != nil { - return stats, fmt.Errorf("Failed to get counts for layer %s (%d): %w", layer.Name, layer.ID, err) - } - log.Info().Str("name", layer.Name).Uint("id", layer.ID).Msg("Starting on layer") - if count.Count == 0 { - return stats, nil - } - pool := pond.NewResultPool[SyncStats](20) - group := pool.NewGroup() - maxRecords := uint(fssync.MaxRecordCount()) - for offset := uint(0); offset < uint(count.Count); offset += maxRecords { - group.SubmitErr(func() (SyncStats, error) { - /*query := arcgis.NewQuery() - query.ResultRecordCount = maxRecords - query.ResultOffset = offset - query.SpatialReference = "4326" - query.OutFields = "*" - query.Where = "1=1" - qr, err := fssync.DoQuery( - layer.ID, - query) - if err != nil { - return SyncStats{}, fmt.Errorf("Failed to get layer %s (%d) at offset %d: %w", layer.Name, layer.ID, offset, err) - } - - i, u, err := saveOrUpdateDBRecords(ctx, "FS_"+layer.Name, qr, org_id) - if err != nil { - filename := fmt.Sprintf("failure-%s-%d-%d.json", layer.Name, layer.ID, offset) - saveRawQuery(fssync, layer, query, filename) - log.Error().Err(err).Msg("Faild to save DB records") - return SyncStats{}, fmt.Errorf("Failed to save records: %w", err) - } - return SyncStats{ - Inserts: i, - Updates: u, - Unchanged: len(qr.Features) - u - i, - }, nil - */ - return SyncStats{ - Inserts: 0, - Updates: 0, - Unchanged: 0, - }, nil - }) - } - results, err := group.Wait() - if err != nil { - return stats, fmt.Errorf("one or more tasks in the work pool failed: %w", err) - } - for _, r := range results { - stats.Inserts += r.Inserts - stats.Updates += r.Updates - stats.Unchanged += r.Unchanged - } - log.Info().Uint("inserts", stats.Inserts).Uint("updates", stats.Updates).Uint("no change", stats.Unchanged).Msg("Finished layer") - return stats, nil -} - func getOAuthForOrg(ctx context.Context, org *models.Organization) (*models.OauthToken, error) { users, err := org.User().All(ctx, db.PGInstance.BobDB) if err != nil { @@ -402,6 +374,7 @@ func periodicallyExportFieldseeker(ctx context.Context, org *models.Organization if err != nil { return fmt.Errorf("Failed to get oauth for org: %w", err) } + logPermissions(ctx, org, oauth) err = exportFieldseekerData(ctx, org, oauth) syncStatusByOrg[org.ID] = false if err != nil { @@ -436,11 +409,12 @@ func exportFieldseekerData(ctx context.Context, org *models.Organization, oauth return fmt.Errorf("Failed to create fssync: %w", err) } var stats SyncStats - //layers := fssync.FeatureServerLayers() + pool := pond.NewResultPool[SyncStats](20) + group := pool.NewGroup() var ss SyncStats for _, l := range fssync.FeatureServerLayers() { - ss, err = exportFieldseekerLayer(ctx, org, fssync, l) + ss, err = exportFieldseekerLayer(ctx, group, org, fssync, l) if err != nil { return err } @@ -448,6 +422,15 @@ func exportFieldseekerData(ctx context.Context, org *models.Organization, oauth stats.Updates += ss.Updates stats.Unchanged += ss.Unchanged } + results, err := group.Wait() + if err != nil { + return fmt.Errorf("one or more tasks in the work pool failed: %w", err) + } + for _, r := range results { + stats.Inserts += r.Inserts + stats.Updates += r.Updates + stats.Unchanged += r.Unchanged + } setter := models.FieldseekerSyncSetter{ RecordsCreated: omit.From(int32(stats.Inserts)), @@ -463,6 +446,37 @@ func exportFieldseekerData(ctx context.Context, org *models.Organization, oauth return nil } +func logPermissions(ctx context.Context, org *models.Organization, oauth *models.OauthToken) { + ar := arcgis.NewArcGIS( + arcgis.AuthenticatorOAuth{ + AccessToken: oauth.AccessToken, + AccessTokenExpires: oauth.AccessTokenExpires, + RefreshToken: oauth.RefreshToken, + RefreshTokenExpires: oauth.RefreshTokenExpires, + }, + ) + row, err := sql.OrgByOauthId(oauth.ID).One(ctx, db.PGInstance.BobDB) + if err != nil { + log.Error().Err(err).Msg("Failed to get org in log permissions") + return + } + fssync, err := fieldseeker.NewFieldSeeker( + ar, + row.FieldseekerURL.MustGet(), + ) + if err != nil { + log.Error().Err(err).Msg("Failed to create fieldseeker client in log permissions") + return + } + permissions, err := fssync.PermissionList() + if err != nil { + log.Error().Err(err).Msg("Failed to query permissions in log permissions") + return + } + for _, p := range permissions { + log.Info().Str("p", p.Principal).Msg("Permission!") + } +} func maintainOAuth(ctx context.Context, oauth *models.OauthToken) error { for { // Refresh from the database @@ -1086,24 +1100,22 @@ func updateSummaryTables(ctx context.Context, org *models.Organization) { } } -func exportFieldseekerLayer(ctx context.Context, org *models.Organization, fssync *fieldseeker.FieldSeeker, layer arcgis.LayerFeature) (SyncStats, error) { +func exportFieldseekerLayer(ctx context.Context, group pond.ResultTaskGroup[SyncStats], org *models.Organization, fssync *fieldseeker.FieldSeeker, layer arcgis.LayerFeature) (SyncStats, error) { var stats SyncStats count, err := fssync.QueryCount(layer.ID) if err != nil { return stats, fmt.Errorf("Failed to get counts for layer %s (%d): %w", layer.Name, layer.ID, err) } if count.Count == 0 { - log.Info().Str("name", layer.Name).Uint("id", layer.ID).Msg("No records to download") + log.Info().Str("name", layer.Name).Uint("layer_id", layer.ID).Int32("org_id", org.ID).Msg("No records to download") return stats, nil } - log.Info().Str("name", layer.Name).Uint("id", layer.ID).Msg("Starting on layer") - pool := pond.NewResultPool[SyncStats](20) - group := pool.NewGroup() maxRecords := uint(fssync.MaxRecordCount()) l, err := fieldseeker.NameToLayerType(layer.Name) if err != nil { return stats, fmt.Errorf("Failed to get layer for '%s': %w", layer.Name, err) } + log.Info().Str("name", layer.Name).Uint("layer_id", layer.ID).Int32("org_id", org.ID).Int("count", count.Count).Uint("iterations", uint(count.Count)/maxRecords).Msg("Queuing jobs for layer") for offset := uint(0); offset < uint(count.Count); offset += maxRecords { group.SubmitErr(func() (SyncStats, error) { var ss SyncStats @@ -1571,15 +1583,6 @@ func exportFieldseekerLayer(ctx context.Context, org *models.Organization, fssyn return ss, err }) } - results, err := group.Wait() - if err != nil { - return stats, fmt.Errorf("one or more tasks in the work pool failed: %w", err) - } - for _, r := range results { - stats.Inserts += r.Inserts - stats.Updates += r.Updates - stats.Unchanged += r.Unchanged - } - log.Info().Uint("inserts", stats.Inserts).Uint("updates", stats.Updates).Uint("no change", stats.Unchanged).Str("layer", layer.Name).Msg("Finished layer") + //log.Info().Uint("inserts", stats.Inserts).Uint("updates", stats.Updates).Uint("no change", stats.Unchanged).Str("layer", layer.Name).Msg("Finished layer") return stats, nil } From d46d988b4d18b3a10215cf214da96e53ecde3187 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 15 Jan 2026 04:10:54 +0000 Subject: [PATCH 0034/1453] Make aggregate layers clean up, add service request aggregate --- background/arcgis.go | 70 ++---------------- background/summary.go | 137 +++++++++++++++++++++++++++++++++++ sync/template/dashboard.html | 16 +++- 3 files changed, 157 insertions(+), 66 deletions(-) create mode 100644 background/summary.go diff --git a/background/arcgis.go b/background/arcgis.go index cf6836a1..d1622068 100644 --- a/background/arcgis.go +++ b/background/arcgis.go @@ -24,24 +24,18 @@ import ( "github.com/Gleipnir-Technology/arcgis-go/fieldseeker" "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/db/sql" "github.com/Gleipnir-Technology/nidus-sync/debug" - "github.com/Gleipnir-Technology/nidus-sync/h3utils" "github.com/Gleipnir-Technology/nidus-sync/notification" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/alitto/pond/v2" "github.com/jackc/pgx/v5" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob" "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/sm" "github.com/stephenafamo/bob/dialect/psql/um" - "github.com/uber/h3-go/v4" ) var syncStatusByOrg map[int32]bool @@ -468,6 +462,11 @@ func logPermissions(ctx context.Context, org *models.Organization, oauth *models log.Error().Err(err).Msg("Failed to create fieldseeker client in log permissions") return } + _, err = fssync.AdminInfo() + if err != nil { + log.Error().Err(err).Msg("Failed to get admin info log permissions") + return + } permissions, err := fssync.PermissionList() if err != nil { log.Error().Err(err).Msg("Failed to query permissions in log permissions") @@ -477,6 +476,7 @@ func logPermissions(ctx context.Context, org *models.Organization, oauth *models log.Info().Str("p", p.Principal).Msg("Permission!") } } + func maintainOAuth(ctx context.Context, oauth *models.OauthToken) error { for { // Refresh from the database @@ -1042,64 +1042,6 @@ func updateRowFromFeatureFS(ctx context.Context, transaction pgx.Tx, table strin return nil } -func updateSummaryTables(ctx context.Context, org *models.Organization) { - /*org, err := models.FindOrganization(ctx, PGInstance.BobDB, org_id) - if err != nil { - log.Error().Err(err).Msg("Failed to get organization") - }*/ - log.Info().Int("org_id", int(org.ID)).Msg("Getting point locations") - point_locations, err := org.Pointlocations().All(ctx, db.PGInstance.BobDB) - if err != nil { - log.Error().Err(err).Msg("Failed to get organization") - return - } - if len(point_locations) == 0 { - log.Info().Int("org_id", int(org.ID)).Msg("No updates to perform") - return - } - log.Info().Int("count", len(point_locations)).Msg("Summarizing point locations") - - for i := range 16 { - log.Info().Int("resolution", i).Msg("Working summary layer") - cellToCount := make(map[h3.Cell]int, 0) - for _, p := range point_locations { - if p.H3cell.IsNull() { - continue - } - cell, err := h3utils.ToCell(p.H3cell.MustGet()) - if err != nil { - log.Error().Err(err).Msg("Failed to get geometry point") - continue - } - scaled, err := cell.Parent(i) - if err != nil { - log.Error().Err(err).Int("resolution", i).Msg("Failed to get cell's parent at resolution") - continue - } - cellToCount[scaled] = cellToCount[scaled] + 1 - } - var to_insert []bob.Mod[*dialect.InsertQuery] = make([]bob.Mod[*dialect.InsertQuery], 0) - to_insert = append(to_insert, im.Into("h3_aggregation", "cell", "resolution", "count_", "type_", "organization_id", "geometry")) - for cell, count := range cellToCount { - polygon, err := h3utils.CellToPostgisGeometry(cell) - if err != nil { - log.Error().Err(err).Msg("Failed to get PostGIS geometry") - continue - } - // log.Info().Str("polygon", polygon).Msg("Going to insert") - to_insert = append(to_insert, im.Values(psql.Arg(cell.String(), i, count, enums.H3aggregationtypeServicerequest, org.ID), psql.F("st_geomfromtext", psql.S(polygon), 4326))) - } - to_insert = append(to_insert, im.OnConflict("cell, organization_id, type_").DoUpdate( - im.SetCol("count_").To(psql.Raw("EXCLUDED.count_")), - )) - //log.Info().Str("sql", insertQueryToString(psql.Insert(to_insert...))).Msg("Updating...") - _, err := psql.Insert(to_insert...).Exec(ctx, db.PGInstance.BobDB) - if err != nil { - log.Error().Err(err).Msg("Faild to add h3 aggregation") - } - } -} - func exportFieldseekerLayer(ctx context.Context, group pond.ResultTaskGroup[SyncStats], org *models.Organization, fssync *fieldseeker.FieldSeeker, layer arcgis.LayerFeature) (SyncStats, error) { var stats SyncStats count, err := fssync.QueryCount(layer.ID) diff --git a/background/summary.go b/background/summary.go new file mode 100644 index 00000000..fc6b528a --- /dev/null +++ b/background/summary.go @@ -0,0 +1,137 @@ +package background + +import ( + "context" + "fmt" + + "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/rs/zerolog/log" + "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/im" + "github.com/uber/h3-go/v4" +) + +func updateSummaryTables(ctx context.Context, org *models.Organization) { + updateSummaryMosquitoSource(ctx, org) + updateSummaryServiceRequest(ctx, org) +} + +func aggregateAtResolution(ctx context.Context, resolution int, org_id int32, type_ enums.H3aggregationtype, cells []h3.Cell) error { + var err error + log.Info().Int("resolution", resolution).Str("type", string(type_)).Msg("Working summary layer") + cellToCount := make(map[h3.Cell]int, 0) + for _, cell := range cells { + scaled, err := cell.Parent(resolution) + if err != nil { + log.Error().Err(err).Int("resolution", resolution).Msg("Failed to get cell's parent at resolution") + continue + } + cellToCount[scaled] = cellToCount[scaled] + 1 + } + + _, err = models.H3Aggregations.Delete( + dm.Where( + psql.And( + models.H3Aggregations.Columns.OrganizationID.EQ(psql.Arg(org_id)), + models.H3Aggregations.Columns.Resolution.EQ(psql.Arg(resolution)), + models.H3Aggregations.Columns.Type.EQ(psql.Arg(type_)), + ), + ), + ).Exec(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to clear previous aggregation: %w", err) + } + var to_insert []bob.Mod[*dialect.InsertQuery] = make([]bob.Mod[*dialect.InsertQuery], 0) + to_insert = append(to_insert, im.Into("h3_aggregation", "cell", "resolution", "count_", "type_", "organization_id", "geometry")) + for cell, count := range cellToCount { + polygon, err := h3utils.CellToPostgisGeometry(cell) + if err != nil { + log.Error().Err(err).Msg("Failed to get PostGIS geometry") + continue + } + // log.Info().Str("polygon", polygon).Msg("Going to insert") + to_insert = append(to_insert, im.Values(psql.Arg(cell.String(), resolution, count, type_, org_id), psql.F("st_geomfromtext", psql.S(polygon), 4326))) + } + to_insert = append(to_insert, im.OnConflict("cell, organization_id, type_").DoUpdate( + im.SetCol("count_").To(psql.Raw("EXCLUDED.count_")), + )) + //log.Info().Str("sql", insertQueryToString(psql.Insert(to_insert...))).Msg("Updating...") + _, err = psql.Insert(to_insert...).Exec(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to add h3 aggregation: %w", err) + } + return nil +} + +func updateSummaryMosquitoSource(ctx context.Context, org *models.Organization) { + log.Info().Int("org_id", int(org.ID)).Msg("Getting point locations") + point_locations, err := org.Pointlocations().All(ctx, db.PGInstance.BobDB) + if err != nil { + log.Error().Err(err).Msg("Failed to get all point locations") + return + } + if len(point_locations) == 0 { + log.Info().Int("org_id", int(org.ID)).Msg("No updates to perform") + return + } + log.Info().Int("count", len(point_locations)).Msg("Summarizing point locations") + + cells := make([]h3.Cell, 0) + for _, p := range point_locations { + if p.H3cell.IsNull() { + continue + } + cell, err := h3utils.ToCell(p.H3cell.MustGet()) + if err != nil { + log.Error().Err(err).Msg("Failed to get geometry point") + continue + } + cells = append(cells, cell) + } + + for i := range 16 { + err = aggregateAtResolution(ctx, i, org.ID, enums.H3aggregationtypeMosquitosource, cells) + if err != nil { + log.Error().Err(err).Int("resolution", i).Msg("Failed to aggregate mosquito source") + } + } +} + +func updateSummaryServiceRequest(ctx context.Context, org *models.Organization) { + log.Info().Int("org_id", int(org.ID)).Msg("Getting service requests") + service_requests, err := org.Servicerequests().All(ctx, db.PGInstance.BobDB) + if err != nil { + log.Error().Err(err).Msg("Failed to get all service requests") + return + } + if len(service_requests) == 0 { + log.Info().Int("org_id", int(org.ID)).Msg("No updates to perform") + return + } + log.Info().Int("count", len(service_requests)).Msg("Summarizing point locations") + + cells := make([]h3.Cell, 0) + for _, p := range service_requests { + if p.H3cell.IsNull() { + continue + } + cell, err := h3utils.ToCell(p.H3cell.MustGet()) + if err != nil { + log.Error().Err(err).Msg("Failed to get geometry point") + continue + } + cells = append(cells, cell) + } + for i := range 16 { + err = aggregateAtResolution(ctx, i, org.ID, enums.H3aggregationtypeServicerequest, cells) + if err != nil { + log.Error().Err(err).Int("resolution", i).Msg("Failed to aggregate service request") + } + } +} diff --git a/sync/template/dashboard.html b/sync/template/dashboard.html index 420b521d..1479c335 100644 --- a/sync/template/dashboard.html +++ b/sync/template/dashboard.html @@ -26,17 +26,29 @@ function onLoad() { //'maxzoom': 14 }); map.addLayer({ - 'id': 'nidus', // Layer ID + 'id': 'mosquito_source', // Layer ID 'type': 'fill', 'filter': ['==', ['zoom'], ['+', 2, ['to-number', ['get', 'resolution']]]], 'source': 'tegola-nidus', // ID of the tile source created above - 'source-layer': 'h3_aggregation', + 'source-layer': 'mosquito_source', 'paint': { 'fill-opacity': 0.3, 'fill-color': 'rgb(250, 100, 100)' } //slot: 'middle' // middle slot in Mapbox Standard style }); + map.addLayer({ + 'id': 'service_request', // Layer ID + 'type': 'fill', + 'filter': ['==', ['zoom'], ['+', 2, ['to-number', ['get', 'resolution']]]], + 'source': 'tegola-nidus', // ID of the tile source created above + 'source-layer': 'service_request', + 'paint': { + 'fill-opacity': 0.3, + 'fill-color': 'rgb(100, 100, 250)' + } + //slot: 'middle' // middle slot in Mapbox Standard style + }); map.addInteraction("nidus-click-interaction", { type: 'click', target: { layerId: 'nidus' }, From f94b89381f603056a596d4ac367898e08e0b6dbe Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 15 Jan 2026 04:11:19 +0000 Subject: [PATCH 0035/1453] Actually delete the organization in delete-org.sql --- tools/delete-org.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/delete-org.sql b/tools/delete-org.sql index 6bc09998..d01361d7 100644 --- a/tools/delete-org.sql +++ b/tools/delete-org.sql @@ -38,4 +38,5 @@ BEGIN; DELETE FROM fieldseeker.treatmentarea WHERE organization_id = :org_id; DELETE FROM fieldseeker.zones WHERE organization_id = :org_id; DELETE FROM fieldseeker.zones2 WHERE organization_id = :org_id; + DELETE FROM organization WHERE id = :org_id; COMMIT; From 06140a9062b3f8c7f33228795a0ee8f4b65f55be Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 15 Jan 2026 19:18:34 +0000 Subject: [PATCH 0036/1453] Remove bob submodule, add arcgis.user I had to remove the submodule because of the go bug at https://github.com/golang/go/issues/77196 I found the bug because of a bug in bob itself https://github.com/stephenafamo/bob/issues/610 This was because I'm trying to save data about the Arcgis user for use in determining if I can set up hooks to avoid the polling for data changes. --- .gitmodules | 3 - background/arcgis.go | 81 +- db/bob | 1 - db/bobgen.sh | 3 +- db/bobgen.yaml | 7 + db/dberrors/arcgis.user_.bob.go | 17 + db/dberrors/arcgis.user_privilege.bob.go | 17 + db/dberrors/bob_errors.bob.go | 2 +- db/dberrors/district.bob.go | 2 +- .../fieldseeker.containerrelate.bob.go | 2 +- .../fieldseeker.fieldscoutinglog.bob.go | 2 +- db/dberrors/fieldseeker.habitatrelate.bob.go | 2 +- .../fieldseeker.inspectionsample.bob.go | 2 +- .../fieldseeker.inspectionsampledetail.bob.go | 2 +- db/dberrors/fieldseeker.linelocation.bob.go | 2 +- .../fieldseeker.locationtracking.bob.go | 2 +- .../fieldseeker.mosquitoinspection.bob.go | 2 +- db/dberrors/fieldseeker.pointlocation.bob.go | 2 +- .../fieldseeker.polygonlocation.bob.go | 2 +- db/dberrors/fieldseeker.pool.bob.go | 2 +- db/dberrors/fieldseeker.pooldetail.bob.go | 2 +- .../fieldseeker.proposedtreatmentarea.bob.go | 2 +- .../fieldseeker.qamosquitoinspection.bob.go | 2 +- db/dberrors/fieldseeker.rodentlocation.bob.go | 2 +- .../fieldseeker.samplecollection.bob.go | 2 +- db/dberrors/fieldseeker.samplelocation.bob.go | 2 +- db/dberrors/fieldseeker.servicerequest.bob.go | 2 +- .../fieldseeker.speciesabundance.bob.go | 2 +- db/dberrors/fieldseeker.stormdrain.bob.go | 2 +- db/dberrors/fieldseeker.timecard.bob.go | 2 +- db/dberrors/fieldseeker.trapdata.bob.go | 2 +- db/dberrors/fieldseeker.traplocation.bob.go | 2 +- db/dberrors/fieldseeker.treatment.bob.go | 2 +- db/dberrors/fieldseeker.treatmentarea.bob.go | 2 +- db/dberrors/fieldseeker.zones.bob.go | 2 +- db/dberrors/fieldseeker.zones2.bob.go | 2 +- db/dberrors/fieldseeker_sync.bob.go | 2 +- db/dberrors/goose_db_version.bob.go | 2 +- db/dberrors/h3_aggregation.bob.go | 2 +- db/dberrors/note_audio.bob.go | 2 +- db/dberrors/note_audio_breadcrumb.bob.go | 2 +- db/dberrors/note_audio_data.bob.go | 2 +- db/dberrors/note_image.bob.go | 2 +- db/dberrors/note_image_breadcrumb.bob.go | 2 +- db/dberrors/note_image_data.bob.go | 2 +- db/dberrors/notification.bob.go | 2 +- db/dberrors/oauth_token.bob.go | 2 +- db/dberrors/organization.bob.go | 2 +- db/dberrors/publicreport.nuisance.bob.go | 2 +- db/dberrors/publicreport.pool.bob.go | 2 +- db/dberrors/publicreport.pool_photo.bob.go | 2 +- db/dberrors/publicreport.quick.bob.go | 2 +- db/dberrors/publicreport.quick_photo.bob.go | 2 +- db/dberrors/sessions.bob.go | 2 +- db/dberrors/spatial_ref_sys.bob.go | 2 +- db/dberrors/user_.bob.go | 2 +- db/dbinfo/arcgis.user_.bob.go | 237 ++ db/dbinfo/arcgis.user_privilege.bob.go | 122 + db/dbinfo/bob_types.bob.go | 2 +- db/dbinfo/district.bob.go | 2 +- db/dbinfo/fieldseeker.containerrelate.bob.go | 2 +- db/dbinfo/fieldseeker.fieldscoutinglog.bob.go | 2 +- db/dbinfo/fieldseeker.habitatrelate.bob.go | 2 +- db/dbinfo/fieldseeker.inspectionsample.bob.go | 2 +- .../fieldseeker.inspectionsampledetail.bob.go | 2 +- db/dbinfo/fieldseeker.linelocation.bob.go | 2 +- db/dbinfo/fieldseeker.locationtracking.bob.go | 2 +- .../fieldseeker.mosquitoinspection.bob.go | 2 +- db/dbinfo/fieldseeker.pointlocation.bob.go | 2 +- db/dbinfo/fieldseeker.polygonlocation.bob.go | 2 +- db/dbinfo/fieldseeker.pool.bob.go | 2 +- db/dbinfo/fieldseeker.pooldetail.bob.go | 2 +- .../fieldseeker.proposedtreatmentarea.bob.go | 2 +- .../fieldseeker.qamosquitoinspection.bob.go | 2 +- db/dbinfo/fieldseeker.rodentlocation.bob.go | 2 +- db/dbinfo/fieldseeker.samplecollection.bob.go | 2 +- db/dbinfo/fieldseeker.samplelocation.bob.go | 2 +- db/dbinfo/fieldseeker.servicerequest.bob.go | 2 +- db/dbinfo/fieldseeker.speciesabundance.bob.go | 2 +- db/dbinfo/fieldseeker.stormdrain.bob.go | 2 +- db/dbinfo/fieldseeker.timecard.bob.go | 2 +- db/dbinfo/fieldseeker.trapdata.bob.go | 2 +- db/dbinfo/fieldseeker.traplocation.bob.go | 2 +- db/dbinfo/fieldseeker.treatment.bob.go | 2 +- db/dbinfo/fieldseeker.treatmentarea.bob.go | 2 +- db/dbinfo/fieldseeker.zones.bob.go | 2 +- db/dbinfo/fieldseeker.zones2.bob.go | 2 +- db/dbinfo/fieldseeker_sync.bob.go | 2 +- db/dbinfo/geography_columns.bob.go | 2 +- db/dbinfo/geometry_columns.bob.go | 2 +- db/dbinfo/goose_db_version.bob.go | 2 +- db/dbinfo/h3_aggregation.bob.go | 2 +- db/dbinfo/note_audio.bob.go | 2 +- db/dbinfo/note_audio_breadcrumb.bob.go | 2 +- db/dbinfo/note_audio_data.bob.go | 2 +- db/dbinfo/note_image.bob.go | 2 +- db/dbinfo/note_image_breadcrumb.bob.go | 2 +- db/dbinfo/note_image_data.bob.go | 2 +- db/dbinfo/notification.bob.go | 2 +- db/dbinfo/oauth_token.bob.go | 2 +- db/dbinfo/organization.bob.go | 2 +- db/dbinfo/publicreport.nuisance.bob.go | 2 +- db/dbinfo/publicreport.pool.bob.go | 2 +- db/dbinfo/publicreport.pool_photo.bob.go | 2 +- db/dbinfo/publicreport.quick.bob.go | 2 +- db/dbinfo/publicreport.quick_photo.bob.go | 2 +- db/dbinfo/publicreport.report_location.bob.go | 2 +- db/dbinfo/raster_columns.bob.go | 2 +- db/dbinfo/raster_overviews.bob.go | 2 +- db/dbinfo/sessions.bob.go | 2 +- db/dbinfo/spatial_ref_sys.bob.go | 2 +- db/dbinfo/user_.bob.go | 2 +- db/enums/enums.bob.go | 2 +- db/factory/arcgis.user_.bob.go | 984 ++++++++ db/factory/arcgis.user_privilege.bob.go | 369 +++ db/factory/bobfactory_context.bob.go | 12 +- db/factory/bobfactory_main.bob.go | 98 +- db/factory/bobfactory_random.bob.go | 2 +- db/factory/district.bob.go | 2 +- db/factory/fieldseeker.containerrelate.bob.go | 2 +- .../fieldseeker.fieldscoutinglog.bob.go | 2 +- db/factory/fieldseeker.habitatrelate.bob.go | 2 +- .../fieldseeker.inspectionsample.bob.go | 2 +- .../fieldseeker.inspectionsampledetail.bob.go | 2 +- db/factory/fieldseeker.linelocation.bob.go | 2 +- .../fieldseeker.locationtracking.bob.go | 2 +- .../fieldseeker.mosquitoinspection.bob.go | 2 +- db/factory/fieldseeker.pointlocation.bob.go | 2 +- db/factory/fieldseeker.polygonlocation.bob.go | 2 +- db/factory/fieldseeker.pool.bob.go | 2 +- db/factory/fieldseeker.pooldetail.bob.go | 2 +- .../fieldseeker.proposedtreatmentarea.bob.go | 2 +- .../fieldseeker.qamosquitoinspection.bob.go | 2 +- db/factory/fieldseeker.rodentlocation.bob.go | 2 +- .../fieldseeker.samplecollection.bob.go | 2 +- db/factory/fieldseeker.samplelocation.bob.go | 2 +- db/factory/fieldseeker.servicerequest.bob.go | 2 +- .../fieldseeker.speciesabundance.bob.go | 2 +- db/factory/fieldseeker.stormdrain.bob.go | 2 +- db/factory/fieldseeker.timecard.bob.go | 2 +- db/factory/fieldseeker.trapdata.bob.go | 2 +- db/factory/fieldseeker.traplocation.bob.go | 2 +- db/factory/fieldseeker.treatment.bob.go | 2 +- db/factory/fieldseeker.treatmentarea.bob.go | 2 +- db/factory/fieldseeker.zones.bob.go | 2 +- db/factory/fieldseeker.zones2.bob.go | 2 +- db/factory/fieldseeker_sync.bob.go | 2 +- db/factory/geography_columns.bob.go | 2 +- db/factory/geometry_columns.bob.go | 2 +- db/factory/goose_db_version.bob.go | 2 +- db/factory/h3_aggregation.bob.go | 2 +- db/factory/note_audio.bob.go | 2 +- db/factory/note_audio_breadcrumb.bob.go | 2 +- db/factory/note_audio_data.bob.go | 2 +- db/factory/note_image.bob.go | 2 +- db/factory/note_image_breadcrumb.bob.go | 2 +- db/factory/note_image_data.bob.go | 2 +- db/factory/notification.bob.go | 2 +- db/factory/oauth_token.bob.go | 2 +- db/factory/organization.bob.go | 2 +- db/factory/publicreport.nuisance.bob.go | 2 +- db/factory/publicreport.pool.bob.go | 2 +- db/factory/publicreport.pool_photo.bob.go | 2 +- db/factory/publicreport.quick.bob.go | 2 +- db/factory/publicreport.quick_photo.bob.go | 2 +- .../publicreport.report_location.bob.go | 2 +- db/factory/raster_columns.bob.go | 2 +- db/factory/raster_overviews.bob.go | 2 +- db/factory/sessions.bob.go | 2 +- db/factory/spatial_ref_sys.bob.go | 2 +- db/factory/user_.bob.go | 122 +- db/migrations/00031_arcgis_user.sql | 28 + db/models/arcgis.user_.bob.go | 1191 ++++++++++ db/models/arcgis.user_privilege.bob.go | 612 +++++ db/models/bob_counts.bob.go | 168 ++ db/models/bob_joins.bob.go | 6 +- db/models/bob_loaders.bob.go | 10 +- db/models/bob_where.bob.go | 8 +- db/models/district.bob.go | 2 +- db/models/fieldseeker.containerrelate.bob.go | 2 +- db/models/fieldseeker.fieldscoutinglog.bob.go | 2 +- db/models/fieldseeker.habitatrelate.bob.go | 2 +- db/models/fieldseeker.inspectionsample.bob.go | 2 +- .../fieldseeker.inspectionsampledetail.bob.go | 2 +- db/models/fieldseeker.linelocation.bob.go | 2 +- db/models/fieldseeker.locationtracking.bob.go | 2 +- .../fieldseeker.mosquitoinspection.bob.go | 2 +- db/models/fieldseeker.pointlocation.bob.go | 2 +- db/models/fieldseeker.polygonlocation.bob.go | 2 +- db/models/fieldseeker.pool.bob.go | 2 +- db/models/fieldseeker.pooldetail.bob.go | 2 +- .../fieldseeker.proposedtreatmentarea.bob.go | 2 +- .../fieldseeker.qamosquitoinspection.bob.go | 2 +- db/models/fieldseeker.rodentlocation.bob.go | 2 +- db/models/fieldseeker.samplecollection.bob.go | 2 +- db/models/fieldseeker.samplelocation.bob.go | 2 +- db/models/fieldseeker.servicerequest.bob.go | 2 +- db/models/fieldseeker.speciesabundance.bob.go | 2 +- db/models/fieldseeker.stormdrain.bob.go | 2 +- db/models/fieldseeker.timecard.bob.go | 2 +- db/models/fieldseeker.trapdata.bob.go | 2 +- db/models/fieldseeker.traplocation.bob.go | 2 +- db/models/fieldseeker.treatment.bob.go | 2 +- db/models/fieldseeker.treatmentarea.bob.go | 2 +- db/models/fieldseeker.zones.bob.go | 2 +- db/models/fieldseeker.zones2.bob.go | 2 +- db/models/fieldseeker_sync.bob.go | 2 +- db/models/geography_columns.bob.go | 2 +- db/models/geometry_columns.bob.go | 2 +- db/models/goose_db_version.bob.go | 2 +- db/models/h3_aggregation.bob.go | 2 +- db/models/note_audio.bob.go | 160 +- db/models/note_audio_breadcrumb.bob.go | 2 +- db/models/note_audio_data.bob.go | 2 +- db/models/note_image.bob.go | 160 +- db/models/note_image_breadcrumb.bob.go | 2 +- db/models/note_image_data.bob.go | 2 +- db/models/notification.bob.go | 2 +- db/models/oauth_token.bob.go | 2 +- db/models/organization.bob.go | 1988 ++++++++++++++++- db/models/publicreport.nuisance.bob.go | 2 +- db/models/publicreport.pool.bob.go | 97 +- db/models/publicreport.pool_photo.bob.go | 2 +- db/models/publicreport.quick.bob.go | 97 +- db/models/publicreport.quick_photo.bob.go | 2 +- db/models/publicreport.report_location.bob.go | 2 +- db/models/raster_columns.bob.go | 2 +- db/models/raster_overviews.bob.go | 2 +- db/models/sessions.bob.go | 2 +- db/models/spatial_ref_sys.bob.go | 2 +- db/models/user_.bob.go | 656 +++++- db/sql/oauth_by_user_id.bob.go | 111 - db/sql/oauth_by_user_id.bob.sql | 6 - db/sql/oauth_by_user_id.sql | 3 - db/sql/org_by_oauth_id.bob.go | 4 +- db/sql/org_by_oauth_id.bob.sql | 2 +- db/sql/publicreport_publicid_table.bob.go | 4 +- db/sql/publicreport_publicid_table.bob.sql | 2 +- db/sql/trapcount_by_location_id.bob.go | 4 +- db/sql/trapcount_by_location_id.bob.sql | 2 +- db/sql/trapdata_by_location_id_recent.bob.go | 4 +- db/sql/trapdata_by_location_id_recent.bob.sql | 2 +- db/sql/traplocation_by_source_id.bob.go | 6 +- db/sql/traplocation_by_source_id.bob.sql | 2 +- db/sql/update_oauth_org.bob.go | 4 +- db/sql/update_oauth_org.bob.sql | 2 +- db/sql/user_by_username.bob.go | 12 +- db/sql/user_by_username.bob.sql | 4 +- db/sql/user_by_username.sql | 2 +- 249 files changed, 7447 insertions(+), 391 deletions(-) delete mode 160000 db/bob create mode 100644 db/dberrors/arcgis.user_.bob.go create mode 100644 db/dberrors/arcgis.user_privilege.bob.go create mode 100644 db/dbinfo/arcgis.user_.bob.go create mode 100644 db/dbinfo/arcgis.user_privilege.bob.go create mode 100644 db/factory/arcgis.user_.bob.go create mode 100644 db/factory/arcgis.user_privilege.bob.go create mode 100644 db/migrations/00031_arcgis_user.sql create mode 100644 db/models/arcgis.user_.bob.go create mode 100644 db/models/arcgis.user_privilege.bob.go create mode 100644 db/models/bob_counts.bob.go delete mode 100644 db/sql/oauth_by_user_id.bob.go delete mode 100644 db/sql/oauth_by_user_id.bob.sql delete mode 100644 db/sql/oauth_by_user_id.sql diff --git a/.gitmodules b/.gitmodules index 555f12c9..dc0c25cc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "go-geojson2h3"] path = go-geojson2h3 url = git@github.com:Gleipnir-Technology/go-geojson2h3.git -[submodule "bob"] - path = db/bob - url = git@github.com:Gleipnir-Technology/bob.git diff --git a/background/arcgis.go b/background/arcgis.go index d1622068..901fda63 100644 --- a/background/arcgis.go +++ b/background/arcgis.go @@ -34,6 +34,7 @@ import ( "github.com/jackc/pgx/v5" "github.com/rs/zerolog/log" "github.com/stephenafamo/bob/dialect/psql" + "github.com/stephenafamo/bob/dialect/psql/dm" "github.com/stephenafamo/bob/dialect/psql/sm" "github.com/stephenafamo/bob/dialect/psql/um" ) @@ -101,11 +102,15 @@ func HandleOauthAccessCode(ctx context.Context, user *models.User, code string) } func HasFieldseekerConnection(ctx context.Context, user *models.User) (bool, error) { - result, err := sql.OauthTokenByUserId(user.ID).All(ctx, db.PGInstance.BobDB) + result, err := models.OauthTokens.Query( + sm.Where( + models.OauthTokens.Columns.UserID.EQ(psql.Arg(user.ID)), + ), + ).Exists(ctx, db.PGInstance.BobDB) if err != nil { return false, err } - return len(result) > 0, nil + return result, nil } func IsSyncOngoing(org_id int32) bool { @@ -234,12 +239,12 @@ func updateArcgisUserData(ctx context.Context, user *models.User, access_token s RefreshTokenExpires: refresh_token_expires, }, ) - portal, err := client.PortalsSelf() + + portal, err := updatePortalData(ctx, client, user.ID) if err != nil { - log.Error().Err(err).Msg("Failed to get ArcGIS user data") + log.Error().Err(err).Msg("Failed to get portal data") return } - log.Info().Str("Username", portal.User.Username).Str("user_id", portal.User.ID).Str("org_id", portal.User.OrgID).Str("org_name", portal.Name).Str("license_type_id", portal.User.UserLicenseTypeID).Msg("Got portals data") _, err = models.OauthTokens.Update( //um.SetCol(string(models.OauthTokens.Columns.ArcgisID)).ToArg(portal.User.ID), @@ -326,6 +331,72 @@ func updateArcgisUserData(ctx context.Context, user *models.User, access_token s NewOAuthTokenChannel <- struct{}{} } +func updatePortalData(ctx context.Context, client *arcgis.ArcGIS, user_id int32) (*arcgis.PortalsResponse, error) { + p, err := client.PortalsSelf() + if err != nil { + return nil, fmt.Errorf("Failed to get ArcGIS user data: %w", err) + } + + tx, err := db.PGInstance.BobDB.BeginTx(ctx, nil) + _, err = models.ArcgisUserPrivileges.Delete( + dm.Where( + models.ArcgisUserPrivileges.Columns.UserID.EQ(psql.Arg(p.User.ID)), + ), + ).Exec(ctx, tx) + + if err != nil { + tx.Rollback(ctx) + return nil, fmt.Errorf("Failed to delete previous user privilege data: %w", err) + } + + _, err = models.ArcgisUsers.Delete( + dm.Where( + models.ArcgisUsers.Columns.ID.EQ(psql.Arg(p.User.ID)), + ), + ).Exec(ctx, tx) + + if err != nil { + tx.Rollback(ctx) + return nil, fmt.Errorf("Failed to delete previous user data: %w", err) + } + + setter := models.ArcgisUserSetter{ + Access: omit.From(p.Access), + Created: omit.From(time.Unix(p.User.Created, 0)), + Email: omit.From(p.User.Email), + FullName: omit.From(p.User.FullName), + ID: omit.From(p.User.ID), + Level: omit.From(p.User.Level), + OrgID: omit.From(p.User.OrgID), + PublicUserID: omit.From(user_id), + Region: omit.From(p.Region), + Role: omit.From(p.User.Role), + RoleID: omit.From(p.User.RoleId), + Username: omit.From(p.User.Username), + UserLicenseTypeID: omit.From(p.User.UserLicenseTypeID), + UserType: omit.From(p.User.UserType), + } + _, err = models.ArcgisUsers.Insert(&setter).One(ctx, tx) + if err != nil { + tx.Rollback(ctx) + return nil, fmt.Errorf("Failed to add arcgis user data: %w", err) + } + for _, priv := range p.User.Privileges { + s := models.ArcgisUserPrivilegeSetter{ + Privilege: omit.From(priv), + UserID: omit.From(p.User.ID), + } + _, err := models.ArcgisUserPrivileges.Insert(&s).One(ctx, tx) + if err != nil { + tx.Rollback(ctx) + return nil, fmt.Errorf("Failed to add arcgis user privilege data: %w", err) + } + } + err = tx.Commit(ctx) + log.Info().Str("username", p.User.Username).Str("user_id", p.User.ID).Str("org_id", p.User.OrgID).Str("org_name", p.Name).Str("license_type_id", p.User.UserLicenseTypeID).Msg("Updated portals data") + return p, nil +} + func maybeCreateWebhook(ctx context.Context, client *fieldseeker.FieldSeeker) { webhooks, err := client.WebhookList() if err != nil { diff --git a/db/bob b/db/bob deleted file mode 160000 index d277a066..00000000 --- a/db/bob +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d277a066d6bac5336e49615495ce2c74e736a7fd diff --git a/db/bobgen.sh b/db/bobgen.sh index ba30582d..9c2d653c 100755 --- a/db/bobgen.sh +++ b/db/bobgen.sh @@ -1,2 +1,3 @@ #!/run/current-system/sw/bin/bash -PSQL_DSN="postgresql://?host=/var/run/postgresql&sslmode=disable&dbname=nidus-sync" bob/bobgen-psql +PSQL_DSN="postgresql://?host=/var/run/postgresql&sslmode=disable&dbname=nidus-sync" /tmp/bobgen-psql +#PSQL_DSN="postgresql://?host=/var/run/postgresql&sslmode=disable&dbname=nidus-sync" bob/gen/bobgen-psql/bobgen-psql diff --git a/db/bobgen.yaml b/db/bobgen.yaml index e5a60949..b9f4c8d7 100644 --- a/db/bobgen.yaml +++ b/db/bobgen.yaml @@ -1,4 +1,9 @@ aliases: + arcgis.user_: + up_plural: "ArcgisUsers" + up_singular: "ArcgisUser" + down_plural: "arcgisusers" + down_singular: "arcgisuser" user_: up_plural: "Users" up_singular: "User" @@ -7,9 +12,11 @@ aliases: no_tests: true psql: schemas: + - "arcgis" - "public" - "publicreport" - "fieldseeker" + shared_schema: "public" queries: - ./sql uuid_pkg: google diff --git a/db/dberrors/arcgis.user_.bob.go b/db/dberrors/arcgis.user_.bob.go new file mode 100644 index 00000000..d17b1fef --- /dev/null +++ b/db/dberrors/arcgis.user_.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var ArcgisUserErrors = &arcgisuserErrors{ + ErrUniqueUser_Pkey: &UniqueConstraintError{ + schema: "arcgis", + table: "user_", + columns: []string{"id"}, + s: "user__pkey", + }, +} + +type arcgisuserErrors struct { + ErrUniqueUser_Pkey *UniqueConstraintError +} diff --git a/db/dberrors/arcgis.user_privilege.bob.go b/db/dberrors/arcgis.user_privilege.bob.go new file mode 100644 index 00000000..7ff6253f --- /dev/null +++ b/db/dberrors/arcgis.user_privilege.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var ArcgisUserPrivilegeErrors = &arcgisUserPrivilegeErrors{ + ErrUniqueUserPrivilegePkey: &UniqueConstraintError{ + schema: "arcgis", + table: "user_privilege", + columns: []string{"user_id", "privilege"}, + s: "user_privilege_pkey", + }, +} + +type arcgisUserPrivilegeErrors struct { + ErrUniqueUserPrivilegePkey *UniqueConstraintError +} diff --git a/db/dberrors/bob_errors.bob.go b/db/dberrors/bob_errors.bob.go index f98ecde7..ec4056c3 100644 --- a/db/dberrors/bob_errors.bob.go +++ b/db/dberrors/bob_errors.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/district.bob.go b/db/dberrors/district.bob.go index 06f8adee..b564e22b 100644 --- a/db/dberrors/district.bob.go +++ b/db/dberrors/district.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.containerrelate.bob.go b/db/dberrors/fieldseeker.containerrelate.bob.go index 2e5478b3..40d01312 100644 --- a/db/dberrors/fieldseeker.containerrelate.bob.go +++ b/db/dberrors/fieldseeker.containerrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.fieldscoutinglog.bob.go b/db/dberrors/fieldseeker.fieldscoutinglog.bob.go index 25c1147a..3237f5db 100644 --- a/db/dberrors/fieldseeker.fieldscoutinglog.bob.go +++ b/db/dberrors/fieldseeker.fieldscoutinglog.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.habitatrelate.bob.go b/db/dberrors/fieldseeker.habitatrelate.bob.go index 752bf877..d00f431c 100644 --- a/db/dberrors/fieldseeker.habitatrelate.bob.go +++ b/db/dberrors/fieldseeker.habitatrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.inspectionsample.bob.go b/db/dberrors/fieldseeker.inspectionsample.bob.go index b4e31312..ce6725a5 100644 --- a/db/dberrors/fieldseeker.inspectionsample.bob.go +++ b/db/dberrors/fieldseeker.inspectionsample.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.inspectionsampledetail.bob.go b/db/dberrors/fieldseeker.inspectionsampledetail.bob.go index 5538e4c9..69d7b6e6 100644 --- a/db/dberrors/fieldseeker.inspectionsampledetail.bob.go +++ b/db/dberrors/fieldseeker.inspectionsampledetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.linelocation.bob.go b/db/dberrors/fieldseeker.linelocation.bob.go index 353de2a0..5a135a53 100644 --- a/db/dberrors/fieldseeker.linelocation.bob.go +++ b/db/dberrors/fieldseeker.linelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.locationtracking.bob.go b/db/dberrors/fieldseeker.locationtracking.bob.go index 2f1f10ad..e68d1172 100644 --- a/db/dberrors/fieldseeker.locationtracking.bob.go +++ b/db/dberrors/fieldseeker.locationtracking.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.mosquitoinspection.bob.go b/db/dberrors/fieldseeker.mosquitoinspection.bob.go index 127e85ca..f9b119a7 100644 --- a/db/dberrors/fieldseeker.mosquitoinspection.bob.go +++ b/db/dberrors/fieldseeker.mosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.pointlocation.bob.go b/db/dberrors/fieldseeker.pointlocation.bob.go index 1b46e6c2..9078a437 100644 --- a/db/dberrors/fieldseeker.pointlocation.bob.go +++ b/db/dberrors/fieldseeker.pointlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.polygonlocation.bob.go b/db/dberrors/fieldseeker.polygonlocation.bob.go index 1502c145..250e5de2 100644 --- a/db/dberrors/fieldseeker.polygonlocation.bob.go +++ b/db/dberrors/fieldseeker.polygonlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.pool.bob.go b/db/dberrors/fieldseeker.pool.bob.go index fc41b355..7bb30e90 100644 --- a/db/dberrors/fieldseeker.pool.bob.go +++ b/db/dberrors/fieldseeker.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.pooldetail.bob.go b/db/dberrors/fieldseeker.pooldetail.bob.go index 613767bc..1423b462 100644 --- a/db/dberrors/fieldseeker.pooldetail.bob.go +++ b/db/dberrors/fieldseeker.pooldetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.proposedtreatmentarea.bob.go b/db/dberrors/fieldseeker.proposedtreatmentarea.bob.go index fcad48db..991a4d46 100644 --- a/db/dberrors/fieldseeker.proposedtreatmentarea.bob.go +++ b/db/dberrors/fieldseeker.proposedtreatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.qamosquitoinspection.bob.go b/db/dberrors/fieldseeker.qamosquitoinspection.bob.go index 0d940baa..5ae42a56 100644 --- a/db/dberrors/fieldseeker.qamosquitoinspection.bob.go +++ b/db/dberrors/fieldseeker.qamosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.rodentlocation.bob.go b/db/dberrors/fieldseeker.rodentlocation.bob.go index 8b365032..d865425f 100644 --- a/db/dberrors/fieldseeker.rodentlocation.bob.go +++ b/db/dberrors/fieldseeker.rodentlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.samplecollection.bob.go b/db/dberrors/fieldseeker.samplecollection.bob.go index 1bbb4dc8..6f0de912 100644 --- a/db/dberrors/fieldseeker.samplecollection.bob.go +++ b/db/dberrors/fieldseeker.samplecollection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.samplelocation.bob.go b/db/dberrors/fieldseeker.samplelocation.bob.go index 889bb8b5..35a96bc1 100644 --- a/db/dberrors/fieldseeker.samplelocation.bob.go +++ b/db/dberrors/fieldseeker.samplelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.servicerequest.bob.go b/db/dberrors/fieldseeker.servicerequest.bob.go index ec0baf46..5e8d1020 100644 --- a/db/dberrors/fieldseeker.servicerequest.bob.go +++ b/db/dberrors/fieldseeker.servicerequest.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.speciesabundance.bob.go b/db/dberrors/fieldseeker.speciesabundance.bob.go index 5a6fde04..922bcc2a 100644 --- a/db/dberrors/fieldseeker.speciesabundance.bob.go +++ b/db/dberrors/fieldseeker.speciesabundance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.stormdrain.bob.go b/db/dberrors/fieldseeker.stormdrain.bob.go index fa72d71e..689fe28d 100644 --- a/db/dberrors/fieldseeker.stormdrain.bob.go +++ b/db/dberrors/fieldseeker.stormdrain.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.timecard.bob.go b/db/dberrors/fieldseeker.timecard.bob.go index a8c761ab..bf09f766 100644 --- a/db/dberrors/fieldseeker.timecard.bob.go +++ b/db/dberrors/fieldseeker.timecard.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.trapdata.bob.go b/db/dberrors/fieldseeker.trapdata.bob.go index f31e1333..7ffe6603 100644 --- a/db/dberrors/fieldseeker.trapdata.bob.go +++ b/db/dberrors/fieldseeker.trapdata.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.traplocation.bob.go b/db/dberrors/fieldseeker.traplocation.bob.go index a5c182e4..de57fa80 100644 --- a/db/dberrors/fieldseeker.traplocation.bob.go +++ b/db/dberrors/fieldseeker.traplocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.treatment.bob.go b/db/dberrors/fieldseeker.treatment.bob.go index 6231fcb3..40e0bddf 100644 --- a/db/dberrors/fieldseeker.treatment.bob.go +++ b/db/dberrors/fieldseeker.treatment.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.treatmentarea.bob.go b/db/dberrors/fieldseeker.treatmentarea.bob.go index 94ca5921..c7ff263b 100644 --- a/db/dberrors/fieldseeker.treatmentarea.bob.go +++ b/db/dberrors/fieldseeker.treatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.zones.bob.go b/db/dberrors/fieldseeker.zones.bob.go index d18052a7..bd2fd6e6 100644 --- a/db/dberrors/fieldseeker.zones.bob.go +++ b/db/dberrors/fieldseeker.zones.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.zones2.bob.go b/db/dberrors/fieldseeker.zones2.bob.go index 3d378f51..644248d7 100644 --- a/db/dberrors/fieldseeker.zones2.bob.go +++ b/db/dberrors/fieldseeker.zones2.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker_sync.bob.go b/db/dberrors/fieldseeker_sync.bob.go index f02acf71..ac899d66 100644 --- a/db/dberrors/fieldseeker_sync.bob.go +++ b/db/dberrors/fieldseeker_sync.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/goose_db_version.bob.go b/db/dberrors/goose_db_version.bob.go index 1bd13dbd..af370d75 100644 --- a/db/dberrors/goose_db_version.bob.go +++ b/db/dberrors/goose_db_version.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/h3_aggregation.bob.go b/db/dberrors/h3_aggregation.bob.go index 0876f0ad..80c555b1 100644 --- a/db/dberrors/h3_aggregation.bob.go +++ b/db/dberrors/h3_aggregation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_audio.bob.go b/db/dberrors/note_audio.bob.go index 408f8d94..51621255 100644 --- a/db/dberrors/note_audio.bob.go +++ b/db/dberrors/note_audio.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_audio_breadcrumb.bob.go b/db/dberrors/note_audio_breadcrumb.bob.go index 36b73309..c0db7c25 100644 --- a/db/dberrors/note_audio_breadcrumb.bob.go +++ b/db/dberrors/note_audio_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_audio_data.bob.go b/db/dberrors/note_audio_data.bob.go index 6fe4fa73..bd4b07c1 100644 --- a/db/dberrors/note_audio_data.bob.go +++ b/db/dberrors/note_audio_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_image.bob.go b/db/dberrors/note_image.bob.go index 53ada199..8090f936 100644 --- a/db/dberrors/note_image.bob.go +++ b/db/dberrors/note_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_image_breadcrumb.bob.go b/db/dberrors/note_image_breadcrumb.bob.go index 256c0567..7e62d2ad 100644 --- a/db/dberrors/note_image_breadcrumb.bob.go +++ b/db/dberrors/note_image_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_image_data.bob.go b/db/dberrors/note_image_data.bob.go index eb67681f..91c1a89a 100644 --- a/db/dberrors/note_image_data.bob.go +++ b/db/dberrors/note_image_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/notification.bob.go b/db/dberrors/notification.bob.go index f9a429a1..d4f2841a 100644 --- a/db/dberrors/notification.bob.go +++ b/db/dberrors/notification.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/oauth_token.bob.go b/db/dberrors/oauth_token.bob.go index 5340029a..050b1a47 100644 --- a/db/dberrors/oauth_token.bob.go +++ b/db/dberrors/oauth_token.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/organization.bob.go b/db/dberrors/organization.bob.go index 1368c885..d9bf3dbd 100644 --- a/db/dberrors/organization.bob.go +++ b/db/dberrors/organization.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.nuisance.bob.go b/db/dberrors/publicreport.nuisance.bob.go index fe815789..c1fcebb0 100644 --- a/db/dberrors/publicreport.nuisance.bob.go +++ b/db/dberrors/publicreport.nuisance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.pool.bob.go b/db/dberrors/publicreport.pool.bob.go index d7ab3834..aefb420d 100644 --- a/db/dberrors/publicreport.pool.bob.go +++ b/db/dberrors/publicreport.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.pool_photo.bob.go b/db/dberrors/publicreport.pool_photo.bob.go index 2bfed48d..0a5310e6 100644 --- a/db/dberrors/publicreport.pool_photo.bob.go +++ b/db/dberrors/publicreport.pool_photo.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.quick.bob.go b/db/dberrors/publicreport.quick.bob.go index f2d7db21..d51ed705 100644 --- a/db/dberrors/publicreport.quick.bob.go +++ b/db/dberrors/publicreport.quick.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.quick_photo.bob.go b/db/dberrors/publicreport.quick_photo.bob.go index cce0eee3..4dd54714 100644 --- a/db/dberrors/publicreport.quick_photo.bob.go +++ b/db/dberrors/publicreport.quick_photo.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/sessions.bob.go b/db/dberrors/sessions.bob.go index 8b54a349..e271ce05 100644 --- a/db/dberrors/sessions.bob.go +++ b/db/dberrors/sessions.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/spatial_ref_sys.bob.go b/db/dberrors/spatial_ref_sys.bob.go index 769f4bc2..195ac12d 100644 --- a/db/dberrors/spatial_ref_sys.bob.go +++ b/db/dberrors/spatial_ref_sys.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/user_.bob.go b/db/dberrors/user_.bob.go index 8b8b7295..d000ac1f 100644 --- a/db/dberrors/user_.bob.go +++ b/db/dberrors/user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dbinfo/arcgis.user_.bob.go b/db/dbinfo/arcgis.user_.bob.go new file mode 100644 index 00000000..772f92db --- /dev/null +++ b/db/dbinfo/arcgis.user_.bob.go @@ -0,0 +1,237 @@ +// Code generated by BobGen psql v0.42.1. 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 ArcgisUsers = Table[ + arcgisuserColumns, + arcgisuserIndexes, + arcgisuserForeignKeys, + arcgisuserUniques, + arcgisuserChecks, +]{ + Schema: "arcgis", + Name: "user_", + Columns: arcgisuserColumns{ + Access: column{ + Name: "access", + 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, + }, + Email: column{ + Name: "email", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + FullName: column{ + Name: "full_name", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + ID: column{ + Name: "id", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Level: column{ + Name: "level", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + OrgID: column{ + Name: "org_id", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + PublicUserID: column{ + Name: "public_user_id", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Region: column{ + Name: "region", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Role: column{ + Name: "role", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + RoleID: column{ + Name: "role_id", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Username: column{ + Name: "username", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + UserLicenseTypeID: column{ + Name: "user_license_type_id", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + UserType: column{ + Name: "user_type", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: arcgisuserIndexes{ + UserPkey: index{ + Type: "btree", + Name: "user__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: "user__pkey", + Columns: []string{"id"}, + Comment: "", + }, + ForeignKeys: arcgisuserForeignKeys{ + ArcgisUserUserPublicUserIDFkey: foreignKey{ + constraint: constraint{ + Name: "arcgis.user_.user__public_user_id_fkey", + Columns: []string{"public_user_id"}, + Comment: "", + }, + ForeignTable: "user_", + ForeignColumns: []string{"id"}, + }, + }, + + Comment: "", +} + +type arcgisuserColumns struct { + Access column + Created column + Email column + FullName column + ID column + Level column + OrgID column + PublicUserID column + Region column + Role column + RoleID column + Username column + UserLicenseTypeID column + UserType column +} + +func (c arcgisuserColumns) AsSlice() []column { + return []column{ + c.Access, c.Created, c.Email, c.FullName, c.ID, c.Level, c.OrgID, c.PublicUserID, c.Region, c.Role, c.RoleID, c.Username, c.UserLicenseTypeID, c.UserType, + } +} + +type arcgisuserIndexes struct { + UserPkey index +} + +func (i arcgisuserIndexes) AsSlice() []index { + return []index{ + i.UserPkey, + } +} + +type arcgisuserForeignKeys struct { + ArcgisUserUserPublicUserIDFkey foreignKey +} + +func (f arcgisuserForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.ArcgisUserUserPublicUserIDFkey, + } +} + +type arcgisuserUniques struct{} + +func (u arcgisuserUniques) AsSlice() []constraint { + return []constraint{} +} + +type arcgisuserChecks struct{} + +func (c arcgisuserChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/arcgis.user_privilege.bob.go b/db/dbinfo/arcgis.user_privilege.bob.go new file mode 100644 index 00000000..88e2785f --- /dev/null +++ b/db/dbinfo/arcgis.user_privilege.bob.go @@ -0,0 +1,122 @@ +// Code generated by BobGen psql v0.42.1. 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 ArcgisUserPrivileges = Table[ + arcgisUserPrivilegeColumns, + arcgisUserPrivilegeIndexes, + arcgisUserPrivilegeForeignKeys, + arcgisUserPrivilegeUniques, + arcgisUserPrivilegeChecks, +]{ + Schema: "arcgis", + Name: "user_privilege", + Columns: arcgisUserPrivilegeColumns{ + UserID: column{ + Name: "user_id", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Privilege: column{ + Name: "privilege", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: arcgisUserPrivilegeIndexes{ + UserPrivilegePkey: index{ + Type: "btree", + Name: "user_privilege_pkey", + Columns: []indexColumn{ + { + Name: "user_id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + { + Name: "privilege", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false, false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "user_privilege_pkey", + Columns: []string{"user_id", "privilege"}, + Comment: "", + }, + ForeignKeys: arcgisUserPrivilegeForeignKeys{ + ArcgisUserPrivilegeUserPrivilegeUserIDFkey: foreignKey{ + constraint: constraint{ + Name: "arcgis.user_privilege.user_privilege_user_id_fkey", + Columns: []string{"user_id"}, + Comment: "", + }, + ForeignTable: "arcgis.user_", + ForeignColumns: []string{"id"}, + }, + }, + + Comment: "", +} + +type arcgisUserPrivilegeColumns struct { + UserID column + Privilege column +} + +func (c arcgisUserPrivilegeColumns) AsSlice() []column { + return []column{ + c.UserID, c.Privilege, + } +} + +type arcgisUserPrivilegeIndexes struct { + UserPrivilegePkey index +} + +func (i arcgisUserPrivilegeIndexes) AsSlice() []index { + return []index{ + i.UserPrivilegePkey, + } +} + +type arcgisUserPrivilegeForeignKeys struct { + ArcgisUserPrivilegeUserPrivilegeUserIDFkey foreignKey +} + +func (f arcgisUserPrivilegeForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.ArcgisUserPrivilegeUserPrivilegeUserIDFkey, + } +} + +type arcgisUserPrivilegeUniques struct{} + +func (u arcgisUserPrivilegeUniques) AsSlice() []constraint { + return []constraint{} +} + +type arcgisUserPrivilegeChecks struct{} + +func (c arcgisUserPrivilegeChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/bob_types.bob.go b/db/dbinfo/bob_types.bob.go index 53d5f9be..2ece4dab 100644 --- a/db/dbinfo/bob_types.bob.go +++ b/db/dbinfo/bob_types.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/district.bob.go b/db/dbinfo/district.bob.go index 11976f13..1cbd0a70 100644 --- a/db/dbinfo/district.bob.go +++ b/db/dbinfo/district.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.containerrelate.bob.go b/db/dbinfo/fieldseeker.containerrelate.bob.go index e7f9fe89..9b293435 100644 --- a/db/dbinfo/fieldseeker.containerrelate.bob.go +++ b/db/dbinfo/fieldseeker.containerrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.fieldscoutinglog.bob.go b/db/dbinfo/fieldseeker.fieldscoutinglog.bob.go index c506a744..7a8b2e88 100644 --- a/db/dbinfo/fieldseeker.fieldscoutinglog.bob.go +++ b/db/dbinfo/fieldseeker.fieldscoutinglog.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.habitatrelate.bob.go b/db/dbinfo/fieldseeker.habitatrelate.bob.go index b19ac6ac..5b7eef2f 100644 --- a/db/dbinfo/fieldseeker.habitatrelate.bob.go +++ b/db/dbinfo/fieldseeker.habitatrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.inspectionsample.bob.go b/db/dbinfo/fieldseeker.inspectionsample.bob.go index d2e307a2..7222c516 100644 --- a/db/dbinfo/fieldseeker.inspectionsample.bob.go +++ b/db/dbinfo/fieldseeker.inspectionsample.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.inspectionsampledetail.bob.go b/db/dbinfo/fieldseeker.inspectionsampledetail.bob.go index 6587df5f..9bfee61f 100644 --- a/db/dbinfo/fieldseeker.inspectionsampledetail.bob.go +++ b/db/dbinfo/fieldseeker.inspectionsampledetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.linelocation.bob.go b/db/dbinfo/fieldseeker.linelocation.bob.go index ef4c5c02..426609fa 100644 --- a/db/dbinfo/fieldseeker.linelocation.bob.go +++ b/db/dbinfo/fieldseeker.linelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.locationtracking.bob.go b/db/dbinfo/fieldseeker.locationtracking.bob.go index 65aba4b0..fa3d2922 100644 --- a/db/dbinfo/fieldseeker.locationtracking.bob.go +++ b/db/dbinfo/fieldseeker.locationtracking.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.mosquitoinspection.bob.go b/db/dbinfo/fieldseeker.mosquitoinspection.bob.go index 15436115..333ae452 100644 --- a/db/dbinfo/fieldseeker.mosquitoinspection.bob.go +++ b/db/dbinfo/fieldseeker.mosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.pointlocation.bob.go b/db/dbinfo/fieldseeker.pointlocation.bob.go index 999e6ef6..5f65e44a 100644 --- a/db/dbinfo/fieldseeker.pointlocation.bob.go +++ b/db/dbinfo/fieldseeker.pointlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.polygonlocation.bob.go b/db/dbinfo/fieldseeker.polygonlocation.bob.go index 835412ab..e200e6c7 100644 --- a/db/dbinfo/fieldseeker.polygonlocation.bob.go +++ b/db/dbinfo/fieldseeker.polygonlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.pool.bob.go b/db/dbinfo/fieldseeker.pool.bob.go index 51158097..9f359745 100644 --- a/db/dbinfo/fieldseeker.pool.bob.go +++ b/db/dbinfo/fieldseeker.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.pooldetail.bob.go b/db/dbinfo/fieldseeker.pooldetail.bob.go index 65c5b988..b3a9e700 100644 --- a/db/dbinfo/fieldseeker.pooldetail.bob.go +++ b/db/dbinfo/fieldseeker.pooldetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.proposedtreatmentarea.bob.go b/db/dbinfo/fieldseeker.proposedtreatmentarea.bob.go index 8ae3d2f6..f85288c6 100644 --- a/db/dbinfo/fieldseeker.proposedtreatmentarea.bob.go +++ b/db/dbinfo/fieldseeker.proposedtreatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.qamosquitoinspection.bob.go b/db/dbinfo/fieldseeker.qamosquitoinspection.bob.go index c841701d..76da7dd7 100644 --- a/db/dbinfo/fieldseeker.qamosquitoinspection.bob.go +++ b/db/dbinfo/fieldseeker.qamosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.rodentlocation.bob.go b/db/dbinfo/fieldseeker.rodentlocation.bob.go index 5e57a239..8713d101 100644 --- a/db/dbinfo/fieldseeker.rodentlocation.bob.go +++ b/db/dbinfo/fieldseeker.rodentlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.samplecollection.bob.go b/db/dbinfo/fieldseeker.samplecollection.bob.go index a22bc7ec..a205203b 100644 --- a/db/dbinfo/fieldseeker.samplecollection.bob.go +++ b/db/dbinfo/fieldseeker.samplecollection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.samplelocation.bob.go b/db/dbinfo/fieldseeker.samplelocation.bob.go index 89007afd..1f63b1c4 100644 --- a/db/dbinfo/fieldseeker.samplelocation.bob.go +++ b/db/dbinfo/fieldseeker.samplelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.servicerequest.bob.go b/db/dbinfo/fieldseeker.servicerequest.bob.go index 92a55390..30de6afe 100644 --- a/db/dbinfo/fieldseeker.servicerequest.bob.go +++ b/db/dbinfo/fieldseeker.servicerequest.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.speciesabundance.bob.go b/db/dbinfo/fieldseeker.speciesabundance.bob.go index 3d67c19a..7431468f 100644 --- a/db/dbinfo/fieldseeker.speciesabundance.bob.go +++ b/db/dbinfo/fieldseeker.speciesabundance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.stormdrain.bob.go b/db/dbinfo/fieldseeker.stormdrain.bob.go index 931622c0..412832a3 100644 --- a/db/dbinfo/fieldseeker.stormdrain.bob.go +++ b/db/dbinfo/fieldseeker.stormdrain.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.timecard.bob.go b/db/dbinfo/fieldseeker.timecard.bob.go index d635a67a..65b6a792 100644 --- a/db/dbinfo/fieldseeker.timecard.bob.go +++ b/db/dbinfo/fieldseeker.timecard.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.trapdata.bob.go b/db/dbinfo/fieldseeker.trapdata.bob.go index 14f83873..2c7c523f 100644 --- a/db/dbinfo/fieldseeker.trapdata.bob.go +++ b/db/dbinfo/fieldseeker.trapdata.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.traplocation.bob.go b/db/dbinfo/fieldseeker.traplocation.bob.go index b2563629..cc3cda90 100644 --- a/db/dbinfo/fieldseeker.traplocation.bob.go +++ b/db/dbinfo/fieldseeker.traplocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.treatment.bob.go b/db/dbinfo/fieldseeker.treatment.bob.go index 914c0532..b6a0075e 100644 --- a/db/dbinfo/fieldseeker.treatment.bob.go +++ b/db/dbinfo/fieldseeker.treatment.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.treatmentarea.bob.go b/db/dbinfo/fieldseeker.treatmentarea.bob.go index f4dbf8b7..d9df5b58 100644 --- a/db/dbinfo/fieldseeker.treatmentarea.bob.go +++ b/db/dbinfo/fieldseeker.treatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.zones.bob.go b/db/dbinfo/fieldseeker.zones.bob.go index eb1ecf75..1cd771cd 100644 --- a/db/dbinfo/fieldseeker.zones.bob.go +++ b/db/dbinfo/fieldseeker.zones.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.zones2.bob.go b/db/dbinfo/fieldseeker.zones2.bob.go index 6d26143d..7a55e0e8 100644 --- a/db/dbinfo/fieldseeker.zones2.bob.go +++ b/db/dbinfo/fieldseeker.zones2.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker_sync.bob.go b/db/dbinfo/fieldseeker_sync.bob.go index 6a53ec8e..f0d18f63 100644 --- a/db/dbinfo/fieldseeker_sync.bob.go +++ b/db/dbinfo/fieldseeker_sync.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/geography_columns.bob.go b/db/dbinfo/geography_columns.bob.go index 0cd86251..fede3218 100644 --- a/db/dbinfo/geography_columns.bob.go +++ b/db/dbinfo/geography_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/geometry_columns.bob.go b/db/dbinfo/geometry_columns.bob.go index c560a3d8..2292ee9a 100644 --- a/db/dbinfo/geometry_columns.bob.go +++ b/db/dbinfo/geometry_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/goose_db_version.bob.go b/db/dbinfo/goose_db_version.bob.go index 60d0e295..9bda0126 100644 --- a/db/dbinfo/goose_db_version.bob.go +++ b/db/dbinfo/goose_db_version.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/h3_aggregation.bob.go b/db/dbinfo/h3_aggregation.bob.go index 3f640a73..65bd6414 100644 --- a/db/dbinfo/h3_aggregation.bob.go +++ b/db/dbinfo/h3_aggregation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_audio.bob.go b/db/dbinfo/note_audio.bob.go index 413aa3e6..4725221d 100644 --- a/db/dbinfo/note_audio.bob.go +++ b/db/dbinfo/note_audio.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_audio_breadcrumb.bob.go b/db/dbinfo/note_audio_breadcrumb.bob.go index 9031f61a..0f6e5248 100644 --- a/db/dbinfo/note_audio_breadcrumb.bob.go +++ b/db/dbinfo/note_audio_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_audio_data.bob.go b/db/dbinfo/note_audio_data.bob.go index 20b42526..8e3dae3f 100644 --- a/db/dbinfo/note_audio_data.bob.go +++ b/db/dbinfo/note_audio_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_image.bob.go b/db/dbinfo/note_image.bob.go index 36545e55..89a0dccd 100644 --- a/db/dbinfo/note_image.bob.go +++ b/db/dbinfo/note_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_image_breadcrumb.bob.go b/db/dbinfo/note_image_breadcrumb.bob.go index a7dd7323..7824d997 100644 --- a/db/dbinfo/note_image_breadcrumb.bob.go +++ b/db/dbinfo/note_image_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_image_data.bob.go b/db/dbinfo/note_image_data.bob.go index be198b8f..dade46b3 100644 --- a/db/dbinfo/note_image_data.bob.go +++ b/db/dbinfo/note_image_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/notification.bob.go b/db/dbinfo/notification.bob.go index b4869c9a..b405ed17 100644 --- a/db/dbinfo/notification.bob.go +++ b/db/dbinfo/notification.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/oauth_token.bob.go b/db/dbinfo/oauth_token.bob.go index e72e1cb2..e7ae9dcf 100644 --- a/db/dbinfo/oauth_token.bob.go +++ b/db/dbinfo/oauth_token.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/organization.bob.go b/db/dbinfo/organization.bob.go index c65f22a4..3539999d 100644 --- a/db/dbinfo/organization.bob.go +++ b/db/dbinfo/organization.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.nuisance.bob.go b/db/dbinfo/publicreport.nuisance.bob.go index 4eef190c..625162eb 100644 --- a/db/dbinfo/publicreport.nuisance.bob.go +++ b/db/dbinfo/publicreport.nuisance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.pool.bob.go b/db/dbinfo/publicreport.pool.bob.go index 482e9357..045b24d7 100644 --- a/db/dbinfo/publicreport.pool.bob.go +++ b/db/dbinfo/publicreport.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.pool_photo.bob.go b/db/dbinfo/publicreport.pool_photo.bob.go index 8d22839c..8b116463 100644 --- a/db/dbinfo/publicreport.pool_photo.bob.go +++ b/db/dbinfo/publicreport.pool_photo.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.quick.bob.go b/db/dbinfo/publicreport.quick.bob.go index eacf3433..77afb678 100644 --- a/db/dbinfo/publicreport.quick.bob.go +++ b/db/dbinfo/publicreport.quick.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.quick_photo.bob.go b/db/dbinfo/publicreport.quick_photo.bob.go index 9a7d4eca..f827d142 100644 --- a/db/dbinfo/publicreport.quick_photo.bob.go +++ b/db/dbinfo/publicreport.quick_photo.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.report_location.bob.go b/db/dbinfo/publicreport.report_location.bob.go index f59c9427..92fa50fe 100644 --- a/db/dbinfo/publicreport.report_location.bob.go +++ b/db/dbinfo/publicreport.report_location.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/raster_columns.bob.go b/db/dbinfo/raster_columns.bob.go index 485a0e21..a7d56223 100644 --- a/db/dbinfo/raster_columns.bob.go +++ b/db/dbinfo/raster_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/raster_overviews.bob.go b/db/dbinfo/raster_overviews.bob.go index 40e7ff4b..7199817b 100644 --- a/db/dbinfo/raster_overviews.bob.go +++ b/db/dbinfo/raster_overviews.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/sessions.bob.go b/db/dbinfo/sessions.bob.go index 365152f9..4f66b5ce 100644 --- a/db/dbinfo/sessions.bob.go +++ b/db/dbinfo/sessions.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/spatial_ref_sys.bob.go b/db/dbinfo/spatial_ref_sys.bob.go index cd06d3f8..78b5a363 100644 --- a/db/dbinfo/spatial_ref_sys.bob.go +++ b/db/dbinfo/spatial_ref_sys.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/user_.bob.go b/db/dbinfo/user_.bob.go index 556adb13..b72cfaf4 100644 --- a/db/dbinfo/user_.bob.go +++ b/db/dbinfo/user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/enums/enums.bob.go b/db/enums/enums.bob.go index ff1c3c5b..34c6ec6f 100644 --- a/db/enums/enums.bob.go +++ b/db/enums/enums.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package enums diff --git a/db/factory/arcgis.user_.bob.go b/db/factory/arcgis.user_.bob.go new file mode 100644 index 00000000..43969366 --- /dev/null +++ b/db/factory/arcgis.user_.bob.go @@ -0,0 +1,984 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + "time" + + models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/omit" + "github.com/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type ArcgisUserMod interface { + Apply(context.Context, *ArcgisUserTemplate) +} + +type ArcgisUserModFunc func(context.Context, *ArcgisUserTemplate) + +func (f ArcgisUserModFunc) Apply(ctx context.Context, n *ArcgisUserTemplate) { + f(ctx, n) +} + +type ArcgisUserModSlice []ArcgisUserMod + +func (mods ArcgisUserModSlice) Apply(ctx context.Context, n *ArcgisUserTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// ArcgisUserTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type ArcgisUserTemplate struct { + Access func() string + Created func() time.Time + Email func() string + FullName func() string + ID func() string + Level func() string + OrgID func() string + PublicUserID func() int32 + Region func() string + Role func() string + RoleID func() string + Username func() string + UserLicenseTypeID func() string + UserType func() string + + r arcgisuserR + f *Factory + + alreadyPersisted bool +} + +type arcgisuserR struct { + PublicUserUser *arcgisuserRPublicUserUserR + UserUserPrivileges []*arcgisuserRUserUserPrivilegesR +} + +type arcgisuserRPublicUserUserR struct { + o *UserTemplate +} +type arcgisuserRUserUserPrivilegesR struct { + number int + o *ArcgisUserPrivilegeTemplate +} + +// Apply mods to the ArcgisUserTemplate +func (o *ArcgisUserTemplate) Apply(ctx context.Context, mods ...ArcgisUserMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.ArcgisUser +// according to the relationships in the template. Nothing is inserted into the db +func (t ArcgisUserTemplate) setModelRels(o *models.ArcgisUser) { + if t.r.PublicUserUser != nil { + rel := t.r.PublicUserUser.o.Build() + rel.R.PublicUserUser = append(rel.R.PublicUserUser, o) + o.PublicUserID = rel.ID // h2 + o.R.PublicUserUser = rel + } + + if t.r.UserUserPrivileges != nil { + rel := models.ArcgisUserPrivilegeSlice{} + for _, r := range t.r.UserUserPrivileges { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.UserID = o.ID // h2 + rel.R.UserUser = o + } + rel = append(rel, related...) + } + o.R.UserUserPrivileges = rel + } +} + +// BuildSetter returns an *models.ArcgisUserSetter +// this does nothing with the relationship templates +func (o ArcgisUserTemplate) BuildSetter() *models.ArcgisUserSetter { + m := &models.ArcgisUserSetter{} + + if o.Access != nil { + val := o.Access() + m.Access = omit.From(val) + } + if o.Created != nil { + val := o.Created() + m.Created = omit.From(val) + } + if o.Email != nil { + val := o.Email() + m.Email = omit.From(val) + } + if o.FullName != nil { + val := o.FullName() + m.FullName = omit.From(val) + } + if o.ID != nil { + val := o.ID() + m.ID = omit.From(val) + } + if o.Level != nil { + val := o.Level() + m.Level = omit.From(val) + } + if o.OrgID != nil { + val := o.OrgID() + m.OrgID = omit.From(val) + } + if o.PublicUserID != nil { + val := o.PublicUserID() + m.PublicUserID = omit.From(val) + } + if o.Region != nil { + val := o.Region() + m.Region = omit.From(val) + } + if o.Role != nil { + val := o.Role() + m.Role = omit.From(val) + } + if o.RoleID != nil { + val := o.RoleID() + m.RoleID = omit.From(val) + } + if o.Username != nil { + val := o.Username() + m.Username = omit.From(val) + } + if o.UserLicenseTypeID != nil { + val := o.UserLicenseTypeID() + m.UserLicenseTypeID = omit.From(val) + } + if o.UserType != nil { + val := o.UserType() + m.UserType = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.ArcgisUserSetter +// this does nothing with the relationship templates +func (o ArcgisUserTemplate) BuildManySetter(number int) []*models.ArcgisUserSetter { + m := make([]*models.ArcgisUserSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.ArcgisUser +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use ArcgisUserTemplate.Create +func (o ArcgisUserTemplate) Build() *models.ArcgisUser { + m := &models.ArcgisUser{} + + if o.Access != nil { + m.Access = o.Access() + } + if o.Created != nil { + m.Created = o.Created() + } + if o.Email != nil { + m.Email = o.Email() + } + if o.FullName != nil { + m.FullName = o.FullName() + } + if o.ID != nil { + m.ID = o.ID() + } + if o.Level != nil { + m.Level = o.Level() + } + if o.OrgID != nil { + m.OrgID = o.OrgID() + } + if o.PublicUserID != nil { + m.PublicUserID = o.PublicUserID() + } + if o.Region != nil { + m.Region = o.Region() + } + if o.Role != nil { + m.Role = o.Role() + } + if o.RoleID != nil { + m.RoleID = o.RoleID() + } + if o.Username != nil { + m.Username = o.Username() + } + if o.UserLicenseTypeID != nil { + m.UserLicenseTypeID = o.UserLicenseTypeID() + } + if o.UserType != nil { + m.UserType = o.UserType() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.ArcgisUserSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use ArcgisUserTemplate.CreateMany +func (o ArcgisUserTemplate) BuildMany(number int) models.ArcgisUserSlice { + m := make(models.ArcgisUserSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableArcgisUser(m *models.ArcgisUserSetter) { + if !(m.Access.IsValue()) { + val := random_string(nil) + m.Access = omit.From(val) + } + if !(m.Created.IsValue()) { + val := random_time_Time(nil) + m.Created = omit.From(val) + } + if !(m.Email.IsValue()) { + val := random_string(nil) + m.Email = omit.From(val) + } + if !(m.FullName.IsValue()) { + val := random_string(nil) + m.FullName = omit.From(val) + } + if !(m.ID.IsValue()) { + val := random_string(nil) + m.ID = omit.From(val) + } + if !(m.Level.IsValue()) { + val := random_string(nil) + m.Level = omit.From(val) + } + if !(m.OrgID.IsValue()) { + val := random_string(nil) + m.OrgID = omit.From(val) + } + if !(m.PublicUserID.IsValue()) { + val := random_int32(nil) + m.PublicUserID = omit.From(val) + } + if !(m.Region.IsValue()) { + val := random_string(nil) + m.Region = omit.From(val) + } + if !(m.Role.IsValue()) { + val := random_string(nil) + m.Role = omit.From(val) + } + if !(m.RoleID.IsValue()) { + val := random_string(nil) + m.RoleID = omit.From(val) + } + if !(m.Username.IsValue()) { + val := random_string(nil) + m.Username = omit.From(val) + } + if !(m.UserLicenseTypeID.IsValue()) { + val := random_string(nil) + m.UserLicenseTypeID = omit.From(val) + } + if !(m.UserType.IsValue()) { + val := random_string(nil) + m.UserType = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.ArcgisUser +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *ArcgisUserTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.ArcgisUser) error { + var err error + + isUserUserPrivilegesDone, _ := arcgisuserRelUserUserPrivilegesCtx.Value(ctx) + if !isUserUserPrivilegesDone && o.r.UserUserPrivileges != nil { + ctx = arcgisuserRelUserUserPrivilegesCtx.WithValue(ctx, true) + for _, r := range o.r.UserUserPrivileges { + if r.o.alreadyPersisted { + m.R.UserUserPrivileges = append(m.R.UserUserPrivileges, r.o.Build()) + } else { + rel1, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachUserUserPrivileges(ctx, exec, rel1...) + if err != nil { + return err + } + } + } + } + + return err +} + +// Create builds a arcgisuser and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *ArcgisUserTemplate) Create(ctx context.Context, exec bob.Executor) (*models.ArcgisUser, error) { + var err error + opt := o.BuildSetter() + ensureCreatableArcgisUser(opt) + + if o.r.PublicUserUser == nil { + ArcgisUserMods.WithNewPublicUserUser().Apply(ctx, o) + } + + var rel0 *models.User + + if o.r.PublicUserUser.o.alreadyPersisted { + rel0 = o.r.PublicUserUser.o.Build() + } else { + rel0, err = o.r.PublicUserUser.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.PublicUserID = omit.From(rel0.ID) + + m, err := models.ArcgisUsers.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + m.R.PublicUserUser = rel0 + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a arcgisuser and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *ArcgisUserTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.ArcgisUser { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a arcgisuser 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 *ArcgisUserTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.ArcgisUser { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple arcgisusers and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o ArcgisUserTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.ArcgisUserSlice, error) { + var err error + m := make(models.ArcgisUserSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple arcgisusers and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o ArcgisUserTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.ArcgisUserSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple arcgisusers 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 ArcgisUserTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.ArcgisUserSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// ArcgisUser has methods that act as mods for the ArcgisUserTemplate +var ArcgisUserMods arcgisuserMods + +type arcgisuserMods struct{} + +func (m arcgisuserMods) RandomizeAllColumns(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModSlice{ + ArcgisUserMods.RandomAccess(f), + ArcgisUserMods.RandomCreated(f), + ArcgisUserMods.RandomEmail(f), + ArcgisUserMods.RandomFullName(f), + ArcgisUserMods.RandomID(f), + ArcgisUserMods.RandomLevel(f), + ArcgisUserMods.RandomOrgID(f), + ArcgisUserMods.RandomPublicUserID(f), + ArcgisUserMods.RandomRegion(f), + ArcgisUserMods.RandomRole(f), + ArcgisUserMods.RandomRoleID(f), + ArcgisUserMods.RandomUsername(f), + ArcgisUserMods.RandomUserLicenseTypeID(f), + ArcgisUserMods.RandomUserType(f), + } +} + +// Set the model columns to this value +func (m arcgisuserMods) Access(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Access = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) AccessFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Access = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetAccess() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Access = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomAccess(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Access = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) Created(val time.Time) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Created = func() time.Time { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) CreatedFunc(f func() time.Time) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Created = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetCreated() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Created = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomCreated(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Created = func() time.Time { + return random_time_Time(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) Email(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Email = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) EmailFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Email = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetEmail() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Email = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomEmail(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Email = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) FullName(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.FullName = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) FullNameFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.FullName = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetFullName() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.FullName = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomFullName(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.FullName = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) ID(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.ID = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) IDFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetID() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + 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 arcgisuserMods) RandomID(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.ID = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) Level(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Level = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) LevelFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Level = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetLevel() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Level = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomLevel(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Level = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) OrgID(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.OrgID = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) OrgIDFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.OrgID = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetOrgID() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.OrgID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomOrgID(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.OrgID = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) PublicUserID(val int32) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.PublicUserID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) PublicUserIDFunc(f func() int32) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.PublicUserID = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetPublicUserID() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.PublicUserID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomPublicUserID(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.PublicUserID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) Region(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Region = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) RegionFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Region = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetRegion() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Region = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomRegion(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Region = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) Role(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Role = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) RoleFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Role = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetRole() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Role = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomRole(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Role = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) RoleID(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.RoleID = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) RoleIDFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.RoleID = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetRoleID() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.RoleID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomRoleID(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.RoleID = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) Username(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Username = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) UsernameFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Username = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetUsername() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Username = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomUsername(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.Username = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) UserLicenseTypeID(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.UserLicenseTypeID = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) UserLicenseTypeIDFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.UserLicenseTypeID = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetUserLicenseTypeID() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.UserLicenseTypeID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomUserLicenseTypeID(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.UserLicenseTypeID = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisuserMods) UserType(val string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.UserType = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisuserMods) UserTypeFunc(f func() string) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.UserType = f + }) +} + +// Clear any values for the column +func (m arcgisuserMods) UnsetUserType() ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.UserType = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisuserMods) RandomUserType(f *faker.Faker) ArcgisUserMod { + return ArcgisUserModFunc(func(_ context.Context, o *ArcgisUserTemplate) { + o.UserType = func() string { + return random_string(f) + } + }) +} + +func (m arcgisuserMods) WithParentsCascading() ArcgisUserMod { + return ArcgisUserModFunc(func(ctx context.Context, o *ArcgisUserTemplate) { + if isDone, _ := arcgisuserWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = arcgisuserWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewUserWithContext(ctx, UserMods.WithParentsCascading()) + m.WithPublicUserUser(related).Apply(ctx, o) + } + }) +} + +func (m arcgisuserMods) WithPublicUserUser(rel *UserTemplate) ArcgisUserMod { + return ArcgisUserModFunc(func(ctx context.Context, o *ArcgisUserTemplate) { + o.r.PublicUserUser = &arcgisuserRPublicUserUserR{ + o: rel, + } + }) +} + +func (m arcgisuserMods) WithNewPublicUserUser(mods ...UserMod) ArcgisUserMod { + return ArcgisUserModFunc(func(ctx context.Context, o *ArcgisUserTemplate) { + related := o.f.NewUserWithContext(ctx, mods...) + + m.WithPublicUserUser(related).Apply(ctx, o) + }) +} + +func (m arcgisuserMods) WithExistingPublicUserUser(em *models.User) ArcgisUserMod { + return ArcgisUserModFunc(func(ctx context.Context, o *ArcgisUserTemplate) { + o.r.PublicUserUser = &arcgisuserRPublicUserUserR{ + o: o.f.FromExistingUser(em), + } + }) +} + +func (m arcgisuserMods) WithoutPublicUserUser() ArcgisUserMod { + return ArcgisUserModFunc(func(ctx context.Context, o *ArcgisUserTemplate) { + o.r.PublicUserUser = nil + }) +} + +func (m arcgisuserMods) WithUserUserPrivileges(number int, related *ArcgisUserPrivilegeTemplate) ArcgisUserMod { + return ArcgisUserModFunc(func(ctx context.Context, o *ArcgisUserTemplate) { + o.r.UserUserPrivileges = []*arcgisuserRUserUserPrivilegesR{{ + number: number, + o: related, + }} + }) +} + +func (m arcgisuserMods) WithNewUserUserPrivileges(number int, mods ...ArcgisUserPrivilegeMod) ArcgisUserMod { + return ArcgisUserModFunc(func(ctx context.Context, o *ArcgisUserTemplate) { + related := o.f.NewArcgisUserPrivilegeWithContext(ctx, mods...) + m.WithUserUserPrivileges(number, related).Apply(ctx, o) + }) +} + +func (m arcgisuserMods) AddUserUserPrivileges(number int, related *ArcgisUserPrivilegeTemplate) ArcgisUserMod { + return ArcgisUserModFunc(func(ctx context.Context, o *ArcgisUserTemplate) { + o.r.UserUserPrivileges = append(o.r.UserUserPrivileges, &arcgisuserRUserUserPrivilegesR{ + number: number, + o: related, + }) + }) +} + +func (m arcgisuserMods) AddNewUserUserPrivileges(number int, mods ...ArcgisUserPrivilegeMod) ArcgisUserMod { + return ArcgisUserModFunc(func(ctx context.Context, o *ArcgisUserTemplate) { + related := o.f.NewArcgisUserPrivilegeWithContext(ctx, mods...) + m.AddUserUserPrivileges(number, related).Apply(ctx, o) + }) +} + +func (m arcgisuserMods) AddExistingUserUserPrivileges(existingModels ...*models.ArcgisUserPrivilege) ArcgisUserMod { + return ArcgisUserModFunc(func(ctx context.Context, o *ArcgisUserTemplate) { + for _, em := range existingModels { + o.r.UserUserPrivileges = append(o.r.UserUserPrivileges, &arcgisuserRUserUserPrivilegesR{ + o: o.f.FromExistingArcgisUserPrivilege(em), + }) + } + }) +} + +func (m arcgisuserMods) WithoutUserUserPrivileges() ArcgisUserMod { + return ArcgisUserModFunc(func(ctx context.Context, o *ArcgisUserTemplate) { + o.r.UserUserPrivileges = nil + }) +} diff --git a/db/factory/arcgis.user_privilege.bob.go b/db/factory/arcgis.user_privilege.bob.go new file mode 100644 index 00000000..37ed5e39 --- /dev/null +++ b/db/factory/arcgis.user_privilege.bob.go @@ -0,0 +1,369 @@ +// Code generated by BobGen psql v0.42.1. 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/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type ArcgisUserPrivilegeMod interface { + Apply(context.Context, *ArcgisUserPrivilegeTemplate) +} + +type ArcgisUserPrivilegeModFunc func(context.Context, *ArcgisUserPrivilegeTemplate) + +func (f ArcgisUserPrivilegeModFunc) Apply(ctx context.Context, n *ArcgisUserPrivilegeTemplate) { + f(ctx, n) +} + +type ArcgisUserPrivilegeModSlice []ArcgisUserPrivilegeMod + +func (mods ArcgisUserPrivilegeModSlice) Apply(ctx context.Context, n *ArcgisUserPrivilegeTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// ArcgisUserPrivilegeTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type ArcgisUserPrivilegeTemplate struct { + UserID func() string + Privilege func() string + + r arcgisUserPrivilegeR + f *Factory + + alreadyPersisted bool +} + +type arcgisUserPrivilegeR struct { + UserUser *arcgisUserPrivilegeRUserUserR +} + +type arcgisUserPrivilegeRUserUserR struct { + o *ArcgisUserTemplate +} + +// Apply mods to the ArcgisUserPrivilegeTemplate +func (o *ArcgisUserPrivilegeTemplate) Apply(ctx context.Context, mods ...ArcgisUserPrivilegeMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.ArcgisUserPrivilege +// according to the relationships in the template. Nothing is inserted into the db +func (t ArcgisUserPrivilegeTemplate) setModelRels(o *models.ArcgisUserPrivilege) { + if t.r.UserUser != nil { + rel := t.r.UserUser.o.Build() + rel.R.UserUserPrivileges = append(rel.R.UserUserPrivileges, o) + o.UserID = rel.ID // h2 + o.R.UserUser = rel + } +} + +// BuildSetter returns an *models.ArcgisUserPrivilegeSetter +// this does nothing with the relationship templates +func (o ArcgisUserPrivilegeTemplate) BuildSetter() *models.ArcgisUserPrivilegeSetter { + m := &models.ArcgisUserPrivilegeSetter{} + + if o.UserID != nil { + val := o.UserID() + m.UserID = omit.From(val) + } + if o.Privilege != nil { + val := o.Privilege() + m.Privilege = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.ArcgisUserPrivilegeSetter +// this does nothing with the relationship templates +func (o ArcgisUserPrivilegeTemplate) BuildManySetter(number int) []*models.ArcgisUserPrivilegeSetter { + m := make([]*models.ArcgisUserPrivilegeSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.ArcgisUserPrivilege +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use ArcgisUserPrivilegeTemplate.Create +func (o ArcgisUserPrivilegeTemplate) Build() *models.ArcgisUserPrivilege { + m := &models.ArcgisUserPrivilege{} + + if o.UserID != nil { + m.UserID = o.UserID() + } + if o.Privilege != nil { + m.Privilege = o.Privilege() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.ArcgisUserPrivilegeSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use ArcgisUserPrivilegeTemplate.CreateMany +func (o ArcgisUserPrivilegeTemplate) BuildMany(number int) models.ArcgisUserPrivilegeSlice { + m := make(models.ArcgisUserPrivilegeSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableArcgisUserPrivilege(m *models.ArcgisUserPrivilegeSetter) { + if !(m.UserID.IsValue()) { + val := random_string(nil) + m.UserID = omit.From(val) + } + if !(m.Privilege.IsValue()) { + val := random_string(nil) + m.Privilege = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.ArcgisUserPrivilege +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *ArcgisUserPrivilegeTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.ArcgisUserPrivilege) error { + var err error + + return err +} + +// Create builds a arcgisUserPrivilege and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *ArcgisUserPrivilegeTemplate) Create(ctx context.Context, exec bob.Executor) (*models.ArcgisUserPrivilege, error) { + var err error + opt := o.BuildSetter() + ensureCreatableArcgisUserPrivilege(opt) + + if o.r.UserUser == nil { + ArcgisUserPrivilegeMods.WithNewUserUser().Apply(ctx, o) + } + + var rel0 *models.ArcgisUser + + if o.r.UserUser.o.alreadyPersisted { + rel0 = o.r.UserUser.o.Build() + } else { + rel0, err = o.r.UserUser.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.UserID = omit.From(rel0.ID) + + m, err := models.ArcgisUserPrivileges.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + m.R.UserUser = rel0 + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a arcgisUserPrivilege and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *ArcgisUserPrivilegeTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.ArcgisUserPrivilege { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a arcgisUserPrivilege 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 *ArcgisUserPrivilegeTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.ArcgisUserPrivilege { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple arcgisUserPrivileges and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o ArcgisUserPrivilegeTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.ArcgisUserPrivilegeSlice, error) { + var err error + m := make(models.ArcgisUserPrivilegeSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple arcgisUserPrivileges and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o ArcgisUserPrivilegeTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.ArcgisUserPrivilegeSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple arcgisUserPrivileges 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 ArcgisUserPrivilegeTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.ArcgisUserPrivilegeSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// ArcgisUserPrivilege has methods that act as mods for the ArcgisUserPrivilegeTemplate +var ArcgisUserPrivilegeMods arcgisUserPrivilegeMods + +type arcgisUserPrivilegeMods struct{} + +func (m arcgisUserPrivilegeMods) RandomizeAllColumns(f *faker.Faker) ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModSlice{ + ArcgisUserPrivilegeMods.RandomUserID(f), + ArcgisUserPrivilegeMods.RandomPrivilege(f), + } +} + +// Set the model columns to this value +func (m arcgisUserPrivilegeMods) UserID(val string) ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(_ context.Context, o *ArcgisUserPrivilegeTemplate) { + o.UserID = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisUserPrivilegeMods) UserIDFunc(f func() string) ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(_ context.Context, o *ArcgisUserPrivilegeTemplate) { + o.UserID = f + }) +} + +// Clear any values for the column +func (m arcgisUserPrivilegeMods) UnsetUserID() ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(_ context.Context, o *ArcgisUserPrivilegeTemplate) { + o.UserID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisUserPrivilegeMods) RandomUserID(f *faker.Faker) ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(_ context.Context, o *ArcgisUserPrivilegeTemplate) { + o.UserID = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m arcgisUserPrivilegeMods) Privilege(val string) ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(_ context.Context, o *ArcgisUserPrivilegeTemplate) { + o.Privilege = func() string { return val } + }) +} + +// Set the Column from the function +func (m arcgisUserPrivilegeMods) PrivilegeFunc(f func() string) ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(_ context.Context, o *ArcgisUserPrivilegeTemplate) { + o.Privilege = f + }) +} + +// Clear any values for the column +func (m arcgisUserPrivilegeMods) UnsetPrivilege() ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(_ context.Context, o *ArcgisUserPrivilegeTemplate) { + o.Privilege = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m arcgisUserPrivilegeMods) RandomPrivilege(f *faker.Faker) ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(_ context.Context, o *ArcgisUserPrivilegeTemplate) { + o.Privilege = func() string { + return random_string(f) + } + }) +} + +func (m arcgisUserPrivilegeMods) WithParentsCascading() ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(ctx context.Context, o *ArcgisUserPrivilegeTemplate) { + if isDone, _ := arcgisUserPrivilegeWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = arcgisUserPrivilegeWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewArcgisUserWithContext(ctx, ArcgisUserMods.WithParentsCascading()) + m.WithUserUser(related).Apply(ctx, o) + } + }) +} + +func (m arcgisUserPrivilegeMods) WithUserUser(rel *ArcgisUserTemplate) ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(ctx context.Context, o *ArcgisUserPrivilegeTemplate) { + o.r.UserUser = &arcgisUserPrivilegeRUserUserR{ + o: rel, + } + }) +} + +func (m arcgisUserPrivilegeMods) WithNewUserUser(mods ...ArcgisUserMod) ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(ctx context.Context, o *ArcgisUserPrivilegeTemplate) { + related := o.f.NewArcgisUserWithContext(ctx, mods...) + + m.WithUserUser(related).Apply(ctx, o) + }) +} + +func (m arcgisUserPrivilegeMods) WithExistingUserUser(em *models.ArcgisUser) ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(ctx context.Context, o *ArcgisUserPrivilegeTemplate) { + o.r.UserUser = &arcgisUserPrivilegeRUserUserR{ + o: o.f.FromExistingArcgisUser(em), + } + }) +} + +func (m arcgisUserPrivilegeMods) WithoutUserUser() ArcgisUserPrivilegeMod { + return ArcgisUserPrivilegeModFunc(func(ctx context.Context, o *ArcgisUserPrivilegeTemplate) { + o.r.UserUser = nil + }) +} diff --git a/db/factory/bobfactory_context.bob.go b/db/factory/bobfactory_context.bob.go index 0f3fa16f..f584a12c 100644 --- a/db/factory/bobfactory_context.bob.go +++ b/db/factory/bobfactory_context.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,6 +8,15 @@ import "context" type contextKey string var ( + // Relationship Contexts for arcgis.user_ + arcgisuserWithParentsCascadingCtx = newContextual[bool]("arcgisuserWithParentsCascading") + arcgisuserRelPublicUserUserCtx = newContextual[bool]("arcgis.user_.user_.arcgis.user_.user__public_user_id_fkey") + arcgisuserRelUserUserPrivilegesCtx = newContextual[bool]("arcgis.user_.arcgis.user_privilege.arcgis.user_privilege.user_privilege_user_id_fkey") + + // Relationship Contexts for arcgis.user_privilege + arcgisUserPrivilegeWithParentsCascadingCtx = newContextual[bool]("arcgisUserPrivilegeWithParentsCascading") + arcgisUserPrivilegeRelUserUserCtx = newContextual[bool]("arcgis.user_.arcgis.user_privilege.arcgis.user_privilege.user_privilege_user_id_fkey") + // Relationship Contexts for district districtWithParentsCascadingCtx = newContextual[bool]("districtWithParentsCascading") @@ -247,6 +256,7 @@ var ( // Relationship Contexts for user_ userWithParentsCascadingCtx = newContextual[bool]("userWithParentsCascading") + userRelPublicUserUserCtx = newContextual[bool]("arcgis.user_.user_.arcgis.user_.user__public_user_id_fkey") userRelCreatorNoteAudiosCtx = newContextual[bool]("note_audio.user_.note_audio.note_audio_creator_id_fkey") userRelDeletorNoteAudiosCtx = newContextual[bool]("note_audio.user_.note_audio.note_audio_deletor_id_fkey") userRelCreatorNoteImagesCtx = newContextual[bool]("note_image.user_.note_image.note_image_creator_id_fkey") diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index cae5658a..17d49c70 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -18,6 +18,8 @@ import ( ) type Factory struct { + baseArcgisUserMods ArcgisUserModSlice + baseArcgisUserPrivilegeMods ArcgisUserPrivilegeModSlice baseDistrictMods DistrictModSlice baseFieldseekerContainerrelateMods FieldseekerContainerrelateModSlice baseFieldseekerFieldscoutinglogMods FieldseekerFieldscoutinglogModSlice @@ -77,6 +79,81 @@ func New() *Factory { return &Factory{} } +func (f *Factory) NewArcgisUser(mods ...ArcgisUserMod) *ArcgisUserTemplate { + return f.NewArcgisUserWithContext(context.Background(), mods...) +} + +func (f *Factory) NewArcgisUserWithContext(ctx context.Context, mods ...ArcgisUserMod) *ArcgisUserTemplate { + o := &ArcgisUserTemplate{f: f} + + if f != nil { + f.baseArcgisUserMods.Apply(ctx, o) + } + + ArcgisUserModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingArcgisUser(m *models.ArcgisUser) *ArcgisUserTemplate { + o := &ArcgisUserTemplate{f: f, alreadyPersisted: true} + + o.Access = func() string { return m.Access } + o.Created = func() time.Time { return m.Created } + o.Email = func() string { return m.Email } + o.FullName = func() string { return m.FullName } + o.ID = func() string { return m.ID } + o.Level = func() string { return m.Level } + o.OrgID = func() string { return m.OrgID } + o.PublicUserID = func() int32 { return m.PublicUserID } + o.Region = func() string { return m.Region } + o.Role = func() string { return m.Role } + o.RoleID = func() string { return m.RoleID } + o.Username = func() string { return m.Username } + o.UserLicenseTypeID = func() string { return m.UserLicenseTypeID } + o.UserType = func() string { return m.UserType } + + ctx := context.Background() + if m.R.PublicUserUser != nil { + ArcgisUserMods.WithExistingPublicUserUser(m.R.PublicUserUser).Apply(ctx, o) + } + if len(m.R.UserUserPrivileges) > 0 { + ArcgisUserMods.AddExistingUserUserPrivileges(m.R.UserUserPrivileges...).Apply(ctx, o) + } + + return o +} + +func (f *Factory) NewArcgisUserPrivilege(mods ...ArcgisUserPrivilegeMod) *ArcgisUserPrivilegeTemplate { + return f.NewArcgisUserPrivilegeWithContext(context.Background(), mods...) +} + +func (f *Factory) NewArcgisUserPrivilegeWithContext(ctx context.Context, mods ...ArcgisUserPrivilegeMod) *ArcgisUserPrivilegeTemplate { + o := &ArcgisUserPrivilegeTemplate{f: f} + + if f != nil { + f.baseArcgisUserPrivilegeMods.Apply(ctx, o) + } + + ArcgisUserPrivilegeModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingArcgisUserPrivilege(m *models.ArcgisUserPrivilege) *ArcgisUserPrivilegeTemplate { + o := &ArcgisUserPrivilegeTemplate{f: f, alreadyPersisted: true} + + o.UserID = func() string { return m.UserID } + o.Privilege = func() string { return m.Privilege } + + ctx := context.Background() + if m.R.UserUser != nil { + ArcgisUserPrivilegeMods.WithExistingUserUser(m.R.UserUser).Apply(ctx, o) + } + + return o +} + func (f *Factory) NewDistrict(mods ...DistrictMod) *DistrictTemplate { return f.NewDistrictWithContext(context.Background(), mods...) } @@ -2825,6 +2902,9 @@ func (f *Factory) FromExistingUser(m *models.User) *UserTemplate { o.PasswordHash = func() string { return m.PasswordHash } ctx := context.Background() + if len(m.R.PublicUserUser) > 0 { + UserMods.AddExistingPublicUserUser(m.R.PublicUserUser...).Apply(ctx, o) + } if len(m.R.CreatorNoteAudios) > 0 { UserMods.AddExistingCreatorNoteAudios(m.R.CreatorNoteAudios...).Apply(ctx, o) } @@ -2850,6 +2930,22 @@ func (f *Factory) FromExistingUser(m *models.User) *UserTemplate { return o } +func (f *Factory) ClearBaseArcgisUserMods() { + f.baseArcgisUserMods = nil +} + +func (f *Factory) AddBaseArcgisUserMod(mods ...ArcgisUserMod) { + f.baseArcgisUserMods = append(f.baseArcgisUserMods, mods...) +} + +func (f *Factory) ClearBaseArcgisUserPrivilegeMods() { + f.baseArcgisUserPrivilegeMods = nil +} + +func (f *Factory) AddBaseArcgisUserPrivilegeMod(mods ...ArcgisUserPrivilegeMod) { + f.baseArcgisUserPrivilegeMods = append(f.baseArcgisUserPrivilegeMods, mods...) +} + func (f *Factory) ClearBaseDistrictMods() { f.baseDistrictMods = nil } diff --git a/db/factory/bobfactory_random.bob.go b/db/factory/bobfactory_random.bob.go index abb5ef00..0f93c1ad 100644 --- a/db/factory/bobfactory_random.bob.go +++ b/db/factory/bobfactory_random.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/district.bob.go b/db/factory/district.bob.go index 4a25435f..fd5944c4 100644 --- a/db/factory/district.bob.go +++ b/db/factory/district.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.containerrelate.bob.go b/db/factory/fieldseeker.containerrelate.bob.go index 8a1a3613..31dbc2be 100644 --- a/db/factory/fieldseeker.containerrelate.bob.go +++ b/db/factory/fieldseeker.containerrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.fieldscoutinglog.bob.go b/db/factory/fieldseeker.fieldscoutinglog.bob.go index e688ab28..ac7f0351 100644 --- a/db/factory/fieldseeker.fieldscoutinglog.bob.go +++ b/db/factory/fieldseeker.fieldscoutinglog.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.habitatrelate.bob.go b/db/factory/fieldseeker.habitatrelate.bob.go index bbf2eaef..863ee087 100644 --- a/db/factory/fieldseeker.habitatrelate.bob.go +++ b/db/factory/fieldseeker.habitatrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.inspectionsample.bob.go b/db/factory/fieldseeker.inspectionsample.bob.go index 3306127e..75993f79 100644 --- a/db/factory/fieldseeker.inspectionsample.bob.go +++ b/db/factory/fieldseeker.inspectionsample.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.inspectionsampledetail.bob.go b/db/factory/fieldseeker.inspectionsampledetail.bob.go index 006b3dd3..53e60c10 100644 --- a/db/factory/fieldseeker.inspectionsampledetail.bob.go +++ b/db/factory/fieldseeker.inspectionsampledetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.linelocation.bob.go b/db/factory/fieldseeker.linelocation.bob.go index 06e2dd00..4cd24b90 100644 --- a/db/factory/fieldseeker.linelocation.bob.go +++ b/db/factory/fieldseeker.linelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.locationtracking.bob.go b/db/factory/fieldseeker.locationtracking.bob.go index 02742246..a258b8a1 100644 --- a/db/factory/fieldseeker.locationtracking.bob.go +++ b/db/factory/fieldseeker.locationtracking.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.mosquitoinspection.bob.go b/db/factory/fieldseeker.mosquitoinspection.bob.go index 3f5e6093..fa9c480a 100644 --- a/db/factory/fieldseeker.mosquitoinspection.bob.go +++ b/db/factory/fieldseeker.mosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.pointlocation.bob.go b/db/factory/fieldseeker.pointlocation.bob.go index 55faf410..01cf29b0 100644 --- a/db/factory/fieldseeker.pointlocation.bob.go +++ b/db/factory/fieldseeker.pointlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.polygonlocation.bob.go b/db/factory/fieldseeker.polygonlocation.bob.go index 7f6485f8..7def30d7 100644 --- a/db/factory/fieldseeker.polygonlocation.bob.go +++ b/db/factory/fieldseeker.polygonlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.pool.bob.go b/db/factory/fieldseeker.pool.bob.go index 1ffac53a..322b8bcd 100644 --- a/db/factory/fieldseeker.pool.bob.go +++ b/db/factory/fieldseeker.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.pooldetail.bob.go b/db/factory/fieldseeker.pooldetail.bob.go index 22cb2ca4..c4978b51 100644 --- a/db/factory/fieldseeker.pooldetail.bob.go +++ b/db/factory/fieldseeker.pooldetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.proposedtreatmentarea.bob.go b/db/factory/fieldseeker.proposedtreatmentarea.bob.go index 1bf811dd..a9dd58c6 100644 --- a/db/factory/fieldseeker.proposedtreatmentarea.bob.go +++ b/db/factory/fieldseeker.proposedtreatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.qamosquitoinspection.bob.go b/db/factory/fieldseeker.qamosquitoinspection.bob.go index bf8ea348..cabb9b45 100644 --- a/db/factory/fieldseeker.qamosquitoinspection.bob.go +++ b/db/factory/fieldseeker.qamosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.rodentlocation.bob.go b/db/factory/fieldseeker.rodentlocation.bob.go index 63871fcd..4911723e 100644 --- a/db/factory/fieldseeker.rodentlocation.bob.go +++ b/db/factory/fieldseeker.rodentlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.samplecollection.bob.go b/db/factory/fieldseeker.samplecollection.bob.go index 9ce1f745..29e18a7d 100644 --- a/db/factory/fieldseeker.samplecollection.bob.go +++ b/db/factory/fieldseeker.samplecollection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.samplelocation.bob.go b/db/factory/fieldseeker.samplelocation.bob.go index 85d1ece6..bc41d319 100644 --- a/db/factory/fieldseeker.samplelocation.bob.go +++ b/db/factory/fieldseeker.samplelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.servicerequest.bob.go b/db/factory/fieldseeker.servicerequest.bob.go index 0ce06a72..4a69b438 100644 --- a/db/factory/fieldseeker.servicerequest.bob.go +++ b/db/factory/fieldseeker.servicerequest.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.speciesabundance.bob.go b/db/factory/fieldseeker.speciesabundance.bob.go index 18fea4e5..efafd1fe 100644 --- a/db/factory/fieldseeker.speciesabundance.bob.go +++ b/db/factory/fieldseeker.speciesabundance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.stormdrain.bob.go b/db/factory/fieldseeker.stormdrain.bob.go index 3b1caa24..bb3fdca3 100644 --- a/db/factory/fieldseeker.stormdrain.bob.go +++ b/db/factory/fieldseeker.stormdrain.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.timecard.bob.go b/db/factory/fieldseeker.timecard.bob.go index 5001368d..fa18147b 100644 --- a/db/factory/fieldseeker.timecard.bob.go +++ b/db/factory/fieldseeker.timecard.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.trapdata.bob.go b/db/factory/fieldseeker.trapdata.bob.go index aa03e598..649b359f 100644 --- a/db/factory/fieldseeker.trapdata.bob.go +++ b/db/factory/fieldseeker.trapdata.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.traplocation.bob.go b/db/factory/fieldseeker.traplocation.bob.go index b2699c74..621c0929 100644 --- a/db/factory/fieldseeker.traplocation.bob.go +++ b/db/factory/fieldseeker.traplocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.treatment.bob.go b/db/factory/fieldseeker.treatment.bob.go index 99e0a3bf..dadf53bd 100644 --- a/db/factory/fieldseeker.treatment.bob.go +++ b/db/factory/fieldseeker.treatment.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.treatmentarea.bob.go b/db/factory/fieldseeker.treatmentarea.bob.go index 9f48e08a..2290a138 100644 --- a/db/factory/fieldseeker.treatmentarea.bob.go +++ b/db/factory/fieldseeker.treatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.zones.bob.go b/db/factory/fieldseeker.zones.bob.go index e733cca5..1e6fab87 100644 --- a/db/factory/fieldseeker.zones.bob.go +++ b/db/factory/fieldseeker.zones.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker.zones2.bob.go b/db/factory/fieldseeker.zones2.bob.go index 2d383f58..9cf78520 100644 --- a/db/factory/fieldseeker.zones2.bob.go +++ b/db/factory/fieldseeker.zones2.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/fieldseeker_sync.bob.go b/db/factory/fieldseeker_sync.bob.go index 1e4eb34c..2358f34a 100644 --- a/db/factory/fieldseeker_sync.bob.go +++ b/db/factory/fieldseeker_sync.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/geography_columns.bob.go b/db/factory/geography_columns.bob.go index ae74519b..16709713 100644 --- a/db/factory/geography_columns.bob.go +++ b/db/factory/geography_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/geometry_columns.bob.go b/db/factory/geometry_columns.bob.go index f493872e..63c97d2a 100644 --- a/db/factory/geometry_columns.bob.go +++ b/db/factory/geometry_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/goose_db_version.bob.go b/db/factory/goose_db_version.bob.go index 68bc4aea..581502cb 100644 --- a/db/factory/goose_db_version.bob.go +++ b/db/factory/goose_db_version.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/h3_aggregation.bob.go b/db/factory/h3_aggregation.bob.go index ea3b03db..5977ac04 100644 --- a/db/factory/h3_aggregation.bob.go +++ b/db/factory/h3_aggregation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/note_audio.bob.go b/db/factory/note_audio.bob.go index e3a9620a..557894ba 100644 --- a/db/factory/note_audio.bob.go +++ b/db/factory/note_audio.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/note_audio_breadcrumb.bob.go b/db/factory/note_audio_breadcrumb.bob.go index 3abf3eef..b4a8c8a2 100644 --- a/db/factory/note_audio_breadcrumb.bob.go +++ b/db/factory/note_audio_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/note_audio_data.bob.go b/db/factory/note_audio_data.bob.go index cd6578e6..3030278b 100644 --- a/db/factory/note_audio_data.bob.go +++ b/db/factory/note_audio_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/note_image.bob.go b/db/factory/note_image.bob.go index a79ca241..53cc85cf 100644 --- a/db/factory/note_image.bob.go +++ b/db/factory/note_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/note_image_breadcrumb.bob.go b/db/factory/note_image_breadcrumb.bob.go index ed0ae5e7..31cb44f6 100644 --- a/db/factory/note_image_breadcrumb.bob.go +++ b/db/factory/note_image_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/note_image_data.bob.go b/db/factory/note_image_data.bob.go index 3b79cc04..dc8350ce 100644 --- a/db/factory/note_image_data.bob.go +++ b/db/factory/note_image_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/notification.bob.go b/db/factory/notification.bob.go index 42b1912e..9b34b0c7 100644 --- a/db/factory/notification.bob.go +++ b/db/factory/notification.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/oauth_token.bob.go b/db/factory/oauth_token.bob.go index fbe69c7a..12ef5b67 100644 --- a/db/factory/oauth_token.bob.go +++ b/db/factory/oauth_token.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/organization.bob.go b/db/factory/organization.bob.go index f7567bd7..73aa2bed 100644 --- a/db/factory/organization.bob.go +++ b/db/factory/organization.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/publicreport.nuisance.bob.go b/db/factory/publicreport.nuisance.bob.go index 022ef0ea..3590e4e1 100644 --- a/db/factory/publicreport.nuisance.bob.go +++ b/db/factory/publicreport.nuisance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/publicreport.pool.bob.go b/db/factory/publicreport.pool.bob.go index f5184a82..d35ac5c2 100644 --- a/db/factory/publicreport.pool.bob.go +++ b/db/factory/publicreport.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/publicreport.pool_photo.bob.go b/db/factory/publicreport.pool_photo.bob.go index a0a289d0..d41bd538 100644 --- a/db/factory/publicreport.pool_photo.bob.go +++ b/db/factory/publicreport.pool_photo.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/publicreport.quick.bob.go b/db/factory/publicreport.quick.bob.go index 85b456d4..95a66d3a 100644 --- a/db/factory/publicreport.quick.bob.go +++ b/db/factory/publicreport.quick.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/publicreport.quick_photo.bob.go b/db/factory/publicreport.quick_photo.bob.go index 39b70215..4bd2bfb9 100644 --- a/db/factory/publicreport.quick_photo.bob.go +++ b/db/factory/publicreport.quick_photo.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/publicreport.report_location.bob.go b/db/factory/publicreport.report_location.bob.go index a7ff64c2..2b1dac21 100644 --- a/db/factory/publicreport.report_location.bob.go +++ b/db/factory/publicreport.report_location.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/raster_columns.bob.go b/db/factory/raster_columns.bob.go index ca6ca8d8..bd01e09e 100644 --- a/db/factory/raster_columns.bob.go +++ b/db/factory/raster_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/raster_overviews.bob.go b/db/factory/raster_overviews.bob.go index ba9c8a89..3d7f41da 100644 --- a/db/factory/raster_overviews.bob.go +++ b/db/factory/raster_overviews.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/sessions.bob.go b/db/factory/sessions.bob.go index d5293505..d0148f28 100644 --- a/db/factory/sessions.bob.go +++ b/db/factory/sessions.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/spatial_ref_sys.bob.go b/db/factory/spatial_ref_sys.bob.go index 448a30ff..8eeff8fa 100644 --- a/db/factory/spatial_ref_sys.bob.go +++ b/db/factory/spatial_ref_sys.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/user_.bob.go b/db/factory/user_.bob.go index 6425b915..0bfd18f8 100644 --- a/db/factory/user_.bob.go +++ b/db/factory/user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -58,6 +58,7 @@ type UserTemplate struct { } type userR struct { + PublicUserUser []*userRPublicUserUserR CreatorNoteAudios []*userRCreatorNoteAudiosR DeletorNoteAudios []*userRDeletorNoteAudiosR CreatorNoteImages []*userRCreatorNoteImagesR @@ -67,6 +68,10 @@ type userR struct { Organization *userROrganizationR } +type userRPublicUserUserR struct { + number int + o *ArcgisUserTemplate +} type userRCreatorNoteAudiosR struct { number int o *NoteAudioTemplate @@ -105,6 +110,19 @@ func (o *UserTemplate) Apply(ctx context.Context, mods ...UserMod) { // setModelRels creates and sets the relationships on *models.User // according to the relationships in the template. Nothing is inserted into the db func (t UserTemplate) setModelRels(o *models.User) { + if t.r.PublicUserUser != nil { + rel := models.ArcgisUserSlice{} + for _, r := range t.r.PublicUserUser { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.PublicUserID = o.ID // h2 + rel.R.PublicUserUser = o + } + rel = append(rel, related...) + } + o.R.PublicUserUser = rel + } + if t.r.CreatorNoteAudios != nil { rel := models.NoteAudioSlice{} for _, r := range t.r.CreatorNoteAudios { @@ -350,6 +368,26 @@ func ensureCreatableUser(m *models.UserSetter) { func (o *UserTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.User) error { var err error + isPublicUserUserDone, _ := userRelPublicUserUserCtx.Value(ctx) + if !isPublicUserUserDone && o.r.PublicUserUser != nil { + ctx = userRelPublicUserUserCtx.WithValue(ctx, true) + for _, r := range o.r.PublicUserUser { + if r.o.alreadyPersisted { + m.R.PublicUserUser = append(m.R.PublicUserUser, r.o.Build()) + } else { + rel0, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachPublicUserUser(ctx, exec, rel0...) + if err != nil { + return err + } + } + } + } + isCreatorNoteAudiosDone, _ := userRelCreatorNoteAudiosCtx.Value(ctx) if !isCreatorNoteAudiosDone && o.r.CreatorNoteAudios != nil { ctx = userRelCreatorNoteAudiosCtx.WithValue(ctx, true) @@ -357,12 +395,12 @@ func (o *UserTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m * if r.o.alreadyPersisted { m.R.CreatorNoteAudios = append(m.R.CreatorNoteAudios, r.o.Build()) } else { - rel0, err := r.o.CreateMany(ctx, exec, r.number) + rel1, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachCreatorNoteAudios(ctx, exec, rel0...) + err = m.AttachCreatorNoteAudios(ctx, exec, rel1...) if err != nil { return err } @@ -377,12 +415,12 @@ func (o *UserTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m * if r.o.alreadyPersisted { m.R.DeletorNoteAudios = append(m.R.DeletorNoteAudios, r.o.Build()) } else { - rel1, err := r.o.CreateMany(ctx, exec, r.number) + rel2, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachDeletorNoteAudios(ctx, exec, rel1...) + err = m.AttachDeletorNoteAudios(ctx, exec, rel2...) if err != nil { return err } @@ -397,12 +435,12 @@ func (o *UserTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m * if r.o.alreadyPersisted { m.R.CreatorNoteImages = append(m.R.CreatorNoteImages, r.o.Build()) } else { - rel2, err := r.o.CreateMany(ctx, exec, r.number) + rel3, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachCreatorNoteImages(ctx, exec, rel2...) + err = m.AttachCreatorNoteImages(ctx, exec, rel3...) if err != nil { return err } @@ -417,12 +455,12 @@ func (o *UserTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m * if r.o.alreadyPersisted { m.R.DeletorNoteImages = append(m.R.DeletorNoteImages, r.o.Build()) } else { - rel3, err := r.o.CreateMany(ctx, exec, r.number) + rel4, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachDeletorNoteImages(ctx, exec, rel3...) + err = m.AttachDeletorNoteImages(ctx, exec, rel4...) if err != nil { return err } @@ -437,12 +475,12 @@ func (o *UserTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m * if r.o.alreadyPersisted { m.R.UserNotifications = append(m.R.UserNotifications, r.o.Build()) } else { - rel4, err := r.o.CreateMany(ctx, exec, r.number) + rel5, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachUserNotifications(ctx, exec, rel4...) + err = m.AttachUserNotifications(ctx, exec, rel5...) if err != nil { return err } @@ -457,12 +495,12 @@ func (o *UserTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m * if r.o.alreadyPersisted { m.R.UserOauthTokens = append(m.R.UserOauthTokens, r.o.Build()) } else { - rel5, err := r.o.CreateMany(ctx, exec, r.number) + rel6, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachUserOauthTokens(ctx, exec, rel5...) + err = m.AttachUserOauthTokens(ctx, exec, rel6...) if err != nil { return err } @@ -484,25 +522,25 @@ func (o *UserTemplate) Create(ctx context.Context, exec bob.Executor) (*models.U UserMods.WithNewOrganization().Apply(ctx, o) } - var rel6 *models.Organization + var rel7 *models.Organization if o.r.Organization.o.alreadyPersisted { - rel6 = o.r.Organization.o.Build() + rel7 = o.r.Organization.o.Build() } else { - rel6, err = o.r.Organization.o.Create(ctx, exec) + rel7, err = o.r.Organization.o.Create(ctx, exec) if err != nil { return nil, err } } - opt.OrganizationID = omit.From(rel6.ID) + opt.OrganizationID = omit.From(rel7.ID) m, err := models.Users.Insert(opt).One(ctx, exec) if err != nil { return nil, err } - m.R.Organization = rel6 + m.R.Organization = rel7 if err := o.insertOptRels(ctx, exec, m); err != nil { return nil, err @@ -1144,6 +1182,54 @@ func (m userMods) WithoutOrganization() UserMod { }) } +func (m userMods) WithPublicUserUser(number int, related *ArcgisUserTemplate) UserMod { + return UserModFunc(func(ctx context.Context, o *UserTemplate) { + o.r.PublicUserUser = []*userRPublicUserUserR{{ + number: number, + o: related, + }} + }) +} + +func (m userMods) WithNewPublicUserUser(number int, mods ...ArcgisUserMod) UserMod { + return UserModFunc(func(ctx context.Context, o *UserTemplate) { + related := o.f.NewArcgisUserWithContext(ctx, mods...) + m.WithPublicUserUser(number, related).Apply(ctx, o) + }) +} + +func (m userMods) AddPublicUserUser(number int, related *ArcgisUserTemplate) UserMod { + return UserModFunc(func(ctx context.Context, o *UserTemplate) { + o.r.PublicUserUser = append(o.r.PublicUserUser, &userRPublicUserUserR{ + number: number, + o: related, + }) + }) +} + +func (m userMods) AddNewPublicUserUser(number int, mods ...ArcgisUserMod) UserMod { + return UserModFunc(func(ctx context.Context, o *UserTemplate) { + related := o.f.NewArcgisUserWithContext(ctx, mods...) + m.AddPublicUserUser(number, related).Apply(ctx, o) + }) +} + +func (m userMods) AddExistingPublicUserUser(existingModels ...*models.ArcgisUser) UserMod { + return UserModFunc(func(ctx context.Context, o *UserTemplate) { + for _, em := range existingModels { + o.r.PublicUserUser = append(o.r.PublicUserUser, &userRPublicUserUserR{ + o: o.f.FromExistingArcgisUser(em), + }) + } + }) +} + +func (m userMods) WithoutPublicUserUser() UserMod { + return UserModFunc(func(ctx context.Context, o *UserTemplate) { + o.r.PublicUserUser = nil + }) +} + func (m userMods) WithCreatorNoteAudios(number int, related *NoteAudioTemplate) UserMod { return UserModFunc(func(ctx context.Context, o *UserTemplate) { o.r.CreatorNoteAudios = []*userRCreatorNoteAudiosR{{ diff --git a/db/migrations/00031_arcgis_user.sql b/db/migrations/00031_arcgis_user.sql new file mode 100644 index 00000000..0e58193c --- /dev/null +++ b/db/migrations/00031_arcgis_user.sql @@ -0,0 +1,28 @@ +-- +goose Up +CREATE SCHEMA arcgis; +CREATE TABLE arcgis.user_ ( + access TEXT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + email TEXT NOT NULL, + full_name TEXT NOT NULL, + id TEXT NOT NULL, + level TEXT NOT NULL, + org_id TEXT NOT NULL, + public_user_id INTEGER NOT NULL REFERENCES public.user_(id), + region TEXT NOT NULL, + role TEXT NOT NULL, + role_id TEXT NOT NULL, + username TEXT NOT NULL, + user_license_type_id TEXT NOT NULL, + user_type TEXT NOT NULL, + PRIMARY KEY (id) +); +CREATE TABLE arcgis.user_privilege ( + user_id TEXT NOT NULL REFERENCES arcgis.user_(id), + privilege TEXT NOT NULL, + PRIMARY KEY(user_id, privilege) +); +-- +goose Down +DROP TABLE arcgis.user_privilege; +DROP TABLE arcgis.user_; +DROP SCHEMA arcgis; diff --git a/db/models/arcgis.user_.bob.go b/db/models/arcgis.user_.bob.go new file mode 100644 index 00000000..6613b87f --- /dev/null +++ b/db/models/arcgis.user_.bob.go @@ -0,0 +1,1191 @@ +// Code generated by BobGen psql v0.42.1. 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" + "time" + + "github.com/aarondl/opt/omit" + "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" +) + +// ArcgisUser is an object representing the database table. +type ArcgisUser struct { + Access string `db:"access" ` + Created time.Time `db:"created" ` + Email string `db:"email" ` + FullName string `db:"full_name" ` + ID string `db:"id,pk" ` + Level string `db:"level" ` + OrgID string `db:"org_id" ` + PublicUserID int32 `db:"public_user_id" ` + Region string `db:"region" ` + Role string `db:"role" ` + RoleID string `db:"role_id" ` + Username string `db:"username" ` + UserLicenseTypeID string `db:"user_license_type_id" ` + UserType string `db:"user_type" ` + + R arcgisuserR `db:"-" ` + + C arcgisuserC `db:"-" ` +} + +// ArcgisUserSlice is an alias for a slice of pointers to ArcgisUser. +// This should almost always be used instead of []*ArcgisUser. +type ArcgisUserSlice []*ArcgisUser + +// ArcgisUsers contains methods to work with the user_ table +var ArcgisUsers = psql.NewTablex[*ArcgisUser, ArcgisUserSlice, *ArcgisUserSetter]("arcgis", "user_", buildArcgisUserColumns("arcgis.user_")) + +// ArcgisUsersQuery is a query on the user_ table +type ArcgisUsersQuery = *psql.ViewQuery[*ArcgisUser, ArcgisUserSlice] + +// arcgisuserR is where relationships are stored. +type arcgisuserR struct { + PublicUserUser *User // arcgis.user_.user__public_user_id_fkey + UserUserPrivileges ArcgisUserPrivilegeSlice // arcgis.user_privilege.user_privilege_user_id_fkey +} + +func buildArcgisUserColumns(alias string) arcgisuserColumns { + return arcgisuserColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "access", "created", "email", "full_name", "id", "level", "org_id", "public_user_id", "region", "role", "role_id", "username", "user_license_type_id", "user_type", + ).WithParent("arcgis.user_"), + tableAlias: alias, + Access: psql.Quote(alias, "access"), + Created: psql.Quote(alias, "created"), + Email: psql.Quote(alias, "email"), + FullName: psql.Quote(alias, "full_name"), + ID: psql.Quote(alias, "id"), + Level: psql.Quote(alias, "level"), + OrgID: psql.Quote(alias, "org_id"), + PublicUserID: psql.Quote(alias, "public_user_id"), + Region: psql.Quote(alias, "region"), + Role: psql.Quote(alias, "role"), + RoleID: psql.Quote(alias, "role_id"), + Username: psql.Quote(alias, "username"), + UserLicenseTypeID: psql.Quote(alias, "user_license_type_id"), + UserType: psql.Quote(alias, "user_type"), + } +} + +type arcgisuserColumns struct { + expr.ColumnsExpr + tableAlias string + Access psql.Expression + Created psql.Expression + Email psql.Expression + FullName psql.Expression + ID psql.Expression + Level psql.Expression + OrgID psql.Expression + PublicUserID psql.Expression + Region psql.Expression + Role psql.Expression + RoleID psql.Expression + Username psql.Expression + UserLicenseTypeID psql.Expression + UserType psql.Expression +} + +func (c arcgisuserColumns) Alias() string { + return c.tableAlias +} + +func (arcgisuserColumns) AliasedAs(alias string) arcgisuserColumns { + return buildArcgisUserColumns(alias) +} + +// ArcgisUserSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type ArcgisUserSetter struct { + Access omit.Val[string] `db:"access" ` + Created omit.Val[time.Time] `db:"created" ` + Email omit.Val[string] `db:"email" ` + FullName omit.Val[string] `db:"full_name" ` + ID omit.Val[string] `db:"id,pk" ` + Level omit.Val[string] `db:"level" ` + OrgID omit.Val[string] `db:"org_id" ` + PublicUserID omit.Val[int32] `db:"public_user_id" ` + Region omit.Val[string] `db:"region" ` + Role omit.Val[string] `db:"role" ` + RoleID omit.Val[string] `db:"role_id" ` + Username omit.Val[string] `db:"username" ` + UserLicenseTypeID omit.Val[string] `db:"user_license_type_id" ` + UserType omit.Val[string] `db:"user_type" ` +} + +func (s ArcgisUserSetter) SetColumns() []string { + vals := make([]string, 0, 14) + if s.Access.IsValue() { + vals = append(vals, "access") + } + if s.Created.IsValue() { + vals = append(vals, "created") + } + if s.Email.IsValue() { + vals = append(vals, "email") + } + if s.FullName.IsValue() { + vals = append(vals, "full_name") + } + if s.ID.IsValue() { + vals = append(vals, "id") + } + if s.Level.IsValue() { + vals = append(vals, "level") + } + if s.OrgID.IsValue() { + vals = append(vals, "org_id") + } + if s.PublicUserID.IsValue() { + vals = append(vals, "public_user_id") + } + if s.Region.IsValue() { + vals = append(vals, "region") + } + if s.Role.IsValue() { + vals = append(vals, "role") + } + if s.RoleID.IsValue() { + vals = append(vals, "role_id") + } + if s.Username.IsValue() { + vals = append(vals, "username") + } + if s.UserLicenseTypeID.IsValue() { + vals = append(vals, "user_license_type_id") + } + if s.UserType.IsValue() { + vals = append(vals, "user_type") + } + return vals +} + +func (s ArcgisUserSetter) Overwrite(t *ArcgisUser) { + if s.Access.IsValue() { + t.Access = s.Access.MustGet() + } + if s.Created.IsValue() { + t.Created = s.Created.MustGet() + } + if s.Email.IsValue() { + t.Email = s.Email.MustGet() + } + if s.FullName.IsValue() { + t.FullName = s.FullName.MustGet() + } + if s.ID.IsValue() { + t.ID = s.ID.MustGet() + } + if s.Level.IsValue() { + t.Level = s.Level.MustGet() + } + if s.OrgID.IsValue() { + t.OrgID = s.OrgID.MustGet() + } + if s.PublicUserID.IsValue() { + t.PublicUserID = s.PublicUserID.MustGet() + } + if s.Region.IsValue() { + t.Region = s.Region.MustGet() + } + if s.Role.IsValue() { + t.Role = s.Role.MustGet() + } + if s.RoleID.IsValue() { + t.RoleID = s.RoleID.MustGet() + } + if s.Username.IsValue() { + t.Username = s.Username.MustGet() + } + if s.UserLicenseTypeID.IsValue() { + t.UserLicenseTypeID = s.UserLicenseTypeID.MustGet() + } + if s.UserType.IsValue() { + t.UserType = s.UserType.MustGet() + } +} + +func (s *ArcgisUserSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return ArcgisUsers.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, 14) + if s.Access.IsValue() { + vals[0] = psql.Arg(s.Access.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.Created.IsValue() { + vals[1] = psql.Arg(s.Created.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if s.Email.IsValue() { + vals[2] = psql.Arg(s.Email.MustGet()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + if s.FullName.IsValue() { + vals[3] = psql.Arg(s.FullName.MustGet()) + } else { + vals[3] = psql.Raw("DEFAULT") + } + + if s.ID.IsValue() { + vals[4] = psql.Arg(s.ID.MustGet()) + } else { + vals[4] = psql.Raw("DEFAULT") + } + + if s.Level.IsValue() { + vals[5] = psql.Arg(s.Level.MustGet()) + } else { + vals[5] = psql.Raw("DEFAULT") + } + + if s.OrgID.IsValue() { + vals[6] = psql.Arg(s.OrgID.MustGet()) + } else { + vals[6] = psql.Raw("DEFAULT") + } + + if s.PublicUserID.IsValue() { + vals[7] = psql.Arg(s.PublicUserID.MustGet()) + } else { + vals[7] = psql.Raw("DEFAULT") + } + + if s.Region.IsValue() { + vals[8] = psql.Arg(s.Region.MustGet()) + } else { + vals[8] = psql.Raw("DEFAULT") + } + + if s.Role.IsValue() { + vals[9] = psql.Arg(s.Role.MustGet()) + } else { + vals[9] = psql.Raw("DEFAULT") + } + + if s.RoleID.IsValue() { + vals[10] = psql.Arg(s.RoleID.MustGet()) + } else { + vals[10] = psql.Raw("DEFAULT") + } + + if s.Username.IsValue() { + vals[11] = psql.Arg(s.Username.MustGet()) + } else { + vals[11] = psql.Raw("DEFAULT") + } + + if s.UserLicenseTypeID.IsValue() { + vals[12] = psql.Arg(s.UserLicenseTypeID.MustGet()) + } else { + vals[12] = psql.Raw("DEFAULT") + } + + if s.UserType.IsValue() { + vals[13] = psql.Arg(s.UserType.MustGet()) + } else { + vals[13] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s ArcgisUserSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s ArcgisUserSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 14) + + if s.Access.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "access")...), + psql.Arg(s.Access), + }}) + } + + if s.Created.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "created")...), + psql.Arg(s.Created), + }}) + } + + if s.Email.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "email")...), + psql.Arg(s.Email), + }}) + } + + if s.FullName.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "full_name")...), + psql.Arg(s.FullName), + }}) + } + + if s.ID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "id")...), + psql.Arg(s.ID), + }}) + } + + if s.Level.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "level")...), + psql.Arg(s.Level), + }}) + } + + if s.OrgID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "org_id")...), + psql.Arg(s.OrgID), + }}) + } + + if s.PublicUserID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "public_user_id")...), + psql.Arg(s.PublicUserID), + }}) + } + + if s.Region.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "region")...), + psql.Arg(s.Region), + }}) + } + + if s.Role.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "role")...), + psql.Arg(s.Role), + }}) + } + + if s.RoleID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "role_id")...), + psql.Arg(s.RoleID), + }}) + } + + if s.Username.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "username")...), + psql.Arg(s.Username), + }}) + } + + if s.UserLicenseTypeID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "user_license_type_id")...), + psql.Arg(s.UserLicenseTypeID), + }}) + } + + if s.UserType.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "user_type")...), + psql.Arg(s.UserType), + }}) + } + + return exprs +} + +// FindArcgisUser retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindArcgisUser(ctx context.Context, exec bob.Executor, IDPK string, cols ...string) (*ArcgisUser, error) { + if len(cols) == 0 { + return ArcgisUsers.Query( + sm.Where(ArcgisUsers.Columns.ID.EQ(psql.Arg(IDPK))), + ).One(ctx, exec) + } + + return ArcgisUsers.Query( + sm.Where(ArcgisUsers.Columns.ID.EQ(psql.Arg(IDPK))), + sm.Columns(ArcgisUsers.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// ArcgisUserExists checks the presence of a single record by primary key +func ArcgisUserExists(ctx context.Context, exec bob.Executor, IDPK string) (bool, error) { + return ArcgisUsers.Query( + sm.Where(ArcgisUsers.Columns.ID.EQ(psql.Arg(IDPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after ArcgisUser is retrieved from the database +func (o *ArcgisUser) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = ArcgisUsers.AfterSelectHooks.RunHooks(ctx, exec, ArcgisUserSlice{o}) + case bob.QueryTypeInsert: + ctx, err = ArcgisUsers.AfterInsertHooks.RunHooks(ctx, exec, ArcgisUserSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = ArcgisUsers.AfterUpdateHooks.RunHooks(ctx, exec, ArcgisUserSlice{o}) + case bob.QueryTypeDelete: + ctx, err = ArcgisUsers.AfterDeleteHooks.RunHooks(ctx, exec, ArcgisUserSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the ArcgisUser +func (o *ArcgisUser) primaryKeyVals() bob.Expression { + return psql.Arg(o.ID) +} + +func (o *ArcgisUser) pkEQ() dialect.Expression { + return psql.Quote("arcgis.user_", "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 ArcgisUser +func (o *ArcgisUser) Update(ctx context.Context, exec bob.Executor, s *ArcgisUserSetter) error { + v, err := ArcgisUsers.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 ArcgisUser record with an executor +func (o *ArcgisUser) Delete(ctx context.Context, exec bob.Executor) error { + _, err := ArcgisUsers.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the ArcgisUser using the executor +func (o *ArcgisUser) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := ArcgisUsers.Query( + sm.Where(ArcgisUsers.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 ArcgisUserSlice is retrieved from the database +func (o ArcgisUserSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = ArcgisUsers.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = ArcgisUsers.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = ArcgisUsers.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = ArcgisUsers.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o ArcgisUserSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Quote("arcgis.user_", "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 ArcgisUserSlice) copyMatchingRows(from ...*ArcgisUser) { + 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 ArcgisUserSlice) 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 ArcgisUsers.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 *ArcgisUser: + o.copyMatchingRows(retrieved) + case []*ArcgisUser: + o.copyMatchingRows(retrieved...) + case ArcgisUserSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a ArcgisUser or a slice of ArcgisUser + // then run the AfterUpdateHooks on the slice + _, err = ArcgisUsers.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o ArcgisUserSlice) 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 ArcgisUsers.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 *ArcgisUser: + o.copyMatchingRows(retrieved) + case []*ArcgisUser: + o.copyMatchingRows(retrieved...) + case ArcgisUserSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a ArcgisUser or a slice of ArcgisUser + // then run the AfterDeleteHooks on the slice + _, err = ArcgisUsers.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o ArcgisUserSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals ArcgisUserSetter) error { + if len(o) == 0 { + return nil + } + + _, err := ArcgisUsers.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o ArcgisUserSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := ArcgisUsers.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o ArcgisUserSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := ArcgisUsers.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// PublicUserUser starts a query for related objects on user_ +func (o *ArcgisUser) PublicUserUser(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + return Users.Query(append(mods, + sm.Where(Users.Columns.ID.EQ(psql.Arg(o.PublicUserID))), + )...) +} + +func (os ArcgisUserSlice) PublicUserUser(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + pkPublicUserID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkPublicUserID = append(pkPublicUserID, o.PublicUserID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkPublicUserID), "integer[]")), + )) + + return Users.Query(append(mods, + sm.Where(psql.Group(Users.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + +// UserUserPrivileges starts a query for related objects on arcgis.user_privilege +func (o *ArcgisUser) UserUserPrivileges(mods ...bob.Mod[*dialect.SelectQuery]) ArcgisUserPrivilegesQuery { + return ArcgisUserPrivileges.Query(append(mods, + sm.Where(ArcgisUserPrivileges.Columns.UserID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os ArcgisUserSlice) UserUserPrivileges(mods ...bob.Mod[*dialect.SelectQuery]) ArcgisUserPrivilegesQuery { + pkID := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "text[]")), + )) + + return ArcgisUserPrivileges.Query(append(mods, + sm.Where(psql.Group(ArcgisUserPrivileges.Columns.UserID).OP("IN", PKArgExpr)), + )...) +} + +func attachArcgisUserPublicUserUser0(ctx context.Context, exec bob.Executor, count int, arcgisuser0 *ArcgisUser, user1 *User) (*ArcgisUser, error) { + setter := &ArcgisUserSetter{ + PublicUserID: omit.From(user1.ID), + } + + err := arcgisuser0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachArcgisUserPublicUserUser0: %w", err) + } + + return arcgisuser0, nil +} + +func (arcgisuser0 *ArcgisUser) InsertPublicUserUser(ctx context.Context, exec bob.Executor, related *UserSetter) error { + var err error + + user1, err := Users.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachArcgisUserPublicUserUser0(ctx, exec, 1, arcgisuser0, user1) + if err != nil { + return err + } + + arcgisuser0.R.PublicUserUser = user1 + + user1.R.PublicUserUser = append(user1.R.PublicUserUser, arcgisuser0) + + return nil +} + +func (arcgisuser0 *ArcgisUser) AttachPublicUserUser(ctx context.Context, exec bob.Executor, user1 *User) error { + var err error + + _, err = attachArcgisUserPublicUserUser0(ctx, exec, 1, arcgisuser0, user1) + if err != nil { + return err + } + + arcgisuser0.R.PublicUserUser = user1 + + user1.R.PublicUserUser = append(user1.R.PublicUserUser, arcgisuser0) + + return nil +} + +func insertArcgisUserUserUserPrivileges0(ctx context.Context, exec bob.Executor, arcgisUserPrivileges1 []*ArcgisUserPrivilegeSetter, arcgisuser0 *ArcgisUser) (ArcgisUserPrivilegeSlice, error) { + for i := range arcgisUserPrivileges1 { + arcgisUserPrivileges1[i].UserID = omit.From(arcgisuser0.ID) + } + + ret, err := ArcgisUserPrivileges.Insert(bob.ToMods(arcgisUserPrivileges1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertArcgisUserUserUserPrivileges0: %w", err) + } + + return ret, nil +} + +func attachArcgisUserUserUserPrivileges0(ctx context.Context, exec bob.Executor, count int, arcgisUserPrivileges1 ArcgisUserPrivilegeSlice, arcgisuser0 *ArcgisUser) (ArcgisUserPrivilegeSlice, error) { + setter := &ArcgisUserPrivilegeSetter{ + UserID: omit.From(arcgisuser0.ID), + } + + err := arcgisUserPrivileges1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachArcgisUserUserUserPrivileges0: %w", err) + } + + return arcgisUserPrivileges1, nil +} + +func (arcgisuser0 *ArcgisUser) InsertUserUserPrivileges(ctx context.Context, exec bob.Executor, related ...*ArcgisUserPrivilegeSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + arcgisUserPrivileges1, err := insertArcgisUserUserUserPrivileges0(ctx, exec, related, arcgisuser0) + if err != nil { + return err + } + + arcgisuser0.R.UserUserPrivileges = append(arcgisuser0.R.UserUserPrivileges, arcgisUserPrivileges1...) + + for _, rel := range arcgisUserPrivileges1 { + rel.R.UserUser = arcgisuser0 + } + return nil +} + +func (arcgisuser0 *ArcgisUser) AttachUserUserPrivileges(ctx context.Context, exec bob.Executor, related ...*ArcgisUserPrivilege) error { + if len(related) == 0 { + return nil + } + + var err error + arcgisUserPrivileges1 := ArcgisUserPrivilegeSlice(related) + + _, err = attachArcgisUserUserUserPrivileges0(ctx, exec, len(related), arcgisUserPrivileges1, arcgisuser0) + if err != nil { + return err + } + + arcgisuser0.R.UserUserPrivileges = append(arcgisuser0.R.UserUserPrivileges, arcgisUserPrivileges1...) + + for _, rel := range related { + rel.R.UserUser = arcgisuser0 + } + + return nil +} + +type arcgisuserWhere[Q psql.Filterable] struct { + Access psql.WhereMod[Q, string] + Created psql.WhereMod[Q, time.Time] + Email psql.WhereMod[Q, string] + FullName psql.WhereMod[Q, string] + ID psql.WhereMod[Q, string] + Level psql.WhereMod[Q, string] + OrgID psql.WhereMod[Q, string] + PublicUserID psql.WhereMod[Q, int32] + Region psql.WhereMod[Q, string] + Role psql.WhereMod[Q, string] + RoleID psql.WhereMod[Q, string] + Username psql.WhereMod[Q, string] + UserLicenseTypeID psql.WhereMod[Q, string] + UserType psql.WhereMod[Q, string] +} + +func (arcgisuserWhere[Q]) AliasedAs(alias string) arcgisuserWhere[Q] { + return buildArcgisUserWhere[Q](buildArcgisUserColumns(alias)) +} + +func buildArcgisUserWhere[Q psql.Filterable](cols arcgisuserColumns) arcgisuserWhere[Q] { + return arcgisuserWhere[Q]{ + Access: psql.Where[Q, string](cols.Access), + Created: psql.Where[Q, time.Time](cols.Created), + Email: psql.Where[Q, string](cols.Email), + FullName: psql.Where[Q, string](cols.FullName), + ID: psql.Where[Q, string](cols.ID), + Level: psql.Where[Q, string](cols.Level), + OrgID: psql.Where[Q, string](cols.OrgID), + PublicUserID: psql.Where[Q, int32](cols.PublicUserID), + Region: psql.Where[Q, string](cols.Region), + Role: psql.Where[Q, string](cols.Role), + RoleID: psql.Where[Q, string](cols.RoleID), + Username: psql.Where[Q, string](cols.Username), + UserLicenseTypeID: psql.Where[Q, string](cols.UserLicenseTypeID), + UserType: psql.Where[Q, string](cols.UserType), + } +} + +func (o *ArcgisUser) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "PublicUserUser": + rel, ok := retrieved.(*User) + if !ok { + return fmt.Errorf("arcgisuser cannot load %T as %q", retrieved, name) + } + + o.R.PublicUserUser = rel + + if rel != nil { + rel.R.PublicUserUser = ArcgisUserSlice{o} + } + return nil + case "UserUserPrivileges": + rels, ok := retrieved.(ArcgisUserPrivilegeSlice) + if !ok { + return fmt.Errorf("arcgisuser cannot load %T as %q", retrieved, name) + } + + o.R.UserUserPrivileges = rels + + for _, rel := range rels { + if rel != nil { + rel.R.UserUser = o + } + } + return nil + default: + return fmt.Errorf("arcgisuser has no relationship %q", name) + } +} + +type arcgisuserPreloader struct { + PublicUserUser func(...psql.PreloadOption) psql.Preloader +} + +func buildArcgisUserPreloader() arcgisuserPreloader { + return arcgisuserPreloader{ + PublicUserUser: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*User, UserSlice](psql.PreloadRel{ + Name: "PublicUserUser", + Sides: []psql.PreloadSide{ + { + From: ArcgisUsers, + To: Users, + FromColumns: []string{"public_user_id"}, + ToColumns: []string{"id"}, + }, + }, + }, Users.Columns.Names(), opts...) + }, + } +} + +type arcgisuserThenLoader[Q orm.Loadable] struct { + PublicUserUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + UserUserPrivileges func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildArcgisUserThenLoader[Q orm.Loadable]() arcgisuserThenLoader[Q] { + type PublicUserUserLoadInterface interface { + LoadPublicUserUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type UserUserPrivilegesLoadInterface interface { + LoadUserUserPrivileges(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return arcgisuserThenLoader[Q]{ + PublicUserUser: thenLoadBuilder[Q]( + "PublicUserUser", + func(ctx context.Context, exec bob.Executor, retrieved PublicUserUserLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadPublicUserUser(ctx, exec, mods...) + }, + ), + UserUserPrivileges: thenLoadBuilder[Q]( + "UserUserPrivileges", + func(ctx context.Context, exec bob.Executor, retrieved UserUserPrivilegesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadUserUserPrivileges(ctx, exec, mods...) + }, + ), + } +} + +// LoadPublicUserUser loads the arcgisuser's PublicUserUser into the .R struct +func (o *ArcgisUser) LoadPublicUserUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.PublicUserUser = nil + + related, err := o.PublicUserUser(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.PublicUserUser = ArcgisUserSlice{o} + + o.R.PublicUserUser = related + return nil +} + +// LoadPublicUserUser loads the arcgisuser's PublicUserUser into the .R struct +func (os ArcgisUserSlice) LoadPublicUserUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + users, err := os.PublicUserUser(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range users { + + if !(o.PublicUserID == rel.ID) { + continue + } + + rel.R.PublicUserUser = append(rel.R.PublicUserUser, o) + + o.R.PublicUserUser = rel + break + } + } + + return nil +} + +// LoadUserUserPrivileges loads the arcgisuser's UserUserPrivileges into the .R struct +func (o *ArcgisUser) LoadUserUserPrivileges(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.UserUserPrivileges = nil + + related, err := o.UserUserPrivileges(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.UserUser = o + } + + o.R.UserUserPrivileges = related + return nil +} + +// LoadUserUserPrivileges loads the arcgisuser's UserUserPrivileges into the .R struct +func (os ArcgisUserSlice) LoadUserUserPrivileges(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + arcgisUserPrivileges, err := os.UserUserPrivileges(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.UserUserPrivileges = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range arcgisUserPrivileges { + + if !(o.ID == rel.UserID) { + continue + } + + rel.R.UserUser = o + + o.R.UserUserPrivileges = append(o.R.UserUserPrivileges, rel) + } + } + + return nil +} + +// arcgisuserC is where relationship counts are stored. +type arcgisuserC struct { + UserUserPrivileges *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *ArcgisUser) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "UserUserPrivileges": + o.C.UserUserPrivileges = &count + } + return nil +} + +type arcgisuserCountPreloader struct { + UserUserPrivileges func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildArcgisUserCountPreloader() arcgisuserCountPreloader { + return arcgisuserCountPreloader{ + UserUserPrivileges: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*ArcgisUser]("UserUserPrivileges", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = ArcgisUsers.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(ArcgisUserPrivileges.Name()), + sm.Where(psql.Quote(ArcgisUserPrivileges.Alias(), "user_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type arcgisuserCountThenLoader[Q orm.Loadable] struct { + UserUserPrivileges func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildArcgisUserCountThenLoader[Q orm.Loadable]() arcgisuserCountThenLoader[Q] { + type UserUserPrivilegesCountInterface interface { + LoadCountUserUserPrivileges(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return arcgisuserCountThenLoader[Q]{ + UserUserPrivileges: countThenLoadBuilder[Q]( + "UserUserPrivileges", + func(ctx context.Context, exec bob.Executor, retrieved UserUserPrivilegesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountUserUserPrivileges(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountUserUserPrivileges loads the count of UserUserPrivileges into the C struct +func (o *ArcgisUser) LoadCountUserUserPrivileges(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.UserUserPrivileges(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.UserUserPrivileges = &count + return nil +} + +// LoadCountUserUserPrivileges loads the count of UserUserPrivileges for a slice +func (os ArcgisUserSlice) LoadCountUserUserPrivileges(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountUserUserPrivileges(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +type arcgisuserJoins[Q dialect.Joinable] struct { + typ string + PublicUserUser modAs[Q, userColumns] + UserUserPrivileges modAs[Q, arcgisUserPrivilegeColumns] +} + +func (j arcgisuserJoins[Q]) aliasedAs(alias string) arcgisuserJoins[Q] { + return buildArcgisUserJoins[Q](buildArcgisUserColumns(alias), j.typ) +} + +func buildArcgisUserJoins[Q dialect.Joinable](cols arcgisuserColumns, typ string) arcgisuserJoins[Q] { + return arcgisuserJoins[Q]{ + typ: typ, + PublicUserUser: modAs[Q, userColumns]{ + c: Users.Columns, + f: func(to userColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Users.Name().As(to.Alias())).On( + to.ID.EQ(cols.PublicUserID), + )) + } + + return mods + }, + }, + UserUserPrivileges: modAs[Q, arcgisUserPrivilegeColumns]{ + c: ArcgisUserPrivileges.Columns, + f: func(to arcgisUserPrivilegeColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, ArcgisUserPrivileges.Name().As(to.Alias())).On( + to.UserID.EQ(cols.ID), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/arcgis.user_privilege.bob.go b/db/models/arcgis.user_privilege.bob.go new file mode 100644 index 00000000..0783119b --- /dev/null +++ b/db/models/arcgis.user_privilege.bob.go @@ -0,0 +1,612 @@ +// Code generated by BobGen psql v0.42.1. 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/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" +) + +// ArcgisUserPrivilege is an object representing the database table. +type ArcgisUserPrivilege struct { + UserID string `db:"user_id,pk" ` + Privilege string `db:"privilege,pk" ` + + R arcgisUserPrivilegeR `db:"-" ` +} + +// ArcgisUserPrivilegeSlice is an alias for a slice of pointers to ArcgisUserPrivilege. +// This should almost always be used instead of []*ArcgisUserPrivilege. +type ArcgisUserPrivilegeSlice []*ArcgisUserPrivilege + +// ArcgisUserPrivileges contains methods to work with the user_privilege table +var ArcgisUserPrivileges = psql.NewTablex[*ArcgisUserPrivilege, ArcgisUserPrivilegeSlice, *ArcgisUserPrivilegeSetter]("arcgis", "user_privilege", buildArcgisUserPrivilegeColumns("arcgis.user_privilege")) + +// ArcgisUserPrivilegesQuery is a query on the user_privilege table +type ArcgisUserPrivilegesQuery = *psql.ViewQuery[*ArcgisUserPrivilege, ArcgisUserPrivilegeSlice] + +// arcgisUserPrivilegeR is where relationships are stored. +type arcgisUserPrivilegeR struct { + UserUser *ArcgisUser // arcgis.user_privilege.user_privilege_user_id_fkey +} + +func buildArcgisUserPrivilegeColumns(alias string) arcgisUserPrivilegeColumns { + return arcgisUserPrivilegeColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "user_id", "privilege", + ).WithParent("arcgis.user_privilege"), + tableAlias: alias, + UserID: psql.Quote(alias, "user_id"), + Privilege: psql.Quote(alias, "privilege"), + } +} + +type arcgisUserPrivilegeColumns struct { + expr.ColumnsExpr + tableAlias string + UserID psql.Expression + Privilege psql.Expression +} + +func (c arcgisUserPrivilegeColumns) Alias() string { + return c.tableAlias +} + +func (arcgisUserPrivilegeColumns) AliasedAs(alias string) arcgisUserPrivilegeColumns { + return buildArcgisUserPrivilegeColumns(alias) +} + +// ArcgisUserPrivilegeSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type ArcgisUserPrivilegeSetter struct { + UserID omit.Val[string] `db:"user_id,pk" ` + Privilege omit.Val[string] `db:"privilege,pk" ` +} + +func (s ArcgisUserPrivilegeSetter) SetColumns() []string { + vals := make([]string, 0, 2) + if s.UserID.IsValue() { + vals = append(vals, "user_id") + } + if s.Privilege.IsValue() { + vals = append(vals, "privilege") + } + return vals +} + +func (s ArcgisUserPrivilegeSetter) Overwrite(t *ArcgisUserPrivilege) { + if s.UserID.IsValue() { + t.UserID = s.UserID.MustGet() + } + if s.Privilege.IsValue() { + t.Privilege = s.Privilege.MustGet() + } +} + +func (s *ArcgisUserPrivilegeSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return ArcgisUserPrivileges.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, 2) + if s.UserID.IsValue() { + vals[0] = psql.Arg(s.UserID.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.Privilege.IsValue() { + vals[1] = psql.Arg(s.Privilege.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s ArcgisUserPrivilegeSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s ArcgisUserPrivilegeSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 2) + + if s.UserID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "user_id")...), + psql.Arg(s.UserID), + }}) + } + + if s.Privilege.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "privilege")...), + psql.Arg(s.Privilege), + }}) + } + + return exprs +} + +// FindArcgisUserPrivilege retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindArcgisUserPrivilege(ctx context.Context, exec bob.Executor, UserIDPK string, PrivilegePK string, cols ...string) (*ArcgisUserPrivilege, error) { + if len(cols) == 0 { + return ArcgisUserPrivileges.Query( + sm.Where(ArcgisUserPrivileges.Columns.UserID.EQ(psql.Arg(UserIDPK))), + sm.Where(ArcgisUserPrivileges.Columns.Privilege.EQ(psql.Arg(PrivilegePK))), + ).One(ctx, exec) + } + + return ArcgisUserPrivileges.Query( + sm.Where(ArcgisUserPrivileges.Columns.UserID.EQ(psql.Arg(UserIDPK))), + sm.Where(ArcgisUserPrivileges.Columns.Privilege.EQ(psql.Arg(PrivilegePK))), + sm.Columns(ArcgisUserPrivileges.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// ArcgisUserPrivilegeExists checks the presence of a single record by primary key +func ArcgisUserPrivilegeExists(ctx context.Context, exec bob.Executor, UserIDPK string, PrivilegePK string) (bool, error) { + return ArcgisUserPrivileges.Query( + sm.Where(ArcgisUserPrivileges.Columns.UserID.EQ(psql.Arg(UserIDPK))), + sm.Where(ArcgisUserPrivileges.Columns.Privilege.EQ(psql.Arg(PrivilegePK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after ArcgisUserPrivilege is retrieved from the database +func (o *ArcgisUserPrivilege) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = ArcgisUserPrivileges.AfterSelectHooks.RunHooks(ctx, exec, ArcgisUserPrivilegeSlice{o}) + case bob.QueryTypeInsert: + ctx, err = ArcgisUserPrivileges.AfterInsertHooks.RunHooks(ctx, exec, ArcgisUserPrivilegeSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = ArcgisUserPrivileges.AfterUpdateHooks.RunHooks(ctx, exec, ArcgisUserPrivilegeSlice{o}) + case bob.QueryTypeDelete: + ctx, err = ArcgisUserPrivileges.AfterDeleteHooks.RunHooks(ctx, exec, ArcgisUserPrivilegeSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the ArcgisUserPrivilege +func (o *ArcgisUserPrivilege) primaryKeyVals() bob.Expression { + return psql.ArgGroup( + o.UserID, + o.Privilege, + ) +} + +func (o *ArcgisUserPrivilege) pkEQ() dialect.Expression { + return psql.Group(psql.Quote("arcgis.user_privilege", "user_id"), psql.Quote("arcgis.user_privilege", "privilege")).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 ArcgisUserPrivilege +func (o *ArcgisUserPrivilege) Update(ctx context.Context, exec bob.Executor, s *ArcgisUserPrivilegeSetter) error { + v, err := ArcgisUserPrivileges.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 ArcgisUserPrivilege record with an executor +func (o *ArcgisUserPrivilege) Delete(ctx context.Context, exec bob.Executor) error { + _, err := ArcgisUserPrivileges.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the ArcgisUserPrivilege using the executor +func (o *ArcgisUserPrivilege) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := ArcgisUserPrivileges.Query( + sm.Where(ArcgisUserPrivileges.Columns.UserID.EQ(psql.Arg(o.UserID))), + sm.Where(ArcgisUserPrivileges.Columns.Privilege.EQ(psql.Arg(o.Privilege))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after ArcgisUserPrivilegeSlice is retrieved from the database +func (o ArcgisUserPrivilegeSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = ArcgisUserPrivileges.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = ArcgisUserPrivileges.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = ArcgisUserPrivileges.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = ArcgisUserPrivileges.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o ArcgisUserPrivilegeSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Group(psql.Quote("arcgis.user_privilege", "user_id"), psql.Quote("arcgis.user_privilege", "privilege")).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 ArcgisUserPrivilegeSlice) copyMatchingRows(from ...*ArcgisUserPrivilege) { + for i, old := range o { + for _, new := range from { + if new.UserID != old.UserID { + continue + } + if new.Privilege != old.Privilege { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o ArcgisUserPrivilegeSlice) 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 ArcgisUserPrivileges.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 *ArcgisUserPrivilege: + o.copyMatchingRows(retrieved) + case []*ArcgisUserPrivilege: + o.copyMatchingRows(retrieved...) + case ArcgisUserPrivilegeSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a ArcgisUserPrivilege or a slice of ArcgisUserPrivilege + // then run the AfterUpdateHooks on the slice + _, err = ArcgisUserPrivileges.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o ArcgisUserPrivilegeSlice) 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 ArcgisUserPrivileges.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 *ArcgisUserPrivilege: + o.copyMatchingRows(retrieved) + case []*ArcgisUserPrivilege: + o.copyMatchingRows(retrieved...) + case ArcgisUserPrivilegeSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a ArcgisUserPrivilege or a slice of ArcgisUserPrivilege + // then run the AfterDeleteHooks on the slice + _, err = ArcgisUserPrivileges.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o ArcgisUserPrivilegeSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals ArcgisUserPrivilegeSetter) error { + if len(o) == 0 { + return nil + } + + _, err := ArcgisUserPrivileges.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o ArcgisUserPrivilegeSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := ArcgisUserPrivileges.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o ArcgisUserPrivilegeSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := ArcgisUserPrivileges.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// UserUser starts a query for related objects on arcgis.user_ +func (o *ArcgisUserPrivilege) UserUser(mods ...bob.Mod[*dialect.SelectQuery]) ArcgisUsersQuery { + return ArcgisUsers.Query(append(mods, + sm.Where(ArcgisUsers.Columns.ID.EQ(psql.Arg(o.UserID))), + )...) +} + +func (os ArcgisUserPrivilegeSlice) UserUser(mods ...bob.Mod[*dialect.SelectQuery]) ArcgisUsersQuery { + pkUserID := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkUserID = append(pkUserID, o.UserID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkUserID), "text[]")), + )) + + return ArcgisUsers.Query(append(mods, + sm.Where(psql.Group(ArcgisUsers.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + +func attachArcgisUserPrivilegeUserUser0(ctx context.Context, exec bob.Executor, count int, arcgisUserPrivilege0 *ArcgisUserPrivilege, arcgisuser1 *ArcgisUser) (*ArcgisUserPrivilege, error) { + setter := &ArcgisUserPrivilegeSetter{ + UserID: omit.From(arcgisuser1.ID), + } + + err := arcgisUserPrivilege0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachArcgisUserPrivilegeUserUser0: %w", err) + } + + return arcgisUserPrivilege0, nil +} + +func (arcgisUserPrivilege0 *ArcgisUserPrivilege) InsertUserUser(ctx context.Context, exec bob.Executor, related *ArcgisUserSetter) error { + var err error + + arcgisuser1, err := ArcgisUsers.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachArcgisUserPrivilegeUserUser0(ctx, exec, 1, arcgisUserPrivilege0, arcgisuser1) + if err != nil { + return err + } + + arcgisUserPrivilege0.R.UserUser = arcgisuser1 + + arcgisuser1.R.UserUserPrivileges = append(arcgisuser1.R.UserUserPrivileges, arcgisUserPrivilege0) + + return nil +} + +func (arcgisUserPrivilege0 *ArcgisUserPrivilege) AttachUserUser(ctx context.Context, exec bob.Executor, arcgisuser1 *ArcgisUser) error { + var err error + + _, err = attachArcgisUserPrivilegeUserUser0(ctx, exec, 1, arcgisUserPrivilege0, arcgisuser1) + if err != nil { + return err + } + + arcgisUserPrivilege0.R.UserUser = arcgisuser1 + + arcgisuser1.R.UserUserPrivileges = append(arcgisuser1.R.UserUserPrivileges, arcgisUserPrivilege0) + + return nil +} + +type arcgisUserPrivilegeWhere[Q psql.Filterable] struct { + UserID psql.WhereMod[Q, string] + Privilege psql.WhereMod[Q, string] +} + +func (arcgisUserPrivilegeWhere[Q]) AliasedAs(alias string) arcgisUserPrivilegeWhere[Q] { + return buildArcgisUserPrivilegeWhere[Q](buildArcgisUserPrivilegeColumns(alias)) +} + +func buildArcgisUserPrivilegeWhere[Q psql.Filterable](cols arcgisUserPrivilegeColumns) arcgisUserPrivilegeWhere[Q] { + return arcgisUserPrivilegeWhere[Q]{ + UserID: psql.Where[Q, string](cols.UserID), + Privilege: psql.Where[Q, string](cols.Privilege), + } +} + +func (o *ArcgisUserPrivilege) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "UserUser": + rel, ok := retrieved.(*ArcgisUser) + if !ok { + return fmt.Errorf("arcgisUserPrivilege cannot load %T as %q", retrieved, name) + } + + o.R.UserUser = rel + + if rel != nil { + rel.R.UserUserPrivileges = ArcgisUserPrivilegeSlice{o} + } + return nil + default: + return fmt.Errorf("arcgisUserPrivilege has no relationship %q", name) + } +} + +type arcgisUserPrivilegePreloader struct { + UserUser func(...psql.PreloadOption) psql.Preloader +} + +func buildArcgisUserPrivilegePreloader() arcgisUserPrivilegePreloader { + return arcgisUserPrivilegePreloader{ + UserUser: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*ArcgisUser, ArcgisUserSlice](psql.PreloadRel{ + Name: "UserUser", + Sides: []psql.PreloadSide{ + { + From: ArcgisUserPrivileges, + To: ArcgisUsers, + FromColumns: []string{"user_id"}, + ToColumns: []string{"id"}, + }, + }, + }, ArcgisUsers.Columns.Names(), opts...) + }, + } +} + +type arcgisUserPrivilegeThenLoader[Q orm.Loadable] struct { + UserUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildArcgisUserPrivilegeThenLoader[Q orm.Loadable]() arcgisUserPrivilegeThenLoader[Q] { + type UserUserLoadInterface interface { + LoadUserUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return arcgisUserPrivilegeThenLoader[Q]{ + UserUser: thenLoadBuilder[Q]( + "UserUser", + func(ctx context.Context, exec bob.Executor, retrieved UserUserLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadUserUser(ctx, exec, mods...) + }, + ), + } +} + +// LoadUserUser loads the arcgisUserPrivilege's UserUser into the .R struct +func (o *ArcgisUserPrivilege) LoadUserUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.UserUser = nil + + related, err := o.UserUser(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.UserUserPrivileges = ArcgisUserPrivilegeSlice{o} + + o.R.UserUser = related + return nil +} + +// LoadUserUser loads the arcgisUserPrivilege's UserUser into the .R struct +func (os ArcgisUserPrivilegeSlice) LoadUserUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + arcgisusers, err := os.UserUser(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range arcgisusers { + + if !(o.UserID == rel.ID) { + continue + } + + rel.R.UserUserPrivileges = append(rel.R.UserUserPrivileges, o) + + o.R.UserUser = rel + break + } + } + + return nil +} + +type arcgisUserPrivilegeJoins[Q dialect.Joinable] struct { + typ string + UserUser modAs[Q, arcgisuserColumns] +} + +func (j arcgisUserPrivilegeJoins[Q]) aliasedAs(alias string) arcgisUserPrivilegeJoins[Q] { + return buildArcgisUserPrivilegeJoins[Q](buildArcgisUserPrivilegeColumns(alias), j.typ) +} + +func buildArcgisUserPrivilegeJoins[Q dialect.Joinable](cols arcgisUserPrivilegeColumns, typ string) arcgisUserPrivilegeJoins[Q] { + return arcgisUserPrivilegeJoins[Q]{ + typ: typ, + UserUser: modAs[Q, arcgisuserColumns]{ + c: ArcgisUsers.Columns, + f: func(to arcgisuserColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, ArcgisUsers.Name().As(to.Alias())).On( + to.ID.EQ(cols.UserID), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/bob_counts.bob.go b/db/models/bob_counts.bob.go new file mode 100644 index 00000000..be17543e --- /dev/null +++ b/db/models/bob_counts.bob.go @@ -0,0 +1,168 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "io" + + "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" +) + +var ( + PreloadCount = getPreloadCount() + ThenLoadCount = getThenLoadCount[*dialect.SelectQuery]() + InsertThenLoadCount = getThenLoadCount[*dialect.InsertQuery]() +) + +type preloadCounts struct { + ArcgisUser arcgisuserCountPreloader + NoteAudio noteAudioCountPreloader + NoteImage noteImageCountPreloader + Organization organizationCountPreloader + PublicreportPool publicreportPoolCountPreloader + PublicreportQuick publicreportQuickCountPreloader + User userCountPreloader +} + +func getPreloadCount() preloadCounts { + return preloadCounts{ + ArcgisUser: buildArcgisUserCountPreloader(), + NoteAudio: buildNoteAudioCountPreloader(), + NoteImage: buildNoteImageCountPreloader(), + Organization: buildOrganizationCountPreloader(), + PublicreportPool: buildPublicreportPoolCountPreloader(), + PublicreportQuick: buildPublicreportQuickCountPreloader(), + User: buildUserCountPreloader(), + } +} + +type thenLoadCounts[Q orm.Loadable] struct { + ArcgisUser arcgisuserCountThenLoader[Q] + NoteAudio noteAudioCountThenLoader[Q] + NoteImage noteImageCountThenLoader[Q] + Organization organizationCountThenLoader[Q] + PublicreportPool publicreportPoolCountThenLoader[Q] + PublicreportQuick publicreportQuickCountThenLoader[Q] + User userCountThenLoader[Q] +} + +func getThenLoadCount[Q orm.Loadable]() thenLoadCounts[Q] { + return thenLoadCounts[Q]{ + ArcgisUser: buildArcgisUserCountThenLoader[Q](), + NoteAudio: buildNoteAudioCountThenLoader[Q](), + NoteImage: buildNoteImageCountThenLoader[Q](), + Organization: buildOrganizationCountThenLoader[Q](), + PublicreportPool: buildPublicreportPoolCountThenLoader[Q](), + PublicreportQuick: buildPublicreportQuickCountThenLoader[Q](), + User: buildUserCountThenLoader[Q](), + } +} + +func countThenLoadBuilder[Q orm.Loadable, T any](name string, f func(context.Context, bob.Executor, T, ...bob.Mod[*dialect.SelectQuery]) error) func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] { + return func(queryMods ...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] { + return func(ctx context.Context, exec bob.Executor, retrieved any) error { + loader, isLoader := retrieved.(T) + if !isLoader { + return nil // silently skip if not the right type + } + + return f(ctx, exec, loader, queryMods...) + } + } +} + +// countPreloadable is an interface for models that can have counts preloaded +type countPreloadable interface { + PreloadCount(name string, count int64) error +} + +// countPreloadMod is used to add a count subquery to the SELECT +type countPreloadMod[T countPreloadable] struct { + name string + countExpr func(from string) bob.Expression +} + +// Apply implements bob.Mod +func (c countPreloadMod[T]) Apply(q *dialect.SelectQuery) { + c.applyCount(q, "") +} + +// applyCount adds the count subquery to the query +func (c countPreloadMod[T]) applyCount(q *dialect.SelectQuery, parent string) { + countCol := c.countExpr(parent) + q.AppendPreloadSelect(aliasedExpr{expr: countCol, alias: "__count_" + c.name}) +} + +// aliasedExpr wraps an expression with an alias +type aliasedExpr struct { + expr bob.Expression + alias string +} + +func (a aliasedExpr) WriteSQL(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + args, err := a.expr.WriteSQL(ctx, w, d, start) + if err != nil { + return nil, err + } + w.WriteString(" AS ") + d.WriteQuoted(w, a.alias) + return args, nil +} + +// countPreloader returns a Preloader that adds a count subquery +func countPreloader[T countPreloadable](name string, countExpr func(from string) bob.Expression) psql.Preloader { + return func(parent string) (bob.Mod[*dialect.SelectQuery], scan.MapperMod, []bob.Loader) { + m := countPreloadMod[T]{ + name: name, + countExpr: countExpr, + } + + queryMod := bob.ModFunc[*dialect.SelectQuery](func(q *dialect.SelectQuery) { + m.applyCount(q, parent) + }) + + mapperMod := func(ctx context.Context, cols []string) (scan.BeforeFunc, scan.AfterMod) { + // Find the count column + countColName := "__count_" + name + colIndex := -1 + for i, col := range cols { + if col == countColName { + colIndex = i + break + } + } + + return func(r *scan.Row) (any, error) { + if colIndex >= 0 { + var count *int64 + r.ScheduleScanByIndex(colIndex, &count) + return &count, nil + } + return nil, nil + }, func(link, retrieved any) error { + if link == nil { + return nil + } + countPtr, ok := link.(**int64) + if !ok || countPtr == nil || *countPtr == nil { + return nil + } + + loader, isLoader := retrieved.(countPreloadable) + if !isLoader { + return nil + } + + return loader.PreloadCount(name, **countPtr) + } + } + + return queryMod, mapperMod, nil + } +} diff --git a/db/models/bob_joins.bob.go b/db/models/bob_joins.bob.go index 37643b0c..03d85cb1 100644 --- a/db/models/bob_joins.bob.go +++ b/db/models/bob_joins.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -32,6 +32,8 @@ func (j joinSet[Q]) AliasedAs(alias string) joinSet[Q] { } type joins[Q dialect.Joinable] struct { + ArcgisUsers joinSet[arcgisuserJoins[Q]] + ArcgisUserPrivileges joinSet[arcgisUserPrivilegeJoins[Q]] FieldseekerContainerrelates joinSet[fieldseekerContainerrelateJoins[Q]] FieldseekerFieldscoutinglogs joinSet[fieldseekerFieldscoutinglogJoins[Q]] FieldseekerHabitatrelates joinSet[fieldseekerHabitatrelateJoins[Q]] @@ -87,6 +89,8 @@ func buildJoinSet[Q interface{ aliasedAs(string) Q }, C any, F func(C, string) Q func getJoins[Q dialect.Joinable]() joins[Q] { return joins[Q]{ + ArcgisUsers: buildJoinSet[arcgisuserJoins[Q]](ArcgisUsers.Columns, buildArcgisUserJoins), + ArcgisUserPrivileges: buildJoinSet[arcgisUserPrivilegeJoins[Q]](ArcgisUserPrivileges.Columns, buildArcgisUserPrivilegeJoins), FieldseekerContainerrelates: buildJoinSet[fieldseekerContainerrelateJoins[Q]](FieldseekerContainerrelates.Columns, buildFieldseekerContainerrelateJoins), FieldseekerFieldscoutinglogs: buildJoinSet[fieldseekerFieldscoutinglogJoins[Q]](FieldseekerFieldscoutinglogs.Columns, buildFieldseekerFieldscoutinglogJoins), FieldseekerHabitatrelates: buildJoinSet[fieldseekerHabitatrelateJoins[Q]](FieldseekerHabitatrelates.Columns, buildFieldseekerHabitatrelateJoins), diff --git a/db/models/bob_loaders.bob.go b/db/models/bob_loaders.bob.go index 5af29980..21748f91 100644 --- a/db/models/bob_loaders.bob.go +++ b/db/models/bob_loaders.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -17,6 +17,8 @@ import ( var Preload = getPreloaders() type preloaders struct { + ArcgisUser arcgisuserPreloader + ArcgisUserPrivilege arcgisUserPrivilegePreloader FieldseekerContainerrelate fieldseekerContainerrelatePreloader FieldseekerFieldscoutinglog fieldseekerFieldscoutinglogPreloader FieldseekerHabitatrelate fieldseekerHabitatrelatePreloader @@ -64,6 +66,8 @@ type preloaders struct { func getPreloaders() preloaders { return preloaders{ + ArcgisUser: buildArcgisUserPreloader(), + ArcgisUserPrivilege: buildArcgisUserPrivilegePreloader(), FieldseekerContainerrelate: buildFieldseekerContainerrelatePreloader(), FieldseekerFieldscoutinglog: buildFieldseekerFieldscoutinglogPreloader(), FieldseekerHabitatrelate: buildFieldseekerHabitatrelatePreloader(), @@ -117,6 +121,8 @@ var ( ) type thenLoaders[Q orm.Loadable] struct { + ArcgisUser arcgisuserThenLoader[Q] + ArcgisUserPrivilege arcgisUserPrivilegeThenLoader[Q] FieldseekerContainerrelate fieldseekerContainerrelateThenLoader[Q] FieldseekerFieldscoutinglog fieldseekerFieldscoutinglogThenLoader[Q] FieldseekerHabitatrelate fieldseekerHabitatrelateThenLoader[Q] @@ -164,6 +170,8 @@ type thenLoaders[Q orm.Loadable] struct { func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] { return thenLoaders[Q]{ + ArcgisUser: buildArcgisUserThenLoader[Q](), + ArcgisUserPrivilege: buildArcgisUserPrivilegeThenLoader[Q](), FieldseekerContainerrelate: buildFieldseekerContainerrelateThenLoader[Q](), FieldseekerFieldscoutinglog: buildFieldseekerFieldscoutinglogThenLoader[Q](), FieldseekerHabitatrelate: buildFieldseekerHabitatrelateThenLoader[Q](), diff --git a/db/models/bob_where.bob.go b/db/models/bob_where.bob.go index 6af44c49..69ff2736 100644 --- a/db/models/bob_where.bob.go +++ b/db/models/bob_where.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -17,6 +17,8 @@ var ( ) func Where[Q psql.Filterable]() struct { + ArcgisUsers arcgisuserWhere[Q] + ArcgisUserPrivileges arcgisUserPrivilegeWhere[Q] Districts districtWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] @@ -72,6 +74,8 @@ func Where[Q psql.Filterable]() struct { Users userWhere[Q] } { return struct { + ArcgisUsers arcgisuserWhere[Q] + ArcgisUserPrivileges arcgisUserPrivilegeWhere[Q] Districts districtWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] @@ -126,6 +130,8 @@ func Where[Q psql.Filterable]() struct { SpatialRefSys spatialRefSyWhere[Q] Users userWhere[Q] }{ + ArcgisUsers: buildArcgisUserWhere[Q](ArcgisUsers.Columns), + ArcgisUserPrivileges: buildArcgisUserPrivilegeWhere[Q](ArcgisUserPrivileges.Columns), Districts: buildDistrictWhere[Q](Districts.Columns), FieldseekerContainerrelates: buildFieldseekerContainerrelateWhere[Q](FieldseekerContainerrelates.Columns), FieldseekerFieldscoutinglogs: buildFieldseekerFieldscoutinglogWhere[Q](FieldseekerFieldscoutinglogs.Columns), diff --git a/db/models/district.bob.go b/db/models/district.bob.go index 026f4c47..a0c3bb5b 100644 --- a/db/models/district.bob.go +++ b/db/models/district.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.containerrelate.bob.go b/db/models/fieldseeker.containerrelate.bob.go index 434c2320..8b439dce 100644 --- a/db/models/fieldseeker.containerrelate.bob.go +++ b/db/models/fieldseeker.containerrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.fieldscoutinglog.bob.go b/db/models/fieldseeker.fieldscoutinglog.bob.go index 3d88e8fd..a0caa392 100644 --- a/db/models/fieldseeker.fieldscoutinglog.bob.go +++ b/db/models/fieldseeker.fieldscoutinglog.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.habitatrelate.bob.go b/db/models/fieldseeker.habitatrelate.bob.go index f78468e3..cefb73a8 100644 --- a/db/models/fieldseeker.habitatrelate.bob.go +++ b/db/models/fieldseeker.habitatrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.inspectionsample.bob.go b/db/models/fieldseeker.inspectionsample.bob.go index 16fd4908..73aaa6da 100644 --- a/db/models/fieldseeker.inspectionsample.bob.go +++ b/db/models/fieldseeker.inspectionsample.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.inspectionsampledetail.bob.go b/db/models/fieldseeker.inspectionsampledetail.bob.go index de4bebc5..d9d4d406 100644 --- a/db/models/fieldseeker.inspectionsampledetail.bob.go +++ b/db/models/fieldseeker.inspectionsampledetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.linelocation.bob.go b/db/models/fieldseeker.linelocation.bob.go index 9cafe385..9efc4ee5 100644 --- a/db/models/fieldseeker.linelocation.bob.go +++ b/db/models/fieldseeker.linelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.locationtracking.bob.go b/db/models/fieldseeker.locationtracking.bob.go index da97a897..facfcf7d 100644 --- a/db/models/fieldseeker.locationtracking.bob.go +++ b/db/models/fieldseeker.locationtracking.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.mosquitoinspection.bob.go b/db/models/fieldseeker.mosquitoinspection.bob.go index 85baa2b7..db094235 100644 --- a/db/models/fieldseeker.mosquitoinspection.bob.go +++ b/db/models/fieldseeker.mosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.pointlocation.bob.go b/db/models/fieldseeker.pointlocation.bob.go index 4a879030..e212b558 100644 --- a/db/models/fieldseeker.pointlocation.bob.go +++ b/db/models/fieldseeker.pointlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.polygonlocation.bob.go b/db/models/fieldseeker.polygonlocation.bob.go index 2e65aa46..5003caa2 100644 --- a/db/models/fieldseeker.polygonlocation.bob.go +++ b/db/models/fieldseeker.polygonlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.pool.bob.go b/db/models/fieldseeker.pool.bob.go index c204b6c0..898e7bcb 100644 --- a/db/models/fieldseeker.pool.bob.go +++ b/db/models/fieldseeker.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.pooldetail.bob.go b/db/models/fieldseeker.pooldetail.bob.go index 385f4543..a9cbd612 100644 --- a/db/models/fieldseeker.pooldetail.bob.go +++ b/db/models/fieldseeker.pooldetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.proposedtreatmentarea.bob.go b/db/models/fieldseeker.proposedtreatmentarea.bob.go index 9669e95f..8f89fe65 100644 --- a/db/models/fieldseeker.proposedtreatmentarea.bob.go +++ b/db/models/fieldseeker.proposedtreatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.qamosquitoinspection.bob.go b/db/models/fieldseeker.qamosquitoinspection.bob.go index b742d6b3..76fcae96 100644 --- a/db/models/fieldseeker.qamosquitoinspection.bob.go +++ b/db/models/fieldseeker.qamosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.rodentlocation.bob.go b/db/models/fieldseeker.rodentlocation.bob.go index 6e57a0a1..28117cfb 100644 --- a/db/models/fieldseeker.rodentlocation.bob.go +++ b/db/models/fieldseeker.rodentlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.samplecollection.bob.go b/db/models/fieldseeker.samplecollection.bob.go index 512a787f..f3648eda 100644 --- a/db/models/fieldseeker.samplecollection.bob.go +++ b/db/models/fieldseeker.samplecollection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.samplelocation.bob.go b/db/models/fieldseeker.samplelocation.bob.go index 6782a33b..cde38c45 100644 --- a/db/models/fieldseeker.samplelocation.bob.go +++ b/db/models/fieldseeker.samplelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.servicerequest.bob.go b/db/models/fieldseeker.servicerequest.bob.go index 5cac059f..f3b89f50 100644 --- a/db/models/fieldseeker.servicerequest.bob.go +++ b/db/models/fieldseeker.servicerequest.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.speciesabundance.bob.go b/db/models/fieldseeker.speciesabundance.bob.go index bb8fef8f..f8b46124 100644 --- a/db/models/fieldseeker.speciesabundance.bob.go +++ b/db/models/fieldseeker.speciesabundance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.stormdrain.bob.go b/db/models/fieldseeker.stormdrain.bob.go index 8169a1cd..c603b20c 100644 --- a/db/models/fieldseeker.stormdrain.bob.go +++ b/db/models/fieldseeker.stormdrain.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.timecard.bob.go b/db/models/fieldseeker.timecard.bob.go index d5571f34..8b7b1805 100644 --- a/db/models/fieldseeker.timecard.bob.go +++ b/db/models/fieldseeker.timecard.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.trapdata.bob.go b/db/models/fieldseeker.trapdata.bob.go index e23fa62d..f6fe3f01 100644 --- a/db/models/fieldseeker.trapdata.bob.go +++ b/db/models/fieldseeker.trapdata.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.traplocation.bob.go b/db/models/fieldseeker.traplocation.bob.go index ae0123ef..a3e56cda 100644 --- a/db/models/fieldseeker.traplocation.bob.go +++ b/db/models/fieldseeker.traplocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.treatment.bob.go b/db/models/fieldseeker.treatment.bob.go index f4129dbd..d219c571 100644 --- a/db/models/fieldseeker.treatment.bob.go +++ b/db/models/fieldseeker.treatment.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.treatmentarea.bob.go b/db/models/fieldseeker.treatmentarea.bob.go index 507f947c..6e1ffcb8 100644 --- a/db/models/fieldseeker.treatmentarea.bob.go +++ b/db/models/fieldseeker.treatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.zones.bob.go b/db/models/fieldseeker.zones.bob.go index c6b8695b..e5e9f7b4 100644 --- a/db/models/fieldseeker.zones.bob.go +++ b/db/models/fieldseeker.zones.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker.zones2.bob.go b/db/models/fieldseeker.zones2.bob.go index 93c6badb..02e3a743 100644 --- a/db/models/fieldseeker.zones2.bob.go +++ b/db/models/fieldseeker.zones2.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/fieldseeker_sync.bob.go b/db/models/fieldseeker_sync.bob.go index b2a8ea0e..fcf5e738 100644 --- a/db/models/fieldseeker_sync.bob.go +++ b/db/models/fieldseeker_sync.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/geography_columns.bob.go b/db/models/geography_columns.bob.go index 4e1b1c04..c1452873 100644 --- a/db/models/geography_columns.bob.go +++ b/db/models/geography_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/geometry_columns.bob.go b/db/models/geometry_columns.bob.go index 9b93f543..5d875b78 100644 --- a/db/models/geometry_columns.bob.go +++ b/db/models/geometry_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/goose_db_version.bob.go b/db/models/goose_db_version.bob.go index d65bd269..19519e27 100644 --- a/db/models/goose_db_version.bob.go +++ b/db/models/goose_db_version.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/h3_aggregation.bob.go b/db/models/h3_aggregation.bob.go index 476bdba1..7e09d5c5 100644 --- a/db/models/h3_aggregation.bob.go +++ b/db/models/h3_aggregation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/note_audio.bob.go b/db/models/note_audio.bob.go index b491baed..204cb229 100644 --- a/db/models/note_audio.bob.go +++ b/db/models/note_audio.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -39,6 +39,8 @@ type NoteAudio struct { UUID uuid.UUID `db:"uuid,pk" ` R noteAudioR `db:"-" ` + + C noteAudioC `db:"-" ` } // NoteAudioSlice is an alias for a slice of pointers to NoteAudio. @@ -1487,6 +1489,162 @@ func (os NoteAudioSlice) LoadNoteAudioData(ctx context.Context, exec bob.Executo return nil } +// noteAudioC is where relationship counts are stored. +type noteAudioC struct { + NoteAudioBreadcrumbs *int64 + NoteAudioData *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *NoteAudio) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "NoteAudioBreadcrumbs": + o.C.NoteAudioBreadcrumbs = &count + case "NoteAudioData": + o.C.NoteAudioData = &count + } + return nil +} + +type noteAudioCountPreloader struct { + NoteAudioBreadcrumbs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + NoteAudioData func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildNoteAudioCountPreloader() noteAudioCountPreloader { + return noteAudioCountPreloader{ + NoteAudioBreadcrumbs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*NoteAudio]("NoteAudioBreadcrumbs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = NoteAudios.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(NoteAudioBreadcrumbs.Name()), + sm.Where(psql.Quote(NoteAudioBreadcrumbs.Alias(), "note_audio_version").EQ(psql.Quote(parent, "version"))), + sm.Where(psql.Quote(NoteAudioBreadcrumbs.Alias(), "note_audio_uuid").EQ(psql.Quote(parent, "uuid"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + NoteAudioData: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*NoteAudio]("NoteAudioData", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = NoteAudios.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(NoteAudioData.Name()), + sm.Where(psql.Quote(NoteAudioData.Alias(), "note_audio_version").EQ(psql.Quote(parent, "version"))), + sm.Where(psql.Quote(NoteAudioData.Alias(), "note_audio_uuid").EQ(psql.Quote(parent, "uuid"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type noteAudioCountThenLoader[Q orm.Loadable] struct { + NoteAudioBreadcrumbs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + NoteAudioData func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildNoteAudioCountThenLoader[Q orm.Loadable]() noteAudioCountThenLoader[Q] { + type NoteAudioBreadcrumbsCountInterface interface { + LoadCountNoteAudioBreadcrumbs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type NoteAudioDataCountInterface interface { + LoadCountNoteAudioData(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return noteAudioCountThenLoader[Q]{ + NoteAudioBreadcrumbs: countThenLoadBuilder[Q]( + "NoteAudioBreadcrumbs", + func(ctx context.Context, exec bob.Executor, retrieved NoteAudioBreadcrumbsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountNoteAudioBreadcrumbs(ctx, exec, mods...) + }, + ), + NoteAudioData: countThenLoadBuilder[Q]( + "NoteAudioData", + func(ctx context.Context, exec bob.Executor, retrieved NoteAudioDataCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountNoteAudioData(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountNoteAudioBreadcrumbs loads the count of NoteAudioBreadcrumbs into the C struct +func (o *NoteAudio) LoadCountNoteAudioBreadcrumbs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.NoteAudioBreadcrumbs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.NoteAudioBreadcrumbs = &count + return nil +} + +// LoadCountNoteAudioBreadcrumbs loads the count of NoteAudioBreadcrumbs for a slice +func (os NoteAudioSlice) LoadCountNoteAudioBreadcrumbs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountNoteAudioBreadcrumbs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountNoteAudioData loads the count of NoteAudioData into the C struct +func (o *NoteAudio) LoadCountNoteAudioData(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.NoteAudioData(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.NoteAudioData = &count + return nil +} + +// LoadCountNoteAudioData loads the count of NoteAudioData for a slice +func (os NoteAudioSlice) LoadCountNoteAudioData(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountNoteAudioData(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + type noteAudioJoins[Q dialect.Joinable] struct { typ string CreatorUser modAs[Q, userColumns] diff --git a/db/models/note_audio_breadcrumb.bob.go b/db/models/note_audio_breadcrumb.bob.go index 5dd41d5b..67165c53 100644 --- a/db/models/note_audio_breadcrumb.bob.go +++ b/db/models/note_audio_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/note_audio_data.bob.go b/db/models/note_audio_data.bob.go index b4a9ec78..89dd6055 100644 --- a/db/models/note_audio_data.bob.go +++ b/db/models/note_audio_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/note_image.bob.go b/db/models/note_image.bob.go index 071a1ace..34e4edd2 100644 --- a/db/models/note_image.bob.go +++ b/db/models/note_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -36,6 +36,8 @@ type NoteImage struct { UUID uuid.UUID `db:"uuid,pk" ` R noteImageR `db:"-" ` + + C noteImageC `db:"-" ` } // NoteImageSlice is an alias for a slice of pointers to NoteImage. @@ -1412,6 +1414,162 @@ func (os NoteImageSlice) LoadNoteImageData(ctx context.Context, exec bob.Executo return nil } +// noteImageC is where relationship counts are stored. +type noteImageC struct { + NoteImageBreadcrumbs *int64 + NoteImageData *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *NoteImage) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "NoteImageBreadcrumbs": + o.C.NoteImageBreadcrumbs = &count + case "NoteImageData": + o.C.NoteImageData = &count + } + return nil +} + +type noteImageCountPreloader struct { + NoteImageBreadcrumbs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + NoteImageData func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildNoteImageCountPreloader() noteImageCountPreloader { + return noteImageCountPreloader{ + NoteImageBreadcrumbs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*NoteImage]("NoteImageBreadcrumbs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = NoteImages.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(NoteImageBreadcrumbs.Name()), + sm.Where(psql.Quote(NoteImageBreadcrumbs.Alias(), "note_image_version").EQ(psql.Quote(parent, "version"))), + sm.Where(psql.Quote(NoteImageBreadcrumbs.Alias(), "note_image_uuid").EQ(psql.Quote(parent, "uuid"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + NoteImageData: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*NoteImage]("NoteImageData", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = NoteImages.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(NoteImageData.Name()), + sm.Where(psql.Quote(NoteImageData.Alias(), "note_image_version").EQ(psql.Quote(parent, "version"))), + sm.Where(psql.Quote(NoteImageData.Alias(), "note_image_uuid").EQ(psql.Quote(parent, "uuid"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type noteImageCountThenLoader[Q orm.Loadable] struct { + NoteImageBreadcrumbs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + NoteImageData func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildNoteImageCountThenLoader[Q orm.Loadable]() noteImageCountThenLoader[Q] { + type NoteImageBreadcrumbsCountInterface interface { + LoadCountNoteImageBreadcrumbs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type NoteImageDataCountInterface interface { + LoadCountNoteImageData(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return noteImageCountThenLoader[Q]{ + NoteImageBreadcrumbs: countThenLoadBuilder[Q]( + "NoteImageBreadcrumbs", + func(ctx context.Context, exec bob.Executor, retrieved NoteImageBreadcrumbsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountNoteImageBreadcrumbs(ctx, exec, mods...) + }, + ), + NoteImageData: countThenLoadBuilder[Q]( + "NoteImageData", + func(ctx context.Context, exec bob.Executor, retrieved NoteImageDataCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountNoteImageData(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountNoteImageBreadcrumbs loads the count of NoteImageBreadcrumbs into the C struct +func (o *NoteImage) LoadCountNoteImageBreadcrumbs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.NoteImageBreadcrumbs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.NoteImageBreadcrumbs = &count + return nil +} + +// LoadCountNoteImageBreadcrumbs loads the count of NoteImageBreadcrumbs for a slice +func (os NoteImageSlice) LoadCountNoteImageBreadcrumbs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountNoteImageBreadcrumbs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountNoteImageData loads the count of NoteImageData into the C struct +func (o *NoteImage) LoadCountNoteImageData(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.NoteImageData(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.NoteImageData = &count + return nil +} + +// LoadCountNoteImageData loads the count of NoteImageData for a slice +func (os NoteImageSlice) LoadCountNoteImageData(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountNoteImageData(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + type noteImageJoins[Q dialect.Joinable] struct { typ string CreatorUser modAs[Q, userColumns] diff --git a/db/models/note_image_breadcrumb.bob.go b/db/models/note_image_breadcrumb.bob.go index eee5ba78..be232187 100644 --- a/db/models/note_image_breadcrumb.bob.go +++ b/db/models/note_image_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/note_image_data.bob.go b/db/models/note_image_data.bob.go index 7adfe9b2..2eb3f729 100644 --- a/db/models/note_image_data.bob.go +++ b/db/models/note_image_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/notification.bob.go b/db/models/notification.bob.go index 5456125b..ee150774 100644 --- a/db/models/notification.bob.go +++ b/db/models/notification.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/oauth_token.bob.go b/db/models/oauth_token.bob.go index cd7ff27d..9b8c7156 100644 --- a/db/models/oauth_token.bob.go +++ b/db/models/oauth_token.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/organization.bob.go b/db/models/organization.bob.go index f9bfcc28..256a8f90 100644 --- a/db/models/organization.bob.go +++ b/db/models/organization.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -32,6 +32,8 @@ type Organization struct { FieldseekerURL null.Val[string] `db:"fieldseeker_url" ` R organizationR `db:"-" ` + + C organizationC `db:"-" ` } // OrganizationSlice is an alias for a slice of pointers to Organization. @@ -6182,6 +6184,1990 @@ func (os OrganizationSlice) LoadUser(ctx context.Context, exec bob.Executor, mod return nil } +// organizationC is where relationship counts are stored. +type organizationC struct { + Containerrelates *int64 + Fieldscoutinglogs *int64 + Habitatrelates *int64 + Inspectionsamples *int64 + Inspectionsampledetails *int64 + Linelocations *int64 + Locationtrackings *int64 + Mosquitoinspections *int64 + Pointlocations *int64 + Polygonlocations *int64 + Pools *int64 + Pooldetails *int64 + Proposedtreatmentareas *int64 + Qamosquitoinspections *int64 + Rodentlocations *int64 + Samplecollections *int64 + Samplelocations *int64 + Servicerequests *int64 + Speciesabundances *int64 + Stormdrains *int64 + Timecards *int64 + Trapdata *int64 + Traplocations *int64 + Treatments *int64 + Treatmentareas *int64 + Zones *int64 + Zones2s *int64 + FieldseekerSyncs *int64 + H3Aggregations *int64 + NoteAudios *int64 + NoteImages *int64 + User *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *Organization) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "Containerrelates": + o.C.Containerrelates = &count + case "Fieldscoutinglogs": + o.C.Fieldscoutinglogs = &count + case "Habitatrelates": + o.C.Habitatrelates = &count + case "Inspectionsamples": + o.C.Inspectionsamples = &count + case "Inspectionsampledetails": + o.C.Inspectionsampledetails = &count + case "Linelocations": + o.C.Linelocations = &count + case "Locationtrackings": + o.C.Locationtrackings = &count + case "Mosquitoinspections": + o.C.Mosquitoinspections = &count + case "Pointlocations": + o.C.Pointlocations = &count + case "Polygonlocations": + o.C.Polygonlocations = &count + case "Pools": + o.C.Pools = &count + case "Pooldetails": + o.C.Pooldetails = &count + case "Proposedtreatmentareas": + o.C.Proposedtreatmentareas = &count + case "Qamosquitoinspections": + o.C.Qamosquitoinspections = &count + case "Rodentlocations": + o.C.Rodentlocations = &count + case "Samplecollections": + o.C.Samplecollections = &count + case "Samplelocations": + o.C.Samplelocations = &count + case "Servicerequests": + o.C.Servicerequests = &count + case "Speciesabundances": + o.C.Speciesabundances = &count + case "Stormdrains": + o.C.Stormdrains = &count + case "Timecards": + o.C.Timecards = &count + case "Trapdata": + o.C.Trapdata = &count + case "Traplocations": + o.C.Traplocations = &count + case "Treatments": + o.C.Treatments = &count + case "Treatmentareas": + o.C.Treatmentareas = &count + case "Zones": + o.C.Zones = &count + case "Zones2s": + o.C.Zones2s = &count + case "FieldseekerSyncs": + o.C.FieldseekerSyncs = &count + case "H3Aggregations": + o.C.H3Aggregations = &count + case "NoteAudios": + o.C.NoteAudios = &count + case "NoteImages": + o.C.NoteImages = &count + case "User": + o.C.User = &count + } + return nil +} + +type organizationCountPreloader struct { + Containerrelates func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Fieldscoutinglogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Habitatrelates func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Inspectionsamples func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Inspectionsampledetails func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Linelocations func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Locationtrackings func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Mosquitoinspections func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Pointlocations func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Polygonlocations func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Pools func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Pooldetails func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Proposedtreatmentareas func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Qamosquitoinspections func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Rodentlocations func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Samplecollections func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Samplelocations func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Servicerequests func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Speciesabundances func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Stormdrains func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Timecards func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Trapdata func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Traplocations func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Treatments func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Treatmentareas func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Zones func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Zones2s func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + FieldseekerSyncs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + H3Aggregations func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + NoteAudios func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + NoteImages func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + User func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildOrganizationCountPreloader() organizationCountPreloader { + return organizationCountPreloader{ + Containerrelates: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Containerrelates", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerContainerrelates.Name()), + sm.Where(psql.Quote(FieldseekerContainerrelates.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Fieldscoutinglogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Fieldscoutinglogs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerFieldscoutinglogs.Name()), + sm.Where(psql.Quote(FieldseekerFieldscoutinglogs.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Habitatrelates: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Habitatrelates", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerHabitatrelates.Name()), + sm.Where(psql.Quote(FieldseekerHabitatrelates.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Inspectionsamples: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Inspectionsamples", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerInspectionsamples.Name()), + sm.Where(psql.Quote(FieldseekerInspectionsamples.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Inspectionsampledetails: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Inspectionsampledetails", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerInspectionsampledetails.Name()), + sm.Where(psql.Quote(FieldseekerInspectionsampledetails.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Linelocations: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Linelocations", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerLinelocations.Name()), + sm.Where(psql.Quote(FieldseekerLinelocations.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Locationtrackings: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Locationtrackings", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerLocationtrackings.Name()), + sm.Where(psql.Quote(FieldseekerLocationtrackings.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Mosquitoinspections: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Mosquitoinspections", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerMosquitoinspections.Name()), + sm.Where(psql.Quote(FieldseekerMosquitoinspections.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Pointlocations: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Pointlocations", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerPointlocations.Name()), + sm.Where(psql.Quote(FieldseekerPointlocations.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Polygonlocations: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Polygonlocations", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerPolygonlocations.Name()), + sm.Where(psql.Quote(FieldseekerPolygonlocations.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Pools: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Pools", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerPools.Name()), + sm.Where(psql.Quote(FieldseekerPools.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Pooldetails: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Pooldetails", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerPooldetails.Name()), + sm.Where(psql.Quote(FieldseekerPooldetails.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Proposedtreatmentareas: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Proposedtreatmentareas", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerProposedtreatmentareas.Name()), + sm.Where(psql.Quote(FieldseekerProposedtreatmentareas.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Qamosquitoinspections: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Qamosquitoinspections", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerQamosquitoinspections.Name()), + sm.Where(psql.Quote(FieldseekerQamosquitoinspections.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Rodentlocations: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Rodentlocations", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerRodentlocations.Name()), + sm.Where(psql.Quote(FieldseekerRodentlocations.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Samplecollections: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Samplecollections", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerSamplecollections.Name()), + sm.Where(psql.Quote(FieldseekerSamplecollections.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Samplelocations: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Samplelocations", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerSamplelocations.Name()), + sm.Where(psql.Quote(FieldseekerSamplelocations.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Servicerequests: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Servicerequests", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerServicerequests.Name()), + sm.Where(psql.Quote(FieldseekerServicerequests.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Speciesabundances: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Speciesabundances", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerSpeciesabundances.Name()), + sm.Where(psql.Quote(FieldseekerSpeciesabundances.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Stormdrains: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Stormdrains", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerStormdrains.Name()), + sm.Where(psql.Quote(FieldseekerStormdrains.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Timecards: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Timecards", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerTimecards.Name()), + sm.Where(psql.Quote(FieldseekerTimecards.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Trapdata: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Trapdata", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerTrapdata.Name()), + sm.Where(psql.Quote(FieldseekerTrapdata.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Traplocations: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Traplocations", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerTraplocations.Name()), + sm.Where(psql.Quote(FieldseekerTraplocations.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Treatments: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Treatments", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerTreatments.Name()), + sm.Where(psql.Quote(FieldseekerTreatments.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Treatmentareas: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Treatmentareas", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerTreatmentareas.Name()), + sm.Where(psql.Quote(FieldseekerTreatmentareas.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Zones: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Zones", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerZones.Name()), + sm.Where(psql.Quote(FieldseekerZones.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Zones2s: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Zones2s", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerZones2s.Name()), + sm.Where(psql.Quote(FieldseekerZones2s.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + FieldseekerSyncs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("FieldseekerSyncs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FieldseekerSyncs.Name()), + sm.Where(psql.Quote(FieldseekerSyncs.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + H3Aggregations: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("H3Aggregations", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(H3Aggregations.Name()), + sm.Where(psql.Quote(H3Aggregations.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + NoteAudios: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("NoteAudios", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(NoteAudios.Name()), + sm.Where(psql.Quote(NoteAudios.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + NoteImages: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("NoteImages", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(NoteImages.Name()), + sm.Where(psql.Quote(NoteImages.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + User: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("User", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(Users.Name()), + sm.Where(psql.Quote(Users.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type organizationCountThenLoader[Q orm.Loadable] struct { + Containerrelates func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Fieldscoutinglogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Habitatrelates func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Inspectionsamples func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Inspectionsampledetails func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Linelocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Locationtrackings func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Mosquitoinspections func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Pointlocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Polygonlocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Pools func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Pooldetails func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Proposedtreatmentareas func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Qamosquitoinspections func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Rodentlocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Samplecollections func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Samplelocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Servicerequests func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Speciesabundances func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Stormdrains func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Timecards func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Trapdata func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Traplocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Treatments func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Treatmentareas func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Zones func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Zones2s func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + FieldseekerSyncs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + H3Aggregations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + NoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + NoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + User func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildOrganizationCountThenLoader[Q orm.Loadable]() organizationCountThenLoader[Q] { + type ContainerrelatesCountInterface interface { + LoadCountContainerrelates(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type FieldscoutinglogsCountInterface interface { + LoadCountFieldscoutinglogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type HabitatrelatesCountInterface interface { + LoadCountHabitatrelates(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type InspectionsamplesCountInterface interface { + LoadCountInspectionsamples(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type InspectionsampledetailsCountInterface interface { + LoadCountInspectionsampledetails(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type LinelocationsCountInterface interface { + LoadCountLinelocations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type LocationtrackingsCountInterface interface { + LoadCountLocationtrackings(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type MosquitoinspectionsCountInterface interface { + LoadCountMosquitoinspections(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PointlocationsCountInterface interface { + LoadCountPointlocations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PolygonlocationsCountInterface interface { + LoadCountPolygonlocations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PoolsCountInterface interface { + LoadCountPools(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PooldetailsCountInterface interface { + LoadCountPooldetails(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type ProposedtreatmentareasCountInterface interface { + LoadCountProposedtreatmentareas(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type QamosquitoinspectionsCountInterface interface { + LoadCountQamosquitoinspections(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type RodentlocationsCountInterface interface { + LoadCountRodentlocations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type SamplecollectionsCountInterface interface { + LoadCountSamplecollections(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type SamplelocationsCountInterface interface { + LoadCountSamplelocations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type ServicerequestsCountInterface interface { + LoadCountServicerequests(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type SpeciesabundancesCountInterface interface { + LoadCountSpeciesabundances(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type StormdrainsCountInterface interface { + LoadCountStormdrains(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type TimecardsCountInterface interface { + LoadCountTimecards(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type TrapdataCountInterface interface { + LoadCountTrapdata(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type TraplocationsCountInterface interface { + LoadCountTraplocations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type TreatmentsCountInterface interface { + LoadCountTreatments(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type TreatmentareasCountInterface interface { + LoadCountTreatmentareas(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type ZonesCountInterface interface { + LoadCountZones(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type Zones2sCountInterface interface { + LoadCountZones2s(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type FieldseekerSyncsCountInterface interface { + LoadCountFieldseekerSyncs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type H3AggregationsCountInterface interface { + LoadCountH3Aggregations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type NoteAudiosCountInterface interface { + LoadCountNoteAudios(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type NoteImagesCountInterface interface { + LoadCountNoteImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type UserCountInterface interface { + LoadCountUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return organizationCountThenLoader[Q]{ + Containerrelates: countThenLoadBuilder[Q]( + "Containerrelates", + func(ctx context.Context, exec bob.Executor, retrieved ContainerrelatesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountContainerrelates(ctx, exec, mods...) + }, + ), + Fieldscoutinglogs: countThenLoadBuilder[Q]( + "Fieldscoutinglogs", + func(ctx context.Context, exec bob.Executor, retrieved FieldscoutinglogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountFieldscoutinglogs(ctx, exec, mods...) + }, + ), + Habitatrelates: countThenLoadBuilder[Q]( + "Habitatrelates", + func(ctx context.Context, exec bob.Executor, retrieved HabitatrelatesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountHabitatrelates(ctx, exec, mods...) + }, + ), + Inspectionsamples: countThenLoadBuilder[Q]( + "Inspectionsamples", + func(ctx context.Context, exec bob.Executor, retrieved InspectionsamplesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountInspectionsamples(ctx, exec, mods...) + }, + ), + Inspectionsampledetails: countThenLoadBuilder[Q]( + "Inspectionsampledetails", + func(ctx context.Context, exec bob.Executor, retrieved InspectionsampledetailsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountInspectionsampledetails(ctx, exec, mods...) + }, + ), + Linelocations: countThenLoadBuilder[Q]( + "Linelocations", + func(ctx context.Context, exec bob.Executor, retrieved LinelocationsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountLinelocations(ctx, exec, mods...) + }, + ), + Locationtrackings: countThenLoadBuilder[Q]( + "Locationtrackings", + func(ctx context.Context, exec bob.Executor, retrieved LocationtrackingsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountLocationtrackings(ctx, exec, mods...) + }, + ), + Mosquitoinspections: countThenLoadBuilder[Q]( + "Mosquitoinspections", + func(ctx context.Context, exec bob.Executor, retrieved MosquitoinspectionsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountMosquitoinspections(ctx, exec, mods...) + }, + ), + Pointlocations: countThenLoadBuilder[Q]( + "Pointlocations", + func(ctx context.Context, exec bob.Executor, retrieved PointlocationsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountPointlocations(ctx, exec, mods...) + }, + ), + Polygonlocations: countThenLoadBuilder[Q]( + "Polygonlocations", + func(ctx context.Context, exec bob.Executor, retrieved PolygonlocationsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountPolygonlocations(ctx, exec, mods...) + }, + ), + Pools: countThenLoadBuilder[Q]( + "Pools", + func(ctx context.Context, exec bob.Executor, retrieved PoolsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountPools(ctx, exec, mods...) + }, + ), + Pooldetails: countThenLoadBuilder[Q]( + "Pooldetails", + func(ctx context.Context, exec bob.Executor, retrieved PooldetailsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountPooldetails(ctx, exec, mods...) + }, + ), + Proposedtreatmentareas: countThenLoadBuilder[Q]( + "Proposedtreatmentareas", + func(ctx context.Context, exec bob.Executor, retrieved ProposedtreatmentareasCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountProposedtreatmentareas(ctx, exec, mods...) + }, + ), + Qamosquitoinspections: countThenLoadBuilder[Q]( + "Qamosquitoinspections", + func(ctx context.Context, exec bob.Executor, retrieved QamosquitoinspectionsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountQamosquitoinspections(ctx, exec, mods...) + }, + ), + Rodentlocations: countThenLoadBuilder[Q]( + "Rodentlocations", + func(ctx context.Context, exec bob.Executor, retrieved RodentlocationsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountRodentlocations(ctx, exec, mods...) + }, + ), + Samplecollections: countThenLoadBuilder[Q]( + "Samplecollections", + func(ctx context.Context, exec bob.Executor, retrieved SamplecollectionsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountSamplecollections(ctx, exec, mods...) + }, + ), + Samplelocations: countThenLoadBuilder[Q]( + "Samplelocations", + func(ctx context.Context, exec bob.Executor, retrieved SamplelocationsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountSamplelocations(ctx, exec, mods...) + }, + ), + Servicerequests: countThenLoadBuilder[Q]( + "Servicerequests", + func(ctx context.Context, exec bob.Executor, retrieved ServicerequestsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountServicerequests(ctx, exec, mods...) + }, + ), + Speciesabundances: countThenLoadBuilder[Q]( + "Speciesabundances", + func(ctx context.Context, exec bob.Executor, retrieved SpeciesabundancesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountSpeciesabundances(ctx, exec, mods...) + }, + ), + Stormdrains: countThenLoadBuilder[Q]( + "Stormdrains", + func(ctx context.Context, exec bob.Executor, retrieved StormdrainsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountStormdrains(ctx, exec, mods...) + }, + ), + Timecards: countThenLoadBuilder[Q]( + "Timecards", + func(ctx context.Context, exec bob.Executor, retrieved TimecardsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountTimecards(ctx, exec, mods...) + }, + ), + Trapdata: countThenLoadBuilder[Q]( + "Trapdata", + func(ctx context.Context, exec bob.Executor, retrieved TrapdataCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountTrapdata(ctx, exec, mods...) + }, + ), + Traplocations: countThenLoadBuilder[Q]( + "Traplocations", + func(ctx context.Context, exec bob.Executor, retrieved TraplocationsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountTraplocations(ctx, exec, mods...) + }, + ), + Treatments: countThenLoadBuilder[Q]( + "Treatments", + func(ctx context.Context, exec bob.Executor, retrieved TreatmentsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountTreatments(ctx, exec, mods...) + }, + ), + Treatmentareas: countThenLoadBuilder[Q]( + "Treatmentareas", + func(ctx context.Context, exec bob.Executor, retrieved TreatmentareasCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountTreatmentareas(ctx, exec, mods...) + }, + ), + Zones: countThenLoadBuilder[Q]( + "Zones", + func(ctx context.Context, exec bob.Executor, retrieved ZonesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountZones(ctx, exec, mods...) + }, + ), + Zones2s: countThenLoadBuilder[Q]( + "Zones2s", + func(ctx context.Context, exec bob.Executor, retrieved Zones2sCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountZones2s(ctx, exec, mods...) + }, + ), + FieldseekerSyncs: countThenLoadBuilder[Q]( + "FieldseekerSyncs", + func(ctx context.Context, exec bob.Executor, retrieved FieldseekerSyncsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountFieldseekerSyncs(ctx, exec, mods...) + }, + ), + H3Aggregations: countThenLoadBuilder[Q]( + "H3Aggregations", + func(ctx context.Context, exec bob.Executor, retrieved H3AggregationsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountH3Aggregations(ctx, exec, mods...) + }, + ), + NoteAudios: countThenLoadBuilder[Q]( + "NoteAudios", + func(ctx context.Context, exec bob.Executor, retrieved NoteAudiosCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountNoteAudios(ctx, exec, mods...) + }, + ), + NoteImages: countThenLoadBuilder[Q]( + "NoteImages", + func(ctx context.Context, exec bob.Executor, retrieved NoteImagesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountNoteImages(ctx, exec, mods...) + }, + ), + User: countThenLoadBuilder[Q]( + "User", + func(ctx context.Context, exec bob.Executor, retrieved UserCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountUser(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountContainerrelates loads the count of Containerrelates into the C struct +func (o *Organization) LoadCountContainerrelates(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Containerrelates(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Containerrelates = &count + return nil +} + +// LoadCountContainerrelates loads the count of Containerrelates for a slice +func (os OrganizationSlice) LoadCountContainerrelates(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountContainerrelates(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountFieldscoutinglogs loads the count of Fieldscoutinglogs into the C struct +func (o *Organization) LoadCountFieldscoutinglogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Fieldscoutinglogs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Fieldscoutinglogs = &count + return nil +} + +// LoadCountFieldscoutinglogs loads the count of Fieldscoutinglogs for a slice +func (os OrganizationSlice) LoadCountFieldscoutinglogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountFieldscoutinglogs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountHabitatrelates loads the count of Habitatrelates into the C struct +func (o *Organization) LoadCountHabitatrelates(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Habitatrelates(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Habitatrelates = &count + return nil +} + +// LoadCountHabitatrelates loads the count of Habitatrelates for a slice +func (os OrganizationSlice) LoadCountHabitatrelates(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountHabitatrelates(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountInspectionsamples loads the count of Inspectionsamples into the C struct +func (o *Organization) LoadCountInspectionsamples(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Inspectionsamples(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Inspectionsamples = &count + return nil +} + +// LoadCountInspectionsamples loads the count of Inspectionsamples for a slice +func (os OrganizationSlice) LoadCountInspectionsamples(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountInspectionsamples(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountInspectionsampledetails loads the count of Inspectionsampledetails into the C struct +func (o *Organization) LoadCountInspectionsampledetails(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Inspectionsampledetails(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Inspectionsampledetails = &count + return nil +} + +// LoadCountInspectionsampledetails loads the count of Inspectionsampledetails for a slice +func (os OrganizationSlice) LoadCountInspectionsampledetails(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountInspectionsampledetails(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountLinelocations loads the count of Linelocations into the C struct +func (o *Organization) LoadCountLinelocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Linelocations(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Linelocations = &count + return nil +} + +// LoadCountLinelocations loads the count of Linelocations for a slice +func (os OrganizationSlice) LoadCountLinelocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountLinelocations(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountLocationtrackings loads the count of Locationtrackings into the C struct +func (o *Organization) LoadCountLocationtrackings(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Locationtrackings(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Locationtrackings = &count + return nil +} + +// LoadCountLocationtrackings loads the count of Locationtrackings for a slice +func (os OrganizationSlice) LoadCountLocationtrackings(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountLocationtrackings(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountMosquitoinspections loads the count of Mosquitoinspections into the C struct +func (o *Organization) LoadCountMosquitoinspections(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Mosquitoinspections(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Mosquitoinspections = &count + return nil +} + +// LoadCountMosquitoinspections loads the count of Mosquitoinspections for a slice +func (os OrganizationSlice) LoadCountMosquitoinspections(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountMosquitoinspections(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountPointlocations loads the count of Pointlocations into the C struct +func (o *Organization) LoadCountPointlocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Pointlocations(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Pointlocations = &count + return nil +} + +// LoadCountPointlocations loads the count of Pointlocations for a slice +func (os OrganizationSlice) LoadCountPointlocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountPointlocations(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountPolygonlocations loads the count of Polygonlocations into the C struct +func (o *Organization) LoadCountPolygonlocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Polygonlocations(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Polygonlocations = &count + return nil +} + +// LoadCountPolygonlocations loads the count of Polygonlocations for a slice +func (os OrganizationSlice) LoadCountPolygonlocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountPolygonlocations(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountPools loads the count of Pools into the C struct +func (o *Organization) LoadCountPools(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Pools(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Pools = &count + return nil +} + +// LoadCountPools loads the count of Pools for a slice +func (os OrganizationSlice) LoadCountPools(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountPools(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountPooldetails loads the count of Pooldetails into the C struct +func (o *Organization) LoadCountPooldetails(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Pooldetails(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Pooldetails = &count + return nil +} + +// LoadCountPooldetails loads the count of Pooldetails for a slice +func (os OrganizationSlice) LoadCountPooldetails(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountPooldetails(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountProposedtreatmentareas loads the count of Proposedtreatmentareas into the C struct +func (o *Organization) LoadCountProposedtreatmentareas(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Proposedtreatmentareas(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Proposedtreatmentareas = &count + return nil +} + +// LoadCountProposedtreatmentareas loads the count of Proposedtreatmentareas for a slice +func (os OrganizationSlice) LoadCountProposedtreatmentareas(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountProposedtreatmentareas(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountQamosquitoinspections loads the count of Qamosquitoinspections into the C struct +func (o *Organization) LoadCountQamosquitoinspections(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Qamosquitoinspections(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Qamosquitoinspections = &count + return nil +} + +// LoadCountQamosquitoinspections loads the count of Qamosquitoinspections for a slice +func (os OrganizationSlice) LoadCountQamosquitoinspections(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountQamosquitoinspections(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountRodentlocations loads the count of Rodentlocations into the C struct +func (o *Organization) LoadCountRodentlocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Rodentlocations(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Rodentlocations = &count + return nil +} + +// LoadCountRodentlocations loads the count of Rodentlocations for a slice +func (os OrganizationSlice) LoadCountRodentlocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountRodentlocations(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountSamplecollections loads the count of Samplecollections into the C struct +func (o *Organization) LoadCountSamplecollections(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Samplecollections(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Samplecollections = &count + return nil +} + +// LoadCountSamplecollections loads the count of Samplecollections for a slice +func (os OrganizationSlice) LoadCountSamplecollections(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountSamplecollections(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountSamplelocations loads the count of Samplelocations into the C struct +func (o *Organization) LoadCountSamplelocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Samplelocations(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Samplelocations = &count + return nil +} + +// LoadCountSamplelocations loads the count of Samplelocations for a slice +func (os OrganizationSlice) LoadCountSamplelocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountSamplelocations(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountServicerequests loads the count of Servicerequests into the C struct +func (o *Organization) LoadCountServicerequests(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Servicerequests(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Servicerequests = &count + return nil +} + +// LoadCountServicerequests loads the count of Servicerequests for a slice +func (os OrganizationSlice) LoadCountServicerequests(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountServicerequests(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountSpeciesabundances loads the count of Speciesabundances into the C struct +func (o *Organization) LoadCountSpeciesabundances(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Speciesabundances(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Speciesabundances = &count + return nil +} + +// LoadCountSpeciesabundances loads the count of Speciesabundances for a slice +func (os OrganizationSlice) LoadCountSpeciesabundances(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountSpeciesabundances(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountStormdrains loads the count of Stormdrains into the C struct +func (o *Organization) LoadCountStormdrains(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Stormdrains(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Stormdrains = &count + return nil +} + +// LoadCountStormdrains loads the count of Stormdrains for a slice +func (os OrganizationSlice) LoadCountStormdrains(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountStormdrains(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountTimecards loads the count of Timecards into the C struct +func (o *Organization) LoadCountTimecards(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Timecards(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Timecards = &count + return nil +} + +// LoadCountTimecards loads the count of Timecards for a slice +func (os OrganizationSlice) LoadCountTimecards(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountTimecards(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountTrapdata loads the count of Trapdata into the C struct +func (o *Organization) LoadCountTrapdata(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Trapdata(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Trapdata = &count + return nil +} + +// LoadCountTrapdata loads the count of Trapdata for a slice +func (os OrganizationSlice) LoadCountTrapdata(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountTrapdata(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountTraplocations loads the count of Traplocations into the C struct +func (o *Organization) LoadCountTraplocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Traplocations(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Traplocations = &count + return nil +} + +// LoadCountTraplocations loads the count of Traplocations for a slice +func (os OrganizationSlice) LoadCountTraplocations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountTraplocations(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountTreatments loads the count of Treatments into the C struct +func (o *Organization) LoadCountTreatments(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Treatments(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Treatments = &count + return nil +} + +// LoadCountTreatments loads the count of Treatments for a slice +func (os OrganizationSlice) LoadCountTreatments(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountTreatments(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountTreatmentareas loads the count of Treatmentareas into the C struct +func (o *Organization) LoadCountTreatmentareas(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Treatmentareas(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Treatmentareas = &count + return nil +} + +// LoadCountTreatmentareas loads the count of Treatmentareas for a slice +func (os OrganizationSlice) LoadCountTreatmentareas(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountTreatmentareas(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountZones loads the count of Zones into the C struct +func (o *Organization) LoadCountZones(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Zones(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Zones = &count + return nil +} + +// LoadCountZones loads the count of Zones for a slice +func (os OrganizationSlice) LoadCountZones(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountZones(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountZones2s loads the count of Zones2s into the C struct +func (o *Organization) LoadCountZones2s(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Zones2s(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Zones2s = &count + return nil +} + +// LoadCountZones2s loads the count of Zones2s for a slice +func (os OrganizationSlice) LoadCountZones2s(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountZones2s(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountFieldseekerSyncs loads the count of FieldseekerSyncs into the C struct +func (o *Organization) LoadCountFieldseekerSyncs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.FieldseekerSyncs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.FieldseekerSyncs = &count + return nil +} + +// LoadCountFieldseekerSyncs loads the count of FieldseekerSyncs for a slice +func (os OrganizationSlice) LoadCountFieldseekerSyncs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountFieldseekerSyncs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountH3Aggregations loads the count of H3Aggregations into the C struct +func (o *Organization) LoadCountH3Aggregations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.H3Aggregations(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.H3Aggregations = &count + return nil +} + +// LoadCountH3Aggregations loads the count of H3Aggregations for a slice +func (os OrganizationSlice) LoadCountH3Aggregations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountH3Aggregations(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountNoteAudios loads the count of NoteAudios into the C struct +func (o *Organization) LoadCountNoteAudios(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.NoteAudios(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.NoteAudios = &count + return nil +} + +// LoadCountNoteAudios loads the count of NoteAudios for a slice +func (os OrganizationSlice) LoadCountNoteAudios(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountNoteAudios(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountNoteImages loads the count of NoteImages into the C struct +func (o *Organization) LoadCountNoteImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.NoteImages(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.NoteImages = &count + return nil +} + +// LoadCountNoteImages loads the count of NoteImages for a slice +func (os OrganizationSlice) LoadCountNoteImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountNoteImages(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountUser loads the count of User into the C struct +func (o *Organization) LoadCountUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.User(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.User = &count + return nil +} + +// LoadCountUser loads the count of User for a slice +func (os OrganizationSlice) LoadCountUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountUser(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + type organizationJoins[Q dialect.Joinable] struct { typ string Containerrelates modAs[Q, fieldseekerContainerrelateColumns] diff --git a/db/models/publicreport.nuisance.bob.go b/db/models/publicreport.nuisance.bob.go index 78ce3f54..031d4bf0 100644 --- a/db/models/publicreport.nuisance.bob.go +++ b/db/models/publicreport.nuisance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/publicreport.pool.bob.go b/db/models/publicreport.pool.bob.go index 805c0576..350af417 100644 --- a/db/models/publicreport.pool.bob.go +++ b/db/models/publicreport.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -59,6 +59,8 @@ type PublicreportPool struct { Status enums.PublicreportReportstatustype `db:"status" ` R publicreportPoolR `db:"-" ` + + C publicreportPoolC `db:"-" ` } // PublicreportPoolSlice is an alias for a slice of pointers to PublicreportPool. @@ -1291,6 +1293,99 @@ func (os PublicreportPoolSlice) LoadPoolPhotos(ctx context.Context, exec bob.Exe return nil } +// publicreportPoolC is where relationship counts are stored. +type publicreportPoolC struct { + PoolPhotos *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *PublicreportPool) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "PoolPhotos": + o.C.PoolPhotos = &count + } + return nil +} + +type publicreportPoolCountPreloader struct { + PoolPhotos func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildPublicreportPoolCountPreloader() publicreportPoolCountPreloader { + return publicreportPoolCountPreloader{ + PoolPhotos: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*PublicreportPool]("PoolPhotos", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = PublicreportPools.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(PublicreportPoolPhotos.Name()), + sm.Where(psql.Quote(PublicreportPoolPhotos.Alias(), "pool_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type publicreportPoolCountThenLoader[Q orm.Loadable] struct { + PoolPhotos func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildPublicreportPoolCountThenLoader[Q orm.Loadable]() publicreportPoolCountThenLoader[Q] { + type PoolPhotosCountInterface interface { + LoadCountPoolPhotos(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return publicreportPoolCountThenLoader[Q]{ + PoolPhotos: countThenLoadBuilder[Q]( + "PoolPhotos", + func(ctx context.Context, exec bob.Executor, retrieved PoolPhotosCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountPoolPhotos(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountPoolPhotos loads the count of PoolPhotos into the C struct +func (o *PublicreportPool) LoadCountPoolPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.PoolPhotos(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.PoolPhotos = &count + return nil +} + +// LoadCountPoolPhotos loads the count of PoolPhotos for a slice +func (os PublicreportPoolSlice) LoadCountPoolPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountPoolPhotos(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + type publicreportPoolJoins[Q dialect.Joinable] struct { typ string PoolPhotos modAs[Q, publicreportPoolPhotoColumns] diff --git a/db/models/publicreport.pool_photo.bob.go b/db/models/publicreport.pool_photo.bob.go index 11a67efb..1240a0e3 100644 --- a/db/models/publicreport.pool_photo.bob.go +++ b/db/models/publicreport.pool_photo.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/publicreport.quick.bob.go b/db/models/publicreport.quick.bob.go index 1a97365f..5cec372c 100644 --- a/db/models/publicreport.quick.bob.go +++ b/db/models/publicreport.quick.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -39,6 +39,8 @@ type PublicreportQuick struct { Status enums.PublicreportReportstatustype `db:"status" ` R publicreportQuickR `db:"-" ` + + C publicreportQuickC `db:"-" ` } // PublicreportQuickSlice is an alias for a slice of pointers to PublicreportQuick. @@ -791,6 +793,99 @@ func (os PublicreportQuickSlice) LoadQuickPhotos(ctx context.Context, exec bob.E return nil } +// publicreportQuickC is where relationship counts are stored. +type publicreportQuickC struct { + QuickPhotos *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *PublicreportQuick) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "QuickPhotos": + o.C.QuickPhotos = &count + } + return nil +} + +type publicreportQuickCountPreloader struct { + QuickPhotos func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildPublicreportQuickCountPreloader() publicreportQuickCountPreloader { + return publicreportQuickCountPreloader{ + QuickPhotos: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*PublicreportQuick]("QuickPhotos", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = PublicreportQuicks.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(PublicreportQuickPhotos.Name()), + sm.Where(psql.Quote(PublicreportQuickPhotos.Alias(), "quick_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type publicreportQuickCountThenLoader[Q orm.Loadable] struct { + QuickPhotos func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildPublicreportQuickCountThenLoader[Q orm.Loadable]() publicreportQuickCountThenLoader[Q] { + type QuickPhotosCountInterface interface { + LoadCountQuickPhotos(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return publicreportQuickCountThenLoader[Q]{ + QuickPhotos: countThenLoadBuilder[Q]( + "QuickPhotos", + func(ctx context.Context, exec bob.Executor, retrieved QuickPhotosCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountQuickPhotos(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountQuickPhotos loads the count of QuickPhotos into the C struct +func (o *PublicreportQuick) LoadCountQuickPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.QuickPhotos(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.QuickPhotos = &count + return nil +} + +// LoadCountQuickPhotos loads the count of QuickPhotos for a slice +func (os PublicreportQuickSlice) LoadCountQuickPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountQuickPhotos(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + type publicreportQuickJoins[Q dialect.Joinable] struct { typ string QuickPhotos modAs[Q, publicreportQuickPhotoColumns] diff --git a/db/models/publicreport.quick_photo.bob.go b/db/models/publicreport.quick_photo.bob.go index 0dc99964..9f5171d4 100644 --- a/db/models/publicreport.quick_photo.bob.go +++ b/db/models/publicreport.quick_photo.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/publicreport.report_location.bob.go b/db/models/publicreport.report_location.bob.go index 7882ba24..1bbb46e9 100644 --- a/db/models/publicreport.report_location.bob.go +++ b/db/models/publicreport.report_location.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/raster_columns.bob.go b/db/models/raster_columns.bob.go index f53ea1e0..a97fc1cb 100644 --- a/db/models/raster_columns.bob.go +++ b/db/models/raster_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/raster_overviews.bob.go b/db/models/raster_overviews.bob.go index b21dcf47..bc5115b3 100644 --- a/db/models/raster_overviews.bob.go +++ b/db/models/raster_overviews.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/sessions.bob.go b/db/models/sessions.bob.go index 7e0d3f29..4899065f 100644 --- a/db/models/sessions.bob.go +++ b/db/models/sessions.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/spatial_ref_sys.bob.go b/db/models/spatial_ref_sys.bob.go index 20b5bfcb..651d4ad9 100644 --- a/db/models/spatial_ref_sys.bob.go +++ b/db/models/spatial_ref_sys.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models diff --git a/db/models/user_.bob.go b/db/models/user_.bob.go index 7063a0e8..2b8e80c2 100644 --- a/db/models/user_.bob.go +++ b/db/models/user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -41,6 +41,8 @@ type User struct { PasswordHash string `db:"password_hash" ` R userR `db:"-" ` + + C userC `db:"-" ` } // UserSlice is an alias for a slice of pointers to User. @@ -55,6 +57,7 @@ type UsersQuery = *psql.ViewQuery[*User, UserSlice] // userR is where relationships are stored. type userR struct { + PublicUserUser ArcgisUserSlice // arcgis.user_.user__public_user_id_fkey CreatorNoteAudios NoteAudioSlice // note_audio.note_audio_creator_id_fkey DeletorNoteAudios NoteAudioSlice // note_audio.note_audio_deletor_id_fkey CreatorNoteImages NoteImageSlice // note_image.note_image_creator_id_fkey @@ -608,6 +611,30 @@ func (o UserSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { return nil } +// PublicUserUser starts a query for related objects on arcgis.user_ +func (o *User) PublicUserUser(mods ...bob.Mod[*dialect.SelectQuery]) ArcgisUsersQuery { + return ArcgisUsers.Query(append(mods, + sm.Where(ArcgisUsers.Columns.PublicUserID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os UserSlice) PublicUserUser(mods ...bob.Mod[*dialect.SelectQuery]) ArcgisUsersQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return ArcgisUsers.Query(append(mods, + sm.Where(psql.Group(ArcgisUsers.Columns.PublicUserID).OP("IN", PKArgExpr)), + )...) +} + // CreatorNoteAudios starts a query for related objects on note_audio func (o *User) CreatorNoteAudios(mods ...bob.Mod[*dialect.SelectQuery]) NoteAudiosQuery { return NoteAudios.Query(append(mods, @@ -776,6 +803,74 @@ func (os UserSlice) Organization(mods ...bob.Mod[*dialect.SelectQuery]) Organiza )...) } +func insertUserPublicUserUser0(ctx context.Context, exec bob.Executor, arcgisusers1 []*ArcgisUserSetter, user0 *User) (ArcgisUserSlice, error) { + for i := range arcgisusers1 { + arcgisusers1[i].PublicUserID = omit.From(user0.ID) + } + + ret, err := ArcgisUsers.Insert(bob.ToMods(arcgisusers1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertUserPublicUserUser0: %w", err) + } + + return ret, nil +} + +func attachUserPublicUserUser0(ctx context.Context, exec bob.Executor, count int, arcgisusers1 ArcgisUserSlice, user0 *User) (ArcgisUserSlice, error) { + setter := &ArcgisUserSetter{ + PublicUserID: omit.From(user0.ID), + } + + err := arcgisusers1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachUserPublicUserUser0: %w", err) + } + + return arcgisusers1, nil +} + +func (user0 *User) InsertPublicUserUser(ctx context.Context, exec bob.Executor, related ...*ArcgisUserSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + arcgisusers1, err := insertUserPublicUserUser0(ctx, exec, related, user0) + if err != nil { + return err + } + + user0.R.PublicUserUser = append(user0.R.PublicUserUser, arcgisusers1...) + + for _, rel := range arcgisusers1 { + rel.R.PublicUserUser = user0 + } + return nil +} + +func (user0 *User) AttachPublicUserUser(ctx context.Context, exec bob.Executor, related ...*ArcgisUser) error { + if len(related) == 0 { + return nil + } + + var err error + arcgisusers1 := ArcgisUserSlice(related) + + _, err = attachUserPublicUserUser0(ctx, exec, len(related), arcgisusers1, user0) + if err != nil { + return err + } + + user0.R.PublicUserUser = append(user0.R.PublicUserUser, arcgisusers1...) + + for _, rel := range related { + rel.R.PublicUserUser = user0 + } + + return nil +} + func insertUserCreatorNoteAudios0(ctx context.Context, exec bob.Executor, noteAudios1 []*NoteAudioSetter, user0 *User) (NoteAudioSlice, error) { for i := range noteAudios1 { noteAudios1[i].CreatorID = omit.From(user0.ID) @@ -1274,6 +1369,20 @@ func (o *User) Preload(name string, retrieved any) error { } switch name { + case "PublicUserUser": + rels, ok := retrieved.(ArcgisUserSlice) + if !ok { + return fmt.Errorf("user cannot load %T as %q", retrieved, name) + } + + o.R.PublicUserUser = rels + + for _, rel := range rels { + if rel != nil { + rel.R.PublicUserUser = o + } + } + return nil case "CreatorNoteAudios": rels, ok := retrieved.(NoteAudioSlice) if !ok { @@ -1398,6 +1507,7 @@ func buildUserPreloader() userPreloader { } type userThenLoader[Q orm.Loadable] struct { + PublicUserUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CreatorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] DeletorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CreatorNoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] @@ -1408,6 +1518,9 @@ type userThenLoader[Q orm.Loadable] struct { } func buildUserThenLoader[Q orm.Loadable]() userThenLoader[Q] { + type PublicUserUserLoadInterface interface { + LoadPublicUserUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type CreatorNoteAudiosLoadInterface interface { LoadCreatorNoteAudios(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -1431,6 +1544,12 @@ func buildUserThenLoader[Q orm.Loadable]() userThenLoader[Q] { } return userThenLoader[Q]{ + PublicUserUser: thenLoadBuilder[Q]( + "PublicUserUser", + func(ctx context.Context, exec bob.Executor, retrieved PublicUserUserLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadPublicUserUser(ctx, exec, mods...) + }, + ), CreatorNoteAudios: thenLoadBuilder[Q]( "CreatorNoteAudios", func(ctx context.Context, exec bob.Executor, retrieved CreatorNoteAudiosLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -1476,6 +1595,67 @@ func buildUserThenLoader[Q orm.Loadable]() userThenLoader[Q] { } } +// LoadPublicUserUser loads the user's PublicUserUser into the .R struct +func (o *User) LoadPublicUserUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.PublicUserUser = nil + + related, err := o.PublicUserUser(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.PublicUserUser = o + } + + o.R.PublicUserUser = related + return nil +} + +// LoadPublicUserUser loads the user's PublicUserUser into the .R struct +func (os UserSlice) LoadPublicUserUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + arcgisusers, err := os.PublicUserUser(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.PublicUserUser = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range arcgisusers { + + if !(o.ID == rel.PublicUserID) { + continue + } + + rel.R.PublicUserUser = o + + o.R.PublicUserUser = append(o.R.PublicUserUser, rel) + } + } + + return nil +} + // LoadCreatorNoteAudios loads the user's CreatorNoteAudios into the .R struct func (o *User) LoadCreatorNoteAudios(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -1900,8 +2080,468 @@ func (os UserSlice) LoadOrganization(ctx context.Context, exec bob.Executor, mod return nil } +// userC is where relationship counts are stored. +type userC struct { + PublicUserUser *int64 + CreatorNoteAudios *int64 + DeletorNoteAudios *int64 + CreatorNoteImages *int64 + DeletorNoteImages *int64 + UserNotifications *int64 + UserOauthTokens *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *User) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "PublicUserUser": + o.C.PublicUserUser = &count + case "CreatorNoteAudios": + o.C.CreatorNoteAudios = &count + case "DeletorNoteAudios": + o.C.DeletorNoteAudios = &count + case "CreatorNoteImages": + o.C.CreatorNoteImages = &count + case "DeletorNoteImages": + o.C.DeletorNoteImages = &count + case "UserNotifications": + o.C.UserNotifications = &count + case "UserOauthTokens": + o.C.UserOauthTokens = &count + } + return nil +} + +type userCountPreloader struct { + PublicUserUser func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + CreatorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + DeletorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + CreatorNoteImages func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + DeletorNoteImages func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + UserNotifications func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + UserOauthTokens func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildUserCountPreloader() userCountPreloader { + return userCountPreloader{ + PublicUserUser: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*User]("PublicUserUser", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Users.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(ArcgisUsers.Name()), + sm.Where(psql.Quote(ArcgisUsers.Alias(), "public_user_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + CreatorNoteAudios: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*User]("CreatorNoteAudios", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Users.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(NoteAudios.Name()), + sm.Where(psql.Quote(NoteAudios.Alias(), "creator_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + DeletorNoteAudios: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*User]("DeletorNoteAudios", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Users.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(NoteAudios.Name()), + sm.Where(psql.Quote(NoteAudios.Alias(), "deletor_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + CreatorNoteImages: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*User]("CreatorNoteImages", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Users.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(NoteImages.Name()), + sm.Where(psql.Quote(NoteImages.Alias(), "creator_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + DeletorNoteImages: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*User]("DeletorNoteImages", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Users.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(NoteImages.Name()), + sm.Where(psql.Quote(NoteImages.Alias(), "deletor_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + UserNotifications: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*User]("UserNotifications", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Users.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(Notifications.Name()), + sm.Where(psql.Quote(Notifications.Alias(), "user_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + UserOauthTokens: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*User]("UserOauthTokens", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Users.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(OauthTokens.Name()), + sm.Where(psql.Quote(OauthTokens.Alias(), "user_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type userCountThenLoader[Q orm.Loadable] struct { + PublicUserUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + DeletorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorNoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + DeletorNoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + UserNotifications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + UserOauthTokens func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildUserCountThenLoader[Q orm.Loadable]() userCountThenLoader[Q] { + type PublicUserUserCountInterface interface { + LoadCountPublicUserUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type CreatorNoteAudiosCountInterface interface { + LoadCountCreatorNoteAudios(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type DeletorNoteAudiosCountInterface interface { + LoadCountDeletorNoteAudios(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type CreatorNoteImagesCountInterface interface { + LoadCountCreatorNoteImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type DeletorNoteImagesCountInterface interface { + LoadCountDeletorNoteImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type UserNotificationsCountInterface interface { + LoadCountUserNotifications(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type UserOauthTokensCountInterface interface { + LoadCountUserOauthTokens(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return userCountThenLoader[Q]{ + PublicUserUser: countThenLoadBuilder[Q]( + "PublicUserUser", + func(ctx context.Context, exec bob.Executor, retrieved PublicUserUserCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountPublicUserUser(ctx, exec, mods...) + }, + ), + CreatorNoteAudios: countThenLoadBuilder[Q]( + "CreatorNoteAudios", + func(ctx context.Context, exec bob.Executor, retrieved CreatorNoteAudiosCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountCreatorNoteAudios(ctx, exec, mods...) + }, + ), + DeletorNoteAudios: countThenLoadBuilder[Q]( + "DeletorNoteAudios", + func(ctx context.Context, exec bob.Executor, retrieved DeletorNoteAudiosCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountDeletorNoteAudios(ctx, exec, mods...) + }, + ), + CreatorNoteImages: countThenLoadBuilder[Q]( + "CreatorNoteImages", + func(ctx context.Context, exec bob.Executor, retrieved CreatorNoteImagesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountCreatorNoteImages(ctx, exec, mods...) + }, + ), + DeletorNoteImages: countThenLoadBuilder[Q]( + "DeletorNoteImages", + func(ctx context.Context, exec bob.Executor, retrieved DeletorNoteImagesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountDeletorNoteImages(ctx, exec, mods...) + }, + ), + UserNotifications: countThenLoadBuilder[Q]( + "UserNotifications", + func(ctx context.Context, exec bob.Executor, retrieved UserNotificationsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountUserNotifications(ctx, exec, mods...) + }, + ), + UserOauthTokens: countThenLoadBuilder[Q]( + "UserOauthTokens", + func(ctx context.Context, exec bob.Executor, retrieved UserOauthTokensCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountUserOauthTokens(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountPublicUserUser loads the count of PublicUserUser into the C struct +func (o *User) LoadCountPublicUserUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.PublicUserUser(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.PublicUserUser = &count + return nil +} + +// LoadCountPublicUserUser loads the count of PublicUserUser for a slice +func (os UserSlice) LoadCountPublicUserUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountPublicUserUser(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountCreatorNoteAudios loads the count of CreatorNoteAudios into the C struct +func (o *User) LoadCountCreatorNoteAudios(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.CreatorNoteAudios(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.CreatorNoteAudios = &count + return nil +} + +// LoadCountCreatorNoteAudios loads the count of CreatorNoteAudios for a slice +func (os UserSlice) LoadCountCreatorNoteAudios(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountCreatorNoteAudios(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountDeletorNoteAudios loads the count of DeletorNoteAudios into the C struct +func (o *User) LoadCountDeletorNoteAudios(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.DeletorNoteAudios(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.DeletorNoteAudios = &count + return nil +} + +// LoadCountDeletorNoteAudios loads the count of DeletorNoteAudios for a slice +func (os UserSlice) LoadCountDeletorNoteAudios(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountDeletorNoteAudios(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountCreatorNoteImages loads the count of CreatorNoteImages into the C struct +func (o *User) LoadCountCreatorNoteImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.CreatorNoteImages(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.CreatorNoteImages = &count + return nil +} + +// LoadCountCreatorNoteImages loads the count of CreatorNoteImages for a slice +func (os UserSlice) LoadCountCreatorNoteImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountCreatorNoteImages(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountDeletorNoteImages loads the count of DeletorNoteImages into the C struct +func (o *User) LoadCountDeletorNoteImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.DeletorNoteImages(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.DeletorNoteImages = &count + return nil +} + +// LoadCountDeletorNoteImages loads the count of DeletorNoteImages for a slice +func (os UserSlice) LoadCountDeletorNoteImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountDeletorNoteImages(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountUserNotifications loads the count of UserNotifications into the C struct +func (o *User) LoadCountUserNotifications(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.UserNotifications(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.UserNotifications = &count + return nil +} + +// LoadCountUserNotifications loads the count of UserNotifications for a slice +func (os UserSlice) LoadCountUserNotifications(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountUserNotifications(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountUserOauthTokens loads the count of UserOauthTokens into the C struct +func (o *User) LoadCountUserOauthTokens(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.UserOauthTokens(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.UserOauthTokens = &count + return nil +} + +// LoadCountUserOauthTokens loads the count of UserOauthTokens for a slice +func (os UserSlice) LoadCountUserOauthTokens(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountUserOauthTokens(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + type userJoins[Q dialect.Joinable] struct { typ string + PublicUserUser modAs[Q, arcgisuserColumns] CreatorNoteAudios modAs[Q, noteAudioColumns] DeletorNoteAudios modAs[Q, noteAudioColumns] CreatorNoteImages modAs[Q, noteImageColumns] @@ -1918,6 +2558,20 @@ func (j userJoins[Q]) aliasedAs(alias string) userJoins[Q] { func buildUserJoins[Q dialect.Joinable](cols userColumns, typ string) userJoins[Q] { return userJoins[Q]{ typ: typ, + PublicUserUser: modAs[Q, arcgisuserColumns]{ + c: ArcgisUsers.Columns, + f: func(to arcgisuserColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, ArcgisUsers.Name().As(to.Alias())).On( + to.PublicUserID.EQ(cols.ID), + )) + } + + return mods + }, + }, CreatorNoteAudios: modAs[Q, noteAudioColumns]{ c: NoteAudios.Columns, f: func(to noteAudioColumns) bob.Mod[Q] { diff --git a/db/sql/oauth_by_user_id.bob.go b/db/sql/oauth_by_user_id.bob.go deleted file mode 100644 index f2b6eb5d..00000000 --- a/db/sql/oauth_by_user_id.bob.go +++ /dev/null @@ -1,111 +0,0 @@ -// 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" - "time" - - "github.com/aarondl/opt/null" - "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 oauth_by_user_id.bob.sql -var formattedQueries_oauth_by_user_id string - -var oauthTokenByUserIdSQL = formattedQueries_oauth_by_user_id[191:729] - -type OauthTokenByUserIdQuery = orm.ModQuery[*dialect.SelectQuery, oauthTokenByUserId, OauthTokenByUserIdRow, []OauthTokenByUserIdRow, oauthTokenByUserIdTransformer] - -func OauthTokenByUserId(UserID int32) *OauthTokenByUserIdQuery { - var expressionTypArgs oauthTokenByUserId - - expressionTypArgs.UserID = psql.Arg(UserID) - - return &OauthTokenByUserIdQuery{ - Query: orm.Query[oauthTokenByUserId, OauthTokenByUserIdRow, []OauthTokenByUserIdRow, oauthTokenByUserIdTransformer]{ - ExecQuery: orm.ExecQuery[oauthTokenByUserId]{ - BaseQuery: bob.BaseQuery[oauthTokenByUserId]{ - Expression: expressionTypArgs, - Dialect: dialect.Dialect, - QueryType: bob.QueryTypeSelect, - }, - }, - Scanner: func(context.Context, []string) (func(*scan.Row) (any, error), func(any) (OauthTokenByUserIdRow, error)) { - return func(row *scan.Row) (any, error) { - var t OauthTokenByUserIdRow - row.ScheduleScanByIndex(0, &t.ID) - row.ScheduleScanByIndex(1, &t.AccessToken) - row.ScheduleScanByIndex(2, &t.AccessTokenExpires) - row.ScheduleScanByIndex(3, &t.RefreshToken) - row.ScheduleScanByIndex(4, &t.Username) - row.ScheduleScanByIndex(5, &t.UserID) - row.ScheduleScanByIndex(6, &t.ArcgisID) - row.ScheduleScanByIndex(7, &t.ArcgisLicenseTypeID) - row.ScheduleScanByIndex(8, &t.RefreshTokenExpires) - row.ScheduleScanByIndex(9, &t.InvalidatedAt) - return &t, nil - }, func(v any) (OauthTokenByUserIdRow, error) { - return *(v.(*OauthTokenByUserIdRow)), nil - } - }, - }, - Mod: bob.ModFunc[*dialect.SelectQuery](func(q *dialect.SelectQuery) { - q.AppendSelect(expressionTypArgs.subExpr(7, 501)) - q.SetTable(expressionTypArgs.subExpr(507, 518)) - q.AppendWhere(expressionTypArgs.subExpr(526, 538)) - }), - } -} - -type OauthTokenByUserIdRow = struct { - ID int32 `db:"id"` - AccessToken string `db:"access_token"` - AccessTokenExpires time.Time `db:"access_token_expires"` - RefreshToken string `db:"refresh_token"` - Username string `db:"username"` - UserID int32 `db:"user_id"` - ArcgisID null.Val[string] `db:"arcgis_id"` - ArcgisLicenseTypeID null.Val[string] `db:"arcgis_license_type_id"` - RefreshTokenExpires time.Time `db:"refresh_token_expires"` - InvalidatedAt null.Val[time.Time] `db:"invalidated_at"` -} - -type oauthTokenByUserIdTransformer = bob.SliceTransformer[OauthTokenByUserIdRow, []OauthTokenByUserIdRow] - -type oauthTokenByUserId struct { - UserID bob.Expression -} - -func (o oauthTokenByUserId) args() iter.Seq[orm.ArgWithPosition] { - return func(yield func(arg orm.ArgWithPosition) bool) { - if !yield(orm.ArgWithPosition{ - Name: "userID", - Start: 536, - Stop: 538, - Expression: o.UserID, - }) { - return - } - } -} - -func (o oauthTokenByUserId) raw(from, to int) string { - return oauthTokenByUserIdSQL[from:to] -} - -func (o oauthTokenByUserId) subExpr(from, to int) bob.Expression { - return orm.ArgsToExpression(oauthTokenByUserIdSQL, from, to, o.args()) -} - -func (o oauthTokenByUserId) WriteSQL(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - return o.subExpr(0, len(oauthTokenByUserIdSQL)).WriteSQL(ctx, w, d, start) -} diff --git a/db/sql/oauth_by_user_id.bob.sql b/db/sql/oauth_by_user_id.bob.sql deleted file mode 100644 index ff32b8e7..00000000 --- a/db/sql/oauth_by_user_id.bob.sql +++ /dev/null @@ -1,6 +0,0 @@ --- 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. - --- OauthTokenByUserId -SELECT "oauth_token"."id" AS "id", "oauth_token"."access_token" AS "access_token", "oauth_token"."access_token_expires" AS "access_token_expires", "oauth_token"."refresh_token" AS "refresh_token", "oauth_token"."username" AS "username", "oauth_token"."user_id" AS "user_id", "oauth_token"."arcgis_id" AS "arcgis_id", "oauth_token"."arcgis_license_type_id" AS "arcgis_license_type_id", "oauth_token"."refresh_token_expires" AS "refresh_token_expires", "oauth_token"."invalidated_at" AS "invalidated_at" FROM oauth_token WHERE - user_id = $1; diff --git a/db/sql/oauth_by_user_id.sql b/db/sql/oauth_by_user_id.sql deleted file mode 100644 index 40a297b2..00000000 --- a/db/sql/oauth_by_user_id.sql +++ /dev/null @@ -1,3 +0,0 @@ --- OauthTokenByUserId -SELECT * FROM oauth_token WHERE - user_id = $1; diff --git a/db/sql/org_by_oauth_id.bob.go b/db/sql/org_by_oauth_id.bob.go index 690b6ab9..f9b35ccd 100644 --- a/db/sql/org_by_oauth_id.bob.go +++ b/db/sql/org_by_oauth_id.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -20,7 +20,7 @@ import ( //go:embed org_by_oauth_id.bob.sql var formattedQueries_org_by_oauth_id string -var orgByOauthIdSQL = formattedQueries_org_by_oauth_id[185:398] +var orgByOauthIdSQL = formattedQueries_org_by_oauth_id[150:363] type OrgByOauthIdQuery = orm.ModQuery[*dialect.SelectQuery, orgByOauthId, OrgByOauthIdRow, []OrgByOauthIdRow, orgByOauthIdTransformer] diff --git a/db/sql/org_by_oauth_id.bob.sql b/db/sql/org_by_oauth_id.bob.sql index c7be4696..f0945529 100644 --- a/db/sql/org_by_oauth_id.bob.sql +++ b/db/sql/org_by_oauth_id.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.1. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- OrgByOauthId diff --git a/db/sql/publicreport_publicid_table.bob.go b/db/sql/publicreport_publicid_table.bob.go index 9883cb40..f52b9b42 100644 --- a/db/sql/publicreport_publicid_table.bob.go +++ b/db/sql/publicreport_publicid_table.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -20,7 +20,7 @@ import ( //go:embed publicreport_publicid_table.bob.sql var formattedQueries_publicreport_publicid_table string -var publicreportIDTableSQL = formattedQueries_publicreport_publicid_table[192:659] +var publicreportIDTableSQL = formattedQueries_publicreport_publicid_table[157:624] type PublicreportIDTableQuery = orm.ModQuery[*dialect.SelectQuery, publicreportIDTable, PublicreportIDTableRow, []PublicreportIDTableRow, publicreportIDTableTransformer] diff --git a/db/sql/publicreport_publicid_table.bob.sql b/db/sql/publicreport_publicid_table.bob.sql index 8a6381d9..f9e1c4a6 100644 --- a/db/sql/publicreport_publicid_table.bob.sql +++ b/db/sql/publicreport_publicid_table.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.1. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- PublicreportIDTable diff --git a/db/sql/trapcount_by_location_id.bob.go b/db/sql/trapcount_by_location_id.bob.go index 70a39347..73475582 100644 --- a/db/sql/trapcount_by_location_id.bob.go +++ b/db/sql/trapcount_by_location_id.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -23,7 +23,7 @@ import ( //go:embed trapcount_by_location_id.bob.sql var formattedQueries_trapcount_by_location_id string -var trapCountByLocationIDSQL = formattedQueries_trapcount_by_location_id[194:644] +var trapCountByLocationIDSQL = formattedQueries_trapcount_by_location_id[159:609] type TrapCountByLocationIDQuery = orm.ModQuery[*dialect.SelectQuery, trapCountByLocationID, TrapCountByLocationIDRow, []TrapCountByLocationIDRow, trapCountByLocationIDTransformer] diff --git a/db/sql/trapcount_by_location_id.bob.sql b/db/sql/trapcount_by_location_id.bob.sql index 385e4c9e..6e76e700 100644 --- a/db/sql/trapcount_by_location_id.bob.sql +++ b/db/sql/trapcount_by_location_id.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.1. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- TrapCountByLocationID diff --git a/db/sql/trapdata_by_location_id_recent.bob.go b/db/sql/trapdata_by_location_id_recent.bob.go index 38db78f9..704f722c 100644 --- a/db/sql/trapdata_by_location_id_recent.bob.go +++ b/db/sql/trapdata_by_location_id_recent.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -22,7 +22,7 @@ import ( //go:embed trapdata_by_location_id_recent.bob.sql var formattedQueries_trapdata_by_location_id_recent string -var trapDataByLocationIDRecentSQL = formattedQueries_trapdata_by_location_id_recent[199:498] +var trapDataByLocationIDRecentSQL = formattedQueries_trapdata_by_location_id_recent[164:463] type TrapDataByLocationIDRecentQuery = orm.ModQuery[*dialect.SelectQuery, trapDataByLocationIDRecent, TrapDataByLocationIDRecentRow, []TrapDataByLocationIDRecentRow, trapDataByLocationIDRecentTransformer] diff --git a/db/sql/trapdata_by_location_id_recent.bob.sql b/db/sql/trapdata_by_location_id_recent.bob.sql index c3326b69..917e2ee6 100644 --- a/db/sql/trapdata_by_location_id_recent.bob.sql +++ b/db/sql/trapdata_by_location_id_recent.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.1. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- TrapDataByLocationIDRecent diff --git a/db/sql/traplocation_by_source_id.bob.go b/db/sql/traplocation_by_source_id.bob.go index 8f10bb55..d0b437e3 100644 --- a/db/sql/traplocation_by_source_id.bob.go +++ b/db/sql/traplocation_by_source_id.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -20,7 +20,7 @@ import ( //go:embed traplocation_by_source_id.bob.sql var formattedQueries_traplocation_by_source_id string -var trapLocationBySourceIDSQL = formattedQueries_traplocation_by_source_id[195:488] +var trapLocationBySourceIDSQL = formattedQueries_traplocation_by_source_id[160:453] type TrapLocationBySourceIDQuery = orm.ModQuery[*dialect.SelectQuery, trapLocationBySourceID, TrapLocationBySourceIDRow, []TrapLocationBySourceIDRow, trapLocationBySourceIDTransformer] @@ -51,11 +51,11 @@ func TrapLocationBySourceID(OrganizationID int32, Globalid uuid.UUID) *TrapLocat }, }, Mod: bob.ModFunc[*dialect.SelectQuery](func(q *dialect.SelectQuery) { - q.CombinedLimit.SetLimit(psql.Raw("4")) q.AppendSelect(expressionTypArgs.subExpr(9, 102)) q.SetTable(expressionTypArgs.subExpr(110, 178)) q.AppendWhere(expressionTypArgs.subExpr(187, 232)) q.CombinedOrder.AppendOrder(expressionTypArgs.subExpr(244, 285)) + q.CombinedLimit.SetLimit(expressionTypArgs.subExpr(292, 293)) }), } } diff --git a/db/sql/traplocation_by_source_id.bob.sql b/db/sql/traplocation_by_source_id.bob.sql index 9f788cef..b4610521 100644 --- a/db/sql/traplocation_by_source_id.bob.sql +++ b/db/sql/traplocation_by_source_id.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.1. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- TrapLocationBySourceID diff --git a/db/sql/update_oauth_org.bob.go b/db/sql/update_oauth_org.bob.go index 6d8776d5..7bb34302 100644 --- a/db/sql/update_oauth_org.bob.go +++ b/db/sql/update_oauth_org.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -18,7 +18,7 @@ import ( //go:embed update_oauth_org.bob.sql var formattedQueries_update_oauth_org string -var updateOauthTokenOrgSQL = formattedQueries_update_oauth_org[192:284] +var updateOauthTokenOrgSQL = formattedQueries_update_oauth_org[157:249] type UpdateOauthTokenOrgQuery = orm.ModExecQuery[*dialect.UpdateQuery, updateOauthTokenOrg] diff --git a/db/sql/update_oauth_org.bob.sql b/db/sql/update_oauth_org.bob.sql index 080766b8..64bdb6cb 100644 --- a/db/sql/update_oauth_org.bob.sql +++ b/db/sql/update_oauth_org.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.1. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- UpdateOauthTokenOrg diff --git a/db/sql/user_by_username.bob.go b/db/sql/user_by_username.bob.go index e3eef602..4ee9ec2f 100644 --- a/db/sql/user_by_username.bob.go +++ b/db/sql/user_by_username.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -22,7 +22,7 @@ import ( //go:embed user_by_username.bob.sql var formattedQueries_user_by_username string -var userByUsernameSQL = formattedQueries_user_by_username[187:808] +var userByUsernameSQL = formattedQueries_user_by_username[152:780] type UserByUsernameQuery = orm.ModQuery[*dialect.SelectQuery, userByUsername, UserByUsernameRow, []UserByUsernameRow, userByUsernameTransformer] @@ -63,8 +63,8 @@ func UserByUsername(Username string) *UserByUsernameQuery { }, Mod: bob.ModFunc[*dialect.SelectQuery](func(q *dialect.SelectQuery) { q.AppendSelect(expressionTypArgs.subExpr(7, 551)) - q.SetTable(expressionTypArgs.subExpr(557, 562)) - q.AppendWhere(expressionTypArgs.subExpr(570, 621)) + q.SetTable(expressionTypArgs.subExpr(557, 569)) + q.AppendWhere(expressionTypArgs.subExpr(577, 628)) }), } } @@ -94,8 +94,8 @@ func (o userByUsername) args() iter.Seq[orm.ArgWithPosition] { return func(yield func(arg orm.ArgWithPosition) bool) { if !yield(orm.ArgWithPosition{ Name: "username", - Start: 581, - Stop: 583, + Start: 588, + Stop: 590, Expression: o.Username, }) { return diff --git a/db/sql/user_by_username.bob.sql b/db/sql/user_by_username.bob.sql index 596ef88d..9526a44e 100644 --- a/db/sql/user_by_username.bob.sql +++ b/db/sql/user_by_username.bob.sql @@ -1,7 +1,7 @@ --- Code generated by BobGen psql v0.0.4-0.20260105020634-53e08d840e47+dirty. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.1. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- UserByUsername -SELECT "user_"."id" AS "id", "user_"."arcgis_access_token" AS "arcgis_access_token", "user_"."arcgis_license" AS "arcgis_license", "user_"."arcgis_refresh_token" AS "arcgis_refresh_token", "user_"."arcgis_refresh_token_expires" AS "arcgis_refresh_token_expires", "user_"."arcgis_role" AS "arcgis_role", "user_"."display_name" AS "display_name", "user_"."email" AS "email", "user_"."organization_id" AS "organization_id", "user_"."username" AS "username", "user_"."password_hash_type" AS "password_hash_type", "user_"."password_hash" AS "password_hash" FROM user_ WHERE +SELECT "user_"."id" AS "id", "user_"."arcgis_access_token" AS "arcgis_access_token", "user_"."arcgis_license" AS "arcgis_license", "user_"."arcgis_refresh_token" AS "arcgis_refresh_token", "user_"."arcgis_refresh_token_expires" AS "arcgis_refresh_token_expires", "user_"."arcgis_role" AS "arcgis_role", "user_"."display_name" AS "display_name", "user_"."email" AS "email", "user_"."organization_id" AS "organization_id", "user_"."username" AS "username", "user_"."password_hash_type" AS "password_hash_type", "user_"."password_hash" AS "password_hash" FROM public.user_ WHERE username = $1 AND password_hash_type = 'bcrypt-14'; diff --git a/db/sql/user_by_username.sql b/db/sql/user_by_username.sql index 64d96249..6de278cc 100644 --- a/db/sql/user_by_username.sql +++ b/db/sql/user_by_username.sql @@ -1,4 +1,4 @@ -- UserByUsername -SELECT * FROM user_ WHERE +SELECT * FROM public.user_ WHERE username = $1 AND password_hash_type = 'bcrypt-14'; From f50f3892f20b5ae459d07333b69868fdda09b02c Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 15 Jan 2026 19:32:42 +0000 Subject: [PATCH 0037/1453] Switch to trap counts instead of inspections on the dash Because we can show those on a map. --- sync/dash.go | 8 ++++---- sync/template/dashboard.html | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sync/dash.go b/sync/dash.go index 227f4b19..66e1f1f6 100644 --- a/sync/dash.go +++ b/sync/dash.go @@ -35,7 +35,7 @@ type Config struct { type ContextDashboard struct { Config Config - CountInspections int + CountTraps int CountMosquitoSources int CountServiceRequests int Geo template.JS @@ -202,9 +202,9 @@ func dashboard(ctx context.Context, w http.ResponseWriter, user *models.User) { lastSync = &sync.Created } is_syncing := background.IsSyncOngoing(org.ID) - inspectionCount, err := org.Mosquitoinspections().Count(ctx, db.PGInstance.BobDB) + trapCount, err := org.Traplocations().Count(ctx, db.PGInstance.BobDB) if err != nil { - respondError(w, "Failed to get inspection count", err, http.StatusInternalServerError) + respondError(w, "Failed to get trap count", err, http.StatusInternalServerError) return } sourceCount, err := org.Pointlocations().Count(ctx, db.PGInstance.BobDB) @@ -240,7 +240,7 @@ func dashboard(ctx context.Context, w http.ResponseWriter, user *models.User) { Config: Config{ URLTegola: config.URLTegola, }, - CountInspections: int(inspectionCount), + CountTraps: int(trapCount), CountMosquitoSources: int(sourceCount), CountServiceRequests: int(serviceCount), IsSyncOngoing: is_syncing, diff --git a/sync/template/dashboard.html b/sync/template/dashboard.html index 1479c335..3cfa0abd 100644 --- a/sync/template/dashboard.html +++ b/sync/template/dashboard.html @@ -249,11 +249,11 @@ body {
-
Inspections
+
Traps
{{ if .IsSyncOngoing }} -

{{ .CountInspections | bigNumber }}...?

+

{{ .CountTraps | bigNumber }}...?

{{ else }} -

{{ .CountInspections | bigNumber }}

+

{{ .CountTraps | bigNumber }}

{{ end }}
+

Traps

+ {{ if gt (len .Traps) 0}} +
+
+
+ + + + + + + + + + {{ range .Traps }} + + + + + + {{ end }} + +
IDActiveComments
{{.GlobalID|uuidShort}}{{.Active}}{{.Comments}}
+
+
+
+ {{ else }} +

No traps

+ {{ end }} +

Treatment History

diff --git a/sync/types.go b/sync/types.go index b8435495..a31b3712 100644 --- a/sync/types.go +++ b/sync/types.go @@ -28,14 +28,6 @@ type ComponentMap struct { type ContentAuthenticatedPlaceholder struct { User User } -type ContentCell struct { - BreedingSources []BreedingSourceSummary - CellBoundary h3.CellBoundary - Inspections []Inspection - MapData ComponentMap - Treatments []Treatment - User User -} type ContentMockURLs struct { Dispatch string DispatchResults string @@ -95,7 +87,7 @@ type Link struct { Title string } type Organization struct { - ID int + ID int Name string } type ServiceRequestSummary struct { @@ -104,9 +96,9 @@ type ServiceRequestSummary struct { Status string } type User struct { - DisplayName string - Initials string - Notifications []notification.Notification - Organization Organization - Username string + DisplayName string + Initials string + Notifications []notification.Notification + Organization Organization + Username string } diff --git a/sync/utils.go b/sync/utils.go index 45c0d23b..7cba5560 100644 --- a/sync/utils.go +++ b/sync/utils.go @@ -100,11 +100,11 @@ func contentForUser(ctx context.Context, user *models.User) (User, error) { DisplayName: user.DisplayName, Initials: extractInitials(user.DisplayName), Notifications: notifications, - Organization: Organization { - ID: int(org.ID), + Organization: Organization{ + ID: int(org.ID), Name: org.Name, }, - Username: user.Username, + Username: user.Username, }, nil } @@ -181,7 +181,7 @@ func trapsBySource(ctx context.Context, org *models.Organization, sourceID uuid. return nil, fmt.Errorf("Failed to query trap counts: %w", err) } - traps, err := toTemplateTraps(locations, trap_data, counts) + traps, err := toTemplateTrapsNearby(locations, trap_data, counts) if err != nil { return nil, fmt.Errorf("Failed to convert trap data: %w", err) } @@ -203,6 +203,24 @@ func treatmentsBySource(ctx context.Context, org *models.Organization, sourceID return toTemplateTreatment(rows) } +func trapsByCell(ctx context.Context, org *models.Organization, c h3.Cell) (results []Trap, err error) { + boundary, err := c.Boundary() + if err != nil { + return results, fmt.Errorf("Failed to get cell boundary: %w", err) + } + geom_query := gisStatement(boundary) + rows, err := org.Traplocations( + sm.Where( + psql.F("ST_Within", "geospatial", geom_query), + ), + sm.OrderBy("objectid"), + ).All(ctx, db.PGInstance.BobDB) + if err != nil { + return results, fmt.Errorf("Failed to query rows: %w", err) + } + return toTemplateTrap(rows) +} + func treatmentsByCell(ctx context.Context, org *models.Organization, c h3.Cell) ([]Treatment, error) { var results []Treatment boundary, err := c.Boundary() From e2549f03171d6daf01e226dd29cff12a1d8b1f9e Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 15 Jan 2026 22:12:35 +0000 Subject: [PATCH 0043/1453] Make trap page that shows collection information --- sync/dash.go | 72 ++++++++++++++++++++- sync/model_conversion.go | 74 +++++++++++++++++++--- sync/routes.go | 1 + sync/template/trap.html | 131 +++++++++++++++++++++++++++++++++++++++ sync/types.go | 10 --- sync/utils.go | 25 +++++++- 6 files changed, 293 insertions(+), 20 deletions(-) create mode 100644 sync/template/trap.html diff --git a/sync/dash.go b/sync/dash.go index 04b464ac..11858361 100644 --- a/sync/dash.go +++ b/sync/dash.go @@ -27,18 +27,34 @@ var ( districtT = buildTemplate("district", "base") settingsT = buildTemplate("settings", "authenticated") sourceT = buildTemplate("source", "authenticated") + trapT = buildTemplate("trap", "authenticated") ) type Config struct { URLTegola string } +type ContentSource struct { + Inspections []Inspection + MapData ComponentMap + Source *BreedingSourceDetail + Traps []TrapNearby + Treatments []Treatment + //TreatmentCadence TreatmentCadence + TreatmentModels []TreatmentModel + User User +} +type ContentTrap struct { + MapData ComponentMap + Trap Trap + User User +} type ContextCell struct { BreedingSources []BreedingSourceSummary CellBoundary h3.CellBoundary Inspections []Inspection MapData ComponentMap - Traps []Trap + Traps []TrapSummary Treatments []Treatment User User } @@ -133,6 +149,20 @@ func getSource(w http.ResponseWriter, r *http.Request, u *models.User) { source(w, r, u, globalid) } +func getTrap(w http.ResponseWriter, r *http.Request, u *models.User) { + globalid_s := chi.URLParam(r, "globalid") + if globalid_s == "" { + respondError(w, "No globalid provided", nil, http.StatusBadRequest) + return + } + globalid, err := uuid.Parse(globalid_s) + if err != nil { + respondError(w, "globalid is not a UUID", nil, http.StatusBadRequest) + return + } + trap(w, r, u, globalid) +} + func cell(ctx context.Context, w http.ResponseWriter, user *models.User, c int64) { org, err := user.Organization().One(ctx, db.PGInstance.BobDB) if err != nil { @@ -342,3 +372,43 @@ func source(w http.ResponseWriter, r *http.Request, user *models.User, id uuid.U htmlpage.RenderOrError(w, sourceT, data) } + +func trap(w http.ResponseWriter, r *http.Request, user *models.User, id uuid.UUID) { + org, err := user.Organization().One(r.Context(), db.PGInstance.BobDB) + if err != nil { + respondError(w, "Failed to get org", err, http.StatusInternalServerError) + return + } + userContent, err := contentForUser(r.Context(), user) + if err != nil { + respondError(w, "Failed to get user content", err, http.StatusInternalServerError) + return + } + t, err := trapByGlobalId(r.Context(), org, id) + if err != nil { + respondError(w, "Failed to get trap", err, http.StatusInternalServerError) + return + } + latlng, err := t.H3Cell.LatLng() + if err != nil { + respondError(w, "Failed to get latlng", err, http.StatusInternalServerError) + return + } + data := ContentTrap{ + MapData: ComponentMap{ + Center: latlng, + //GeoJSON: + MapboxToken: config.MapboxToken, + Markers: []MapMarker{ + MapMarker{ + LatLng: latlng, + }, + }, + Zoom: 13, + }, + Trap: t, + User: userContent, + } + + htmlpage.RenderOrError(w, trapT, data) +} diff --git a/sync/model_conversion.go b/sync/model_conversion.go index 31156274..9b4fa373 100644 --- a/sync/model_conversion.go +++ b/sync/model_conversion.go @@ -76,10 +76,13 @@ type BreedingSourceDetail struct { Comments string `json:"comments"` } -type TrapNearby struct { - Counts []*TrapCount - Distance string - ID uuid.UUID +type Trap struct { + Active bool + Comments string + Collections []TrapData + Description string + GlobalID uuid.UUID + H3Cell h3.Cell } type TrapCount struct { @@ -152,9 +155,18 @@ type TrapData struct { LastEditedDate *time.Time `json:"lastEditedDate"` LastEditedUser string `json:"lastEditedUser"` Comments string `json:"comments"` + + // Stuff I actually use + Count TrapCount } -type Trap struct { +type TrapNearby struct { + Counts []*TrapCount + Distance string + ID uuid.UUID +} + +type TrapSummary struct { Active bool Comments string Description string @@ -169,9 +181,57 @@ type Treatment struct { Product string } -func toTemplateTrap(traps models.FieldseekerTraplocationSlice) (results []Trap, err error) { +func toTemplateTrap(trap *models.FieldseekerTraplocation, trap_data []sql.TrapDataByLocationIDRecentRow, count_slice []sql.TrapCountByLocationIDRow) (result Trap, err error) { + log.Debug().Str("globalid", trap.Globalid.String()).Msg("Working on trap") + cell, err := h3utils.ToCell(trap.H3cell.MustGet()) + if err != nil { + return result, fmt.Errorf("Failed to convert h3 cell: %w", err) + } + + count_by_trapdata_id := make(map[uuid.UUID]TrapCount, 0) + for _, count := range count_slice { + count_by_trapdata_id[count.TrapdataGlobalid] = TrapCount{ + Ended: count.TrapdataEnddate.MustGet(), + Females: int(count.TotalFemales), + Males: int(count.TotalMales), + Total: int(count.Total), + } + } + + data_by_id := make(map[uuid.UUID]TrapData, 0) + for _, dt := range trap_data { + if dt.LocID != trap.Globalid { + return result, fmt.Errorf("Bad query") + } + log.Debug().Str("trapdata", dt.Globalid.String()).Msg("Aggregating trapdata") + count, ok := count_by_trapdata_id[dt.Globalid] + if !ok { + count = TrapCount{} + } + data_by_id[dt.Globalid] = TrapData{ + Count: count, + EndDateTime: &dt.Enddatetime, + GlobalID: dt.Globalid, + } + } + data := make([]TrapData, 0) + for _, v := range data_by_id { + data = append(data, v) + } + + return Trap{ + Active: toBool16Or(trap.Active, false), + Comments: trap.Comments.GetOr(""), + Collections: data, + Description: trap.Description.GetOr(""), + GlobalID: trap.Globalid, + H3Cell: cell, + }, nil +} + +func toTemplateTrapSummary(traps models.FieldseekerTraplocationSlice) (results []TrapSummary, err error) { for _, t := range traps { - results = append(results, Trap{ + results = append(results, TrapSummary{ Active: toBool16Or(t.Active, false), Comments: t.Comments.GetOr(""), Description: t.Description.GetOr(""), diff --git a/sync/routes.go b/sync/routes.go index 89cf6444..6fa8ae8d 100644 --- a/sync/routes.go +++ b/sync/routes.go @@ -66,6 +66,7 @@ func Router() chi.Router { r.Method("GET", "/cell/{cell}", auth.NewEnsureAuth(getCellDetails)) r.Method("GET", "/settings", auth.NewEnsureAuth(getSettings)) r.Method("GET", "/source/{globalid}", auth.NewEnsureAuth(getSource)) + r.Method("GET", "/trap/{globalid}", auth.NewEnsureAuth(getTrap)) htmlpage.AddStaticRoute(r, "/static") return r diff --git a/sync/template/trap.html b/sync/template/trap.html new file mode 100644 index 00000000..3f22a94e --- /dev/null +++ b/sync/template/trap.html @@ -0,0 +1,131 @@ +{{template "authenticated.html" .}} + +{{define "title"}}Dash{{end}} +{{define "extraheader"}} +{{template "map" .MapData}} + +{{end}} +{{define "content"}} +
+ +
+
+

Trap Detail

+
+
+ +
+
+
+
+
+
Trap ID: {{ .Trap.GlobalID }}
+ + + + + + + + + + + + + +
Active:{{ .Trap.Active }}
Comments:{{ .Trap.Comments }}
Description:{{ .Trap.Description }}
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ +
+
+

Trap Collections

+
+ + + + + + + + + + + + + {{ range .Trap.Collections }} + + + + + + + + {{ end }} + +
Collection DateCollection IDFemalesMaleTotal
{{ .EndDateTime|timeSince }}{{ .GlobalID }}{{ .Count.Females }}{{ .Count.Males }}{{ .Count.Total }}
+
+
+
+
+{{end}} diff --git a/sync/types.go b/sync/types.go index a31b3712..8306d280 100644 --- a/sync/types.go +++ b/sync/types.go @@ -65,16 +65,6 @@ type ContentSignin struct { InvalidCredentials bool } type ContentSignup struct{} -type ContentSource struct { - Inspections []Inspection - MapData ComponentMap - Source *BreedingSourceDetail - Traps []TrapNearby - Treatments []Treatment - //TreatmentCadence TreatmentCadence - TreatmentModels []TreatmentModel - User User -} type Inspection struct { Action string Date *time.Time diff --git a/sync/utils.go b/sync/utils.go index 7cba5560..94f2dcd7 100644 --- a/sync/utils.go +++ b/sync/utils.go @@ -203,7 +203,28 @@ func treatmentsBySource(ctx context.Context, org *models.Organization, sourceID return toTemplateTreatment(rows) } -func trapsByCell(ctx context.Context, org *models.Organization, c h3.Cell) (results []Trap, err error) { +func trapByGlobalId(ctx context.Context, org *models.Organization, id uuid.UUID) (result Trap, err error) { + row, err := org.Traplocations( + sm.Where(models.FieldseekerTraplocations.Columns.Globalid.EQ(psql.Arg(id))), + ).One(ctx, db.PGInstance.BobDB) + if err != nil { + return result, fmt.Errorf("Failed to get trap location: %w", err) + } + + trap_data, err := sql.TrapDataByLocationIDRecent(org.ID, []uuid.UUID{id}).All(ctx, db.PGInstance.BobDB) + if err != nil { + return result, fmt.Errorf("Failed to query trap data: %w", err) + } + + counts, err := sql.TrapCountByLocationID(org.ID, []uuid.UUID{id}).All(ctx, db.PGInstance.BobDB) + if err != nil { + return result, fmt.Errorf("Failed to query trap counts: %w", err) + } + + return toTemplateTrap(row, trap_data, counts) +} + +func trapsByCell(ctx context.Context, org *models.Organization, c h3.Cell) (results []TrapSummary, err error) { boundary, err := c.Boundary() if err != nil { return results, fmt.Errorf("Failed to get cell boundary: %w", err) @@ -218,7 +239,7 @@ func trapsByCell(ctx context.Context, org *models.Organization, c h3.Cell) (resu if err != nil { return results, fmt.Errorf("Failed to query rows: %w", err) } - return toTemplateTrap(rows) + return toTemplateTrapSummary(rows) } func treatmentsByCell(ctx context.Context, org *models.Organization, c h3.Cell) ([]Treatment, error) { From 0bb055b3915499b9335b85fd0db3d0e4f59b1e77 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 15 Jan 2026 22:15:29 +0000 Subject: [PATCH 0044/1453] Allow clicks on cells without sources --- htmlpage/static/js/map-aggregate.js | 32 ++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/htmlpage/static/js/map-aggregate.js b/htmlpage/static/js/map-aggregate.js index 11175360..52146995 100644 --- a/htmlpage/static/js/map-aggregate.js +++ b/htmlpage/static/js/map-aggregate.js @@ -132,7 +132,7 @@ class MapAggregate extends HTMLElement { 'fill-color': '#0dcaf0' } }); - map.addInteraction("tegola-click-interaction", { + map.addInteraction("tegola-click-mosquito-source", { type: "click", target: { layerId: "mosquito_source" }, handler: (e) => { @@ -147,6 +147,36 @@ class MapAggregate extends HTMLElement { })); } }); + map.addInteraction("tegola-click-service-request", { + type: "click", + target: { layerId: "service_request" }, + handler: (e) => { + const coordinates = e.feature.geometry.coordinates.slice(); + const properties = e.feature.properties; + this.dispatchEvent(new CustomEvent("cell-click", { + bubbles: true, + composed: true, // Allows event to cross shadow DOM boundary + detail: { + cell: properties.cell + } + })); + } + }); + map.addInteraction("tegola-click-trap", { + type: "click", + target: { layerId: "trap" }, + handler: (e) => { + const coordinates = e.feature.geometry.coordinates.slice(); + const properties = e.feature.properties; + this.dispatchEvent(new CustomEvent("cell-click", { + bubbles: true, + composed: true, // Allows event to cross shadow DOM boundary + detail: { + cell: properties.cell + } + })); + } + }); }); } From f6b5a1e580fc601f4eff8ba7386e15a702d80cce Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 15 Jan 2026 22:56:32 +0000 Subject: [PATCH 0045/1453] Add API to query district by GPS location --- api/api.go | 33 +++++++++++++++++++++++++++++++++ api/endpoint.go | 1 + api/types.go | 8 ++++++++ platform/district.go | 31 +++++++++++++++++++++++++++++++ sync/template/district.html | 13 +++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 platform/district.go diff --git a/api/api.go b/api/api.go index aff1bb40..2e0a268c 100644 --- a/api/api.go +++ b/api/api.go @@ -78,6 +78,39 @@ func apiAudioContentPost(w http.ResponseWriter, r *http.Request, u *models.User) w.WriteHeader(http.StatusOK) } +func apiGetDistrict(w http.ResponseWriter, r *http.Request) { + var latStr, lngStr string + err := r.ParseForm() + if err != nil { + render.Render(w, r, errRender(fmt.Errorf("Failed to parse GET form: %w", err))) + return + } else { + latStr = r.FormValue("lat") + lngStr = r.FormValue("lng") + } + lat, err := strconv.ParseFloat(latStr, 64) + if err != nil { + render.Render(w, r, errRender(fmt.Errorf("Failed to parse lat as float: %w", err))) + return + } + lng, err := strconv.ParseFloat(lngStr, 64) + if err != nil { + render.Render(w, r, errRender(fmt.Errorf("Failed to parse lng as float: %w", err))) + return + } + district, err := platform.DistrictForLocation(r.Context(), lng, lat) + if err != nil { + render.Render(w, r, errRender(fmt.Errorf("Failed to get district: %w", err))) + return + } + d := ResponseDistrict{ + Agency: district.Agency.GetOr(""), + } + if err := render.Render(w, r, d); err != nil { + render.Render(w, r, errRender(err)) + } +} + func handleClientIos(w http.ResponseWriter, r *http.Request, u *models.User) { var sinceStr string err := r.ParseForm() diff --git a/api/endpoint.go b/api/endpoint.go index 6a9a3bbe..fd86109f 100644 --- a/api/endpoint.go +++ b/api/endpoint.go @@ -20,6 +20,7 @@ func AddRoutes(r chi.Router) { r.Method("POST", "/image/{uuid}/content", auth.NewEnsureAuth(apiImageContentPost)) // Unauthenticated endpoints + r.Get("/district", apiGetDistrict) r.Get("/webhook/fieldseeker", webhookFieldseeker) r.Post("/webhook/fieldseeker", webhookFieldseeker) } diff --git a/api/types.go b/api/types.go index 9ce61554..b511b456 100644 --- a/api/types.go +++ b/api/types.go @@ -62,6 +62,10 @@ type NoteAudioPayload struct { Version int32 `json:"version"` } +type ResponseDistrict struct { + Agency string `json:"agency"` +} + type ResponseMosquitoSource struct { Access string `json:"access"` Active *bool `json:"active"` @@ -154,6 +158,10 @@ func NewResponseMosquitoInspections(inspections models.FieldseekerMosquitoinspec return results } +func (rd ResponseDistrict) Render(w http.ResponseWriter, r *http.Request) error { + return nil +} + func (rtd ResponseMosquitoSource) Render(w http.ResponseWriter, r *http.Request) error { return nil } diff --git a/platform/district.go b/platform/district.go new file mode 100644 index 00000000..fd3d76c7 --- /dev/null +++ b/platform/district.go @@ -0,0 +1,31 @@ +package platform + +import ( + "context" + "fmt" + + "github.com/Gleipnir-Technology/nidus-sync/db" + "github.com/Gleipnir-Technology/nidus-sync/db/models" + + "github.com/stephenafamo/bob/dialect/psql" + "github.com/stephenafamo/bob/dialect/psql/sm" +) + +func DistrictForLocation(ctx context.Context, lng float64, lat float64) (*models.District, error) { + rows, err := models.Districts.Query( + sm.Where( + psql.F("ST_Contains", psql.Raw("geom_4326"), psql.F("ST_SetSRID", psql.F("ST_MakePoint", psql.Arg(lng), psql.Arg(lat)), psql.Arg(4326))), + ), + ).All(ctx, db.PGInstance.BobDB) + if err != nil { + return nil, fmt.Errorf("failed to query district: %w", err) + } + switch len(rows) { + case 0: + return nil, nil + case 1: + return rows[0], nil + default: + return nil, nil + } +} diff --git a/sync/template/district.html b/sync/template/district.html index a75253cd..c9c6aaec 100644 --- a/sync/template/district.html +++ b/sync/template/district.html @@ -35,6 +35,18 @@ function setDistrictColors(map) { //console.log("using fallback district coloring"); } } + +async function fetchDistrict(lng, lat) { + try { + const url = `/api/district?lat=${lat}&lng=${lng}` + const response = await fetch(url); + const data = await response.json(); + console.log("district", data); + } catch (error) { + console.error('Error fetching geocoding suggestions:', error); + } +} + function onLoad() { console.log("Setting up the map..."); mapboxgl.accessToken = MAPBOX_ACCESS_TOKEN; @@ -118,6 +130,7 @@ function onLoad() { });*/ addressDisplay.show(l); + fetchDistrict(l.geometry.coordinates[0], l.geometry.coordinates[1]); }); } document.addEventListener("DOMContentLoaded", onLoad); From 9b5140f0c2649dbf748092c675647e7693445b7a Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 15 Jan 2026 23:19:31 +0000 Subject: [PATCH 0046/1453] Show full district details on location search --- api/api.go | 9 ++++- api/types.go | 5 ++- sync/template/district.html | 66 +++++++++++++++++++++++++++++++++---- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/api/api.go b/api/api.go index 2e0a268c..7e61ea0c 100644 --- a/api/api.go +++ b/api/api.go @@ -103,8 +103,15 @@ func apiGetDistrict(w http.ResponseWriter, r *http.Request) { render.Render(w, r, errRender(fmt.Errorf("Failed to get district: %w", err))) return } + if district == nil { + http.NotFound(w, r) + return + } d := ResponseDistrict{ - Agency: district.Agency.GetOr(""), + Agency: district.Agency.GetOr(""), + Manager: district.GeneralMG.GetOr(""), + Phone: district.Phone1.GetOr(""), + Website: district.Website.GetOr(""), } if err := render.Render(w, r, d); err != nil { render.Render(w, r, errRender(err)) diff --git a/api/types.go b/api/types.go index b511b456..1f23fdc9 100644 --- a/api/types.go +++ b/api/types.go @@ -63,7 +63,10 @@ type NoteAudioPayload struct { } type ResponseDistrict struct { - Agency string `json:"agency"` + Agency string `json:"agency"` + Manager string `json:"manager"` + Phone string `json:"phone"` + Website string `json:"website"` } type ResponseMosquitoSource struct { diff --git a/sync/template/district.html b/sync/template/district.html index c9c6aaec..b1cf612c 100644 --- a/sync/template/district.html +++ b/sync/template/district.html @@ -40,8 +40,23 @@ async function fetchDistrict(lng, lat) { try { const url = `/api/district?lat=${lat}&lng=${lng}` const response = await fetch(url); + var agencyEl = document.querySelector("#district-agency"); + var managerEl = document.querySelector("#district-manager"); + var phoneEl = document.querySelector("#district-phone"); + var websiteEl = document.querySelector("#district-website"); + if (response.status == 404) { + agencyEl.innerHTML = "no matching district"; + managerEl.innerHTML = "-"; + phoneEl.innerHTML = "-"; + websiteEl.innerHTML = "-"; + return + } const data = await response.json(); console.log("district", data); + agencyEl.innerHTML = data.agency; + managerEl.innerHTML = data.manager; + phoneEl.innerHTML = data.phone; + websiteEl.innerHTML = data.website; } catch (error) { console.error('Error fetching geocoding suggestions:', error); } @@ -117,17 +132,20 @@ function onLoad() { addressSuggestionEnable(address, suggestionsContainer);*/ const addressDisplay = document.querySelector("address-display"); const addressInput = document.querySelector("address-input"); - const mapComponent = document.querySelector("map-component"); addressInput.addEventListener("address-selected", (event) => { const l = event.detail.location; // Center map on selected address - //mapComponent.jumpTo(coordinates); + map.jumpTo({ + center: l.geometry.coordinates, + zoom: 14, + }); // Add marker for selected address - /*mapComponent.addMarker(coordinates, { - title: event.detail.address - });*/ + const marker = new mapboxgl.Marker({ + color: "#FF0000", + draggable: false + }).setLngLat(l.geometry.coordinates).addTo(map); addressDisplay.show(l); fetchDistrict(l.geometry.coordinates[0], l.geometry.coordinates[1]); @@ -139,6 +157,13 @@ document.addEventListener("DOMContentLoaded", onLoad); body { background-color: #f8f9fa; } +.detail-label { + font-size: 0.8rem; + text-transform: uppercase; + color: #6c757d; + margin-bottom: 2px; + font-weight: 600; +} .dashboard-container { padding: 20px 0; } @@ -225,8 +250,35 @@ body {
-
- +
+
+
+ +
+
+
+
+
District Details
+
+
+
Agency
+
-
+
+
+
Manager
+
-
+
+
+
Phone
+
-
+
+
+
Website
+
-
+
+
+
+
{{end}} From 684c42413191deb6d50d9ad483d97472ecefa9a9 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 16 Jan 2026 14:43:26 +0000 Subject: [PATCH 0047/1453] Move imported districts to its own schema, add ref from organization This will make it possible to assign reports to an organization --- db/bobgen.yaml | 1 + ...district.bob.go => import.district.bob.go} | 6 +- db/dberrors/organization.bob.go | 18 + ...district.bob.go => import.district.bob.go} | 40 +- db/dbinfo/organization.bob.go | 116 ++- db/factory/bobfactory_context.bob.go | 74 +- db/factory/bobfactory_main.bob.go | 120 ++-- ...district.bob.go => import.district.bob.go} | 678 ++++++++++-------- db/factory/organization.bob.go | 267 ++++++- db/migrations/00034_district.sql | 6 + db/models/bob_joins.bob.go | 2 + db/models/bob_loaders.bob.go | 4 + db/models/bob_where.bob.go | 6 +- ...district.bob.go => import.district.bob.go} | 420 ++++++++--- db/models/organization.bob.go | 501 +++++++++---- platform/district.go | 4 +- 16 files changed, 1561 insertions(+), 702 deletions(-) rename db/dberrors/{district.bob.go => import.district.bob.go} (75%) rename db/dbinfo/{district.bob.go => import.district.bob.go} (89%) rename db/factory/{district.bob.go => import.district.bob.go} (50%) create mode 100644 db/migrations/00034_district.sql rename db/models/{district.bob.go => import.district.bob.go} (60%) diff --git a/db/bobgen.yaml b/db/bobgen.yaml index b9f4c8d7..47f2dcc1 100644 --- a/db/bobgen.yaml +++ b/db/bobgen.yaml @@ -13,6 +13,7 @@ no_tests: true psql: schemas: - "arcgis" + - "import" - "public" - "publicreport" - "fieldseeker" diff --git a/db/dberrors/district.bob.go b/db/dberrors/import.district.bob.go similarity index 75% rename from db/dberrors/district.bob.go rename to db/dberrors/import.district.bob.go index b564e22b..a69509a5 100644 --- a/db/dberrors/district.bob.go +++ b/db/dberrors/import.district.bob.go @@ -3,15 +3,15 @@ package dberrors -var DistrictErrors = &districtErrors{ +var ImportDistrictErrors = &importDistrictErrors{ ErrUniqueDistrictPkey: &UniqueConstraintError{ - schema: "", + schema: "import", table: "district", columns: []string{"gid"}, s: "district_pkey", }, } -type districtErrors struct { +type importDistrictErrors struct { ErrUniqueDistrictPkey *UniqueConstraintError } diff --git a/db/dberrors/organization.bob.go b/db/dberrors/organization.bob.go index d9bf3dbd..fee0219d 100644 --- a/db/dberrors/organization.bob.go +++ b/db/dberrors/organization.bob.go @@ -10,8 +10,26 @@ var OrganizationErrors = &organizationErrors{ columns: []string{"id"}, s: "organization_pkey", }, + + ErrUniqueOrganizationImportDistrictGidKey: &UniqueConstraintError{ + schema: "", + table: "organization", + columns: []string{"import_district_gid"}, + s: "organization_import_district_gid_key", + }, + + ErrUniqueOrganizationWebsiteKey: &UniqueConstraintError{ + schema: "", + table: "organization", + columns: []string{"website"}, + s: "organization_website_key", + }, } type organizationErrors struct { ErrUniqueOrganizationPkey *UniqueConstraintError + + ErrUniqueOrganizationImportDistrictGidKey *UniqueConstraintError + + ErrUniqueOrganizationWebsiteKey *UniqueConstraintError } diff --git a/db/dbinfo/district.bob.go b/db/dbinfo/import.district.bob.go similarity index 89% rename from db/dbinfo/district.bob.go rename to db/dbinfo/import.district.bob.go index 1cbd0a70..e51c4fcb 100644 --- a/db/dbinfo/district.bob.go +++ b/db/dbinfo/import.district.bob.go @@ -5,20 +5,20 @@ package dbinfo import "github.com/aarondl/opt/null" -var Districts = Table[ - districtColumns, - districtIndexes, - districtForeignKeys, - districtUniques, - districtChecks, +var ImportDistricts = Table[ + importDistrictColumns, + importDistrictIndexes, + importDistrictForeignKeys, + importDistrictUniques, + importDistrictChecks, ]{ - Schema: "", + Schema: "import", Name: "district", - Columns: districtColumns{ + Columns: importDistrictColumns{ Gid: column{ Name: "gid", DBType: "integer", - Default: "nextval('district_gid_seq'::regclass)", + Default: "nextval('import.district_gid_seq'::regclass)", Comment: "", Nullable: false, Generated: false, @@ -223,7 +223,7 @@ var Districts = Table[ AutoIncr: false, }, }, - Indexes: districtIndexes{ + Indexes: importDistrictIndexes{ DistrictPkey: index{ Type: "btree", Name: "district_pkey", @@ -268,7 +268,7 @@ var Districts = Table[ Comment: "", } -type districtColumns struct { +type importDistrictColumns struct { Gid column ID column Website column @@ -294,37 +294,37 @@ type districtColumns struct { Geom4326 column } -func (c districtColumns) AsSlice() []column { +func (c importDistrictColumns) AsSlice() []column { return []column{ c.Gid, c.ID, c.Website, c.Contact, c.Address, c.Regionid, c.PostalCod, c.Phone1, c.Fax1, c.Agency, c.Code1, c.City1, c.ShapeLeng, c.Address2, c.GeneralMG, c.City2, c.PostalC1, c.Fax2, c.Phone2, c.ShapeLe1, c.ShapeArea, c.Geom, c.Geom4326, } } -type districtIndexes struct { +type importDistrictIndexes struct { DistrictPkey index DistrictGeomIdx index } -func (i districtIndexes) AsSlice() []index { +func (i importDistrictIndexes) AsSlice() []index { return []index{ i.DistrictPkey, i.DistrictGeomIdx, } } -type districtForeignKeys struct{} +type importDistrictForeignKeys struct{} -func (f districtForeignKeys) AsSlice() []foreignKey { +func (f importDistrictForeignKeys) AsSlice() []foreignKey { return []foreignKey{} } -type districtUniques struct{} +type importDistrictUniques struct{} -func (u districtUniques) AsSlice() []constraint { +func (u importDistrictUniques) AsSlice() []constraint { return []constraint{} } -type districtChecks struct{} +type importDistrictChecks struct{} -func (c districtChecks) AsSlice() []check { +func (c importDistrictChecks) AsSlice() []check { return []check{} } diff --git a/db/dbinfo/organization.bob.go b/db/dbinfo/organization.bob.go index 3539999d..cd42ae32 100644 --- a/db/dbinfo/organization.bob.go +++ b/db/dbinfo/organization.bob.go @@ -60,6 +60,24 @@ var Organizations = Table[ Generated: false, AutoIncr: false, }, + ImportDistrictGid: column{ + Name: "import_district_gid", + DBType: "integer", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + Website: column{ + Name: "website", + DBType: "text", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, }, Indexes: organizationIndexes{ OrganizationPkey: index{ @@ -79,50 +97,120 @@ var Organizations = Table[ Where: "", Include: []string{}, }, + OrganizationImportDistrictGidKey: index{ + Type: "btree", + Name: "organization_import_district_gid_key", + Columns: []indexColumn{ + { + Name: "import_district_gid", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + OrganizationWebsiteKey: index{ + Type: "btree", + Name: "organization_website_key", + Columns: []indexColumn{ + { + Name: "website", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, }, PrimaryKey: &constraint{ Name: "organization_pkey", Columns: []string{"id"}, Comment: "", }, + ForeignKeys: organizationForeignKeys{ + OrganizationOrganizationImportDistrictGidFkey: foreignKey{ + constraint: constraint{ + Name: "organization.organization_import_district_gid_fkey", + Columns: []string{"import_district_gid"}, + Comment: "", + }, + ForeignTable: "import.district", + ForeignColumns: []string{"gid"}, + }, + }, + Uniques: organizationUniques{ + OrganizationImportDistrictGidKey: constraint{ + Name: "organization_import_district_gid_key", + Columns: []string{"import_district_gid"}, + Comment: "", + }, + OrganizationWebsiteKey: constraint{ + Name: "organization_website_key", + Columns: []string{"website"}, + Comment: "", + }, + }, Comment: "", } type organizationColumns struct { - ID column - Name column - ArcgisID column - ArcgisName column - FieldseekerURL column + ID column + Name column + ArcgisID column + ArcgisName column + FieldseekerURL column + ImportDistrictGid column + Website column } func (c organizationColumns) AsSlice() []column { return []column{ - c.ID, c.Name, c.ArcgisID, c.ArcgisName, c.FieldseekerURL, + c.ID, c.Name, c.ArcgisID, c.ArcgisName, c.FieldseekerURL, c.ImportDistrictGid, c.Website, } } type organizationIndexes struct { - OrganizationPkey index + OrganizationPkey index + OrganizationImportDistrictGidKey index + OrganizationWebsiteKey index } func (i organizationIndexes) AsSlice() []index { return []index{ - i.OrganizationPkey, + i.OrganizationPkey, i.OrganizationImportDistrictGidKey, i.OrganizationWebsiteKey, } } -type organizationForeignKeys struct{} - -func (f organizationForeignKeys) AsSlice() []foreignKey { - return []foreignKey{} +type organizationForeignKeys struct { + OrganizationOrganizationImportDistrictGidFkey foreignKey } -type organizationUniques struct{} +func (f organizationForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.OrganizationOrganizationImportDistrictGidFkey, + } +} + +type organizationUniques struct { + OrganizationImportDistrictGidKey constraint + OrganizationWebsiteKey constraint +} func (u organizationUniques) AsSlice() []constraint { - return []constraint{} + return []constraint{ + u.OrganizationImportDistrictGidKey, u.OrganizationWebsiteKey, + } } type organizationChecks struct{} diff --git a/db/factory/bobfactory_context.bob.go b/db/factory/bobfactory_context.bob.go index f584a12c..289d2646 100644 --- a/db/factory/bobfactory_context.bob.go +++ b/db/factory/bobfactory_context.bob.go @@ -17,9 +17,6 @@ var ( arcgisUserPrivilegeWithParentsCascadingCtx = newContextual[bool]("arcgisUserPrivilegeWithParentsCascading") arcgisUserPrivilegeRelUserUserCtx = newContextual[bool]("arcgis.user_.arcgis.user_privilege.arcgis.user_privilege.user_privilege_user_id_fkey") - // Relationship Contexts for district - districtWithParentsCascadingCtx = newContextual[bool]("districtWithParentsCascading") - // Relationship Contexts for fieldseeker.containerrelate fieldseekerContainerrelateWithParentsCascadingCtx = newContextual[bool]("fieldseekerContainerrelateWithParentsCascading") fieldseekerContainerrelateRelOrganizationCtx = newContextual[bool]("fieldseeker.containerrelate.organization.fieldseeker.containerrelate.containerrelate_organization_id_fkey") @@ -145,6 +142,10 @@ var ( h3AggregationWithParentsCascadingCtx = newContextual[bool]("h3AggregationWithParentsCascading") h3AggregationRelOrganizationCtx = newContextual[bool]("h3_aggregation.organization.h3_aggregation.h3_aggregation_organization_id_fkey") + // Relationship Contexts for import.district + importDistrictWithParentsCascadingCtx = newContextual[bool]("importDistrictWithParentsCascading") + importDistrictRelImportDistrictGidOrganizationCtx = newContextual[bool]("import.district.organization.organization.organization_import_district_gid_fkey") + // Relationship Contexts for note_audio noteAudioWithParentsCascadingCtx = newContextual[bool]("noteAudioWithParentsCascading") noteAudioRelCreatorUserCtx = newContextual[bool]("note_audio.user_.note_audio.note_audio_creator_id_fkey") @@ -186,39 +187,40 @@ var ( oauthTokenRelUserUserCtx = newContextual[bool]("oauth_token.user_.oauth_token.oauth_token_user_id_fkey") // Relationship Contexts for organization - organizationWithParentsCascadingCtx = newContextual[bool]("organizationWithParentsCascading") - organizationRelContainerrelatesCtx = newContextual[bool]("fieldseeker.containerrelate.organization.fieldseeker.containerrelate.containerrelate_organization_id_fkey") - organizationRelFieldscoutinglogsCtx = newContextual[bool]("fieldseeker.fieldscoutinglog.organization.fieldseeker.fieldscoutinglog.fieldscoutinglog_organization_id_fkey") - organizationRelHabitatrelatesCtx = newContextual[bool]("fieldseeker.habitatrelate.organization.fieldseeker.habitatrelate.habitatrelate_organization_id_fkey") - organizationRelInspectionsamplesCtx = newContextual[bool]("fieldseeker.inspectionsample.organization.fieldseeker.inspectionsample.inspectionsample_organization_id_fkey") - organizationRelInspectionsampledetailsCtx = newContextual[bool]("fieldseeker.inspectionsampledetail.organization.fieldseeker.inspectionsampledetail.inspectionsampledetail_organization_id_fkey") - organizationRelLinelocationsCtx = newContextual[bool]("fieldseeker.linelocation.organization.fieldseeker.linelocation.linelocation_organization_id_fkey") - organizationRelLocationtrackingsCtx = newContextual[bool]("fieldseeker.locationtracking.organization.fieldseeker.locationtracking.locationtracking_organization_id_fkey") - organizationRelMosquitoinspectionsCtx = newContextual[bool]("fieldseeker.mosquitoinspection.organization.fieldseeker.mosquitoinspection.mosquitoinspection_organization_id_fkey") - organizationRelPointlocationsCtx = newContextual[bool]("fieldseeker.pointlocation.organization.fieldseeker.pointlocation.pointlocation_organization_id_fkey") - organizationRelPolygonlocationsCtx = newContextual[bool]("fieldseeker.polygonlocation.organization.fieldseeker.polygonlocation.polygonlocation_organization_id_fkey") - organizationRelPoolsCtx = newContextual[bool]("fieldseeker.pool.organization.fieldseeker.pool.pool_organization_id_fkey") - organizationRelPooldetailsCtx = newContextual[bool]("fieldseeker.pooldetail.organization.fieldseeker.pooldetail.pooldetail_organization_id_fkey") - organizationRelProposedtreatmentareasCtx = newContextual[bool]("fieldseeker.proposedtreatmentarea.organization.fieldseeker.proposedtreatmentarea.proposedtreatmentarea_organization_id_fkey") - organizationRelQamosquitoinspectionsCtx = newContextual[bool]("fieldseeker.qamosquitoinspection.organization.fieldseeker.qamosquitoinspection.qamosquitoinspection_organization_id_fkey") - organizationRelRodentlocationsCtx = newContextual[bool]("fieldseeker.rodentlocation.organization.fieldseeker.rodentlocation.rodentlocation_organization_id_fkey") - organizationRelSamplecollectionsCtx = newContextual[bool]("fieldseeker.samplecollection.organization.fieldseeker.samplecollection.samplecollection_organization_id_fkey") - organizationRelSamplelocationsCtx = newContextual[bool]("fieldseeker.samplelocation.organization.fieldseeker.samplelocation.samplelocation_organization_id_fkey") - organizationRelServicerequestsCtx = newContextual[bool]("fieldseeker.servicerequest.organization.fieldseeker.servicerequest.servicerequest_organization_id_fkey") - organizationRelSpeciesabundancesCtx = newContextual[bool]("fieldseeker.speciesabundance.organization.fieldseeker.speciesabundance.speciesabundance_organization_id_fkey") - organizationRelStormdrainsCtx = newContextual[bool]("fieldseeker.stormdrain.organization.fieldseeker.stormdrain.stormdrain_organization_id_fkey") - organizationRelTimecardsCtx = newContextual[bool]("fieldseeker.timecard.organization.fieldseeker.timecard.timecard_organization_id_fkey") - organizationRelTrapdataCtx = newContextual[bool]("fieldseeker.trapdata.organization.fieldseeker.trapdata.trapdata_organization_id_fkey") - organizationRelTraplocationsCtx = newContextual[bool]("fieldseeker.traplocation.organization.fieldseeker.traplocation.traplocation_organization_id_fkey") - organizationRelTreatmentsCtx = newContextual[bool]("fieldseeker.treatment.organization.fieldseeker.treatment.treatment_organization_id_fkey") - organizationRelTreatmentareasCtx = newContextual[bool]("fieldseeker.treatmentarea.organization.fieldseeker.treatmentarea.treatmentarea_organization_id_fkey") - organizationRelZonesCtx = newContextual[bool]("fieldseeker.zones.organization.fieldseeker.zones.zones_organization_id_fkey") - organizationRelZones2sCtx = newContextual[bool]("fieldseeker.zones2.organization.fieldseeker.zones2.zones2_organization_id_fkey") - organizationRelFieldseekerSyncsCtx = newContextual[bool]("fieldseeker_sync.organization.fieldseeker_sync.fieldseeker_sync_organization_id_fkey") - organizationRelH3AggregationsCtx = newContextual[bool]("h3_aggregation.organization.h3_aggregation.h3_aggregation_organization_id_fkey") - organizationRelNoteAudiosCtx = newContextual[bool]("note_audio.organization.note_audio.note_audio_organization_id_fkey") - organizationRelNoteImagesCtx = newContextual[bool]("note_image.organization.note_image.note_image_organization_id_fkey") - organizationRelUserCtx = newContextual[bool]("organization.user_.user_.user__organization_id_fkey") + organizationWithParentsCascadingCtx = newContextual[bool]("organizationWithParentsCascading") + organizationRelContainerrelatesCtx = newContextual[bool]("fieldseeker.containerrelate.organization.fieldseeker.containerrelate.containerrelate_organization_id_fkey") + organizationRelFieldscoutinglogsCtx = newContextual[bool]("fieldseeker.fieldscoutinglog.organization.fieldseeker.fieldscoutinglog.fieldscoutinglog_organization_id_fkey") + organizationRelHabitatrelatesCtx = newContextual[bool]("fieldseeker.habitatrelate.organization.fieldseeker.habitatrelate.habitatrelate_organization_id_fkey") + organizationRelInspectionsamplesCtx = newContextual[bool]("fieldseeker.inspectionsample.organization.fieldseeker.inspectionsample.inspectionsample_organization_id_fkey") + organizationRelInspectionsampledetailsCtx = newContextual[bool]("fieldseeker.inspectionsampledetail.organization.fieldseeker.inspectionsampledetail.inspectionsampledetail_organization_id_fkey") + organizationRelLinelocationsCtx = newContextual[bool]("fieldseeker.linelocation.organization.fieldseeker.linelocation.linelocation_organization_id_fkey") + organizationRelLocationtrackingsCtx = newContextual[bool]("fieldseeker.locationtracking.organization.fieldseeker.locationtracking.locationtracking_organization_id_fkey") + organizationRelMosquitoinspectionsCtx = newContextual[bool]("fieldseeker.mosquitoinspection.organization.fieldseeker.mosquitoinspection.mosquitoinspection_organization_id_fkey") + organizationRelPointlocationsCtx = newContextual[bool]("fieldseeker.pointlocation.organization.fieldseeker.pointlocation.pointlocation_organization_id_fkey") + organizationRelPolygonlocationsCtx = newContextual[bool]("fieldseeker.polygonlocation.organization.fieldseeker.polygonlocation.polygonlocation_organization_id_fkey") + organizationRelPoolsCtx = newContextual[bool]("fieldseeker.pool.organization.fieldseeker.pool.pool_organization_id_fkey") + organizationRelPooldetailsCtx = newContextual[bool]("fieldseeker.pooldetail.organization.fieldseeker.pooldetail.pooldetail_organization_id_fkey") + organizationRelProposedtreatmentareasCtx = newContextual[bool]("fieldseeker.proposedtreatmentarea.organization.fieldseeker.proposedtreatmentarea.proposedtreatmentarea_organization_id_fkey") + organizationRelQamosquitoinspectionsCtx = newContextual[bool]("fieldseeker.qamosquitoinspection.organization.fieldseeker.qamosquitoinspection.qamosquitoinspection_organization_id_fkey") + organizationRelRodentlocationsCtx = newContextual[bool]("fieldseeker.rodentlocation.organization.fieldseeker.rodentlocation.rodentlocation_organization_id_fkey") + organizationRelSamplecollectionsCtx = newContextual[bool]("fieldseeker.samplecollection.organization.fieldseeker.samplecollection.samplecollection_organization_id_fkey") + organizationRelSamplelocationsCtx = newContextual[bool]("fieldseeker.samplelocation.organization.fieldseeker.samplelocation.samplelocation_organization_id_fkey") + organizationRelServicerequestsCtx = newContextual[bool]("fieldseeker.servicerequest.organization.fieldseeker.servicerequest.servicerequest_organization_id_fkey") + organizationRelSpeciesabundancesCtx = newContextual[bool]("fieldseeker.speciesabundance.organization.fieldseeker.speciesabundance.speciesabundance_organization_id_fkey") + organizationRelStormdrainsCtx = newContextual[bool]("fieldseeker.stormdrain.organization.fieldseeker.stormdrain.stormdrain_organization_id_fkey") + organizationRelTimecardsCtx = newContextual[bool]("fieldseeker.timecard.organization.fieldseeker.timecard.timecard_organization_id_fkey") + organizationRelTrapdataCtx = newContextual[bool]("fieldseeker.trapdata.organization.fieldseeker.trapdata.trapdata_organization_id_fkey") + organizationRelTraplocationsCtx = newContextual[bool]("fieldseeker.traplocation.organization.fieldseeker.traplocation.traplocation_organization_id_fkey") + organizationRelTreatmentsCtx = newContextual[bool]("fieldseeker.treatment.organization.fieldseeker.treatment.treatment_organization_id_fkey") + organizationRelTreatmentareasCtx = newContextual[bool]("fieldseeker.treatmentarea.organization.fieldseeker.treatmentarea.treatmentarea_organization_id_fkey") + organizationRelZonesCtx = newContextual[bool]("fieldseeker.zones.organization.fieldseeker.zones.zones_organization_id_fkey") + organizationRelZones2sCtx = newContextual[bool]("fieldseeker.zones2.organization.fieldseeker.zones2.zones2_organization_id_fkey") + organizationRelFieldseekerSyncsCtx = newContextual[bool]("fieldseeker_sync.organization.fieldseeker_sync.fieldseeker_sync_organization_id_fkey") + organizationRelH3AggregationsCtx = newContextual[bool]("h3_aggregation.organization.h3_aggregation.h3_aggregation_organization_id_fkey") + organizationRelNoteAudiosCtx = newContextual[bool]("note_audio.organization.note_audio.note_audio_organization_id_fkey") + organizationRelNoteImagesCtx = newContextual[bool]("note_image.organization.note_image.note_image_organization_id_fkey") + organizationRelImportDistrictGidDistrictCtx = newContextual[bool]("import.district.organization.organization.organization_import_district_gid_fkey") + organizationRelUserCtx = newContextual[bool]("organization.user_.user_.user__organization_id_fkey") // Relationship Contexts for publicreport.nuisance publicreportNuisanceWithParentsCascadingCtx = newContextual[bool]("publicreportNuisanceWithParentsCascading") diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index d3822ee8..eadbf506 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -20,7 +20,6 @@ import ( type Factory struct { baseArcgisUserMods ArcgisUserModSlice baseArcgisUserPrivilegeMods ArcgisUserPrivilegeModSlice - baseDistrictMods DistrictModSlice baseFieldseekerContainerrelateMods FieldseekerContainerrelateModSlice baseFieldseekerFieldscoutinglogMods FieldseekerFieldscoutinglogModSlice baseFieldseekerHabitatrelateMods FieldseekerHabitatrelateModSlice @@ -53,6 +52,7 @@ type Factory struct { baseGeometryColumnMods GeometryColumnModSlice baseGooseDBVersionMods GooseDBVersionModSlice baseH3AggregationMods H3AggregationModSlice + baseImportDistrictMods ImportDistrictModSlice baseNoteAudioMods NoteAudioModSlice baseNoteAudioBreadcrumbMods NoteAudioBreadcrumbModSlice baseNoteAudioDatumMods NoteAudioDatumModSlice @@ -154,52 +154,6 @@ func (f *Factory) FromExistingArcgisUserPrivilege(m *models.ArcgisUserPrivilege) return o } -func (f *Factory) NewDistrict(mods ...DistrictMod) *DistrictTemplate { - return f.NewDistrictWithContext(context.Background(), mods...) -} - -func (f *Factory) NewDistrictWithContext(ctx context.Context, mods ...DistrictMod) *DistrictTemplate { - o := &DistrictTemplate{f: f} - - if f != nil { - f.baseDistrictMods.Apply(ctx, o) - } - - DistrictModSlice(mods).Apply(ctx, o) - - return o -} - -func (f *Factory) FromExistingDistrict(m *models.District) *DistrictTemplate { - o := &DistrictTemplate{f: f, alreadyPersisted: true} - - o.Gid = func() int32 { return m.Gid } - o.ID = func() null.Val[decimal.Decimal] { return m.ID } - o.Website = func() null.Val[string] { return m.Website } - o.Contact = func() null.Val[string] { return m.Contact } - o.Address = func() null.Val[string] { return m.Address } - o.Regionid = func() null.Val[decimal.Decimal] { return m.Regionid } - o.PostalCod = func() null.Val[decimal.Decimal] { return m.PostalCod } - o.Phone1 = func() null.Val[string] { return m.Phone1 } - o.Fax1 = func() null.Val[string] { return m.Fax1 } - o.Agency = func() null.Val[string] { return m.Agency } - o.Code1 = func() null.Val[string] { return m.Code1 } - o.City1 = func() null.Val[string] { return m.City1 } - o.ShapeLeng = func() null.Val[decimal.Decimal] { return m.ShapeLeng } - o.Address2 = func() null.Val[string] { return m.Address2 } - o.GeneralMG = func() null.Val[string] { return m.GeneralMG } - o.City2 = func() null.Val[string] { return m.City2 } - o.PostalC1 = func() null.Val[decimal.Decimal] { return m.PostalC1 } - o.Fax2 = func() null.Val[string] { return m.Fax2 } - o.Phone2 = func() null.Val[string] { return m.Phone2 } - o.ShapeLe1 = func() null.Val[decimal.Decimal] { return m.ShapeLe1 } - o.ShapeArea = func() null.Val[decimal.Decimal] { return m.ShapeArea } - o.Geom = func() null.Val[string] { return m.Geom } - o.Geom4326 = func() null.Val[string] { return m.Geom4326 } - - return o -} - func (f *Factory) NewFieldseekerContainerrelate(mods ...FieldseekerContainerrelateMod) *FieldseekerContainerrelateTemplate { return f.NewFieldseekerContainerrelateWithContext(context.Background(), mods...) } @@ -2074,6 +2028,57 @@ func (f *Factory) FromExistingH3Aggregation(m *models.H3Aggregation) *H3Aggregat return o } +func (f *Factory) NewImportDistrict(mods ...ImportDistrictMod) *ImportDistrictTemplate { + return f.NewImportDistrictWithContext(context.Background(), mods...) +} + +func (f *Factory) NewImportDistrictWithContext(ctx context.Context, mods ...ImportDistrictMod) *ImportDistrictTemplate { + o := &ImportDistrictTemplate{f: f} + + if f != nil { + f.baseImportDistrictMods.Apply(ctx, o) + } + + ImportDistrictModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingImportDistrict(m *models.ImportDistrict) *ImportDistrictTemplate { + o := &ImportDistrictTemplate{f: f, alreadyPersisted: true} + + o.Gid = func() int32 { return m.Gid } + o.ID = func() null.Val[decimal.Decimal] { return m.ID } + o.Website = func() null.Val[string] { return m.Website } + o.Contact = func() null.Val[string] { return m.Contact } + o.Address = func() null.Val[string] { return m.Address } + o.Regionid = func() null.Val[decimal.Decimal] { return m.Regionid } + o.PostalCod = func() null.Val[decimal.Decimal] { return m.PostalCod } + o.Phone1 = func() null.Val[string] { return m.Phone1 } + o.Fax1 = func() null.Val[string] { return m.Fax1 } + o.Agency = func() null.Val[string] { return m.Agency } + o.Code1 = func() null.Val[string] { return m.Code1 } + o.City1 = func() null.Val[string] { return m.City1 } + o.ShapeLeng = func() null.Val[decimal.Decimal] { return m.ShapeLeng } + o.Address2 = func() null.Val[string] { return m.Address2 } + o.GeneralMG = func() null.Val[string] { return m.GeneralMG } + o.City2 = func() null.Val[string] { return m.City2 } + o.PostalC1 = func() null.Val[decimal.Decimal] { return m.PostalC1 } + o.Fax2 = func() null.Val[string] { return m.Fax2 } + o.Phone2 = func() null.Val[string] { return m.Phone2 } + o.ShapeLe1 = func() null.Val[decimal.Decimal] { return m.ShapeLe1 } + o.ShapeArea = func() null.Val[decimal.Decimal] { return m.ShapeArea } + o.Geom = func() null.Val[string] { return m.Geom } + o.Geom4326 = func() null.Val[string] { return m.Geom4326 } + + ctx := context.Background() + if m.R.ImportDistrictGidOrganization != nil { + ImportDistrictMods.WithExistingImportDistrictGidOrganization(m.R.ImportDistrictGidOrganization).Apply(ctx, o) + } + + return o +} + func (f *Factory) NewNoteAudio(mods ...NoteAudioMod) *NoteAudioTemplate { return f.NewNoteAudioWithContext(context.Background(), mods...) } @@ -2400,6 +2405,8 @@ func (f *Factory) FromExistingOrganization(m *models.Organization) *Organization o.ArcgisID = func() null.Val[string] { return m.ArcgisID } o.ArcgisName = func() null.Val[string] { return m.ArcgisName } o.FieldseekerURL = func() null.Val[string] { return m.FieldseekerURL } + o.ImportDistrictGid = func() null.Val[int32] { return m.ImportDistrictGid } + o.Website = func() null.Val[string] { return m.Website } ctx := context.Background() if len(m.R.Containerrelates) > 0 { @@ -2495,6 +2502,9 @@ func (f *Factory) FromExistingOrganization(m *models.Organization) *Organization if len(m.R.NoteImages) > 0 { OrganizationMods.AddExistingNoteImages(m.R.NoteImages...).Apply(ctx, o) } + if m.R.ImportDistrictGidDistrict != nil { + OrganizationMods.WithExistingImportDistrictGidDistrict(m.R.ImportDistrictGidDistrict).Apply(ctx, o) + } if len(m.R.User) > 0 { OrganizationMods.AddExistingUser(m.R.User...).Apply(ctx, o) } @@ -2947,14 +2957,6 @@ func (f *Factory) AddBaseArcgisUserPrivilegeMod(mods ...ArcgisUserPrivilegeMod) f.baseArcgisUserPrivilegeMods = append(f.baseArcgisUserPrivilegeMods, mods...) } -func (f *Factory) ClearBaseDistrictMods() { - f.baseDistrictMods = nil -} - -func (f *Factory) AddBaseDistrictMod(mods ...DistrictMod) { - f.baseDistrictMods = append(f.baseDistrictMods, mods...) -} - func (f *Factory) ClearBaseFieldseekerContainerrelateMods() { f.baseFieldseekerContainerrelateMods = nil } @@ -3211,6 +3213,14 @@ func (f *Factory) AddBaseH3AggregationMod(mods ...H3AggregationMod) { f.baseH3AggregationMods = append(f.baseH3AggregationMods, mods...) } +func (f *Factory) ClearBaseImportDistrictMods() { + f.baseImportDistrictMods = nil +} + +func (f *Factory) AddBaseImportDistrictMod(mods ...ImportDistrictMod) { + f.baseImportDistrictMods = append(f.baseImportDistrictMods, mods...) +} + func (f *Factory) ClearBaseNoteAudioMods() { f.baseNoteAudioMods = nil } diff --git a/db/factory/district.bob.go b/db/factory/import.district.bob.go similarity index 50% rename from db/factory/district.bob.go rename to db/factory/import.district.bob.go index fd5944c4..b2ebd11c 100644 --- a/db/factory/district.bob.go +++ b/db/factory/import.district.bob.go @@ -16,27 +16,27 @@ import ( "github.com/stephenafamo/bob" ) -type DistrictMod interface { - Apply(context.Context, *DistrictTemplate) +type ImportDistrictMod interface { + Apply(context.Context, *ImportDistrictTemplate) } -type DistrictModFunc func(context.Context, *DistrictTemplate) +type ImportDistrictModFunc func(context.Context, *ImportDistrictTemplate) -func (f DistrictModFunc) Apply(ctx context.Context, n *DistrictTemplate) { +func (f ImportDistrictModFunc) Apply(ctx context.Context, n *ImportDistrictTemplate) { f(ctx, n) } -type DistrictModSlice []DistrictMod +type ImportDistrictModSlice []ImportDistrictMod -func (mods DistrictModSlice) Apply(ctx context.Context, n *DistrictTemplate) { +func (mods ImportDistrictModSlice) Apply(ctx context.Context, n *ImportDistrictTemplate) { for _, f := range mods { f.Apply(ctx, n) } } -// DistrictTemplate is an object representing the database table. +// ImportDistrictTemplate is an object representing the database table. // all columns are optional and should be set by mods -type DistrictTemplate struct { +type ImportDistrictTemplate struct { Gid func() int32 ID func() null.Val[decimal.Decimal] Website func() null.Val[string] @@ -61,26 +61,42 @@ type DistrictTemplate struct { Geom func() null.Val[string] Geom4326 func() null.Val[string] + r importDistrictR f *Factory alreadyPersisted bool } -// Apply mods to the DistrictTemplate -func (o *DistrictTemplate) Apply(ctx context.Context, mods ...DistrictMod) { +type importDistrictR struct { + ImportDistrictGidOrganization *importDistrictRImportDistrictGidOrganizationR +} + +type importDistrictRImportDistrictGidOrganizationR struct { + o *OrganizationTemplate +} + +// Apply mods to the ImportDistrictTemplate +func (o *ImportDistrictTemplate) Apply(ctx context.Context, mods ...ImportDistrictMod) { for _, mod := range mods { mod.Apply(ctx, o) } } -// setModelRels creates and sets the relationships on *models.District +// setModelRels creates and sets the relationships on *models.ImportDistrict // according to the relationships in the template. Nothing is inserted into the db -func (t DistrictTemplate) setModelRels(o *models.District) {} +func (t ImportDistrictTemplate) setModelRels(o *models.ImportDistrict) { + if t.r.ImportDistrictGidOrganization != nil { + rel := t.r.ImportDistrictGidOrganization.o.Build() + rel.R.ImportDistrictGidDistrict = o + rel.ImportDistrictGid = null.From(o.Gid) // h2 + o.R.ImportDistrictGidOrganization = rel + } +} -// BuildSetter returns an *models.DistrictSetter +// BuildSetter returns an *models.ImportDistrictSetter // this does nothing with the relationship templates -func (o DistrictTemplate) BuildSetter() *models.DistrictSetter { - m := &models.DistrictSetter{} +func (o ImportDistrictTemplate) BuildSetter() *models.ImportDistrictSetter { + m := &models.ImportDistrictSetter{} if o.Gid != nil { val := o.Gid() @@ -174,10 +190,10 @@ func (o DistrictTemplate) BuildSetter() *models.DistrictSetter { return m } -// BuildManySetter returns an []*models.DistrictSetter +// BuildManySetter returns an []*models.ImportDistrictSetter // this does nothing with the relationship templates -func (o DistrictTemplate) BuildManySetter(number int) []*models.DistrictSetter { - m := make([]*models.DistrictSetter, number) +func (o ImportDistrictTemplate) BuildManySetter(number int) []*models.ImportDistrictSetter { + m := make([]*models.ImportDistrictSetter, number) for i := range m { m[i] = o.BuildSetter() @@ -186,11 +202,11 @@ func (o DistrictTemplate) BuildManySetter(number int) []*models.DistrictSetter { return m } -// Build returns an *models.District +// Build returns an *models.ImportDistrict // Related objects are also created and placed in the .R field -// NOTE: Objects are not inserted into the database. Use DistrictTemplate.Create -func (o DistrictTemplate) Build() *models.District { - m := &models.District{} +// NOTE: Objects are not inserted into the database. Use ImportDistrictTemplate.Create +func (o ImportDistrictTemplate) Build() *models.ImportDistrict { + m := &models.ImportDistrict{} if o.Gid != nil { m.Gid = o.Gid() @@ -267,11 +283,11 @@ func (o DistrictTemplate) Build() *models.District { return m } -// BuildMany returns an models.DistrictSlice +// BuildMany returns an models.ImportDistrictSlice // Related objects are also created and placed in the .R field -// NOTE: Objects are not inserted into the database. Use DistrictTemplate.CreateMany -func (o DistrictTemplate) BuildMany(number int) models.DistrictSlice { - m := make(models.DistrictSlice, number) +// NOTE: Objects are not inserted into the database. Use ImportDistrictTemplate.CreateMany +func (o ImportDistrictTemplate) BuildMany(number int) models.ImportDistrictSlice { + m := make(models.ImportDistrictSlice, number) for i := range m { m[i] = o.Build() @@ -280,26 +296,45 @@ func (o DistrictTemplate) BuildMany(number int) models.DistrictSlice { return m } -func ensureCreatableDistrict(m *models.DistrictSetter) { +func ensureCreatableImportDistrict(m *models.ImportDistrictSetter) { } -// insertOptRels creates and inserts any optional the relationships on *models.District +// insertOptRels creates and inserts any optional the relationships on *models.ImportDistrict // according to the relationships in the template. // any required relationship should have already exist on the model -func (o *DistrictTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.District) error { +func (o *ImportDistrictTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.ImportDistrict) error { var err error + isImportDistrictGidOrganizationDone, _ := importDistrictRelImportDistrictGidOrganizationCtx.Value(ctx) + if !isImportDistrictGidOrganizationDone && o.r.ImportDistrictGidOrganization != nil { + ctx = importDistrictRelImportDistrictGidOrganizationCtx.WithValue(ctx, true) + if o.r.ImportDistrictGidOrganization.o.alreadyPersisted { + m.R.ImportDistrictGidOrganization = o.r.ImportDistrictGidOrganization.o.Build() + } else { + var rel0 *models.Organization + rel0, err = o.r.ImportDistrictGidOrganization.o.Create(ctx, exec) + if err != nil { + return err + } + err = m.AttachImportDistrictGidOrganization(ctx, exec, rel0) + if err != nil { + return err + } + } + + } + return err } -// Create builds a district and inserts it into the database +// Create builds a importDistrict and inserts it into the database // Relations objects are also inserted and placed in the .R field -func (o *DistrictTemplate) Create(ctx context.Context, exec bob.Executor) (*models.District, error) { +func (o *ImportDistrictTemplate) Create(ctx context.Context, exec bob.Executor) (*models.ImportDistrict, error) { var err error opt := o.BuildSetter() - ensureCreatableDistrict(opt) + ensureCreatableImportDistrict(opt) - m, err := models.Districts.Insert(opt).One(ctx, exec) + m, err := models.ImportDistricts.Insert(opt).One(ctx, exec) if err != nil { return nil, err } @@ -310,10 +345,10 @@ func (o *DistrictTemplate) Create(ctx context.Context, exec bob.Executor) (*mode return m, err } -// MustCreate builds a district and inserts it into the database +// MustCreate builds a importDistrict and inserts it into the database // Relations objects are also inserted and placed in the .R field // panics if an error occurs -func (o *DistrictTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.District { +func (o *ImportDistrictTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.ImportDistrict { m, err := o.Create(ctx, exec) if err != nil { panic(err) @@ -321,10 +356,10 @@ func (o *DistrictTemplate) MustCreate(ctx context.Context, exec bob.Executor) *m return m } -// CreateOrFail builds a district and inserts it into the database +// CreateOrFail builds a importDistrict 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 *DistrictTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.District { +func (o *ImportDistrictTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.ImportDistrict { tb.Helper() m, err := o.Create(ctx, exec) if err != nil { @@ -334,11 +369,11 @@ func (o *DistrictTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec return m } -// CreateMany builds multiple districts and inserts them into the database +// CreateMany builds multiple importDistricts and inserts them into the database // Relations objects are also inserted and placed in the .R field -func (o DistrictTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.DistrictSlice, error) { +func (o ImportDistrictTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.ImportDistrictSlice, error) { var err error - m := make(models.DistrictSlice, number) + m := make(models.ImportDistrictSlice, number) for i := range m { m[i], err = o.Create(ctx, exec) @@ -350,10 +385,10 @@ func (o DistrictTemplate) CreateMany(ctx context.Context, exec bob.Executor, num return m, nil } -// MustCreateMany builds multiple districts and inserts them into the database +// MustCreateMany builds multiple importDistricts and inserts them into the database // Relations objects are also inserted and placed in the .R field // panics if an error occurs -func (o DistrictTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.DistrictSlice { +func (o ImportDistrictTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.ImportDistrictSlice { m, err := o.CreateMany(ctx, exec, number) if err != nil { panic(err) @@ -361,10 +396,10 @@ func (o DistrictTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, return m } -// CreateManyOrFail builds multiple districts and inserts them into the database +// CreateManyOrFail builds multiple importDistricts 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 DistrictTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.DistrictSlice { +func (o ImportDistrictTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.ImportDistrictSlice { tb.Helper() m, err := o.CreateMany(ctx, exec, number) if err != nil { @@ -374,64 +409,64 @@ func (o DistrictTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, e return m } -// District has methods that act as mods for the DistrictTemplate -var DistrictMods districtMods +// ImportDistrict has methods that act as mods for the ImportDistrictTemplate +var ImportDistrictMods importDistrictMods -type districtMods struct{} +type importDistrictMods struct{} -func (m districtMods) RandomizeAllColumns(f *faker.Faker) DistrictMod { - return DistrictModSlice{ - DistrictMods.RandomGid(f), - DistrictMods.RandomID(f), - DistrictMods.RandomWebsite(f), - DistrictMods.RandomContact(f), - DistrictMods.RandomAddress(f), - DistrictMods.RandomRegionid(f), - DistrictMods.RandomPostalCod(f), - DistrictMods.RandomPhone1(f), - DistrictMods.RandomFax1(f), - DistrictMods.RandomAgency(f), - DistrictMods.RandomCode1(f), - DistrictMods.RandomCity1(f), - DistrictMods.RandomShapeLeng(f), - DistrictMods.RandomAddress2(f), - DistrictMods.RandomGeneralMG(f), - DistrictMods.RandomCity2(f), - DistrictMods.RandomPostalC1(f), - DistrictMods.RandomFax2(f), - DistrictMods.RandomPhone2(f), - DistrictMods.RandomShapeLe1(f), - DistrictMods.RandomShapeArea(f), - DistrictMods.RandomGeom(f), - DistrictMods.RandomGeom4326(f), +func (m importDistrictMods) RandomizeAllColumns(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModSlice{ + ImportDistrictMods.RandomGid(f), + ImportDistrictMods.RandomID(f), + ImportDistrictMods.RandomWebsite(f), + ImportDistrictMods.RandomContact(f), + ImportDistrictMods.RandomAddress(f), + ImportDistrictMods.RandomRegionid(f), + ImportDistrictMods.RandomPostalCod(f), + ImportDistrictMods.RandomPhone1(f), + ImportDistrictMods.RandomFax1(f), + ImportDistrictMods.RandomAgency(f), + ImportDistrictMods.RandomCode1(f), + ImportDistrictMods.RandomCity1(f), + ImportDistrictMods.RandomShapeLeng(f), + ImportDistrictMods.RandomAddress2(f), + ImportDistrictMods.RandomGeneralMG(f), + ImportDistrictMods.RandomCity2(f), + ImportDistrictMods.RandomPostalC1(f), + ImportDistrictMods.RandomFax2(f), + ImportDistrictMods.RandomPhone2(f), + ImportDistrictMods.RandomShapeLe1(f), + ImportDistrictMods.RandomShapeArea(f), + ImportDistrictMods.RandomGeom(f), + ImportDistrictMods.RandomGeom4326(f), } } // Set the model columns to this value -func (m districtMods) Gid(val int32) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Gid(val int32) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Gid = func() int32 { return val } }) } // Set the Column from the function -func (m districtMods) GidFunc(f func() int32) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) GidFunc(f func() int32) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Gid = f }) } // Clear any values for the column -func (m districtMods) UnsetGid() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetGid() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Gid = nil }) } // Generates a random value for the column using the given faker // if faker is nil, a default faker is used -func (m districtMods) RandomGid(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomGid(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Gid = func() int32 { return random_int32(f) } @@ -439,22 +474,22 @@ func (m districtMods) RandomGid(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) ID(val null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) ID(val null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ID = func() null.Val[decimal.Decimal] { return val } }) } // Set the Column from the function -func (m districtMods) IDFunc(f func() null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) IDFunc(f func() null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ID = f }) } // Clear any values for the column -func (m districtMods) UnsetID() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetID() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ID = nil }) } @@ -462,8 +497,8 @@ func (m districtMods) UnsetID() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomID(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomID(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ID = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -478,8 +513,8 @@ func (m districtMods) RandomID(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomIDNotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomIDNotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ID = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -492,22 +527,22 @@ func (m districtMods) RandomIDNotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Website(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Website(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Website = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) WebsiteFunc(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) WebsiteFunc(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Website = f }) } // Clear any values for the column -func (m districtMods) UnsetWebsite() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetWebsite() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Website = nil }) } @@ -515,8 +550,8 @@ func (m districtMods) UnsetWebsite() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomWebsite(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomWebsite(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Website = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -531,8 +566,8 @@ func (m districtMods) RandomWebsite(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomWebsiteNotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomWebsiteNotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Website = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -545,22 +580,22 @@ func (m districtMods) RandomWebsiteNotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Contact(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Contact(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Contact = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) ContactFunc(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) ContactFunc(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Contact = f }) } // Clear any values for the column -func (m districtMods) UnsetContact() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetContact() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Contact = nil }) } @@ -568,8 +603,8 @@ func (m districtMods) UnsetContact() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomContact(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomContact(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Contact = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -584,8 +619,8 @@ func (m districtMods) RandomContact(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomContactNotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomContactNotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Contact = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -598,22 +633,22 @@ func (m districtMods) RandomContactNotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Address(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Address(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Address = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) AddressFunc(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) AddressFunc(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Address = f }) } // Clear any values for the column -func (m districtMods) UnsetAddress() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetAddress() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Address = nil }) } @@ -621,8 +656,8 @@ func (m districtMods) UnsetAddress() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomAddress(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomAddress(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Address = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -637,8 +672,8 @@ func (m districtMods) RandomAddress(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomAddressNotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomAddressNotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Address = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -651,22 +686,22 @@ func (m districtMods) RandomAddressNotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Regionid(val null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Regionid(val null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Regionid = func() null.Val[decimal.Decimal] { return val } }) } // Set the Column from the function -func (m districtMods) RegionidFunc(f func() null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RegionidFunc(f func() null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Regionid = f }) } // Clear any values for the column -func (m districtMods) UnsetRegionid() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetRegionid() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Regionid = nil }) } @@ -674,8 +709,8 @@ func (m districtMods) UnsetRegionid() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomRegionid(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomRegionid(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Regionid = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -690,8 +725,8 @@ func (m districtMods) RandomRegionid(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomRegionidNotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomRegionidNotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Regionid = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -704,22 +739,22 @@ func (m districtMods) RandomRegionidNotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) PostalCod(val null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) PostalCod(val null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.PostalCod = func() null.Val[decimal.Decimal] { return val } }) } // Set the Column from the function -func (m districtMods) PostalCodFunc(f func() null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) PostalCodFunc(f func() null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.PostalCod = f }) } // Clear any values for the column -func (m districtMods) UnsetPostalCod() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetPostalCod() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.PostalCod = nil }) } @@ -727,8 +762,8 @@ func (m districtMods) UnsetPostalCod() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomPostalCod(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomPostalCod(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.PostalCod = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -743,8 +778,8 @@ func (m districtMods) RandomPostalCod(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomPostalCodNotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomPostalCodNotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.PostalCod = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -757,22 +792,22 @@ func (m districtMods) RandomPostalCodNotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Phone1(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Phone1(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Phone1 = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) Phone1Func(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Phone1Func(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Phone1 = f }) } // Clear any values for the column -func (m districtMods) UnsetPhone1() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetPhone1() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Phone1 = nil }) } @@ -780,8 +815,8 @@ func (m districtMods) UnsetPhone1() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomPhone1(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomPhone1(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Phone1 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -796,8 +831,8 @@ func (m districtMods) RandomPhone1(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomPhone1NotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomPhone1NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Phone1 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -810,22 +845,22 @@ func (m districtMods) RandomPhone1NotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Fax1(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Fax1(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Fax1 = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) Fax1Func(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Fax1Func(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Fax1 = f }) } // Clear any values for the column -func (m districtMods) UnsetFax1() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetFax1() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Fax1 = nil }) } @@ -833,8 +868,8 @@ func (m districtMods) UnsetFax1() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomFax1(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomFax1(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Fax1 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -849,8 +884,8 @@ func (m districtMods) RandomFax1(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomFax1NotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomFax1NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Fax1 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -863,22 +898,22 @@ func (m districtMods) RandomFax1NotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Agency(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Agency(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Agency = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) AgencyFunc(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) AgencyFunc(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Agency = f }) } // Clear any values for the column -func (m districtMods) UnsetAgency() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetAgency() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Agency = nil }) } @@ -886,8 +921,8 @@ func (m districtMods) UnsetAgency() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomAgency(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomAgency(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Agency = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -902,8 +937,8 @@ func (m districtMods) RandomAgency(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomAgencyNotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomAgencyNotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Agency = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -916,22 +951,22 @@ func (m districtMods) RandomAgencyNotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Code1(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Code1(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Code1 = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) Code1Func(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Code1Func(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Code1 = f }) } // Clear any values for the column -func (m districtMods) UnsetCode1() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetCode1() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Code1 = nil }) } @@ -939,8 +974,8 @@ func (m districtMods) UnsetCode1() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomCode1(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomCode1(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Code1 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -955,8 +990,8 @@ func (m districtMods) RandomCode1(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomCode1NotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomCode1NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Code1 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -969,22 +1004,22 @@ func (m districtMods) RandomCode1NotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) City1(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) City1(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.City1 = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) City1Func(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) City1Func(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.City1 = f }) } // Clear any values for the column -func (m districtMods) UnsetCity1() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetCity1() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.City1 = nil }) } @@ -992,8 +1027,8 @@ func (m districtMods) UnsetCity1() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomCity1(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomCity1(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.City1 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1008,8 +1043,8 @@ func (m districtMods) RandomCity1(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomCity1NotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomCity1NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.City1 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1022,22 +1057,22 @@ func (m districtMods) RandomCity1NotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) ShapeLeng(val null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) ShapeLeng(val null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeLeng = func() null.Val[decimal.Decimal] { return val } }) } // Set the Column from the function -func (m districtMods) ShapeLengFunc(f func() null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) ShapeLengFunc(f func() null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeLeng = f }) } // Clear any values for the column -func (m districtMods) UnsetShapeLeng() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetShapeLeng() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeLeng = nil }) } @@ -1045,8 +1080,8 @@ func (m districtMods) UnsetShapeLeng() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomShapeLeng(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomShapeLeng(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeLeng = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -1061,8 +1096,8 @@ func (m districtMods) RandomShapeLeng(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomShapeLengNotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomShapeLengNotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeLeng = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -1075,22 +1110,22 @@ func (m districtMods) RandomShapeLengNotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Address2(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Address2(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Address2 = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) Address2Func(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Address2Func(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Address2 = f }) } // Clear any values for the column -func (m districtMods) UnsetAddress2() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetAddress2() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Address2 = nil }) } @@ -1098,8 +1133,8 @@ func (m districtMods) UnsetAddress2() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomAddress2(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomAddress2(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Address2 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1114,8 +1149,8 @@ func (m districtMods) RandomAddress2(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomAddress2NotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomAddress2NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Address2 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1128,22 +1163,22 @@ func (m districtMods) RandomAddress2NotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) GeneralMG(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) GeneralMG(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.GeneralMG = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) GeneralMGFunc(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) GeneralMGFunc(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.GeneralMG = f }) } // Clear any values for the column -func (m districtMods) UnsetGeneralMG() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetGeneralMG() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.GeneralMG = nil }) } @@ -1151,8 +1186,8 @@ func (m districtMods) UnsetGeneralMG() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomGeneralMG(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomGeneralMG(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.GeneralMG = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1167,8 +1202,8 @@ func (m districtMods) RandomGeneralMG(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomGeneralMGNotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomGeneralMGNotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.GeneralMG = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1181,22 +1216,22 @@ func (m districtMods) RandomGeneralMGNotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) City2(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) City2(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.City2 = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) City2Func(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) City2Func(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.City2 = f }) } // Clear any values for the column -func (m districtMods) UnsetCity2() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetCity2() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.City2 = nil }) } @@ -1204,8 +1239,8 @@ func (m districtMods) UnsetCity2() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomCity2(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomCity2(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.City2 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1220,8 +1255,8 @@ func (m districtMods) RandomCity2(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomCity2NotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomCity2NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.City2 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1234,22 +1269,22 @@ func (m districtMods) RandomCity2NotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) PostalC1(val null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) PostalC1(val null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.PostalC1 = func() null.Val[decimal.Decimal] { return val } }) } // Set the Column from the function -func (m districtMods) PostalC1Func(f func() null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) PostalC1Func(f func() null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.PostalC1 = f }) } // Clear any values for the column -func (m districtMods) UnsetPostalC1() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetPostalC1() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.PostalC1 = nil }) } @@ -1257,8 +1292,8 @@ func (m districtMods) UnsetPostalC1() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomPostalC1(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomPostalC1(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.PostalC1 = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -1273,8 +1308,8 @@ func (m districtMods) RandomPostalC1(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomPostalC1NotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomPostalC1NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.PostalC1 = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -1287,22 +1322,22 @@ func (m districtMods) RandomPostalC1NotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Fax2(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Fax2(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Fax2 = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) Fax2Func(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Fax2Func(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Fax2 = f }) } // Clear any values for the column -func (m districtMods) UnsetFax2() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetFax2() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Fax2 = nil }) } @@ -1310,8 +1345,8 @@ func (m districtMods) UnsetFax2() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomFax2(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomFax2(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Fax2 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1326,8 +1361,8 @@ func (m districtMods) RandomFax2(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomFax2NotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomFax2NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Fax2 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1340,22 +1375,22 @@ func (m districtMods) RandomFax2NotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Phone2(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Phone2(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Phone2 = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) Phone2Func(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Phone2Func(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Phone2 = f }) } // Clear any values for the column -func (m districtMods) UnsetPhone2() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetPhone2() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Phone2 = nil }) } @@ -1363,8 +1398,8 @@ func (m districtMods) UnsetPhone2() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomPhone2(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomPhone2(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Phone2 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1379,8 +1414,8 @@ func (m districtMods) RandomPhone2(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomPhone2NotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomPhone2NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Phone2 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1393,22 +1428,22 @@ func (m districtMods) RandomPhone2NotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) ShapeLe1(val null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) ShapeLe1(val null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeLe1 = func() null.Val[decimal.Decimal] { return val } }) } // Set the Column from the function -func (m districtMods) ShapeLe1Func(f func() null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) ShapeLe1Func(f func() null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeLe1 = f }) } // Clear any values for the column -func (m districtMods) UnsetShapeLe1() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetShapeLe1() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeLe1 = nil }) } @@ -1416,8 +1451,8 @@ func (m districtMods) UnsetShapeLe1() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomShapeLe1(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomShapeLe1(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeLe1 = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -1432,8 +1467,8 @@ func (m districtMods) RandomShapeLe1(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomShapeLe1NotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomShapeLe1NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeLe1 = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -1446,22 +1481,22 @@ func (m districtMods) RandomShapeLe1NotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) ShapeArea(val null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) ShapeArea(val null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeArea = func() null.Val[decimal.Decimal] { return val } }) } // Set the Column from the function -func (m districtMods) ShapeAreaFunc(f func() null.Val[decimal.Decimal]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) ShapeAreaFunc(f func() null.Val[decimal.Decimal]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeArea = f }) } // Clear any values for the column -func (m districtMods) UnsetShapeArea() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetShapeArea() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeArea = nil }) } @@ -1469,8 +1504,8 @@ func (m districtMods) UnsetShapeArea() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomShapeArea(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomShapeArea(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeArea = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -1485,8 +1520,8 @@ func (m districtMods) RandomShapeArea(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomShapeAreaNotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomShapeAreaNotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.ShapeArea = func() null.Val[decimal.Decimal] { if f == nil { f = &defaultFaker @@ -1499,22 +1534,22 @@ func (m districtMods) RandomShapeAreaNotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Geom(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Geom(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Geom = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) GeomFunc(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) GeomFunc(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Geom = f }) } // Clear any values for the column -func (m districtMods) UnsetGeom() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetGeom() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Geom = nil }) } @@ -1522,8 +1557,8 @@ func (m districtMods) UnsetGeom() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomGeom(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomGeom(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Geom = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1538,8 +1573,8 @@ func (m districtMods) RandomGeom(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomGeomNotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomGeomNotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Geom = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1552,22 +1587,22 @@ func (m districtMods) RandomGeomNotNull(f *faker.Faker) DistrictMod { } // Set the model columns to this value -func (m districtMods) Geom4326(val null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Geom4326(val null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Geom4326 = func() null.Val[string] { return val } }) } // Set the Column from the function -func (m districtMods) Geom4326Func(f func() null.Val[string]) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) Geom4326Func(f func() null.Val[string]) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Geom4326 = f }) } // Clear any values for the column -func (m districtMods) UnsetGeom4326() DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) UnsetGeom4326() ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Geom4326 = nil }) } @@ -1575,8 +1610,8 @@ func (m districtMods) UnsetGeom4326() DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is sometimes null -func (m districtMods) RandomGeom4326(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomGeom4326(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Geom4326 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1591,8 +1626,8 @@ func (m districtMods) RandomGeom4326(f *faker.Faker) DistrictMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used // The generated value is never null -func (m districtMods) RandomGeom4326NotNull(f *faker.Faker) DistrictMod { - return DistrictModFunc(func(_ context.Context, o *DistrictTemplate) { +func (m importDistrictMods) RandomGeom4326NotNull(f *faker.Faker) ImportDistrictMod { + return ImportDistrictModFunc(func(_ context.Context, o *ImportDistrictTemplate) { o.Geom4326 = func() null.Val[string] { if f == nil { f = &defaultFaker @@ -1604,11 +1639,46 @@ func (m districtMods) RandomGeom4326NotNull(f *faker.Faker) DistrictMod { }) } -func (m districtMods) WithParentsCascading() DistrictMod { - return DistrictModFunc(func(ctx context.Context, o *DistrictTemplate) { - if isDone, _ := districtWithParentsCascadingCtx.Value(ctx); isDone { +func (m importDistrictMods) WithParentsCascading() ImportDistrictMod { + return ImportDistrictModFunc(func(ctx context.Context, o *ImportDistrictTemplate) { + if isDone, _ := importDistrictWithParentsCascadingCtx.Value(ctx); isDone { return } - ctx = districtWithParentsCascadingCtx.WithValue(ctx, true) + ctx = importDistrictWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewOrganizationWithContext(ctx, OrganizationMods.WithParentsCascading()) + m.WithImportDistrictGidOrganization(related).Apply(ctx, o) + } + }) +} + +func (m importDistrictMods) WithImportDistrictGidOrganization(rel *OrganizationTemplate) ImportDistrictMod { + return ImportDistrictModFunc(func(ctx context.Context, o *ImportDistrictTemplate) { + o.r.ImportDistrictGidOrganization = &importDistrictRImportDistrictGidOrganizationR{ + o: rel, + } + }) +} + +func (m importDistrictMods) WithNewImportDistrictGidOrganization(mods ...OrganizationMod) ImportDistrictMod { + return ImportDistrictModFunc(func(ctx context.Context, o *ImportDistrictTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + + m.WithImportDistrictGidOrganization(related).Apply(ctx, o) + }) +} + +func (m importDistrictMods) WithExistingImportDistrictGidOrganization(em *models.Organization) ImportDistrictMod { + return ImportDistrictModFunc(func(ctx context.Context, o *ImportDistrictTemplate) { + o.r.ImportDistrictGidOrganization = &importDistrictRImportDistrictGidOrganizationR{ + o: o.f.FromExistingOrganization(em), + } + }) +} + +func (m importDistrictMods) WithoutImportDistrictGidOrganization() ImportDistrictMod { + return ImportDistrictModFunc(func(ctx context.Context, o *ImportDistrictTemplate) { + o.r.ImportDistrictGidOrganization = nil }) } diff --git a/db/factory/organization.bob.go b/db/factory/organization.bob.go index 73aa2bed..957d1c96 100644 --- a/db/factory/organization.bob.go +++ b/db/factory/organization.bob.go @@ -36,11 +36,13 @@ func (mods OrganizationModSlice) Apply(ctx context.Context, n *OrganizationTempl // OrganizationTemplate is an object representing the database table. // all columns are optional and should be set by mods type OrganizationTemplate struct { - ID func() int32 - Name func() string - ArcgisID func() null.Val[string] - ArcgisName func() null.Val[string] - FieldseekerURL func() null.Val[string] + ID func() int32 + Name func() string + ArcgisID func() null.Val[string] + ArcgisName func() null.Val[string] + FieldseekerURL func() null.Val[string] + ImportDistrictGid func() null.Val[int32] + Website func() null.Val[string] r organizationR f *Factory @@ -49,38 +51,39 @@ type OrganizationTemplate struct { } type organizationR struct { - Containerrelates []*organizationRContainerrelatesR - Fieldscoutinglogs []*organizationRFieldscoutinglogsR - Habitatrelates []*organizationRHabitatrelatesR - Inspectionsamples []*organizationRInspectionsamplesR - Inspectionsampledetails []*organizationRInspectionsampledetailsR - Linelocations []*organizationRLinelocationsR - Locationtrackings []*organizationRLocationtrackingsR - Mosquitoinspections []*organizationRMosquitoinspectionsR - Pointlocations []*organizationRPointlocationsR - Polygonlocations []*organizationRPolygonlocationsR - Pools []*organizationRPoolsR - Pooldetails []*organizationRPooldetailsR - Proposedtreatmentareas []*organizationRProposedtreatmentareasR - Qamosquitoinspections []*organizationRQamosquitoinspectionsR - Rodentlocations []*organizationRRodentlocationsR - Samplecollections []*organizationRSamplecollectionsR - Samplelocations []*organizationRSamplelocationsR - Servicerequests []*organizationRServicerequestsR - Speciesabundances []*organizationRSpeciesabundancesR - Stormdrains []*organizationRStormdrainsR - Timecards []*organizationRTimecardsR - Trapdata []*organizationRTrapdataR - Traplocations []*organizationRTraplocationsR - Treatments []*organizationRTreatmentsR - Treatmentareas []*organizationRTreatmentareasR - Zones []*organizationRZonesR - Zones2s []*organizationRZones2sR - FieldseekerSyncs []*organizationRFieldseekerSyncsR - H3Aggregations []*organizationRH3AggregationsR - NoteAudios []*organizationRNoteAudiosR - NoteImages []*organizationRNoteImagesR - User []*organizationRUserR + Containerrelates []*organizationRContainerrelatesR + Fieldscoutinglogs []*organizationRFieldscoutinglogsR + Habitatrelates []*organizationRHabitatrelatesR + Inspectionsamples []*organizationRInspectionsamplesR + Inspectionsampledetails []*organizationRInspectionsampledetailsR + Linelocations []*organizationRLinelocationsR + Locationtrackings []*organizationRLocationtrackingsR + Mosquitoinspections []*organizationRMosquitoinspectionsR + Pointlocations []*organizationRPointlocationsR + Polygonlocations []*organizationRPolygonlocationsR + Pools []*organizationRPoolsR + Pooldetails []*organizationRPooldetailsR + Proposedtreatmentareas []*organizationRProposedtreatmentareasR + Qamosquitoinspections []*organizationRQamosquitoinspectionsR + Rodentlocations []*organizationRRodentlocationsR + Samplecollections []*organizationRSamplecollectionsR + Samplelocations []*organizationRSamplelocationsR + Servicerequests []*organizationRServicerequestsR + Speciesabundances []*organizationRSpeciesabundancesR + Stormdrains []*organizationRStormdrainsR + Timecards []*organizationRTimecardsR + Trapdata []*organizationRTrapdataR + Traplocations []*organizationRTraplocationsR + Treatments []*organizationRTreatmentsR + Treatmentareas []*organizationRTreatmentareasR + Zones []*organizationRZonesR + Zones2s []*organizationRZones2sR + FieldseekerSyncs []*organizationRFieldseekerSyncsR + H3Aggregations []*organizationRH3AggregationsR + NoteAudios []*organizationRNoteAudiosR + NoteImages []*organizationRNoteImagesR + ImportDistrictGidDistrict *organizationRImportDistrictGidDistrictR + User []*organizationRUserR } type organizationRContainerrelatesR struct { @@ -207,6 +210,9 @@ type organizationRNoteImagesR struct { number int o *NoteImageTemplate } +type organizationRImportDistrictGidDistrictR struct { + o *ImportDistrictTemplate +} type organizationRUserR struct { number int o *UserTemplate @@ -625,6 +631,13 @@ func (t OrganizationTemplate) setModelRels(o *models.Organization) { o.R.NoteImages = rel } + if t.r.ImportDistrictGidDistrict != nil { + rel := t.r.ImportDistrictGidDistrict.o.Build() + rel.R.ImportDistrictGidOrganization = o + o.ImportDistrictGid = null.From(rel.Gid) // h2 + o.R.ImportDistrictGidDistrict = rel + } + if t.r.User != nil { rel := models.UserSlice{} for _, r := range t.r.User { @@ -664,6 +677,14 @@ func (o OrganizationTemplate) BuildSetter() *models.OrganizationSetter { val := o.FieldseekerURL() m.FieldseekerURL = omitnull.FromNull(val) } + if o.ImportDistrictGid != nil { + val := o.ImportDistrictGid() + m.ImportDistrictGid = omitnull.FromNull(val) + } + if o.Website != nil { + val := o.Website() + m.Website = omitnull.FromNull(val) + } return m } @@ -701,6 +722,12 @@ func (o OrganizationTemplate) Build() *models.Organization { if o.FieldseekerURL != nil { m.FieldseekerURL = o.FieldseekerURL() } + if o.ImportDistrictGid != nil { + m.ImportDistrictGid = o.ImportDistrictGid() + } + if o.Website != nil { + m.Website = o.Website() + } o.setModelRels(m) @@ -1353,6 +1380,25 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu } } + isImportDistrictGidDistrictDone, _ := organizationRelImportDistrictGidDistrictCtx.Value(ctx) + if !isImportDistrictGidDistrictDone && o.r.ImportDistrictGidDistrict != nil { + ctx = organizationRelImportDistrictGidDistrictCtx.WithValue(ctx, true) + if o.r.ImportDistrictGidDistrict.o.alreadyPersisted { + m.R.ImportDistrictGidDistrict = o.r.ImportDistrictGidDistrict.o.Build() + } else { + var rel31 *models.ImportDistrict + rel31, err = o.r.ImportDistrictGidDistrict.o.Create(ctx, exec) + if err != nil { + return err + } + err = m.AttachImportDistrictGidDistrict(ctx, exec, rel31) + if err != nil { + return err + } + } + + } + isUserDone, _ := organizationRelUserCtx.Value(ctx) if !isUserDone && o.r.User != nil { ctx = organizationRelUserCtx.WithValue(ctx, true) @@ -1360,12 +1406,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.User = append(m.R.User, r.o.Build()) } else { - rel31, err := r.o.CreateMany(ctx, exec, r.number) + rel32, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachUser(ctx, exec, rel31...) + err = m.AttachUser(ctx, exec, rel32...) if err != nil { return err } @@ -1470,6 +1516,8 @@ func (m organizationMods) RandomizeAllColumns(f *faker.Faker) OrganizationMod { OrganizationMods.RandomArcgisID(f), OrganizationMods.RandomArcgisName(f), OrganizationMods.RandomFieldseekerURL(f), + OrganizationMods.RandomImportDistrictGid(f), + OrganizationMods.RandomWebsite(f), } } @@ -1694,12 +1742,153 @@ func (m organizationMods) RandomFieldseekerURLNotNull(f *faker.Faker) Organizati }) } +// Set the model columns to this value +func (m organizationMods) ImportDistrictGid(val null.Val[int32]) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.ImportDistrictGid = func() null.Val[int32] { return val } + }) +} + +// Set the Column from the function +func (m organizationMods) ImportDistrictGidFunc(f func() null.Val[int32]) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.ImportDistrictGid = f + }) +} + +// Clear any values for the column +func (m organizationMods) UnsetImportDistrictGid() OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.ImportDistrictGid = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m organizationMods) RandomImportDistrictGid(f *faker.Faker) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.ImportDistrictGid = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m organizationMods) RandomImportDistrictGidNotNull(f *faker.Faker) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.ImportDistrictGid = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m organizationMods) Website(val null.Val[string]) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Website = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m organizationMods) WebsiteFunc(f func() null.Val[string]) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Website = f + }) +} + +// Clear any values for the column +func (m organizationMods) UnsetWebsite() OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Website = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m organizationMods) RandomWebsite(f *faker.Faker) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Website = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m organizationMods) RandomWebsiteNotNull(f *faker.Faker) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Website = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + func (m organizationMods) WithParentsCascading() OrganizationMod { return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { if isDone, _ := organizationWithParentsCascadingCtx.Value(ctx); isDone { return } ctx = organizationWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewImportDistrictWithContext(ctx, ImportDistrictMods.WithParentsCascading()) + m.WithImportDistrictGidDistrict(related).Apply(ctx, o) + } + }) +} + +func (m organizationMods) WithImportDistrictGidDistrict(rel *ImportDistrictTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.ImportDistrictGidDistrict = &organizationRImportDistrictGidDistrictR{ + o: rel, + } + }) +} + +func (m organizationMods) WithNewImportDistrictGidDistrict(mods ...ImportDistrictMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewImportDistrictWithContext(ctx, mods...) + + m.WithImportDistrictGidDistrict(related).Apply(ctx, o) + }) +} + +func (m organizationMods) WithExistingImportDistrictGidDistrict(em *models.ImportDistrict) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.ImportDistrictGidDistrict = &organizationRImportDistrictGidDistrictR{ + o: o.f.FromExistingImportDistrict(em), + } + }) +} + +func (m organizationMods) WithoutImportDistrictGidDistrict() OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.ImportDistrictGidDistrict = nil }) } diff --git a/db/migrations/00034_district.sql b/db/migrations/00034_district.sql new file mode 100644 index 00000000..603a0064 --- /dev/null +++ b/db/migrations/00034_district.sql @@ -0,0 +1,6 @@ +-- +goose Up +ALTER TABLE public.organization ADD COLUMN import_district_gid INTEGER UNIQUE REFERENCES import.district(gid); +ALTER TABLE public.organization ADD COLUMN website TEXT UNIQUE; +-- +goose Down +ALTER TABLE public.organization DROP COLUMN website; +ALTER TABLE public.organization DROP COLUMN import_district_gid; diff --git a/db/models/bob_joins.bob.go b/db/models/bob_joins.bob.go index 03d85cb1..ef21e961 100644 --- a/db/models/bob_joins.bob.go +++ b/db/models/bob_joins.bob.go @@ -63,6 +63,7 @@ type joins[Q dialect.Joinable] struct { FieldseekerZones2s joinSet[fieldseekerZones2Joins[Q]] FieldseekerSyncs joinSet[fieldseekerSyncJoins[Q]] H3Aggregations joinSet[h3AggregationJoins[Q]] + ImportDistricts joinSet[importDistrictJoins[Q]] NoteAudios joinSet[noteAudioJoins[Q]] NoteAudioBreadcrumbs joinSet[noteAudioBreadcrumbJoins[Q]] NoteAudioData joinSet[noteAudioDatumJoins[Q]] @@ -120,6 +121,7 @@ func getJoins[Q dialect.Joinable]() joins[Q] { FieldseekerZones2s: buildJoinSet[fieldseekerZones2Joins[Q]](FieldseekerZones2s.Columns, buildFieldseekerZones2Joins), FieldseekerSyncs: buildJoinSet[fieldseekerSyncJoins[Q]](FieldseekerSyncs.Columns, buildFieldseekerSyncJoins), H3Aggregations: buildJoinSet[h3AggregationJoins[Q]](H3Aggregations.Columns, buildH3AggregationJoins), + ImportDistricts: buildJoinSet[importDistrictJoins[Q]](ImportDistricts.Columns, buildImportDistrictJoins), NoteAudios: buildJoinSet[noteAudioJoins[Q]](NoteAudios.Columns, buildNoteAudioJoins), NoteAudioBreadcrumbs: buildJoinSet[noteAudioBreadcrumbJoins[Q]](NoteAudioBreadcrumbs.Columns, buildNoteAudioBreadcrumbJoins), NoteAudioData: buildJoinSet[noteAudioDatumJoins[Q]](NoteAudioData.Columns, buildNoteAudioDatumJoins), diff --git a/db/models/bob_loaders.bob.go b/db/models/bob_loaders.bob.go index 21748f91..50d02a99 100644 --- a/db/models/bob_loaders.bob.go +++ b/db/models/bob_loaders.bob.go @@ -48,6 +48,7 @@ type preloaders struct { FieldseekerZones2 fieldseekerZones2Preloader FieldseekerSync fieldseekerSyncPreloader H3Aggregation h3AggregationPreloader + ImportDistrict importDistrictPreloader NoteAudio noteAudioPreloader NoteAudioBreadcrumb noteAudioBreadcrumbPreloader NoteAudioDatum noteAudioDatumPreloader @@ -97,6 +98,7 @@ func getPreloaders() preloaders { FieldseekerZones2: buildFieldseekerZones2Preloader(), FieldseekerSync: buildFieldseekerSyncPreloader(), H3Aggregation: buildH3AggregationPreloader(), + ImportDistrict: buildImportDistrictPreloader(), NoteAudio: buildNoteAudioPreloader(), NoteAudioBreadcrumb: buildNoteAudioBreadcrumbPreloader(), NoteAudioDatum: buildNoteAudioDatumPreloader(), @@ -152,6 +154,7 @@ type thenLoaders[Q orm.Loadable] struct { FieldseekerZones2 fieldseekerZones2ThenLoader[Q] FieldseekerSync fieldseekerSyncThenLoader[Q] H3Aggregation h3AggregationThenLoader[Q] + ImportDistrict importDistrictThenLoader[Q] NoteAudio noteAudioThenLoader[Q] NoteAudioBreadcrumb noteAudioBreadcrumbThenLoader[Q] NoteAudioDatum noteAudioDatumThenLoader[Q] @@ -201,6 +204,7 @@ func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] { FieldseekerZones2: buildFieldseekerZones2ThenLoader[Q](), FieldseekerSync: buildFieldseekerSyncThenLoader[Q](), H3Aggregation: buildH3AggregationThenLoader[Q](), + ImportDistrict: buildImportDistrictThenLoader[Q](), NoteAudio: buildNoteAudioThenLoader[Q](), NoteAudioBreadcrumb: buildNoteAudioBreadcrumbThenLoader[Q](), NoteAudioDatum: buildNoteAudioDatumThenLoader[Q](), diff --git a/db/models/bob_where.bob.go b/db/models/bob_where.bob.go index 69ff2736..046968ad 100644 --- a/db/models/bob_where.bob.go +++ b/db/models/bob_where.bob.go @@ -19,7 +19,6 @@ var ( func Where[Q psql.Filterable]() struct { ArcgisUsers arcgisuserWhere[Q] ArcgisUserPrivileges arcgisUserPrivilegeWhere[Q] - Districts districtWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] FieldseekerHabitatrelates fieldseekerHabitatrelateWhere[Q] @@ -52,6 +51,7 @@ func Where[Q psql.Filterable]() struct { GeometryColumns geometryColumnWhere[Q] GooseDBVersions gooseDBVersionWhere[Q] H3Aggregations h3AggregationWhere[Q] + ImportDistricts importDistrictWhere[Q] NoteAudios noteAudioWhere[Q] NoteAudioBreadcrumbs noteAudioBreadcrumbWhere[Q] NoteAudioData noteAudioDatumWhere[Q] @@ -76,7 +76,6 @@ func Where[Q psql.Filterable]() struct { return struct { ArcgisUsers arcgisuserWhere[Q] ArcgisUserPrivileges arcgisUserPrivilegeWhere[Q] - Districts districtWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] FieldseekerHabitatrelates fieldseekerHabitatrelateWhere[Q] @@ -109,6 +108,7 @@ func Where[Q psql.Filterable]() struct { GeometryColumns geometryColumnWhere[Q] GooseDBVersions gooseDBVersionWhere[Q] H3Aggregations h3AggregationWhere[Q] + ImportDistricts importDistrictWhere[Q] NoteAudios noteAudioWhere[Q] NoteAudioBreadcrumbs noteAudioBreadcrumbWhere[Q] NoteAudioData noteAudioDatumWhere[Q] @@ -132,7 +132,6 @@ func Where[Q psql.Filterable]() struct { }{ ArcgisUsers: buildArcgisUserWhere[Q](ArcgisUsers.Columns), ArcgisUserPrivileges: buildArcgisUserPrivilegeWhere[Q](ArcgisUserPrivileges.Columns), - Districts: buildDistrictWhere[Q](Districts.Columns), FieldseekerContainerrelates: buildFieldseekerContainerrelateWhere[Q](FieldseekerContainerrelates.Columns), FieldseekerFieldscoutinglogs: buildFieldseekerFieldscoutinglogWhere[Q](FieldseekerFieldscoutinglogs.Columns), FieldseekerHabitatrelates: buildFieldseekerHabitatrelateWhere[Q](FieldseekerHabitatrelates.Columns), @@ -165,6 +164,7 @@ func Where[Q psql.Filterable]() struct { GeometryColumns: buildGeometryColumnWhere[Q](GeometryColumns.Columns), GooseDBVersions: buildGooseDBVersionWhere[Q](GooseDBVersions.Columns), H3Aggregations: buildH3AggregationWhere[Q](H3Aggregations.Columns), + ImportDistricts: buildImportDistrictWhere[Q](ImportDistricts.Columns), NoteAudios: buildNoteAudioWhere[Q](NoteAudios.Columns), NoteAudioBreadcrumbs: buildNoteAudioBreadcrumbWhere[Q](NoteAudioBreadcrumbs.Columns), NoteAudioData: buildNoteAudioDatumWhere[Q](NoteAudioData.Columns), diff --git a/db/models/district.bob.go b/db/models/import.district.bob.go similarity index 60% rename from db/models/district.bob.go rename to db/models/import.district.bob.go index a0c3bb5b..6f922256 100644 --- a/db/models/district.bob.go +++ b/db/models/import.district.bob.go @@ -5,6 +5,7 @@ package models import ( "context" + "fmt" "io" "github.com/aarondl/opt/null" @@ -18,10 +19,13 @@ import ( "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" ) -// District is an object representing the database table. -type District struct { +// ImportDistrict is an object representing the database table. +type ImportDistrict struct { Gid int32 `db:"gid,pk" ` ID null.Val[decimal.Decimal] `db:"id" ` Website null.Val[string] `db:"website" ` @@ -45,23 +49,30 @@ type District struct { ShapeArea null.Val[decimal.Decimal] `db:"shape_area" ` Geom null.Val[string] `db:"geom" ` Geom4326 null.Val[string] `db:"geom_4326,generated" ` + + R importDistrictR `db:"-" ` } -// DistrictSlice is an alias for a slice of pointers to District. -// This should almost always be used instead of []*District. -type DistrictSlice []*District +// ImportDistrictSlice is an alias for a slice of pointers to ImportDistrict. +// This should almost always be used instead of []*ImportDistrict. +type ImportDistrictSlice []*ImportDistrict -// Districts contains methods to work with the district table -var Districts = psql.NewTablex[*District, DistrictSlice, *DistrictSetter]("", "district", buildDistrictColumns("district")) +// ImportDistricts contains methods to work with the district table +var ImportDistricts = psql.NewTablex[*ImportDistrict, ImportDistrictSlice, *ImportDistrictSetter]("import", "district", buildImportDistrictColumns("import.district")) -// DistrictsQuery is a query on the district table -type DistrictsQuery = *psql.ViewQuery[*District, DistrictSlice] +// ImportDistrictsQuery is a query on the district table +type ImportDistrictsQuery = *psql.ViewQuery[*ImportDistrict, ImportDistrictSlice] -func buildDistrictColumns(alias string) districtColumns { - return districtColumns{ +// importDistrictR is where relationships are stored. +type importDistrictR struct { + ImportDistrictGidOrganization *Organization // organization.organization_import_district_gid_fkey +} + +func buildImportDistrictColumns(alias string) importDistrictColumns { + return importDistrictColumns{ ColumnsExpr: expr.NewColumnsExpr( "gid", "id", "website", "contact", "address", "regionid", "postal_cod", "phone1", "fax1", "agency", "code1", "city1", "shape_leng", "address2", "general_mg", "city2", "postal_c_1", "fax2", "phone2", "shape_le_1", "shape_area", "geom", "geom_4326", - ).WithParent("district"), + ).WithParent("import.district"), tableAlias: alias, Gid: psql.Quote(alias, "gid"), ID: psql.Quote(alias, "id"), @@ -89,7 +100,7 @@ func buildDistrictColumns(alias string) districtColumns { } } -type districtColumns struct { +type importDistrictColumns struct { expr.ColumnsExpr tableAlias string Gid psql.Expression @@ -117,18 +128,18 @@ type districtColumns struct { Geom4326 psql.Expression } -func (c districtColumns) Alias() string { +func (c importDistrictColumns) Alias() string { return c.tableAlias } -func (districtColumns) AliasedAs(alias string) districtColumns { - return buildDistrictColumns(alias) +func (importDistrictColumns) AliasedAs(alias string) importDistrictColumns { + return buildImportDistrictColumns(alias) } -// DistrictSetter is used for insert/upsert/update operations +// ImportDistrictSetter is used for insert/upsert/update operations // All values are optional, and do not have to be set // Generated columns are not included -type DistrictSetter struct { +type ImportDistrictSetter struct { Gid omit.Val[int32] `db:"gid,pk" ` ID omitnull.Val[decimal.Decimal] `db:"id" ` Website omitnull.Val[string] `db:"website" ` @@ -153,7 +164,7 @@ type DistrictSetter struct { Geom omitnull.Val[string] `db:"geom" ` } -func (s DistrictSetter) SetColumns() []string { +func (s ImportDistrictSetter) SetColumns() []string { vals := make([]string, 0, 22) if s.Gid.IsValue() { vals = append(vals, "gid") @@ -224,7 +235,7 @@ func (s DistrictSetter) SetColumns() []string { return vals } -func (s DistrictSetter) Overwrite(t *District) { +func (s ImportDistrictSetter) Overwrite(t *ImportDistrict) { if s.Gid.IsValue() { t.Gid = s.Gid.MustGet() } @@ -293,9 +304,9 @@ func (s DistrictSetter) Overwrite(t *District) { } } -func (s *DistrictSetter) Apply(q *dialect.InsertQuery) { +func (s *ImportDistrictSetter) Apply(q *dialect.InsertQuery) { q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { - return Districts.BeforeInsertHooks.RunHooks(ctx, exec, s) + return ImportDistricts.BeforeInsertHooks.RunHooks(ctx, exec, s) }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { @@ -436,11 +447,11 @@ func (s *DistrictSetter) Apply(q *dialect.InsertQuery) { })) } -func (s DistrictSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { +func (s ImportDistrictSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { return um.Set(s.Expressions()...) } -func (s DistrictSetter) Expressions(prefix ...string) []bob.Expression { +func (s ImportDistrictSetter) Expressions(prefix ...string) []bob.Expression { exprs := make([]bob.Expression, 0, 22) if s.Gid.IsValue() { @@ -600,113 +611,114 @@ func (s DistrictSetter) Expressions(prefix ...string) []bob.Expression { return exprs } -// FindDistrict retrieves a single record by primary key +// FindImportDistrict retrieves a single record by primary key // If cols is empty Find will return all columns. -func FindDistrict(ctx context.Context, exec bob.Executor, GidPK int32, cols ...string) (*District, error) { +func FindImportDistrict(ctx context.Context, exec bob.Executor, GidPK int32, cols ...string) (*ImportDistrict, error) { if len(cols) == 0 { - return Districts.Query( - sm.Where(Districts.Columns.Gid.EQ(psql.Arg(GidPK))), + return ImportDistricts.Query( + sm.Where(ImportDistricts.Columns.Gid.EQ(psql.Arg(GidPK))), ).One(ctx, exec) } - return Districts.Query( - sm.Where(Districts.Columns.Gid.EQ(psql.Arg(GidPK))), - sm.Columns(Districts.Columns.Only(cols...)), + return ImportDistricts.Query( + sm.Where(ImportDistricts.Columns.Gid.EQ(psql.Arg(GidPK))), + sm.Columns(ImportDistricts.Columns.Only(cols...)), ).One(ctx, exec) } -// DistrictExists checks the presence of a single record by primary key -func DistrictExists(ctx context.Context, exec bob.Executor, GidPK int32) (bool, error) { - return Districts.Query( - sm.Where(Districts.Columns.Gid.EQ(psql.Arg(GidPK))), +// ImportDistrictExists checks the presence of a single record by primary key +func ImportDistrictExists(ctx context.Context, exec bob.Executor, GidPK int32) (bool, error) { + return ImportDistricts.Query( + sm.Where(ImportDistricts.Columns.Gid.EQ(psql.Arg(GidPK))), ).Exists(ctx, exec) } -// AfterQueryHook is called after District is retrieved from the database -func (o *District) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { +// AfterQueryHook is called after ImportDistrict is retrieved from the database +func (o *ImportDistrict) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { var err error switch queryType { case bob.QueryTypeSelect: - ctx, err = Districts.AfterSelectHooks.RunHooks(ctx, exec, DistrictSlice{o}) + ctx, err = ImportDistricts.AfterSelectHooks.RunHooks(ctx, exec, ImportDistrictSlice{o}) case bob.QueryTypeInsert: - ctx, err = Districts.AfterInsertHooks.RunHooks(ctx, exec, DistrictSlice{o}) + ctx, err = ImportDistricts.AfterInsertHooks.RunHooks(ctx, exec, ImportDistrictSlice{o}) case bob.QueryTypeUpdate: - ctx, err = Districts.AfterUpdateHooks.RunHooks(ctx, exec, DistrictSlice{o}) + ctx, err = ImportDistricts.AfterUpdateHooks.RunHooks(ctx, exec, ImportDistrictSlice{o}) case bob.QueryTypeDelete: - ctx, err = Districts.AfterDeleteHooks.RunHooks(ctx, exec, DistrictSlice{o}) + ctx, err = ImportDistricts.AfterDeleteHooks.RunHooks(ctx, exec, ImportDistrictSlice{o}) } return err } -// primaryKeyVals returns the primary key values of the District -func (o *District) primaryKeyVals() bob.Expression { +// primaryKeyVals returns the primary key values of the ImportDistrict +func (o *ImportDistrict) primaryKeyVals() bob.Expression { return psql.Arg(o.Gid) } -func (o *District) pkEQ() dialect.Expression { - return psql.Quote("district", "gid").EQ(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { +func (o *ImportDistrict) pkEQ() dialect.Expression { + return psql.Quote("import.district", "gid").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 District -func (o *District) Update(ctx context.Context, exec bob.Executor, s *DistrictSetter) error { - v, err := Districts.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) +// Update uses an executor to update the ImportDistrict +func (o *ImportDistrict) Update(ctx context.Context, exec bob.Executor, s *ImportDistrictSetter) error { + v, err := ImportDistricts.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 District record with an executor -func (o *District) Delete(ctx context.Context, exec bob.Executor) error { - _, err := Districts.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) +// Delete deletes a single ImportDistrict record with an executor +func (o *ImportDistrict) Delete(ctx context.Context, exec bob.Executor) error { + _, err := ImportDistricts.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) return err } -// Reload refreshes the District using the executor -func (o *District) Reload(ctx context.Context, exec bob.Executor) error { - o2, err := Districts.Query( - sm.Where(Districts.Columns.Gid.EQ(psql.Arg(o.Gid))), +// Reload refreshes the ImportDistrict using the executor +func (o *ImportDistrict) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := ImportDistricts.Query( + sm.Where(ImportDistricts.Columns.Gid.EQ(psql.Arg(o.Gid))), ).One(ctx, exec) if err != nil { return err } - + o2.R = o.R *o = *o2 return nil } -// AfterQueryHook is called after DistrictSlice is retrieved from the database -func (o DistrictSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { +// AfterQueryHook is called after ImportDistrictSlice is retrieved from the database +func (o ImportDistrictSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { var err error switch queryType { case bob.QueryTypeSelect: - ctx, err = Districts.AfterSelectHooks.RunHooks(ctx, exec, o) + ctx, err = ImportDistricts.AfterSelectHooks.RunHooks(ctx, exec, o) case bob.QueryTypeInsert: - ctx, err = Districts.AfterInsertHooks.RunHooks(ctx, exec, o) + ctx, err = ImportDistricts.AfterInsertHooks.RunHooks(ctx, exec, o) case bob.QueryTypeUpdate: - ctx, err = Districts.AfterUpdateHooks.RunHooks(ctx, exec, o) + ctx, err = ImportDistricts.AfterUpdateHooks.RunHooks(ctx, exec, o) case bob.QueryTypeDelete: - ctx, err = Districts.AfterDeleteHooks.RunHooks(ctx, exec, o) + ctx, err = ImportDistricts.AfterDeleteHooks.RunHooks(ctx, exec, o) } return err } -func (o DistrictSlice) pkIN() dialect.Expression { +func (o ImportDistrictSlice) pkIN() dialect.Expression { if len(o) == 0 { return psql.Raw("NULL") } - return psql.Quote("district", "gid").In(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return psql.Quote("import.district", "gid").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() @@ -718,13 +730,13 @@ func (o DistrictSlice) pkIN() dialect.Expression { // 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 DistrictSlice) copyMatchingRows(from ...*District) { +func (o ImportDistrictSlice) copyMatchingRows(from ...*ImportDistrict) { for i, old := range o { for _, new := range from { if new.Gid != old.Gid { continue } - + new.R = old.R o[i] = new break } @@ -732,25 +744,25 @@ func (o DistrictSlice) copyMatchingRows(from ...*District) { } // UpdateMod modifies an update query with "WHERE primary_key IN (o...)" -func (o DistrictSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { +func (o ImportDistrictSlice) 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 Districts.BeforeUpdateHooks.RunHooks(ctx, exec, o) + return ImportDistricts.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 *District: + case *ImportDistrict: o.copyMatchingRows(retrieved) - case []*District: + case []*ImportDistrict: o.copyMatchingRows(retrieved...) - case DistrictSlice: + case ImportDistrictSlice: o.copyMatchingRows(retrieved...) default: - // If the retrieved value is not a District or a slice of District + // If the retrieved value is not a ImportDistrict or a slice of ImportDistrict // then run the AfterUpdateHooks on the slice - _, err = Districts.AfterUpdateHooks.RunHooks(ctx, exec, o) + _, err = ImportDistricts.AfterUpdateHooks.RunHooks(ctx, exec, o) } return err @@ -761,25 +773,25 @@ func (o DistrictSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } // DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" -func (o DistrictSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { +func (o ImportDistrictSlice) 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 Districts.BeforeDeleteHooks.RunHooks(ctx, exec, o) + return ImportDistricts.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 *District: + case *ImportDistrict: o.copyMatchingRows(retrieved) - case []*District: + case []*ImportDistrict: o.copyMatchingRows(retrieved...) - case DistrictSlice: + case ImportDistrictSlice: o.copyMatchingRows(retrieved...) default: - // If the retrieved value is not a District or a slice of District + // If the retrieved value is not a ImportDistrict or a slice of ImportDistrict // then run the AfterDeleteHooks on the slice - _, err = Districts.AfterDeleteHooks.RunHooks(ctx, exec, o) + _, err = ImportDistricts.AfterDeleteHooks.RunHooks(ctx, exec, o) } return err @@ -789,30 +801,30 @@ func (o DistrictSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { }) } -func (o DistrictSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals DistrictSetter) error { +func (o ImportDistrictSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals ImportDistrictSetter) error { if len(o) == 0 { return nil } - _, err := Districts.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + _, err := ImportDistricts.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) return err } -func (o DistrictSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { +func (o ImportDistrictSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { if len(o) == 0 { return nil } - _, err := Districts.Delete(o.DeleteMod()).Exec(ctx, exec) + _, err := ImportDistricts.Delete(o.DeleteMod()).Exec(ctx, exec) return err } -func (o DistrictSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { +func (o ImportDistrictSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { if len(o) == 0 { return nil } - o2, err := Districts.Query(sm.Where(o.pkIN())).All(ctx, exec) + o2, err := ImportDistricts.Query(sm.Where(o.pkIN())).All(ctx, exec) if err != nil { return err } @@ -822,7 +834,85 @@ func (o DistrictSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { return nil } -type districtWhere[Q psql.Filterable] struct { +// ImportDistrictGidOrganization starts a query for related objects on organization +func (o *ImportDistrict) ImportDistrictGidOrganization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + return Organizations.Query(append(mods, + sm.Where(Organizations.Columns.ImportDistrictGid.EQ(psql.Arg(o.Gid))), + )...) +} + +func (os ImportDistrictSlice) ImportDistrictGidOrganization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + pkGid := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkGid = append(pkGid, o.Gid) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkGid), "integer[]")), + )) + + return Organizations.Query(append(mods, + sm.Where(psql.Group(Organizations.Columns.ImportDistrictGid).OP("IN", PKArgExpr)), + )...) +} + +func insertImportDistrictImportDistrictGidOrganization0(ctx context.Context, exec bob.Executor, organization1 *OrganizationSetter, importDistrict0 *ImportDistrict) (*Organization, error) { + organization1.ImportDistrictGid = omitnull.From(importDistrict0.Gid) + + ret, err := Organizations.Insert(organization1).One(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertImportDistrictImportDistrictGidOrganization0: %w", err) + } + + return ret, nil +} + +func attachImportDistrictImportDistrictGidOrganization0(ctx context.Context, exec bob.Executor, count int, organization1 *Organization, importDistrict0 *ImportDistrict) (*Organization, error) { + setter := &OrganizationSetter{ + ImportDistrictGid: omitnull.From(importDistrict0.Gid), + } + + err := organization1.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachImportDistrictImportDistrictGidOrganization0: %w", err) + } + + return organization1, nil +} + +func (importDistrict0 *ImportDistrict) InsertImportDistrictGidOrganization(ctx context.Context, exec bob.Executor, related *OrganizationSetter) error { + var err error + + organization1, err := insertImportDistrictImportDistrictGidOrganization0(ctx, exec, related, importDistrict0) + if err != nil { + return err + } + + importDistrict0.R.ImportDistrictGidOrganization = organization1 + + organization1.R.ImportDistrictGidDistrict = importDistrict0 + + return nil +} + +func (importDistrict0 *ImportDistrict) AttachImportDistrictGidOrganization(ctx context.Context, exec bob.Executor, organization1 *Organization) error { + var err error + + _, err = attachImportDistrictImportDistrictGidOrganization0(ctx, exec, 1, organization1, importDistrict0) + if err != nil { + return err + } + + importDistrict0.R.ImportDistrictGidOrganization = organization1 + + organization1.R.ImportDistrictGidDistrict = importDistrict0 + + return nil +} + +type importDistrictWhere[Q psql.Filterable] struct { Gid psql.WhereMod[Q, int32] ID psql.WhereNullMod[Q, decimal.Decimal] Website psql.WhereNullMod[Q, string] @@ -848,12 +938,12 @@ type districtWhere[Q psql.Filterable] struct { Geom4326 psql.WhereNullMod[Q, string] } -func (districtWhere[Q]) AliasedAs(alias string) districtWhere[Q] { - return buildDistrictWhere[Q](buildDistrictColumns(alias)) +func (importDistrictWhere[Q]) AliasedAs(alias string) importDistrictWhere[Q] { + return buildImportDistrictWhere[Q](buildImportDistrictColumns(alias)) } -func buildDistrictWhere[Q psql.Filterable](cols districtColumns) districtWhere[Q] { - return districtWhere[Q]{ +func buildImportDistrictWhere[Q psql.Filterable](cols importDistrictColumns) importDistrictWhere[Q] { + return importDistrictWhere[Q]{ Gid: psql.Where[Q, int32](cols.Gid), ID: psql.WhereNull[Q, decimal.Decimal](cols.ID), Website: psql.WhereNull[Q, string](cols.Website), @@ -879,3 +969,151 @@ func buildDistrictWhere[Q psql.Filterable](cols districtColumns) districtWhere[Q Geom4326: psql.WhereNull[Q, string](cols.Geom4326), } } + +func (o *ImportDistrict) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "ImportDistrictGidOrganization": + rel, ok := retrieved.(*Organization) + if !ok { + return fmt.Errorf("importDistrict cannot load %T as %q", retrieved, name) + } + + o.R.ImportDistrictGidOrganization = rel + + if rel != nil { + rel.R.ImportDistrictGidDistrict = o + } + return nil + default: + return fmt.Errorf("importDistrict has no relationship %q", name) + } +} + +type importDistrictPreloader struct { + ImportDistrictGidOrganization func(...psql.PreloadOption) psql.Preloader +} + +func buildImportDistrictPreloader() importDistrictPreloader { + return importDistrictPreloader{ + ImportDistrictGidOrganization: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*Organization, OrganizationSlice](psql.PreloadRel{ + Name: "ImportDistrictGidOrganization", + Sides: []psql.PreloadSide{ + { + From: ImportDistricts, + To: Organizations, + FromColumns: []string{"gid"}, + ToColumns: []string{"import_district_gid"}, + }, + }, + }, Organizations.Columns.Names(), opts...) + }, + } +} + +type importDistrictThenLoader[Q orm.Loadable] struct { + ImportDistrictGidOrganization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildImportDistrictThenLoader[Q orm.Loadable]() importDistrictThenLoader[Q] { + type ImportDistrictGidOrganizationLoadInterface interface { + LoadImportDistrictGidOrganization(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return importDistrictThenLoader[Q]{ + ImportDistrictGidOrganization: thenLoadBuilder[Q]( + "ImportDistrictGidOrganization", + func(ctx context.Context, exec bob.Executor, retrieved ImportDistrictGidOrganizationLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadImportDistrictGidOrganization(ctx, exec, mods...) + }, + ), + } +} + +// LoadImportDistrictGidOrganization loads the importDistrict's ImportDistrictGidOrganization into the .R struct +func (o *ImportDistrict) LoadImportDistrictGidOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.ImportDistrictGidOrganization = nil + + related, err := o.ImportDistrictGidOrganization(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.ImportDistrictGidDistrict = o + + o.R.ImportDistrictGidOrganization = related + return nil +} + +// LoadImportDistrictGidOrganization loads the importDistrict's ImportDistrictGidOrganization into the .R struct +func (os ImportDistrictSlice) LoadImportDistrictGidOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + organizations, err := os.ImportDistrictGidOrganization(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range organizations { + + if !rel.ImportDistrictGid.IsValue() { + continue + } + if !(rel.ImportDistrictGid.IsValue() && o.Gid == rel.ImportDistrictGid.MustGet()) { + continue + } + + rel.R.ImportDistrictGidDistrict = o + + o.R.ImportDistrictGidOrganization = rel + break + } + } + + return nil +} + +type importDistrictJoins[Q dialect.Joinable] struct { + typ string + ImportDistrictGidOrganization modAs[Q, organizationColumns] +} + +func (j importDistrictJoins[Q]) aliasedAs(alias string) importDistrictJoins[Q] { + return buildImportDistrictJoins[Q](buildImportDistrictColumns(alias), j.typ) +} + +func buildImportDistrictJoins[Q dialect.Joinable](cols importDistrictColumns, typ string) importDistrictJoins[Q] { + return importDistrictJoins[Q]{ + typ: typ, + ImportDistrictGidOrganization: modAs[Q, organizationColumns]{ + c: Organizations.Columns, + f: func(to organizationColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Organizations.Name().As(to.Alias())).On( + to.ImportDistrictGid.EQ(cols.Gid), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/organization.bob.go b/db/models/organization.bob.go index 256a8f90..c735bef2 100644 --- a/db/models/organization.bob.go +++ b/db/models/organization.bob.go @@ -25,11 +25,13 @@ import ( // Organization is an object representing the database table. type Organization struct { - ID int32 `db:"id,pk" ` - Name string `db:"name" ` - ArcgisID null.Val[string] `db:"arcgis_id" ` - ArcgisName null.Val[string] `db:"arcgis_name" ` - FieldseekerURL null.Val[string] `db:"fieldseeker_url" ` + ID int32 `db:"id,pk" ` + Name string `db:"name" ` + ArcgisID null.Val[string] `db:"arcgis_id" ` + ArcgisName null.Val[string] `db:"arcgis_name" ` + FieldseekerURL null.Val[string] `db:"fieldseeker_url" ` + ImportDistrictGid null.Val[int32] `db:"import_district_gid" ` + Website null.Val[string] `db:"website" ` R organizationR `db:"-" ` @@ -48,62 +50,67 @@ type OrganizationsQuery = *psql.ViewQuery[*Organization, OrganizationSlice] // organizationR is where relationships are stored. type organizationR struct { - Containerrelates FieldseekerContainerrelateSlice // fieldseeker.containerrelate.containerrelate_organization_id_fkey - Fieldscoutinglogs FieldseekerFieldscoutinglogSlice // fieldseeker.fieldscoutinglog.fieldscoutinglog_organization_id_fkey - Habitatrelates FieldseekerHabitatrelateSlice // fieldseeker.habitatrelate.habitatrelate_organization_id_fkey - Inspectionsamples FieldseekerInspectionsampleSlice // fieldseeker.inspectionsample.inspectionsample_organization_id_fkey - Inspectionsampledetails FieldseekerInspectionsampledetailSlice // fieldseeker.inspectionsampledetail.inspectionsampledetail_organization_id_fkey - Linelocations FieldseekerLinelocationSlice // fieldseeker.linelocation.linelocation_organization_id_fkey - Locationtrackings FieldseekerLocationtrackingSlice // fieldseeker.locationtracking.locationtracking_organization_id_fkey - Mosquitoinspections FieldseekerMosquitoinspectionSlice // fieldseeker.mosquitoinspection.mosquitoinspection_organization_id_fkey - Pointlocations FieldseekerPointlocationSlice // fieldseeker.pointlocation.pointlocation_organization_id_fkey - Polygonlocations FieldseekerPolygonlocationSlice // fieldseeker.polygonlocation.polygonlocation_organization_id_fkey - Pools FieldseekerPoolSlice // fieldseeker.pool.pool_organization_id_fkey - Pooldetails FieldseekerPooldetailSlice // fieldseeker.pooldetail.pooldetail_organization_id_fkey - Proposedtreatmentareas FieldseekerProposedtreatmentareaSlice // fieldseeker.proposedtreatmentarea.proposedtreatmentarea_organization_id_fkey - Qamosquitoinspections FieldseekerQamosquitoinspectionSlice // fieldseeker.qamosquitoinspection.qamosquitoinspection_organization_id_fkey - Rodentlocations FieldseekerRodentlocationSlice // fieldseeker.rodentlocation.rodentlocation_organization_id_fkey - Samplecollections FieldseekerSamplecollectionSlice // fieldseeker.samplecollection.samplecollection_organization_id_fkey - Samplelocations FieldseekerSamplelocationSlice // fieldseeker.samplelocation.samplelocation_organization_id_fkey - Servicerequests FieldseekerServicerequestSlice // fieldseeker.servicerequest.servicerequest_organization_id_fkey - Speciesabundances FieldseekerSpeciesabundanceSlice // fieldseeker.speciesabundance.speciesabundance_organization_id_fkey - Stormdrains FieldseekerStormdrainSlice // fieldseeker.stormdrain.stormdrain_organization_id_fkey - Timecards FieldseekerTimecardSlice // fieldseeker.timecard.timecard_organization_id_fkey - Trapdata FieldseekerTrapdatumSlice // fieldseeker.trapdata.trapdata_organization_id_fkey - Traplocations FieldseekerTraplocationSlice // fieldseeker.traplocation.traplocation_organization_id_fkey - Treatments FieldseekerTreatmentSlice // fieldseeker.treatment.treatment_organization_id_fkey - Treatmentareas FieldseekerTreatmentareaSlice // fieldseeker.treatmentarea.treatmentarea_organization_id_fkey - Zones FieldseekerZoneSlice // fieldseeker.zones.zones_organization_id_fkey - Zones2s FieldseekerZones2Slice // fieldseeker.zones2.zones2_organization_id_fkey - FieldseekerSyncs FieldseekerSyncSlice // fieldseeker_sync.fieldseeker_sync_organization_id_fkey - H3Aggregations H3AggregationSlice // h3_aggregation.h3_aggregation_organization_id_fkey - NoteAudios NoteAudioSlice // note_audio.note_audio_organization_id_fkey - NoteImages NoteImageSlice // note_image.note_image_organization_id_fkey - User UserSlice // user_.user__organization_id_fkey + Containerrelates FieldseekerContainerrelateSlice // fieldseeker.containerrelate.containerrelate_organization_id_fkey + Fieldscoutinglogs FieldseekerFieldscoutinglogSlice // fieldseeker.fieldscoutinglog.fieldscoutinglog_organization_id_fkey + Habitatrelates FieldseekerHabitatrelateSlice // fieldseeker.habitatrelate.habitatrelate_organization_id_fkey + Inspectionsamples FieldseekerInspectionsampleSlice // fieldseeker.inspectionsample.inspectionsample_organization_id_fkey + Inspectionsampledetails FieldseekerInspectionsampledetailSlice // fieldseeker.inspectionsampledetail.inspectionsampledetail_organization_id_fkey + Linelocations FieldseekerLinelocationSlice // fieldseeker.linelocation.linelocation_organization_id_fkey + Locationtrackings FieldseekerLocationtrackingSlice // fieldseeker.locationtracking.locationtracking_organization_id_fkey + Mosquitoinspections FieldseekerMosquitoinspectionSlice // fieldseeker.mosquitoinspection.mosquitoinspection_organization_id_fkey + Pointlocations FieldseekerPointlocationSlice // fieldseeker.pointlocation.pointlocation_organization_id_fkey + Polygonlocations FieldseekerPolygonlocationSlice // fieldseeker.polygonlocation.polygonlocation_organization_id_fkey + Pools FieldseekerPoolSlice // fieldseeker.pool.pool_organization_id_fkey + Pooldetails FieldseekerPooldetailSlice // fieldseeker.pooldetail.pooldetail_organization_id_fkey + Proposedtreatmentareas FieldseekerProposedtreatmentareaSlice // fieldseeker.proposedtreatmentarea.proposedtreatmentarea_organization_id_fkey + Qamosquitoinspections FieldseekerQamosquitoinspectionSlice // fieldseeker.qamosquitoinspection.qamosquitoinspection_organization_id_fkey + Rodentlocations FieldseekerRodentlocationSlice // fieldseeker.rodentlocation.rodentlocation_organization_id_fkey + Samplecollections FieldseekerSamplecollectionSlice // fieldseeker.samplecollection.samplecollection_organization_id_fkey + Samplelocations FieldseekerSamplelocationSlice // fieldseeker.samplelocation.samplelocation_organization_id_fkey + Servicerequests FieldseekerServicerequestSlice // fieldseeker.servicerequest.servicerequest_organization_id_fkey + Speciesabundances FieldseekerSpeciesabundanceSlice // fieldseeker.speciesabundance.speciesabundance_organization_id_fkey + Stormdrains FieldseekerStormdrainSlice // fieldseeker.stormdrain.stormdrain_organization_id_fkey + Timecards FieldseekerTimecardSlice // fieldseeker.timecard.timecard_organization_id_fkey + Trapdata FieldseekerTrapdatumSlice // fieldseeker.trapdata.trapdata_organization_id_fkey + Traplocations FieldseekerTraplocationSlice // fieldseeker.traplocation.traplocation_organization_id_fkey + Treatments FieldseekerTreatmentSlice // fieldseeker.treatment.treatment_organization_id_fkey + Treatmentareas FieldseekerTreatmentareaSlice // fieldseeker.treatmentarea.treatmentarea_organization_id_fkey + Zones FieldseekerZoneSlice // fieldseeker.zones.zones_organization_id_fkey + Zones2s FieldseekerZones2Slice // fieldseeker.zones2.zones2_organization_id_fkey + FieldseekerSyncs FieldseekerSyncSlice // fieldseeker_sync.fieldseeker_sync_organization_id_fkey + H3Aggregations H3AggregationSlice // h3_aggregation.h3_aggregation_organization_id_fkey + NoteAudios NoteAudioSlice // note_audio.note_audio_organization_id_fkey + NoteImages NoteImageSlice // note_image.note_image_organization_id_fkey + ImportDistrictGidDistrict *ImportDistrict // organization.organization_import_district_gid_fkey + User UserSlice // user_.user__organization_id_fkey } func buildOrganizationColumns(alias string) organizationColumns { return organizationColumns{ ColumnsExpr: expr.NewColumnsExpr( - "id", "name", "arcgis_id", "arcgis_name", "fieldseeker_url", + "id", "name", "arcgis_id", "arcgis_name", "fieldseeker_url", "import_district_gid", "website", ).WithParent("organization"), - tableAlias: alias, - ID: psql.Quote(alias, "id"), - Name: psql.Quote(alias, "name"), - ArcgisID: psql.Quote(alias, "arcgis_id"), - ArcgisName: psql.Quote(alias, "arcgis_name"), - FieldseekerURL: psql.Quote(alias, "fieldseeker_url"), + tableAlias: alias, + ID: psql.Quote(alias, "id"), + Name: psql.Quote(alias, "name"), + ArcgisID: psql.Quote(alias, "arcgis_id"), + ArcgisName: psql.Quote(alias, "arcgis_name"), + FieldseekerURL: psql.Quote(alias, "fieldseeker_url"), + ImportDistrictGid: psql.Quote(alias, "import_district_gid"), + Website: psql.Quote(alias, "website"), } } type organizationColumns struct { expr.ColumnsExpr - tableAlias string - ID psql.Expression - Name psql.Expression - ArcgisID psql.Expression - ArcgisName psql.Expression - FieldseekerURL psql.Expression + tableAlias string + ID psql.Expression + Name psql.Expression + ArcgisID psql.Expression + ArcgisName psql.Expression + FieldseekerURL psql.Expression + ImportDistrictGid psql.Expression + Website psql.Expression } func (c organizationColumns) Alias() string { @@ -118,15 +125,17 @@ func (organizationColumns) AliasedAs(alias string) organizationColumns { // All values are optional, and do not have to be set // Generated columns are not included type OrganizationSetter struct { - ID omit.Val[int32] `db:"id,pk" ` - Name omit.Val[string] `db:"name" ` - ArcgisID omitnull.Val[string] `db:"arcgis_id" ` - ArcgisName omitnull.Val[string] `db:"arcgis_name" ` - FieldseekerURL omitnull.Val[string] `db:"fieldseeker_url" ` + ID omit.Val[int32] `db:"id,pk" ` + Name omit.Val[string] `db:"name" ` + ArcgisID omitnull.Val[string] `db:"arcgis_id" ` + ArcgisName omitnull.Val[string] `db:"arcgis_name" ` + FieldseekerURL omitnull.Val[string] `db:"fieldseeker_url" ` + ImportDistrictGid omitnull.Val[int32] `db:"import_district_gid" ` + Website omitnull.Val[string] `db:"website" ` } func (s OrganizationSetter) SetColumns() []string { - vals := make([]string, 0, 5) + vals := make([]string, 0, 7) if s.ID.IsValue() { vals = append(vals, "id") } @@ -142,6 +151,12 @@ func (s OrganizationSetter) SetColumns() []string { if !s.FieldseekerURL.IsUnset() { vals = append(vals, "fieldseeker_url") } + if !s.ImportDistrictGid.IsUnset() { + vals = append(vals, "import_district_gid") + } + if !s.Website.IsUnset() { + vals = append(vals, "website") + } return vals } @@ -161,6 +176,12 @@ func (s OrganizationSetter) Overwrite(t *Organization) { if !s.FieldseekerURL.IsUnset() { t.FieldseekerURL = s.FieldseekerURL.MustGetNull() } + if !s.ImportDistrictGid.IsUnset() { + t.ImportDistrictGid = s.ImportDistrictGid.MustGetNull() + } + if !s.Website.IsUnset() { + t.Website = s.Website.MustGetNull() + } } func (s *OrganizationSetter) Apply(q *dialect.InsertQuery) { @@ -169,7 +190,7 @@ func (s *OrganizationSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 5) + vals := make([]bob.Expression, 7) if s.ID.IsValue() { vals[0] = psql.Arg(s.ID.MustGet()) } else { @@ -200,6 +221,18 @@ func (s *OrganizationSetter) Apply(q *dialect.InsertQuery) { vals[4] = psql.Raw("DEFAULT") } + if !s.ImportDistrictGid.IsUnset() { + vals[5] = psql.Arg(s.ImportDistrictGid.MustGetNull()) + } else { + vals[5] = psql.Raw("DEFAULT") + } + + if !s.Website.IsUnset() { + vals[6] = psql.Arg(s.Website.MustGetNull()) + } else { + vals[6] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -209,7 +242,7 @@ func (s OrganizationSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s OrganizationSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 5) + exprs := make([]bob.Expression, 0, 7) if s.ID.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -246,6 +279,20 @@ func (s OrganizationSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if !s.ImportDistrictGid.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "import_district_gid")...), + psql.Arg(s.ImportDistrictGid), + }}) + } + + if !s.Website.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "website")...), + psql.Arg(s.Website), + }}) + } + return exprs } @@ -1216,6 +1263,30 @@ func (os OrganizationSlice) NoteImages(mods ...bob.Mod[*dialect.SelectQuery]) No )...) } +// ImportDistrictGidDistrict starts a query for related objects on import.district +func (o *Organization) ImportDistrictGidDistrict(mods ...bob.Mod[*dialect.SelectQuery]) ImportDistrictsQuery { + return ImportDistricts.Query(append(mods, + sm.Where(ImportDistricts.Columns.Gid.EQ(psql.Arg(o.ImportDistrictGid))), + )...) +} + +func (os OrganizationSlice) ImportDistrictGidDistrict(mods ...bob.Mod[*dialect.SelectQuery]) ImportDistrictsQuery { + pkImportDistrictGid := make(pgtypes.Array[null.Val[int32]], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkImportDistrictGid = append(pkImportDistrictGid, o.ImportDistrictGid) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkImportDistrictGid), "integer[]")), + )) + + return ImportDistricts.Query(append(mods, + sm.Where(psql.Group(ImportDistricts.Columns.Gid).OP("IN", PKArgExpr)), + )...) +} + // User starts a query for related objects on user_ func (o *Organization) User(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { return Users.Query(append(mods, @@ -3348,6 +3419,54 @@ func (organization0 *Organization) AttachNoteImages(ctx context.Context, exec bo return nil } +func attachOrganizationImportDistrictGidDistrict0(ctx context.Context, exec bob.Executor, count int, organization0 *Organization, importDistrict1 *ImportDistrict) (*Organization, error) { + setter := &OrganizationSetter{ + ImportDistrictGid: omitnull.From(importDistrict1.Gid), + } + + err := organization0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachOrganizationImportDistrictGidDistrict0: %w", err) + } + + return organization0, nil +} + +func (organization0 *Organization) InsertImportDistrictGidDistrict(ctx context.Context, exec bob.Executor, related *ImportDistrictSetter) error { + var err error + + importDistrict1, err := ImportDistricts.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachOrganizationImportDistrictGidDistrict0(ctx, exec, 1, organization0, importDistrict1) + if err != nil { + return err + } + + organization0.R.ImportDistrictGidDistrict = importDistrict1 + + importDistrict1.R.ImportDistrictGidOrganization = organization0 + + return nil +} + +func (organization0 *Organization) AttachImportDistrictGidDistrict(ctx context.Context, exec bob.Executor, importDistrict1 *ImportDistrict) error { + var err error + + _, err = attachOrganizationImportDistrictGidDistrict0(ctx, exec, 1, organization0, importDistrict1) + if err != nil { + return err + } + + organization0.R.ImportDistrictGidDistrict = importDistrict1 + + importDistrict1.R.ImportDistrictGidOrganization = organization0 + + return nil +} + func insertOrganizationUser0(ctx context.Context, exec bob.Executor, users1 []*UserSetter, organization0 *Organization) (UserSlice, error) { for i := range users1 { users1[i].OrganizationID = omit.From(organization0.ID) @@ -3417,11 +3536,13 @@ func (organization0 *Organization) AttachUser(ctx context.Context, exec bob.Exec } type organizationWhere[Q psql.Filterable] struct { - ID psql.WhereMod[Q, int32] - Name psql.WhereMod[Q, string] - ArcgisID psql.WhereNullMod[Q, string] - ArcgisName psql.WhereNullMod[Q, string] - FieldseekerURL psql.WhereNullMod[Q, string] + ID psql.WhereMod[Q, int32] + Name psql.WhereMod[Q, string] + ArcgisID psql.WhereNullMod[Q, string] + ArcgisName psql.WhereNullMod[Q, string] + FieldseekerURL psql.WhereNullMod[Q, string] + ImportDistrictGid psql.WhereNullMod[Q, int32] + Website psql.WhereNullMod[Q, string] } func (organizationWhere[Q]) AliasedAs(alias string) organizationWhere[Q] { @@ -3430,11 +3551,13 @@ func (organizationWhere[Q]) AliasedAs(alias string) organizationWhere[Q] { func buildOrganizationWhere[Q psql.Filterable](cols organizationColumns) organizationWhere[Q] { return organizationWhere[Q]{ - ID: psql.Where[Q, int32](cols.ID), - Name: psql.Where[Q, string](cols.Name), - ArcgisID: psql.WhereNull[Q, string](cols.ArcgisID), - ArcgisName: psql.WhereNull[Q, string](cols.ArcgisName), - FieldseekerURL: psql.WhereNull[Q, string](cols.FieldseekerURL), + ID: psql.Where[Q, int32](cols.ID), + Name: psql.Where[Q, string](cols.Name), + ArcgisID: psql.WhereNull[Q, string](cols.ArcgisID), + ArcgisName: psql.WhereNull[Q, string](cols.ArcgisName), + FieldseekerURL: psql.WhereNull[Q, string](cols.FieldseekerURL), + ImportDistrictGid: psql.WhereNull[Q, int32](cols.ImportDistrictGid), + Website: psql.WhereNull[Q, string](cols.Website), } } @@ -3878,6 +4001,18 @@ func (o *Organization) Preload(name string, retrieved any) error { } } return nil + case "ImportDistrictGidDistrict": + rel, ok := retrieved.(*ImportDistrict) + if !ok { + return fmt.Errorf("organization cannot load %T as %q", retrieved, name) + } + + o.R.ImportDistrictGidDistrict = rel + + if rel != nil { + rel.R.ImportDistrictGidOrganization = o + } + return nil case "User": rels, ok := retrieved.(UserSlice) if !ok { @@ -3897,45 +4032,62 @@ func (o *Organization) Preload(name string, retrieved any) error { } } -type organizationPreloader struct{} +type organizationPreloader struct { + ImportDistrictGidDistrict func(...psql.PreloadOption) psql.Preloader +} func buildOrganizationPreloader() organizationPreloader { - return organizationPreloader{} + return organizationPreloader{ + ImportDistrictGidDistrict: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*ImportDistrict, ImportDistrictSlice](psql.PreloadRel{ + Name: "ImportDistrictGidDistrict", + Sides: []psql.PreloadSide{ + { + From: Organizations, + To: ImportDistricts, + FromColumns: []string{"import_district_gid"}, + ToColumns: []string{"gid"}, + }, + }, + }, ImportDistricts.Columns.Names(), opts...) + }, + } } type organizationThenLoader[Q orm.Loadable] struct { - Containerrelates func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Fieldscoutinglogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Habitatrelates func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Inspectionsamples func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Inspectionsampledetails func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Linelocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Locationtrackings func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Mosquitoinspections func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Pointlocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Polygonlocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Pools func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Pooldetails func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Proposedtreatmentareas func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Qamosquitoinspections func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Rodentlocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Samplecollections func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Samplelocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Servicerequests func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Speciesabundances func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Stormdrains func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Timecards func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Trapdata func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Traplocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Treatments func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Treatmentareas func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Zones func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Zones2s func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - FieldseekerSyncs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - H3Aggregations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - NoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - NoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - User func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Containerrelates func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Fieldscoutinglogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Habitatrelates func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Inspectionsamples func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Inspectionsampledetails func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Linelocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Locationtrackings func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Mosquitoinspections func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Pointlocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Polygonlocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Pools func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Pooldetails func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Proposedtreatmentareas func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Qamosquitoinspections func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Rodentlocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Samplecollections func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Samplelocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Servicerequests func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Speciesabundances func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Stormdrains func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Timecards func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Trapdata func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Traplocations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Treatments func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Treatmentareas func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Zones func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Zones2s func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + FieldseekerSyncs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + H3Aggregations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + NoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + NoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ImportDistrictGidDistrict func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + User func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { @@ -4032,6 +4184,9 @@ func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { type NoteImagesLoadInterface interface { LoadNoteImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type ImportDistrictGidDistrictLoadInterface interface { + LoadImportDistrictGidDistrict(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type UserLoadInterface interface { LoadUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -4223,6 +4378,12 @@ func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { return retrieved.LoadNoteImages(ctx, exec, mods...) }, ), + ImportDistrictGidDistrict: thenLoadBuilder[Q]( + "ImportDistrictGidDistrict", + func(ctx context.Context, exec bob.Executor, retrieved ImportDistrictGidDistrictLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadImportDistrictGidDistrict(ctx, exec, mods...) + }, + ), User: thenLoadBuilder[Q]( "User", func(ctx context.Context, exec bob.Executor, retrieved UserLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -6123,6 +6284,61 @@ func (os OrganizationSlice) LoadNoteImages(ctx context.Context, exec bob.Executo return nil } +// LoadImportDistrictGidDistrict loads the organization's ImportDistrictGidDistrict into the .R struct +func (o *Organization) LoadImportDistrictGidDistrict(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.ImportDistrictGidDistrict = nil + + related, err := o.ImportDistrictGidDistrict(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.ImportDistrictGidOrganization = o + + o.R.ImportDistrictGidDistrict = related + return nil +} + +// LoadImportDistrictGidDistrict loads the organization's ImportDistrictGidDistrict into the .R struct +func (os OrganizationSlice) LoadImportDistrictGidDistrict(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + importDistricts, err := os.ImportDistrictGidDistrict(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range importDistricts { + if !o.ImportDistrictGid.IsValue() { + continue + } + + if !(o.ImportDistrictGid.IsValue() && o.ImportDistrictGid.MustGet() == rel.Gid) { + continue + } + + rel.R.ImportDistrictGidOrganization = o + + o.R.ImportDistrictGidDistrict = rel + break + } + } + + return nil +} + // LoadUser loads the organization's User into the .R struct func (o *Organization) LoadUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -8169,39 +8385,40 @@ func (os OrganizationSlice) LoadCountUser(ctx context.Context, exec bob.Executor } type organizationJoins[Q dialect.Joinable] struct { - typ string - Containerrelates modAs[Q, fieldseekerContainerrelateColumns] - Fieldscoutinglogs modAs[Q, fieldseekerFieldscoutinglogColumns] - Habitatrelates modAs[Q, fieldseekerHabitatrelateColumns] - Inspectionsamples modAs[Q, fieldseekerInspectionsampleColumns] - Inspectionsampledetails modAs[Q, fieldseekerInspectionsampledetailColumns] - Linelocations modAs[Q, fieldseekerLinelocationColumns] - Locationtrackings modAs[Q, fieldseekerLocationtrackingColumns] - Mosquitoinspections modAs[Q, fieldseekerMosquitoinspectionColumns] - Pointlocations modAs[Q, fieldseekerPointlocationColumns] - Polygonlocations modAs[Q, fieldseekerPolygonlocationColumns] - Pools modAs[Q, fieldseekerPoolColumns] - Pooldetails modAs[Q, fieldseekerPooldetailColumns] - Proposedtreatmentareas modAs[Q, fieldseekerProposedtreatmentareaColumns] - Qamosquitoinspections modAs[Q, fieldseekerQamosquitoinspectionColumns] - Rodentlocations modAs[Q, fieldseekerRodentlocationColumns] - Samplecollections modAs[Q, fieldseekerSamplecollectionColumns] - Samplelocations modAs[Q, fieldseekerSamplelocationColumns] - Servicerequests modAs[Q, fieldseekerServicerequestColumns] - Speciesabundances modAs[Q, fieldseekerSpeciesabundanceColumns] - Stormdrains modAs[Q, fieldseekerStormdrainColumns] - Timecards modAs[Q, fieldseekerTimecardColumns] - Trapdata modAs[Q, fieldseekerTrapdatumColumns] - Traplocations modAs[Q, fieldseekerTraplocationColumns] - Treatments modAs[Q, fieldseekerTreatmentColumns] - Treatmentareas modAs[Q, fieldseekerTreatmentareaColumns] - Zones modAs[Q, fieldseekerZoneColumns] - Zones2s modAs[Q, fieldseekerZones2Columns] - FieldseekerSyncs modAs[Q, fieldseekerSyncColumns] - H3Aggregations modAs[Q, h3AggregationColumns] - NoteAudios modAs[Q, noteAudioColumns] - NoteImages modAs[Q, noteImageColumns] - User modAs[Q, userColumns] + typ string + Containerrelates modAs[Q, fieldseekerContainerrelateColumns] + Fieldscoutinglogs modAs[Q, fieldseekerFieldscoutinglogColumns] + Habitatrelates modAs[Q, fieldseekerHabitatrelateColumns] + Inspectionsamples modAs[Q, fieldseekerInspectionsampleColumns] + Inspectionsampledetails modAs[Q, fieldseekerInspectionsampledetailColumns] + Linelocations modAs[Q, fieldseekerLinelocationColumns] + Locationtrackings modAs[Q, fieldseekerLocationtrackingColumns] + Mosquitoinspections modAs[Q, fieldseekerMosquitoinspectionColumns] + Pointlocations modAs[Q, fieldseekerPointlocationColumns] + Polygonlocations modAs[Q, fieldseekerPolygonlocationColumns] + Pools modAs[Q, fieldseekerPoolColumns] + Pooldetails modAs[Q, fieldseekerPooldetailColumns] + Proposedtreatmentareas modAs[Q, fieldseekerProposedtreatmentareaColumns] + Qamosquitoinspections modAs[Q, fieldseekerQamosquitoinspectionColumns] + Rodentlocations modAs[Q, fieldseekerRodentlocationColumns] + Samplecollections modAs[Q, fieldseekerSamplecollectionColumns] + Samplelocations modAs[Q, fieldseekerSamplelocationColumns] + Servicerequests modAs[Q, fieldseekerServicerequestColumns] + Speciesabundances modAs[Q, fieldseekerSpeciesabundanceColumns] + Stormdrains modAs[Q, fieldseekerStormdrainColumns] + Timecards modAs[Q, fieldseekerTimecardColumns] + Trapdata modAs[Q, fieldseekerTrapdatumColumns] + Traplocations modAs[Q, fieldseekerTraplocationColumns] + Treatments modAs[Q, fieldseekerTreatmentColumns] + Treatmentareas modAs[Q, fieldseekerTreatmentareaColumns] + Zones modAs[Q, fieldseekerZoneColumns] + Zones2s modAs[Q, fieldseekerZones2Columns] + FieldseekerSyncs modAs[Q, fieldseekerSyncColumns] + H3Aggregations modAs[Q, h3AggregationColumns] + NoteAudios modAs[Q, noteAudioColumns] + NoteImages modAs[Q, noteImageColumns] + ImportDistrictGidDistrict modAs[Q, importDistrictColumns] + User modAs[Q, userColumns] } func (j organizationJoins[Q]) aliasedAs(alias string) organizationJoins[Q] { @@ -8645,6 +8862,20 @@ func buildOrganizationJoins[Q dialect.Joinable](cols organizationColumns, typ st return mods }, }, + ImportDistrictGidDistrict: modAs[Q, importDistrictColumns]{ + c: ImportDistricts.Columns, + f: func(to importDistrictColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, ImportDistricts.Name().As(to.Alias())).On( + to.Gid.EQ(cols.ImportDistrictGid), + )) + } + + return mods + }, + }, User: modAs[Q, userColumns]{ c: Users.Columns, f: func(to userColumns) bob.Mod[Q] { diff --git a/platform/district.go b/platform/district.go index fd3d76c7..d9c821ba 100644 --- a/platform/district.go +++ b/platform/district.go @@ -11,8 +11,8 @@ import ( "github.com/stephenafamo/bob/dialect/psql/sm" ) -func DistrictForLocation(ctx context.Context, lng float64, lat float64) (*models.District, error) { - rows, err := models.Districts.Query( +func DistrictForLocation(ctx context.Context, lng float64, lat float64) (*models.ImportDistrict, error) { + rows, err := models.ImportDistricts.Query( sm.Where( psql.F("ST_Contains", psql.Raw("geom_4326"), psql.F("ST_SetSRID", psql.F("ST_MakePoint", psql.Arg(lng), psql.Arg(lat)), psql.Arg(4326))), ), From b95a3275ff49ff342844081249a83220f22b89db Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 16 Jan 2026 14:51:50 +0000 Subject: [PATCH 0048/1453] Set address to empty in quick report upload Fixes a null value error --- public-report/quick.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public-report/quick.go b/public-report/quick.go index c6498920..1509df56 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -54,7 +54,6 @@ func postQuick(w http.ResponseWriter, r *http.Request) { 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 { @@ -73,6 +72,7 @@ func postQuick(w http.ResponseWriter, r *http.Request) { } c, err := h3utils.GetCell(longitude, latitude, 15) setter := models.PublicreportQuickSetter{ + Address: omit.From(""), Created: omit.From(time.Now()), Comments: omit.From(comments), //Location: omitnull.From(fmt.Sprintf("ST_GeometryFromText(Point(%s %s))", longitude, latitude)), From 079d20c086e337986e22cd824e124928921551d6 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 16 Jan 2026 14:52:11 +0000 Subject: [PATCH 0049/1453] Extract EXIF data from images This required a schema change and actually dumps all existing photo data from the public reports page. That's probably fine since it's not deployed to any customers so all data is currently test data. --- background/arcgis.go | 4 + db/dberrors/publicreport.image.bob.go | 17 + db/dberrors/publicreport.image_exif.bob.go | 17 + db/dberrors/publicreport.pool_image.bob.go | 17 + db/dberrors/publicreport.pool_photo.bob.go | 17 - db/dberrors/publicreport.quick_image.bob.go | 17 + db/dberrors/publicreport.quick_photo.bob.go | 17 - db/dbinfo/publicreport.image.bob.go | 162 +++ db/dbinfo/publicreport.image_exif.bob.go | 137 ++ db/dbinfo/publicreport.pool_image.bob.go | 132 ++ db/dbinfo/publicreport.pool_photo.bob.go | 147 -- db/dbinfo/publicreport.quick_image.bob.go | 132 ++ db/dbinfo/publicreport.quick_photo.bob.go | 147 -- db/factory/bobfactory_context.bob.go | 28 +- db/factory/bobfactory_main.bob.go | 171 ++- db/factory/publicreport.image.bob.go | 822 +++++++++++ db/factory/publicreport.image_exif.bob.go | 413 ++++++ db/factory/publicreport.pool.bob.go | 59 +- db/factory/publicreport.pool_image.bob.go | 431 ++++++ db/factory/publicreport.pool_photo.bob.go | 498 ------- db/factory/publicreport.quick.bob.go | 59 +- db/factory/publicreport.quick_image.bob.go | 431 ++++++ db/factory/publicreport.quick_photo.bob.go | 498 ------- db/migrations/00035_photo_content_type.sql | 37 + db/models/bob_counts.bob.go | 4 + db/models/bob_joins.bob.go | 12 +- db/models/bob_loaders.bob.go | 24 +- db/models/bob_where.bob.go | 18 +- db/models/publicreport.image.bob.go | 1440 +++++++++++++++++++ db/models/publicreport.image_exif.bob.go | 645 +++++++++ db/models/publicreport.pool.bob.go | 235 +-- db/models/publicreport.pool_image.bob.go | 766 ++++++++++ db/models/publicreport.pool_photo.bob.go | 678 --------- db/models/publicreport.quick.bob.go | 235 +-- db/models/publicreport.quick_image.bob.go | 766 ++++++++++ db/models/publicreport.quick_photo.bob.go | 678 --------- go.mod | 10 + go.sum | 55 + public-report/image-upload.go | 182 +++ public-report/photo-upload.go | 59 - public-report/pool.go | 34 +- public-report/quick.go | 41 +- userfile/userfile.go | 10 +- 43 files changed, 7208 insertions(+), 3094 deletions(-) create mode 100644 db/dberrors/publicreport.image.bob.go create mode 100644 db/dberrors/publicreport.image_exif.bob.go create mode 100644 db/dberrors/publicreport.pool_image.bob.go delete mode 100644 db/dberrors/publicreport.pool_photo.bob.go create mode 100644 db/dberrors/publicreport.quick_image.bob.go delete mode 100644 db/dberrors/publicreport.quick_photo.bob.go create mode 100644 db/dbinfo/publicreport.image.bob.go create mode 100644 db/dbinfo/publicreport.image_exif.bob.go create mode 100644 db/dbinfo/publicreport.pool_image.bob.go delete mode 100644 db/dbinfo/publicreport.pool_photo.bob.go create mode 100644 db/dbinfo/publicreport.quick_image.bob.go delete mode 100644 db/dbinfo/publicreport.quick_photo.bob.go create mode 100644 db/factory/publicreport.image.bob.go create mode 100644 db/factory/publicreport.image_exif.bob.go create mode 100644 db/factory/publicreport.pool_image.bob.go delete mode 100644 db/factory/publicreport.pool_photo.bob.go create mode 100644 db/factory/publicreport.quick_image.bob.go delete mode 100644 db/factory/publicreport.quick_photo.bob.go create mode 100644 db/migrations/00035_photo_content_type.sql create mode 100644 db/models/publicreport.image.bob.go create mode 100644 db/models/publicreport.image_exif.bob.go create mode 100644 db/models/publicreport.pool_image.bob.go delete mode 100644 db/models/publicreport.pool_photo.bob.go create mode 100644 db/models/publicreport.quick_image.bob.go delete mode 100644 db/models/publicreport.quick_photo.bob.go create mode 100644 public-report/image-upload.go delete mode 100644 public-report/photo-upload.go diff --git a/background/arcgis.go b/background/arcgis.go index 901fda63..d53177da 100644 --- a/background/arcgis.go +++ b/background/arcgis.go @@ -338,6 +338,10 @@ func updatePortalData(ctx context.Context, client *arcgis.ArcGIS, user_id int32) } tx, err := db.PGInstance.BobDB.BeginTx(ctx, nil) + if err != nil { + return nil, fmt.Errorf("Failed to create transaction: %w", err) + } + _, err = models.ArcgisUserPrivileges.Delete( dm.Where( models.ArcgisUserPrivileges.Columns.UserID.EQ(psql.Arg(p.User.ID)), diff --git a/db/dberrors/publicreport.image.bob.go b/db/dberrors/publicreport.image.bob.go new file mode 100644 index 00000000..4986333c --- /dev/null +++ b/db/dberrors/publicreport.image.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var PublicreportImageErrors = &publicreportImageErrors{ + ErrUniqueImagePkey: &UniqueConstraintError{ + schema: "publicreport", + table: "image", + columns: []string{"id"}, + s: "image_pkey", + }, +} + +type publicreportImageErrors struct { + ErrUniqueImagePkey *UniqueConstraintError +} diff --git a/db/dberrors/publicreport.image_exif.bob.go b/db/dberrors/publicreport.image_exif.bob.go new file mode 100644 index 00000000..28ab3ada --- /dev/null +++ b/db/dberrors/publicreport.image_exif.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var PublicreportImageExifErrors = &publicreportImageExifErrors{ + ErrUniqueImageExifPkey: &UniqueConstraintError{ + schema: "publicreport", + table: "image_exif", + columns: []string{"image_id", "name", "value"}, + s: "image_exif_pkey", + }, +} + +type publicreportImageExifErrors struct { + ErrUniqueImageExifPkey *UniqueConstraintError +} diff --git a/db/dberrors/publicreport.pool_image.bob.go b/db/dberrors/publicreport.pool_image.bob.go new file mode 100644 index 00000000..5317ef9f --- /dev/null +++ b/db/dberrors/publicreport.pool_image.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var PublicreportPoolImageErrors = &publicreportPoolImageErrors{ + ErrUniquePoolImagePkey: &UniqueConstraintError{ + schema: "publicreport", + table: "pool_image", + columns: []string{"image_id", "pool_id"}, + s: "pool_image_pkey", + }, +} + +type publicreportPoolImageErrors struct { + ErrUniquePoolImagePkey *UniqueConstraintError +} diff --git a/db/dberrors/publicreport.pool_photo.bob.go b/db/dberrors/publicreport.pool_photo.bob.go deleted file mode 100644 index 0a5310e6..00000000 --- a/db/dberrors/publicreport.pool_photo.bob.go +++ /dev/null @@ -1,17 +0,0 @@ -// Code generated by BobGen psql v0.42.1. 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 -} diff --git a/db/dberrors/publicreport.quick_image.bob.go b/db/dberrors/publicreport.quick_image.bob.go new file mode 100644 index 00000000..d0bb4097 --- /dev/null +++ b/db/dberrors/publicreport.quick_image.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var PublicreportQuickImageErrors = &publicreportQuickImageErrors{ + ErrUniqueQuickImagePkey: &UniqueConstraintError{ + schema: "publicreport", + table: "quick_image", + columns: []string{"image_id", "quick_id"}, + s: "quick_image_pkey", + }, +} + +type publicreportQuickImageErrors struct { + ErrUniqueQuickImagePkey *UniqueConstraintError +} diff --git a/db/dberrors/publicreport.quick_photo.bob.go b/db/dberrors/publicreport.quick_photo.bob.go deleted file mode 100644 index 4dd54714..00000000 --- a/db/dberrors/publicreport.quick_photo.bob.go +++ /dev/null @@ -1,17 +0,0 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. -// This file is meant to be re-generated in place and/or deleted at any time. - -package dberrors - -var PublicreportQuickPhotoErrors = &publicreportQuickPhotoErrors{ - ErrUniqueQuickPhotoPkey: &UniqueConstraintError{ - schema: "publicreport", - table: "quick_photo", - columns: []string{"id"}, - s: "quick_photo_pkey", - }, -} - -type publicreportQuickPhotoErrors struct { - ErrUniqueQuickPhotoPkey *UniqueConstraintError -} diff --git a/db/dbinfo/publicreport.image.bob.go b/db/dbinfo/publicreport.image.bob.go new file mode 100644 index 00000000..aca07c41 --- /dev/null +++ b/db/dbinfo/publicreport.image.bob.go @@ -0,0 +1,162 @@ +// Code generated by BobGen psql v0.42.1. 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 PublicreportImages = Table[ + publicreportImageColumns, + publicreportImageIndexes, + publicreportImageForeignKeys, + publicreportImageUniques, + publicreportImageChecks, +]{ + Schema: "publicreport", + Name: "image", + Columns: publicreportImageColumns{ + ID: column{ + Name: "id", + DBType: "integer", + Default: "nextval('publicreport.image_id_seq'::regclass)", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + ContentType: column{ + Name: "content_type", + 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, + }, + ResolutionX: column{ + Name: "resolution_x", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + ResolutionY: column{ + Name: "resolution_y", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + StorageUUID: column{ + Name: "storage_uuid", + DBType: "uuid", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + StorageSize: column{ + Name: "storage_size", + DBType: "bigint", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + UploadedFilename: column{ + Name: "uploaded_filename", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: publicreportImageIndexes{ + ImagePkey: index{ + Type: "btree", + Name: "image_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: "image_pkey", + Columns: []string{"id"}, + Comment: "", + }, + + Comment: "", +} + +type publicreportImageColumns struct { + ID column + ContentType column + Created column + ResolutionX column + ResolutionY column + StorageUUID column + StorageSize column + UploadedFilename column +} + +func (c publicreportImageColumns) AsSlice() []column { + return []column{ + c.ID, c.ContentType, c.Created, c.ResolutionX, c.ResolutionY, c.StorageUUID, c.StorageSize, c.UploadedFilename, + } +} + +type publicreportImageIndexes struct { + ImagePkey index +} + +func (i publicreportImageIndexes) AsSlice() []index { + return []index{ + i.ImagePkey, + } +} + +type publicreportImageForeignKeys struct{} + +func (f publicreportImageForeignKeys) AsSlice() []foreignKey { + return []foreignKey{} +} + +type publicreportImageUniques struct{} + +func (u publicreportImageUniques) AsSlice() []constraint { + return []constraint{} +} + +type publicreportImageChecks struct{} + +func (c publicreportImageChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/publicreport.image_exif.bob.go b/db/dbinfo/publicreport.image_exif.bob.go new file mode 100644 index 00000000..97aa27b7 --- /dev/null +++ b/db/dbinfo/publicreport.image_exif.bob.go @@ -0,0 +1,137 @@ +// Code generated by BobGen psql v0.42.1. 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 PublicreportImageExifs = Table[ + publicreportImageExifColumns, + publicreportImageExifIndexes, + publicreportImageExifForeignKeys, + publicreportImageExifUniques, + publicreportImageExifChecks, +]{ + Schema: "publicreport", + Name: "image_exif", + Columns: publicreportImageExifColumns{ + ImageID: column{ + Name: "image_id", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Name: column{ + Name: "name", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Value: column{ + Name: "value", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: publicreportImageExifIndexes{ + ImageExifPkey: index{ + Type: "btree", + Name: "image_exif_pkey", + Columns: []indexColumn{ + { + Name: "image_id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + { + Name: "name", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + { + Name: "value", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false, false, false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "image_exif_pkey", + Columns: []string{"image_id", "name", "value"}, + Comment: "", + }, + ForeignKeys: publicreportImageExifForeignKeys{ + PublicreportImageExifImageExifImageIDFkey: foreignKey{ + constraint: constraint{ + Name: "publicreport.image_exif.image_exif_image_id_fkey", + Columns: []string{"image_id"}, + Comment: "", + }, + ForeignTable: "publicreport.image", + ForeignColumns: []string{"id"}, + }, + }, + + Comment: "", +} + +type publicreportImageExifColumns struct { + ImageID column + Name column + Value column +} + +func (c publicreportImageExifColumns) AsSlice() []column { + return []column{ + c.ImageID, c.Name, c.Value, + } +} + +type publicreportImageExifIndexes struct { + ImageExifPkey index +} + +func (i publicreportImageExifIndexes) AsSlice() []index { + return []index{ + i.ImageExifPkey, + } +} + +type publicreportImageExifForeignKeys struct { + PublicreportImageExifImageExifImageIDFkey foreignKey +} + +func (f publicreportImageExifForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.PublicreportImageExifImageExifImageIDFkey, + } +} + +type publicreportImageExifUniques struct{} + +func (u publicreportImageExifUniques) AsSlice() []constraint { + return []constraint{} +} + +type publicreportImageExifChecks struct{} + +func (c publicreportImageExifChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/publicreport.pool_image.bob.go b/db/dbinfo/publicreport.pool_image.bob.go new file mode 100644 index 00000000..cb44e9b9 --- /dev/null +++ b/db/dbinfo/publicreport.pool_image.bob.go @@ -0,0 +1,132 @@ +// Code generated by BobGen psql v0.42.1. 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 PublicreportPoolImages = Table[ + publicreportPoolImageColumns, + publicreportPoolImageIndexes, + publicreportPoolImageForeignKeys, + publicreportPoolImageUniques, + publicreportPoolImageChecks, +]{ + Schema: "publicreport", + Name: "pool_image", + Columns: publicreportPoolImageColumns{ + ImageID: column{ + Name: "image_id", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + PoolID: column{ + Name: "pool_id", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: publicreportPoolImageIndexes{ + PoolImagePkey: index{ + Type: "btree", + Name: "pool_image_pkey", + Columns: []indexColumn{ + { + Name: "image_id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + { + Name: "pool_id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false, false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "pool_image_pkey", + Columns: []string{"image_id", "pool_id"}, + Comment: "", + }, + ForeignKeys: publicreportPoolImageForeignKeys{ + PublicreportPoolImagePoolImageImageIDFkey: foreignKey{ + constraint: constraint{ + Name: "publicreport.pool_image.pool_image_image_id_fkey", + Columns: []string{"image_id"}, + Comment: "", + }, + ForeignTable: "publicreport.image", + ForeignColumns: []string{"id"}, + }, + PublicreportPoolImagePoolImagePoolIDFkey: foreignKey{ + constraint: constraint{ + Name: "publicreport.pool_image.pool_image_pool_id_fkey", + Columns: []string{"pool_id"}, + Comment: "", + }, + ForeignTable: "publicreport.pool", + ForeignColumns: []string{"id"}, + }, + }, + + Comment: "", +} + +type publicreportPoolImageColumns struct { + ImageID column + PoolID column +} + +func (c publicreportPoolImageColumns) AsSlice() []column { + return []column{ + c.ImageID, c.PoolID, + } +} + +type publicreportPoolImageIndexes struct { + PoolImagePkey index +} + +func (i publicreportPoolImageIndexes) AsSlice() []index { + return []index{ + i.PoolImagePkey, + } +} + +type publicreportPoolImageForeignKeys struct { + PublicreportPoolImagePoolImageImageIDFkey foreignKey + PublicreportPoolImagePoolImagePoolIDFkey foreignKey +} + +func (f publicreportPoolImageForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.PublicreportPoolImagePoolImageImageIDFkey, f.PublicreportPoolImagePoolImagePoolIDFkey, + } +} + +type publicreportPoolImageUniques struct{} + +func (u publicreportPoolImageUniques) AsSlice() []constraint { + return []constraint{} +} + +type publicreportPoolImageChecks struct{} + +func (c publicreportPoolImageChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/publicreport.pool_photo.bob.go b/db/dbinfo/publicreport.pool_photo.bob.go deleted file mode 100644 index 8b116463..00000000 --- a/db/dbinfo/publicreport.pool_photo.bob.go +++ /dev/null @@ -1,147 +0,0 @@ -// Code generated by BobGen psql v0.42.1. 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{} -} diff --git a/db/dbinfo/publicreport.quick_image.bob.go b/db/dbinfo/publicreport.quick_image.bob.go new file mode 100644 index 00000000..d311615b --- /dev/null +++ b/db/dbinfo/publicreport.quick_image.bob.go @@ -0,0 +1,132 @@ +// Code generated by BobGen psql v0.42.1. 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 PublicreportQuickImages = Table[ + publicreportQuickImageColumns, + publicreportQuickImageIndexes, + publicreportQuickImageForeignKeys, + publicreportQuickImageUniques, + publicreportQuickImageChecks, +]{ + Schema: "publicreport", + Name: "quick_image", + Columns: publicreportQuickImageColumns{ + ImageID: column{ + Name: "image_id", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + QuickID: column{ + Name: "quick_id", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: publicreportQuickImageIndexes{ + QuickImagePkey: index{ + Type: "btree", + Name: "quick_image_pkey", + Columns: []indexColumn{ + { + Name: "image_id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + { + Name: "quick_id", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false, false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "quick_image_pkey", + Columns: []string{"image_id", "quick_id"}, + Comment: "", + }, + ForeignKeys: publicreportQuickImageForeignKeys{ + PublicreportQuickImageQuickImageImageIDFkey: foreignKey{ + constraint: constraint{ + Name: "publicreport.quick_image.quick_image_image_id_fkey", + Columns: []string{"image_id"}, + Comment: "", + }, + ForeignTable: "publicreport.image", + ForeignColumns: []string{"id"}, + }, + PublicreportQuickImageQuickImageQuickIDFkey: foreignKey{ + constraint: constraint{ + Name: "publicreport.quick_image.quick_image_quick_id_fkey", + Columns: []string{"quick_id"}, + Comment: "", + }, + ForeignTable: "publicreport.quick", + ForeignColumns: []string{"id"}, + }, + }, + + Comment: "", +} + +type publicreportQuickImageColumns struct { + ImageID column + QuickID column +} + +func (c publicreportQuickImageColumns) AsSlice() []column { + return []column{ + c.ImageID, c.QuickID, + } +} + +type publicreportQuickImageIndexes struct { + QuickImagePkey index +} + +func (i publicreportQuickImageIndexes) AsSlice() []index { + return []index{ + i.QuickImagePkey, + } +} + +type publicreportQuickImageForeignKeys struct { + PublicreportQuickImageQuickImageImageIDFkey foreignKey + PublicreportQuickImageQuickImageQuickIDFkey foreignKey +} + +func (f publicreportQuickImageForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.PublicreportQuickImageQuickImageImageIDFkey, f.PublicreportQuickImageQuickImageQuickIDFkey, + } +} + +type publicreportQuickImageUniques struct{} + +func (u publicreportQuickImageUniques) AsSlice() []constraint { + return []constraint{} +} + +type publicreportQuickImageChecks struct{} + +func (c publicreportQuickImageChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/publicreport.quick_photo.bob.go b/db/dbinfo/publicreport.quick_photo.bob.go deleted file mode 100644 index f827d142..00000000 --- a/db/dbinfo/publicreport.quick_photo.bob.go +++ /dev/null @@ -1,147 +0,0 @@ -// Code generated by BobGen psql v0.42.1. 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 PublicreportQuickPhotos = Table[ - publicreportQuickPhotoColumns, - publicreportQuickPhotoIndexes, - publicreportQuickPhotoForeignKeys, - publicreportQuickPhotoUniques, - publicreportQuickPhotoChecks, -]{ - Schema: "publicreport", - Name: "quick_photo", - Columns: publicreportQuickPhotoColumns{ - ID: column{ - Name: "id", - DBType: "integer", - Default: "nextval('publicreport.quick_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, - }, - QuickID: column{ - Name: "quick_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: publicreportQuickPhotoIndexes{ - QuickPhotoPkey: index{ - Type: "btree", - Name: "quick_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: "quick_photo_pkey", - Columns: []string{"id"}, - Comment: "", - }, - ForeignKeys: publicreportQuickPhotoForeignKeys{ - PublicreportQuickPhotoQuickPhotoQuickIDFkey: foreignKey{ - constraint: constraint{ - Name: "publicreport.quick_photo.quick_photo_quick_id_fkey", - Columns: []string{"quick_id"}, - Comment: "", - }, - ForeignTable: "publicreport.quick", - ForeignColumns: []string{"id"}, - }, - }, - - Comment: "", -} - -type publicreportQuickPhotoColumns struct { - ID column - Size column - Filename column - QuickID column - UUID column -} - -func (c publicreportQuickPhotoColumns) AsSlice() []column { - return []column{ - c.ID, c.Size, c.Filename, c.QuickID, c.UUID, - } -} - -type publicreportQuickPhotoIndexes struct { - QuickPhotoPkey index -} - -func (i publicreportQuickPhotoIndexes) AsSlice() []index { - return []index{ - i.QuickPhotoPkey, - } -} - -type publicreportQuickPhotoForeignKeys struct { - PublicreportQuickPhotoQuickPhotoQuickIDFkey foreignKey -} - -func (f publicreportQuickPhotoForeignKeys) AsSlice() []foreignKey { - return []foreignKey{ - f.PublicreportQuickPhotoQuickPhotoQuickIDFkey, - } -} - -type publicreportQuickPhotoUniques struct{} - -func (u publicreportQuickPhotoUniques) AsSlice() []constraint { - return []constraint{} -} - -type publicreportQuickPhotoChecks struct{} - -func (c publicreportQuickPhotoChecks) AsSlice() []check { - return []check{} -} diff --git a/db/factory/bobfactory_context.bob.go b/db/factory/bobfactory_context.bob.go index 289d2646..7e9c7a84 100644 --- a/db/factory/bobfactory_context.bob.go +++ b/db/factory/bobfactory_context.bob.go @@ -222,24 +222,36 @@ var ( organizationRelImportDistrictGidDistrictCtx = newContextual[bool]("import.district.organization.organization.organization_import_district_gid_fkey") organizationRelUserCtx = newContextual[bool]("organization.user_.user_.user__organization_id_fkey") + // Relationship Contexts for publicreport.image + publicreportImageWithParentsCascadingCtx = newContextual[bool]("publicreportImageWithParentsCascading") + publicreportImageRelImageExifsCtx = newContextual[bool]("publicreport.image.publicreport.image_exif.publicreport.image_exif.image_exif_image_id_fkey") + publicreportImageRelPoolsCtx = newContextual[bool]("publicreport.image.publicreport.pool.publicreport.pool_image.pool_image_image_id_fkeypublicreport.pool_image.pool_image_pool_id_fkey") + publicreportImageRelQuicksCtx = newContextual[bool]("publicreport.image.publicreport.quick.publicreport.quick_image.quick_image_image_id_fkeypublicreport.quick_image.quick_image_quick_id_fkey") + + // Relationship Contexts for publicreport.image_exif + publicreportImageExifWithParentsCascadingCtx = newContextual[bool]("publicreportImageExifWithParentsCascading") + publicreportImageExifRelImageCtx = newContextual[bool]("publicreport.image.publicreport.image_exif.publicreport.image_exif.image_exif_image_id_fkey") + // 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") + publicreportPoolRelImagesCtx = newContextual[bool]("publicreport.image.publicreport.pool.publicreport.pool_image.pool_image_image_id_fkeypublicreport.pool_image.pool_image_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.pool_image + publicreportPoolImageWithParentsCascadingCtx = newContextual[bool]("publicreportPoolImageWithParentsCascading") + publicreportPoolImageRelImageCtx = newContextual[bool]("publicreport.image.publicreport.pool_image.publicreport.pool_image.pool_image_image_id_fkey") + publicreportPoolImageRelPoolCtx = newContextual[bool]("publicreport.pool.publicreport.pool_image.publicreport.pool_image.pool_image_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") + publicreportQuickRelImagesCtx = newContextual[bool]("publicreport.image.publicreport.quick.publicreport.quick_image.quick_image_image_id_fkeypublicreport.quick_image.quick_image_quick_id_fkey") - // Relationship Contexts for publicreport.quick_photo - publicreportQuickPhotoWithParentsCascadingCtx = newContextual[bool]("publicreportQuickPhotoWithParentsCascading") - publicreportQuickPhotoRelQuickCtx = newContextual[bool]("publicreport.quick.publicreport.quick_photo.publicreport.quick_photo.quick_photo_quick_id_fkey") + // Relationship Contexts for publicreport.quick_image + publicreportQuickImageWithParentsCascadingCtx = newContextual[bool]("publicreportQuickImageWithParentsCascading") + publicreportQuickImageRelImageCtx = newContextual[bool]("publicreport.image.publicreport.quick_image.publicreport.quick_image.quick_image_image_id_fkey") + publicreportQuickImageRelQuickCtx = newContextual[bool]("publicreport.quick.publicreport.quick_image.publicreport.quick_image.quick_image_quick_id_fkey") // Relationship Contexts for publicreport.report_location publicreportReportLocationWithParentsCascadingCtx = newContextual[bool]("publicreportReportLocationWithParentsCascading") diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index eadbf506..d184b64e 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -62,11 +62,13 @@ type Factory struct { baseNotificationMods NotificationModSlice baseOauthTokenMods OauthTokenModSlice baseOrganizationMods OrganizationModSlice + basePublicreportImageMods PublicreportImageModSlice + basePublicreportImageExifMods PublicreportImageExifModSlice basePublicreportNuisanceMods PublicreportNuisanceModSlice basePublicreportPoolMods PublicreportPoolModSlice - basePublicreportPoolPhotoMods PublicreportPoolPhotoModSlice + basePublicreportPoolImageMods PublicreportPoolImageModSlice basePublicreportQuickMods PublicreportQuickModSlice - basePublicreportQuickPhotoMods PublicreportQuickPhotoModSlice + basePublicreportQuickImageMods PublicreportQuickImageModSlice basePublicreportReportLocationMods PublicreportReportLocationModSlice baseRasterColumnMods RasterColumnModSlice baseRasterOverviewMods RasterOverviewModSlice @@ -2512,6 +2514,79 @@ func (f *Factory) FromExistingOrganization(m *models.Organization) *Organization return o } +func (f *Factory) NewPublicreportImage(mods ...PublicreportImageMod) *PublicreportImageTemplate { + return f.NewPublicreportImageWithContext(context.Background(), mods...) +} + +func (f *Factory) NewPublicreportImageWithContext(ctx context.Context, mods ...PublicreportImageMod) *PublicreportImageTemplate { + o := &PublicreportImageTemplate{f: f} + + if f != nil { + f.basePublicreportImageMods.Apply(ctx, o) + } + + PublicreportImageModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingPublicreportImage(m *models.PublicreportImage) *PublicreportImageTemplate { + o := &PublicreportImageTemplate{f: f, alreadyPersisted: true} + + o.ID = func() int32 { return m.ID } + o.ContentType = func() string { return m.ContentType } + o.Created = func() time.Time { return m.Created } + o.ResolutionX = func() int32 { return m.ResolutionX } + o.ResolutionY = func() int32 { return m.ResolutionY } + o.StorageUUID = func() uuid.UUID { return m.StorageUUID } + o.StorageSize = func() int64 { return m.StorageSize } + o.UploadedFilename = func() string { return m.UploadedFilename } + + ctx := context.Background() + if len(m.R.ImageExifs) > 0 { + PublicreportImageMods.AddExistingImageExifs(m.R.ImageExifs...).Apply(ctx, o) + } + if len(m.R.Pools) > 0 { + PublicreportImageMods.AddExistingPools(m.R.Pools...).Apply(ctx, o) + } + if len(m.R.Quicks) > 0 { + PublicreportImageMods.AddExistingQuicks(m.R.Quicks...).Apply(ctx, o) + } + + return o +} + +func (f *Factory) NewPublicreportImageExif(mods ...PublicreportImageExifMod) *PublicreportImageExifTemplate { + return f.NewPublicreportImageExifWithContext(context.Background(), mods...) +} + +func (f *Factory) NewPublicreportImageExifWithContext(ctx context.Context, mods ...PublicreportImageExifMod) *PublicreportImageExifTemplate { + o := &PublicreportImageExifTemplate{f: f} + + if f != nil { + f.basePublicreportImageExifMods.Apply(ctx, o) + } + + PublicreportImageExifModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingPublicreportImageExif(m *models.PublicreportImageExif) *PublicreportImageExifTemplate { + o := &PublicreportImageExifTemplate{f: f, alreadyPersisted: true} + + o.ImageID = func() int32 { return m.ImageID } + o.Name = func() string { return m.Name } + o.Value = func() string { return m.Value } + + ctx := context.Background() + if m.R.Image != nil { + PublicreportImageExifMods.WithExistingImage(m.R.Image).Apply(ctx, o) + } + + return o +} + func (f *Factory) NewPublicreportNuisance(mods ...PublicreportNuisanceMod) *PublicreportNuisanceTemplate { return f.NewPublicreportNuisanceWithContext(context.Background(), mods...) } @@ -2613,41 +2688,41 @@ func (f *Factory) FromExistingPublicreportPool(m *models.PublicreportPool) *Publ o.Status = func() enums.PublicreportReportstatustype { return m.Status } ctx := context.Background() - if len(m.R.PoolPhotos) > 0 { - PublicreportPoolMods.AddExistingPoolPhotos(m.R.PoolPhotos...).Apply(ctx, o) + if len(m.R.Images) > 0 { + PublicreportPoolMods.AddExistingImages(m.R.Images...).Apply(ctx, o) } return o } -func (f *Factory) NewPublicreportPoolPhoto(mods ...PublicreportPoolPhotoMod) *PublicreportPoolPhotoTemplate { - return f.NewPublicreportPoolPhotoWithContext(context.Background(), mods...) +func (f *Factory) NewPublicreportPoolImage(mods ...PublicreportPoolImageMod) *PublicreportPoolImageTemplate { + return f.NewPublicreportPoolImageWithContext(context.Background(), mods...) } -func (f *Factory) NewPublicreportPoolPhotoWithContext(ctx context.Context, mods ...PublicreportPoolPhotoMod) *PublicreportPoolPhotoTemplate { - o := &PublicreportPoolPhotoTemplate{f: f} +func (f *Factory) NewPublicreportPoolImageWithContext(ctx context.Context, mods ...PublicreportPoolImageMod) *PublicreportPoolImageTemplate { + o := &PublicreportPoolImageTemplate{f: f} if f != nil { - f.basePublicreportPoolPhotoMods.Apply(ctx, o) + f.basePublicreportPoolImageMods.Apply(ctx, o) } - PublicreportPoolPhotoModSlice(mods).Apply(ctx, o) + PublicreportPoolImageModSlice(mods).Apply(ctx, o) return o } -func (f *Factory) FromExistingPublicreportPoolPhoto(m *models.PublicreportPoolPhoto) *PublicreportPoolPhotoTemplate { - o := &PublicreportPoolPhotoTemplate{f: f, alreadyPersisted: true} +func (f *Factory) FromExistingPublicreportPoolImage(m *models.PublicreportPoolImage) *PublicreportPoolImageTemplate { + o := &PublicreportPoolImageTemplate{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.ImageID = func() int32 { return m.ImageID } o.PoolID = func() int32 { return m.PoolID } - o.UUID = func() uuid.UUID { return m.UUID } ctx := context.Background() + if m.R.Image != nil { + PublicreportPoolImageMods.WithExistingImage(m.R.Image).Apply(ctx, o) + } if m.R.Pool != nil { - PublicreportPoolPhotoMods.WithExistingPool(m.R.Pool).Apply(ctx, o) + PublicreportPoolImageMods.WithExistingPool(m.R.Pool).Apply(ctx, o) } return o @@ -2684,41 +2759,41 @@ func (f *Factory) FromExistingPublicreportQuick(m *models.PublicreportQuick) *Pu o.Status = func() enums.PublicreportReportstatustype { return m.Status } ctx := context.Background() - if len(m.R.QuickPhotos) > 0 { - PublicreportQuickMods.AddExistingQuickPhotos(m.R.QuickPhotos...).Apply(ctx, o) + if len(m.R.Images) > 0 { + PublicreportQuickMods.AddExistingImages(m.R.Images...).Apply(ctx, o) } return o } -func (f *Factory) NewPublicreportQuickPhoto(mods ...PublicreportQuickPhotoMod) *PublicreportQuickPhotoTemplate { - return f.NewPublicreportQuickPhotoWithContext(context.Background(), mods...) +func (f *Factory) NewPublicreportQuickImage(mods ...PublicreportQuickImageMod) *PublicreportQuickImageTemplate { + return f.NewPublicreportQuickImageWithContext(context.Background(), mods...) } -func (f *Factory) NewPublicreportQuickPhotoWithContext(ctx context.Context, mods ...PublicreportQuickPhotoMod) *PublicreportQuickPhotoTemplate { - o := &PublicreportQuickPhotoTemplate{f: f} +func (f *Factory) NewPublicreportQuickImageWithContext(ctx context.Context, mods ...PublicreportQuickImageMod) *PublicreportQuickImageTemplate { + o := &PublicreportQuickImageTemplate{f: f} if f != nil { - f.basePublicreportQuickPhotoMods.Apply(ctx, o) + f.basePublicreportQuickImageMods.Apply(ctx, o) } - PublicreportQuickPhotoModSlice(mods).Apply(ctx, o) + PublicreportQuickImageModSlice(mods).Apply(ctx, o) return o } -func (f *Factory) FromExistingPublicreportQuickPhoto(m *models.PublicreportQuickPhoto) *PublicreportQuickPhotoTemplate { - o := &PublicreportQuickPhotoTemplate{f: f, alreadyPersisted: true} +func (f *Factory) FromExistingPublicreportQuickImage(m *models.PublicreportQuickImage) *PublicreportQuickImageTemplate { + o := &PublicreportQuickImageTemplate{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.ImageID = func() int32 { return m.ImageID } o.QuickID = func() int32 { return m.QuickID } - o.UUID = func() uuid.UUID { return m.UUID } ctx := context.Background() + if m.R.Image != nil { + PublicreportQuickImageMods.WithExistingImage(m.R.Image).Apply(ctx, o) + } if m.R.Quick != nil { - PublicreportQuickPhotoMods.WithExistingQuick(m.R.Quick).Apply(ctx, o) + PublicreportQuickImageMods.WithExistingQuick(m.R.Quick).Apply(ctx, o) } return o @@ -3293,6 +3368,22 @@ func (f *Factory) AddBaseOrganizationMod(mods ...OrganizationMod) { f.baseOrganizationMods = append(f.baseOrganizationMods, mods...) } +func (f *Factory) ClearBasePublicreportImageMods() { + f.basePublicreportImageMods = nil +} + +func (f *Factory) AddBasePublicreportImageMod(mods ...PublicreportImageMod) { + f.basePublicreportImageMods = append(f.basePublicreportImageMods, mods...) +} + +func (f *Factory) ClearBasePublicreportImageExifMods() { + f.basePublicreportImageExifMods = nil +} + +func (f *Factory) AddBasePublicreportImageExifMod(mods ...PublicreportImageExifMod) { + f.basePublicreportImageExifMods = append(f.basePublicreportImageExifMods, mods...) +} + func (f *Factory) ClearBasePublicreportNuisanceMods() { f.basePublicreportNuisanceMods = nil } @@ -3309,12 +3400,12 @@ func (f *Factory) AddBasePublicreportPoolMod(mods ...PublicreportPoolMod) { f.basePublicreportPoolMods = append(f.basePublicreportPoolMods, mods...) } -func (f *Factory) ClearBasePublicreportPoolPhotoMods() { - f.basePublicreportPoolPhotoMods = nil +func (f *Factory) ClearBasePublicreportPoolImageMods() { + f.basePublicreportPoolImageMods = nil } -func (f *Factory) AddBasePublicreportPoolPhotoMod(mods ...PublicreportPoolPhotoMod) { - f.basePublicreportPoolPhotoMods = append(f.basePublicreportPoolPhotoMods, mods...) +func (f *Factory) AddBasePublicreportPoolImageMod(mods ...PublicreportPoolImageMod) { + f.basePublicreportPoolImageMods = append(f.basePublicreportPoolImageMods, mods...) } func (f *Factory) ClearBasePublicreportQuickMods() { @@ -3325,12 +3416,12 @@ func (f *Factory) AddBasePublicreportQuickMod(mods ...PublicreportQuickMod) { f.basePublicreportQuickMods = append(f.basePublicreportQuickMods, mods...) } -func (f *Factory) ClearBasePublicreportQuickPhotoMods() { - f.basePublicreportQuickPhotoMods = nil +func (f *Factory) ClearBasePublicreportQuickImageMods() { + f.basePublicreportQuickImageMods = nil } -func (f *Factory) AddBasePublicreportQuickPhotoMod(mods ...PublicreportQuickPhotoMod) { - f.basePublicreportQuickPhotoMods = append(f.basePublicreportQuickPhotoMods, mods...) +func (f *Factory) AddBasePublicreportQuickImageMod(mods ...PublicreportQuickImageMod) { + f.basePublicreportQuickImageMods = append(f.basePublicreportQuickImageMods, mods...) } func (f *Factory) ClearBasePublicreportReportLocationMods() { diff --git a/db/factory/publicreport.image.bob.go b/db/factory/publicreport.image.bob.go new file mode 100644 index 00000000..3d292ad4 --- /dev/null +++ b/db/factory/publicreport.image.bob.go @@ -0,0 +1,822 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + "time" + + 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 PublicreportImageMod interface { + Apply(context.Context, *PublicreportImageTemplate) +} + +type PublicreportImageModFunc func(context.Context, *PublicreportImageTemplate) + +func (f PublicreportImageModFunc) Apply(ctx context.Context, n *PublicreportImageTemplate) { + f(ctx, n) +} + +type PublicreportImageModSlice []PublicreportImageMod + +func (mods PublicreportImageModSlice) Apply(ctx context.Context, n *PublicreportImageTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// PublicreportImageTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type PublicreportImageTemplate struct { + ID func() int32 + ContentType func() string + Created func() time.Time + ResolutionX func() int32 + ResolutionY func() int32 + StorageUUID func() uuid.UUID + StorageSize func() int64 + UploadedFilename func() string + + r publicreportImageR + f *Factory + + alreadyPersisted bool +} + +type publicreportImageR struct { + ImageExifs []*publicreportImageRImageExifsR + Pools []*publicreportImageRPoolsR + Quicks []*publicreportImageRQuicksR +} + +type publicreportImageRImageExifsR struct { + number int + o *PublicreportImageExifTemplate +} +type publicreportImageRPoolsR struct { + number int + o *PublicreportPoolTemplate +} +type publicreportImageRQuicksR struct { + number int + o *PublicreportQuickTemplate +} + +// Apply mods to the PublicreportImageTemplate +func (o *PublicreportImageTemplate) Apply(ctx context.Context, mods ...PublicreportImageMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.PublicreportImage +// according to the relationships in the template. Nothing is inserted into the db +func (t PublicreportImageTemplate) setModelRels(o *models.PublicreportImage) { + if t.r.ImageExifs != nil { + rel := models.PublicreportImageExifSlice{} + for _, r := range t.r.ImageExifs { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.ImageID = o.ID // h2 + rel.R.Image = o + } + rel = append(rel, related...) + } + o.R.ImageExifs = rel + } + + if t.r.Pools != nil { + rel := models.PublicreportPoolSlice{} + for _, r := range t.r.Pools { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.R.Images = append(rel.R.Images, o) + } + rel = append(rel, related...) + } + o.R.Pools = rel + } + + if t.r.Quicks != nil { + rel := models.PublicreportQuickSlice{} + for _, r := range t.r.Quicks { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.R.Images = append(rel.R.Images, o) + } + rel = append(rel, related...) + } + o.R.Quicks = rel + } +} + +// BuildSetter returns an *models.PublicreportImageSetter +// this does nothing with the relationship templates +func (o PublicreportImageTemplate) BuildSetter() *models.PublicreportImageSetter { + m := &models.PublicreportImageSetter{} + + if o.ID != nil { + val := o.ID() + m.ID = omit.From(val) + } + if o.ContentType != nil { + val := o.ContentType() + m.ContentType = omit.From(val) + } + if o.Created != nil { + val := o.Created() + m.Created = omit.From(val) + } + if o.ResolutionX != nil { + val := o.ResolutionX() + m.ResolutionX = omit.From(val) + } + if o.ResolutionY != nil { + val := o.ResolutionY() + m.ResolutionY = omit.From(val) + } + if o.StorageUUID != nil { + val := o.StorageUUID() + m.StorageUUID = omit.From(val) + } + if o.StorageSize != nil { + val := o.StorageSize() + m.StorageSize = omit.From(val) + } + if o.UploadedFilename != nil { + val := o.UploadedFilename() + m.UploadedFilename = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.PublicreportImageSetter +// this does nothing with the relationship templates +func (o PublicreportImageTemplate) BuildManySetter(number int) []*models.PublicreportImageSetter { + m := make([]*models.PublicreportImageSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.PublicreportImage +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use PublicreportImageTemplate.Create +func (o PublicreportImageTemplate) Build() *models.PublicreportImage { + m := &models.PublicreportImage{} + + if o.ID != nil { + m.ID = o.ID() + } + if o.ContentType != nil { + m.ContentType = o.ContentType() + } + if o.Created != nil { + m.Created = o.Created() + } + if o.ResolutionX != nil { + m.ResolutionX = o.ResolutionX() + } + if o.ResolutionY != nil { + m.ResolutionY = o.ResolutionY() + } + if o.StorageUUID != nil { + m.StorageUUID = o.StorageUUID() + } + if o.StorageSize != nil { + m.StorageSize = o.StorageSize() + } + if o.UploadedFilename != nil { + m.UploadedFilename = o.UploadedFilename() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.PublicreportImageSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use PublicreportImageTemplate.CreateMany +func (o PublicreportImageTemplate) BuildMany(number int) models.PublicreportImageSlice { + m := make(models.PublicreportImageSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatablePublicreportImage(m *models.PublicreportImageSetter) { + if !(m.ContentType.IsValue()) { + val := random_string(nil) + m.ContentType = omit.From(val) + } + if !(m.Created.IsValue()) { + val := random_time_Time(nil) + m.Created = omit.From(val) + } + if !(m.ResolutionX.IsValue()) { + val := random_int32(nil) + m.ResolutionX = omit.From(val) + } + if !(m.ResolutionY.IsValue()) { + val := random_int32(nil) + m.ResolutionY = omit.From(val) + } + if !(m.StorageUUID.IsValue()) { + val := random_uuid_UUID(nil) + m.StorageUUID = omit.From(val) + } + if !(m.StorageSize.IsValue()) { + val := random_int64(nil) + m.StorageSize = omit.From(val) + } + if !(m.UploadedFilename.IsValue()) { + val := random_string(nil) + m.UploadedFilename = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.PublicreportImage +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *PublicreportImageTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.PublicreportImage) error { + var err error + + isImageExifsDone, _ := publicreportImageRelImageExifsCtx.Value(ctx) + if !isImageExifsDone && o.r.ImageExifs != nil { + ctx = publicreportImageRelImageExifsCtx.WithValue(ctx, true) + for _, r := range o.r.ImageExifs { + if r.o.alreadyPersisted { + m.R.ImageExifs = append(m.R.ImageExifs, r.o.Build()) + } else { + rel0, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachImageExifs(ctx, exec, rel0...) + if err != nil { + return err + } + } + } + } + + isPoolsDone, _ := publicreportImageRelPoolsCtx.Value(ctx) + if !isPoolsDone && o.r.Pools != nil { + ctx = publicreportImageRelPoolsCtx.WithValue(ctx, true) + for _, r := range o.r.Pools { + if r.o.alreadyPersisted { + m.R.Pools = append(m.R.Pools, r.o.Build()) + } else { + rel1, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachPools(ctx, exec, rel1...) + if err != nil { + return err + } + } + } + } + + isQuicksDone, _ := publicreportImageRelQuicksCtx.Value(ctx) + if !isQuicksDone && o.r.Quicks != nil { + ctx = publicreportImageRelQuicksCtx.WithValue(ctx, true) + for _, r := range o.r.Quicks { + if r.o.alreadyPersisted { + m.R.Quicks = append(m.R.Quicks, r.o.Build()) + } else { + rel2, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachQuicks(ctx, exec, rel2...) + if err != nil { + return err + } + } + } + } + + return err +} + +// Create builds a publicreportImage and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *PublicreportImageTemplate) Create(ctx context.Context, exec bob.Executor) (*models.PublicreportImage, error) { + var err error + opt := o.BuildSetter() + ensureCreatablePublicreportImage(opt) + + m, err := models.PublicreportImages.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a publicreportImage and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *PublicreportImageTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.PublicreportImage { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a publicreportImage 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 *PublicreportImageTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.PublicreportImage { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple publicreportImages and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o PublicreportImageTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.PublicreportImageSlice, error) { + var err error + m := make(models.PublicreportImageSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple publicreportImages and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o PublicreportImageTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.PublicreportImageSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple publicreportImages 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 PublicreportImageTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.PublicreportImageSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// PublicreportImage has methods that act as mods for the PublicreportImageTemplate +var PublicreportImageMods publicreportImageMods + +type publicreportImageMods struct{} + +func (m publicreportImageMods) RandomizeAllColumns(f *faker.Faker) PublicreportImageMod { + return PublicreportImageModSlice{ + PublicreportImageMods.RandomID(f), + PublicreportImageMods.RandomContentType(f), + PublicreportImageMods.RandomCreated(f), + PublicreportImageMods.RandomResolutionX(f), + PublicreportImageMods.RandomResolutionY(f), + PublicreportImageMods.RandomStorageUUID(f), + PublicreportImageMods.RandomStorageSize(f), + PublicreportImageMods.RandomUploadedFilename(f), + } +} + +// Set the model columns to this value +func (m publicreportImageMods) ID(val int32) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageMods) IDFunc(f func() int32) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m publicreportImageMods) UnsetID() PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + 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 publicreportImageMods) RandomID(f *faker.Faker) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m publicreportImageMods) ContentType(val string) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ContentType = func() string { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageMods) ContentTypeFunc(f func() string) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ContentType = f + }) +} + +// Clear any values for the column +func (m publicreportImageMods) UnsetContentType() PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ContentType = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportImageMods) RandomContentType(f *faker.Faker) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ContentType = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m publicreportImageMods) Created(val time.Time) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.Created = func() time.Time { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageMods) CreatedFunc(f func() time.Time) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.Created = f + }) +} + +// Clear any values for the column +func (m publicreportImageMods) UnsetCreated() PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.Created = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportImageMods) RandomCreated(f *faker.Faker) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.Created = func() time.Time { + return random_time_Time(f) + } + }) +} + +// Set the model columns to this value +func (m publicreportImageMods) ResolutionX(val int32) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ResolutionX = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageMods) ResolutionXFunc(f func() int32) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ResolutionX = f + }) +} + +// Clear any values for the column +func (m publicreportImageMods) UnsetResolutionX() PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ResolutionX = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportImageMods) RandomResolutionX(f *faker.Faker) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ResolutionX = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m publicreportImageMods) ResolutionY(val int32) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ResolutionY = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageMods) ResolutionYFunc(f func() int32) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ResolutionY = f + }) +} + +// Clear any values for the column +func (m publicreportImageMods) UnsetResolutionY() PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ResolutionY = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportImageMods) RandomResolutionY(f *faker.Faker) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.ResolutionY = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m publicreportImageMods) StorageUUID(val uuid.UUID) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.StorageUUID = func() uuid.UUID { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageMods) StorageUUIDFunc(f func() uuid.UUID) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.StorageUUID = f + }) +} + +// Clear any values for the column +func (m publicreportImageMods) UnsetStorageUUID() PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.StorageUUID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportImageMods) RandomStorageUUID(f *faker.Faker) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.StorageUUID = func() uuid.UUID { + return random_uuid_UUID(f) + } + }) +} + +// Set the model columns to this value +func (m publicreportImageMods) StorageSize(val int64) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.StorageSize = func() int64 { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageMods) StorageSizeFunc(f func() int64) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.StorageSize = f + }) +} + +// Clear any values for the column +func (m publicreportImageMods) UnsetStorageSize() PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.StorageSize = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportImageMods) RandomStorageSize(f *faker.Faker) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.StorageSize = func() int64 { + return random_int64(f) + } + }) +} + +// Set the model columns to this value +func (m publicreportImageMods) UploadedFilename(val string) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.UploadedFilename = func() string { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageMods) UploadedFilenameFunc(f func() string) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.UploadedFilename = f + }) +} + +// Clear any values for the column +func (m publicreportImageMods) UnsetUploadedFilename() PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.UploadedFilename = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportImageMods) RandomUploadedFilename(f *faker.Faker) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.UploadedFilename = func() string { + return random_string(f) + } + }) +} + +func (m publicreportImageMods) WithParentsCascading() PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + if isDone, _ := publicreportImageWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = publicreportImageWithParentsCascadingCtx.WithValue(ctx, true) + }) +} + +func (m publicreportImageMods) WithImageExifs(number int, related *PublicreportImageExifTemplate) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + o.r.ImageExifs = []*publicreportImageRImageExifsR{{ + number: number, + o: related, + }} + }) +} + +func (m publicreportImageMods) WithNewImageExifs(number int, mods ...PublicreportImageExifMod) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + related := o.f.NewPublicreportImageExifWithContext(ctx, mods...) + m.WithImageExifs(number, related).Apply(ctx, o) + }) +} + +func (m publicreportImageMods) AddImageExifs(number int, related *PublicreportImageExifTemplate) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + o.r.ImageExifs = append(o.r.ImageExifs, &publicreportImageRImageExifsR{ + number: number, + o: related, + }) + }) +} + +func (m publicreportImageMods) AddNewImageExifs(number int, mods ...PublicreportImageExifMod) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + related := o.f.NewPublicreportImageExifWithContext(ctx, mods...) + m.AddImageExifs(number, related).Apply(ctx, o) + }) +} + +func (m publicreportImageMods) AddExistingImageExifs(existingModels ...*models.PublicreportImageExif) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + for _, em := range existingModels { + o.r.ImageExifs = append(o.r.ImageExifs, &publicreportImageRImageExifsR{ + o: o.f.FromExistingPublicreportImageExif(em), + }) + } + }) +} + +func (m publicreportImageMods) WithoutImageExifs() PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + o.r.ImageExifs = nil + }) +} + +func (m publicreportImageMods) WithPools(number int, related *PublicreportPoolTemplate) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + o.r.Pools = []*publicreportImageRPoolsR{{ + number: number, + o: related, + }} + }) +} + +func (m publicreportImageMods) WithNewPools(number int, mods ...PublicreportPoolMod) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + related := o.f.NewPublicreportPoolWithContext(ctx, mods...) + m.WithPools(number, related).Apply(ctx, o) + }) +} + +func (m publicreportImageMods) AddPools(number int, related *PublicreportPoolTemplate) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + o.r.Pools = append(o.r.Pools, &publicreportImageRPoolsR{ + number: number, + o: related, + }) + }) +} + +func (m publicreportImageMods) AddNewPools(number int, mods ...PublicreportPoolMod) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + related := o.f.NewPublicreportPoolWithContext(ctx, mods...) + m.AddPools(number, related).Apply(ctx, o) + }) +} + +func (m publicreportImageMods) AddExistingPools(existingModels ...*models.PublicreportPool) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + for _, em := range existingModels { + o.r.Pools = append(o.r.Pools, &publicreportImageRPoolsR{ + o: o.f.FromExistingPublicreportPool(em), + }) + } + }) +} + +func (m publicreportImageMods) WithoutPools() PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + o.r.Pools = nil + }) +} + +func (m publicreportImageMods) WithQuicks(number int, related *PublicreportQuickTemplate) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + o.r.Quicks = []*publicreportImageRQuicksR{{ + number: number, + o: related, + }} + }) +} + +func (m publicreportImageMods) WithNewQuicks(number int, mods ...PublicreportQuickMod) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + related := o.f.NewPublicreportQuickWithContext(ctx, mods...) + m.WithQuicks(number, related).Apply(ctx, o) + }) +} + +func (m publicreportImageMods) AddQuicks(number int, related *PublicreportQuickTemplate) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + o.r.Quicks = append(o.r.Quicks, &publicreportImageRQuicksR{ + number: number, + o: related, + }) + }) +} + +func (m publicreportImageMods) AddNewQuicks(number int, mods ...PublicreportQuickMod) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + related := o.f.NewPublicreportQuickWithContext(ctx, mods...) + m.AddQuicks(number, related).Apply(ctx, o) + }) +} + +func (m publicreportImageMods) AddExistingQuicks(existingModels ...*models.PublicreportQuick) PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + for _, em := range existingModels { + o.r.Quicks = append(o.r.Quicks, &publicreportImageRQuicksR{ + o: o.f.FromExistingPublicreportQuick(em), + }) + } + }) +} + +func (m publicreportImageMods) WithoutQuicks() PublicreportImageMod { + return PublicreportImageModFunc(func(ctx context.Context, o *PublicreportImageTemplate) { + o.r.Quicks = nil + }) +} diff --git a/db/factory/publicreport.image_exif.bob.go b/db/factory/publicreport.image_exif.bob.go new file mode 100644 index 00000000..742829a4 --- /dev/null +++ b/db/factory/publicreport.image_exif.bob.go @@ -0,0 +1,413 @@ +// Code generated by BobGen psql v0.42.1. 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/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type PublicreportImageExifMod interface { + Apply(context.Context, *PublicreportImageExifTemplate) +} + +type PublicreportImageExifModFunc func(context.Context, *PublicreportImageExifTemplate) + +func (f PublicreportImageExifModFunc) Apply(ctx context.Context, n *PublicreportImageExifTemplate) { + f(ctx, n) +} + +type PublicreportImageExifModSlice []PublicreportImageExifMod + +func (mods PublicreportImageExifModSlice) Apply(ctx context.Context, n *PublicreportImageExifTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// PublicreportImageExifTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type PublicreportImageExifTemplate struct { + ImageID func() int32 + Name func() string + Value func() string + + r publicreportImageExifR + f *Factory + + alreadyPersisted bool +} + +type publicreportImageExifR struct { + Image *publicreportImageExifRImageR +} + +type publicreportImageExifRImageR struct { + o *PublicreportImageTemplate +} + +// Apply mods to the PublicreportImageExifTemplate +func (o *PublicreportImageExifTemplate) Apply(ctx context.Context, mods ...PublicreportImageExifMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.PublicreportImageExif +// according to the relationships in the template. Nothing is inserted into the db +func (t PublicreportImageExifTemplate) setModelRels(o *models.PublicreportImageExif) { + if t.r.Image != nil { + rel := t.r.Image.o.Build() + rel.R.ImageExifs = append(rel.R.ImageExifs, o) + o.ImageID = rel.ID // h2 + o.R.Image = rel + } +} + +// BuildSetter returns an *models.PublicreportImageExifSetter +// this does nothing with the relationship templates +func (o PublicreportImageExifTemplate) BuildSetter() *models.PublicreportImageExifSetter { + m := &models.PublicreportImageExifSetter{} + + if o.ImageID != nil { + val := o.ImageID() + m.ImageID = omit.From(val) + } + if o.Name != nil { + val := o.Name() + m.Name = omit.From(val) + } + if o.Value != nil { + val := o.Value() + m.Value = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.PublicreportImageExifSetter +// this does nothing with the relationship templates +func (o PublicreportImageExifTemplate) BuildManySetter(number int) []*models.PublicreportImageExifSetter { + m := make([]*models.PublicreportImageExifSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.PublicreportImageExif +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use PublicreportImageExifTemplate.Create +func (o PublicreportImageExifTemplate) Build() *models.PublicreportImageExif { + m := &models.PublicreportImageExif{} + + if o.ImageID != nil { + m.ImageID = o.ImageID() + } + if o.Name != nil { + m.Name = o.Name() + } + if o.Value != nil { + m.Value = o.Value() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.PublicreportImageExifSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use PublicreportImageExifTemplate.CreateMany +func (o PublicreportImageExifTemplate) BuildMany(number int) models.PublicreportImageExifSlice { + m := make(models.PublicreportImageExifSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatablePublicreportImageExif(m *models.PublicreportImageExifSetter) { + if !(m.ImageID.IsValue()) { + val := random_int32(nil) + m.ImageID = omit.From(val) + } + if !(m.Name.IsValue()) { + val := random_string(nil) + m.Name = omit.From(val) + } + if !(m.Value.IsValue()) { + val := random_string(nil) + m.Value = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.PublicreportImageExif +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *PublicreportImageExifTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.PublicreportImageExif) error { + var err error + + return err +} + +// Create builds a publicreportImageExif and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *PublicreportImageExifTemplate) Create(ctx context.Context, exec bob.Executor) (*models.PublicreportImageExif, error) { + var err error + opt := o.BuildSetter() + ensureCreatablePublicreportImageExif(opt) + + if o.r.Image == nil { + PublicreportImageExifMods.WithNewImage().Apply(ctx, o) + } + + var rel0 *models.PublicreportImage + + if o.r.Image.o.alreadyPersisted { + rel0 = o.r.Image.o.Build() + } else { + rel0, err = o.r.Image.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.ImageID = omit.From(rel0.ID) + + m, err := models.PublicreportImageExifs.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + m.R.Image = rel0 + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a publicreportImageExif and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *PublicreportImageExifTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.PublicreportImageExif { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a publicreportImageExif 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 *PublicreportImageExifTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.PublicreportImageExif { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple publicreportImageExifs and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o PublicreportImageExifTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.PublicreportImageExifSlice, error) { + var err error + m := make(models.PublicreportImageExifSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple publicreportImageExifs and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o PublicreportImageExifTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.PublicreportImageExifSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple publicreportImageExifs 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 PublicreportImageExifTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.PublicreportImageExifSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// PublicreportImageExif has methods that act as mods for the PublicreportImageExifTemplate +var PublicreportImageExifMods publicreportImageExifMods + +type publicreportImageExifMods struct{} + +func (m publicreportImageExifMods) RandomizeAllColumns(f *faker.Faker) PublicreportImageExifMod { + return PublicreportImageExifModSlice{ + PublicreportImageExifMods.RandomImageID(f), + PublicreportImageExifMods.RandomName(f), + PublicreportImageExifMods.RandomValue(f), + } +} + +// Set the model columns to this value +func (m publicreportImageExifMods) ImageID(val int32) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.ImageID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageExifMods) ImageIDFunc(f func() int32) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.ImageID = f + }) +} + +// Clear any values for the column +func (m publicreportImageExifMods) UnsetImageID() PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.ImageID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportImageExifMods) RandomImageID(f *faker.Faker) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.ImageID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m publicreportImageExifMods) Name(val string) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.Name = func() string { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageExifMods) NameFunc(f func() string) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.Name = f + }) +} + +// Clear any values for the column +func (m publicreportImageExifMods) UnsetName() PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.Name = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportImageExifMods) RandomName(f *faker.Faker) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.Name = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m publicreportImageExifMods) Value(val string) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.Value = func() string { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageExifMods) ValueFunc(f func() string) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.Value = f + }) +} + +// Clear any values for the column +func (m publicreportImageExifMods) UnsetValue() PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.Value = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportImageExifMods) RandomValue(f *faker.Faker) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(_ context.Context, o *PublicreportImageExifTemplate) { + o.Value = func() string { + return random_string(f) + } + }) +} + +func (m publicreportImageExifMods) WithParentsCascading() PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(ctx context.Context, o *PublicreportImageExifTemplate) { + if isDone, _ := publicreportImageExifWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = publicreportImageExifWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewPublicreportImageWithContext(ctx, PublicreportImageMods.WithParentsCascading()) + m.WithImage(related).Apply(ctx, o) + } + }) +} + +func (m publicreportImageExifMods) WithImage(rel *PublicreportImageTemplate) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(ctx context.Context, o *PublicreportImageExifTemplate) { + o.r.Image = &publicreportImageExifRImageR{ + o: rel, + } + }) +} + +func (m publicreportImageExifMods) WithNewImage(mods ...PublicreportImageMod) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(ctx context.Context, o *PublicreportImageExifTemplate) { + related := o.f.NewPublicreportImageWithContext(ctx, mods...) + + m.WithImage(related).Apply(ctx, o) + }) +} + +func (m publicreportImageExifMods) WithExistingImage(em *models.PublicreportImage) PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(ctx context.Context, o *PublicreportImageExifTemplate) { + o.r.Image = &publicreportImageExifRImageR{ + o: o.f.FromExistingPublicreportImage(em), + } + }) +} + +func (m publicreportImageExifMods) WithoutImage() PublicreportImageExifMod { + return PublicreportImageExifModFunc(func(ctx context.Context, o *PublicreportImageExifTemplate) { + o.r.Image = nil + }) +} diff --git a/db/factory/publicreport.pool.bob.go b/db/factory/publicreport.pool.bob.go index d35ac5c2..72b2f50e 100644 --- a/db/factory/publicreport.pool.bob.go +++ b/db/factory/publicreport.pool.bob.go @@ -76,12 +76,12 @@ type PublicreportPoolTemplate struct { } type publicreportPoolR struct { - PoolPhotos []*publicreportPoolRPoolPhotosR + Images []*publicreportPoolRImagesR } -type publicreportPoolRPoolPhotosR struct { +type publicreportPoolRImagesR struct { number int - o *PublicreportPoolPhotoTemplate + o *PublicreportImageTemplate } // Apply mods to the PublicreportPoolTemplate @@ -94,17 +94,16 @@ func (o *PublicreportPoolTemplate) Apply(ctx context.Context, mods ...Publicrepo // setModelRels creates and sets the relationships on *models.PublicreportPool // according to the relationships in the template. Nothing is inserted into the db func (t PublicreportPoolTemplate) setModelRels(o *models.PublicreportPool) { - if t.r.PoolPhotos != nil { - rel := models.PublicreportPoolPhotoSlice{} - for _, r := range t.r.PoolPhotos { + if t.r.Images != nil { + rel := models.PublicreportImageSlice{} + for _, r := range t.r.Images { related := r.o.BuildMany(r.number) for _, rel := range related { - rel.PoolID = o.ID // h2 - rel.R.Pool = o + rel.R.Pools = append(rel.R.Pools, o) } rel = append(rel, related...) } - o.R.PoolPhotos = rel + o.R.Images = rel } } @@ -481,19 +480,19 @@ func ensureCreatablePublicreportPool(m *models.PublicreportPoolSetter) { func (o *PublicreportPoolTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.PublicreportPool) error { var err error - isPoolPhotosDone, _ := publicreportPoolRelPoolPhotosCtx.Value(ctx) - if !isPoolPhotosDone && o.r.PoolPhotos != nil { - ctx = publicreportPoolRelPoolPhotosCtx.WithValue(ctx, true) - for _, r := range o.r.PoolPhotos { + isImagesDone, _ := publicreportPoolRelImagesCtx.Value(ctx) + if !isImagesDone && o.r.Images != nil { + ctx = publicreportPoolRelImagesCtx.WithValue(ctx, true) + for _, r := range o.r.Images { if r.o.alreadyPersisted { - m.R.PoolPhotos = append(m.R.PoolPhotos, r.o.Build()) + m.R.Images = append(m.R.Images, r.o.Build()) } else { rel0, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachPoolPhotos(ctx, exec, rel0...) + err = m.AttachImages(ctx, exec, rel0...) if err != nil { return err } @@ -1609,50 +1608,50 @@ func (m publicreportPoolMods) WithParentsCascading() PublicreportPoolMod { }) } -func (m publicreportPoolMods) WithPoolPhotos(number int, related *PublicreportPoolPhotoTemplate) PublicreportPoolMod { +func (m publicreportPoolMods) WithImages(number int, related *PublicreportImageTemplate) PublicreportPoolMod { return PublicreportPoolModFunc(func(ctx context.Context, o *PublicreportPoolTemplate) { - o.r.PoolPhotos = []*publicreportPoolRPoolPhotosR{{ + o.r.Images = []*publicreportPoolRImagesR{{ number: number, o: related, }} }) } -func (m publicreportPoolMods) WithNewPoolPhotos(number int, mods ...PublicreportPoolPhotoMod) PublicreportPoolMod { +func (m publicreportPoolMods) WithNewImages(number int, mods ...PublicreportImageMod) PublicreportPoolMod { return PublicreportPoolModFunc(func(ctx context.Context, o *PublicreportPoolTemplate) { - related := o.f.NewPublicreportPoolPhotoWithContext(ctx, mods...) - m.WithPoolPhotos(number, related).Apply(ctx, o) + related := o.f.NewPublicreportImageWithContext(ctx, mods...) + m.WithImages(number, related).Apply(ctx, o) }) } -func (m publicreportPoolMods) AddPoolPhotos(number int, related *PublicreportPoolPhotoTemplate) PublicreportPoolMod { +func (m publicreportPoolMods) AddImages(number int, related *PublicreportImageTemplate) PublicreportPoolMod { return PublicreportPoolModFunc(func(ctx context.Context, o *PublicreportPoolTemplate) { - o.r.PoolPhotos = append(o.r.PoolPhotos, &publicreportPoolRPoolPhotosR{ + o.r.Images = append(o.r.Images, &publicreportPoolRImagesR{ number: number, o: related, }) }) } -func (m publicreportPoolMods) AddNewPoolPhotos(number int, mods ...PublicreportPoolPhotoMod) PublicreportPoolMod { +func (m publicreportPoolMods) AddNewImages(number int, mods ...PublicreportImageMod) PublicreportPoolMod { return PublicreportPoolModFunc(func(ctx context.Context, o *PublicreportPoolTemplate) { - related := o.f.NewPublicreportPoolPhotoWithContext(ctx, mods...) - m.AddPoolPhotos(number, related).Apply(ctx, o) + related := o.f.NewPublicreportImageWithContext(ctx, mods...) + m.AddImages(number, related).Apply(ctx, o) }) } -func (m publicreportPoolMods) AddExistingPoolPhotos(existingModels ...*models.PublicreportPoolPhoto) PublicreportPoolMod { +func (m publicreportPoolMods) AddExistingImages(existingModels ...*models.PublicreportImage) PublicreportPoolMod { return PublicreportPoolModFunc(func(ctx context.Context, o *PublicreportPoolTemplate) { for _, em := range existingModels { - o.r.PoolPhotos = append(o.r.PoolPhotos, &publicreportPoolRPoolPhotosR{ - o: o.f.FromExistingPublicreportPoolPhoto(em), + o.r.Images = append(o.r.Images, &publicreportPoolRImagesR{ + o: o.f.FromExistingPublicreportImage(em), }) } }) } -func (m publicreportPoolMods) WithoutPoolPhotos() PublicreportPoolMod { +func (m publicreportPoolMods) WithoutImages() PublicreportPoolMod { return PublicreportPoolModFunc(func(ctx context.Context, o *PublicreportPoolTemplate) { - o.r.PoolPhotos = nil + o.r.Images = nil }) } diff --git a/db/factory/publicreport.pool_image.bob.go b/db/factory/publicreport.pool_image.bob.go new file mode 100644 index 00000000..e7af585c --- /dev/null +++ b/db/factory/publicreport.pool_image.bob.go @@ -0,0 +1,431 @@ +// Code generated by BobGen psql v0.42.1. 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/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type PublicreportPoolImageMod interface { + Apply(context.Context, *PublicreportPoolImageTemplate) +} + +type PublicreportPoolImageModFunc func(context.Context, *PublicreportPoolImageTemplate) + +func (f PublicreportPoolImageModFunc) Apply(ctx context.Context, n *PublicreportPoolImageTemplate) { + f(ctx, n) +} + +type PublicreportPoolImageModSlice []PublicreportPoolImageMod + +func (mods PublicreportPoolImageModSlice) Apply(ctx context.Context, n *PublicreportPoolImageTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// PublicreportPoolImageTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type PublicreportPoolImageTemplate struct { + ImageID func() int32 + PoolID func() int32 + + r publicreportPoolImageR + f *Factory + + alreadyPersisted bool +} + +type publicreportPoolImageR struct { + Image *publicreportPoolImageRImageR + Pool *publicreportPoolImageRPoolR +} + +type publicreportPoolImageRImageR struct { + o *PublicreportImageTemplate +} +type publicreportPoolImageRPoolR struct { + o *PublicreportPoolTemplate +} + +// Apply mods to the PublicreportPoolImageTemplate +func (o *PublicreportPoolImageTemplate) Apply(ctx context.Context, mods ...PublicreportPoolImageMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.PublicreportPoolImage +// according to the relationships in the template. Nothing is inserted into the db +func (t PublicreportPoolImageTemplate) setModelRels(o *models.PublicreportPoolImage) { + if t.r.Image != nil { + rel := t.r.Image.o.Build() + o.ImageID = rel.ID // h2 + o.R.Image = rel + } + + if t.r.Pool != nil { + rel := t.r.Pool.o.Build() + o.PoolID = rel.ID // h2 + o.R.Pool = rel + } +} + +// BuildSetter returns an *models.PublicreportPoolImageSetter +// this does nothing with the relationship templates +func (o PublicreportPoolImageTemplate) BuildSetter() *models.PublicreportPoolImageSetter { + m := &models.PublicreportPoolImageSetter{} + + if o.ImageID != nil { + val := o.ImageID() + m.ImageID = omit.From(val) + } + if o.PoolID != nil { + val := o.PoolID() + m.PoolID = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.PublicreportPoolImageSetter +// this does nothing with the relationship templates +func (o PublicreportPoolImageTemplate) BuildManySetter(number int) []*models.PublicreportPoolImageSetter { + m := make([]*models.PublicreportPoolImageSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.PublicreportPoolImage +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use PublicreportPoolImageTemplate.Create +func (o PublicreportPoolImageTemplate) Build() *models.PublicreportPoolImage { + m := &models.PublicreportPoolImage{} + + if o.ImageID != nil { + m.ImageID = o.ImageID() + } + if o.PoolID != nil { + m.PoolID = o.PoolID() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.PublicreportPoolImageSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use PublicreportPoolImageTemplate.CreateMany +func (o PublicreportPoolImageTemplate) BuildMany(number int) models.PublicreportPoolImageSlice { + m := make(models.PublicreportPoolImageSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatablePublicreportPoolImage(m *models.PublicreportPoolImageSetter) { + if !(m.ImageID.IsValue()) { + val := random_int32(nil) + m.ImageID = omit.From(val) + } + if !(m.PoolID.IsValue()) { + val := random_int32(nil) + m.PoolID = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.PublicreportPoolImage +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *PublicreportPoolImageTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.PublicreportPoolImage) error { + var err error + + return err +} + +// Create builds a publicreportPoolImage and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *PublicreportPoolImageTemplate) Create(ctx context.Context, exec bob.Executor) (*models.PublicreportPoolImage, error) { + var err error + opt := o.BuildSetter() + ensureCreatablePublicreportPoolImage(opt) + + if o.r.Image == nil { + PublicreportPoolImageMods.WithNewImage().Apply(ctx, o) + } + + var rel0 *models.PublicreportImage + + if o.r.Image.o.alreadyPersisted { + rel0 = o.r.Image.o.Build() + } else { + rel0, err = o.r.Image.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.ImageID = omit.From(rel0.ID) + + if o.r.Pool == nil { + PublicreportPoolImageMods.WithNewPool().Apply(ctx, o) + } + + var rel1 *models.PublicreportPool + + if o.r.Pool.o.alreadyPersisted { + rel1 = o.r.Pool.o.Build() + } else { + rel1, err = o.r.Pool.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.PoolID = omit.From(rel1.ID) + + m, err := models.PublicreportPoolImages.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + m.R.Image = rel0 + m.R.Pool = rel1 + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a publicreportPoolImage and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *PublicreportPoolImageTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.PublicreportPoolImage { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a publicreportPoolImage 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 *PublicreportPoolImageTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.PublicreportPoolImage { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple publicreportPoolImages and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o PublicreportPoolImageTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.PublicreportPoolImageSlice, error) { + var err error + m := make(models.PublicreportPoolImageSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple publicreportPoolImages and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o PublicreportPoolImageTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.PublicreportPoolImageSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple publicreportPoolImages 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 PublicreportPoolImageTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.PublicreportPoolImageSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// PublicreportPoolImage has methods that act as mods for the PublicreportPoolImageTemplate +var PublicreportPoolImageMods publicreportPoolImageMods + +type publicreportPoolImageMods struct{} + +func (m publicreportPoolImageMods) RandomizeAllColumns(f *faker.Faker) PublicreportPoolImageMod { + return PublicreportPoolImageModSlice{ + PublicreportPoolImageMods.RandomImageID(f), + PublicreportPoolImageMods.RandomPoolID(f), + } +} + +// Set the model columns to this value +func (m publicreportPoolImageMods) ImageID(val int32) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(_ context.Context, o *PublicreportPoolImageTemplate) { + o.ImageID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m publicreportPoolImageMods) ImageIDFunc(f func() int32) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(_ context.Context, o *PublicreportPoolImageTemplate) { + o.ImageID = f + }) +} + +// Clear any values for the column +func (m publicreportPoolImageMods) UnsetImageID() PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(_ context.Context, o *PublicreportPoolImageTemplate) { + o.ImageID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportPoolImageMods) RandomImageID(f *faker.Faker) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(_ context.Context, o *PublicreportPoolImageTemplate) { + o.ImageID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m publicreportPoolImageMods) PoolID(val int32) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(_ context.Context, o *PublicreportPoolImageTemplate) { + o.PoolID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m publicreportPoolImageMods) PoolIDFunc(f func() int32) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(_ context.Context, o *PublicreportPoolImageTemplate) { + o.PoolID = f + }) +} + +// Clear any values for the column +func (m publicreportPoolImageMods) UnsetPoolID() PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(_ context.Context, o *PublicreportPoolImageTemplate) { + 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 publicreportPoolImageMods) RandomPoolID(f *faker.Faker) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(_ context.Context, o *PublicreportPoolImageTemplate) { + o.PoolID = func() int32 { + return random_int32(f) + } + }) +} + +func (m publicreportPoolImageMods) WithParentsCascading() PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(ctx context.Context, o *PublicreportPoolImageTemplate) { + if isDone, _ := publicreportPoolImageWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = publicreportPoolImageWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewPublicreportImageWithContext(ctx, PublicreportImageMods.WithParentsCascading()) + m.WithImage(related).Apply(ctx, o) + } + { + + related := o.f.NewPublicreportPoolWithContext(ctx, PublicreportPoolMods.WithParentsCascading()) + m.WithPool(related).Apply(ctx, o) + } + }) +} + +func (m publicreportPoolImageMods) WithImage(rel *PublicreportImageTemplate) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(ctx context.Context, o *PublicreportPoolImageTemplate) { + o.r.Image = &publicreportPoolImageRImageR{ + o: rel, + } + }) +} + +func (m publicreportPoolImageMods) WithNewImage(mods ...PublicreportImageMod) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(ctx context.Context, o *PublicreportPoolImageTemplate) { + related := o.f.NewPublicreportImageWithContext(ctx, mods...) + + m.WithImage(related).Apply(ctx, o) + }) +} + +func (m publicreportPoolImageMods) WithExistingImage(em *models.PublicreportImage) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(ctx context.Context, o *PublicreportPoolImageTemplate) { + o.r.Image = &publicreportPoolImageRImageR{ + o: o.f.FromExistingPublicreportImage(em), + } + }) +} + +func (m publicreportPoolImageMods) WithoutImage() PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(ctx context.Context, o *PublicreportPoolImageTemplate) { + o.r.Image = nil + }) +} + +func (m publicreportPoolImageMods) WithPool(rel *PublicreportPoolTemplate) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(ctx context.Context, o *PublicreportPoolImageTemplate) { + o.r.Pool = &publicreportPoolImageRPoolR{ + o: rel, + } + }) +} + +func (m publicreportPoolImageMods) WithNewPool(mods ...PublicreportPoolMod) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(ctx context.Context, o *PublicreportPoolImageTemplate) { + related := o.f.NewPublicreportPoolWithContext(ctx, mods...) + + m.WithPool(related).Apply(ctx, o) + }) +} + +func (m publicreportPoolImageMods) WithExistingPool(em *models.PublicreportPool) PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(ctx context.Context, o *PublicreportPoolImageTemplate) { + o.r.Pool = &publicreportPoolImageRPoolR{ + o: o.f.FromExistingPublicreportPool(em), + } + }) +} + +func (m publicreportPoolImageMods) WithoutPool() PublicreportPoolImageMod { + return PublicreportPoolImageModFunc(func(ctx context.Context, o *PublicreportPoolImageTemplate) { + o.r.Pool = nil + }) +} diff --git a/db/factory/publicreport.pool_photo.bob.go b/db/factory/publicreport.pool_photo.bob.go deleted file mode 100644 index d41bd538..00000000 --- a/db/factory/publicreport.pool_photo.bob.go +++ /dev/null @@ -1,498 +0,0 @@ -// Code generated by BobGen psql v0.42.1. 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 - }) -} diff --git a/db/factory/publicreport.quick.bob.go b/db/factory/publicreport.quick.bob.go index 95a66d3a..9f372444 100644 --- a/db/factory/publicreport.quick.bob.go +++ b/db/factory/publicreport.quick.bob.go @@ -56,12 +56,12 @@ type PublicreportQuickTemplate struct { } type publicreportQuickR struct { - QuickPhotos []*publicreportQuickRQuickPhotosR + Images []*publicreportQuickRImagesR } -type publicreportQuickRQuickPhotosR struct { +type publicreportQuickRImagesR struct { number int - o *PublicreportQuickPhotoTemplate + o *PublicreportImageTemplate } // Apply mods to the PublicreportQuickTemplate @@ -74,17 +74,16 @@ func (o *PublicreportQuickTemplate) Apply(ctx context.Context, mods ...Publicrep // setModelRels creates and sets the relationships on *models.PublicreportQuick // according to the relationships in the template. Nothing is inserted into the db func (t PublicreportQuickTemplate) setModelRels(o *models.PublicreportQuick) { - if t.r.QuickPhotos != nil { - rel := models.PublicreportQuickPhotoSlice{} - for _, r := range t.r.QuickPhotos { + if t.r.Images != nil { + rel := models.PublicreportImageSlice{} + for _, r := range t.r.Images { related := r.o.BuildMany(r.number) for _, rel := range related { - rel.QuickID = o.ID // h2 - rel.R.Quick = o + rel.R.Quicks = append(rel.R.Quicks, o) } rel = append(rel, related...) } - o.R.QuickPhotos = rel + o.R.Images = rel } } @@ -241,19 +240,19 @@ func ensureCreatablePublicreportQuick(m *models.PublicreportQuickSetter) { func (o *PublicreportQuickTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.PublicreportQuick) error { var err error - isQuickPhotosDone, _ := publicreportQuickRelQuickPhotosCtx.Value(ctx) - if !isQuickPhotosDone && o.r.QuickPhotos != nil { - ctx = publicreportQuickRelQuickPhotosCtx.WithValue(ctx, true) - for _, r := range o.r.QuickPhotos { + isImagesDone, _ := publicreportQuickRelImagesCtx.Value(ctx) + if !isImagesDone && o.r.Images != nil { + ctx = publicreportQuickRelImagesCtx.WithValue(ctx, true) + for _, r := range o.r.Images { if r.o.alreadyPersisted { - m.R.QuickPhotos = append(m.R.QuickPhotos, r.o.Build()) + m.R.Images = append(m.R.Images, r.o.Build()) } else { rel0, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachQuickPhotos(ctx, exec, rel0...) + err = m.AttachImages(ctx, exec, rel0...) if err != nil { return err } @@ -729,50 +728,50 @@ func (m publicreportQuickMods) WithParentsCascading() PublicreportQuickMod { }) } -func (m publicreportQuickMods) WithQuickPhotos(number int, related *PublicreportQuickPhotoTemplate) PublicreportQuickMod { +func (m publicreportQuickMods) WithImages(number int, related *PublicreportImageTemplate) PublicreportQuickMod { return PublicreportQuickModFunc(func(ctx context.Context, o *PublicreportQuickTemplate) { - o.r.QuickPhotos = []*publicreportQuickRQuickPhotosR{{ + o.r.Images = []*publicreportQuickRImagesR{{ number: number, o: related, }} }) } -func (m publicreportQuickMods) WithNewQuickPhotos(number int, mods ...PublicreportQuickPhotoMod) PublicreportQuickMod { +func (m publicreportQuickMods) WithNewImages(number int, mods ...PublicreportImageMod) PublicreportQuickMod { return PublicreportQuickModFunc(func(ctx context.Context, o *PublicreportQuickTemplate) { - related := o.f.NewPublicreportQuickPhotoWithContext(ctx, mods...) - m.WithQuickPhotos(number, related).Apply(ctx, o) + related := o.f.NewPublicreportImageWithContext(ctx, mods...) + m.WithImages(number, related).Apply(ctx, o) }) } -func (m publicreportQuickMods) AddQuickPhotos(number int, related *PublicreportQuickPhotoTemplate) PublicreportQuickMod { +func (m publicreportQuickMods) AddImages(number int, related *PublicreportImageTemplate) PublicreportQuickMod { return PublicreportQuickModFunc(func(ctx context.Context, o *PublicreportQuickTemplate) { - o.r.QuickPhotos = append(o.r.QuickPhotos, &publicreportQuickRQuickPhotosR{ + o.r.Images = append(o.r.Images, &publicreportQuickRImagesR{ number: number, o: related, }) }) } -func (m publicreportQuickMods) AddNewQuickPhotos(number int, mods ...PublicreportQuickPhotoMod) PublicreportQuickMod { +func (m publicreportQuickMods) AddNewImages(number int, mods ...PublicreportImageMod) PublicreportQuickMod { return PublicreportQuickModFunc(func(ctx context.Context, o *PublicreportQuickTemplate) { - related := o.f.NewPublicreportQuickPhotoWithContext(ctx, mods...) - m.AddQuickPhotos(number, related).Apply(ctx, o) + related := o.f.NewPublicreportImageWithContext(ctx, mods...) + m.AddImages(number, related).Apply(ctx, o) }) } -func (m publicreportQuickMods) AddExistingQuickPhotos(existingModels ...*models.PublicreportQuickPhoto) PublicreportQuickMod { +func (m publicreportQuickMods) AddExistingImages(existingModels ...*models.PublicreportImage) PublicreportQuickMod { return PublicreportQuickModFunc(func(ctx context.Context, o *PublicreportQuickTemplate) { for _, em := range existingModels { - o.r.QuickPhotos = append(o.r.QuickPhotos, &publicreportQuickRQuickPhotosR{ - o: o.f.FromExistingPublicreportQuickPhoto(em), + o.r.Images = append(o.r.Images, &publicreportQuickRImagesR{ + o: o.f.FromExistingPublicreportImage(em), }) } }) } -func (m publicreportQuickMods) WithoutQuickPhotos() PublicreportQuickMod { +func (m publicreportQuickMods) WithoutImages() PublicreportQuickMod { return PublicreportQuickModFunc(func(ctx context.Context, o *PublicreportQuickTemplate) { - o.r.QuickPhotos = nil + o.r.Images = nil }) } diff --git a/db/factory/publicreport.quick_image.bob.go b/db/factory/publicreport.quick_image.bob.go new file mode 100644 index 00000000..951dc333 --- /dev/null +++ b/db/factory/publicreport.quick_image.bob.go @@ -0,0 +1,431 @@ +// Code generated by BobGen psql v0.42.1. 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/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type PublicreportQuickImageMod interface { + Apply(context.Context, *PublicreportQuickImageTemplate) +} + +type PublicreportQuickImageModFunc func(context.Context, *PublicreportQuickImageTemplate) + +func (f PublicreportQuickImageModFunc) Apply(ctx context.Context, n *PublicreportQuickImageTemplate) { + f(ctx, n) +} + +type PublicreportQuickImageModSlice []PublicreportQuickImageMod + +func (mods PublicreportQuickImageModSlice) Apply(ctx context.Context, n *PublicreportQuickImageTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// PublicreportQuickImageTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type PublicreportQuickImageTemplate struct { + ImageID func() int32 + QuickID func() int32 + + r publicreportQuickImageR + f *Factory + + alreadyPersisted bool +} + +type publicreportQuickImageR struct { + Image *publicreportQuickImageRImageR + Quick *publicreportQuickImageRQuickR +} + +type publicreportQuickImageRImageR struct { + o *PublicreportImageTemplate +} +type publicreportQuickImageRQuickR struct { + o *PublicreportQuickTemplate +} + +// Apply mods to the PublicreportQuickImageTemplate +func (o *PublicreportQuickImageTemplate) Apply(ctx context.Context, mods ...PublicreportQuickImageMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.PublicreportQuickImage +// according to the relationships in the template. Nothing is inserted into the db +func (t PublicreportQuickImageTemplate) setModelRels(o *models.PublicreportQuickImage) { + if t.r.Image != nil { + rel := t.r.Image.o.Build() + o.ImageID = rel.ID // h2 + o.R.Image = rel + } + + if t.r.Quick != nil { + rel := t.r.Quick.o.Build() + o.QuickID = rel.ID // h2 + o.R.Quick = rel + } +} + +// BuildSetter returns an *models.PublicreportQuickImageSetter +// this does nothing with the relationship templates +func (o PublicreportQuickImageTemplate) BuildSetter() *models.PublicreportQuickImageSetter { + m := &models.PublicreportQuickImageSetter{} + + if o.ImageID != nil { + val := o.ImageID() + m.ImageID = omit.From(val) + } + if o.QuickID != nil { + val := o.QuickID() + m.QuickID = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.PublicreportQuickImageSetter +// this does nothing with the relationship templates +func (o PublicreportQuickImageTemplate) BuildManySetter(number int) []*models.PublicreportQuickImageSetter { + m := make([]*models.PublicreportQuickImageSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.PublicreportQuickImage +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use PublicreportQuickImageTemplate.Create +func (o PublicreportQuickImageTemplate) Build() *models.PublicreportQuickImage { + m := &models.PublicreportQuickImage{} + + if o.ImageID != nil { + m.ImageID = o.ImageID() + } + if o.QuickID != nil { + m.QuickID = o.QuickID() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.PublicreportQuickImageSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use PublicreportQuickImageTemplate.CreateMany +func (o PublicreportQuickImageTemplate) BuildMany(number int) models.PublicreportQuickImageSlice { + m := make(models.PublicreportQuickImageSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatablePublicreportQuickImage(m *models.PublicreportQuickImageSetter) { + if !(m.ImageID.IsValue()) { + val := random_int32(nil) + m.ImageID = omit.From(val) + } + if !(m.QuickID.IsValue()) { + val := random_int32(nil) + m.QuickID = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.PublicreportQuickImage +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *PublicreportQuickImageTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.PublicreportQuickImage) error { + var err error + + return err +} + +// Create builds a publicreportQuickImage and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *PublicreportQuickImageTemplate) Create(ctx context.Context, exec bob.Executor) (*models.PublicreportQuickImage, error) { + var err error + opt := o.BuildSetter() + ensureCreatablePublicreportQuickImage(opt) + + if o.r.Image == nil { + PublicreportQuickImageMods.WithNewImage().Apply(ctx, o) + } + + var rel0 *models.PublicreportImage + + if o.r.Image.o.alreadyPersisted { + rel0 = o.r.Image.o.Build() + } else { + rel0, err = o.r.Image.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.ImageID = omit.From(rel0.ID) + + if o.r.Quick == nil { + PublicreportQuickImageMods.WithNewQuick().Apply(ctx, o) + } + + var rel1 *models.PublicreportQuick + + if o.r.Quick.o.alreadyPersisted { + rel1 = o.r.Quick.o.Build() + } else { + rel1, err = o.r.Quick.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.QuickID = omit.From(rel1.ID) + + m, err := models.PublicreportQuickImages.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + m.R.Image = rel0 + m.R.Quick = rel1 + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a publicreportQuickImage and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *PublicreportQuickImageTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.PublicreportQuickImage { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a publicreportQuickImage 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 *PublicreportQuickImageTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.PublicreportQuickImage { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple publicreportQuickImages and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o PublicreportQuickImageTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.PublicreportQuickImageSlice, error) { + var err error + m := make(models.PublicreportQuickImageSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple publicreportQuickImages and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o PublicreportQuickImageTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.PublicreportQuickImageSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple publicreportQuickImages 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 PublicreportQuickImageTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.PublicreportQuickImageSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// PublicreportQuickImage has methods that act as mods for the PublicreportQuickImageTemplate +var PublicreportQuickImageMods publicreportQuickImageMods + +type publicreportQuickImageMods struct{} + +func (m publicreportQuickImageMods) RandomizeAllColumns(f *faker.Faker) PublicreportQuickImageMod { + return PublicreportQuickImageModSlice{ + PublicreportQuickImageMods.RandomImageID(f), + PublicreportQuickImageMods.RandomQuickID(f), + } +} + +// Set the model columns to this value +func (m publicreportQuickImageMods) ImageID(val int32) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(_ context.Context, o *PublicreportQuickImageTemplate) { + o.ImageID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m publicreportQuickImageMods) ImageIDFunc(f func() int32) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(_ context.Context, o *PublicreportQuickImageTemplate) { + o.ImageID = f + }) +} + +// Clear any values for the column +func (m publicreportQuickImageMods) UnsetImageID() PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(_ context.Context, o *PublicreportQuickImageTemplate) { + o.ImageID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportQuickImageMods) RandomImageID(f *faker.Faker) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(_ context.Context, o *PublicreportQuickImageTemplate) { + o.ImageID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m publicreportQuickImageMods) QuickID(val int32) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(_ context.Context, o *PublicreportQuickImageTemplate) { + o.QuickID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m publicreportQuickImageMods) QuickIDFunc(f func() int32) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(_ context.Context, o *PublicreportQuickImageTemplate) { + o.QuickID = f + }) +} + +// Clear any values for the column +func (m publicreportQuickImageMods) UnsetQuickID() PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(_ context.Context, o *PublicreportQuickImageTemplate) { + o.QuickID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m publicreportQuickImageMods) RandomQuickID(f *faker.Faker) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(_ context.Context, o *PublicreportQuickImageTemplate) { + o.QuickID = func() int32 { + return random_int32(f) + } + }) +} + +func (m publicreportQuickImageMods) WithParentsCascading() PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(ctx context.Context, o *PublicreportQuickImageTemplate) { + if isDone, _ := publicreportQuickImageWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = publicreportQuickImageWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewPublicreportImageWithContext(ctx, PublicreportImageMods.WithParentsCascading()) + m.WithImage(related).Apply(ctx, o) + } + { + + related := o.f.NewPublicreportQuickWithContext(ctx, PublicreportQuickMods.WithParentsCascading()) + m.WithQuick(related).Apply(ctx, o) + } + }) +} + +func (m publicreportQuickImageMods) WithImage(rel *PublicreportImageTemplate) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(ctx context.Context, o *PublicreportQuickImageTemplate) { + o.r.Image = &publicreportQuickImageRImageR{ + o: rel, + } + }) +} + +func (m publicreportQuickImageMods) WithNewImage(mods ...PublicreportImageMod) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(ctx context.Context, o *PublicreportQuickImageTemplate) { + related := o.f.NewPublicreportImageWithContext(ctx, mods...) + + m.WithImage(related).Apply(ctx, o) + }) +} + +func (m publicreportQuickImageMods) WithExistingImage(em *models.PublicreportImage) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(ctx context.Context, o *PublicreportQuickImageTemplate) { + o.r.Image = &publicreportQuickImageRImageR{ + o: o.f.FromExistingPublicreportImage(em), + } + }) +} + +func (m publicreportQuickImageMods) WithoutImage() PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(ctx context.Context, o *PublicreportQuickImageTemplate) { + o.r.Image = nil + }) +} + +func (m publicreportQuickImageMods) WithQuick(rel *PublicreportQuickTemplate) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(ctx context.Context, o *PublicreportQuickImageTemplate) { + o.r.Quick = &publicreportQuickImageRQuickR{ + o: rel, + } + }) +} + +func (m publicreportQuickImageMods) WithNewQuick(mods ...PublicreportQuickMod) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(ctx context.Context, o *PublicreportQuickImageTemplate) { + related := o.f.NewPublicreportQuickWithContext(ctx, mods...) + + m.WithQuick(related).Apply(ctx, o) + }) +} + +func (m publicreportQuickImageMods) WithExistingQuick(em *models.PublicreportQuick) PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(ctx context.Context, o *PublicreportQuickImageTemplate) { + o.r.Quick = &publicreportQuickImageRQuickR{ + o: o.f.FromExistingPublicreportQuick(em), + } + }) +} + +func (m publicreportQuickImageMods) WithoutQuick() PublicreportQuickImageMod { + return PublicreportQuickImageModFunc(func(ctx context.Context, o *PublicreportQuickImageTemplate) { + o.r.Quick = nil + }) +} diff --git a/db/factory/publicreport.quick_photo.bob.go b/db/factory/publicreport.quick_photo.bob.go deleted file mode 100644 index 4bd2bfb9..00000000 --- a/db/factory/publicreport.quick_photo.bob.go +++ /dev/null @@ -1,498 +0,0 @@ -// Code generated by BobGen psql v0.42.1. 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 PublicreportQuickPhotoMod interface { - Apply(context.Context, *PublicreportQuickPhotoTemplate) -} - -type PublicreportQuickPhotoModFunc func(context.Context, *PublicreportQuickPhotoTemplate) - -func (f PublicreportQuickPhotoModFunc) Apply(ctx context.Context, n *PublicreportQuickPhotoTemplate) { - f(ctx, n) -} - -type PublicreportQuickPhotoModSlice []PublicreportQuickPhotoMod - -func (mods PublicreportQuickPhotoModSlice) Apply(ctx context.Context, n *PublicreportQuickPhotoTemplate) { - for _, f := range mods { - f.Apply(ctx, n) - } -} - -// PublicreportQuickPhotoTemplate is an object representing the database table. -// all columns are optional and should be set by mods -type PublicreportQuickPhotoTemplate struct { - ID func() int32 - Size func() int64 - Filename func() string - QuickID func() int32 - UUID func() uuid.UUID - - r publicreportQuickPhotoR - f *Factory - - alreadyPersisted bool -} - -type publicreportQuickPhotoR struct { - Quick *publicreportQuickPhotoRQuickR -} - -type publicreportQuickPhotoRQuickR struct { - o *PublicreportQuickTemplate -} - -// Apply mods to the PublicreportQuickPhotoTemplate -func (o *PublicreportQuickPhotoTemplate) Apply(ctx context.Context, mods ...PublicreportQuickPhotoMod) { - for _, mod := range mods { - mod.Apply(ctx, o) - } -} - -// setModelRels creates and sets the relationships on *models.PublicreportQuickPhoto -// according to the relationships in the template. Nothing is inserted into the db -func (t PublicreportQuickPhotoTemplate) setModelRels(o *models.PublicreportQuickPhoto) { - if t.r.Quick != nil { - rel := t.r.Quick.o.Build() - rel.R.QuickPhotos = append(rel.R.QuickPhotos, o) - o.QuickID = rel.ID // h2 - o.R.Quick = rel - } -} - -// BuildSetter returns an *models.PublicreportQuickPhotoSetter -// this does nothing with the relationship templates -func (o PublicreportQuickPhotoTemplate) BuildSetter() *models.PublicreportQuickPhotoSetter { - m := &models.PublicreportQuickPhotoSetter{} - - 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.QuickID != nil { - val := o.QuickID() - m.QuickID = omit.From(val) - } - if o.UUID != nil { - val := o.UUID() - m.UUID = omit.From(val) - } - - return m -} - -// BuildManySetter returns an []*models.PublicreportQuickPhotoSetter -// this does nothing with the relationship templates -func (o PublicreportQuickPhotoTemplate) BuildManySetter(number int) []*models.PublicreportQuickPhotoSetter { - m := make([]*models.PublicreportQuickPhotoSetter, number) - - for i := range m { - m[i] = o.BuildSetter() - } - - return m -} - -// Build returns an *models.PublicreportQuickPhoto -// Related objects are also created and placed in the .R field -// NOTE: Objects are not inserted into the database. Use PublicreportQuickPhotoTemplate.Create -func (o PublicreportQuickPhotoTemplate) Build() *models.PublicreportQuickPhoto { - m := &models.PublicreportQuickPhoto{} - - 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.QuickID != nil { - m.QuickID = o.QuickID() - } - if o.UUID != nil { - m.UUID = o.UUID() - } - - o.setModelRels(m) - - return m -} - -// BuildMany returns an models.PublicreportQuickPhotoSlice -// Related objects are also created and placed in the .R field -// NOTE: Objects are not inserted into the database. Use PublicreportQuickPhotoTemplate.CreateMany -func (o PublicreportQuickPhotoTemplate) BuildMany(number int) models.PublicreportQuickPhotoSlice { - m := make(models.PublicreportQuickPhotoSlice, number) - - for i := range m { - m[i] = o.Build() - } - - return m -} - -func ensureCreatablePublicreportQuickPhoto(m *models.PublicreportQuickPhotoSetter) { - 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.QuickID.IsValue()) { - val := random_int32(nil) - m.QuickID = 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.PublicreportQuickPhoto -// according to the relationships in the template. -// any required relationship should have already exist on the model -func (o *PublicreportQuickPhotoTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.PublicreportQuickPhoto) error { - var err error - - return err -} - -// Create builds a publicreportQuickPhoto and inserts it into the database -// Relations objects are also inserted and placed in the .R field -func (o *PublicreportQuickPhotoTemplate) Create(ctx context.Context, exec bob.Executor) (*models.PublicreportQuickPhoto, error) { - var err error - opt := o.BuildSetter() - ensureCreatablePublicreportQuickPhoto(opt) - - if o.r.Quick == nil { - PublicreportQuickPhotoMods.WithNewQuick().Apply(ctx, o) - } - - var rel0 *models.PublicreportQuick - - if o.r.Quick.o.alreadyPersisted { - rel0 = o.r.Quick.o.Build() - } else { - rel0, err = o.r.Quick.o.Create(ctx, exec) - if err != nil { - return nil, err - } - } - - opt.QuickID = omit.From(rel0.ID) - - m, err := models.PublicreportQuickPhotos.Insert(opt).One(ctx, exec) - if err != nil { - return nil, err - } - - m.R.Quick = rel0 - - if err := o.insertOptRels(ctx, exec, m); err != nil { - return nil, err - } - return m, err -} - -// MustCreate builds a publicreportQuickPhoto and inserts it into the database -// Relations objects are also inserted and placed in the .R field -// panics if an error occurs -func (o *PublicreportQuickPhotoTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.PublicreportQuickPhoto { - m, err := o.Create(ctx, exec) - if err != nil { - panic(err) - } - return m -} - -// CreateOrFail builds a publicreportQuickPhoto 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 *PublicreportQuickPhotoTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.PublicreportQuickPhoto { - tb.Helper() - m, err := o.Create(ctx, exec) - if err != nil { - tb.Fatal(err) - return nil - } - return m -} - -// CreateMany builds multiple publicreportQuickPhotos and inserts them into the database -// Relations objects are also inserted and placed in the .R field -func (o PublicreportQuickPhotoTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.PublicreportQuickPhotoSlice, error) { - var err error - m := make(models.PublicreportQuickPhotoSlice, number) - - for i := range m { - m[i], err = o.Create(ctx, exec) - if err != nil { - return nil, err - } - } - - return m, nil -} - -// MustCreateMany builds multiple publicreportQuickPhotos and inserts them into the database -// Relations objects are also inserted and placed in the .R field -// panics if an error occurs -func (o PublicreportQuickPhotoTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.PublicreportQuickPhotoSlice { - m, err := o.CreateMany(ctx, exec, number) - if err != nil { - panic(err) - } - return m -} - -// CreateManyOrFail builds multiple publicreportQuickPhotos 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 PublicreportQuickPhotoTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.PublicreportQuickPhotoSlice { - tb.Helper() - m, err := o.CreateMany(ctx, exec, number) - if err != nil { - tb.Fatal(err) - return nil - } - return m -} - -// PublicreportQuickPhoto has methods that act as mods for the PublicreportQuickPhotoTemplate -var PublicreportQuickPhotoMods publicreportQuickPhotoMods - -type publicreportQuickPhotoMods struct{} - -func (m publicreportQuickPhotoMods) RandomizeAllColumns(f *faker.Faker) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModSlice{ - PublicreportQuickPhotoMods.RandomID(f), - PublicreportQuickPhotoMods.RandomSize(f), - PublicreportQuickPhotoMods.RandomFilename(f), - PublicreportQuickPhotoMods.RandomQuickID(f), - PublicreportQuickPhotoMods.RandomUUID(f), - } -} - -// Set the model columns to this value -func (m publicreportQuickPhotoMods) ID(val int32) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.ID = func() int32 { return val } - }) -} - -// Set the Column from the function -func (m publicreportQuickPhotoMods) IDFunc(f func() int32) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.ID = f - }) -} - -// Clear any values for the column -func (m publicreportQuickPhotoMods) UnsetID() PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - 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 publicreportQuickPhotoMods) RandomID(f *faker.Faker) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.ID = func() int32 { - return random_int32(f) - } - }) -} - -// Set the model columns to this value -func (m publicreportQuickPhotoMods) Size(val int64) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.Size = func() int64 { return val } - }) -} - -// Set the Column from the function -func (m publicreportQuickPhotoMods) SizeFunc(f func() int64) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.Size = f - }) -} - -// Clear any values for the column -func (m publicreportQuickPhotoMods) UnsetSize() PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - 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 publicreportQuickPhotoMods) RandomSize(f *faker.Faker) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.Size = func() int64 { - return random_int64(f) - } - }) -} - -// Set the model columns to this value -func (m publicreportQuickPhotoMods) Filename(val string) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.Filename = func() string { return val } - }) -} - -// Set the Column from the function -func (m publicreportQuickPhotoMods) FilenameFunc(f func() string) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.Filename = f - }) -} - -// Clear any values for the column -func (m publicreportQuickPhotoMods) UnsetFilename() PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - 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 publicreportQuickPhotoMods) RandomFilename(f *faker.Faker) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.Filename = func() string { - return random_string(f) - } - }) -} - -// Set the model columns to this value -func (m publicreportQuickPhotoMods) QuickID(val int32) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.QuickID = func() int32 { return val } - }) -} - -// Set the Column from the function -func (m publicreportQuickPhotoMods) QuickIDFunc(f func() int32) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.QuickID = f - }) -} - -// Clear any values for the column -func (m publicreportQuickPhotoMods) UnsetQuickID() PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.QuickID = nil - }) -} - -// Generates a random value for the column using the given faker -// if faker is nil, a default faker is used -func (m publicreportQuickPhotoMods) RandomQuickID(f *faker.Faker) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.QuickID = func() int32 { - return random_int32(f) - } - }) -} - -// Set the model columns to this value -func (m publicreportQuickPhotoMods) UUID(val uuid.UUID) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.UUID = func() uuid.UUID { return val } - }) -} - -// Set the Column from the function -func (m publicreportQuickPhotoMods) UUIDFunc(f func() uuid.UUID) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.UUID = f - }) -} - -// Clear any values for the column -func (m publicreportQuickPhotoMods) UnsetUUID() PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - 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 publicreportQuickPhotoMods) RandomUUID(f *faker.Faker) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(_ context.Context, o *PublicreportQuickPhotoTemplate) { - o.UUID = func() uuid.UUID { - return random_uuid_UUID(f) - } - }) -} - -func (m publicreportQuickPhotoMods) WithParentsCascading() PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(ctx context.Context, o *PublicreportQuickPhotoTemplate) { - if isDone, _ := publicreportQuickPhotoWithParentsCascadingCtx.Value(ctx); isDone { - return - } - ctx = publicreportQuickPhotoWithParentsCascadingCtx.WithValue(ctx, true) - { - - related := o.f.NewPublicreportQuickWithContext(ctx, PublicreportQuickMods.WithParentsCascading()) - m.WithQuick(related).Apply(ctx, o) - } - }) -} - -func (m publicreportQuickPhotoMods) WithQuick(rel *PublicreportQuickTemplate) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(ctx context.Context, o *PublicreportQuickPhotoTemplate) { - o.r.Quick = &publicreportQuickPhotoRQuickR{ - o: rel, - } - }) -} - -func (m publicreportQuickPhotoMods) WithNewQuick(mods ...PublicreportQuickMod) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(ctx context.Context, o *PublicreportQuickPhotoTemplate) { - related := o.f.NewPublicreportQuickWithContext(ctx, mods...) - - m.WithQuick(related).Apply(ctx, o) - }) -} - -func (m publicreportQuickPhotoMods) WithExistingQuick(em *models.PublicreportQuick) PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(ctx context.Context, o *PublicreportQuickPhotoTemplate) { - o.r.Quick = &publicreportQuickPhotoRQuickR{ - o: o.f.FromExistingPublicreportQuick(em), - } - }) -} - -func (m publicreportQuickPhotoMods) WithoutQuick() PublicreportQuickPhotoMod { - return PublicreportQuickPhotoModFunc(func(ctx context.Context, o *PublicreportQuickPhotoTemplate) { - o.r.Quick = nil - }) -} diff --git a/db/migrations/00035_photo_content_type.sql b/db/migrations/00035_photo_content_type.sql new file mode 100644 index 00000000..cafad2e5 --- /dev/null +++ b/db/migrations/00035_photo_content_type.sql @@ -0,0 +1,37 @@ +-- +goose Up +CREATE TABLE publicreport.image ( + id SERIAL, + content_type TEXT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + location GEOGRAPHY, + resolution_x INTEGER NOT NULL, + resolution_y INTEGER NOT NULL, + storage_uuid UUID NOT NULL, + storage_size BIGINT NOT NULL, + uploaded_filename TEXT NOT NULL, + PRIMARY KEY(id) +); +CREATE TABLE publicreport.image_exif ( + image_id INTEGER NOT NULL REFERENCES publicreport.image(id), + name TEXT NOT NULL, + value TEXT NOT NULL, + PRIMARY KEY(image_id, name, value) +); +CREATE TABLE publicreport.pool_image ( + image_id INTEGER NOT NULL REFERENCES publicreport.image(id), + pool_id INTEGER NOT NULL REFERENCES publicreport.pool(id), + PRIMARY KEY (image_id, pool_id) +); +CREATE TABLE publicreport.quick_image ( + image_id INTEGER NOT NULL REFERENCES publicreport.image(id), + quick_id INTEGER NOT NULL REFERENCES publicreport.quick(id), + PRIMARY KEY (image_id, quick_id) +); +DROP TABLE IF EXISTS publicreport.pool_photo; +DROP TABLE IF EXISTS publicreport.quick_photo; +-- +goose Down +DROP TABLE publicreport.quick_image; +DROP TABLE publicreport.pool_image; +DROP TABLE publicreport.image_exif; +DROP TABLE publicreport.image; +-- that's right, I'm not rebuilding the pool_photo or quick_photo tables because I'm lazy. diff --git a/db/models/bob_counts.bob.go b/db/models/bob_counts.bob.go index be17543e..de00d405 100644 --- a/db/models/bob_counts.bob.go +++ b/db/models/bob_counts.bob.go @@ -25,6 +25,7 @@ type preloadCounts struct { NoteAudio noteAudioCountPreloader NoteImage noteImageCountPreloader Organization organizationCountPreloader + PublicreportImage publicreportImageCountPreloader PublicreportPool publicreportPoolCountPreloader PublicreportQuick publicreportQuickCountPreloader User userCountPreloader @@ -36,6 +37,7 @@ func getPreloadCount() preloadCounts { NoteAudio: buildNoteAudioCountPreloader(), NoteImage: buildNoteImageCountPreloader(), Organization: buildOrganizationCountPreloader(), + PublicreportImage: buildPublicreportImageCountPreloader(), PublicreportPool: buildPublicreportPoolCountPreloader(), PublicreportQuick: buildPublicreportQuickCountPreloader(), User: buildUserCountPreloader(), @@ -47,6 +49,7 @@ type thenLoadCounts[Q orm.Loadable] struct { NoteAudio noteAudioCountThenLoader[Q] NoteImage noteImageCountThenLoader[Q] Organization organizationCountThenLoader[Q] + PublicreportImage publicreportImageCountThenLoader[Q] PublicreportPool publicreportPoolCountThenLoader[Q] PublicreportQuick publicreportQuickCountThenLoader[Q] User userCountThenLoader[Q] @@ -58,6 +61,7 @@ func getThenLoadCount[Q orm.Loadable]() thenLoadCounts[Q] { NoteAudio: buildNoteAudioCountThenLoader[Q](), NoteImage: buildNoteImageCountThenLoader[Q](), Organization: buildOrganizationCountThenLoader[Q](), + PublicreportImage: buildPublicreportImageCountThenLoader[Q](), PublicreportPool: buildPublicreportPoolCountThenLoader[Q](), PublicreportQuick: buildPublicreportQuickCountThenLoader[Q](), User: buildUserCountThenLoader[Q](), diff --git a/db/models/bob_joins.bob.go b/db/models/bob_joins.bob.go index ef21e961..f387e8d3 100644 --- a/db/models/bob_joins.bob.go +++ b/db/models/bob_joins.bob.go @@ -73,10 +73,12 @@ type joins[Q dialect.Joinable] struct { Notifications joinSet[notificationJoins[Q]] OauthTokens joinSet[oauthTokenJoins[Q]] Organizations joinSet[organizationJoins[Q]] + PublicreportImages joinSet[publicreportImageJoins[Q]] + PublicreportImageExifs joinSet[publicreportImageExifJoins[Q]] PublicreportPools joinSet[publicreportPoolJoins[Q]] - PublicreportPoolPhotos joinSet[publicreportPoolPhotoJoins[Q]] + PublicreportPoolImages joinSet[publicreportPoolImageJoins[Q]] PublicreportQuicks joinSet[publicreportQuickJoins[Q]] - PublicreportQuickPhotos joinSet[publicreportQuickPhotoJoins[Q]] + PublicreportQuickImages joinSet[publicreportQuickImageJoins[Q]] Users joinSet[userJoins[Q]] } @@ -131,10 +133,12 @@ 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), + PublicreportImages: buildJoinSet[publicreportImageJoins[Q]](PublicreportImages.Columns, buildPublicreportImageJoins), + PublicreportImageExifs: buildJoinSet[publicreportImageExifJoins[Q]](PublicreportImageExifs.Columns, buildPublicreportImageExifJoins), PublicreportPools: buildJoinSet[publicreportPoolJoins[Q]](PublicreportPools.Columns, buildPublicreportPoolJoins), - PublicreportPoolPhotos: buildJoinSet[publicreportPoolPhotoJoins[Q]](PublicreportPoolPhotos.Columns, buildPublicreportPoolPhotoJoins), + PublicreportPoolImages: buildJoinSet[publicreportPoolImageJoins[Q]](PublicreportPoolImages.Columns, buildPublicreportPoolImageJoins), PublicreportQuicks: buildJoinSet[publicreportQuickJoins[Q]](PublicreportQuicks.Columns, buildPublicreportQuickJoins), - PublicreportQuickPhotos: buildJoinSet[publicreportQuickPhotoJoins[Q]](PublicreportQuickPhotos.Columns, buildPublicreportQuickPhotoJoins), + PublicreportQuickImages: buildJoinSet[publicreportQuickImageJoins[Q]](PublicreportQuickImages.Columns, buildPublicreportQuickImageJoins), Users: buildJoinSet[userJoins[Q]](Users.Columns, buildUserJoins), } } diff --git a/db/models/bob_loaders.bob.go b/db/models/bob_loaders.bob.go index 50d02a99..fe6d28c7 100644 --- a/db/models/bob_loaders.bob.go +++ b/db/models/bob_loaders.bob.go @@ -58,10 +58,12 @@ type preloaders struct { Notification notificationPreloader OauthToken oauthTokenPreloader Organization organizationPreloader + PublicreportImage publicreportImagePreloader + PublicreportImageExif publicreportImageExifPreloader PublicreportPool publicreportPoolPreloader - PublicreportPoolPhoto publicreportPoolPhotoPreloader + PublicreportPoolImage publicreportPoolImagePreloader PublicreportQuick publicreportQuickPreloader - PublicreportQuickPhoto publicreportQuickPhotoPreloader + PublicreportQuickImage publicreportQuickImagePreloader User userPreloader } @@ -108,10 +110,12 @@ func getPreloaders() preloaders { Notification: buildNotificationPreloader(), OauthToken: buildOauthTokenPreloader(), Organization: buildOrganizationPreloader(), + PublicreportImage: buildPublicreportImagePreloader(), + PublicreportImageExif: buildPublicreportImageExifPreloader(), PublicreportPool: buildPublicreportPoolPreloader(), - PublicreportPoolPhoto: buildPublicreportPoolPhotoPreloader(), + PublicreportPoolImage: buildPublicreportPoolImagePreloader(), PublicreportQuick: buildPublicreportQuickPreloader(), - PublicreportQuickPhoto: buildPublicreportQuickPhotoPreloader(), + PublicreportQuickImage: buildPublicreportQuickImagePreloader(), User: buildUserPreloader(), } } @@ -164,10 +168,12 @@ type thenLoaders[Q orm.Loadable] struct { Notification notificationThenLoader[Q] OauthToken oauthTokenThenLoader[Q] Organization organizationThenLoader[Q] + PublicreportImage publicreportImageThenLoader[Q] + PublicreportImageExif publicreportImageExifThenLoader[Q] PublicreportPool publicreportPoolThenLoader[Q] - PublicreportPoolPhoto publicreportPoolPhotoThenLoader[Q] + PublicreportPoolImage publicreportPoolImageThenLoader[Q] PublicreportQuick publicreportQuickThenLoader[Q] - PublicreportQuickPhoto publicreportQuickPhotoThenLoader[Q] + PublicreportQuickImage publicreportQuickImageThenLoader[Q] User userThenLoader[Q] } @@ -214,10 +220,12 @@ func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] { Notification: buildNotificationThenLoader[Q](), OauthToken: buildOauthTokenThenLoader[Q](), Organization: buildOrganizationThenLoader[Q](), + PublicreportImage: buildPublicreportImageThenLoader[Q](), + PublicreportImageExif: buildPublicreportImageExifThenLoader[Q](), PublicreportPool: buildPublicreportPoolThenLoader[Q](), - PublicreportPoolPhoto: buildPublicreportPoolPhotoThenLoader[Q](), + PublicreportPoolImage: buildPublicreportPoolImageThenLoader[Q](), PublicreportQuick: buildPublicreportQuickThenLoader[Q](), - PublicreportQuickPhoto: buildPublicreportQuickPhotoThenLoader[Q](), + PublicreportQuickImage: buildPublicreportQuickImageThenLoader[Q](), User: buildUserThenLoader[Q](), } } diff --git a/db/models/bob_where.bob.go b/db/models/bob_where.bob.go index 046968ad..4b2c8b5a 100644 --- a/db/models/bob_where.bob.go +++ b/db/models/bob_where.bob.go @@ -61,11 +61,13 @@ func Where[Q psql.Filterable]() struct { Notifications notificationWhere[Q] OauthTokens oauthTokenWhere[Q] Organizations organizationWhere[Q] + PublicreportImages publicreportImageWhere[Q] + PublicreportImageExifs publicreportImageExifWhere[Q] PublicreportNuisances publicreportNuisanceWhere[Q] PublicreportPools publicreportPoolWhere[Q] - PublicreportPoolPhotos publicreportPoolPhotoWhere[Q] + PublicreportPoolImages publicreportPoolImageWhere[Q] PublicreportQuicks publicreportQuickWhere[Q] - PublicreportQuickPhotos publicreportQuickPhotoWhere[Q] + PublicreportQuickImages publicreportQuickImageWhere[Q] PublicreportReportLocations publicreportReportLocationWhere[Q] RasterColumns rasterColumnWhere[Q] RasterOverviews rasterOverviewWhere[Q] @@ -118,11 +120,13 @@ func Where[Q psql.Filterable]() struct { Notifications notificationWhere[Q] OauthTokens oauthTokenWhere[Q] Organizations organizationWhere[Q] + PublicreportImages publicreportImageWhere[Q] + PublicreportImageExifs publicreportImageExifWhere[Q] PublicreportNuisances publicreportNuisanceWhere[Q] PublicreportPools publicreportPoolWhere[Q] - PublicreportPoolPhotos publicreportPoolPhotoWhere[Q] + PublicreportPoolImages publicreportPoolImageWhere[Q] PublicreportQuicks publicreportQuickWhere[Q] - PublicreportQuickPhotos publicreportQuickPhotoWhere[Q] + PublicreportQuickImages publicreportQuickImageWhere[Q] PublicreportReportLocations publicreportReportLocationWhere[Q] RasterColumns rasterColumnWhere[Q] RasterOverviews rasterOverviewWhere[Q] @@ -174,11 +178,13 @@ func Where[Q psql.Filterable]() struct { Notifications: buildNotificationWhere[Q](Notifications.Columns), OauthTokens: buildOauthTokenWhere[Q](OauthTokens.Columns), Organizations: buildOrganizationWhere[Q](Organizations.Columns), + PublicreportImages: buildPublicreportImageWhere[Q](PublicreportImages.Columns), + PublicreportImageExifs: buildPublicreportImageExifWhere[Q](PublicreportImageExifs.Columns), PublicreportNuisances: buildPublicreportNuisanceWhere[Q](PublicreportNuisances.Columns), PublicreportPools: buildPublicreportPoolWhere[Q](PublicreportPools.Columns), - PublicreportPoolPhotos: buildPublicreportPoolPhotoWhere[Q](PublicreportPoolPhotos.Columns), + PublicreportPoolImages: buildPublicreportPoolImageWhere[Q](PublicreportPoolImages.Columns), PublicreportQuicks: buildPublicreportQuickWhere[Q](PublicreportQuicks.Columns), - PublicreportQuickPhotos: buildPublicreportQuickPhotoWhere[Q](PublicreportQuickPhotos.Columns), + PublicreportQuickImages: buildPublicreportQuickImageWhere[Q](PublicreportQuickImages.Columns), PublicreportReportLocations: buildPublicreportReportLocationWhere[Q](PublicreportReportLocations.Columns), RasterColumns: buildRasterColumnWhere[Q](RasterColumns.Columns), RasterOverviews: buildRasterOverviewWhere[Q](RasterOverviews.Columns), diff --git a/db/models/publicreport.image.bob.go b/db/models/publicreport.image.bob.go new file mode 100644 index 00000000..01aae3bd --- /dev/null +++ b/db/models/publicreport.image.bob.go @@ -0,0 +1,1440 @@ +// Code generated by BobGen psql v0.42.1. 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" + "strconv" + "time" + + "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" + "github.com/stephenafamo/scan" +) + +// PublicreportImage is an object representing the database table. +type PublicreportImage struct { + ID int32 `db:"id,pk" ` + ContentType string `db:"content_type" ` + Created time.Time `db:"created" ` + ResolutionX int32 `db:"resolution_x" ` + ResolutionY int32 `db:"resolution_y" ` + StorageUUID uuid.UUID `db:"storage_uuid" ` + StorageSize int64 `db:"storage_size" ` + UploadedFilename string `db:"uploaded_filename" ` + + R publicreportImageR `db:"-" ` + + C publicreportImageC `db:"-" ` +} + +// PublicreportImageSlice is an alias for a slice of pointers to PublicreportImage. +// This should almost always be used instead of []*PublicreportImage. +type PublicreportImageSlice []*PublicreportImage + +// PublicreportImages contains methods to work with the image table +var PublicreportImages = psql.NewTablex[*PublicreportImage, PublicreportImageSlice, *PublicreportImageSetter]("publicreport", "image", buildPublicreportImageColumns("publicreport.image")) + +// PublicreportImagesQuery is a query on the image table +type PublicreportImagesQuery = *psql.ViewQuery[*PublicreportImage, PublicreportImageSlice] + +// publicreportImageR is where relationships are stored. +type publicreportImageR struct { + ImageExifs PublicreportImageExifSlice // publicreport.image_exif.image_exif_image_id_fkey + Pools PublicreportPoolSlice // publicreport.pool_image.pool_image_image_id_fkeypublicreport.pool_image.pool_image_pool_id_fkey + Quicks PublicreportQuickSlice // publicreport.quick_image.quick_image_image_id_fkeypublicreport.quick_image.quick_image_quick_id_fkey +} + +func buildPublicreportImageColumns(alias string) publicreportImageColumns { + return publicreportImageColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "id", "content_type", "created", "resolution_x", "resolution_y", "storage_uuid", "storage_size", "uploaded_filename", + ).WithParent("publicreport.image"), + tableAlias: alias, + ID: psql.Quote(alias, "id"), + ContentType: psql.Quote(alias, "content_type"), + Created: psql.Quote(alias, "created"), + ResolutionX: psql.Quote(alias, "resolution_x"), + ResolutionY: psql.Quote(alias, "resolution_y"), + StorageUUID: psql.Quote(alias, "storage_uuid"), + StorageSize: psql.Quote(alias, "storage_size"), + UploadedFilename: psql.Quote(alias, "uploaded_filename"), + } +} + +type publicreportImageColumns struct { + expr.ColumnsExpr + tableAlias string + ID psql.Expression + ContentType psql.Expression + Created psql.Expression + ResolutionX psql.Expression + ResolutionY psql.Expression + StorageUUID psql.Expression + StorageSize psql.Expression + UploadedFilename psql.Expression +} + +func (c publicreportImageColumns) Alias() string { + return c.tableAlias +} + +func (publicreportImageColumns) AliasedAs(alias string) publicreportImageColumns { + return buildPublicreportImageColumns(alias) +} + +// PublicreportImageSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type PublicreportImageSetter struct { + ID omit.Val[int32] `db:"id,pk" ` + ContentType omit.Val[string] `db:"content_type" ` + Created omit.Val[time.Time] `db:"created" ` + ResolutionX omit.Val[int32] `db:"resolution_x" ` + ResolutionY omit.Val[int32] `db:"resolution_y" ` + StorageUUID omit.Val[uuid.UUID] `db:"storage_uuid" ` + StorageSize omit.Val[int64] `db:"storage_size" ` + UploadedFilename omit.Val[string] `db:"uploaded_filename" ` +} + +func (s PublicreportImageSetter) SetColumns() []string { + vals := make([]string, 0, 8) + if s.ID.IsValue() { + vals = append(vals, "id") + } + if s.ContentType.IsValue() { + vals = append(vals, "content_type") + } + if s.Created.IsValue() { + vals = append(vals, "created") + } + if s.ResolutionX.IsValue() { + vals = append(vals, "resolution_x") + } + if s.ResolutionY.IsValue() { + vals = append(vals, "resolution_y") + } + if s.StorageUUID.IsValue() { + vals = append(vals, "storage_uuid") + } + if s.StorageSize.IsValue() { + vals = append(vals, "storage_size") + } + if s.UploadedFilename.IsValue() { + vals = append(vals, "uploaded_filename") + } + return vals +} + +func (s PublicreportImageSetter) Overwrite(t *PublicreportImage) { + if s.ID.IsValue() { + t.ID = s.ID.MustGet() + } + if s.ContentType.IsValue() { + t.ContentType = s.ContentType.MustGet() + } + if s.Created.IsValue() { + t.Created = s.Created.MustGet() + } + if s.ResolutionX.IsValue() { + t.ResolutionX = s.ResolutionX.MustGet() + } + if s.ResolutionY.IsValue() { + t.ResolutionY = s.ResolutionY.MustGet() + } + if s.StorageUUID.IsValue() { + t.StorageUUID = s.StorageUUID.MustGet() + } + if s.StorageSize.IsValue() { + t.StorageSize = s.StorageSize.MustGet() + } + if s.UploadedFilename.IsValue() { + t.UploadedFilename = s.UploadedFilename.MustGet() + } +} + +func (s *PublicreportImageSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return PublicreportImages.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, 8) + if s.ID.IsValue() { + vals[0] = psql.Arg(s.ID.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.ContentType.IsValue() { + vals[1] = psql.Arg(s.ContentType.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if s.Created.IsValue() { + vals[2] = psql.Arg(s.Created.MustGet()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + if s.ResolutionX.IsValue() { + vals[3] = psql.Arg(s.ResolutionX.MustGet()) + } else { + vals[3] = psql.Raw("DEFAULT") + } + + if s.ResolutionY.IsValue() { + vals[4] = psql.Arg(s.ResolutionY.MustGet()) + } else { + vals[4] = psql.Raw("DEFAULT") + } + + if s.StorageUUID.IsValue() { + vals[5] = psql.Arg(s.StorageUUID.MustGet()) + } else { + vals[5] = psql.Raw("DEFAULT") + } + + if s.StorageSize.IsValue() { + vals[6] = psql.Arg(s.StorageSize.MustGet()) + } else { + vals[6] = psql.Raw("DEFAULT") + } + + if s.UploadedFilename.IsValue() { + vals[7] = psql.Arg(s.UploadedFilename.MustGet()) + } else { + vals[7] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s PublicreportImageSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s PublicreportImageSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 8) + + if s.ID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "id")...), + psql.Arg(s.ID), + }}) + } + + if s.ContentType.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "content_type")...), + psql.Arg(s.ContentType), + }}) + } + + if s.Created.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "created")...), + psql.Arg(s.Created), + }}) + } + + if s.ResolutionX.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "resolution_x")...), + psql.Arg(s.ResolutionX), + }}) + } + + if s.ResolutionY.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "resolution_y")...), + psql.Arg(s.ResolutionY), + }}) + } + + if s.StorageUUID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "storage_uuid")...), + psql.Arg(s.StorageUUID), + }}) + } + + if s.StorageSize.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "storage_size")...), + psql.Arg(s.StorageSize), + }}) + } + + if s.UploadedFilename.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "uploaded_filename")...), + psql.Arg(s.UploadedFilename), + }}) + } + + return exprs +} + +// FindPublicreportImage retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindPublicreportImage(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*PublicreportImage, error) { + if len(cols) == 0 { + return PublicreportImages.Query( + sm.Where(PublicreportImages.Columns.ID.EQ(psql.Arg(IDPK))), + ).One(ctx, exec) + } + + return PublicreportImages.Query( + sm.Where(PublicreportImages.Columns.ID.EQ(psql.Arg(IDPK))), + sm.Columns(PublicreportImages.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// PublicreportImageExists checks the presence of a single record by primary key +func PublicreportImageExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { + return PublicreportImages.Query( + sm.Where(PublicreportImages.Columns.ID.EQ(psql.Arg(IDPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after PublicreportImage is retrieved from the database +func (o *PublicreportImage) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = PublicreportImages.AfterSelectHooks.RunHooks(ctx, exec, PublicreportImageSlice{o}) + case bob.QueryTypeInsert: + ctx, err = PublicreportImages.AfterInsertHooks.RunHooks(ctx, exec, PublicreportImageSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = PublicreportImages.AfterUpdateHooks.RunHooks(ctx, exec, PublicreportImageSlice{o}) + case bob.QueryTypeDelete: + ctx, err = PublicreportImages.AfterDeleteHooks.RunHooks(ctx, exec, PublicreportImageSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the PublicreportImage +func (o *PublicreportImage) primaryKeyVals() bob.Expression { + return psql.Arg(o.ID) +} + +func (o *PublicreportImage) pkEQ() dialect.Expression { + return psql.Quote("publicreport.image", "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 PublicreportImage +func (o *PublicreportImage) Update(ctx context.Context, exec bob.Executor, s *PublicreportImageSetter) error { + v, err := PublicreportImages.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 PublicreportImage record with an executor +func (o *PublicreportImage) Delete(ctx context.Context, exec bob.Executor) error { + _, err := PublicreportImages.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the PublicreportImage using the executor +func (o *PublicreportImage) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := PublicreportImages.Query( + sm.Where(PublicreportImages.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 PublicreportImageSlice is retrieved from the database +func (o PublicreportImageSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = PublicreportImages.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = PublicreportImages.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = PublicreportImages.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = PublicreportImages.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o PublicreportImageSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Quote("publicreport.image", "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 PublicreportImageSlice) copyMatchingRows(from ...*PublicreportImage) { + 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 PublicreportImageSlice) 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 PublicreportImages.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 *PublicreportImage: + o.copyMatchingRows(retrieved) + case []*PublicreportImage: + o.copyMatchingRows(retrieved...) + case PublicreportImageSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a PublicreportImage or a slice of PublicreportImage + // then run the AfterUpdateHooks on the slice + _, err = PublicreportImages.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o PublicreportImageSlice) 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 PublicreportImages.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 *PublicreportImage: + o.copyMatchingRows(retrieved) + case []*PublicreportImage: + o.copyMatchingRows(retrieved...) + case PublicreportImageSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a PublicreportImage or a slice of PublicreportImage + // then run the AfterDeleteHooks on the slice + _, err = PublicreportImages.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o PublicreportImageSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals PublicreportImageSetter) error { + if len(o) == 0 { + return nil + } + + _, err := PublicreportImages.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o PublicreportImageSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := PublicreportImages.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o PublicreportImageSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := PublicreportImages.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// ImageExifs starts a query for related objects on publicreport.image_exif +func (o *PublicreportImage) ImageExifs(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImageExifsQuery { + return PublicreportImageExifs.Query(append(mods, + sm.Where(PublicreportImageExifs.Columns.ImageID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os PublicreportImageSlice) ImageExifs(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImageExifsQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return PublicreportImageExifs.Query(append(mods, + sm.Where(psql.Group(PublicreportImageExifs.Columns.ImageID).OP("IN", PKArgExpr)), + )...) +} + +// Pools starts a query for related objects on publicreport.pool +func (o *PublicreportImage) Pools(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportPoolsQuery { + return PublicreportPools.Query(append(mods, + sm.InnerJoin(PublicreportPoolImages.NameAs()).On( + PublicreportPools.Columns.ID.EQ(PublicreportPoolImages.Columns.PoolID)), + sm.Where(PublicreportPoolImages.Columns.ImageID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os PublicreportImageSlice) Pools(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportPoolsQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return PublicreportPools.Query(append(mods, + sm.InnerJoin(PublicreportPoolImages.NameAs()).On( + PublicreportPools.Columns.ID.EQ(PublicreportPoolImages.Columns.PoolID), + ), + sm.Where(psql.Group(PublicreportPoolImages.Columns.ImageID).OP("IN", PKArgExpr)), + )...) +} + +// Quicks starts a query for related objects on publicreport.quick +func (o *PublicreportImage) Quicks(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportQuicksQuery { + return PublicreportQuicks.Query(append(mods, + sm.InnerJoin(PublicreportQuickImages.NameAs()).On( + PublicreportQuicks.Columns.ID.EQ(PublicreportQuickImages.Columns.QuickID)), + sm.Where(PublicreportQuickImages.Columns.ImageID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os PublicreportImageSlice) Quicks(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportQuicksQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return PublicreportQuicks.Query(append(mods, + sm.InnerJoin(PublicreportQuickImages.NameAs()).On( + PublicreportQuicks.Columns.ID.EQ(PublicreportQuickImages.Columns.QuickID), + ), + sm.Where(psql.Group(PublicreportQuickImages.Columns.ImageID).OP("IN", PKArgExpr)), + )...) +} + +func insertPublicreportImageImageExifs0(ctx context.Context, exec bob.Executor, publicreportImageExifs1 []*PublicreportImageExifSetter, publicreportImage0 *PublicreportImage) (PublicreportImageExifSlice, error) { + for i := range publicreportImageExifs1 { + publicreportImageExifs1[i].ImageID = omit.From(publicreportImage0.ID) + } + + ret, err := PublicreportImageExifs.Insert(bob.ToMods(publicreportImageExifs1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertPublicreportImageImageExifs0: %w", err) + } + + return ret, nil +} + +func attachPublicreportImageImageExifs0(ctx context.Context, exec bob.Executor, count int, publicreportImageExifs1 PublicreportImageExifSlice, publicreportImage0 *PublicreportImage) (PublicreportImageExifSlice, error) { + setter := &PublicreportImageExifSetter{ + ImageID: omit.From(publicreportImage0.ID), + } + + err := publicreportImageExifs1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachPublicreportImageImageExifs0: %w", err) + } + + return publicreportImageExifs1, nil +} + +func (publicreportImage0 *PublicreportImage) InsertImageExifs(ctx context.Context, exec bob.Executor, related ...*PublicreportImageExifSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + publicreportImageExifs1, err := insertPublicreportImageImageExifs0(ctx, exec, related, publicreportImage0) + if err != nil { + return err + } + + publicreportImage0.R.ImageExifs = append(publicreportImage0.R.ImageExifs, publicreportImageExifs1...) + + for _, rel := range publicreportImageExifs1 { + rel.R.Image = publicreportImage0 + } + return nil +} + +func (publicreportImage0 *PublicreportImage) AttachImageExifs(ctx context.Context, exec bob.Executor, related ...*PublicreportImageExif) error { + if len(related) == 0 { + return nil + } + + var err error + publicreportImageExifs1 := PublicreportImageExifSlice(related) + + _, err = attachPublicreportImageImageExifs0(ctx, exec, len(related), publicreportImageExifs1, publicreportImage0) + if err != nil { + return err + } + + publicreportImage0.R.ImageExifs = append(publicreportImage0.R.ImageExifs, publicreportImageExifs1...) + + for _, rel := range related { + rel.R.Image = publicreportImage0 + } + + return nil +} + +func attachPublicreportImagePools0(ctx context.Context, exec bob.Executor, count int, publicreportImage0 *PublicreportImage, publicreportPools2 PublicreportPoolSlice) (PublicreportPoolImageSlice, error) { + setters := make([]*PublicreportPoolImageSetter, count) + for i := range count { + setters[i] = &PublicreportPoolImageSetter{ + ImageID: omit.From(publicreportImage0.ID), + PoolID: omit.From(publicreportPools2[i].ID), + } + } + + publicreportPoolImages1, err := PublicreportPoolImages.Insert(bob.ToMods(setters...)).All(ctx, exec) + if err != nil { + return nil, fmt.Errorf("attachPublicreportImagePools0: %w", err) + } + + return publicreportPoolImages1, nil +} + +func (publicreportImage0 *PublicreportImage) InsertPools(ctx context.Context, exec bob.Executor, related ...*PublicreportPoolSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + inserted, err := PublicreportPools.Insert(bob.ToMods(related...)).All(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + publicreportPools2 := PublicreportPoolSlice(inserted) + + _, err = attachPublicreportImagePools0(ctx, exec, len(related), publicreportImage0, publicreportPools2) + if err != nil { + return err + } + + publicreportImage0.R.Pools = append(publicreportImage0.R.Pools, publicreportPools2...) + + for _, rel := range publicreportPools2 { + rel.R.Images = append(rel.R.Images, publicreportImage0) + } + return nil +} + +func (publicreportImage0 *PublicreportImage) AttachPools(ctx context.Context, exec bob.Executor, related ...*PublicreportPool) error { + if len(related) == 0 { + return nil + } + + var err error + publicreportPools2 := PublicreportPoolSlice(related) + + _, err = attachPublicreportImagePools0(ctx, exec, len(related), publicreportImage0, publicreportPools2) + if err != nil { + return err + } + + publicreportImage0.R.Pools = append(publicreportImage0.R.Pools, publicreportPools2...) + + for _, rel := range related { + rel.R.Images = append(rel.R.Images, publicreportImage0) + } + + return nil +} + +func attachPublicreportImageQuicks0(ctx context.Context, exec bob.Executor, count int, publicreportImage0 *PublicreportImage, publicreportQuicks2 PublicreportQuickSlice) (PublicreportQuickImageSlice, error) { + setters := make([]*PublicreportQuickImageSetter, count) + for i := range count { + setters[i] = &PublicreportQuickImageSetter{ + ImageID: omit.From(publicreportImage0.ID), + QuickID: omit.From(publicreportQuicks2[i].ID), + } + } + + publicreportQuickImages1, err := PublicreportQuickImages.Insert(bob.ToMods(setters...)).All(ctx, exec) + if err != nil { + return nil, fmt.Errorf("attachPublicreportImageQuicks0: %w", err) + } + + return publicreportQuickImages1, nil +} + +func (publicreportImage0 *PublicreportImage) InsertQuicks(ctx context.Context, exec bob.Executor, related ...*PublicreportQuickSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + inserted, err := PublicreportQuicks.Insert(bob.ToMods(related...)).All(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + publicreportQuicks2 := PublicreportQuickSlice(inserted) + + _, err = attachPublicreportImageQuicks0(ctx, exec, len(related), publicreportImage0, publicreportQuicks2) + if err != nil { + return err + } + + publicreportImage0.R.Quicks = append(publicreportImage0.R.Quicks, publicreportQuicks2...) + + for _, rel := range publicreportQuicks2 { + rel.R.Images = append(rel.R.Images, publicreportImage0) + } + return nil +} + +func (publicreportImage0 *PublicreportImage) AttachQuicks(ctx context.Context, exec bob.Executor, related ...*PublicreportQuick) error { + if len(related) == 0 { + return nil + } + + var err error + publicreportQuicks2 := PublicreportQuickSlice(related) + + _, err = attachPublicreportImageQuicks0(ctx, exec, len(related), publicreportImage0, publicreportQuicks2) + if err != nil { + return err + } + + publicreportImage0.R.Quicks = append(publicreportImage0.R.Quicks, publicreportQuicks2...) + + for _, rel := range related { + rel.R.Images = append(rel.R.Images, publicreportImage0) + } + + return nil +} + +type publicreportImageWhere[Q psql.Filterable] struct { + ID psql.WhereMod[Q, int32] + ContentType psql.WhereMod[Q, string] + Created psql.WhereMod[Q, time.Time] + ResolutionX psql.WhereMod[Q, int32] + ResolutionY psql.WhereMod[Q, int32] + StorageUUID psql.WhereMod[Q, uuid.UUID] + StorageSize psql.WhereMod[Q, int64] + UploadedFilename psql.WhereMod[Q, string] +} + +func (publicreportImageWhere[Q]) AliasedAs(alias string) publicreportImageWhere[Q] { + return buildPublicreportImageWhere[Q](buildPublicreportImageColumns(alias)) +} + +func buildPublicreportImageWhere[Q psql.Filterable](cols publicreportImageColumns) publicreportImageWhere[Q] { + return publicreportImageWhere[Q]{ + ID: psql.Where[Q, int32](cols.ID), + ContentType: psql.Where[Q, string](cols.ContentType), + Created: psql.Where[Q, time.Time](cols.Created), + ResolutionX: psql.Where[Q, int32](cols.ResolutionX), + ResolutionY: psql.Where[Q, int32](cols.ResolutionY), + StorageUUID: psql.Where[Q, uuid.UUID](cols.StorageUUID), + StorageSize: psql.Where[Q, int64](cols.StorageSize), + UploadedFilename: psql.Where[Q, string](cols.UploadedFilename), + } +} + +func (o *PublicreportImage) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "ImageExifs": + rels, ok := retrieved.(PublicreportImageExifSlice) + if !ok { + return fmt.Errorf("publicreportImage cannot load %T as %q", retrieved, name) + } + + o.R.ImageExifs = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Image = o + } + } + return nil + case "Pools": + rels, ok := retrieved.(PublicreportPoolSlice) + if !ok { + return fmt.Errorf("publicreportImage cannot load %T as %q", retrieved, name) + } + + o.R.Pools = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Images = PublicreportImageSlice{o} + } + } + return nil + case "Quicks": + rels, ok := retrieved.(PublicreportQuickSlice) + if !ok { + return fmt.Errorf("publicreportImage cannot load %T as %q", retrieved, name) + } + + o.R.Quicks = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Images = PublicreportImageSlice{o} + } + } + return nil + default: + return fmt.Errorf("publicreportImage has no relationship %q", name) + } +} + +type publicreportImagePreloader struct{} + +func buildPublicreportImagePreloader() publicreportImagePreloader { + return publicreportImagePreloader{} +} + +type publicreportImageThenLoader[Q orm.Loadable] struct { + ImageExifs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Pools func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Quicks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildPublicreportImageThenLoader[Q orm.Loadable]() publicreportImageThenLoader[Q] { + type ImageExifsLoadInterface interface { + LoadImageExifs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PoolsLoadInterface interface { + LoadPools(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type QuicksLoadInterface interface { + LoadQuicks(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return publicreportImageThenLoader[Q]{ + ImageExifs: thenLoadBuilder[Q]( + "ImageExifs", + func(ctx context.Context, exec bob.Executor, retrieved ImageExifsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadImageExifs(ctx, exec, mods...) + }, + ), + Pools: thenLoadBuilder[Q]( + "Pools", + func(ctx context.Context, exec bob.Executor, retrieved PoolsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadPools(ctx, exec, mods...) + }, + ), + Quicks: thenLoadBuilder[Q]( + "Quicks", + func(ctx context.Context, exec bob.Executor, retrieved QuicksLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadQuicks(ctx, exec, mods...) + }, + ), + } +} + +// LoadImageExifs loads the publicreportImage's ImageExifs into the .R struct +func (o *PublicreportImage) LoadImageExifs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.ImageExifs = nil + + related, err := o.ImageExifs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Image = o + } + + o.R.ImageExifs = related + return nil +} + +// LoadImageExifs loads the publicreportImage's ImageExifs into the .R struct +func (os PublicreportImageSlice) LoadImageExifs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + publicreportImageExifs, err := os.ImageExifs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.ImageExifs = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range publicreportImageExifs { + + if !(o.ID == rel.ImageID) { + continue + } + + rel.R.Image = o + + o.R.ImageExifs = append(o.R.ImageExifs, rel) + } + } + + return nil +} + +// LoadPools loads the publicreportImage's Pools into the .R struct +func (o *PublicreportImage) LoadPools(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Pools = nil + + related, err := o.Pools(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Images = PublicreportImageSlice{o} + } + + o.R.Pools = related + return nil +} + +// LoadPools loads the publicreportImage's Pools into the .R struct +func (os PublicreportImageSlice) LoadPools(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + // since we are changing the columns, we need to check if the original columns were set or add the defaults + sq := dialect.SelectQuery{} + for _, mod := range mods { + mod.Apply(&sq) + } + + if len(sq.SelectList.Columns) == 0 { + mods = append(mods, sm.Columns(PublicreportPools.Columns)) + } + + q := os.Pools(append( + mods, + sm.Columns(PublicreportPoolImages.Columns.ImageID.As("related_publicreport.image.ID")), + )...) + + IDSlice := []int32{} + + mapper := scan.Mod(scan.StructMapper[*PublicreportPool](), func(ctx context.Context, cols []string) (scan.BeforeFunc, func(any, any) error) { + return func(row *scan.Row) (any, error) { + IDSlice = append(IDSlice, *new(int32)) + row.ScheduleScanByName("related_publicreport.image.ID", &IDSlice[len(IDSlice)-1]) + + return nil, nil + }, + func(any, any) error { + return nil + } + }) + + publicreportPools, err := bob.Allx[bob.SliceTransformer[*PublicreportPool, PublicreportPoolSlice]](ctx, exec, q, mapper) + if err != nil { + return err + } + + for _, o := range os { + o.R.Pools = nil + } + + for _, o := range os { + for i, rel := range publicreportPools { + if !(o.ID == IDSlice[i]) { + continue + } + + rel.R.Images = append(rel.R.Images, o) + + o.R.Pools = append(o.R.Pools, rel) + } + } + + return nil +} + +// LoadQuicks loads the publicreportImage's Quicks into the .R struct +func (o *PublicreportImage) LoadQuicks(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Quicks = nil + + related, err := o.Quicks(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Images = PublicreportImageSlice{o} + } + + o.R.Quicks = related + return nil +} + +// LoadQuicks loads the publicreportImage's Quicks into the .R struct +func (os PublicreportImageSlice) LoadQuicks(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + // since we are changing the columns, we need to check if the original columns were set or add the defaults + sq := dialect.SelectQuery{} + for _, mod := range mods { + mod.Apply(&sq) + } + + if len(sq.SelectList.Columns) == 0 { + mods = append(mods, sm.Columns(PublicreportQuicks.Columns)) + } + + q := os.Quicks(append( + mods, + sm.Columns(PublicreportQuickImages.Columns.ImageID.As("related_publicreport.image.ID")), + )...) + + IDSlice := []int32{} + + mapper := scan.Mod(scan.StructMapper[*PublicreportQuick](), func(ctx context.Context, cols []string) (scan.BeforeFunc, func(any, any) error) { + return func(row *scan.Row) (any, error) { + IDSlice = append(IDSlice, *new(int32)) + row.ScheduleScanByName("related_publicreport.image.ID", &IDSlice[len(IDSlice)-1]) + + return nil, nil + }, + func(any, any) error { + return nil + } + }) + + publicreportQuicks, err := bob.Allx[bob.SliceTransformer[*PublicreportQuick, PublicreportQuickSlice]](ctx, exec, q, mapper) + if err != nil { + return err + } + + for _, o := range os { + o.R.Quicks = nil + } + + for _, o := range os { + for i, rel := range publicreportQuicks { + if !(o.ID == IDSlice[i]) { + continue + } + + rel.R.Images = append(rel.R.Images, o) + + o.R.Quicks = append(o.R.Quicks, rel) + } + } + + return nil +} + +// publicreportImageC is where relationship counts are stored. +type publicreportImageC struct { + ImageExifs *int64 + Pools *int64 + Quicks *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *PublicreportImage) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "ImageExifs": + o.C.ImageExifs = &count + case "Pools": + o.C.Pools = &count + case "Quicks": + o.C.Quicks = &count + } + return nil +} + +type publicreportImageCountPreloader struct { + ImageExifs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Pools func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Quicks func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildPublicreportImageCountPreloader() publicreportImageCountPreloader { + return publicreportImageCountPreloader{ + ImageExifs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*PublicreportImage]("ImageExifs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = PublicreportImages.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(PublicreportImageExifs.Name()), + sm.Where(psql.Quote(PublicreportImageExifs.Alias(), "image_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Pools: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*PublicreportImage]("Pools", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = PublicreportImages.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(PublicreportPoolImages.Name()), + sm.Where(psql.Quote(PublicreportPoolImages.Alias(), "image_id").EQ(psql.Quote(parent, "id"))), + sm.InnerJoin(PublicreportPools.Name()).On( + psql.Quote(PublicreportPools.Alias(), "id").EQ(psql.Quote(PublicreportPoolImages.Alias(), "pool_id")), + ), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Quicks: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*PublicreportImage]("Quicks", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = PublicreportImages.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(PublicreportQuickImages.Name()), + sm.Where(psql.Quote(PublicreportQuickImages.Alias(), "image_id").EQ(psql.Quote(parent, "id"))), + sm.InnerJoin(PublicreportQuicks.Name()).On( + psql.Quote(PublicreportQuicks.Alias(), "id").EQ(psql.Quote(PublicreportQuickImages.Alias(), "quick_id")), + ), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type publicreportImageCountThenLoader[Q orm.Loadable] struct { + ImageExifs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Pools func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Quicks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildPublicreportImageCountThenLoader[Q orm.Loadable]() publicreportImageCountThenLoader[Q] { + type ImageExifsCountInterface interface { + LoadCountImageExifs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PoolsCountInterface interface { + LoadCountPools(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type QuicksCountInterface interface { + LoadCountQuicks(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return publicreportImageCountThenLoader[Q]{ + ImageExifs: countThenLoadBuilder[Q]( + "ImageExifs", + func(ctx context.Context, exec bob.Executor, retrieved ImageExifsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountImageExifs(ctx, exec, mods...) + }, + ), + Pools: countThenLoadBuilder[Q]( + "Pools", + func(ctx context.Context, exec bob.Executor, retrieved PoolsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountPools(ctx, exec, mods...) + }, + ), + Quicks: countThenLoadBuilder[Q]( + "Quicks", + func(ctx context.Context, exec bob.Executor, retrieved QuicksCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountQuicks(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountImageExifs loads the count of ImageExifs into the C struct +func (o *PublicreportImage) LoadCountImageExifs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.ImageExifs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.ImageExifs = &count + return nil +} + +// LoadCountImageExifs loads the count of ImageExifs for a slice +func (os PublicreportImageSlice) LoadCountImageExifs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountImageExifs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountPools loads the count of Pools into the C struct +func (o *PublicreportImage) LoadCountPools(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Pools(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Pools = &count + return nil +} + +// LoadCountPools loads the count of Pools for a slice +func (os PublicreportImageSlice) LoadCountPools(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountPools(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountQuicks loads the count of Quicks into the C struct +func (o *PublicreportImage) LoadCountQuicks(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Quicks(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Quicks = &count + return nil +} + +// LoadCountQuicks loads the count of Quicks for a slice +func (os PublicreportImageSlice) LoadCountQuicks(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountQuicks(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +type publicreportImageJoins[Q dialect.Joinable] struct { + typ string + ImageExifs modAs[Q, publicreportImageExifColumns] + Pools modAs[Q, publicreportPoolColumns] + Quicks modAs[Q, publicreportQuickColumns] +} + +func (j publicreportImageJoins[Q]) aliasedAs(alias string) publicreportImageJoins[Q] { + return buildPublicreportImageJoins[Q](buildPublicreportImageColumns(alias), j.typ) +} + +func buildPublicreportImageJoins[Q dialect.Joinable](cols publicreportImageColumns, typ string) publicreportImageJoins[Q] { + return publicreportImageJoins[Q]{ + typ: typ, + ImageExifs: modAs[Q, publicreportImageExifColumns]{ + c: PublicreportImageExifs.Columns, + f: func(to publicreportImageExifColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, PublicreportImageExifs.Name().As(to.Alias())).On( + to.ImageID.EQ(cols.ID), + )) + } + + return mods + }, + }, + Pools: modAs[Q, publicreportPoolColumns]{ + c: PublicreportPools.Columns, + f: func(to publicreportPoolColumns) bob.Mod[Q] { + random := strconv.FormatInt(randInt(), 10) + mods := make(mods.QueryMods[Q], 0, 2) + + { + to := PublicreportPoolImages.Columns.AliasedAs(PublicreportPoolImages.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, PublicreportPoolImages.Name().As(to.Alias())).On( + to.ImageID.EQ(cols.ID), + )) + } + { + cols := PublicreportPoolImages.Columns.AliasedAs(PublicreportPoolImages.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, PublicreportPools.Name().As(to.Alias())).On( + to.ID.EQ(cols.PoolID), + )) + } + + return mods + }, + }, + Quicks: modAs[Q, publicreportQuickColumns]{ + c: PublicreportQuicks.Columns, + f: func(to publicreportQuickColumns) bob.Mod[Q] { + random := strconv.FormatInt(randInt(), 10) + mods := make(mods.QueryMods[Q], 0, 2) + + { + to := PublicreportQuickImages.Columns.AliasedAs(PublicreportQuickImages.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, PublicreportQuickImages.Name().As(to.Alias())).On( + to.ImageID.EQ(cols.ID), + )) + } + { + cols := PublicreportQuickImages.Columns.AliasedAs(PublicreportQuickImages.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, PublicreportQuicks.Name().As(to.Alias())).On( + to.ID.EQ(cols.QuickID), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/publicreport.image_exif.bob.go b/db/models/publicreport.image_exif.bob.go new file mode 100644 index 00000000..bc4ac461 --- /dev/null +++ b/db/models/publicreport.image_exif.bob.go @@ -0,0 +1,645 @@ +// Code generated by BobGen psql v0.42.1. 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/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" +) + +// PublicreportImageExif is an object representing the database table. +type PublicreportImageExif struct { + ImageID int32 `db:"image_id,pk" ` + Name string `db:"name,pk" ` + Value string `db:"value,pk" ` + + R publicreportImageExifR `db:"-" ` +} + +// PublicreportImageExifSlice is an alias for a slice of pointers to PublicreportImageExif. +// This should almost always be used instead of []*PublicreportImageExif. +type PublicreportImageExifSlice []*PublicreportImageExif + +// PublicreportImageExifs contains methods to work with the image_exif table +var PublicreportImageExifs = psql.NewTablex[*PublicreportImageExif, PublicreportImageExifSlice, *PublicreportImageExifSetter]("publicreport", "image_exif", buildPublicreportImageExifColumns("publicreport.image_exif")) + +// PublicreportImageExifsQuery is a query on the image_exif table +type PublicreportImageExifsQuery = *psql.ViewQuery[*PublicreportImageExif, PublicreportImageExifSlice] + +// publicreportImageExifR is where relationships are stored. +type publicreportImageExifR struct { + Image *PublicreportImage // publicreport.image_exif.image_exif_image_id_fkey +} + +func buildPublicreportImageExifColumns(alias string) publicreportImageExifColumns { + return publicreportImageExifColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "image_id", "name", "value", + ).WithParent("publicreport.image_exif"), + tableAlias: alias, + ImageID: psql.Quote(alias, "image_id"), + Name: psql.Quote(alias, "name"), + Value: psql.Quote(alias, "value"), + } +} + +type publicreportImageExifColumns struct { + expr.ColumnsExpr + tableAlias string + ImageID psql.Expression + Name psql.Expression + Value psql.Expression +} + +func (c publicreportImageExifColumns) Alias() string { + return c.tableAlias +} + +func (publicreportImageExifColumns) AliasedAs(alias string) publicreportImageExifColumns { + return buildPublicreportImageExifColumns(alias) +} + +// PublicreportImageExifSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type PublicreportImageExifSetter struct { + ImageID omit.Val[int32] `db:"image_id,pk" ` + Name omit.Val[string] `db:"name,pk" ` + Value omit.Val[string] `db:"value,pk" ` +} + +func (s PublicreportImageExifSetter) SetColumns() []string { + vals := make([]string, 0, 3) + if s.ImageID.IsValue() { + vals = append(vals, "image_id") + } + if s.Name.IsValue() { + vals = append(vals, "name") + } + if s.Value.IsValue() { + vals = append(vals, "value") + } + return vals +} + +func (s PublicreportImageExifSetter) Overwrite(t *PublicreportImageExif) { + if s.ImageID.IsValue() { + t.ImageID = s.ImageID.MustGet() + } + if s.Name.IsValue() { + t.Name = s.Name.MustGet() + } + if s.Value.IsValue() { + t.Value = s.Value.MustGet() + } +} + +func (s *PublicreportImageExifSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return PublicreportImageExifs.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, 3) + if s.ImageID.IsValue() { + vals[0] = psql.Arg(s.ImageID.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.Name.IsValue() { + vals[1] = psql.Arg(s.Name.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if s.Value.IsValue() { + vals[2] = psql.Arg(s.Value.MustGet()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s PublicreportImageExifSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s PublicreportImageExifSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 3) + + if s.ImageID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "image_id")...), + psql.Arg(s.ImageID), + }}) + } + + if s.Name.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "name")...), + psql.Arg(s.Name), + }}) + } + + if s.Value.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "value")...), + psql.Arg(s.Value), + }}) + } + + return exprs +} + +// FindPublicreportImageExif retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindPublicreportImageExif(ctx context.Context, exec bob.Executor, ImageIDPK int32, NamePK string, ValuePK string, cols ...string) (*PublicreportImageExif, error) { + if len(cols) == 0 { + return PublicreportImageExifs.Query( + sm.Where(PublicreportImageExifs.Columns.ImageID.EQ(psql.Arg(ImageIDPK))), + sm.Where(PublicreportImageExifs.Columns.Name.EQ(psql.Arg(NamePK))), + sm.Where(PublicreportImageExifs.Columns.Value.EQ(psql.Arg(ValuePK))), + ).One(ctx, exec) + } + + return PublicreportImageExifs.Query( + sm.Where(PublicreportImageExifs.Columns.ImageID.EQ(psql.Arg(ImageIDPK))), + sm.Where(PublicreportImageExifs.Columns.Name.EQ(psql.Arg(NamePK))), + sm.Where(PublicreportImageExifs.Columns.Value.EQ(psql.Arg(ValuePK))), + sm.Columns(PublicreportImageExifs.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// PublicreportImageExifExists checks the presence of a single record by primary key +func PublicreportImageExifExists(ctx context.Context, exec bob.Executor, ImageIDPK int32, NamePK string, ValuePK string) (bool, error) { + return PublicreportImageExifs.Query( + sm.Where(PublicreportImageExifs.Columns.ImageID.EQ(psql.Arg(ImageIDPK))), + sm.Where(PublicreportImageExifs.Columns.Name.EQ(psql.Arg(NamePK))), + sm.Where(PublicreportImageExifs.Columns.Value.EQ(psql.Arg(ValuePK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after PublicreportImageExif is retrieved from the database +func (o *PublicreportImageExif) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = PublicreportImageExifs.AfterSelectHooks.RunHooks(ctx, exec, PublicreportImageExifSlice{o}) + case bob.QueryTypeInsert: + ctx, err = PublicreportImageExifs.AfterInsertHooks.RunHooks(ctx, exec, PublicreportImageExifSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = PublicreportImageExifs.AfterUpdateHooks.RunHooks(ctx, exec, PublicreportImageExifSlice{o}) + case bob.QueryTypeDelete: + ctx, err = PublicreportImageExifs.AfterDeleteHooks.RunHooks(ctx, exec, PublicreportImageExifSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the PublicreportImageExif +func (o *PublicreportImageExif) primaryKeyVals() bob.Expression { + return psql.ArgGroup( + o.ImageID, + o.Name, + o.Value, + ) +} + +func (o *PublicreportImageExif) pkEQ() dialect.Expression { + return psql.Group(psql.Quote("publicreport.image_exif", "image_id"), psql.Quote("publicreport.image_exif", "name"), psql.Quote("publicreport.image_exif", "value")).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 PublicreportImageExif +func (o *PublicreportImageExif) Update(ctx context.Context, exec bob.Executor, s *PublicreportImageExifSetter) error { + v, err := PublicreportImageExifs.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 PublicreportImageExif record with an executor +func (o *PublicreportImageExif) Delete(ctx context.Context, exec bob.Executor) error { + _, err := PublicreportImageExifs.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the PublicreportImageExif using the executor +func (o *PublicreportImageExif) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := PublicreportImageExifs.Query( + sm.Where(PublicreportImageExifs.Columns.ImageID.EQ(psql.Arg(o.ImageID))), + sm.Where(PublicreportImageExifs.Columns.Name.EQ(psql.Arg(o.Name))), + sm.Where(PublicreportImageExifs.Columns.Value.EQ(psql.Arg(o.Value))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after PublicreportImageExifSlice is retrieved from the database +func (o PublicreportImageExifSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = PublicreportImageExifs.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = PublicreportImageExifs.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = PublicreportImageExifs.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = PublicreportImageExifs.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o PublicreportImageExifSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Group(psql.Quote("publicreport.image_exif", "image_id"), psql.Quote("publicreport.image_exif", "name"), psql.Quote("publicreport.image_exif", "value")).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 PublicreportImageExifSlice) copyMatchingRows(from ...*PublicreportImageExif) { + for i, old := range o { + for _, new := range from { + if new.ImageID != old.ImageID { + continue + } + if new.Name != old.Name { + continue + } + if new.Value != old.Value { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o PublicreportImageExifSlice) 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 PublicreportImageExifs.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 *PublicreportImageExif: + o.copyMatchingRows(retrieved) + case []*PublicreportImageExif: + o.copyMatchingRows(retrieved...) + case PublicreportImageExifSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a PublicreportImageExif or a slice of PublicreportImageExif + // then run the AfterUpdateHooks on the slice + _, err = PublicreportImageExifs.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o PublicreportImageExifSlice) 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 PublicreportImageExifs.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 *PublicreportImageExif: + o.copyMatchingRows(retrieved) + case []*PublicreportImageExif: + o.copyMatchingRows(retrieved...) + case PublicreportImageExifSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a PublicreportImageExif or a slice of PublicreportImageExif + // then run the AfterDeleteHooks on the slice + _, err = PublicreportImageExifs.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o PublicreportImageExifSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals PublicreportImageExifSetter) error { + if len(o) == 0 { + return nil + } + + _, err := PublicreportImageExifs.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o PublicreportImageExifSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := PublicreportImageExifs.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o PublicreportImageExifSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := PublicreportImageExifs.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// Image starts a query for related objects on publicreport.image +func (o *PublicreportImageExif) Image(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { + return PublicreportImages.Query(append(mods, + sm.Where(PublicreportImages.Columns.ID.EQ(psql.Arg(o.ImageID))), + )...) +} + +func (os PublicreportImageExifSlice) Image(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { + pkImageID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkImageID = append(pkImageID, o.ImageID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkImageID), "integer[]")), + )) + + return PublicreportImages.Query(append(mods, + sm.Where(psql.Group(PublicreportImages.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + +func attachPublicreportImageExifImage0(ctx context.Context, exec bob.Executor, count int, publicreportImageExif0 *PublicreportImageExif, publicreportImage1 *PublicreportImage) (*PublicreportImageExif, error) { + setter := &PublicreportImageExifSetter{ + ImageID: omit.From(publicreportImage1.ID), + } + + err := publicreportImageExif0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachPublicreportImageExifImage0: %w", err) + } + + return publicreportImageExif0, nil +} + +func (publicreportImageExif0 *PublicreportImageExif) InsertImage(ctx context.Context, exec bob.Executor, related *PublicreportImageSetter) error { + var err error + + publicreportImage1, err := PublicreportImages.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachPublicreportImageExifImage0(ctx, exec, 1, publicreportImageExif0, publicreportImage1) + if err != nil { + return err + } + + publicreportImageExif0.R.Image = publicreportImage1 + + publicreportImage1.R.ImageExifs = append(publicreportImage1.R.ImageExifs, publicreportImageExif0) + + return nil +} + +func (publicreportImageExif0 *PublicreportImageExif) AttachImage(ctx context.Context, exec bob.Executor, publicreportImage1 *PublicreportImage) error { + var err error + + _, err = attachPublicreportImageExifImage0(ctx, exec, 1, publicreportImageExif0, publicreportImage1) + if err != nil { + return err + } + + publicreportImageExif0.R.Image = publicreportImage1 + + publicreportImage1.R.ImageExifs = append(publicreportImage1.R.ImageExifs, publicreportImageExif0) + + return nil +} + +type publicreportImageExifWhere[Q psql.Filterable] struct { + ImageID psql.WhereMod[Q, int32] + Name psql.WhereMod[Q, string] + Value psql.WhereMod[Q, string] +} + +func (publicreportImageExifWhere[Q]) AliasedAs(alias string) publicreportImageExifWhere[Q] { + return buildPublicreportImageExifWhere[Q](buildPublicreportImageExifColumns(alias)) +} + +func buildPublicreportImageExifWhere[Q psql.Filterable](cols publicreportImageExifColumns) publicreportImageExifWhere[Q] { + return publicreportImageExifWhere[Q]{ + ImageID: psql.Where[Q, int32](cols.ImageID), + Name: psql.Where[Q, string](cols.Name), + Value: psql.Where[Q, string](cols.Value), + } +} + +func (o *PublicreportImageExif) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "Image": + rel, ok := retrieved.(*PublicreportImage) + if !ok { + return fmt.Errorf("publicreportImageExif cannot load %T as %q", retrieved, name) + } + + o.R.Image = rel + + if rel != nil { + rel.R.ImageExifs = PublicreportImageExifSlice{o} + } + return nil + default: + return fmt.Errorf("publicreportImageExif has no relationship %q", name) + } +} + +type publicreportImageExifPreloader struct { + Image func(...psql.PreloadOption) psql.Preloader +} + +func buildPublicreportImageExifPreloader() publicreportImageExifPreloader { + return publicreportImageExifPreloader{ + Image: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*PublicreportImage, PublicreportImageSlice](psql.PreloadRel{ + Name: "Image", + Sides: []psql.PreloadSide{ + { + From: PublicreportImageExifs, + To: PublicreportImages, + FromColumns: []string{"image_id"}, + ToColumns: []string{"id"}, + }, + }, + }, PublicreportImages.Columns.Names(), opts...) + }, + } +} + +type publicreportImageExifThenLoader[Q orm.Loadable] struct { + Image func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildPublicreportImageExifThenLoader[Q orm.Loadable]() publicreportImageExifThenLoader[Q] { + type ImageLoadInterface interface { + LoadImage(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return publicreportImageExifThenLoader[Q]{ + Image: thenLoadBuilder[Q]( + "Image", + func(ctx context.Context, exec bob.Executor, retrieved ImageLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadImage(ctx, exec, mods...) + }, + ), + } +} + +// LoadImage loads the publicreportImageExif's Image into the .R struct +func (o *PublicreportImageExif) LoadImage(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Image = nil + + related, err := o.Image(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.ImageExifs = PublicreportImageExifSlice{o} + + o.R.Image = related + return nil +} + +// LoadImage loads the publicreportImageExif's Image into the .R struct +func (os PublicreportImageExifSlice) LoadImage(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + publicreportImages, err := os.Image(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range publicreportImages { + + if !(o.ImageID == rel.ID) { + continue + } + + rel.R.ImageExifs = append(rel.R.ImageExifs, o) + + o.R.Image = rel + break + } + } + + return nil +} + +type publicreportImageExifJoins[Q dialect.Joinable] struct { + typ string + Image modAs[Q, publicreportImageColumns] +} + +func (j publicreportImageExifJoins[Q]) aliasedAs(alias string) publicreportImageExifJoins[Q] { + return buildPublicreportImageExifJoins[Q](buildPublicreportImageExifColumns(alias), j.typ) +} + +func buildPublicreportImageExifJoins[Q dialect.Joinable](cols publicreportImageExifColumns, typ string) publicreportImageExifJoins[Q] { + return publicreportImageExifJoins[Q]{ + typ: typ, + Image: modAs[Q, publicreportImageColumns]{ + c: PublicreportImages.Columns, + f: func(to publicreportImageColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, PublicreportImages.Name().As(to.Alias())).On( + to.ID.EQ(cols.ImageID), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/publicreport.pool.bob.go b/db/models/publicreport.pool.bob.go index 350af417..0a5a9d8e 100644 --- a/db/models/publicreport.pool.bob.go +++ b/db/models/publicreport.pool.bob.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "io" + "strconv" "time" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" @@ -23,6 +24,7 @@ import ( "github.com/stephenafamo/bob/mods" "github.com/stephenafamo/bob/orm" "github.com/stephenafamo/bob/types/pgtypes" + "github.com/stephenafamo/scan" ) // PublicreportPool is an object representing the database table. @@ -75,7 +77,7 @@ type PublicreportPoolsQuery = *psql.ViewQuery[*PublicreportPool, PublicreportPoo // publicreportPoolR is where relationships are stored. type publicreportPoolR struct { - PoolPhotos PublicreportPoolPhotoSlice // publicreport.pool_photo.pool_photo_pool_id_fkey + Images PublicreportImageSlice // publicreport.pool_image.pool_image_image_id_fkeypublicreport.pool_image.pool_image_pool_id_fkey } func buildPublicreportPoolColumns(alias string) publicreportPoolColumns { @@ -1018,14 +1020,16 @@ func (o PublicreportPoolSlice) ReloadAll(ctx context.Context, exec bob.Executor) return nil } -// PoolPhotos starts a query for related objects on publicreport.pool_photo -func (o *PublicreportPool) PoolPhotos(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportPoolPhotosQuery { - return PublicreportPoolPhotos.Query(append(mods, - sm.Where(PublicreportPoolPhotos.Columns.PoolID.EQ(psql.Arg(o.ID))), +// Images starts a query for related objects on publicreport.image +func (o *PublicreportPool) Images(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { + return PublicreportImages.Query(append(mods, + sm.InnerJoin(PublicreportPoolImages.NameAs()).On( + PublicreportImages.Columns.ID.EQ(PublicreportPoolImages.Columns.ImageID)), + sm.Where(PublicreportPoolImages.Columns.PoolID.EQ(psql.Arg(o.ID))), )...) } -func (os PublicreportPoolSlice) PoolPhotos(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportPoolPhotosQuery { +func (os PublicreportPoolSlice) Images(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { pkID := make(pgtypes.Array[int32], 0, len(os)) for _, o := range os { if o == nil { @@ -1037,74 +1041,74 @@ func (os PublicreportPoolSlice) PoolPhotos(mods ...bob.Mod[*dialect.SelectQuery] psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), )) - return PublicreportPoolPhotos.Query(append(mods, - sm.Where(psql.Group(PublicreportPoolPhotos.Columns.PoolID).OP("IN", PKArgExpr)), + return PublicreportImages.Query(append(mods, + sm.InnerJoin(PublicreportPoolImages.NameAs()).On( + PublicreportImages.Columns.ID.EQ(PublicreportPoolImages.Columns.ImageID), + ), + sm.Where(psql.Group(PublicreportPoolImages.Columns.PoolID).OP("IN", PKArgExpr)), )...) } -func insertPublicreportPoolPoolPhotos0(ctx context.Context, exec bob.Executor, publicreportPoolPhotos1 []*PublicreportPoolPhotoSetter, publicreportPool0 *PublicreportPool) (PublicreportPoolPhotoSlice, error) { - for i := range publicreportPoolPhotos1 { - publicreportPoolPhotos1[i].PoolID = omit.From(publicreportPool0.ID) +func attachPublicreportPoolImages0(ctx context.Context, exec bob.Executor, count int, publicreportPool0 *PublicreportPool, publicreportImages2 PublicreportImageSlice) (PublicreportPoolImageSlice, error) { + setters := make([]*PublicreportPoolImageSetter, count) + for i := range count { + setters[i] = &PublicreportPoolImageSetter{ + PoolID: omit.From(publicreportPool0.ID), + ImageID: omit.From(publicreportImages2[i].ID), + } } - ret, err := PublicreportPoolPhotos.Insert(bob.ToMods(publicreportPoolPhotos1...)).All(ctx, exec) + publicreportPoolImages1, err := PublicreportPoolImages.Insert(bob.ToMods(setters...)).All(ctx, exec) if err != nil { - return ret, fmt.Errorf("insertPublicreportPoolPoolPhotos0: %w", err) + return nil, fmt.Errorf("attachPublicreportPoolImages0: %w", err) } - return ret, nil + return publicreportPoolImages1, nil } -func attachPublicreportPoolPoolPhotos0(ctx context.Context, exec bob.Executor, count int, publicreportPoolPhotos1 PublicreportPoolPhotoSlice, publicreportPool0 *PublicreportPool) (PublicreportPoolPhotoSlice, error) { - setter := &PublicreportPoolPhotoSetter{ - PoolID: omit.From(publicreportPool0.ID), - } - - err := publicreportPoolPhotos1.UpdateAll(ctx, exec, *setter) - if err != nil { - return nil, fmt.Errorf("attachPublicreportPoolPoolPhotos0: %w", err) - } - - return publicreportPoolPhotos1, nil -} - -func (publicreportPool0 *PublicreportPool) InsertPoolPhotos(ctx context.Context, exec bob.Executor, related ...*PublicreportPoolPhotoSetter) error { +func (publicreportPool0 *PublicreportPool) InsertImages(ctx context.Context, exec bob.Executor, related ...*PublicreportImageSetter) error { if len(related) == 0 { return nil } var err error - publicreportPoolPhotos1, err := insertPublicreportPoolPoolPhotos0(ctx, exec, related, publicreportPool0) + inserted, err := PublicreportImages.Insert(bob.ToMods(related...)).All(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + publicreportImages2 := PublicreportImageSlice(inserted) + + _, err = attachPublicreportPoolImages0(ctx, exec, len(related), publicreportPool0, publicreportImages2) if err != nil { return err } - publicreportPool0.R.PoolPhotos = append(publicreportPool0.R.PoolPhotos, publicreportPoolPhotos1...) + publicreportPool0.R.Images = append(publicreportPool0.R.Images, publicreportImages2...) - for _, rel := range publicreportPoolPhotos1 { - rel.R.Pool = publicreportPool0 + for _, rel := range publicreportImages2 { + rel.R.Pools = append(rel.R.Pools, publicreportPool0) } return nil } -func (publicreportPool0 *PublicreportPool) AttachPoolPhotos(ctx context.Context, exec bob.Executor, related ...*PublicreportPoolPhoto) error { +func (publicreportPool0 *PublicreportPool) AttachImages(ctx context.Context, exec bob.Executor, related ...*PublicreportImage) error { if len(related) == 0 { return nil } var err error - publicreportPoolPhotos1 := PublicreportPoolPhotoSlice(related) + publicreportImages2 := PublicreportImageSlice(related) - _, err = attachPublicreportPoolPoolPhotos0(ctx, exec, len(related), publicreportPoolPhotos1, publicreportPool0) + _, err = attachPublicreportPoolImages0(ctx, exec, len(related), publicreportPool0, publicreportImages2) if err != nil { return err } - publicreportPool0.R.PoolPhotos = append(publicreportPool0.R.PoolPhotos, publicreportPoolPhotos1...) + publicreportPool0.R.Images = append(publicreportPool0.R.Images, publicreportImages2...) for _, rel := range related { - rel.R.Pool = publicreportPool0 + rel.R.Pools = append(rel.R.Pools, publicreportPool0) } return nil @@ -1188,17 +1192,17 @@ func (o *PublicreportPool) Preload(name string, retrieved any) error { } switch name { - case "PoolPhotos": - rels, ok := retrieved.(PublicreportPoolPhotoSlice) + case "Images": + rels, ok := retrieved.(PublicreportImageSlice) if !ok { return fmt.Errorf("publicreportPool cannot load %T as %q", retrieved, name) } - o.R.PoolPhotos = rels + o.R.Images = rels for _, rel := range rels { if rel != nil { - rel.R.Pool = o + rel.R.Pools = PublicreportPoolSlice{o} } } return nil @@ -1214,79 +1218,99 @@ func buildPublicreportPoolPreloader() publicreportPoolPreloader { } type publicreportPoolThenLoader[Q orm.Loadable] struct { - PoolPhotos func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Images func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildPublicreportPoolThenLoader[Q orm.Loadable]() publicreportPoolThenLoader[Q] { - type PoolPhotosLoadInterface interface { - LoadPoolPhotos(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type ImagesLoadInterface interface { + LoadImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } return publicreportPoolThenLoader[Q]{ - PoolPhotos: thenLoadBuilder[Q]( - "PoolPhotos", - func(ctx context.Context, exec bob.Executor, retrieved PoolPhotosLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadPoolPhotos(ctx, exec, mods...) + Images: thenLoadBuilder[Q]( + "Images", + func(ctx context.Context, exec bob.Executor, retrieved ImagesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadImages(ctx, exec, mods...) }, ), } } -// LoadPoolPhotos loads the publicreportPool's PoolPhotos into the .R struct -func (o *PublicreportPool) LoadPoolPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadImages loads the publicreportPool's Images into the .R struct +func (o *PublicreportPool) LoadImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } // Reset the relationship - o.R.PoolPhotos = nil + o.R.Images = nil - related, err := o.PoolPhotos(mods...).All(ctx, exec) + related, err := o.Images(mods...).All(ctx, exec) if err != nil { return err } for _, rel := range related { - rel.R.Pool = o + rel.R.Pools = PublicreportPoolSlice{o} } - o.R.PoolPhotos = related + o.R.Images = related return nil } -// LoadPoolPhotos loads the publicreportPool's PoolPhotos into the .R struct -func (os PublicreportPoolSlice) LoadPoolPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadImages loads the publicreportPool's Images into the .R struct +func (os PublicreportPoolSlice) LoadImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } - publicreportPoolPhotos, err := os.PoolPhotos(mods...).All(ctx, exec) + // since we are changing the columns, we need to check if the original columns were set or add the defaults + sq := dialect.SelectQuery{} + for _, mod := range mods { + mod.Apply(&sq) + } + + if len(sq.SelectList.Columns) == 0 { + mods = append(mods, sm.Columns(PublicreportImages.Columns)) + } + + q := os.Images(append( + mods, + sm.Columns(PublicreportPoolImages.Columns.PoolID.As("related_publicreport.pool.ID")), + )...) + + IDSlice := []int32{} + + mapper := scan.Mod(scan.StructMapper[*PublicreportImage](), func(ctx context.Context, cols []string) (scan.BeforeFunc, func(any, any) error) { + return func(row *scan.Row) (any, error) { + IDSlice = append(IDSlice, *new(int32)) + row.ScheduleScanByName("related_publicreport.pool.ID", &IDSlice[len(IDSlice)-1]) + + return nil, nil + }, + func(any, any) error { + return nil + } + }) + + publicreportImages, err := bob.Allx[bob.SliceTransformer[*PublicreportImage, PublicreportImageSlice]](ctx, exec, q, mapper) if err != nil { return err } for _, o := range os { - if o == nil { - continue - } - - o.R.PoolPhotos = nil + o.R.Images = nil } for _, o := range os { - if o == nil { - continue - } - - for _, rel := range publicreportPoolPhotos { - - if !(o.ID == rel.PoolID) { + for i, rel := range publicreportImages { + if !(o.ID == IDSlice[i]) { continue } - rel.R.Pool = o + rel.R.Pools = append(rel.R.Pools, o) - o.R.PoolPhotos = append(o.R.PoolPhotos, rel) + o.R.Images = append(o.R.Images, rel) } } @@ -1295,7 +1319,7 @@ func (os PublicreportPoolSlice) LoadPoolPhotos(ctx context.Context, exec bob.Exe // publicreportPoolC is where relationship counts are stored. type publicreportPoolC struct { - PoolPhotos *int64 + Images *int64 } // PreloadCount sets a count in the C struct by name @@ -1305,20 +1329,20 @@ func (o *PublicreportPool) PreloadCount(name string, count int64) error { } switch name { - case "PoolPhotos": - o.C.PoolPhotos = &count + case "Images": + o.C.Images = &count } return nil } type publicreportPoolCountPreloader struct { - PoolPhotos func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Images func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader } func buildPublicreportPoolCountPreloader() publicreportPoolCountPreloader { return publicreportPoolCountPreloader{ - PoolPhotos: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { - return countPreloader[*PublicreportPool]("PoolPhotos", func(parent string) bob.Expression { + Images: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*PublicreportPool]("Images", func(parent string) bob.Expression { // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) if parent == "" { parent = PublicreportPools.Alias() @@ -1327,8 +1351,11 @@ func buildPublicreportPoolCountPreloader() publicreportPoolCountPreloader { subqueryMods := []bob.Mod[*dialect.SelectQuery]{ sm.Columns(psql.Raw("count(*)")), - sm.From(PublicreportPoolPhotos.Name()), - sm.Where(psql.Quote(PublicreportPoolPhotos.Alias(), "pool_id").EQ(psql.Quote(parent, "id"))), + sm.From(PublicreportPoolImages.Name()), + sm.Where(psql.Quote(PublicreportPoolImages.Alias(), "pool_id").EQ(psql.Quote(parent, "id"))), + sm.InnerJoin(PublicreportImages.Name()).On( + psql.Quote(PublicreportImages.Alias(), "id").EQ(psql.Quote(PublicreportPoolImages.Alias(), "image_id")), + ), } subqueryMods = append(subqueryMods, mods...) return psql.Group(psql.Select(subqueryMods...).Expression) @@ -1338,47 +1365,47 @@ func buildPublicreportPoolCountPreloader() publicreportPoolCountPreloader { } type publicreportPoolCountThenLoader[Q orm.Loadable] struct { - PoolPhotos func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Images func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildPublicreportPoolCountThenLoader[Q orm.Loadable]() publicreportPoolCountThenLoader[Q] { - type PoolPhotosCountInterface interface { - LoadCountPoolPhotos(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type ImagesCountInterface interface { + LoadCountImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } return publicreportPoolCountThenLoader[Q]{ - PoolPhotos: countThenLoadBuilder[Q]( - "PoolPhotos", - func(ctx context.Context, exec bob.Executor, retrieved PoolPhotosCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadCountPoolPhotos(ctx, exec, mods...) + Images: countThenLoadBuilder[Q]( + "Images", + func(ctx context.Context, exec bob.Executor, retrieved ImagesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountImages(ctx, exec, mods...) }, ), } } -// LoadCountPoolPhotos loads the count of PoolPhotos into the C struct -func (o *PublicreportPool) LoadCountPoolPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadCountImages loads the count of Images into the C struct +func (o *PublicreportPool) LoadCountImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } - count, err := o.PoolPhotos(mods...).Count(ctx, exec) + count, err := o.Images(mods...).Count(ctx, exec) if err != nil { return err } - o.C.PoolPhotos = &count + o.C.Images = &count return nil } -// LoadCountPoolPhotos loads the count of PoolPhotos for a slice -func (os PublicreportPoolSlice) LoadCountPoolPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadCountImages loads the count of Images for a slice +func (os PublicreportPoolSlice) LoadCountImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } for _, o := range os { - if err := o.LoadCountPoolPhotos(ctx, exec, mods...); err != nil { + if err := o.LoadCountImages(ctx, exec, mods...); err != nil { return err } } @@ -1387,8 +1414,8 @@ func (os PublicreportPoolSlice) LoadCountPoolPhotos(ctx context.Context, exec bo } type publicreportPoolJoins[Q dialect.Joinable] struct { - typ string - PoolPhotos modAs[Q, publicreportPoolPhotoColumns] + typ string + Images modAs[Q, publicreportImageColumns] } func (j publicreportPoolJoins[Q]) aliasedAs(alias string) publicreportPoolJoins[Q] { @@ -1398,16 +1425,24 @@ func (j publicreportPoolJoins[Q]) aliasedAs(alias string) publicreportPoolJoins[ func buildPublicreportPoolJoins[Q dialect.Joinable](cols publicreportPoolColumns, typ string) publicreportPoolJoins[Q] { return publicreportPoolJoins[Q]{ typ: typ, - PoolPhotos: modAs[Q, publicreportPoolPhotoColumns]{ - c: PublicreportPoolPhotos.Columns, - f: func(to publicreportPoolPhotoColumns) bob.Mod[Q] { - mods := make(mods.QueryMods[Q], 0, 1) + Images: modAs[Q, publicreportImageColumns]{ + c: PublicreportImages.Columns, + f: func(to publicreportImageColumns) bob.Mod[Q] { + random := strconv.FormatInt(randInt(), 10) + mods := make(mods.QueryMods[Q], 0, 2) { - mods = append(mods, dialect.Join[Q](typ, PublicreportPoolPhotos.Name().As(to.Alias())).On( + to := PublicreportPoolImages.Columns.AliasedAs(PublicreportPoolImages.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, PublicreportPoolImages.Name().As(to.Alias())).On( to.PoolID.EQ(cols.ID), )) } + { + cols := PublicreportPoolImages.Columns.AliasedAs(PublicreportPoolImages.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, PublicreportImages.Name().As(to.Alias())).On( + to.ID.EQ(cols.ImageID), + )) + } return mods }, diff --git a/db/models/publicreport.pool_image.bob.go b/db/models/publicreport.pool_image.bob.go new file mode 100644 index 00000000..3547232d --- /dev/null +++ b/db/models/publicreport.pool_image.bob.go @@ -0,0 +1,766 @@ +// Code generated by BobGen psql v0.42.1. 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/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" +) + +// PublicreportPoolImage is an object representing the database table. +type PublicreportPoolImage struct { + ImageID int32 `db:"image_id,pk" ` + PoolID int32 `db:"pool_id,pk" ` + + R publicreportPoolImageR `db:"-" ` +} + +// PublicreportPoolImageSlice is an alias for a slice of pointers to PublicreportPoolImage. +// This should almost always be used instead of []*PublicreportPoolImage. +type PublicreportPoolImageSlice []*PublicreportPoolImage + +// PublicreportPoolImages contains methods to work with the pool_image table +var PublicreportPoolImages = psql.NewTablex[*PublicreportPoolImage, PublicreportPoolImageSlice, *PublicreportPoolImageSetter]("publicreport", "pool_image", buildPublicreportPoolImageColumns("publicreport.pool_image")) + +// PublicreportPoolImagesQuery is a query on the pool_image table +type PublicreportPoolImagesQuery = *psql.ViewQuery[*PublicreportPoolImage, PublicreportPoolImageSlice] + +// publicreportPoolImageR is where relationships are stored. +type publicreportPoolImageR struct { + Image *PublicreportImage // publicreport.pool_image.pool_image_image_id_fkey + Pool *PublicreportPool // publicreport.pool_image.pool_image_pool_id_fkey +} + +func buildPublicreportPoolImageColumns(alias string) publicreportPoolImageColumns { + return publicreportPoolImageColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "image_id", "pool_id", + ).WithParent("publicreport.pool_image"), + tableAlias: alias, + ImageID: psql.Quote(alias, "image_id"), + PoolID: psql.Quote(alias, "pool_id"), + } +} + +type publicreportPoolImageColumns struct { + expr.ColumnsExpr + tableAlias string + ImageID psql.Expression + PoolID psql.Expression +} + +func (c publicreportPoolImageColumns) Alias() string { + return c.tableAlias +} + +func (publicreportPoolImageColumns) AliasedAs(alias string) publicreportPoolImageColumns { + return buildPublicreportPoolImageColumns(alias) +} + +// PublicreportPoolImageSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type PublicreportPoolImageSetter struct { + ImageID omit.Val[int32] `db:"image_id,pk" ` + PoolID omit.Val[int32] `db:"pool_id,pk" ` +} + +func (s PublicreportPoolImageSetter) SetColumns() []string { + vals := make([]string, 0, 2) + if s.ImageID.IsValue() { + vals = append(vals, "image_id") + } + if s.PoolID.IsValue() { + vals = append(vals, "pool_id") + } + return vals +} + +func (s PublicreportPoolImageSetter) Overwrite(t *PublicreportPoolImage) { + if s.ImageID.IsValue() { + t.ImageID = s.ImageID.MustGet() + } + if s.PoolID.IsValue() { + t.PoolID = s.PoolID.MustGet() + } +} + +func (s *PublicreportPoolImageSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return PublicreportPoolImages.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, 2) + if s.ImageID.IsValue() { + vals[0] = psql.Arg(s.ImageID.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.PoolID.IsValue() { + vals[1] = psql.Arg(s.PoolID.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s PublicreportPoolImageSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s PublicreportPoolImageSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 2) + + if s.ImageID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "image_id")...), + psql.Arg(s.ImageID), + }}) + } + + if s.PoolID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "pool_id")...), + psql.Arg(s.PoolID), + }}) + } + + return exprs +} + +// FindPublicreportPoolImage retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindPublicreportPoolImage(ctx context.Context, exec bob.Executor, ImageIDPK int32, PoolIDPK int32, cols ...string) (*PublicreportPoolImage, error) { + if len(cols) == 0 { + return PublicreportPoolImages.Query( + sm.Where(PublicreportPoolImages.Columns.ImageID.EQ(psql.Arg(ImageIDPK))), + sm.Where(PublicreportPoolImages.Columns.PoolID.EQ(psql.Arg(PoolIDPK))), + ).One(ctx, exec) + } + + return PublicreportPoolImages.Query( + sm.Where(PublicreportPoolImages.Columns.ImageID.EQ(psql.Arg(ImageIDPK))), + sm.Where(PublicreportPoolImages.Columns.PoolID.EQ(psql.Arg(PoolIDPK))), + sm.Columns(PublicreportPoolImages.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// PublicreportPoolImageExists checks the presence of a single record by primary key +func PublicreportPoolImageExists(ctx context.Context, exec bob.Executor, ImageIDPK int32, PoolIDPK int32) (bool, error) { + return PublicreportPoolImages.Query( + sm.Where(PublicreportPoolImages.Columns.ImageID.EQ(psql.Arg(ImageIDPK))), + sm.Where(PublicreportPoolImages.Columns.PoolID.EQ(psql.Arg(PoolIDPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after PublicreportPoolImage is retrieved from the database +func (o *PublicreportPoolImage) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = PublicreportPoolImages.AfterSelectHooks.RunHooks(ctx, exec, PublicreportPoolImageSlice{o}) + case bob.QueryTypeInsert: + ctx, err = PublicreportPoolImages.AfterInsertHooks.RunHooks(ctx, exec, PublicreportPoolImageSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = PublicreportPoolImages.AfterUpdateHooks.RunHooks(ctx, exec, PublicreportPoolImageSlice{o}) + case bob.QueryTypeDelete: + ctx, err = PublicreportPoolImages.AfterDeleteHooks.RunHooks(ctx, exec, PublicreportPoolImageSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the PublicreportPoolImage +func (o *PublicreportPoolImage) primaryKeyVals() bob.Expression { + return psql.ArgGroup( + o.ImageID, + o.PoolID, + ) +} + +func (o *PublicreportPoolImage) pkEQ() dialect.Expression { + return psql.Group(psql.Quote("publicreport.pool_image", "image_id"), psql.Quote("publicreport.pool_image", "pool_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 PublicreportPoolImage +func (o *PublicreportPoolImage) Update(ctx context.Context, exec bob.Executor, s *PublicreportPoolImageSetter) error { + v, err := PublicreportPoolImages.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 PublicreportPoolImage record with an executor +func (o *PublicreportPoolImage) Delete(ctx context.Context, exec bob.Executor) error { + _, err := PublicreportPoolImages.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the PublicreportPoolImage using the executor +func (o *PublicreportPoolImage) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := PublicreportPoolImages.Query( + sm.Where(PublicreportPoolImages.Columns.ImageID.EQ(psql.Arg(o.ImageID))), + sm.Where(PublicreportPoolImages.Columns.PoolID.EQ(psql.Arg(o.PoolID))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after PublicreportPoolImageSlice is retrieved from the database +func (o PublicreportPoolImageSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = PublicreportPoolImages.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = PublicreportPoolImages.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = PublicreportPoolImages.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = PublicreportPoolImages.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o PublicreportPoolImageSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Group(psql.Quote("publicreport.pool_image", "image_id"), psql.Quote("publicreport.pool_image", "pool_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 PublicreportPoolImageSlice) copyMatchingRows(from ...*PublicreportPoolImage) { + for i, old := range o { + for _, new := range from { + if new.ImageID != old.ImageID { + continue + } + if new.PoolID != old.PoolID { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o PublicreportPoolImageSlice) 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 PublicreportPoolImages.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 *PublicreportPoolImage: + o.copyMatchingRows(retrieved) + case []*PublicreportPoolImage: + o.copyMatchingRows(retrieved...) + case PublicreportPoolImageSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a PublicreportPoolImage or a slice of PublicreportPoolImage + // then run the AfterUpdateHooks on the slice + _, err = PublicreportPoolImages.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o PublicreportPoolImageSlice) 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 PublicreportPoolImages.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 *PublicreportPoolImage: + o.copyMatchingRows(retrieved) + case []*PublicreportPoolImage: + o.copyMatchingRows(retrieved...) + case PublicreportPoolImageSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a PublicreportPoolImage or a slice of PublicreportPoolImage + // then run the AfterDeleteHooks on the slice + _, err = PublicreportPoolImages.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o PublicreportPoolImageSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals PublicreportPoolImageSetter) error { + if len(o) == 0 { + return nil + } + + _, err := PublicreportPoolImages.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o PublicreportPoolImageSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := PublicreportPoolImages.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o PublicreportPoolImageSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := PublicreportPoolImages.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// Image starts a query for related objects on publicreport.image +func (o *PublicreportPoolImage) Image(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { + return PublicreportImages.Query(append(mods, + sm.Where(PublicreportImages.Columns.ID.EQ(psql.Arg(o.ImageID))), + )...) +} + +func (os PublicreportPoolImageSlice) Image(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { + pkImageID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkImageID = append(pkImageID, o.ImageID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkImageID), "integer[]")), + )) + + return PublicreportImages.Query(append(mods, + sm.Where(psql.Group(PublicreportImages.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + +// Pool starts a query for related objects on publicreport.pool +func (o *PublicreportPoolImage) Pool(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportPoolsQuery { + return PublicreportPools.Query(append(mods, + sm.Where(PublicreportPools.Columns.ID.EQ(psql.Arg(o.PoolID))), + )...) +} + +func (os PublicreportPoolImageSlice) 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 attachPublicreportPoolImageImage0(ctx context.Context, exec bob.Executor, count int, publicreportPoolImage0 *PublicreportPoolImage, publicreportImage1 *PublicreportImage) (*PublicreportPoolImage, error) { + setter := &PublicreportPoolImageSetter{ + ImageID: omit.From(publicreportImage1.ID), + } + + err := publicreportPoolImage0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachPublicreportPoolImageImage0: %w", err) + } + + return publicreportPoolImage0, nil +} + +func (publicreportPoolImage0 *PublicreportPoolImage) InsertImage(ctx context.Context, exec bob.Executor, related *PublicreportImageSetter) error { + var err error + + publicreportImage1, err := PublicreportImages.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachPublicreportPoolImageImage0(ctx, exec, 1, publicreportPoolImage0, publicreportImage1) + if err != nil { + return err + } + + publicreportPoolImage0.R.Image = publicreportImage1 + + return nil +} + +func (publicreportPoolImage0 *PublicreportPoolImage) AttachImage(ctx context.Context, exec bob.Executor, publicreportImage1 *PublicreportImage) error { + var err error + + _, err = attachPublicreportPoolImageImage0(ctx, exec, 1, publicreportPoolImage0, publicreportImage1) + if err != nil { + return err + } + + publicreportPoolImage0.R.Image = publicreportImage1 + + return nil +} + +func attachPublicreportPoolImagePool0(ctx context.Context, exec bob.Executor, count int, publicreportPoolImage0 *PublicreportPoolImage, publicreportPool1 *PublicreportPool) (*PublicreportPoolImage, error) { + setter := &PublicreportPoolImageSetter{ + PoolID: omit.From(publicreportPool1.ID), + } + + err := publicreportPoolImage0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachPublicreportPoolImagePool0: %w", err) + } + + return publicreportPoolImage0, nil +} + +func (publicreportPoolImage0 *PublicreportPoolImage) 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 = attachPublicreportPoolImagePool0(ctx, exec, 1, publicreportPoolImage0, publicreportPool1) + if err != nil { + return err + } + + publicreportPoolImage0.R.Pool = publicreportPool1 + + return nil +} + +func (publicreportPoolImage0 *PublicreportPoolImage) AttachPool(ctx context.Context, exec bob.Executor, publicreportPool1 *PublicreportPool) error { + var err error + + _, err = attachPublicreportPoolImagePool0(ctx, exec, 1, publicreportPoolImage0, publicreportPool1) + if err != nil { + return err + } + + publicreportPoolImage0.R.Pool = publicreportPool1 + + return nil +} + +type publicreportPoolImageWhere[Q psql.Filterable] struct { + ImageID psql.WhereMod[Q, int32] + PoolID psql.WhereMod[Q, int32] +} + +func (publicreportPoolImageWhere[Q]) AliasedAs(alias string) publicreportPoolImageWhere[Q] { + return buildPublicreportPoolImageWhere[Q](buildPublicreportPoolImageColumns(alias)) +} + +func buildPublicreportPoolImageWhere[Q psql.Filterable](cols publicreportPoolImageColumns) publicreportPoolImageWhere[Q] { + return publicreportPoolImageWhere[Q]{ + ImageID: psql.Where[Q, int32](cols.ImageID), + PoolID: psql.Where[Q, int32](cols.PoolID), + } +} + +func (o *PublicreportPoolImage) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "Image": + rel, ok := retrieved.(*PublicreportImage) + if !ok { + return fmt.Errorf("publicreportPoolImage cannot load %T as %q", retrieved, name) + } + + o.R.Image = rel + + return nil + case "Pool": + rel, ok := retrieved.(*PublicreportPool) + if !ok { + return fmt.Errorf("publicreportPoolImage cannot load %T as %q", retrieved, name) + } + + o.R.Pool = rel + + return nil + default: + return fmt.Errorf("publicreportPoolImage has no relationship %q", name) + } +} + +type publicreportPoolImagePreloader struct { + Image func(...psql.PreloadOption) psql.Preloader + Pool func(...psql.PreloadOption) psql.Preloader +} + +func buildPublicreportPoolImagePreloader() publicreportPoolImagePreloader { + return publicreportPoolImagePreloader{ + Image: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*PublicreportImage, PublicreportImageSlice](psql.PreloadRel{ + Name: "Image", + Sides: []psql.PreloadSide{ + { + From: PublicreportPoolImages, + To: PublicreportImages, + FromColumns: []string{"image_id"}, + ToColumns: []string{"id"}, + }, + }, + }, PublicreportImages.Columns.Names(), opts...) + }, + Pool: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*PublicreportPool, PublicreportPoolSlice](psql.PreloadRel{ + Name: "Pool", + Sides: []psql.PreloadSide{ + { + From: PublicreportPoolImages, + To: PublicreportPools, + FromColumns: []string{"pool_id"}, + ToColumns: []string{"id"}, + }, + }, + }, PublicreportPools.Columns.Names(), opts...) + }, + } +} + +type publicreportPoolImageThenLoader[Q orm.Loadable] struct { + Image func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Pool func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildPublicreportPoolImageThenLoader[Q orm.Loadable]() publicreportPoolImageThenLoader[Q] { + type ImageLoadInterface interface { + LoadImage(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PoolLoadInterface interface { + LoadPool(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return publicreportPoolImageThenLoader[Q]{ + Image: thenLoadBuilder[Q]( + "Image", + func(ctx context.Context, exec bob.Executor, retrieved ImageLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadImage(ctx, exec, mods...) + }, + ), + 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...) + }, + ), + } +} + +// LoadImage loads the publicreportPoolImage's Image into the .R struct +func (o *PublicreportPoolImage) LoadImage(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Image = nil + + related, err := o.Image(mods...).One(ctx, exec) + if err != nil { + return err + } + + o.R.Image = related + return nil +} + +// LoadImage loads the publicreportPoolImage's Image into the .R struct +func (os PublicreportPoolImageSlice) LoadImage(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + publicreportImages, err := os.Image(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range publicreportImages { + + if !(o.ImageID == rel.ID) { + continue + } + + o.R.Image = rel + break + } + } + + return nil +} + +// LoadPool loads the publicreportPoolImage's Pool into the .R struct +func (o *PublicreportPoolImage) 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 + } + + o.R.Pool = related + return nil +} + +// LoadPool loads the publicreportPoolImage's Pool into the .R struct +func (os PublicreportPoolImageSlice) 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 + } + + o.R.Pool = rel + break + } + } + + return nil +} + +type publicreportPoolImageJoins[Q dialect.Joinable] struct { + typ string + Image modAs[Q, publicreportImageColumns] + Pool modAs[Q, publicreportPoolColumns] +} + +func (j publicreportPoolImageJoins[Q]) aliasedAs(alias string) publicreportPoolImageJoins[Q] { + return buildPublicreportPoolImageJoins[Q](buildPublicreportPoolImageColumns(alias), j.typ) +} + +func buildPublicreportPoolImageJoins[Q dialect.Joinable](cols publicreportPoolImageColumns, typ string) publicreportPoolImageJoins[Q] { + return publicreportPoolImageJoins[Q]{ + typ: typ, + Image: modAs[Q, publicreportImageColumns]{ + c: PublicreportImages.Columns, + f: func(to publicreportImageColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, PublicreportImages.Name().As(to.Alias())).On( + to.ID.EQ(cols.ImageID), + )) + } + + return mods + }, + }, + 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 + }, + }, + } +} diff --git a/db/models/publicreport.pool_photo.bob.go b/db/models/publicreport.pool_photo.bob.go deleted file mode 100644 index 1240a0e3..00000000 --- a/db/models/publicreport.pool_photo.bob.go +++ /dev/null @@ -1,678 +0,0 @@ -// Code generated by BobGen psql v0.42.1. 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 - }, - }, - } -} diff --git a/db/models/publicreport.quick.bob.go b/db/models/publicreport.quick.bob.go index 5cec372c..c6514bb6 100644 --- a/db/models/publicreport.quick.bob.go +++ b/db/models/publicreport.quick.bob.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "io" + "strconv" "time" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" @@ -23,6 +24,7 @@ import ( "github.com/stephenafamo/bob/mods" "github.com/stephenafamo/bob/orm" "github.com/stephenafamo/bob/types/pgtypes" + "github.com/stephenafamo/scan" ) // PublicreportQuick is an object representing the database table. @@ -55,7 +57,7 @@ type PublicreportQuicksQuery = *psql.ViewQuery[*PublicreportQuick, PublicreportQ // publicreportQuickR is where relationships are stored. type publicreportQuickR struct { - QuickPhotos PublicreportQuickPhotoSlice // publicreport.quick_photo.quick_photo_quick_id_fkey + Images PublicreportImageSlice // publicreport.quick_image.quick_image_image_id_fkeypublicreport.quick_image.quick_image_quick_id_fkey } func buildPublicreportQuickColumns(alias string) publicreportQuickColumns { @@ -558,14 +560,16 @@ func (o PublicreportQuickSlice) ReloadAll(ctx context.Context, exec bob.Executor return nil } -// QuickPhotos starts a query for related objects on publicreport.quick_photo -func (o *PublicreportQuick) QuickPhotos(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportQuickPhotosQuery { - return PublicreportQuickPhotos.Query(append(mods, - sm.Where(PublicreportQuickPhotos.Columns.QuickID.EQ(psql.Arg(o.ID))), +// Images starts a query for related objects on publicreport.image +func (o *PublicreportQuick) Images(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { + return PublicreportImages.Query(append(mods, + sm.InnerJoin(PublicreportQuickImages.NameAs()).On( + PublicreportImages.Columns.ID.EQ(PublicreportQuickImages.Columns.ImageID)), + sm.Where(PublicreportQuickImages.Columns.QuickID.EQ(psql.Arg(o.ID))), )...) } -func (os PublicreportQuickSlice) QuickPhotos(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportQuickPhotosQuery { +func (os PublicreportQuickSlice) Images(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { pkID := make(pgtypes.Array[int32], 0, len(os)) for _, o := range os { if o == nil { @@ -577,74 +581,74 @@ func (os PublicreportQuickSlice) QuickPhotos(mods ...bob.Mod[*dialect.SelectQuer psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), )) - return PublicreportQuickPhotos.Query(append(mods, - sm.Where(psql.Group(PublicreportQuickPhotos.Columns.QuickID).OP("IN", PKArgExpr)), + return PublicreportImages.Query(append(mods, + sm.InnerJoin(PublicreportQuickImages.NameAs()).On( + PublicreportImages.Columns.ID.EQ(PublicreportQuickImages.Columns.ImageID), + ), + sm.Where(psql.Group(PublicreportQuickImages.Columns.QuickID).OP("IN", PKArgExpr)), )...) } -func insertPublicreportQuickQuickPhotos0(ctx context.Context, exec bob.Executor, publicreportQuickPhotos1 []*PublicreportQuickPhotoSetter, publicreportQuick0 *PublicreportQuick) (PublicreportQuickPhotoSlice, error) { - for i := range publicreportQuickPhotos1 { - publicreportQuickPhotos1[i].QuickID = omit.From(publicreportQuick0.ID) +func attachPublicreportQuickImages0(ctx context.Context, exec bob.Executor, count int, publicreportQuick0 *PublicreportQuick, publicreportImages2 PublicreportImageSlice) (PublicreportQuickImageSlice, error) { + setters := make([]*PublicreportQuickImageSetter, count) + for i := range count { + setters[i] = &PublicreportQuickImageSetter{ + QuickID: omit.From(publicreportQuick0.ID), + ImageID: omit.From(publicreportImages2[i].ID), + } } - ret, err := PublicreportQuickPhotos.Insert(bob.ToMods(publicreportQuickPhotos1...)).All(ctx, exec) + publicreportQuickImages1, err := PublicreportQuickImages.Insert(bob.ToMods(setters...)).All(ctx, exec) if err != nil { - return ret, fmt.Errorf("insertPublicreportQuickQuickPhotos0: %w", err) + return nil, fmt.Errorf("attachPublicreportQuickImages0: %w", err) } - return ret, nil + return publicreportQuickImages1, nil } -func attachPublicreportQuickQuickPhotos0(ctx context.Context, exec bob.Executor, count int, publicreportQuickPhotos1 PublicreportQuickPhotoSlice, publicreportQuick0 *PublicreportQuick) (PublicreportQuickPhotoSlice, error) { - setter := &PublicreportQuickPhotoSetter{ - QuickID: omit.From(publicreportQuick0.ID), - } - - err := publicreportQuickPhotos1.UpdateAll(ctx, exec, *setter) - if err != nil { - return nil, fmt.Errorf("attachPublicreportQuickQuickPhotos0: %w", err) - } - - return publicreportQuickPhotos1, nil -} - -func (publicreportQuick0 *PublicreportQuick) InsertQuickPhotos(ctx context.Context, exec bob.Executor, related ...*PublicreportQuickPhotoSetter) error { +func (publicreportQuick0 *PublicreportQuick) InsertImages(ctx context.Context, exec bob.Executor, related ...*PublicreportImageSetter) error { if len(related) == 0 { return nil } var err error - publicreportQuickPhotos1, err := insertPublicreportQuickQuickPhotos0(ctx, exec, related, publicreportQuick0) + inserted, err := PublicreportImages.Insert(bob.ToMods(related...)).All(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + publicreportImages2 := PublicreportImageSlice(inserted) + + _, err = attachPublicreportQuickImages0(ctx, exec, len(related), publicreportQuick0, publicreportImages2) if err != nil { return err } - publicreportQuick0.R.QuickPhotos = append(publicreportQuick0.R.QuickPhotos, publicreportQuickPhotos1...) + publicreportQuick0.R.Images = append(publicreportQuick0.R.Images, publicreportImages2...) - for _, rel := range publicreportQuickPhotos1 { - rel.R.Quick = publicreportQuick0 + for _, rel := range publicreportImages2 { + rel.R.Quicks = append(rel.R.Quicks, publicreportQuick0) } return nil } -func (publicreportQuick0 *PublicreportQuick) AttachQuickPhotos(ctx context.Context, exec bob.Executor, related ...*PublicreportQuickPhoto) error { +func (publicreportQuick0 *PublicreportQuick) AttachImages(ctx context.Context, exec bob.Executor, related ...*PublicreportImage) error { if len(related) == 0 { return nil } var err error - publicreportQuickPhotos1 := PublicreportQuickPhotoSlice(related) + publicreportImages2 := PublicreportImageSlice(related) - _, err = attachPublicreportQuickQuickPhotos0(ctx, exec, len(related), publicreportQuickPhotos1, publicreportQuick0) + _, err = attachPublicreportQuickImages0(ctx, exec, len(related), publicreportQuick0, publicreportImages2) if err != nil { return err } - publicreportQuick0.R.QuickPhotos = append(publicreportQuick0.R.QuickPhotos, publicreportQuickPhotos1...) + publicreportQuick0.R.Images = append(publicreportQuick0.R.Images, publicreportImages2...) for _, rel := range related { - rel.R.Quick = publicreportQuick0 + rel.R.Quicks = append(rel.R.Quicks, publicreportQuick0) } return nil @@ -688,17 +692,17 @@ func (o *PublicreportQuick) Preload(name string, retrieved any) error { } switch name { - case "QuickPhotos": - rels, ok := retrieved.(PublicreportQuickPhotoSlice) + case "Images": + rels, ok := retrieved.(PublicreportImageSlice) if !ok { return fmt.Errorf("publicreportQuick cannot load %T as %q", retrieved, name) } - o.R.QuickPhotos = rels + o.R.Images = rels for _, rel := range rels { if rel != nil { - rel.R.Quick = o + rel.R.Quicks = PublicreportQuickSlice{o} } } return nil @@ -714,79 +718,99 @@ func buildPublicreportQuickPreloader() publicreportQuickPreloader { } type publicreportQuickThenLoader[Q orm.Loadable] struct { - QuickPhotos func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Images func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildPublicreportQuickThenLoader[Q orm.Loadable]() publicreportQuickThenLoader[Q] { - type QuickPhotosLoadInterface interface { - LoadQuickPhotos(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type ImagesLoadInterface interface { + LoadImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } return publicreportQuickThenLoader[Q]{ - QuickPhotos: thenLoadBuilder[Q]( - "QuickPhotos", - func(ctx context.Context, exec bob.Executor, retrieved QuickPhotosLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadQuickPhotos(ctx, exec, mods...) + Images: thenLoadBuilder[Q]( + "Images", + func(ctx context.Context, exec bob.Executor, retrieved ImagesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadImages(ctx, exec, mods...) }, ), } } -// LoadQuickPhotos loads the publicreportQuick's QuickPhotos into the .R struct -func (o *PublicreportQuick) LoadQuickPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadImages loads the publicreportQuick's Images into the .R struct +func (o *PublicreportQuick) LoadImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } // Reset the relationship - o.R.QuickPhotos = nil + o.R.Images = nil - related, err := o.QuickPhotos(mods...).All(ctx, exec) + related, err := o.Images(mods...).All(ctx, exec) if err != nil { return err } for _, rel := range related { - rel.R.Quick = o + rel.R.Quicks = PublicreportQuickSlice{o} } - o.R.QuickPhotos = related + o.R.Images = related return nil } -// LoadQuickPhotos loads the publicreportQuick's QuickPhotos into the .R struct -func (os PublicreportQuickSlice) LoadQuickPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadImages loads the publicreportQuick's Images into the .R struct +func (os PublicreportQuickSlice) LoadImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } - publicreportQuickPhotos, err := os.QuickPhotos(mods...).All(ctx, exec) + // since we are changing the columns, we need to check if the original columns were set or add the defaults + sq := dialect.SelectQuery{} + for _, mod := range mods { + mod.Apply(&sq) + } + + if len(sq.SelectList.Columns) == 0 { + mods = append(mods, sm.Columns(PublicreportImages.Columns)) + } + + q := os.Images(append( + mods, + sm.Columns(PublicreportQuickImages.Columns.QuickID.As("related_publicreport.quick.ID")), + )...) + + IDSlice := []int32{} + + mapper := scan.Mod(scan.StructMapper[*PublicreportImage](), func(ctx context.Context, cols []string) (scan.BeforeFunc, func(any, any) error) { + return func(row *scan.Row) (any, error) { + IDSlice = append(IDSlice, *new(int32)) + row.ScheduleScanByName("related_publicreport.quick.ID", &IDSlice[len(IDSlice)-1]) + + return nil, nil + }, + func(any, any) error { + return nil + } + }) + + publicreportImages, err := bob.Allx[bob.SliceTransformer[*PublicreportImage, PublicreportImageSlice]](ctx, exec, q, mapper) if err != nil { return err } for _, o := range os { - if o == nil { - continue - } - - o.R.QuickPhotos = nil + o.R.Images = nil } for _, o := range os { - if o == nil { - continue - } - - for _, rel := range publicreportQuickPhotos { - - if !(o.ID == rel.QuickID) { + for i, rel := range publicreportImages { + if !(o.ID == IDSlice[i]) { continue } - rel.R.Quick = o + rel.R.Quicks = append(rel.R.Quicks, o) - o.R.QuickPhotos = append(o.R.QuickPhotos, rel) + o.R.Images = append(o.R.Images, rel) } } @@ -795,7 +819,7 @@ func (os PublicreportQuickSlice) LoadQuickPhotos(ctx context.Context, exec bob.E // publicreportQuickC is where relationship counts are stored. type publicreportQuickC struct { - QuickPhotos *int64 + Images *int64 } // PreloadCount sets a count in the C struct by name @@ -805,20 +829,20 @@ func (o *PublicreportQuick) PreloadCount(name string, count int64) error { } switch name { - case "QuickPhotos": - o.C.QuickPhotos = &count + case "Images": + o.C.Images = &count } return nil } type publicreportQuickCountPreloader struct { - QuickPhotos func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Images func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader } func buildPublicreportQuickCountPreloader() publicreportQuickCountPreloader { return publicreportQuickCountPreloader{ - QuickPhotos: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { - return countPreloader[*PublicreportQuick]("QuickPhotos", func(parent string) bob.Expression { + Images: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*PublicreportQuick]("Images", func(parent string) bob.Expression { // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) if parent == "" { parent = PublicreportQuicks.Alias() @@ -827,8 +851,11 @@ func buildPublicreportQuickCountPreloader() publicreportQuickCountPreloader { subqueryMods := []bob.Mod[*dialect.SelectQuery]{ sm.Columns(psql.Raw("count(*)")), - sm.From(PublicreportQuickPhotos.Name()), - sm.Where(psql.Quote(PublicreportQuickPhotos.Alias(), "quick_id").EQ(psql.Quote(parent, "id"))), + sm.From(PublicreportQuickImages.Name()), + sm.Where(psql.Quote(PublicreportQuickImages.Alias(), "quick_id").EQ(psql.Quote(parent, "id"))), + sm.InnerJoin(PublicreportImages.Name()).On( + psql.Quote(PublicreportImages.Alias(), "id").EQ(psql.Quote(PublicreportQuickImages.Alias(), "image_id")), + ), } subqueryMods = append(subqueryMods, mods...) return psql.Group(psql.Select(subqueryMods...).Expression) @@ -838,47 +865,47 @@ func buildPublicreportQuickCountPreloader() publicreportQuickCountPreloader { } type publicreportQuickCountThenLoader[Q orm.Loadable] struct { - QuickPhotos func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Images func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildPublicreportQuickCountThenLoader[Q orm.Loadable]() publicreportQuickCountThenLoader[Q] { - type QuickPhotosCountInterface interface { - LoadCountQuickPhotos(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type ImagesCountInterface interface { + LoadCountImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } return publicreportQuickCountThenLoader[Q]{ - QuickPhotos: countThenLoadBuilder[Q]( - "QuickPhotos", - func(ctx context.Context, exec bob.Executor, retrieved QuickPhotosCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadCountQuickPhotos(ctx, exec, mods...) + Images: countThenLoadBuilder[Q]( + "Images", + func(ctx context.Context, exec bob.Executor, retrieved ImagesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountImages(ctx, exec, mods...) }, ), } } -// LoadCountQuickPhotos loads the count of QuickPhotos into the C struct -func (o *PublicreportQuick) LoadCountQuickPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadCountImages loads the count of Images into the C struct +func (o *PublicreportQuick) LoadCountImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } - count, err := o.QuickPhotos(mods...).Count(ctx, exec) + count, err := o.Images(mods...).Count(ctx, exec) if err != nil { return err } - o.C.QuickPhotos = &count + o.C.Images = &count return nil } -// LoadCountQuickPhotos loads the count of QuickPhotos for a slice -func (os PublicreportQuickSlice) LoadCountQuickPhotos(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadCountImages loads the count of Images for a slice +func (os PublicreportQuickSlice) LoadCountImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } for _, o := range os { - if err := o.LoadCountQuickPhotos(ctx, exec, mods...); err != nil { + if err := o.LoadCountImages(ctx, exec, mods...); err != nil { return err } } @@ -887,8 +914,8 @@ func (os PublicreportQuickSlice) LoadCountQuickPhotos(ctx context.Context, exec } type publicreportQuickJoins[Q dialect.Joinable] struct { - typ string - QuickPhotos modAs[Q, publicreportQuickPhotoColumns] + typ string + Images modAs[Q, publicreportImageColumns] } func (j publicreportQuickJoins[Q]) aliasedAs(alias string) publicreportQuickJoins[Q] { @@ -898,16 +925,24 @@ func (j publicreportQuickJoins[Q]) aliasedAs(alias string) publicreportQuickJoin func buildPublicreportQuickJoins[Q dialect.Joinable](cols publicreportQuickColumns, typ string) publicreportQuickJoins[Q] { return publicreportQuickJoins[Q]{ typ: typ, - QuickPhotos: modAs[Q, publicreportQuickPhotoColumns]{ - c: PublicreportQuickPhotos.Columns, - f: func(to publicreportQuickPhotoColumns) bob.Mod[Q] { - mods := make(mods.QueryMods[Q], 0, 1) + Images: modAs[Q, publicreportImageColumns]{ + c: PublicreportImages.Columns, + f: func(to publicreportImageColumns) bob.Mod[Q] { + random := strconv.FormatInt(randInt(), 10) + mods := make(mods.QueryMods[Q], 0, 2) { - mods = append(mods, dialect.Join[Q](typ, PublicreportQuickPhotos.Name().As(to.Alias())).On( + to := PublicreportQuickImages.Columns.AliasedAs(PublicreportQuickImages.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, PublicreportQuickImages.Name().As(to.Alias())).On( to.QuickID.EQ(cols.ID), )) } + { + cols := PublicreportQuickImages.Columns.AliasedAs(PublicreportQuickImages.Columns.Alias() + random) + mods = append(mods, dialect.Join[Q](typ, PublicreportImages.Name().As(to.Alias())).On( + to.ID.EQ(cols.ImageID), + )) + } return mods }, diff --git a/db/models/publicreport.quick_image.bob.go b/db/models/publicreport.quick_image.bob.go new file mode 100644 index 00000000..9d78ab8c --- /dev/null +++ b/db/models/publicreport.quick_image.bob.go @@ -0,0 +1,766 @@ +// Code generated by BobGen psql v0.42.1. 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/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" +) + +// PublicreportQuickImage is an object representing the database table. +type PublicreportQuickImage struct { + ImageID int32 `db:"image_id,pk" ` + QuickID int32 `db:"quick_id,pk" ` + + R publicreportQuickImageR `db:"-" ` +} + +// PublicreportQuickImageSlice is an alias for a slice of pointers to PublicreportQuickImage. +// This should almost always be used instead of []*PublicreportQuickImage. +type PublicreportQuickImageSlice []*PublicreportQuickImage + +// PublicreportQuickImages contains methods to work with the quick_image table +var PublicreportQuickImages = psql.NewTablex[*PublicreportQuickImage, PublicreportQuickImageSlice, *PublicreportQuickImageSetter]("publicreport", "quick_image", buildPublicreportQuickImageColumns("publicreport.quick_image")) + +// PublicreportQuickImagesQuery is a query on the quick_image table +type PublicreportQuickImagesQuery = *psql.ViewQuery[*PublicreportQuickImage, PublicreportQuickImageSlice] + +// publicreportQuickImageR is where relationships are stored. +type publicreportQuickImageR struct { + Image *PublicreportImage // publicreport.quick_image.quick_image_image_id_fkey + Quick *PublicreportQuick // publicreport.quick_image.quick_image_quick_id_fkey +} + +func buildPublicreportQuickImageColumns(alias string) publicreportQuickImageColumns { + return publicreportQuickImageColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "image_id", "quick_id", + ).WithParent("publicreport.quick_image"), + tableAlias: alias, + ImageID: psql.Quote(alias, "image_id"), + QuickID: psql.Quote(alias, "quick_id"), + } +} + +type publicreportQuickImageColumns struct { + expr.ColumnsExpr + tableAlias string + ImageID psql.Expression + QuickID psql.Expression +} + +func (c publicreportQuickImageColumns) Alias() string { + return c.tableAlias +} + +func (publicreportQuickImageColumns) AliasedAs(alias string) publicreportQuickImageColumns { + return buildPublicreportQuickImageColumns(alias) +} + +// PublicreportQuickImageSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type PublicreportQuickImageSetter struct { + ImageID omit.Val[int32] `db:"image_id,pk" ` + QuickID omit.Val[int32] `db:"quick_id,pk" ` +} + +func (s PublicreportQuickImageSetter) SetColumns() []string { + vals := make([]string, 0, 2) + if s.ImageID.IsValue() { + vals = append(vals, "image_id") + } + if s.QuickID.IsValue() { + vals = append(vals, "quick_id") + } + return vals +} + +func (s PublicreportQuickImageSetter) Overwrite(t *PublicreportQuickImage) { + if s.ImageID.IsValue() { + t.ImageID = s.ImageID.MustGet() + } + if s.QuickID.IsValue() { + t.QuickID = s.QuickID.MustGet() + } +} + +func (s *PublicreportQuickImageSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return PublicreportQuickImages.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, 2) + if s.ImageID.IsValue() { + vals[0] = psql.Arg(s.ImageID.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.QuickID.IsValue() { + vals[1] = psql.Arg(s.QuickID.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s PublicreportQuickImageSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s PublicreportQuickImageSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 2) + + if s.ImageID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "image_id")...), + psql.Arg(s.ImageID), + }}) + } + + if s.QuickID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "quick_id")...), + psql.Arg(s.QuickID), + }}) + } + + return exprs +} + +// FindPublicreportQuickImage retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindPublicreportQuickImage(ctx context.Context, exec bob.Executor, ImageIDPK int32, QuickIDPK int32, cols ...string) (*PublicreportQuickImage, error) { + if len(cols) == 0 { + return PublicreportQuickImages.Query( + sm.Where(PublicreportQuickImages.Columns.ImageID.EQ(psql.Arg(ImageIDPK))), + sm.Where(PublicreportQuickImages.Columns.QuickID.EQ(psql.Arg(QuickIDPK))), + ).One(ctx, exec) + } + + return PublicreportQuickImages.Query( + sm.Where(PublicreportQuickImages.Columns.ImageID.EQ(psql.Arg(ImageIDPK))), + sm.Where(PublicreportQuickImages.Columns.QuickID.EQ(psql.Arg(QuickIDPK))), + sm.Columns(PublicreportQuickImages.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// PublicreportQuickImageExists checks the presence of a single record by primary key +func PublicreportQuickImageExists(ctx context.Context, exec bob.Executor, ImageIDPK int32, QuickIDPK int32) (bool, error) { + return PublicreportQuickImages.Query( + sm.Where(PublicreportQuickImages.Columns.ImageID.EQ(psql.Arg(ImageIDPK))), + sm.Where(PublicreportQuickImages.Columns.QuickID.EQ(psql.Arg(QuickIDPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after PublicreportQuickImage is retrieved from the database +func (o *PublicreportQuickImage) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = PublicreportQuickImages.AfterSelectHooks.RunHooks(ctx, exec, PublicreportQuickImageSlice{o}) + case bob.QueryTypeInsert: + ctx, err = PublicreportQuickImages.AfterInsertHooks.RunHooks(ctx, exec, PublicreportQuickImageSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = PublicreportQuickImages.AfterUpdateHooks.RunHooks(ctx, exec, PublicreportQuickImageSlice{o}) + case bob.QueryTypeDelete: + ctx, err = PublicreportQuickImages.AfterDeleteHooks.RunHooks(ctx, exec, PublicreportQuickImageSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the PublicreportQuickImage +func (o *PublicreportQuickImage) primaryKeyVals() bob.Expression { + return psql.ArgGroup( + o.ImageID, + o.QuickID, + ) +} + +func (o *PublicreportQuickImage) pkEQ() dialect.Expression { + return psql.Group(psql.Quote("publicreport.quick_image", "image_id"), psql.Quote("publicreport.quick_image", "quick_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 PublicreportQuickImage +func (o *PublicreportQuickImage) Update(ctx context.Context, exec bob.Executor, s *PublicreportQuickImageSetter) error { + v, err := PublicreportQuickImages.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 PublicreportQuickImage record with an executor +func (o *PublicreportQuickImage) Delete(ctx context.Context, exec bob.Executor) error { + _, err := PublicreportQuickImages.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the PublicreportQuickImage using the executor +func (o *PublicreportQuickImage) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := PublicreportQuickImages.Query( + sm.Where(PublicreportQuickImages.Columns.ImageID.EQ(psql.Arg(o.ImageID))), + sm.Where(PublicreportQuickImages.Columns.QuickID.EQ(psql.Arg(o.QuickID))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after PublicreportQuickImageSlice is retrieved from the database +func (o PublicreportQuickImageSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = PublicreportQuickImages.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = PublicreportQuickImages.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = PublicreportQuickImages.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = PublicreportQuickImages.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o PublicreportQuickImageSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Group(psql.Quote("publicreport.quick_image", "image_id"), psql.Quote("publicreport.quick_image", "quick_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 PublicreportQuickImageSlice) copyMatchingRows(from ...*PublicreportQuickImage) { + for i, old := range o { + for _, new := range from { + if new.ImageID != old.ImageID { + continue + } + if new.QuickID != old.QuickID { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o PublicreportQuickImageSlice) 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 PublicreportQuickImages.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 *PublicreportQuickImage: + o.copyMatchingRows(retrieved) + case []*PublicreportQuickImage: + o.copyMatchingRows(retrieved...) + case PublicreportQuickImageSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a PublicreportQuickImage or a slice of PublicreportQuickImage + // then run the AfterUpdateHooks on the slice + _, err = PublicreportQuickImages.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o PublicreportQuickImageSlice) 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 PublicreportQuickImages.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 *PublicreportQuickImage: + o.copyMatchingRows(retrieved) + case []*PublicreportQuickImage: + o.copyMatchingRows(retrieved...) + case PublicreportQuickImageSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a PublicreportQuickImage or a slice of PublicreportQuickImage + // then run the AfterDeleteHooks on the slice + _, err = PublicreportQuickImages.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o PublicreportQuickImageSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals PublicreportQuickImageSetter) error { + if len(o) == 0 { + return nil + } + + _, err := PublicreportQuickImages.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o PublicreportQuickImageSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := PublicreportQuickImages.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o PublicreportQuickImageSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := PublicreportQuickImages.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// Image starts a query for related objects on publicreport.image +func (o *PublicreportQuickImage) Image(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { + return PublicreportImages.Query(append(mods, + sm.Where(PublicreportImages.Columns.ID.EQ(psql.Arg(o.ImageID))), + )...) +} + +func (os PublicreportQuickImageSlice) Image(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { + pkImageID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkImageID = append(pkImageID, o.ImageID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkImageID), "integer[]")), + )) + + return PublicreportImages.Query(append(mods, + sm.Where(psql.Group(PublicreportImages.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + +// Quick starts a query for related objects on publicreport.quick +func (o *PublicreportQuickImage) Quick(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportQuicksQuery { + return PublicreportQuicks.Query(append(mods, + sm.Where(PublicreportQuicks.Columns.ID.EQ(psql.Arg(o.QuickID))), + )...) +} + +func (os PublicreportQuickImageSlice) Quick(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportQuicksQuery { + pkQuickID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkQuickID = append(pkQuickID, o.QuickID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkQuickID), "integer[]")), + )) + + return PublicreportQuicks.Query(append(mods, + sm.Where(psql.Group(PublicreportQuicks.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + +func attachPublicreportQuickImageImage0(ctx context.Context, exec bob.Executor, count int, publicreportQuickImage0 *PublicreportQuickImage, publicreportImage1 *PublicreportImage) (*PublicreportQuickImage, error) { + setter := &PublicreportQuickImageSetter{ + ImageID: omit.From(publicreportImage1.ID), + } + + err := publicreportQuickImage0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachPublicreportQuickImageImage0: %w", err) + } + + return publicreportQuickImage0, nil +} + +func (publicreportQuickImage0 *PublicreportQuickImage) InsertImage(ctx context.Context, exec bob.Executor, related *PublicreportImageSetter) error { + var err error + + publicreportImage1, err := PublicreportImages.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachPublicreportQuickImageImage0(ctx, exec, 1, publicreportQuickImage0, publicreportImage1) + if err != nil { + return err + } + + publicreportQuickImage0.R.Image = publicreportImage1 + + return nil +} + +func (publicreportQuickImage0 *PublicreportQuickImage) AttachImage(ctx context.Context, exec bob.Executor, publicreportImage1 *PublicreportImage) error { + var err error + + _, err = attachPublicreportQuickImageImage0(ctx, exec, 1, publicreportQuickImage0, publicreportImage1) + if err != nil { + return err + } + + publicreportQuickImage0.R.Image = publicreportImage1 + + return nil +} + +func attachPublicreportQuickImageQuick0(ctx context.Context, exec bob.Executor, count int, publicreportQuickImage0 *PublicreportQuickImage, publicreportQuick1 *PublicreportQuick) (*PublicreportQuickImage, error) { + setter := &PublicreportQuickImageSetter{ + QuickID: omit.From(publicreportQuick1.ID), + } + + err := publicreportQuickImage0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachPublicreportQuickImageQuick0: %w", err) + } + + return publicreportQuickImage0, nil +} + +func (publicreportQuickImage0 *PublicreportQuickImage) InsertQuick(ctx context.Context, exec bob.Executor, related *PublicreportQuickSetter) error { + var err error + + publicreportQuick1, err := PublicreportQuicks.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachPublicreportQuickImageQuick0(ctx, exec, 1, publicreportQuickImage0, publicreportQuick1) + if err != nil { + return err + } + + publicreportQuickImage0.R.Quick = publicreportQuick1 + + return nil +} + +func (publicreportQuickImage0 *PublicreportQuickImage) AttachQuick(ctx context.Context, exec bob.Executor, publicreportQuick1 *PublicreportQuick) error { + var err error + + _, err = attachPublicreportQuickImageQuick0(ctx, exec, 1, publicreportQuickImage0, publicreportQuick1) + if err != nil { + return err + } + + publicreportQuickImage0.R.Quick = publicreportQuick1 + + return nil +} + +type publicreportQuickImageWhere[Q psql.Filterable] struct { + ImageID psql.WhereMod[Q, int32] + QuickID psql.WhereMod[Q, int32] +} + +func (publicreportQuickImageWhere[Q]) AliasedAs(alias string) publicreportQuickImageWhere[Q] { + return buildPublicreportQuickImageWhere[Q](buildPublicreportQuickImageColumns(alias)) +} + +func buildPublicreportQuickImageWhere[Q psql.Filterable](cols publicreportQuickImageColumns) publicreportQuickImageWhere[Q] { + return publicreportQuickImageWhere[Q]{ + ImageID: psql.Where[Q, int32](cols.ImageID), + QuickID: psql.Where[Q, int32](cols.QuickID), + } +} + +func (o *PublicreportQuickImage) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "Image": + rel, ok := retrieved.(*PublicreportImage) + if !ok { + return fmt.Errorf("publicreportQuickImage cannot load %T as %q", retrieved, name) + } + + o.R.Image = rel + + return nil + case "Quick": + rel, ok := retrieved.(*PublicreportQuick) + if !ok { + return fmt.Errorf("publicreportQuickImage cannot load %T as %q", retrieved, name) + } + + o.R.Quick = rel + + return nil + default: + return fmt.Errorf("publicreportQuickImage has no relationship %q", name) + } +} + +type publicreportQuickImagePreloader struct { + Image func(...psql.PreloadOption) psql.Preloader + Quick func(...psql.PreloadOption) psql.Preloader +} + +func buildPublicreportQuickImagePreloader() publicreportQuickImagePreloader { + return publicreportQuickImagePreloader{ + Image: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*PublicreportImage, PublicreportImageSlice](psql.PreloadRel{ + Name: "Image", + Sides: []psql.PreloadSide{ + { + From: PublicreportQuickImages, + To: PublicreportImages, + FromColumns: []string{"image_id"}, + ToColumns: []string{"id"}, + }, + }, + }, PublicreportImages.Columns.Names(), opts...) + }, + Quick: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*PublicreportQuick, PublicreportQuickSlice](psql.PreloadRel{ + Name: "Quick", + Sides: []psql.PreloadSide{ + { + From: PublicreportQuickImages, + To: PublicreportQuicks, + FromColumns: []string{"quick_id"}, + ToColumns: []string{"id"}, + }, + }, + }, PublicreportQuicks.Columns.Names(), opts...) + }, + } +} + +type publicreportQuickImageThenLoader[Q orm.Loadable] struct { + Image func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Quick func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildPublicreportQuickImageThenLoader[Q orm.Loadable]() publicreportQuickImageThenLoader[Q] { + type ImageLoadInterface interface { + LoadImage(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type QuickLoadInterface interface { + LoadQuick(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return publicreportQuickImageThenLoader[Q]{ + Image: thenLoadBuilder[Q]( + "Image", + func(ctx context.Context, exec bob.Executor, retrieved ImageLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadImage(ctx, exec, mods...) + }, + ), + Quick: thenLoadBuilder[Q]( + "Quick", + func(ctx context.Context, exec bob.Executor, retrieved QuickLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadQuick(ctx, exec, mods...) + }, + ), + } +} + +// LoadImage loads the publicreportQuickImage's Image into the .R struct +func (o *PublicreportQuickImage) LoadImage(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Image = nil + + related, err := o.Image(mods...).One(ctx, exec) + if err != nil { + return err + } + + o.R.Image = related + return nil +} + +// LoadImage loads the publicreportQuickImage's Image into the .R struct +func (os PublicreportQuickImageSlice) LoadImage(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + publicreportImages, err := os.Image(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range publicreportImages { + + if !(o.ImageID == rel.ID) { + continue + } + + o.R.Image = rel + break + } + } + + return nil +} + +// LoadQuick loads the publicreportQuickImage's Quick into the .R struct +func (o *PublicreportQuickImage) LoadQuick(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Quick = nil + + related, err := o.Quick(mods...).One(ctx, exec) + if err != nil { + return err + } + + o.R.Quick = related + return nil +} + +// LoadQuick loads the publicreportQuickImage's Quick into the .R struct +func (os PublicreportQuickImageSlice) LoadQuick(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + publicreportQuicks, err := os.Quick(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range publicreportQuicks { + + if !(o.QuickID == rel.ID) { + continue + } + + o.R.Quick = rel + break + } + } + + return nil +} + +type publicreportQuickImageJoins[Q dialect.Joinable] struct { + typ string + Image modAs[Q, publicreportImageColumns] + Quick modAs[Q, publicreportQuickColumns] +} + +func (j publicreportQuickImageJoins[Q]) aliasedAs(alias string) publicreportQuickImageJoins[Q] { + return buildPublicreportQuickImageJoins[Q](buildPublicreportQuickImageColumns(alias), j.typ) +} + +func buildPublicreportQuickImageJoins[Q dialect.Joinable](cols publicreportQuickImageColumns, typ string) publicreportQuickImageJoins[Q] { + return publicreportQuickImageJoins[Q]{ + typ: typ, + Image: modAs[Q, publicreportImageColumns]{ + c: PublicreportImages.Columns, + f: func(to publicreportImageColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, PublicreportImages.Name().As(to.Alias())).On( + to.ID.EQ(cols.ImageID), + )) + } + + return mods + }, + }, + Quick: modAs[Q, publicreportQuickColumns]{ + c: PublicreportQuicks.Columns, + f: func(to publicreportQuickColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, PublicreportQuicks.Name().As(to.Alias())).On( + to.ID.EQ(cols.QuickID), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/publicreport.quick_photo.bob.go b/db/models/publicreport.quick_photo.bob.go deleted file mode 100644 index 9f5171d4..00000000 --- a/db/models/publicreport.quick_photo.bob.go +++ /dev/null @@ -1,678 +0,0 @@ -// Code generated by BobGen psql v0.42.1. 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" -) - -// PublicreportQuickPhoto is an object representing the database table. -type PublicreportQuickPhoto struct { - ID int32 `db:"id,pk" ` - Size int64 `db:"size" ` - Filename string `db:"filename" ` - QuickID int32 `db:"quick_id" ` - UUID uuid.UUID `db:"uuid" ` - - R publicreportQuickPhotoR `db:"-" ` -} - -// PublicreportQuickPhotoSlice is an alias for a slice of pointers to PublicreportQuickPhoto. -// This should almost always be used instead of []*PublicreportQuickPhoto. -type PublicreportQuickPhotoSlice []*PublicreportQuickPhoto - -// PublicreportQuickPhotos contains methods to work with the quick_photo table -var PublicreportQuickPhotos = psql.NewTablex[*PublicreportQuickPhoto, PublicreportQuickPhotoSlice, *PublicreportQuickPhotoSetter]("publicreport", "quick_photo", buildPublicreportQuickPhotoColumns("publicreport.quick_photo")) - -// PublicreportQuickPhotosQuery is a query on the quick_photo table -type PublicreportQuickPhotosQuery = *psql.ViewQuery[*PublicreportQuickPhoto, PublicreportQuickPhotoSlice] - -// publicreportQuickPhotoR is where relationships are stored. -type publicreportQuickPhotoR struct { - Quick *PublicreportQuick // publicreport.quick_photo.quick_photo_quick_id_fkey -} - -func buildPublicreportQuickPhotoColumns(alias string) publicreportQuickPhotoColumns { - return publicreportQuickPhotoColumns{ - ColumnsExpr: expr.NewColumnsExpr( - "id", "size", "filename", "quick_id", "uuid", - ).WithParent("publicreport.quick_photo"), - tableAlias: alias, - ID: psql.Quote(alias, "id"), - Size: psql.Quote(alias, "size"), - Filename: psql.Quote(alias, "filename"), - QuickID: psql.Quote(alias, "quick_id"), - UUID: psql.Quote(alias, "uuid"), - } -} - -type publicreportQuickPhotoColumns struct { - expr.ColumnsExpr - tableAlias string - ID psql.Expression - Size psql.Expression - Filename psql.Expression - QuickID psql.Expression - UUID psql.Expression -} - -func (c publicreportQuickPhotoColumns) Alias() string { - return c.tableAlias -} - -func (publicreportQuickPhotoColumns) AliasedAs(alias string) publicreportQuickPhotoColumns { - return buildPublicreportQuickPhotoColumns(alias) -} - -// PublicreportQuickPhotoSetter is used for insert/upsert/update operations -// All values are optional, and do not have to be set -// Generated columns are not included -type PublicreportQuickPhotoSetter struct { - ID omit.Val[int32] `db:"id,pk" ` - Size omit.Val[int64] `db:"size" ` - Filename omit.Val[string] `db:"filename" ` - QuickID omit.Val[int32] `db:"quick_id" ` - UUID omit.Val[uuid.UUID] `db:"uuid" ` -} - -func (s PublicreportQuickPhotoSetter) 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.QuickID.IsValue() { - vals = append(vals, "quick_id") - } - if s.UUID.IsValue() { - vals = append(vals, "uuid") - } - return vals -} - -func (s PublicreportQuickPhotoSetter) Overwrite(t *PublicreportQuickPhoto) { - 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.QuickID.IsValue() { - t.QuickID = s.QuickID.MustGet() - } - if s.UUID.IsValue() { - t.UUID = s.UUID.MustGet() - } -} - -func (s *PublicreportQuickPhotoSetter) Apply(q *dialect.InsertQuery) { - q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { - return PublicreportQuickPhotos.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.QuickID.IsValue() { - vals[3] = psql.Arg(s.QuickID.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 PublicreportQuickPhotoSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { - return um.Set(s.Expressions()...) -} - -func (s PublicreportQuickPhotoSetter) 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.QuickID.IsValue() { - exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ - psql.Quote(append(prefix, "quick_id")...), - psql.Arg(s.QuickID), - }}) - } - - if s.UUID.IsValue() { - exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ - psql.Quote(append(prefix, "uuid")...), - psql.Arg(s.UUID), - }}) - } - - return exprs -} - -// FindPublicreportQuickPhoto retrieves a single record by primary key -// If cols is empty Find will return all columns. -func FindPublicreportQuickPhoto(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*PublicreportQuickPhoto, error) { - if len(cols) == 0 { - return PublicreportQuickPhotos.Query( - sm.Where(PublicreportQuickPhotos.Columns.ID.EQ(psql.Arg(IDPK))), - ).One(ctx, exec) - } - - return PublicreportQuickPhotos.Query( - sm.Where(PublicreportQuickPhotos.Columns.ID.EQ(psql.Arg(IDPK))), - sm.Columns(PublicreportQuickPhotos.Columns.Only(cols...)), - ).One(ctx, exec) -} - -// PublicreportQuickPhotoExists checks the presence of a single record by primary key -func PublicreportQuickPhotoExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { - return PublicreportQuickPhotos.Query( - sm.Where(PublicreportQuickPhotos.Columns.ID.EQ(psql.Arg(IDPK))), - ).Exists(ctx, exec) -} - -// AfterQueryHook is called after PublicreportQuickPhoto is retrieved from the database -func (o *PublicreportQuickPhoto) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { - var err error - - switch queryType { - case bob.QueryTypeSelect: - ctx, err = PublicreportQuickPhotos.AfterSelectHooks.RunHooks(ctx, exec, PublicreportQuickPhotoSlice{o}) - case bob.QueryTypeInsert: - ctx, err = PublicreportQuickPhotos.AfterInsertHooks.RunHooks(ctx, exec, PublicreportQuickPhotoSlice{o}) - case bob.QueryTypeUpdate: - ctx, err = PublicreportQuickPhotos.AfterUpdateHooks.RunHooks(ctx, exec, PublicreportQuickPhotoSlice{o}) - case bob.QueryTypeDelete: - ctx, err = PublicreportQuickPhotos.AfterDeleteHooks.RunHooks(ctx, exec, PublicreportQuickPhotoSlice{o}) - } - - return err -} - -// primaryKeyVals returns the primary key values of the PublicreportQuickPhoto -func (o *PublicreportQuickPhoto) primaryKeyVals() bob.Expression { - return psql.Arg(o.ID) -} - -func (o *PublicreportQuickPhoto) pkEQ() dialect.Expression { - return psql.Quote("publicreport.quick_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 PublicreportQuickPhoto -func (o *PublicreportQuickPhoto) Update(ctx context.Context, exec bob.Executor, s *PublicreportQuickPhotoSetter) error { - v, err := PublicreportQuickPhotos.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 PublicreportQuickPhoto record with an executor -func (o *PublicreportQuickPhoto) Delete(ctx context.Context, exec bob.Executor) error { - _, err := PublicreportQuickPhotos.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) - return err -} - -// Reload refreshes the PublicreportQuickPhoto using the executor -func (o *PublicreportQuickPhoto) Reload(ctx context.Context, exec bob.Executor) error { - o2, err := PublicreportQuickPhotos.Query( - sm.Where(PublicreportQuickPhotos.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 PublicreportQuickPhotoSlice is retrieved from the database -func (o PublicreportQuickPhotoSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { - var err error - - switch queryType { - case bob.QueryTypeSelect: - ctx, err = PublicreportQuickPhotos.AfterSelectHooks.RunHooks(ctx, exec, o) - case bob.QueryTypeInsert: - ctx, err = PublicreportQuickPhotos.AfterInsertHooks.RunHooks(ctx, exec, o) - case bob.QueryTypeUpdate: - ctx, err = PublicreportQuickPhotos.AfterUpdateHooks.RunHooks(ctx, exec, o) - case bob.QueryTypeDelete: - ctx, err = PublicreportQuickPhotos.AfterDeleteHooks.RunHooks(ctx, exec, o) - } - - return err -} - -func (o PublicreportQuickPhotoSlice) pkIN() dialect.Expression { - if len(o) == 0 { - return psql.Raw("NULL") - } - - return psql.Quote("publicreport.quick_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 PublicreportQuickPhotoSlice) copyMatchingRows(from ...*PublicreportQuickPhoto) { - 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 PublicreportQuickPhotoSlice) 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 PublicreportQuickPhotos.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 *PublicreportQuickPhoto: - o.copyMatchingRows(retrieved) - case []*PublicreportQuickPhoto: - o.copyMatchingRows(retrieved...) - case PublicreportQuickPhotoSlice: - o.copyMatchingRows(retrieved...) - default: - // If the retrieved value is not a PublicreportQuickPhoto or a slice of PublicreportQuickPhoto - // then run the AfterUpdateHooks on the slice - _, err = PublicreportQuickPhotos.AfterUpdateHooks.RunHooks(ctx, exec, o) - } - - return err - })) - - q.AppendWhere(o.pkIN()) - }) -} - -// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" -func (o PublicreportQuickPhotoSlice) 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 PublicreportQuickPhotos.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 *PublicreportQuickPhoto: - o.copyMatchingRows(retrieved) - case []*PublicreportQuickPhoto: - o.copyMatchingRows(retrieved...) - case PublicreportQuickPhotoSlice: - o.copyMatchingRows(retrieved...) - default: - // If the retrieved value is not a PublicreportQuickPhoto or a slice of PublicreportQuickPhoto - // then run the AfterDeleteHooks on the slice - _, err = PublicreportQuickPhotos.AfterDeleteHooks.RunHooks(ctx, exec, o) - } - - return err - })) - - q.AppendWhere(o.pkIN()) - }) -} - -func (o PublicreportQuickPhotoSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals PublicreportQuickPhotoSetter) error { - if len(o) == 0 { - return nil - } - - _, err := PublicreportQuickPhotos.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) - return err -} - -func (o PublicreportQuickPhotoSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { - if len(o) == 0 { - return nil - } - - _, err := PublicreportQuickPhotos.Delete(o.DeleteMod()).Exec(ctx, exec) - return err -} - -func (o PublicreportQuickPhotoSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { - if len(o) == 0 { - return nil - } - - o2, err := PublicreportQuickPhotos.Query(sm.Where(o.pkIN())).All(ctx, exec) - if err != nil { - return err - } - - o.copyMatchingRows(o2...) - - return nil -} - -// Quick starts a query for related objects on publicreport.quick -func (o *PublicreportQuickPhoto) Quick(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportQuicksQuery { - return PublicreportQuicks.Query(append(mods, - sm.Where(PublicreportQuicks.Columns.ID.EQ(psql.Arg(o.QuickID))), - )...) -} - -func (os PublicreportQuickPhotoSlice) Quick(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportQuicksQuery { - pkQuickID := make(pgtypes.Array[int32], 0, len(os)) - for _, o := range os { - if o == nil { - continue - } - pkQuickID = append(pkQuickID, o.QuickID) - } - PKArgExpr := psql.Select(sm.Columns( - psql.F("unnest", psql.Cast(psql.Arg(pkQuickID), "integer[]")), - )) - - return PublicreportQuicks.Query(append(mods, - sm.Where(psql.Group(PublicreportQuicks.Columns.ID).OP("IN", PKArgExpr)), - )...) -} - -func attachPublicreportQuickPhotoQuick0(ctx context.Context, exec bob.Executor, count int, publicreportQuickPhoto0 *PublicreportQuickPhoto, publicreportQuick1 *PublicreportQuick) (*PublicreportQuickPhoto, error) { - setter := &PublicreportQuickPhotoSetter{ - QuickID: omit.From(publicreportQuick1.ID), - } - - err := publicreportQuickPhoto0.Update(ctx, exec, setter) - if err != nil { - return nil, fmt.Errorf("attachPublicreportQuickPhotoQuick0: %w", err) - } - - return publicreportQuickPhoto0, nil -} - -func (publicreportQuickPhoto0 *PublicreportQuickPhoto) InsertQuick(ctx context.Context, exec bob.Executor, related *PublicreportQuickSetter) error { - var err error - - publicreportQuick1, err := PublicreportQuicks.Insert(related).One(ctx, exec) - if err != nil { - return fmt.Errorf("inserting related objects: %w", err) - } - - _, err = attachPublicreportQuickPhotoQuick0(ctx, exec, 1, publicreportQuickPhoto0, publicreportQuick1) - if err != nil { - return err - } - - publicreportQuickPhoto0.R.Quick = publicreportQuick1 - - publicreportQuick1.R.QuickPhotos = append(publicreportQuick1.R.QuickPhotos, publicreportQuickPhoto0) - - return nil -} - -func (publicreportQuickPhoto0 *PublicreportQuickPhoto) AttachQuick(ctx context.Context, exec bob.Executor, publicreportQuick1 *PublicreportQuick) error { - var err error - - _, err = attachPublicreportQuickPhotoQuick0(ctx, exec, 1, publicreportQuickPhoto0, publicreportQuick1) - if err != nil { - return err - } - - publicreportQuickPhoto0.R.Quick = publicreportQuick1 - - publicreportQuick1.R.QuickPhotos = append(publicreportQuick1.R.QuickPhotos, publicreportQuickPhoto0) - - return nil -} - -type publicreportQuickPhotoWhere[Q psql.Filterable] struct { - ID psql.WhereMod[Q, int32] - Size psql.WhereMod[Q, int64] - Filename psql.WhereMod[Q, string] - QuickID psql.WhereMod[Q, int32] - UUID psql.WhereMod[Q, uuid.UUID] -} - -func (publicreportQuickPhotoWhere[Q]) AliasedAs(alias string) publicreportQuickPhotoWhere[Q] { - return buildPublicreportQuickPhotoWhere[Q](buildPublicreportQuickPhotoColumns(alias)) -} - -func buildPublicreportQuickPhotoWhere[Q psql.Filterable](cols publicreportQuickPhotoColumns) publicreportQuickPhotoWhere[Q] { - return publicreportQuickPhotoWhere[Q]{ - ID: psql.Where[Q, int32](cols.ID), - Size: psql.Where[Q, int64](cols.Size), - Filename: psql.Where[Q, string](cols.Filename), - QuickID: psql.Where[Q, int32](cols.QuickID), - UUID: psql.Where[Q, uuid.UUID](cols.UUID), - } -} - -func (o *PublicreportQuickPhoto) Preload(name string, retrieved any) error { - if o == nil { - return nil - } - - switch name { - case "Quick": - rel, ok := retrieved.(*PublicreportQuick) - if !ok { - return fmt.Errorf("publicreportQuickPhoto cannot load %T as %q", retrieved, name) - } - - o.R.Quick = rel - - if rel != nil { - rel.R.QuickPhotos = PublicreportQuickPhotoSlice{o} - } - return nil - default: - return fmt.Errorf("publicreportQuickPhoto has no relationship %q", name) - } -} - -type publicreportQuickPhotoPreloader struct { - Quick func(...psql.PreloadOption) psql.Preloader -} - -func buildPublicreportQuickPhotoPreloader() publicreportQuickPhotoPreloader { - return publicreportQuickPhotoPreloader{ - Quick: func(opts ...psql.PreloadOption) psql.Preloader { - return psql.Preload[*PublicreportQuick, PublicreportQuickSlice](psql.PreloadRel{ - Name: "Quick", - Sides: []psql.PreloadSide{ - { - From: PublicreportQuickPhotos, - To: PublicreportQuicks, - FromColumns: []string{"quick_id"}, - ToColumns: []string{"id"}, - }, - }, - }, PublicreportQuicks.Columns.Names(), opts...) - }, - } -} - -type publicreportQuickPhotoThenLoader[Q orm.Loadable] struct { - Quick func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] -} - -func buildPublicreportQuickPhotoThenLoader[Q orm.Loadable]() publicreportQuickPhotoThenLoader[Q] { - type QuickLoadInterface interface { - LoadQuick(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error - } - - return publicreportQuickPhotoThenLoader[Q]{ - Quick: thenLoadBuilder[Q]( - "Quick", - func(ctx context.Context, exec bob.Executor, retrieved QuickLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadQuick(ctx, exec, mods...) - }, - ), - } -} - -// LoadQuick loads the publicreportQuickPhoto's Quick into the .R struct -func (o *PublicreportQuickPhoto) LoadQuick(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { - if o == nil { - return nil - } - - // Reset the relationship - o.R.Quick = nil - - related, err := o.Quick(mods...).One(ctx, exec) - if err != nil { - return err - } - - related.R.QuickPhotos = PublicreportQuickPhotoSlice{o} - - o.R.Quick = related - return nil -} - -// LoadQuick loads the publicreportQuickPhoto's Quick into the .R struct -func (os PublicreportQuickPhotoSlice) LoadQuick(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { - if len(os) == 0 { - return nil - } - - publicreportQuicks, err := os.Quick(mods...).All(ctx, exec) - if err != nil { - return err - } - - for _, o := range os { - if o == nil { - continue - } - - for _, rel := range publicreportQuicks { - - if !(o.QuickID == rel.ID) { - continue - } - - rel.R.QuickPhotos = append(rel.R.QuickPhotos, o) - - o.R.Quick = rel - break - } - } - - return nil -} - -type publicreportQuickPhotoJoins[Q dialect.Joinable] struct { - typ string - Quick modAs[Q, publicreportQuickColumns] -} - -func (j publicreportQuickPhotoJoins[Q]) aliasedAs(alias string) publicreportQuickPhotoJoins[Q] { - return buildPublicreportQuickPhotoJoins[Q](buildPublicreportQuickPhotoColumns(alias), j.typ) -} - -func buildPublicreportQuickPhotoJoins[Q dialect.Joinable](cols publicreportQuickPhotoColumns, typ string) publicreportQuickPhotoJoins[Q] { - return publicreportQuickPhotoJoins[Q]{ - typ: typ, - Quick: modAs[Q, publicreportQuickColumns]{ - c: PublicreportQuicks.Columns, - f: func(to publicreportQuickColumns) bob.Mod[Q] { - mods := make(mods.QueryMods[Q], 0, 1) - - { - mods = append(mods, dialect.Join[Q](typ, PublicreportQuicks.Name().As(to.Alias())).On( - to.ID.EQ(cols.QuickID), - )) - } - - return mods - }, - }, - } -} diff --git a/go.mod b/go.mod index 85d2a31f..ef56dc10 100644 --- a/go.mod +++ b/go.mod @@ -33,8 +33,17 @@ require ( require ( github.com/ajg/form v1.5.1 // indirect + github.com/dsoprea/go-exif/v3 v3.0.1 // indirect + github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect + github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20221012074422-4f3f7e934102 // indirect + github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect + github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c // indirect + github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-ini/ini v1.67.0 // indirect + github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect + github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect @@ -67,5 +76,6 @@ require ( golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.29.0 // indirect google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2b5f8289..c6543560 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,31 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E= +github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8= +github.com/dsoprea/go-exif/v3 v3.0.0-20210428042052-dca55bf8ca15/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= +github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= +github.com/dsoprea/go-exif/v3 v3.0.0-20221003160559-cf5cd88aa559/go.mod h1:rW6DMEv25U9zCtE5ukC7ttBRllXj7g7TAHl7tQrT5No= +github.com/dsoprea/go-exif/v3 v3.0.0-20221003171958-de6cb6e380a8/go.mod h1:akyZEJZ/k5bmbC9gA612ZLQkcED8enS9vuTiuAkENr0= +github.com/dsoprea/go-exif/v3 v3.0.1 h1:/IE4iW7gvY7BablV1XY0unqhMv26EYpOquVMwoBo/wc= +github.com/dsoprea/go-exif/v3 v3.0.1/go.mod h1:10HkA1Wz3h398cDP66L+Is9kKDmlqlIJGPv8pk4EWvc= +github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb h1:gwjJjUr6FY7zAWVEueFPrcRHhd9+IK81TcItbqw2du4= +github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM= +github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20221012074422-4f3f7e934102 h1:gmTXQdSuuuORRFPTS2uaYpAXU5oUNkXdeYSlZe5NvsE= +github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20221012074422-4f3f7e934102/go.mod h1:WaARaUjQuSuDCDFAiU/GwzfxMTJBulfEhqEA2Tx6B4Y= +github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= +github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= +github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg= +github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= +github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c h1:7j5aWACOzROpr+dvMtu8GnI97g9ShLWD72XIELMgn+c= +github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c/go.mod h1:pqKB+ijp27cEcrHxhXVgUUMlSDRuGJJp1E+20Lj5H0E= +github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf h1:/w4QxepU4AHh3AuO6/g8y/YIIHH5+aKP3Bj8sg5cqhU= +github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8= +github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU= +github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LVjRU0RNUuMDqkPTxcALio0LWPFPXxxFCvVGVAwEpFc= +github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU= +github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw= +github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= @@ -56,6 +81,11 @@ github.com/go-chi/hostrouter v0.3.0 h1:75it1eO3FvkG8te1CvU6Kvr3WzAZNEBbo8xIrxUKL github.com/go-chi/hostrouter v0.3.0/go.mod h1:KLB+7PH/ceOr6FCmMyWD2Dmql/clpOe+y7I7CUeTkaQ= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= +github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -64,11 +94,17 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= +github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -88,6 +124,8 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jaswdr/faker/v2 v2.8.1 h1:2AcPgHDBXYQregFUH9LgVZKfFupc4SIquYhp29sf5wQ= github.com/jaswdr/faker/v2 v2.8.1/go.mod h1:jZq+qzNQr8/P+5fHd9t3txe2GNPnthrTfohtnJ7B+68= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -247,6 +285,7 @@ go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXe go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= @@ -255,9 +294,16 @@ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/y golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= @@ -268,11 +314,16 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -309,6 +360,10 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/public-report/image-upload.go b/public-report/image-upload.go new file mode 100644 index 00000000..ea40a0cd --- /dev/null +++ b/public-report/image-upload.go @@ -0,0 +1,182 @@ +package publicreport + +import ( + "bytes" + "context" + "fmt" + "image" + _ "image/gif" // register GIF format + _ "image/jpeg" // register JPEG format + _ "image/png" // register PNG format + "io" + "mime/multipart" + "net/http" + "time" + + "github.com/aarondl/opt/omit" + "github.com/dsoprea/go-exif/v3" + exifcommon "github.com/dsoprea/go-exif/v3/common" + //"github.com/dsoprea/go-jpeg-image-structure/v2" + "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/Gleipnir-Technology/nidus-sync/userfile" + "github.com/google/uuid" + "github.com/rs/zerolog/log" + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/dialect/psql" + "github.com/stephenafamo/bob/dialect/psql/um" +) + +type ExifCollection struct { + GPS *exif.GpsInfo + Tags map[string]string +} + +type ImageUpload struct { + Bounds image.Rectangle + ContentType string + Exif ExifCollection + Format string + + UploadFilesize int + UploadFilename string + UUID uuid.UUID +} + +func extractExif(file_bytes []byte) (result ExifCollection, err error) { + + raw_exif, err := exif.SearchAndExtractExifWithReader(bytes.NewReader(file_bytes)) + if err != nil { + return result, fmt.Errorf("Failed to find exif: %w", err) + } + im, err := exifcommon.NewIfdMappingWithStandard() + if err != nil { + return result, fmt.Errorf("Failed to create new idf mapping: %w", err) + } + ti := exif.NewTagIndex() + _, index, err := exif.Collect(im, ti, raw_exif) + if err != nil { + return result, fmt.Errorf("Failed to collect exif: %w", err) + } + ifd, err := index.RootIfd.ChildWithIfdPath(exifcommon.IfdGpsInfoStandardIfdIdentity) + if err != nil { + return result, fmt.Errorf("Failed to find gps exif: %w", err) + } + gi, err := ifd.GpsInfo() + if err != nil { + log.Info().Err(err).Msg("Failed to get GPS info for uploaded image") + result.GPS = nil + } else { + result.GPS = gi + } + result.Tags = make(map[string]string, 0) + + tags, _, err := exif.GetFlatExifData(raw_exif, &exif.ScanOptions{}) + if err != nil { + return result, fmt.Errorf("Failed to gather flat exif: %w", err) + } + for _, t := range tags { + result.Tags[t.TagName] = t.Formatted + } + log.Info().Str("GPS", fmt.Sprintf("%s", gi)).Int("count", len(result.Tags)).Msg("Extracted exif tags") + return result, nil +} + +func extractImageUpload(headers *multipart.FileHeader) (upload ImageUpload, err error) { + file, err := headers.Open() + if err != nil { + return upload, fmt.Errorf("Failed to open header: %w", err) + } + defer file.Close() + + file_bytes, err := io.ReadAll(file) + content_type := http.DetectContentType(file_bytes) + + exif, err := extractExif(file_bytes) + if err != nil { + //return upload, fmt.Errorf("Failed to extract EXIF data: %w", err) + log.Warn().Err(err).Msg("Failed to extract EXIF data") + } + + i, format, err := image.Decode(bytes.NewReader(file_bytes)) + if err != nil { + return upload, fmt.Errorf("Failed to decode image file: %w", err) + } + + u, err := uuid.NewUUID() + if err != nil { + return upload, fmt.Errorf("Failed to create quick report photo uuid", err) + } + err = userfile.PublicImageFileContentWrite(u, bytes.NewReader(file_bytes)) + if err != nil { + return upload, fmt.Errorf("Failed to write image file to disk: %w", err) + } + log.Info().Int("size", len(file_bytes)).Str("uploaded_filename", headers.Filename).Str("content-type", content_type).Str("uuid", u.String()).Msg("Saved an uploaded file to disk") + return ImageUpload{ + Bounds: i.Bounds(), + ContentType: content_type, + Exif: exif, + Format: format, + UploadFilename: headers.Filename, + UploadFilesize: len(file_bytes), + UUID: u, + }, nil +} + +func extractImageUploads(r *http.Request) (uploads []ImageUpload, err error) { + uploads = make([]ImageUpload, 0) + for _, fheaders := range r.MultipartForm.File { + for _, headers := range fheaders { + upload, err := extractImageUpload(headers) + if err != nil { + return make([]ImageUpload, 0), fmt.Errorf("Failed to extract photo upload: %w", err) + } + uploads = append(uploads, upload) + } + } + return uploads, nil +} + +func saveImageUploads(ctx context.Context, tx bob.Tx, uploads []ImageUpload) (models.PublicreportImageSlice, error) { + images := make(models.PublicreportImageSlice, 0) + for _, u := range uploads { + image, err := models.PublicreportImages.Insert(&models.PublicreportImageSetter{ + ContentType: omit.From(u.ContentType), + + Created: omit.From(time.Now()), + //Location: omitnull.From(nil), + ResolutionX: omit.From(int32(u.Bounds.Max.X)), + ResolutionY: omit.From(int32(u.Bounds.Max.Y)), + StorageUUID: omit.From(u.UUID), + StorageSize: omit.From(int64(u.UploadFilesize)), + UploadedFilename: omit.From(u.UploadFilename), + }).One(ctx, tx) + if err != nil { + return images, fmt.Errorf("Failed to create photo records: %w", err) + } + + // TODO: figure out how to do this via the setter...? + if u.Exif.GPS != nil { + _, err = psql.Update( + um.Table("publicreport.image"), + um.SetCol("location").To(fmt.Sprintf("ST_GeometryFromText('Point(%f %f)')", u.Exif.GPS.Longitude.Decimal(), u.Exif.GPS.Latitude.Decimal())), + um.Where(psql.Quote("id").EQ(psql.Arg(image.ID))), + ).Exec(ctx, tx) + } + + exif_setters := make([]*models.PublicreportImageExifSetter, 0) + for k, v := range u.Exif.Tags { + exif_setters = append(exif_setters, &models.PublicreportImageExifSetter{ + ImageID: omit.From(image.ID), + Name: omit.From(k), + Value: omit.From(v), + }) + } + _, err = models.PublicreportImageExifs.Insert(bob.ToMods(exif_setters...)).Exec(ctx, tx) + if err != nil { + return images, fmt.Errorf("Failed to create photo exif records: %w", err) + } + images = append(images, image) + log.Info().Int32("id", image.ID).Int("tags", len(u.Exif.Tags)).Msg("Saved an uploaded file to the database") + } + return images, nil +} diff --git a/public-report/photo-upload.go b/public-report/photo-upload.go deleted file mode 100644 index 6d40946f..00000000 --- a/public-report/photo-upload.go +++ /dev/null @@ -1,59 +0,0 @@ -package publicreport - -import ( - "bytes" - "fmt" - "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) - 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 -} diff --git a/public-report/pool.go b/public-report/pool.go index 5f5d5004..6872c8a7 100644 --- a/public-report/pool.go +++ b/public-report/pool.go @@ -13,6 +13,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/aarondl/opt/omit" "github.com/rs/zerolog/log" + "github.com/stephenafamo/bob" "github.com/stephenafamo/bob/dialect/psql" "github.com/stephenafamo/bob/dialect/psql/um" ) @@ -90,6 +91,14 @@ func postPool(w http.ResponseWriter, r *http.Request) { return } + ctx := r.Context() + tx, err := db.PGInstance.BobDB.BeginTx(ctx, nil) + if err != nil { + respondError(w, "Failed to create transaction", err, http.StatusInternalServerError) + return + } + defer tx.Rollback(ctx) + setter := models.PublicreportPoolSetter{ AccessComments: omit.From(access_comments), AccessGate: omit.From(access_gate), @@ -121,7 +130,7 @@ func postPool(w http.ResponseWriter, r *http.Request) { Status: omit.From(enums.PublicreportReportstatustypeReported), Subscribe: omit.From(subscribe), } - pool, err := models.PublicreportPools.Insert(&setter).One(r.Context(), db.PGInstance.BobDB) + pool, err := models.PublicreportPools.Insert(&setter).One(ctx, tx) if err != nil { respondError(w, "Failed to create database record", err, http.StatusInternalServerError) return @@ -138,30 +147,31 @@ func postPool(w http.ResponseWriter, r *http.Request) { 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) + ).Exec(ctx, tx) 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) + uploads, err := extractImageUploads(r) if err != nil { - respondError(w, "Failed to extract photo uploads", err, http.StatusInternalServerError) + respondError(w, "Failed to extract image 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), + images, err := saveImageUploads(r.Context(), tx, uploads) + setters := make([]*models.PublicreportPoolImageSetter, 0) + for _, image := range images { + setters = append(setters, &models.PublicreportPoolImageSetter{ + ImageID: omit.From(int32(image.ID)), + PoolID: omit.From(int32(pool.ID)), }) } - err = pool.InsertPoolPhotos(r.Context(), db.PGInstance.BobDB, photoSetters...) + _, err = models.PublicreportPoolImages.Insert(bob.ToMods(setters...)).Exec(r.Context(), tx) if err != nil { - respondError(w, "Failed to create photo records", err, http.StatusInternalServerError) + respondError(w, "Failed to save upload relationships", err, http.StatusInternalServerError) return } + tx.Commit(ctx) http.Redirect(w, r, fmt.Sprintf("/pool-submit-complete?report=%s", public_id), http.StatusFound) } diff --git a/public-report/quick.go b/public-report/quick.go index 1509df56..e076d425 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -14,6 +14,7 @@ import ( "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" + "github.com/stephenafamo/bob" "github.com/stephenafamo/bob/dialect/psql" "github.com/stephenafamo/bob/dialect/psql/um" ) @@ -70,6 +71,14 @@ func postQuick(w http.ResponseWriter, r *http.Request) { respondError(w, "Failed to create quick report public ID", err, http.StatusInternalServerError) return } + ctx := r.Context() + tx, err := db.PGInstance.BobDB.BeginTx(ctx, nil) + if err != nil { + respondError(w, "Failed to create transaction", err, http.StatusInternalServerError) + return + } + defer tx.Rollback(ctx) + c, err := h3utils.GetCell(longitude, latitude, 15) setter := models.PublicreportQuickSetter{ Address: omit.From(""), @@ -82,7 +91,7 @@ func postQuick(w http.ResponseWriter, r *http.Request) { ReporterPhone: omit.From(""), Status: omit.From(enums.PublicreportReportstatustypeReported), } - quick, err := models.PublicreportQuicks.Insert(&setter).One(r.Context(), db.PGInstance.BobDB) + quick, err := models.PublicreportQuicks.Insert(&setter).One(ctx, tx) if err != nil { respondError(w, "Failed to create database record", err, http.StatusInternalServerError) return @@ -91,29 +100,37 @@ func postQuick(w http.ResponseWriter, r *http.Request) { 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) + ).Exec(ctx, tx) 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) + uploads, err := extractImageUploads(r) + log.Info().Int("len", len(uploads)).Msg("extracted uploads") if err != nil { - respondError(w, "Failed to extract photo uploads", err, http.StatusInternalServerError) + respondError(w, "Failed to extract image 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), + images, err := saveImageUploads(ctx, tx, uploads) + if err != nil { + respondError(w, "Failed to save image uploads", err, http.StatusInternalServerError) + return + } + log.Info().Int("len", len(images)).Msg("saved uploads") + setters := make([]*models.PublicreportQuickImageSetter, 0) + for _, image := range images { + setters = append(setters, &models.PublicreportQuickImageSetter{ + ImageID: omit.From(int32(image.ID)), + QuickID: omit.From(int32(quick.ID)), }) } - err = quick.InsertQuickPhotos(r.Context(), db.PGInstance.BobDB, photoSetters...) + _, err = models.PublicreportQuickImages.Insert(bob.ToMods(setters...)).Exec(ctx, tx) if err != nil { - respondError(w, "Failed to create photo records", err, http.StatusInternalServerError) + respondError(w, "Failed to save reference to images", err, http.StatusInternalServerError) return } + log.Info().Int("len", len(images)).Msg("saved uploads") + tx.Commit(ctx) http.Redirect(w, r, fmt.Sprintf("/quick-submit-complete?report=%s", u), http.StatusFound) } diff --git a/userfile/userfile.go b/userfile/userfile.go index 2608d975..794450e0 100644 --- a/userfile/userfile.go +++ b/userfile/userfile.go @@ -3,11 +3,11 @@ package userfile import ( "fmt" "io" - "log" "os" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/google/uuid" + "github.com/rs/zerolog/log" ) func AudioFileContentPathRaw(audioUUID string) string { @@ -27,7 +27,7 @@ func AudioFileContentWrite(audioUUID uuid.UUID, body io.Reader) error { filepath := AudioFileContentPathRaw(audioUUID.String()) dst, err := os.Create(filepath) if err != nil { - log.Printf("Failed to create audio file at %s: %v\n", filepath, err) + log.Error().Err(err).Str("filepath", filepath).Msg("Failed to create audio file") return fmt.Errorf("Failed to create audio file at %s: %v", filepath, err) } defer dst.Close() @@ -37,7 +37,7 @@ func AudioFileContentWrite(audioUUID uuid.UUID, body io.Reader) error { if err != nil { return fmt.Errorf("Unable to save file to create audio file at %s: %v", filepath, err) } - log.Printf("Saved audio content to %s\n", filepath) + log.Info().Str("filepath", filepath).Msg("Save audio file content") return nil } func ImageFileContentPathRaw(uid string) string { @@ -65,7 +65,7 @@ func PublicImageFileContentWrite(uid uuid.UUID, body io.Reader) error { filepath := PublicImageFileContentPathRaw(uid.String()) dst, err := os.Create(filepath) if err != nil { - log.Printf("Failed to create public image file at %s: %v\n", filepath, err) + log.Error().Err(err).Str("filepath", filepath).Msg("Failed to create public image file") return fmt.Errorf("Failed to create public image file at %s: %v", filepath, err) } defer dst.Close() @@ -75,7 +75,7 @@ func PublicImageFileContentWrite(uid uuid.UUID, body io.Reader) error { if err != nil { return fmt.Errorf("Unable to save file to create audio file at %s: %v", filepath, err) } - log.Printf("Saved audio content to %s\n", filepath) + log.Info().Str("filepath", filepath).Msg("Saved public report image file content") return nil } func PublicImageFileContentPathRaw(uid string) string { From 8ab0b78e6e6ac390da3659d3c40ecf8326a10693 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 16 Jan 2026 21:16:59 +0000 Subject: [PATCH 0050/1453] Make sure they consent to get notifications in the UI. --- public-report/template/quick-submit-complete.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public-report/template/quick-submit-complete.html b/public-report/template/quick-submit-complete.html index 35dc6673..da711c4b 100644 --- a/public-report/template/quick-submit-complete.html +++ b/public-report/template/quick-submit-complete.html @@ -59,7 +59,7 @@

Provide your contact information to receive updates about your report.

-
+
@@ -87,8 +87,8 @@
-
- +
+ From 7abaebe49689c535026526c2cdf148110ed2250d Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 17 Jan 2026 01:13:27 +0000 Subject: [PATCH 0051/1453] Add support for sending SMS --- comms/email.go | 58 ++++++++++++++++++++++++ comms/sms.go | 58 ++++++++++++++++++++++++ config/config.go | 93 ++++++++++++++++++++++++++++----------- public-report/endpoint.go | 50 --------------------- public-report/quick.go | 65 +++++++++++++++++++++++++++ 5 files changed, 249 insertions(+), 75 deletions(-) create mode 100644 comms/email.go create mode 100644 comms/sms.go diff --git a/comms/email.go b/comms/email.go new file mode 100644 index 00000000..a31c145a --- /dev/null +++ b/comms/email.go @@ -0,0 +1,58 @@ +package comms + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/rs/zerolog/log" +) + +type AttachmentRequest struct { + Filename string `json:"filename"` + Content string `json:"content"` +} + +type EmailRequest struct { + From string `json:"from"` + To string `json:"to"` + CC []string `json:"cc,omitempty"` + BCC []string `json:"bcc,omitempty"` + Subject string `json:"subject"` + Text string `json:"text"` + HTML string `json:"html,omitempty"` + Attachments []AttachmentRequest `json:"attachments,omitempty"` + Sender string `json:"sender"` + ReplyTo string `json:"replyTo,omitempty"` + InReplyTo string `json:"inReplyTo,omitempty"` + References []string `json:"references,omitempty"` +} + +type EmailResponse struct { + Message string `json:"message"` +} + +func SendEmail(email EmailRequest) error { + url := "https://api.forwardemail.net/v1/emails" + + payload, err := json.Marshal(email) + if err != nil { + return fmt.Errorf("Failed to marshal email request: %w", err) + } + //payload := strings.NewReader("{\n \"from\": \"\",\n \"to\": \"\",\n \"cc\": \"\",\n \"bcc\": \"\",\n \"subject\": \"\",\n \"text\": \"\",\n \"html\": \"\",\n \"attachments\": [\n {}\n ],\n \"sender\": \"\",\n \"replyTo\": \"\",\n \"inReplyTo\": \"\",\n \"references\": \"\",\n \"attachDataUrls\": true,\n \"watchHtml\": \"\",\n \"amp\": \"\",\n \"icalEvent\": {},\n \"alternatives\": [\n {}\n ],\n \"encoding\": \"\",\n \"raw\": \"\",\n \"textEncoding\": \"quoted-printable\",\n \"priority\": \"high\",\n \"headers\": {\"ANY_ADDITIONAL_PROPERTY\": \"anything\"},\n \"messageId\": \"\",\n \"date\": \"\",\n \"list\": {},\n \"requireTLS\": true\n}") + + req, _ := http.NewRequest("POST", url, bytes.NewReader(payload)) + req.SetBasicAuth(config.ForwardEmailAPIToken, "") + req.Header.Add("Content-Type", "application/json") + + res, _ := http.DefaultClient.Do(req) + + defer res.Body.Close() + body, _ := io.ReadAll(res.Body) + + log.Info().Str("status", res.Status).Str("request_body", string(payload)).Str("response_body", string(body)).Msg("Attempted to send email") + return nil +} diff --git a/comms/sms.go b/comms/sms.go new file mode 100644 index 00000000..1a5ad94a --- /dev/null +++ b/comms/sms.go @@ -0,0 +1,58 @@ +package comms + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/rs/zerolog/log" +) + +var VOIP_MS_API = "https://voip.ms/api/v1/rest.php" + +type SendSMSResponse struct { + Status string `json:"status"` + SMS int `json:"sms"` +} +func SendSMS(to string, content string) error { + if len(content) > 160 { + return errors.New("Message content is more than 160 characters") + } + params := url.Values{} + params.Add("api_password", config.VoipMSPassword) + params.Add("api_username", config.VoipMSUsername) + params.Add("method", "sendSMS") + params.Add("did", config.VoipMSNumber) + params.Add("dst", to) + params.Add("message", content) + // Construct the URL with query parameters + full_url := VOIP_MS_API + "?" + params.Encode() + + // Make the HTTP request + resp, err := http.Get(full_url) + if err != nil { + log.Warn().Err(err).Str("url", full_url).Msg("Failed to make request to Voip.MS") + return fmt.Errorf("Error making request: %w", err) + } + defer resp.Body.Close() + + // Read the response body + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Warn().Err(err).Str("url", full_url).Msg("Failed to read Voip.MS response body") + return fmt.Errorf("Failed to read response: %w", err) + } + log.Info().Str("response", string(body)).Msg("Response from Voip.MS") + + // Parse the JSON response + var response SendSMSResponse + err = json.Unmarshal(body, &response) + if err != nil { + return fmt.Errorf("Failed to unmarshal JSON response: %w", err) + } + return nil +} diff --git a/config/config.go b/config/config.go index 1b6ac507..e4a7879c 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,26 @@ import ( "strconv" ) -var Bind, ClientID, ClientSecret, Environment, FilesDirectoryPublic, FilesDirectoryUser, FieldseekerSchemaDirectory, MapboxToken, PGDSN, URLReport, URLSync, URLTegola string +var ( + Bind string + ClientID string + ClientSecret string + Environment string + FilesDirectoryPublic string + FilesDirectoryUser string + FieldseekerSchemaDirectory string + ForwardEmailAPIToken string + ForwardEmailReportPassword string + ForwardEmailReportUsername string + MapboxToken string + PGDSN string + URLReport string + URLSync string + URLTegola string + VoipMSPassword string + VoipMSNumber string + VoipMSUsername string +) // Build the ArcGIS authorization URL with PKCE func BuildArcGISAuthURL(clientID string) string { @@ -43,6 +62,10 @@ func MakeURLSync(path string) string { } func Parse() error { + Bind = os.Getenv("BIND") + if Bind == "" { + Bind = ":9001" + } ClientID = os.Getenv("ARCGIS_CLIENT_ID") if ClientID == "" { return fmt.Errorf("You must specify a non-empty ARCGIS_CLIENT_ID") @@ -51,22 +74,6 @@ func Parse() error { if ClientSecret == "" { return fmt.Errorf("You must specify a non-empty ARCGIS_CLIENT_SECRET") } - URLReport = os.Getenv("URL_REPORT") - if URLReport == "" { - return fmt.Errorf("You must specify a non-empty URL_REPORT") - } - URLSync = os.Getenv("URL_SYNC") - if URLSync == "" { - return fmt.Errorf("You must specify a non-empty URL_SYNC") - } - URLTegola = os.Getenv("URL_TEGOLA") - if URLTegola == "" { - return fmt.Errorf("You must specify a non-empty URL_TEGOLA") - } - Bind = os.Getenv("BIND") - if Bind == "" { - Bind = ":9001" - } Environment = os.Getenv("ENVIRONMENT") if Environment == "" { return fmt.Errorf("You must specify a non-empty ENVIRONMENT") @@ -74,14 +81,6 @@ func Parse() error { if !(Environment == "PRODUCTION" || Environment == "DEVELOPMENT") { return fmt.Errorf("ENVIRONMENT should be either DEVELOPMENT or PRODUCTION") } - MapboxToken = os.Getenv("MAPBOX_TOKEN") - if MapboxToken == "" { - return fmt.Errorf("You must specify a non-empty MAPBOX_TOKEN") - } - PGDSN = os.Getenv("POSTGRES_DSN") - if PGDSN == "" { - return fmt.Errorf("You must specify a non-empty POSTGRES_DSN") - } FieldseekerSchemaDirectory = os.Getenv("FIELDSEEKER_SCHEMA_DIRECTORY") if FieldseekerSchemaDirectory == "" { return fmt.Errorf("You must specify a non-empty FIELDSEEKER_SCHEMA_DIRECTORY") @@ -94,6 +93,50 @@ func Parse() error { if FilesDirectoryUser == "" { return fmt.Errorf("You must specify a non-empty FILES_DIRECTORY_USER") } + ForwardEmailAPIToken = os.Getenv("FORWARDEMAIL_API_TOKEN") + if ForwardEmailAPIToken == "" { + return fmt.Errorf("You must specify a non-empty FORWARDEMAIL_API_TOKEN") + } + ForwardEmailReportUsername = os.Getenv("FORWARDEMAIL_REPORT_USERNAME") + if ForwardEmailReportUsername == "" { + return fmt.Errorf("You must specify a non-empty FORWARDEMAIL_REPORT_USERNAME") + } + ForwardEmailReportPassword = os.Getenv("FORWARDEMAIL_REPORT_PASSWORD") + if ForwardEmailReportPassword == "" { + return fmt.Errorf("You must specify a non-empty FORWARDEMAIL_REPORT_PASSWORD") + } + MapboxToken = os.Getenv("MAPBOX_TOKEN") + if MapboxToken == "" { + return fmt.Errorf("You must specify a non-empty MAPBOX_TOKEN") + } + PGDSN = os.Getenv("POSTGRES_DSN") + if PGDSN == "" { + return fmt.Errorf("You must specify a non-empty POSTGRES_DSN") + } + URLReport = os.Getenv("URL_REPORT") + if URLReport == "" { + return fmt.Errorf("You must specify a non-empty URL_REPORT") + } + URLSync = os.Getenv("URL_SYNC") + if URLSync == "" { + return fmt.Errorf("You must specify a non-empty URL_SYNC") + } + URLTegola = os.Getenv("URL_TEGOLA") + if URLTegola == "" { + return fmt.Errorf("You must specify a non-empty URL_TEGOLA") + } + VoipMSNumber = os.Getenv("VOIPMS_NUMBER") + if VoipMSNumber == "" { + return fmt.Errorf("You must specify a non-empty VOIPMS_NUMBER") + } + VoipMSPassword = os.Getenv("VOIPMS_PASSWORD") + if VoipMSPassword == "" { + return fmt.Errorf("You must specify a non-empty VOIPMS_PASSWORD") + } + VoipMSUsername = os.Getenv("VOIPMS_USERNAME") + if VoipMSUsername == "" { + return fmt.Errorf("You must specify a non-empty VOIPMS_USERNAME") + } return nil } diff --git a/public-report/endpoint.go b/public-report/endpoint.go index 35bff222..58cfab0f 100644 --- a/public-report/endpoint.go +++ b/public-report/endpoint.go @@ -1,14 +1,10 @@ package publicreport import ( - "fmt" "net/http" - "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/um" ) type ContextRegisterNotificationsComplete struct { @@ -29,52 +25,6 @@ func getRoot(w http.ResponseWriter, r *http.Request) { ) } -func getRegisterNotificationsComplete(w http.ResponseWriter, r *http.Request) { - report := r.URL.Query().Get("report") - htmlpage.RenderOrError( - w, - RegisterNotificationsComplete, - ContextRegisterNotificationsComplete{ - ReportID: report, - }, - ) -} -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 func respondError(w http.ResponseWriter, m string, e error, s int) { log.Warn().Int("status", s).Err(e).Str("user message", m).Msg("Responding with an error") diff --git a/public-report/quick.go b/public-report/quick.go index e076d425..ef686783 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -6,6 +6,7 @@ import ( "strconv" "time" + "github.com/Gleipnir-Technology/nidus-sync/comms" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" @@ -46,6 +47,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, + RegisterNotificationsComplete, + ContextRegisterNotificationsComplete{ + ReportID: report, + }, + ) +} func postQuick(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(32 << 10) // 32 MB buffer if err != nil { @@ -134,3 +145,57 @@ func postQuick(w http.ResponseWriter, r *http.Request) { tx.Commit(ctx) 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 + } + if email == "" && phone == "" { + http.Redirect(w, r, fmt.Sprintf("/quick-submit-complete?report=%s", report_id), http.StatusFound) + 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 email != "" { + comms.SendEmail(comms.EmailRequest{ + From: "website@mosquitoes.online", + To: email, + Subject: "test email", + Text: "This is just testing that I can send email", + }) + } + if phone != "" { + err := comms.SendSMS(phone, "testing 1 2 3") + if err != nil { + log.Error().Err(err).Msg("Failed to send SMS") + } + } + 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) + } +} + From 4e294699d3c673d6b31ef1180c75530f2852189b Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sun, 18 Jan 2026 03:00:48 +0000 Subject: [PATCH 0052/1453] Initial test email works. --- comms/email.go | 27 +++++++++++++-------------- config/config.go | 35 ++++++++++++++++++++--------------- public-report/quick.go | 34 ++++++++++++++++++---------------- 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/comms/email.go b/comms/email.go index a31c145a..4c9de747 100644 --- a/comms/email.go +++ b/comms/email.go @@ -13,22 +13,22 @@ import ( type AttachmentRequest struct { Filename string `json:"filename"` - Content string `json:"content"` + Content string `json:"content"` } type EmailRequest struct { - From string `json:"from"` - To string `json:"to"` - CC []string `json:"cc,omitempty"` - BCC []string `json:"bcc,omitempty"` - Subject string `json:"subject"` - Text string `json:"text"` - HTML string `json:"html,omitempty"` + From string `json:"from"` + To string `json:"to"` + CC []string `json:"cc,omitempty"` + BCC []string `json:"bcc,omitempty"` + Subject string `json:"subject"` + Text string `json:"text"` + HTML string `json:"html,omitempty"` Attachments []AttachmentRequest `json:"attachments,omitempty"` - Sender string `json:"sender"` - ReplyTo string `json:"replyTo,omitempty"` - InReplyTo string `json:"inReplyTo,omitempty"` - References []string `json:"references,omitempty"` + Sender string `json:"sender"` + ReplyTo string `json:"replyTo,omitempty"` + InReplyTo string `json:"inReplyTo,omitempty"` + References []string `json:"references,omitempty"` } type EmailResponse struct { @@ -42,7 +42,6 @@ func SendEmail(email EmailRequest) error { if err != nil { return fmt.Errorf("Failed to marshal email request: %w", err) } - //payload := strings.NewReader("{\n \"from\": \"\",\n \"to\": \"\",\n \"cc\": \"\",\n \"bcc\": \"\",\n \"subject\": \"\",\n \"text\": \"\",\n \"html\": \"\",\n \"attachments\": [\n {}\n ],\n \"sender\": \"\",\n \"replyTo\": \"\",\n \"inReplyTo\": \"\",\n \"references\": \"\",\n \"attachDataUrls\": true,\n \"watchHtml\": \"\",\n \"amp\": \"\",\n \"icalEvent\": {},\n \"alternatives\": [\n {}\n ],\n \"encoding\": \"\",\n \"raw\": \"\",\n \"textEncoding\": \"quoted-printable\",\n \"priority\": \"high\",\n \"headers\": {\"ANY_ADDITIONAL_PROPERTY\": \"anything\"},\n \"messageId\": \"\",\n \"date\": \"\",\n \"list\": {},\n \"requireTLS\": true\n}") req, _ := http.NewRequest("POST", url, bytes.NewReader(payload)) req.SetBasicAuth(config.ForwardEmailAPIToken, "") @@ -53,6 +52,6 @@ func SendEmail(email EmailRequest) error { defer res.Body.Close() body, _ := io.ReadAll(res.Body) - log.Info().Str("status", res.Status).Str("request_body", string(payload)).Str("response_body", string(body)).Msg("Attempted to send email") + log.Info().Str("status", res.Status).Str("response_body", string(body)).Msg("Attempted to send email") return nil } diff --git a/config/config.go b/config/config.go index e4a7879c..1bd04286 100644 --- a/config/config.go +++ b/config/config.go @@ -8,24 +8,25 @@ import ( ) var ( - Bind string - ClientID string - ClientSecret string - Environment string - FilesDirectoryPublic string - FilesDirectoryUser string + Bind string + ClientID string + ClientSecret string + Environment string + FilesDirectoryPublic string + FilesDirectoryUser string FieldseekerSchemaDirectory string - ForwardEmailAPIToken string + ForwardEmailAPIToken string + ForwardEmailReportAddress string ForwardEmailReportPassword string ForwardEmailReportUsername string - MapboxToken string - PGDSN string - URLReport string - URLSync string - URLTegola string - VoipMSPassword string - VoipMSNumber string - VoipMSUsername string + MapboxToken string + PGDSN string + URLReport string + URLSync string + URLTegola string + VoipMSPassword string + VoipMSNumber string + VoipMSUsername string ) // Build the ArcGIS authorization URL with PKCE @@ -93,6 +94,10 @@ func Parse() error { if FilesDirectoryUser == "" { return fmt.Errorf("You must specify a non-empty FILES_DIRECTORY_USER") } + ForwardEmailReportAddress = os.Getenv("FORWARDEMAIL_REPORT_ADDRESS") + if ForwardEmailReportAddress == "" { + return fmt.Errorf("You must specify a non-empty FORWARDEMAIL_REPORT_ADDRESS") + } ForwardEmailAPIToken = os.Getenv("FORWARDEMAIL_API_TOKEN") if ForwardEmailAPIToken == "" { return fmt.Errorf("You must specify a non-empty FORWARDEMAIL_API_TOKEN") diff --git a/public-report/quick.go b/public-report/quick.go index ef686783..78cb5d9f 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -7,6 +7,7 @@ import ( "time" "github.com/Gleipnir-Technology/nidus-sync/comms" + "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" @@ -129,19 +130,21 @@ func postQuick(w http.ResponseWriter, r *http.Request) { return } log.Info().Int("len", len(images)).Msg("saved uploads") - setters := make([]*models.PublicreportQuickImageSetter, 0) - for _, image := range images { - setters = append(setters, &models.PublicreportQuickImageSetter{ - ImageID: omit.From(int32(image.ID)), - QuickID: omit.From(int32(quick.ID)), - }) + if len(images) > 0 { + setters := make([]*models.PublicreportQuickImageSetter, 0) + for _, image := range images { + setters = append(setters, &models.PublicreportQuickImageSetter{ + ImageID: omit.From(int32(image.ID)), + QuickID: omit.From(int32(quick.ID)), + }) + } + _, err = models.PublicreportQuickImages.Insert(bob.ToMods(setters...)).Exec(ctx, tx) + if err != nil { + respondError(w, "Failed to save reference to images", err, http.StatusInternalServerError) + return + } + log.Info().Int("len", len(images)).Msg("saved uploads") } - _, err = models.PublicreportQuickImages.Insert(bob.ToMods(setters...)).Exec(ctx, tx) - if err != nil { - respondError(w, "Failed to save reference to images", err, http.StatusInternalServerError) - return - } - log.Info().Int("len", len(images)).Msg("saved uploads") tx.Commit(ctx) http.Redirect(w, r, fmt.Sprintf("/quick-submit-complete?report=%s", u), http.StatusFound) } @@ -180,10 +183,10 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { } if email != "" { comms.SendEmail(comms.EmailRequest{ - From: "website@mosquitoes.online", - To: email, + From: config.ForwardEmailReportAddress, + To: email, Subject: "test email", - Text: "This is just testing that I can send email", + Text: "This is just testing that I can send email", }) } if phone != "" { @@ -198,4 +201,3 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, fmt.Sprintf("/register-notifications-complete?report=%s", report_id), http.StatusFound) } } - From 087f29d49102177c998f90a28b13f88b7503ea24 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 19 Jan 2026 14:58:28 +0000 Subject: [PATCH 0053/1453] Add support for simple MMS Tested and works, though it is a bit ugly. --- comms/sms.go | 53 ++++++++++++++++++++++++++++++++++++------ public-report/quick.go | 3 ++- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/comms/sms.go b/comms/sms.go index 1a5ad94a..cd8a4acc 100644 --- a/comms/sms.go +++ b/comms/sms.go @@ -14,10 +14,38 @@ import ( var VOIP_MS_API = "https://voip.ms/api/v1/rest.php" -type SendSMSResponse struct { +type VoipMSResponse struct { Status string `json:"status"` - SMS int `json:"sms"` + SMS int `json:"sms"` } + +func SendMMS(to string, content string, media ...string) error { + if len(content) > 2048 { + return errors.New("Message content is more than 160 characters") + } + params := url.Values{} + params.Add("api_password", config.VoipMSPassword) + params.Add("api_username", config.VoipMSUsername) + params.Add("method", "sendMMS") + params.Add("did", config.VoipMSNumber) + params.Add("dst", to) + params.Add("message", content) + for i, med := range media { + // These should be one of: + // 1. A full URL that the service cat GET + // 2. A base64-encoded image starting with "data:image/png;base64,iVBORw0KGgoAAAANSUh..." + params.Add(fmt.Sprintf("media%d", i+1), med) + } + params.Add(fmt.Sprintf("media%d", len(media)+1), "") + + response, err := makeVoipMSRequest(params) + if err != nil { + return fmt.Errorf("Failed to send MMS: %w", err) + } + log.Info().Str("status", response.Status).Int("sms", response.SMS).Msg("Sent MMS message") + return nil +} + func SendSMS(to string, content string) error { if len(content) > 160 { return errors.New("Message content is more than 160 characters") @@ -29,6 +57,17 @@ func SendSMS(to string, content string) error { params.Add("did", config.VoipMSNumber) params.Add("dst", to) params.Add("message", content) + + response, err := makeVoipMSRequest(params) + if err != nil { + return fmt.Errorf("Failed to send SMS: %w", err) + } + log.Info().Str("status", response.Status).Int("sms", response.SMS).Msg("Sent MMS message") + return nil +} + +func makeVoipMSRequest(params url.Values) (VoipMSResponse, error) { + result := VoipMSResponse{} // Construct the URL with query parameters full_url := VOIP_MS_API + "?" + params.Encode() @@ -36,7 +75,7 @@ func SendSMS(to string, content string) error { resp, err := http.Get(full_url) if err != nil { log.Warn().Err(err).Str("url", full_url).Msg("Failed to make request to Voip.MS") - return fmt.Errorf("Error making request: %w", err) + return result, fmt.Errorf("Error making request: %w", err) } defer resp.Body.Close() @@ -44,15 +83,15 @@ func SendSMS(to string, content string) error { body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Warn().Err(err).Str("url", full_url).Msg("Failed to read Voip.MS response body") - return fmt.Errorf("Failed to read response: %w", err) + return result, fmt.Errorf("Failed to read response: %w", err) } log.Info().Str("response", string(body)).Msg("Response from Voip.MS") // Parse the JSON response - var response SendSMSResponse + var response VoipMSResponse err = json.Unmarshal(body, &response) if err != nil { - return fmt.Errorf("Failed to unmarshal JSON response: %w", err) + return result, fmt.Errorf("Failed to unmarshal JSON response: %w", err) } - return nil + return response, nil } diff --git a/public-report/quick.go b/public-report/quick.go index 78cb5d9f..eadda73e 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -190,7 +190,8 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { }) } if phone != "" { - err := comms.SendSMS(phone, "testing 1 2 3") + //err := comms.SendSMS(phone, "testing 1 2 3") + err := comms.SendMMS(phone, "Check out my Nidus logo", "https://dev-sync.nidus.cloud/static/img/nidus-logo-no-lettering-64.png") if err != nil { log.Error().Err(err).Msg("Failed to send SMS") } From 2c880568dd7c856a58c2df3bdc71462107b8dbb9 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 19 Jan 2026 17:58:30 +0000 Subject: [PATCH 0054/1453] Initial work on email templates At this point I got a nice-looking formatted message in my mail client. --- comms/email.go | 45 +++++- comms/template.go | 146 ++++++++++++++++++ .../report-subscription-confirmation.html | 99 ++++++++++++ .../report-subscription-confirmation.txt | 9 ++ public-report/quick.go | 11 +- 5 files changed, 298 insertions(+), 12 deletions(-) create mode 100644 comms/template.go create mode 100644 comms/template/report-subscription-confirmation.html create mode 100644 comms/template/report-subscription-confirmation.txt diff --git a/comms/email.go b/comms/email.go index 4c9de747..cd1e930e 100644 --- a/comms/email.go +++ b/comms/email.go @@ -2,6 +2,7 @@ package comms import ( "bytes" + "embed" "encoding/json" "fmt" "io" @@ -11,12 +12,46 @@ import ( "github.com/rs/zerolog/log" ) -type AttachmentRequest struct { +func SendEmailReportConfirmation(to string, report_id string) error { + content := contentEmailReportConfirmation{ + URLLogo: "https://dev-sync.nidus.cloud/static/img/nidus-logo-no-lettering-64.png", + URLReportStatus: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s", report_id), + URLReportUnsubscribe: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s/unsubscribe", report_id), + URLViewInBrowser: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s/subscribe-confirmation", report_id), + } + text, html, err := renderEmailTemplates(reportConfirmationT, content) + if err != nil { + return fmt.Errorf("Failed to render template %s: %w", reportConfirmationT.name, err) + } + return sendEmail(emailRequest{ + From: config.ForwardEmailReportAddress, + HTML: html, + Subject: fmt.Sprintf("Mosquito Report %s Submission", report_id), + Text: text, + To: to, + }) +} + +var ( + reportConfirmationT = buildTemplate("report-subscription-confirmation") +) + +//go:embed template/* +var embeddedFiles embed.FS + +type attachmentRequest struct { Filename string `json:"filename"` Content string `json:"content"` } -type EmailRequest struct { +type contentEmailReportConfirmation struct { + URLLogo string + URLReportStatus string + URLReportUnsubscribe string + URLViewInBrowser string +} + +type emailRequest struct { From string `json:"from"` To string `json:"to"` CC []string `json:"cc,omitempty"` @@ -24,18 +59,18 @@ type EmailRequest struct { Subject string `json:"subject"` Text string `json:"text"` HTML string `json:"html,omitempty"` - Attachments []AttachmentRequest `json:"attachments,omitempty"` + Attachments []attachmentRequest `json:"attachments,omitempty"` Sender string `json:"sender"` ReplyTo string `json:"replyTo,omitempty"` InReplyTo string `json:"inReplyTo,omitempty"` References []string `json:"references,omitempty"` } -type EmailResponse struct { +type emailResponse struct { Message string `json:"message"` } -func SendEmail(email EmailRequest) error { +func sendEmail(email emailRequest) error { url := "https://api.forwardemail.net/v1/emails" payload, err := json.Marshal(email) diff --git a/comms/template.go b/comms/template.go new file mode 100644 index 00000000..0ec5e8cd --- /dev/null +++ b/comms/template.go @@ -0,0 +1,146 @@ +package comms + +import ( + "bytes" + "embed" + "errors" + "fmt" + templatehtml "html/template" + "io" + "os" + "path" + "strings" + templatetxt "text/template" + + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/rs/zerolog/log" +) + +type builtTemplate struct { + name string + templateHTML *templatehtml.Template + templateTXT *templatetxt.Template +} + +func (bt *builtTemplate) executeTemplateHTML(w io.Writer, content any) error { + if bt.templateHTML == nil { + file := templateFileHTML(bt.name) + templ, err := parseFromDiskHTML(file) + if err != nil { + return fmt.Errorf("Failed to parse template file: %w", err) + } + if templ == nil { + w.Write([]byte("Failed to read from disk: ")) + return errors.New("Template parsing failed") + } + //log.Debug().Str("name", templ.Name()).Msg("Parsed template") + return templ.ExecuteTemplate(w, bt.name+".html", content) + } else { + return bt.templateHTML.ExecuteTemplate(w, bt.name+".html", content) + } +} +func (bt *builtTemplate) executeTemplateTXT(w io.Writer, content any) error { + if bt.templateTXT == nil { + file := templateFileTXT(bt.name) + templ, err := parseFromDiskTXT(file) + if err != nil { + return fmt.Errorf("Failed to parse template file: %w", err) + } + if templ == nil { + w.Write([]byte("Failed to read from disk: ")) + return errors.New("Template parsing failed") + } + //log.Debug().Str("name", templ.Name()).Msg("Parsed template") + return templ.ExecuteTemplate(w, bt.name+".txt", content) + } else { + return bt.templateTXT.ExecuteTemplate(w, bt.name+".txt", content) + } +} +func templateFileHTML(name string) string { + return fmt.Sprintf("comms/template/%s.html", name) +} +func templateFileTXT(name string) string { + return fmt.Sprintf("comms/template/%s.txt", name) +} + +func buildTemplate(name string) *builtTemplate { + files_on_disk := true + file_html := templateFileHTML(name) + file_txt := templateFileTXT(name) + for _, f := range []string{file_html, file_txt} { + _, err := os.Stat(f) + if err != nil { + files_on_disk = false + if !config.IsProductionEnvironment() { + log.Warn().Str("file", f).Msg("email template file is not on disk") + } + break + } + } + var result builtTemplate + if files_on_disk { + result = builtTemplate{ + name: name, + templateHTML: nil, + templateTXT: nil, + } + } else { + result = builtTemplate{ + name: name, + templateHTML: parseEmbeddedHTML(embeddedFiles, "comms", file_html), + templateTXT: parseEmbeddedTXT(embeddedFiles, "comms", file_txt), + } + } + return &result +} + +func parseEmbeddedHTML(embeddedFiles embed.FS, subdir string, file string) *templatehtml.Template { + // Remap the file names to embedded paths + embeddedFilePaths := []string{strings.TrimPrefix(file, subdir)} + name := path.Base(embeddedFilePaths[0]) + log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template") + return templatehtml.Must( + templatehtml.New(name).ParseFS(embeddedFiles, embeddedFilePaths...)) +} +func parseEmbeddedTXT(embeddedFiles embed.FS, subdir string, file string) *templatetxt.Template { + // Remap the file names to embedded paths + embeddedFilePaths := []string{strings.TrimPrefix(file, subdir)} + name := path.Base(embeddedFilePaths[0]) + log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template") + return templatetxt.Must( + templatetxt.New(name).ParseFS(embeddedFiles, embeddedFilePaths...)) +} + +func parseFromDiskHTML(file string) (*templatehtml.Template, error) { + name := path.Base(file) + //log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk") + templ, err := templatehtml.New(name).ParseFiles(file) + if err != nil { + return nil, fmt.Errorf("Failed to parse %s: %w", file, err) + } + return templ, nil +} + +func parseFromDiskTXT(file string) (*templatetxt.Template, error) { + name := path.Base(file) + //log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk") + templ, err := templatetxt.New(name).ParseFiles(file) + if err != nil { + return nil, fmt.Errorf("Failed to parse %s: %w", file, err) + } + return templ, nil +} + +func renderEmailTemplates(t *builtTemplate, content interface{}) (text string, html string, err error) { + buf_txt := &bytes.Buffer{} + err = t.executeTemplateTXT(buf_txt, content) + if err != nil { + return "", "", fmt.Errorf("Failed to render TXT template: %w", err) + } + buf_html := &bytes.Buffer{} + err = t.executeTemplateHTML(buf_html, content) + if err != nil { + return "", "", fmt.Errorf("Failed to render HTML template: %w", err) + } + return buf_txt.String(), buf_html.String(), nil +} diff --git a/comms/template/report-subscription-confirmation.html b/comms/template/report-subscription-confirmation.html new file mode 100644 index 00000000..8fe723b1 --- /dev/null +++ b/comms/template/report-subscription-confirmation.html @@ -0,0 +1,99 @@ + + + + + + Thank You for Your Mosquito Report + + + +
+
+ Email not displaying correctly? View it in your browser +
+ +
+ + +
+ +
+

Thank You for Your Report

+ +

We've received your mosquito report. Thanks! We appreciate you taking the time to submit it.

+ +

You can check the current status of your report at any time by clicking the button below:

+ + + +

We'll send you additional updates as work is scheduled and completed.

+ +

If you have any questions or need further assistance, please don't hesitate to contact us by replying to this email.

+
+ + +
+ + diff --git a/comms/template/report-subscription-confirmation.txt b/comms/template/report-subscription-confirmation.txt new file mode 100644 index 00000000..31df8a44 --- /dev/null +++ b/comms/template/report-subscription-confirmation.txt @@ -0,0 +1,9 @@ +We've received your mosquito report. Thanks! We appreciate you taking the time to submit it. + +You can check the current status of your report at any time at {{.URLReportStatus}} + +We'll send you additional updates as work is scheduled and completed. + +If you have any questions or need further assistance, please don't hesitate to contact us by replying to this email. + +If you no longer wish to receive these updates, navigate your browser to {{.URLReportUnsubscribe}} to unsubscribe. diff --git a/public-report/quick.go b/public-report/quick.go index eadda73e..a3cb41e8 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -7,7 +7,6 @@ import ( "time" "github.com/Gleipnir-Technology/nidus-sync/comms" - "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" @@ -182,12 +181,10 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { return } if email != "" { - comms.SendEmail(comms.EmailRequest{ - From: config.ForwardEmailReportAddress, - To: email, - Subject: "test email", - Text: "This is just testing that I can send email", - }) + err := comms.SendEmailReportConfirmation(email, report_id) + if err != nil { + log.Error().Err(err).Msg("Failed to send email") + } } if phone != "" { //err := comms.SendSMS(phone, "testing 1 2 3") From 4ab3c355c5187ef5e6bb803f63cc4819dec6060c Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 19 Jan 2026 18:10:17 +0000 Subject: [PATCH 0055/1453] Parse email send response, log the email ID. --- comms/email.go | 54 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/comms/email.go b/comms/email.go index cd1e930e..86accbaf 100644 --- a/comms/email.go +++ b/comms/email.go @@ -23,13 +23,18 @@ func SendEmailReportConfirmation(to string, report_id string) error { if err != nil { return fmt.Errorf("Failed to render template %s: %w", reportConfirmationT.name, err) } - return sendEmail(emailRequest{ + resp, err := sendEmail(emailRequest{ From: config.ForwardEmailReportAddress, HTML: html, - Subject: fmt.Sprintf("Mosquito Report %s Submission", report_id), + Subject: fmt.Sprintf("Mosquito Report Submission - %s", report_id), Text: text, To: to, }) + if err != nil { + return fmt.Errorf("Failed to send email report confirmation to %s for report %s: %w", to, report_id, err) + } + log.Info().Str("id", resp.ID).Str("to", to).Str("report_id", report_id).Msg("Sent report confirmation email") + return nil } var ( @@ -66,16 +71,44 @@ type emailRequest struct { References []string `json:"references,omitempty"` } -type emailResponse struct { - Message string `json:"message"` +type emailEnvelope struct { + From string `json:"from"` + To []string `json:"to"` } -func sendEmail(email emailRequest) error { +type emailResponse struct { + IsRedacted bool `json:"is_redacted"` + CreatedAt string `json:"created_at"` + HardBounces []string `json:"hard_bounces"` + SoftBounces []string `json:"soft_bounces"` + IsBounce bool `json:"is_bounce"` + Alias string `json:"alias"` + Domain string `json:"domain"` + User string `json:"user"` + Status string `json:"status"` + IsLocked bool `json:"is_locked"` + Envelope emailEnvelope `json:"envelope"` + RequireTLS bool `json:"requireTLS"` + MessageID string `json:"messageId"` + Headers map[string]string `json:"headers"` + Date string `json:"date"` + Subject string `json:"subject"` + Accepted []string `json:"accepted"` + Deliveries []string `json:"deliveries"` + RejectedErrors []string `json:"rejectedErrors"` + ID string `json:"id"` + Object string `json:"object"` + UpdatedAt string `json:"updated_at"` + Link string `json:"link"` + Message string `json:"message"` +} + +func sendEmail(email emailRequest) (response emailResponse, err error) { url := "https://api.forwardemail.net/v1/emails" payload, err := json.Marshal(email) if err != nil { - return fmt.Errorf("Failed to marshal email request: %w", err) + return response, fmt.Errorf("Failed to marshal email request: %w", err) } req, _ := http.NewRequest("POST", url, bytes.NewReader(payload)) @@ -87,6 +120,11 @@ func sendEmail(email emailRequest) error { defer res.Body.Close() body, _ := io.ReadAll(res.Body) - log.Info().Str("status", res.Status).Str("response_body", string(body)).Msg("Attempted to send email") - return nil + // Parse the JSON response + err = json.Unmarshal(body, &response) + if err != nil { + log.Warn().Str("status", res.Status).Str("response_body", string(body)).Msg("Attempted to send email but couldn't parse the resulting JSON") + return response, fmt.Errorf("Failed to unmarshal JSON response: %w", err) + } + return response, nil } From 1232a7c0ecfd4c05af6f5fc1735d7b76a84c0863 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 19 Jan 2026 18:19:02 +0000 Subject: [PATCH 0056/1453] Show pretty report ID in email subject --- comms/email.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/comms/email.go b/comms/email.go index 86accbaf..37d11124 100644 --- a/comms/email.go +++ b/comms/email.go @@ -13,6 +13,7 @@ import ( ) func SendEmailReportConfirmation(to string, report_id string) error { + report_id_str := publicReportID(report_id) content := contentEmailReportConfirmation{ URLLogo: "https://dev-sync.nidus.cloud/static/img/nidus-logo-no-lettering-64.png", URLReportStatus: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s", report_id), @@ -26,7 +27,7 @@ func SendEmailReportConfirmation(to string, report_id string) error { resp, err := sendEmail(emailRequest{ From: config.ForwardEmailReportAddress, HTML: html, - Subject: fmt.Sprintf("Mosquito Report Submission - %s", report_id), + Subject: fmt.Sprintf("Mosquito Report Submission - %s", report_id_str), Text: text, To: to, }) @@ -103,6 +104,13 @@ type emailResponse struct { Message string `json:"message"` } +func publicReportID(s string) string { + if len(s) != 12 { + return s + } + return s[0:4] + "-" + s[4:8] + "-" + s[8:12] +} + func sendEmail(email emailRequest) (response emailResponse, err error) { url := "https://api.forwardemail.net/v1/emails" From 42caa77b3eb266f191313f8cdee485abce029a63 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 19 Jan 2026 21:21:02 +0000 Subject: [PATCH 0057/1453] Use the same code paths to render the browser version of emails --- comms/email.go | 32 ++++++++++++++++++++++++++------ htmlpage/fileserver.go | 2 +- htmlpage/html.go | 2 +- htmlpage/response.go | 2 +- public-report/report.go | 13 +++++++++++-- public-report/routes.go | 1 + 6 files changed, 41 insertions(+), 11 deletions(-) diff --git a/comms/email.go b/comms/email.go index 37d11124..8352c092 100644 --- a/comms/email.go +++ b/comms/email.go @@ -9,17 +9,17 @@ import ( "net/http" "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/rs/zerolog/log" ) +func RenderEmailReportConfirmation(w http.ResponseWriter, report_id string) { + content := contentEmailSubscriptionConfirmation(report_id) + renderOrError(w, reportConfirmationT, content) +} func SendEmailReportConfirmation(to string, report_id string) error { report_id_str := publicReportID(report_id) - content := contentEmailReportConfirmation{ - URLLogo: "https://dev-sync.nidus.cloud/static/img/nidus-logo-no-lettering-64.png", - URLReportStatus: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s", report_id), - URLReportUnsubscribe: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s/unsubscribe", report_id), - URLViewInBrowser: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s/subscribe-confirmation", report_id), - } + content := contentEmailSubscriptionConfirmation(report_id) text, html, err := renderEmailTemplates(reportConfirmationT, content) if err != nil { return fmt.Errorf("Failed to render template %s: %w", reportConfirmationT.name, err) @@ -104,6 +104,15 @@ type emailResponse struct { Message string `json:"message"` } +func contentEmailSubscriptionConfirmation(report_id string) contentEmailReportConfirmation { + return contentEmailReportConfirmation{ + URLLogo: "https://dev-sync.nidus.cloud/static/img/nidus-logo-no-lettering-64.png", + URLReportStatus: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s", report_id), + URLReportUnsubscribe: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s/unsubscribe", report_id), + URLViewInBrowser: fmt.Sprintf("https://dev-sync.nidus.cloud/email/report/%s/subscription-confirmation", report_id), + } +} + func publicReportID(s string) string { if len(s) != 12 { return s @@ -111,6 +120,17 @@ func publicReportID(s string) string { return s[0:4] + "-" + s[4:8] + "-" + s[8:12] } +func renderOrError(w http.ResponseWriter, template *builtTemplate, context interface{}) { + buf := &bytes.Buffer{} + err := template.executeTemplateHTML(buf, context) + if err != nil { + log.Error().Err(err).Str("name", template.name).Msg("Failed to render template") + htmlpage.RespondError(w, "Failed to render template", err, http.StatusInternalServerError) + return + } + buf.WriteTo(w) +} + func sendEmail(email emailRequest) (response emailResponse, err error) { url := "https://api.forwardemail.net/v1/emails" diff --git a/htmlpage/fileserver.go b/htmlpage/fileserver.go index d84647bf..5dc69871 100644 --- a/htmlpage/fileserver.go +++ b/htmlpage/fileserver.go @@ -54,7 +54,7 @@ func FileServer(r chi.Router, path string, root http.FileSystem, embeddedFS embe // Try to open from local filesystem for development fileToServe, err = root.Open(requestedPath) if err != nil { - respondError(w, "Failed to open file", err, http.StatusNotFound) + RespondError(w, "Failed to open file", err, http.StatusNotFound) return } } diff --git a/htmlpage/html.go b/htmlpage/html.go index 0fa93df0..7d379c26 100644 --- a/htmlpage/html.go +++ b/htmlpage/html.go @@ -85,7 +85,7 @@ func RenderOrError(w http.ResponseWriter, template *BuiltTemplate, context inter err := template.executeTemplate(buf, context) if err != nil { log.Error().Err(err).Strs("files", template.files).Msg("Failed to render template") - respondError(w, "Failed to render template", err, http.StatusInternalServerError) + RespondError(w, "Failed to render template", err, http.StatusInternalServerError) return } buf.WriteTo(w) diff --git a/htmlpage/response.go b/htmlpage/response.go index 499e54fb..74b5cc2d 100644 --- a/htmlpage/response.go +++ b/htmlpage/response.go @@ -34,7 +34,7 @@ func (crw *customResponseWriter) Write(b []byte) (int, error) { } // Respond with an error that is visible to the user -func respondError(w http.ResponseWriter, m string, e error, s int) { +func RespondError(w http.ResponseWriter, m string, e error, s int) { log.Warn().Int("status", s).Err(e).Str("user message", m).Msg("Responding with an error") http.Error(w, m, s) } diff --git a/public-report/report.go b/public-report/report.go index d557e909..3a6960bd 100644 --- a/public-report/report.go +++ b/public-report/report.go @@ -4,14 +4,18 @@ import ( "crypto/rand" "fmt" "math/big" + "net/http" "strings" + + "github.com/Gleipnir-Technology/nidus-sync/comms" + "github.com/go-chi/chi/v5" ) // 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" + // Define character set (no O/0, I/l/1, 2/Z to avoid confusion) + const charset = "ABCDEFGHJKLMNPQRSTUVWXY3456789" const length = 12 var builder strings.Builder @@ -31,3 +35,8 @@ func GenerateReportID() (string, error) { return builder.String(), nil } + +func getEmailReportSubscriptionConfirmation(w http.ResponseWriter, r *http.Request) { + report_id := chi.URLParam(r, "report_id") + comms.RenderEmailReportConfirmation(w, report_id) +} diff --git a/public-report/routes.go b/public-report/routes.go index 67188698..dcd26315 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -8,6 +8,7 @@ import ( func Router() chi.Router { r := chi.NewRouter() r.Get("/", getRoot) + r.Get("/email/report/{report_id}/subscription-confirmation", getEmailReportSubscriptionConfirmation) r.Get("/nuisance", getNuisance) r.Post("/nuisance-submit", postNuisance) r.Get("/nuisance-submit-complete", getNuisanceSubmitComplete) From 98372d924dbf82bde6f617d1c550a3562dc31114 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 19 Jan 2026 21:24:34 +0000 Subject: [PATCH 0058/1453] Use proper config values in email templates --- comms/email.go | 8 ++++---- public-report/quick.go | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/comms/email.go b/comms/email.go index 8352c092..ee020986 100644 --- a/comms/email.go +++ b/comms/email.go @@ -106,10 +106,10 @@ type emailResponse struct { func contentEmailSubscriptionConfirmation(report_id string) contentEmailReportConfirmation { return contentEmailReportConfirmation{ - URLLogo: "https://dev-sync.nidus.cloud/static/img/nidus-logo-no-lettering-64.png", - URLReportStatus: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s", report_id), - URLReportUnsubscribe: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s/unsubscribe", report_id), - URLViewInBrowser: fmt.Sprintf("https://dev-sync.nidus.cloud/email/report/%s/subscription-confirmation", report_id), + URLLogo: fmt.Sprintf("https://%s/static/img/nidus-logo-no-lettering-64.png", config.URLReport), + URLReportStatus: fmt.Sprintf("https://%s/status/%s", config.URLReport, report_id), + URLReportUnsubscribe: fmt.Sprintf("https://%s/report/%s/unsubscribe", config.URLReport, report_id), + URLViewInBrowser: fmt.Sprintf("https://%s/email/report/%s/subscription-confirmation", config.URLReport, report_id), } } diff --git a/public-report/quick.go b/public-report/quick.go index a3cb41e8..354751ed 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -187,8 +187,7 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { } } if phone != "" { - //err := comms.SendSMS(phone, "testing 1 2 3") - err := comms.SendMMS(phone, "Check out my Nidus logo", "https://dev-sync.nidus.cloud/static/img/nidus-logo-no-lettering-64.png") + err := comms.SendSMS(phone, "testing 1 2 3") if err != nil { log.Error().Err(err).Msg("Failed to send SMS") } From 8f44e57c72ef283b7694e798df0b142c397a6374 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 19 Jan 2026 21:33:26 +0000 Subject: [PATCH 0059/1453] Add basic robots.txt So we get indexed. --- public-report/endpoint.go | 6 ++++++ public-report/routes.go | 1 + 2 files changed, 7 insertions(+) diff --git a/public-report/endpoint.go b/public-report/endpoint.go index 58cfab0f..206778f9 100644 --- a/public-report/endpoint.go +++ b/public-report/endpoint.go @@ -1,6 +1,7 @@ package publicreport import ( + "fmt" "net/http" "github.com/Gleipnir-Technology/nidus-sync/htmlpage" @@ -25,6 +26,11 @@ func getRoot(w http.ResponseWriter, r *http.Request) { ) } +func getRobots(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "User-agent: *\n") + fmt.Fprint(w, "Allow: /\n") +} + // Respond with an error that is visible to the user func respondError(w http.ResponseWriter, m string, e error, s int) { log.Warn().Int("status", s).Err(e).Str("user message", m).Msg("Responding with an error") diff --git a/public-report/routes.go b/public-report/routes.go index dcd26315..9f5487e7 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -8,6 +8,7 @@ import ( func Router() chi.Router { r := chi.NewRouter() r.Get("/", getRoot) + r.Get("/robots.txt", getRobots) r.Get("/email/report/{report_id}/subscription-confirmation", getEmailReportSubscriptionConfirmation) r.Get("/nuisance", getNuisance) r.Post("/nuisance-submit", postNuisance) From 842e6cff43583016a5f2bb44aa01acca287bb83b Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 20 Jan 2026 17:10:22 +0000 Subject: [PATCH 0060/1453] Move comms work to background goroutine This is a sort of random checkpoint of work * add schema for tracking messages sent to DB * add terms of service and privacy policy for RCS compliance * standardize some things about background workers * update some missing stuff from generated DB code --- api/api.go | 2 +- background/audio.go | 101 ++ background/comms.go | 52 + comms/email.go | 6 +- db/bobgen.yaml | 1 + db/dberrors/comms.email.bob.go | 17 + db/dberrors/comms.email_log.bob.go | 17 + db/dberrors/comms.phone.bob.go | 17 + db/dberrors/comms.sms_log.bob.go | 17 + db/dbinfo/comms.email.bob.go | 112 ++ db/dbinfo/comms.email_log.bob.go | 157 +++ db/dbinfo/comms.phone.bob.go | 102 ++ db/dbinfo/comms.sms_log.bob.go | 157 +++ db/dbinfo/publicreport.image.bob.go | 12 +- db/enums/enums.bob.go | 152 ++ db/factory/bobfactory_context.bob.go | 20 + db/factory/bobfactory_main.bob.go | 174 +++ db/factory/bobfactory_random.bob.go | 20 + db/factory/comms.email.bob.go | 434 ++++++ db/factory/comms.email_log.bob.go | 523 +++++++ db/factory/comms.phone.bob.go | 562 ++++++++ db/factory/comms.sms_log.bob.go | 523 +++++++ db/factory/publicreport.image.bob.go | 64 + db/migrations/00036_comms.sql | 37 + db/models/bob_counts.bob.go | 8 + db/models/bob_joins.bob.go | 8 + db/models/bob_loaders.bob.go | 16 + db/models/bob_where.bob.go | 12 + db/models/comms.email.bob.go | 737 ++++++++++ db/models/comms.email_log.bob.go | 848 ++++++++++++ db/models/comms.phone.bob.go | 1220 +++++++++++++++++ db/models/comms.sms_log.bob.go | 848 ++++++++++++ db/models/publicreport.image.bob.go | 87 +- htmlpage/static/img/rmo-banner-1440.png | Bin 0 -> 33292 bytes htmlpage/static/img/rmo-logo-224.png | Bin 0 -> 49048 bytes main.go | 16 +- public-report/endpoint.go | 21 +- public-report/page.go | 3 - public-report/quick.go | 32 +- public-report/routes.go | 2 + .../static/vendor/css/bootstrap.min.css | 7 - .../static/vendor/js/bootstrap.bundle.min.js | 7 - .../static/vendor/js/bootstrap.min.js | 7 - public-report/template/privacy.html | 212 +++ public-report/template/terms.html | 26 + queue/audio_processing.go | 106 +- queue/comms.go | 38 + 47 files changed, 7361 insertions(+), 179 deletions(-) create mode 100644 background/audio.go create mode 100644 background/comms.go create mode 100644 db/dberrors/comms.email.bob.go create mode 100644 db/dberrors/comms.email_log.bob.go create mode 100644 db/dberrors/comms.phone.bob.go create mode 100644 db/dberrors/comms.sms_log.bob.go create mode 100644 db/dbinfo/comms.email.bob.go create mode 100644 db/dbinfo/comms.email_log.bob.go create mode 100644 db/dbinfo/comms.phone.bob.go create mode 100644 db/dbinfo/comms.sms_log.bob.go create mode 100644 db/factory/comms.email.bob.go create mode 100644 db/factory/comms.email_log.bob.go create mode 100644 db/factory/comms.phone.bob.go create mode 100644 db/factory/comms.sms_log.bob.go create mode 100644 db/migrations/00036_comms.sql create mode 100644 db/models/comms.email.bob.go create mode 100644 db/models/comms.email_log.bob.go create mode 100644 db/models/comms.phone.bob.go create mode 100644 db/models/comms.sms_log.bob.go create mode 100644 htmlpage/static/img/rmo-banner-1440.png create mode 100644 htmlpage/static/img/rmo-logo-224.png delete mode 100644 public-report/static/vendor/css/bootstrap.min.css delete mode 100644 public-report/static/vendor/js/bootstrap.bundle.min.js delete mode 100644 public-report/static/vendor/js/bootstrap.min.js create mode 100644 public-report/template/privacy.html create mode 100644 public-report/template/terms.html create mode 100644 queue/comms.go diff --git a/api/api.go b/api/api.go index 7e61ea0c..3e58db81 100644 --- a/api/api.go +++ b/api/api.go @@ -74,7 +74,7 @@ func apiAudioContentPost(w http.ResponseWriter, r *http.Request, u *models.User) http.Error(w, "failed to write content file", http.StatusInternalServerError) } - queue.EnqueueAudioJob(queue.AudioJob{AudioUUID: audioUUID}) + queue.EnqueueAudioJob(queue.JobAudio{AudioUUID: audioUUID}) w.WriteHeader(http.StatusOK) } diff --git a/background/audio.go b/background/audio.go new file mode 100644 index 00000000..b61a0546 --- /dev/null +++ b/background/audio.go @@ -0,0 +1,101 @@ +package background + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + + "github.com/Gleipnir-Technology/nidus-sync/db" + "github.com/Gleipnir-Technology/nidus-sync/queue" + "github.com/Gleipnir-Technology/nidus-sync/userfile" + "github.com/google/uuid" + "github.com/rs/zerolog/log" +) + +// StartAudioWorker initializes the audio job channel and starts the worker goroutine. +func StartWorkerAudio(ctx context.Context, audioJobChannel chan queue.JobAudio) { + go func() { + for { + select { + case <-ctx.Done(): + log.Info().Msg("Audio worker shutting down.") + return + case job := <-audioJobChannel: + log.Info().Str("uuid", job.AudioUUID.String()).Msg("Processing audio job") + err := processAudioFile(job.AudioUUID) + if err != nil { + log.Error().Err(err).Str("uuid", job.AudioUUID.String()).Msg("Error processing audio file") + } + } + } + }() +} + +func normalizeAudio(audioUUID uuid.UUID) error { + source := userfile.AudioFileContentPathRaw(audioUUID.String()) + _, err := os.Stat(source) + if errors.Is(err, os.ErrNotExist) { + log.Warn().Str("source", source).Msg("file doesn't exist, skipping normalization") + return nil + } + log.Info().Str("sourcce", source).Msg("Normalizing") + destination := userfile.AudioFileContentPathNormalized(audioUUID.String()) + // Use "ffmpeg" directly, assuming it's in the system PATH + cmd := exec.Command("ffmpeg", "-i", source, "-filter:a", "loudnorm", destination) + out, err := cmd.CombinedOutput() + if err != nil { + log.Printf("FFmpeg output for normalization: %s", out) + return fmt.Errorf("ffmpeg normalization failed: %v", err) + } + err = db.NoteAudioNormalized(audioUUID.String()) + if err != nil { + return fmt.Errorf("failed to update database for normalized audio %s: %v", audioUUID, err) + } + log.Info().Str("destination", destination).Msg("Normalized audio") + return nil +} + +func processAudioFile(audioUUID uuid.UUID) error { + // Normalize audio + err := normalizeAudio(audioUUID) + if err != nil { + return fmt.Errorf("failed to normalize audio %s: %v", audioUUID, err) + } + + // Transcode to OGG + err = transcodeToOgg(audioUUID) + if err != nil { + return fmt.Errorf("failed to transcode audio %s to OGG: %v", audioUUID, err) + } + + queue.EnqueueLabelStudioJob(queue.LabelStudioJob{ + UUID: audioUUID, + }) + return nil +} + +func transcodeToOgg(audioUUID uuid.UUID) error { + source := userfile.AudioFileContentPathNormalized(audioUUID.String()) + _, err := os.Stat(source) + if errors.Is(err, os.ErrNotExist) { + log.Warn().Str("source", source).Msg("file doesn't exist, skipping OGG transcoding") + return nil + } + log.Info().Str("source", source).Msg("Transcoding to ogg") + destination := userfile.AudioFileContentPathOgg(audioUUID.String()) + // Use "ffmpeg" directly, assuming it's in the system PATH + cmd := exec.Command("ffmpeg", "-i", source, "-vn", "-acodec", "libvorbis", destination) + out, err := cmd.CombinedOutput() + if err != nil { + log.Error().Err(err).Bytes("out", out).Msg("FFmpeg output for OGG transcoding") + return fmt.Errorf("ffmpeg OGG transcoding failed: %v", err) + } + err = db.NoteAudioTranscodedToOgg(audioUUID.String()) + if err != nil { + return fmt.Errorf("failed to update database for OGG transcoded audio %s: %v", audioUUID, err) + } + log.Info().Str("destination", destination).Msg("Transcoded audio") + return nil +} diff --git a/background/comms.go b/background/comms.go new file mode 100644 index 00000000..fd1c6af4 --- /dev/null +++ b/background/comms.go @@ -0,0 +1,52 @@ +package background + +import ( + "context" + + "github.com/Gleipnir-Technology/nidus-sync/queue" + "github.com/rs/zerolog/log" +) + +func StartWorkerEmail(ctx context.Context, channel chan queue.JobEmail) { + go func() { + for { + select { + case <-ctx.Done(): + log.Info().Msg("Email worker shutting down.") + return + case job := <-channel: + err := processJobEmail(job) + if err != nil { + log.Error().Err(err).Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Error processing audio file") + } + } + } + }() +} + +func StartWorkerSMS(ctx context.Context, channel chan queue.JobSMS) { + go func() { + for { + select { + case <-ctx.Done(): + log.Info().Msg("Email worker shutting down.") + return + case job := <-channel: + err := processJobSMS(job) + if err != nil { + log.Error().Err(err).Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Error processing audio file") + } + } + } + }() +} + +func processJobEmail(job queue.JobEmail) error { + log.Info().Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Pretend doing email job") + return nil +} + +func processJobSMS(job queue.JobSMS) error { + log.Info().Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Pretend doing email job") + return nil +} diff --git a/comms/email.go b/comms/email.go index ee020986..64d7f970 100644 --- a/comms/email.go +++ b/comms/email.go @@ -131,15 +131,15 @@ func renderOrError(w http.ResponseWriter, template *builtTemplate, context inter buf.WriteTo(w) } -func sendEmail(email emailRequest) (response emailResponse, err error) { - url := "https://api.forwardemail.net/v1/emails" +var FORWARDEMAIL_API = "https://api.forwardemail.net/v1/emails" +func sendEmail(email emailRequest) (response emailResponse, err error) { payload, err := json.Marshal(email) if err != nil { return response, fmt.Errorf("Failed to marshal email request: %w", err) } - req, _ := http.NewRequest("POST", url, bytes.NewReader(payload)) + req, _ := http.NewRequest("POST", FORWARDEMAIL_API, bytes.NewReader(payload)) req.SetBasicAuth(config.ForwardEmailAPIToken, "") req.Header.Add("Content-Type", "application/json") diff --git a/db/bobgen.yaml b/db/bobgen.yaml index 47f2dcc1..1480da9e 100644 --- a/db/bobgen.yaml +++ b/db/bobgen.yaml @@ -13,6 +13,7 @@ no_tests: true psql: schemas: - "arcgis" + - "comms" - "import" - "public" - "publicreport" diff --git a/db/dberrors/comms.email.bob.go b/db/dberrors/comms.email.bob.go new file mode 100644 index 00000000..09f25a12 --- /dev/null +++ b/db/dberrors/comms.email.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var CommsEmailErrors = &commsEmailErrors{ + ErrUniqueEmailPkey: &UniqueConstraintError{ + schema: "comms", + table: "email", + columns: []string{"address"}, + s: "email_pkey", + }, +} + +type commsEmailErrors struct { + ErrUniqueEmailPkey *UniqueConstraintError +} diff --git a/db/dberrors/comms.email_log.bob.go b/db/dberrors/comms.email_log.bob.go new file mode 100644 index 00000000..6948b065 --- /dev/null +++ b/db/dberrors/comms.email_log.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var CommsEmailLogErrors = &commsEmailLogErrors{ + ErrUniqueEmailLogPkey: &UniqueConstraintError{ + schema: "comms", + table: "email_log", + columns: []string{"destination", "source", "type"}, + s: "email_log_pkey", + }, +} + +type commsEmailLogErrors struct { + ErrUniqueEmailLogPkey *UniqueConstraintError +} diff --git a/db/dberrors/comms.phone.bob.go b/db/dberrors/comms.phone.bob.go new file mode 100644 index 00000000..ee7af99c --- /dev/null +++ b/db/dberrors/comms.phone.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var CommsPhoneErrors = &commsPhoneErrors{ + ErrUniquePhonePkey: &UniqueConstraintError{ + schema: "comms", + table: "phone", + columns: []string{"e164"}, + s: "phone_pkey", + }, +} + +type commsPhoneErrors struct { + ErrUniquePhonePkey *UniqueConstraintError +} diff --git a/db/dberrors/comms.sms_log.bob.go b/db/dberrors/comms.sms_log.bob.go new file mode 100644 index 00000000..e4d154a4 --- /dev/null +++ b/db/dberrors/comms.sms_log.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var CommsSMSLogErrors = &commsSMSLogErrors{ + ErrUniqueSmsLogPkey: &UniqueConstraintError{ + schema: "comms", + table: "sms_log", + columns: []string{"destination", "source", "type"}, + s: "sms_log_pkey", + }, +} + +type commsSMSLogErrors struct { + ErrUniqueSmsLogPkey *UniqueConstraintError +} diff --git a/db/dbinfo/comms.email.bob.go b/db/dbinfo/comms.email.bob.go new file mode 100644 index 00000000..8e5d1b15 --- /dev/null +++ b/db/dbinfo/comms.email.bob.go @@ -0,0 +1,112 @@ +// Code generated by BobGen psql v0.42.1. 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 CommsEmails = Table[ + commsEmailColumns, + commsEmailIndexes, + commsEmailForeignKeys, + commsEmailUniques, + commsEmailChecks, +]{ + Schema: "comms", + Name: "email", + Columns: commsEmailColumns{ + Address: column{ + Name: "address", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Confirmed: column{ + Name: "confirmed", + DBType: "boolean", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + IsSubscribed: column{ + Name: "is_subscribed", + DBType: "boolean", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: commsEmailIndexes{ + EmailPkey: index{ + Type: "btree", + Name: "email_pkey", + Columns: []indexColumn{ + { + Name: "address", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "email_pkey", + Columns: []string{"address"}, + Comment: "", + }, + + Comment: "", +} + +type commsEmailColumns struct { + Address column + Confirmed column + IsSubscribed column +} + +func (c commsEmailColumns) AsSlice() []column { + return []column{ + c.Address, c.Confirmed, c.IsSubscribed, + } +} + +type commsEmailIndexes struct { + EmailPkey index +} + +func (i commsEmailIndexes) AsSlice() []index { + return []index{ + i.EmailPkey, + } +} + +type commsEmailForeignKeys struct{} + +func (f commsEmailForeignKeys) AsSlice() []foreignKey { + return []foreignKey{} +} + +type commsEmailUniques struct{} + +func (u commsEmailUniques) AsSlice() []constraint { + return []constraint{} +} + +type commsEmailChecks struct{} + +func (c commsEmailChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/comms.email_log.bob.go b/db/dbinfo/comms.email_log.bob.go new file mode 100644 index 00000000..055042bf --- /dev/null +++ b/db/dbinfo/comms.email_log.bob.go @@ -0,0 +1,157 @@ +// Code generated by BobGen psql v0.42.1. 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 CommsEmailLogs = Table[ + commsEmailLogColumns, + commsEmailLogIndexes, + commsEmailLogForeignKeys, + commsEmailLogUniques, + commsEmailLogChecks, +]{ + Schema: "comms", + Name: "email_log", + Columns: commsEmailLogColumns{ + Created: column{ + Name: "created", + DBType: "timestamp without time zone", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Destination: column{ + Name: "destination", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Source: column{ + Name: "source", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Type: column{ + Name: "type", + DBType: "comms.emailmessagetype", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: commsEmailLogIndexes{ + EmailLogPkey: index{ + Type: "btree", + Name: "email_log_pkey", + Columns: []indexColumn{ + { + Name: "destination", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + { + Name: "source", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + { + Name: "type", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false, false, false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "email_log_pkey", + Columns: []string{"destination", "source", "type"}, + Comment: "", + }, + ForeignKeys: commsEmailLogForeignKeys{ + CommsEmailLogEmailLogDestinationFkey: foreignKey{ + constraint: constraint{ + Name: "comms.email_log.email_log_destination_fkey", + Columns: []string{"destination"}, + Comment: "", + }, + ForeignTable: "comms.email", + ForeignColumns: []string{"address"}, + }, + CommsEmailLogEmailLogSourceFkey: foreignKey{ + constraint: constraint{ + Name: "comms.email_log.email_log_source_fkey", + Columns: []string{"source"}, + Comment: "", + }, + ForeignTable: "comms.phone", + ForeignColumns: []string{"e164"}, + }, + }, + + Comment: "", +} + +type commsEmailLogColumns struct { + Created column + Destination column + Source column + Type column +} + +func (c commsEmailLogColumns) AsSlice() []column { + return []column{ + c.Created, c.Destination, c.Source, c.Type, + } +} + +type commsEmailLogIndexes struct { + EmailLogPkey index +} + +func (i commsEmailLogIndexes) AsSlice() []index { + return []index{ + i.EmailLogPkey, + } +} + +type commsEmailLogForeignKeys struct { + CommsEmailLogEmailLogDestinationFkey foreignKey + CommsEmailLogEmailLogSourceFkey foreignKey +} + +func (f commsEmailLogForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.CommsEmailLogEmailLogDestinationFkey, f.CommsEmailLogEmailLogSourceFkey, + } +} + +type commsEmailLogUniques struct{} + +func (u commsEmailLogUniques) AsSlice() []constraint { + return []constraint{} +} + +type commsEmailLogChecks struct{} + +func (c commsEmailLogChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/comms.phone.bob.go b/db/dbinfo/comms.phone.bob.go new file mode 100644 index 00000000..2919387c --- /dev/null +++ b/db/dbinfo/comms.phone.bob.go @@ -0,0 +1,102 @@ +// Code generated by BobGen psql v0.42.1. 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 CommsPhones = Table[ + commsPhoneColumns, + commsPhoneIndexes, + commsPhoneForeignKeys, + commsPhoneUniques, + commsPhoneChecks, +]{ + Schema: "comms", + Name: "phone", + Columns: commsPhoneColumns{ + E164: column{ + Name: "e164", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + IsSubscribed: column{ + Name: "is_subscribed", + DBType: "boolean", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: commsPhoneIndexes{ + PhonePkey: index{ + Type: "btree", + Name: "phone_pkey", + Columns: []indexColumn{ + { + Name: "e164", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "phone_pkey", + Columns: []string{"e164"}, + Comment: "", + }, + + Comment: "", +} + +type commsPhoneColumns struct { + E164 column + IsSubscribed column +} + +func (c commsPhoneColumns) AsSlice() []column { + return []column{ + c.E164, c.IsSubscribed, + } +} + +type commsPhoneIndexes struct { + PhonePkey index +} + +func (i commsPhoneIndexes) AsSlice() []index { + return []index{ + i.PhonePkey, + } +} + +type commsPhoneForeignKeys struct{} + +func (f commsPhoneForeignKeys) AsSlice() []foreignKey { + return []foreignKey{} +} + +type commsPhoneUniques struct{} + +func (u commsPhoneUniques) AsSlice() []constraint { + return []constraint{} +} + +type commsPhoneChecks struct{} + +func (c commsPhoneChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/comms.sms_log.bob.go b/db/dbinfo/comms.sms_log.bob.go new file mode 100644 index 00000000..7f21a59a --- /dev/null +++ b/db/dbinfo/comms.sms_log.bob.go @@ -0,0 +1,157 @@ +// Code generated by BobGen psql v0.42.1. 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 CommsSMSLogs = Table[ + commsSMSLogColumns, + commsSMSLogIndexes, + commsSMSLogForeignKeys, + commsSMSLogUniques, + commsSMSLogChecks, +]{ + Schema: "comms", + Name: "sms_log", + Columns: commsSMSLogColumns{ + Created: column{ + Name: "created", + DBType: "timestamp without time zone", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Destination: column{ + Name: "destination", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Source: column{ + Name: "source", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Type: column{ + Name: "type", + DBType: "comms.smsmessagetype", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: commsSMSLogIndexes{ + SMSLogPkey: index{ + Type: "btree", + Name: "sms_log_pkey", + Columns: []indexColumn{ + { + Name: "destination", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + { + Name: "source", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + { + Name: "type", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false, false, false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, + }, + PrimaryKey: &constraint{ + Name: "sms_log_pkey", + Columns: []string{"destination", "source", "type"}, + Comment: "", + }, + ForeignKeys: commsSMSLogForeignKeys{ + CommsSMSLogSMSLogDestinationFkey: foreignKey{ + constraint: constraint{ + Name: "comms.sms_log.sms_log_destination_fkey", + Columns: []string{"destination"}, + Comment: "", + }, + ForeignTable: "comms.phone", + ForeignColumns: []string{"e164"}, + }, + CommsSMSLogSMSLogSourceFkey: foreignKey{ + constraint: constraint{ + Name: "comms.sms_log.sms_log_source_fkey", + Columns: []string{"source"}, + Comment: "", + }, + ForeignTable: "comms.phone", + ForeignColumns: []string{"e164"}, + }, + }, + + Comment: "", +} + +type commsSMSLogColumns struct { + Created column + Destination column + Source column + Type column +} + +func (c commsSMSLogColumns) AsSlice() []column { + return []column{ + c.Created, c.Destination, c.Source, c.Type, + } +} + +type commsSMSLogIndexes struct { + SMSLogPkey index +} + +func (i commsSMSLogIndexes) AsSlice() []index { + return []index{ + i.SMSLogPkey, + } +} + +type commsSMSLogForeignKeys struct { + CommsSMSLogSMSLogDestinationFkey foreignKey + CommsSMSLogSMSLogSourceFkey foreignKey +} + +func (f commsSMSLogForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.CommsSMSLogSMSLogDestinationFkey, f.CommsSMSLogSMSLogSourceFkey, + } +} + +type commsSMSLogUniques struct{} + +func (u commsSMSLogUniques) AsSlice() []constraint { + return []constraint{} +} + +type commsSMSLogChecks struct{} + +func (c commsSMSLogChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/publicreport.image.bob.go b/db/dbinfo/publicreport.image.bob.go index aca07c41..231da24f 100644 --- a/db/dbinfo/publicreport.image.bob.go +++ b/db/dbinfo/publicreport.image.bob.go @@ -42,6 +42,15 @@ var PublicreportImages = Table[ Generated: false, AutoIncr: false, }, + Location: column{ + Name: "location", + DBType: "geography", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, ResolutionX: column{ Name: "resolution_x", DBType: "integer", @@ -120,6 +129,7 @@ type publicreportImageColumns struct { ID column ContentType column Created column + Location column ResolutionX column ResolutionY column StorageUUID column @@ -129,7 +139,7 @@ type publicreportImageColumns struct { func (c publicreportImageColumns) AsSlice() []column { return []column{ - c.ID, c.ContentType, c.Created, c.ResolutionX, c.ResolutionY, c.StorageUUID, c.StorageSize, c.UploadedFilename, + c.ID, c.ContentType, c.Created, c.Location, c.ResolutionX, c.ResolutionY, c.StorageUUID, c.StorageSize, c.UploadedFilename, } } diff --git a/db/enums/enums.bob.go b/db/enums/enums.bob.go index 0fe4f29e..22fde9b7 100644 --- a/db/enums/enums.bob.go +++ b/db/enums/enums.bob.go @@ -193,6 +193,158 @@ func (e *Audiodatatype) Scan(value any) error { return nil } +// Enum values for CommsEmailmessagetype +const ( + CommsEmailmessagetypeReportSubscriptionConfirmation CommsEmailmessagetype = "report-subscription-confirmation" + CommsEmailmessagetypeReportStatusScheduled CommsEmailmessagetype = "report-status-scheduled" + CommsEmailmessagetypeReportStatusComplete CommsEmailmessagetype = "report-status-complete" +) + +func AllCommsEmailmessagetype() []CommsEmailmessagetype { + return []CommsEmailmessagetype{ + CommsEmailmessagetypeReportSubscriptionConfirmation, + CommsEmailmessagetypeReportStatusScheduled, + CommsEmailmessagetypeReportStatusComplete, + } +} + +type CommsEmailmessagetype string + +func (e CommsEmailmessagetype) String() string { + return string(e) +} + +func (e CommsEmailmessagetype) Valid() bool { + switch e { + case CommsEmailmessagetypeReportSubscriptionConfirmation, + CommsEmailmessagetypeReportStatusScheduled, + CommsEmailmessagetypeReportStatusComplete: + return true + default: + return false + } +} + +// useful when testing in other packages +func (e CommsEmailmessagetype) All() []CommsEmailmessagetype { + return AllCommsEmailmessagetype() +} + +func (e CommsEmailmessagetype) MarshalText() ([]byte, error) { + return []byte(e), nil +} + +func (e *CommsEmailmessagetype) UnmarshalText(text []byte) error { + return e.Scan(text) +} + +func (e CommsEmailmessagetype) MarshalBinary() ([]byte, error) { + return []byte(e), nil +} + +func (e *CommsEmailmessagetype) UnmarshalBinary(data []byte) error { + return e.Scan(data) +} + +func (e CommsEmailmessagetype) Value() (driver.Value, error) { + return string(e), nil +} + +func (e *CommsEmailmessagetype) Scan(value any) error { + switch x := value.(type) { + case string: + *e = CommsEmailmessagetype(x) + case []byte: + *e = CommsEmailmessagetype(x) + case nil: + return fmt.Errorf("cannot nil into CommsEmailmessagetype") + default: + return fmt.Errorf("cannot scan type %T: %v", value, value) + } + + if !e.Valid() { + return fmt.Errorf("invalid CommsEmailmessagetype value: %s", *e) + } + + return nil +} + +// Enum values for CommsSmsmessagetype +const ( + CommsSmsmessagetypeReportSubscriptionConfirmation CommsSmsmessagetype = "report-subscription-confirmation" + CommsSmsmessagetypeReportStatusScheduled CommsSmsmessagetype = "report-status-scheduled" + CommsSmsmessagetypeReportStatusComplete CommsSmsmessagetype = "report-status-complete" +) + +func AllCommsSmsmessagetype() []CommsSmsmessagetype { + return []CommsSmsmessagetype{ + CommsSmsmessagetypeReportSubscriptionConfirmation, + CommsSmsmessagetypeReportStatusScheduled, + CommsSmsmessagetypeReportStatusComplete, + } +} + +type CommsSmsmessagetype string + +func (e CommsSmsmessagetype) String() string { + return string(e) +} + +func (e CommsSmsmessagetype) Valid() bool { + switch e { + case CommsSmsmessagetypeReportSubscriptionConfirmation, + CommsSmsmessagetypeReportStatusScheduled, + CommsSmsmessagetypeReportStatusComplete: + return true + default: + return false + } +} + +// useful when testing in other packages +func (e CommsSmsmessagetype) All() []CommsSmsmessagetype { + return AllCommsSmsmessagetype() +} + +func (e CommsSmsmessagetype) MarshalText() ([]byte, error) { + return []byte(e), nil +} + +func (e *CommsSmsmessagetype) UnmarshalText(text []byte) error { + return e.Scan(text) +} + +func (e CommsSmsmessagetype) MarshalBinary() ([]byte, error) { + return []byte(e), nil +} + +func (e *CommsSmsmessagetype) UnmarshalBinary(data []byte) error { + return e.Scan(data) +} + +func (e CommsSmsmessagetype) Value() (driver.Value, error) { + return string(e), nil +} + +func (e *CommsSmsmessagetype) Scan(value any) error { + switch x := value.(type) { + case string: + *e = CommsSmsmessagetype(x) + case []byte: + *e = CommsSmsmessagetype(x) + case nil: + return fmt.Errorf("cannot nil into CommsSmsmessagetype") + default: + return fmt.Errorf("cannot scan type %T: %v", value, value) + } + + if !e.Valid() { + return fmt.Errorf("invalid CommsSmsmessagetype value: %s", *e) + } + + return nil +} + // Enum values for H3aggregationtype const ( H3aggregationtypeMosquitosource H3aggregationtype = "MosquitoSource" diff --git a/db/factory/bobfactory_context.bob.go b/db/factory/bobfactory_context.bob.go index 7e9c7a84..ae8248ec 100644 --- a/db/factory/bobfactory_context.bob.go +++ b/db/factory/bobfactory_context.bob.go @@ -17,6 +17,26 @@ var ( arcgisUserPrivilegeWithParentsCascadingCtx = newContextual[bool]("arcgisUserPrivilegeWithParentsCascading") arcgisUserPrivilegeRelUserUserCtx = newContextual[bool]("arcgis.user_.arcgis.user_privilege.arcgis.user_privilege.user_privilege_user_id_fkey") + // Relationship Contexts for comms.email + commsEmailWithParentsCascadingCtx = newContextual[bool]("commsEmailWithParentsCascading") + commsEmailRelDestinationEmailLogsCtx = newContextual[bool]("comms.email.comms.email_log.comms.email_log.email_log_destination_fkey") + + // Relationship Contexts for comms.email_log + commsEmailLogWithParentsCascadingCtx = newContextual[bool]("commsEmailLogWithParentsCascading") + commsEmailLogRelDestinationEmailCtx = newContextual[bool]("comms.email.comms.email_log.comms.email_log.email_log_destination_fkey") + commsEmailLogRelSourcePhoneCtx = newContextual[bool]("comms.email_log.comms.phone.comms.email_log.email_log_source_fkey") + + // Relationship Contexts for comms.phone + commsPhoneWithParentsCascadingCtx = newContextual[bool]("commsPhoneWithParentsCascading") + commsPhoneRelSourceEmailLogsCtx = newContextual[bool]("comms.email_log.comms.phone.comms.email_log.email_log_source_fkey") + commsPhoneRelDestinationSMSLogsCtx = newContextual[bool]("comms.phone.comms.sms_log.comms.sms_log.sms_log_destination_fkey") + commsPhoneRelSourceSMSLogsCtx = newContextual[bool]("comms.phone.comms.sms_log.comms.sms_log.sms_log_source_fkey") + + // Relationship Contexts for comms.sms_log + commsSMSLogWithParentsCascadingCtx = newContextual[bool]("commsSMSLogWithParentsCascading") + commsSMSLogRelDestinationPhoneCtx = newContextual[bool]("comms.phone.comms.sms_log.comms.sms_log.sms_log_destination_fkey") + commsSMSLogRelSourcePhoneCtx = newContextual[bool]("comms.phone.comms.sms_log.comms.sms_log.sms_log_source_fkey") + // Relationship Contexts for fieldseeker.containerrelate fieldseekerContainerrelateWithParentsCascadingCtx = newContextual[bool]("fieldseekerContainerrelateWithParentsCascading") fieldseekerContainerrelateRelOrganizationCtx = newContextual[bool]("fieldseeker.containerrelate.organization.fieldseeker.containerrelate.containerrelate_organization_id_fkey") diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index d184b64e..e74cd371 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -20,6 +20,10 @@ import ( type Factory struct { baseArcgisUserMods ArcgisUserModSlice baseArcgisUserPrivilegeMods ArcgisUserPrivilegeModSlice + baseCommsEmailMods CommsEmailModSlice + baseCommsEmailLogMods CommsEmailLogModSlice + baseCommsPhoneMods CommsPhoneModSlice + baseCommsSMSLogMods CommsSMSLogModSlice baseFieldseekerContainerrelateMods FieldseekerContainerrelateModSlice baseFieldseekerFieldscoutinglogMods FieldseekerFieldscoutinglogModSlice baseFieldseekerHabitatrelateMods FieldseekerHabitatrelateModSlice @@ -156,6 +160,143 @@ func (f *Factory) FromExistingArcgisUserPrivilege(m *models.ArcgisUserPrivilege) return o } +func (f *Factory) NewCommsEmail(mods ...CommsEmailMod) *CommsEmailTemplate { + return f.NewCommsEmailWithContext(context.Background(), mods...) +} + +func (f *Factory) NewCommsEmailWithContext(ctx context.Context, mods ...CommsEmailMod) *CommsEmailTemplate { + o := &CommsEmailTemplate{f: f} + + if f != nil { + f.baseCommsEmailMods.Apply(ctx, o) + } + + CommsEmailModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingCommsEmail(m *models.CommsEmail) *CommsEmailTemplate { + o := &CommsEmailTemplate{f: f, alreadyPersisted: true} + + o.Address = func() string { return m.Address } + o.Confirmed = func() bool { return m.Confirmed } + o.IsSubscribed = func() bool { return m.IsSubscribed } + + ctx := context.Background() + if len(m.R.DestinationEmailLogs) > 0 { + CommsEmailMods.AddExistingDestinationEmailLogs(m.R.DestinationEmailLogs...).Apply(ctx, o) + } + + return o +} + +func (f *Factory) NewCommsEmailLog(mods ...CommsEmailLogMod) *CommsEmailLogTemplate { + return f.NewCommsEmailLogWithContext(context.Background(), mods...) +} + +func (f *Factory) NewCommsEmailLogWithContext(ctx context.Context, mods ...CommsEmailLogMod) *CommsEmailLogTemplate { + o := &CommsEmailLogTemplate{f: f} + + if f != nil { + f.baseCommsEmailLogMods.Apply(ctx, o) + } + + CommsEmailLogModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingCommsEmailLog(m *models.CommsEmailLog) *CommsEmailLogTemplate { + o := &CommsEmailLogTemplate{f: f, alreadyPersisted: true} + + o.Created = func() time.Time { return m.Created } + o.Destination = func() string { return m.Destination } + o.Source = func() string { return m.Source } + o.Type = func() enums.CommsEmailmessagetype { return m.Type } + + ctx := context.Background() + if m.R.DestinationEmail != nil { + CommsEmailLogMods.WithExistingDestinationEmail(m.R.DestinationEmail).Apply(ctx, o) + } + if m.R.SourcePhone != nil { + CommsEmailLogMods.WithExistingSourcePhone(m.R.SourcePhone).Apply(ctx, o) + } + + return o +} + +func (f *Factory) NewCommsPhone(mods ...CommsPhoneMod) *CommsPhoneTemplate { + return f.NewCommsPhoneWithContext(context.Background(), mods...) +} + +func (f *Factory) NewCommsPhoneWithContext(ctx context.Context, mods ...CommsPhoneMod) *CommsPhoneTemplate { + o := &CommsPhoneTemplate{f: f} + + if f != nil { + f.baseCommsPhoneMods.Apply(ctx, o) + } + + CommsPhoneModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingCommsPhone(m *models.CommsPhone) *CommsPhoneTemplate { + o := &CommsPhoneTemplate{f: f, alreadyPersisted: true} + + o.E164 = func() string { return m.E164 } + o.IsSubscribed = func() bool { return m.IsSubscribed } + + ctx := context.Background() + if len(m.R.SourceEmailLogs) > 0 { + CommsPhoneMods.AddExistingSourceEmailLogs(m.R.SourceEmailLogs...).Apply(ctx, o) + } + if len(m.R.DestinationSMSLogs) > 0 { + CommsPhoneMods.AddExistingDestinationSMSLogs(m.R.DestinationSMSLogs...).Apply(ctx, o) + } + if len(m.R.SourceSMSLogs) > 0 { + CommsPhoneMods.AddExistingSourceSMSLogs(m.R.SourceSMSLogs...).Apply(ctx, o) + } + + return o +} + +func (f *Factory) NewCommsSMSLog(mods ...CommsSMSLogMod) *CommsSMSLogTemplate { + return f.NewCommsSMSLogWithContext(context.Background(), mods...) +} + +func (f *Factory) NewCommsSMSLogWithContext(ctx context.Context, mods ...CommsSMSLogMod) *CommsSMSLogTemplate { + o := &CommsSMSLogTemplate{f: f} + + if f != nil { + f.baseCommsSMSLogMods.Apply(ctx, o) + } + + CommsSMSLogModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingCommsSMSLog(m *models.CommsSMSLog) *CommsSMSLogTemplate { + o := &CommsSMSLogTemplate{f: f, alreadyPersisted: true} + + o.Created = func() time.Time { return m.Created } + o.Destination = func() string { return m.Destination } + o.Source = func() string { return m.Source } + o.Type = func() enums.CommsSmsmessagetype { return m.Type } + + ctx := context.Background() + if m.R.DestinationPhone != nil { + CommsSMSLogMods.WithExistingDestinationPhone(m.R.DestinationPhone).Apply(ctx, o) + } + if m.R.SourcePhone != nil { + CommsSMSLogMods.WithExistingSourcePhone(m.R.SourcePhone).Apply(ctx, o) + } + + return o +} + func (f *Factory) NewFieldseekerContainerrelate(mods ...FieldseekerContainerrelateMod) *FieldseekerContainerrelateTemplate { return f.NewFieldseekerContainerrelateWithContext(context.Background(), mods...) } @@ -2536,6 +2677,7 @@ func (f *Factory) FromExistingPublicreportImage(m *models.PublicreportImage) *Pu o.ID = func() int32 { return m.ID } o.ContentType = func() string { return m.ContentType } o.Created = func() time.Time { return m.Created } + o.Location = func() null.Val[string] { return m.Location } o.ResolutionX = func() int32 { return m.ResolutionX } o.ResolutionY = func() int32 { return m.ResolutionY } o.StorageUUID = func() uuid.UUID { return m.StorageUUID } @@ -3032,6 +3174,38 @@ func (f *Factory) AddBaseArcgisUserPrivilegeMod(mods ...ArcgisUserPrivilegeMod) f.baseArcgisUserPrivilegeMods = append(f.baseArcgisUserPrivilegeMods, mods...) } +func (f *Factory) ClearBaseCommsEmailMods() { + f.baseCommsEmailMods = nil +} + +func (f *Factory) AddBaseCommsEmailMod(mods ...CommsEmailMod) { + f.baseCommsEmailMods = append(f.baseCommsEmailMods, mods...) +} + +func (f *Factory) ClearBaseCommsEmailLogMods() { + f.baseCommsEmailLogMods = nil +} + +func (f *Factory) AddBaseCommsEmailLogMod(mods ...CommsEmailLogMod) { + f.baseCommsEmailLogMods = append(f.baseCommsEmailLogMods, mods...) +} + +func (f *Factory) ClearBaseCommsPhoneMods() { + f.baseCommsPhoneMods = nil +} + +func (f *Factory) AddBaseCommsPhoneMod(mods ...CommsPhoneMod) { + f.baseCommsPhoneMods = append(f.baseCommsPhoneMods, mods...) +} + +func (f *Factory) ClearBaseCommsSMSLogMods() { + f.baseCommsSMSLogMods = nil +} + +func (f *Factory) AddBaseCommsSMSLogMod(mods ...CommsSMSLogMod) { + f.baseCommsSMSLogMods = append(f.baseCommsSMSLogMods, mods...) +} + func (f *Factory) ClearBaseFieldseekerContainerrelateMods() { f.baseFieldseekerContainerrelateMods = nil } diff --git a/db/factory/bobfactory_random.bob.go b/db/factory/bobfactory_random.bob.go index 0f93c1ad..38bd82d3 100644 --- a/db/factory/bobfactory_random.bob.go +++ b/db/factory/bobfactory_random.bob.go @@ -89,6 +89,26 @@ func random_enums_Audiodatatype(f *faker.Faker, limits ...string) enums.Audiodat return all[f.IntBetween(0, len(all)-1)] } +func random_enums_CommsEmailmessagetype(f *faker.Faker, limits ...string) enums.CommsEmailmessagetype { + if f == nil { + f = &defaultFaker + } + + var e enums.CommsEmailmessagetype + all := e.All() + return all[f.IntBetween(0, len(all)-1)] +} + +func random_enums_CommsSmsmessagetype(f *faker.Faker, limits ...string) enums.CommsSmsmessagetype { + if f == nil { + f = &defaultFaker + } + + var e enums.CommsSmsmessagetype + all := e.All() + return all[f.IntBetween(0, len(all)-1)] +} + func random_enums_H3aggregationtype(f *faker.Faker, limits ...string) enums.H3aggregationtype { if f == nil { f = &defaultFaker diff --git a/db/factory/comms.email.bob.go b/db/factory/comms.email.bob.go new file mode 100644 index 00000000..610aae07 --- /dev/null +++ b/db/factory/comms.email.bob.go @@ -0,0 +1,434 @@ +// Code generated by BobGen psql v0.42.1. 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/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type CommsEmailMod interface { + Apply(context.Context, *CommsEmailTemplate) +} + +type CommsEmailModFunc func(context.Context, *CommsEmailTemplate) + +func (f CommsEmailModFunc) Apply(ctx context.Context, n *CommsEmailTemplate) { + f(ctx, n) +} + +type CommsEmailModSlice []CommsEmailMod + +func (mods CommsEmailModSlice) Apply(ctx context.Context, n *CommsEmailTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// CommsEmailTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type CommsEmailTemplate struct { + Address func() string + Confirmed func() bool + IsSubscribed func() bool + + r commsEmailR + f *Factory + + alreadyPersisted bool +} + +type commsEmailR struct { + DestinationEmailLogs []*commsEmailRDestinationEmailLogsR +} + +type commsEmailRDestinationEmailLogsR struct { + number int + o *CommsEmailLogTemplate +} + +// Apply mods to the CommsEmailTemplate +func (o *CommsEmailTemplate) Apply(ctx context.Context, mods ...CommsEmailMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.CommsEmail +// according to the relationships in the template. Nothing is inserted into the db +func (t CommsEmailTemplate) setModelRels(o *models.CommsEmail) { + if t.r.DestinationEmailLogs != nil { + rel := models.CommsEmailLogSlice{} + for _, r := range t.r.DestinationEmailLogs { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.Destination = o.Address // h2 + rel.R.DestinationEmail = o + } + rel = append(rel, related...) + } + o.R.DestinationEmailLogs = rel + } +} + +// BuildSetter returns an *models.CommsEmailSetter +// this does nothing with the relationship templates +func (o CommsEmailTemplate) BuildSetter() *models.CommsEmailSetter { + m := &models.CommsEmailSetter{} + + if o.Address != nil { + val := o.Address() + m.Address = omit.From(val) + } + if o.Confirmed != nil { + val := o.Confirmed() + m.Confirmed = omit.From(val) + } + if o.IsSubscribed != nil { + val := o.IsSubscribed() + m.IsSubscribed = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.CommsEmailSetter +// this does nothing with the relationship templates +func (o CommsEmailTemplate) BuildManySetter(number int) []*models.CommsEmailSetter { + m := make([]*models.CommsEmailSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.CommsEmail +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsEmailTemplate.Create +func (o CommsEmailTemplate) Build() *models.CommsEmail { + m := &models.CommsEmail{} + + if o.Address != nil { + m.Address = o.Address() + } + if o.Confirmed != nil { + m.Confirmed = o.Confirmed() + } + if o.IsSubscribed != nil { + m.IsSubscribed = o.IsSubscribed() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.CommsEmailSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsEmailTemplate.CreateMany +func (o CommsEmailTemplate) BuildMany(number int) models.CommsEmailSlice { + m := make(models.CommsEmailSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableCommsEmail(m *models.CommsEmailSetter) { + if !(m.Address.IsValue()) { + val := random_string(nil) + m.Address = omit.From(val) + } + if !(m.Confirmed.IsValue()) { + val := random_bool(nil) + m.Confirmed = omit.From(val) + } + if !(m.IsSubscribed.IsValue()) { + val := random_bool(nil) + m.IsSubscribed = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.CommsEmail +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *CommsEmailTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsEmail) error { + var err error + + isDestinationEmailLogsDone, _ := commsEmailRelDestinationEmailLogsCtx.Value(ctx) + if !isDestinationEmailLogsDone && o.r.DestinationEmailLogs != nil { + ctx = commsEmailRelDestinationEmailLogsCtx.WithValue(ctx, true) + for _, r := range o.r.DestinationEmailLogs { + if r.o.alreadyPersisted { + m.R.DestinationEmailLogs = append(m.R.DestinationEmailLogs, r.o.Build()) + } else { + rel0, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachDestinationEmailLogs(ctx, exec, rel0...) + if err != nil { + return err + } + } + } + } + + return err +} + +// Create builds a commsEmail and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *CommsEmailTemplate) Create(ctx context.Context, exec bob.Executor) (*models.CommsEmail, error) { + var err error + opt := o.BuildSetter() + ensureCreatableCommsEmail(opt) + + m, err := models.CommsEmails.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a commsEmail and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *CommsEmailTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.CommsEmail { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a commsEmail 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 *CommsEmailTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.CommsEmail { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple commsEmails and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o CommsEmailTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.CommsEmailSlice, error) { + var err error + m := make(models.CommsEmailSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple commsEmails and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o CommsEmailTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.CommsEmailSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple commsEmails 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 CommsEmailTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.CommsEmailSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CommsEmail has methods that act as mods for the CommsEmailTemplate +var CommsEmailMods commsEmailMods + +type commsEmailMods struct{} + +func (m commsEmailMods) RandomizeAllColumns(f *faker.Faker) CommsEmailMod { + return CommsEmailModSlice{ + CommsEmailMods.RandomAddress(f), + CommsEmailMods.RandomConfirmed(f), + CommsEmailMods.RandomIsSubscribed(f), + } +} + +// Set the model columns to this value +func (m commsEmailMods) Address(val string) CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.Address = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailMods) AddressFunc(f func() string) CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.Address = f + }) +} + +// Clear any values for the column +func (m commsEmailMods) UnsetAddress() CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.Address = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailMods) RandomAddress(f *faker.Faker) CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.Address = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailMods) Confirmed(val bool) CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.Confirmed = func() bool { return val } + }) +} + +// Set the Column from the function +func (m commsEmailMods) ConfirmedFunc(f func() bool) CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.Confirmed = f + }) +} + +// Clear any values for the column +func (m commsEmailMods) UnsetConfirmed() CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.Confirmed = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailMods) RandomConfirmed(f *faker.Faker) CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.Confirmed = func() bool { + return random_bool(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailMods) IsSubscribed(val bool) CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.IsSubscribed = func() bool { return val } + }) +} + +// Set the Column from the function +func (m commsEmailMods) IsSubscribedFunc(f func() bool) CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.IsSubscribed = f + }) +} + +// Clear any values for the column +func (m commsEmailMods) UnsetIsSubscribed() CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.IsSubscribed = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailMods) RandomIsSubscribed(f *faker.Faker) CommsEmailMod { + return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { + o.IsSubscribed = func() bool { + return random_bool(f) + } + }) +} + +func (m commsEmailMods) WithParentsCascading() CommsEmailMod { + return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { + if isDone, _ := commsEmailWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = commsEmailWithParentsCascadingCtx.WithValue(ctx, true) + }) +} + +func (m commsEmailMods) WithDestinationEmailLogs(number int, related *CommsEmailLogTemplate) CommsEmailMod { + return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { + o.r.DestinationEmailLogs = []*commsEmailRDestinationEmailLogsR{{ + number: number, + o: related, + }} + }) +} + +func (m commsEmailMods) WithNewDestinationEmailLogs(number int, mods ...CommsEmailLogMod) CommsEmailMod { + return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { + related := o.f.NewCommsEmailLogWithContext(ctx, mods...) + m.WithDestinationEmailLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsEmailMods) AddDestinationEmailLogs(number int, related *CommsEmailLogTemplate) CommsEmailMod { + return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { + o.r.DestinationEmailLogs = append(o.r.DestinationEmailLogs, &commsEmailRDestinationEmailLogsR{ + number: number, + o: related, + }) + }) +} + +func (m commsEmailMods) AddNewDestinationEmailLogs(number int, mods ...CommsEmailLogMod) CommsEmailMod { + return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { + related := o.f.NewCommsEmailLogWithContext(ctx, mods...) + m.AddDestinationEmailLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsEmailMods) AddExistingDestinationEmailLogs(existingModels ...*models.CommsEmailLog) CommsEmailMod { + return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { + for _, em := range existingModels { + o.r.DestinationEmailLogs = append(o.r.DestinationEmailLogs, &commsEmailRDestinationEmailLogsR{ + o: o.f.FromExistingCommsEmailLog(em), + }) + } + }) +} + +func (m commsEmailMods) WithoutDestinationEmailLogs() CommsEmailMod { + return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { + o.r.DestinationEmailLogs = nil + }) +} diff --git a/db/factory/comms.email_log.bob.go b/db/factory/comms.email_log.bob.go new file mode 100644 index 00000000..1a643a2b --- /dev/null +++ b/db/factory/comms.email_log.bob.go @@ -0,0 +1,523 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/omit" + "github.com/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type CommsEmailLogMod interface { + Apply(context.Context, *CommsEmailLogTemplate) +} + +type CommsEmailLogModFunc func(context.Context, *CommsEmailLogTemplate) + +func (f CommsEmailLogModFunc) Apply(ctx context.Context, n *CommsEmailLogTemplate) { + f(ctx, n) +} + +type CommsEmailLogModSlice []CommsEmailLogMod + +func (mods CommsEmailLogModSlice) Apply(ctx context.Context, n *CommsEmailLogTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// CommsEmailLogTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type CommsEmailLogTemplate struct { + Created func() time.Time + Destination func() string + Source func() string + Type func() enums.CommsEmailmessagetype + + r commsEmailLogR + f *Factory + + alreadyPersisted bool +} + +type commsEmailLogR struct { + DestinationEmail *commsEmailLogRDestinationEmailR + SourcePhone *commsEmailLogRSourcePhoneR +} + +type commsEmailLogRDestinationEmailR struct { + o *CommsEmailTemplate +} +type commsEmailLogRSourcePhoneR struct { + o *CommsPhoneTemplate +} + +// Apply mods to the CommsEmailLogTemplate +func (o *CommsEmailLogTemplate) Apply(ctx context.Context, mods ...CommsEmailLogMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.CommsEmailLog +// according to the relationships in the template. Nothing is inserted into the db +func (t CommsEmailLogTemplate) setModelRels(o *models.CommsEmailLog) { + if t.r.DestinationEmail != nil { + rel := t.r.DestinationEmail.o.Build() + rel.R.DestinationEmailLogs = append(rel.R.DestinationEmailLogs, o) + o.Destination = rel.Address // h2 + o.R.DestinationEmail = rel + } + + if t.r.SourcePhone != nil { + rel := t.r.SourcePhone.o.Build() + rel.R.SourceEmailLogs = append(rel.R.SourceEmailLogs, o) + o.Source = rel.E164 // h2 + o.R.SourcePhone = rel + } +} + +// BuildSetter returns an *models.CommsEmailLogSetter +// this does nothing with the relationship templates +func (o CommsEmailLogTemplate) BuildSetter() *models.CommsEmailLogSetter { + m := &models.CommsEmailLogSetter{} + + if o.Created != nil { + val := o.Created() + m.Created = omit.From(val) + } + if o.Destination != nil { + val := o.Destination() + m.Destination = omit.From(val) + } + if o.Source != nil { + val := o.Source() + m.Source = omit.From(val) + } + if o.Type != nil { + val := o.Type() + m.Type = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.CommsEmailLogSetter +// this does nothing with the relationship templates +func (o CommsEmailLogTemplate) BuildManySetter(number int) []*models.CommsEmailLogSetter { + m := make([]*models.CommsEmailLogSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.CommsEmailLog +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsEmailLogTemplate.Create +func (o CommsEmailLogTemplate) Build() *models.CommsEmailLog { + m := &models.CommsEmailLog{} + + if o.Created != nil { + m.Created = o.Created() + } + if o.Destination != nil { + m.Destination = o.Destination() + } + if o.Source != nil { + m.Source = o.Source() + } + if o.Type != nil { + m.Type = o.Type() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.CommsEmailLogSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsEmailLogTemplate.CreateMany +func (o CommsEmailLogTemplate) BuildMany(number int) models.CommsEmailLogSlice { + m := make(models.CommsEmailLogSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableCommsEmailLog(m *models.CommsEmailLogSetter) { + if !(m.Created.IsValue()) { + val := random_time_Time(nil) + m.Created = omit.From(val) + } + if !(m.Destination.IsValue()) { + val := random_string(nil) + m.Destination = omit.From(val) + } + if !(m.Source.IsValue()) { + val := random_string(nil) + m.Source = omit.From(val) + } + if !(m.Type.IsValue()) { + val := random_enums_CommsEmailmessagetype(nil) + m.Type = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.CommsEmailLog +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *CommsEmailLogTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsEmailLog) error { + var err error + + return err +} + +// Create builds a commsEmailLog and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *CommsEmailLogTemplate) Create(ctx context.Context, exec bob.Executor) (*models.CommsEmailLog, error) { + var err error + opt := o.BuildSetter() + ensureCreatableCommsEmailLog(opt) + + if o.r.DestinationEmail == nil { + CommsEmailLogMods.WithNewDestinationEmail().Apply(ctx, o) + } + + var rel0 *models.CommsEmail + + if o.r.DestinationEmail.o.alreadyPersisted { + rel0 = o.r.DestinationEmail.o.Build() + } else { + rel0, err = o.r.DestinationEmail.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.Destination = omit.From(rel0.Address) + + if o.r.SourcePhone == nil { + CommsEmailLogMods.WithNewSourcePhone().Apply(ctx, o) + } + + var rel1 *models.CommsPhone + + if o.r.SourcePhone.o.alreadyPersisted { + rel1 = o.r.SourcePhone.o.Build() + } else { + rel1, err = o.r.SourcePhone.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.Source = omit.From(rel1.E164) + + m, err := models.CommsEmailLogs.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + m.R.DestinationEmail = rel0 + m.R.SourcePhone = rel1 + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a commsEmailLog and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *CommsEmailLogTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.CommsEmailLog { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a commsEmailLog 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 *CommsEmailLogTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.CommsEmailLog { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple commsEmailLogs and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o CommsEmailLogTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.CommsEmailLogSlice, error) { + var err error + m := make(models.CommsEmailLogSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple commsEmailLogs and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o CommsEmailLogTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.CommsEmailLogSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple commsEmailLogs 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 CommsEmailLogTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.CommsEmailLogSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CommsEmailLog has methods that act as mods for the CommsEmailLogTemplate +var CommsEmailLogMods commsEmailLogMods + +type commsEmailLogMods struct{} + +func (m commsEmailLogMods) RandomizeAllColumns(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModSlice{ + CommsEmailLogMods.RandomCreated(f), + CommsEmailLogMods.RandomDestination(f), + CommsEmailLogMods.RandomSource(f), + CommsEmailLogMods.RandomType(f), + } +} + +// Set the model columns to this value +func (m commsEmailLogMods) Created(val time.Time) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Created = func() time.Time { return val } + }) +} + +// Set the Column from the function +func (m commsEmailLogMods) CreatedFunc(f func() time.Time) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Created = f + }) +} + +// Clear any values for the column +func (m commsEmailLogMods) UnsetCreated() CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Created = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailLogMods) RandomCreated(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Created = func() time.Time { + return random_time_Time(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailLogMods) Destination(val string) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Destination = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailLogMods) DestinationFunc(f func() string) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Destination = f + }) +} + +// Clear any values for the column +func (m commsEmailLogMods) UnsetDestination() CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Destination = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailLogMods) RandomDestination(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Destination = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailLogMods) Source(val string) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Source = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailLogMods) SourceFunc(f func() string) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Source = f + }) +} + +// Clear any values for the column +func (m commsEmailLogMods) UnsetSource() CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Source = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailLogMods) RandomSource(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Source = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailLogMods) Type(val enums.CommsEmailmessagetype) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Type = func() enums.CommsEmailmessagetype { return val } + }) +} + +// Set the Column from the function +func (m commsEmailLogMods) TypeFunc(f func() enums.CommsEmailmessagetype) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Type = f + }) +} + +// Clear any values for the column +func (m commsEmailLogMods) UnsetType() CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Type = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailLogMods) RandomType(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Type = func() enums.CommsEmailmessagetype { + return random_enums_CommsEmailmessagetype(f) + } + }) +} + +func (m commsEmailLogMods) WithParentsCascading() CommsEmailLogMod { + return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { + if isDone, _ := commsEmailLogWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = commsEmailLogWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewCommsEmailWithContext(ctx, CommsEmailMods.WithParentsCascading()) + m.WithDestinationEmail(related).Apply(ctx, o) + } + { + + related := o.f.NewCommsPhoneWithContext(ctx, CommsPhoneMods.WithParentsCascading()) + m.WithSourcePhone(related).Apply(ctx, o) + } + }) +} + +func (m commsEmailLogMods) WithDestinationEmail(rel *CommsEmailTemplate) CommsEmailLogMod { + return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { + o.r.DestinationEmail = &commsEmailLogRDestinationEmailR{ + o: rel, + } + }) +} + +func (m commsEmailLogMods) WithNewDestinationEmail(mods ...CommsEmailMod) CommsEmailLogMod { + return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { + related := o.f.NewCommsEmailWithContext(ctx, mods...) + + m.WithDestinationEmail(related).Apply(ctx, o) + }) +} + +func (m commsEmailLogMods) WithExistingDestinationEmail(em *models.CommsEmail) CommsEmailLogMod { + return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { + o.r.DestinationEmail = &commsEmailLogRDestinationEmailR{ + o: o.f.FromExistingCommsEmail(em), + } + }) +} + +func (m commsEmailLogMods) WithoutDestinationEmail() CommsEmailLogMod { + return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { + o.r.DestinationEmail = nil + }) +} + +func (m commsEmailLogMods) WithSourcePhone(rel *CommsPhoneTemplate) CommsEmailLogMod { + return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { + o.r.SourcePhone = &commsEmailLogRSourcePhoneR{ + o: rel, + } + }) +} + +func (m commsEmailLogMods) WithNewSourcePhone(mods ...CommsPhoneMod) CommsEmailLogMod { + return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { + related := o.f.NewCommsPhoneWithContext(ctx, mods...) + + m.WithSourcePhone(related).Apply(ctx, o) + }) +} + +func (m commsEmailLogMods) WithExistingSourcePhone(em *models.CommsPhone) CommsEmailLogMod { + return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { + o.r.SourcePhone = &commsEmailLogRSourcePhoneR{ + o: o.f.FromExistingCommsPhone(em), + } + }) +} + +func (m commsEmailLogMods) WithoutSourcePhone() CommsEmailLogMod { + return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { + o.r.SourcePhone = nil + }) +} diff --git a/db/factory/comms.phone.bob.go b/db/factory/comms.phone.bob.go new file mode 100644 index 00000000..bc823c95 --- /dev/null +++ b/db/factory/comms.phone.bob.go @@ -0,0 +1,562 @@ +// Code generated by BobGen psql v0.42.1. 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/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type CommsPhoneMod interface { + Apply(context.Context, *CommsPhoneTemplate) +} + +type CommsPhoneModFunc func(context.Context, *CommsPhoneTemplate) + +func (f CommsPhoneModFunc) Apply(ctx context.Context, n *CommsPhoneTemplate) { + f(ctx, n) +} + +type CommsPhoneModSlice []CommsPhoneMod + +func (mods CommsPhoneModSlice) Apply(ctx context.Context, n *CommsPhoneTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// CommsPhoneTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type CommsPhoneTemplate struct { + E164 func() string + IsSubscribed func() bool + + r commsPhoneR + f *Factory + + alreadyPersisted bool +} + +type commsPhoneR struct { + SourceEmailLogs []*commsPhoneRSourceEmailLogsR + DestinationSMSLogs []*commsPhoneRDestinationSMSLogsR + SourceSMSLogs []*commsPhoneRSourceSMSLogsR +} + +type commsPhoneRSourceEmailLogsR struct { + number int + o *CommsEmailLogTemplate +} +type commsPhoneRDestinationSMSLogsR struct { + number int + o *CommsSMSLogTemplate +} +type commsPhoneRSourceSMSLogsR struct { + number int + o *CommsSMSLogTemplate +} + +// Apply mods to the CommsPhoneTemplate +func (o *CommsPhoneTemplate) Apply(ctx context.Context, mods ...CommsPhoneMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.CommsPhone +// according to the relationships in the template. Nothing is inserted into the db +func (t CommsPhoneTemplate) setModelRels(o *models.CommsPhone) { + if t.r.SourceEmailLogs != nil { + rel := models.CommsEmailLogSlice{} + for _, r := range t.r.SourceEmailLogs { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.Source = o.E164 // h2 + rel.R.SourcePhone = o + } + rel = append(rel, related...) + } + o.R.SourceEmailLogs = rel + } + + if t.r.DestinationSMSLogs != nil { + rel := models.CommsSMSLogSlice{} + for _, r := range t.r.DestinationSMSLogs { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.Destination = o.E164 // h2 + rel.R.DestinationPhone = o + } + rel = append(rel, related...) + } + o.R.DestinationSMSLogs = rel + } + + if t.r.SourceSMSLogs != nil { + rel := models.CommsSMSLogSlice{} + for _, r := range t.r.SourceSMSLogs { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.Source = o.E164 // h2 + rel.R.SourcePhone = o + } + rel = append(rel, related...) + } + o.R.SourceSMSLogs = rel + } +} + +// BuildSetter returns an *models.CommsPhoneSetter +// this does nothing with the relationship templates +func (o CommsPhoneTemplate) BuildSetter() *models.CommsPhoneSetter { + m := &models.CommsPhoneSetter{} + + if o.E164 != nil { + val := o.E164() + m.E164 = omit.From(val) + } + if o.IsSubscribed != nil { + val := o.IsSubscribed() + m.IsSubscribed = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.CommsPhoneSetter +// this does nothing with the relationship templates +func (o CommsPhoneTemplate) BuildManySetter(number int) []*models.CommsPhoneSetter { + m := make([]*models.CommsPhoneSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.CommsPhone +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsPhoneTemplate.Create +func (o CommsPhoneTemplate) Build() *models.CommsPhone { + m := &models.CommsPhone{} + + if o.E164 != nil { + m.E164 = o.E164() + } + if o.IsSubscribed != nil { + m.IsSubscribed = o.IsSubscribed() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.CommsPhoneSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsPhoneTemplate.CreateMany +func (o CommsPhoneTemplate) BuildMany(number int) models.CommsPhoneSlice { + m := make(models.CommsPhoneSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableCommsPhone(m *models.CommsPhoneSetter) { + if !(m.E164.IsValue()) { + val := random_string(nil) + m.E164 = omit.From(val) + } + if !(m.IsSubscribed.IsValue()) { + val := random_bool(nil) + m.IsSubscribed = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.CommsPhone +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *CommsPhoneTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsPhone) error { + var err error + + isSourceEmailLogsDone, _ := commsPhoneRelSourceEmailLogsCtx.Value(ctx) + if !isSourceEmailLogsDone && o.r.SourceEmailLogs != nil { + ctx = commsPhoneRelSourceEmailLogsCtx.WithValue(ctx, true) + for _, r := range o.r.SourceEmailLogs { + if r.o.alreadyPersisted { + m.R.SourceEmailLogs = append(m.R.SourceEmailLogs, r.o.Build()) + } else { + rel0, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachSourceEmailLogs(ctx, exec, rel0...) + if err != nil { + return err + } + } + } + } + + isDestinationSMSLogsDone, _ := commsPhoneRelDestinationSMSLogsCtx.Value(ctx) + if !isDestinationSMSLogsDone && o.r.DestinationSMSLogs != nil { + ctx = commsPhoneRelDestinationSMSLogsCtx.WithValue(ctx, true) + for _, r := range o.r.DestinationSMSLogs { + if r.o.alreadyPersisted { + m.R.DestinationSMSLogs = append(m.R.DestinationSMSLogs, r.o.Build()) + } else { + rel1, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachDestinationSMSLogs(ctx, exec, rel1...) + if err != nil { + return err + } + } + } + } + + isSourceSMSLogsDone, _ := commsPhoneRelSourceSMSLogsCtx.Value(ctx) + if !isSourceSMSLogsDone && o.r.SourceSMSLogs != nil { + ctx = commsPhoneRelSourceSMSLogsCtx.WithValue(ctx, true) + for _, r := range o.r.SourceSMSLogs { + if r.o.alreadyPersisted { + m.R.SourceSMSLogs = append(m.R.SourceSMSLogs, r.o.Build()) + } else { + rel2, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachSourceSMSLogs(ctx, exec, rel2...) + if err != nil { + return err + } + } + } + } + + return err +} + +// Create builds a commsPhone and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *CommsPhoneTemplate) Create(ctx context.Context, exec bob.Executor) (*models.CommsPhone, error) { + var err error + opt := o.BuildSetter() + ensureCreatableCommsPhone(opt) + + m, err := models.CommsPhones.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a commsPhone and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *CommsPhoneTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.CommsPhone { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a commsPhone 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 *CommsPhoneTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.CommsPhone { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple commsPhones and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o CommsPhoneTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.CommsPhoneSlice, error) { + var err error + m := make(models.CommsPhoneSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple commsPhones and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o CommsPhoneTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.CommsPhoneSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple commsPhones 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 CommsPhoneTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.CommsPhoneSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CommsPhone has methods that act as mods for the CommsPhoneTemplate +var CommsPhoneMods commsPhoneMods + +type commsPhoneMods struct{} + +func (m commsPhoneMods) RandomizeAllColumns(f *faker.Faker) CommsPhoneMod { + return CommsPhoneModSlice{ + CommsPhoneMods.RandomE164(f), + CommsPhoneMods.RandomIsSubscribed(f), + } +} + +// Set the model columns to this value +func (m commsPhoneMods) E164(val string) CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.E164 = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsPhoneMods) E164Func(f func() string) CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.E164 = f + }) +} + +// Clear any values for the column +func (m commsPhoneMods) UnsetE164() CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.E164 = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsPhoneMods) RandomE164(f *faker.Faker) CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.E164 = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsPhoneMods) IsSubscribed(val bool) CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.IsSubscribed = func() bool { return val } + }) +} + +// Set the Column from the function +func (m commsPhoneMods) IsSubscribedFunc(f func() bool) CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.IsSubscribed = f + }) +} + +// Clear any values for the column +func (m commsPhoneMods) UnsetIsSubscribed() CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.IsSubscribed = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsPhoneMods) RandomIsSubscribed(f *faker.Faker) CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.IsSubscribed = func() bool { + return random_bool(f) + } + }) +} + +func (m commsPhoneMods) WithParentsCascading() CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + if isDone, _ := commsPhoneWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = commsPhoneWithParentsCascadingCtx.WithValue(ctx, true) + }) +} + +func (m commsPhoneMods) WithSourceEmailLogs(number int, related *CommsEmailLogTemplate) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.SourceEmailLogs = []*commsPhoneRSourceEmailLogsR{{ + number: number, + o: related, + }} + }) +} + +func (m commsPhoneMods) WithNewSourceEmailLogs(number int, mods ...CommsEmailLogMod) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + related := o.f.NewCommsEmailLogWithContext(ctx, mods...) + m.WithSourceEmailLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsPhoneMods) AddSourceEmailLogs(number int, related *CommsEmailLogTemplate) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.SourceEmailLogs = append(o.r.SourceEmailLogs, &commsPhoneRSourceEmailLogsR{ + number: number, + o: related, + }) + }) +} + +func (m commsPhoneMods) AddNewSourceEmailLogs(number int, mods ...CommsEmailLogMod) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + related := o.f.NewCommsEmailLogWithContext(ctx, mods...) + m.AddSourceEmailLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsPhoneMods) AddExistingSourceEmailLogs(existingModels ...*models.CommsEmailLog) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + for _, em := range existingModels { + o.r.SourceEmailLogs = append(o.r.SourceEmailLogs, &commsPhoneRSourceEmailLogsR{ + o: o.f.FromExistingCommsEmailLog(em), + }) + } + }) +} + +func (m commsPhoneMods) WithoutSourceEmailLogs() CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.SourceEmailLogs = nil + }) +} + +func (m commsPhoneMods) WithDestinationSMSLogs(number int, related *CommsSMSLogTemplate) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.DestinationSMSLogs = []*commsPhoneRDestinationSMSLogsR{{ + number: number, + o: related, + }} + }) +} + +func (m commsPhoneMods) WithNewDestinationSMSLogs(number int, mods ...CommsSMSLogMod) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + related := o.f.NewCommsSMSLogWithContext(ctx, mods...) + m.WithDestinationSMSLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsPhoneMods) AddDestinationSMSLogs(number int, related *CommsSMSLogTemplate) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.DestinationSMSLogs = append(o.r.DestinationSMSLogs, &commsPhoneRDestinationSMSLogsR{ + number: number, + o: related, + }) + }) +} + +func (m commsPhoneMods) AddNewDestinationSMSLogs(number int, mods ...CommsSMSLogMod) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + related := o.f.NewCommsSMSLogWithContext(ctx, mods...) + m.AddDestinationSMSLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsPhoneMods) AddExistingDestinationSMSLogs(existingModels ...*models.CommsSMSLog) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + for _, em := range existingModels { + o.r.DestinationSMSLogs = append(o.r.DestinationSMSLogs, &commsPhoneRDestinationSMSLogsR{ + o: o.f.FromExistingCommsSMSLog(em), + }) + } + }) +} + +func (m commsPhoneMods) WithoutDestinationSMSLogs() CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.DestinationSMSLogs = nil + }) +} + +func (m commsPhoneMods) WithSourceSMSLogs(number int, related *CommsSMSLogTemplate) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.SourceSMSLogs = []*commsPhoneRSourceSMSLogsR{{ + number: number, + o: related, + }} + }) +} + +func (m commsPhoneMods) WithNewSourceSMSLogs(number int, mods ...CommsSMSLogMod) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + related := o.f.NewCommsSMSLogWithContext(ctx, mods...) + m.WithSourceSMSLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsPhoneMods) AddSourceSMSLogs(number int, related *CommsSMSLogTemplate) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.SourceSMSLogs = append(o.r.SourceSMSLogs, &commsPhoneRSourceSMSLogsR{ + number: number, + o: related, + }) + }) +} + +func (m commsPhoneMods) AddNewSourceSMSLogs(number int, mods ...CommsSMSLogMod) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + related := o.f.NewCommsSMSLogWithContext(ctx, mods...) + m.AddSourceSMSLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsPhoneMods) AddExistingSourceSMSLogs(existingModels ...*models.CommsSMSLog) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + for _, em := range existingModels { + o.r.SourceSMSLogs = append(o.r.SourceSMSLogs, &commsPhoneRSourceSMSLogsR{ + o: o.f.FromExistingCommsSMSLog(em), + }) + } + }) +} + +func (m commsPhoneMods) WithoutSourceSMSLogs() CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.SourceSMSLogs = nil + }) +} diff --git a/db/factory/comms.sms_log.bob.go b/db/factory/comms.sms_log.bob.go new file mode 100644 index 00000000..516fe9ff --- /dev/null +++ b/db/factory/comms.sms_log.bob.go @@ -0,0 +1,523 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/omit" + "github.com/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type CommsSMSLogMod interface { + Apply(context.Context, *CommsSMSLogTemplate) +} + +type CommsSMSLogModFunc func(context.Context, *CommsSMSLogTemplate) + +func (f CommsSMSLogModFunc) Apply(ctx context.Context, n *CommsSMSLogTemplate) { + f(ctx, n) +} + +type CommsSMSLogModSlice []CommsSMSLogMod + +func (mods CommsSMSLogModSlice) Apply(ctx context.Context, n *CommsSMSLogTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// CommsSMSLogTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type CommsSMSLogTemplate struct { + Created func() time.Time + Destination func() string + Source func() string + Type func() enums.CommsSmsmessagetype + + r commsSMSLogR + f *Factory + + alreadyPersisted bool +} + +type commsSMSLogR struct { + DestinationPhone *commsSMSLogRDestinationPhoneR + SourcePhone *commsSMSLogRSourcePhoneR +} + +type commsSMSLogRDestinationPhoneR struct { + o *CommsPhoneTemplate +} +type commsSMSLogRSourcePhoneR struct { + o *CommsPhoneTemplate +} + +// Apply mods to the CommsSMSLogTemplate +func (o *CommsSMSLogTemplate) Apply(ctx context.Context, mods ...CommsSMSLogMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.CommsSMSLog +// according to the relationships in the template. Nothing is inserted into the db +func (t CommsSMSLogTemplate) setModelRels(o *models.CommsSMSLog) { + if t.r.DestinationPhone != nil { + rel := t.r.DestinationPhone.o.Build() + rel.R.DestinationSMSLogs = append(rel.R.DestinationSMSLogs, o) + o.Destination = rel.E164 // h2 + o.R.DestinationPhone = rel + } + + if t.r.SourcePhone != nil { + rel := t.r.SourcePhone.o.Build() + rel.R.SourceSMSLogs = append(rel.R.SourceSMSLogs, o) + o.Source = rel.E164 // h2 + o.R.SourcePhone = rel + } +} + +// BuildSetter returns an *models.CommsSMSLogSetter +// this does nothing with the relationship templates +func (o CommsSMSLogTemplate) BuildSetter() *models.CommsSMSLogSetter { + m := &models.CommsSMSLogSetter{} + + if o.Created != nil { + val := o.Created() + m.Created = omit.From(val) + } + if o.Destination != nil { + val := o.Destination() + m.Destination = omit.From(val) + } + if o.Source != nil { + val := o.Source() + m.Source = omit.From(val) + } + if o.Type != nil { + val := o.Type() + m.Type = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.CommsSMSLogSetter +// this does nothing with the relationship templates +func (o CommsSMSLogTemplate) BuildManySetter(number int) []*models.CommsSMSLogSetter { + m := make([]*models.CommsSMSLogSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.CommsSMSLog +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsSMSLogTemplate.Create +func (o CommsSMSLogTemplate) Build() *models.CommsSMSLog { + m := &models.CommsSMSLog{} + + if o.Created != nil { + m.Created = o.Created() + } + if o.Destination != nil { + m.Destination = o.Destination() + } + if o.Source != nil { + m.Source = o.Source() + } + if o.Type != nil { + m.Type = o.Type() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.CommsSMSLogSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsSMSLogTemplate.CreateMany +func (o CommsSMSLogTemplate) BuildMany(number int) models.CommsSMSLogSlice { + m := make(models.CommsSMSLogSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableCommsSMSLog(m *models.CommsSMSLogSetter) { + if !(m.Created.IsValue()) { + val := random_time_Time(nil) + m.Created = omit.From(val) + } + if !(m.Destination.IsValue()) { + val := random_string(nil) + m.Destination = omit.From(val) + } + if !(m.Source.IsValue()) { + val := random_string(nil) + m.Source = omit.From(val) + } + if !(m.Type.IsValue()) { + val := random_enums_CommsSmsmessagetype(nil) + m.Type = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.CommsSMSLog +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *CommsSMSLogTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsSMSLog) error { + var err error + + return err +} + +// Create builds a commsSMSLog and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *CommsSMSLogTemplate) Create(ctx context.Context, exec bob.Executor) (*models.CommsSMSLog, error) { + var err error + opt := o.BuildSetter() + ensureCreatableCommsSMSLog(opt) + + if o.r.DestinationPhone == nil { + CommsSMSLogMods.WithNewDestinationPhone().Apply(ctx, o) + } + + var rel0 *models.CommsPhone + + if o.r.DestinationPhone.o.alreadyPersisted { + rel0 = o.r.DestinationPhone.o.Build() + } else { + rel0, err = o.r.DestinationPhone.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.Destination = omit.From(rel0.E164) + + if o.r.SourcePhone == nil { + CommsSMSLogMods.WithNewSourcePhone().Apply(ctx, o) + } + + var rel1 *models.CommsPhone + + if o.r.SourcePhone.o.alreadyPersisted { + rel1 = o.r.SourcePhone.o.Build() + } else { + rel1, err = o.r.SourcePhone.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.Source = omit.From(rel1.E164) + + m, err := models.CommsSMSLogs.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + m.R.DestinationPhone = rel0 + m.R.SourcePhone = rel1 + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a commsSMSLog and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *CommsSMSLogTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.CommsSMSLog { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a commsSMSLog 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 *CommsSMSLogTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.CommsSMSLog { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple commsSMSLogs and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o CommsSMSLogTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.CommsSMSLogSlice, error) { + var err error + m := make(models.CommsSMSLogSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple commsSMSLogs and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o CommsSMSLogTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.CommsSMSLogSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple commsSMSLogs 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 CommsSMSLogTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.CommsSMSLogSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CommsSMSLog has methods that act as mods for the CommsSMSLogTemplate +var CommsSMSLogMods commsSMSLogMods + +type commsSMSLogMods struct{} + +func (m commsSMSLogMods) RandomizeAllColumns(f *faker.Faker) CommsSMSLogMod { + return CommsSMSLogModSlice{ + CommsSMSLogMods.RandomCreated(f), + CommsSMSLogMods.RandomDestination(f), + CommsSMSLogMods.RandomSource(f), + CommsSMSLogMods.RandomType(f), + } +} + +// Set the model columns to this value +func (m commsSMSLogMods) Created(val time.Time) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Created = func() time.Time { return val } + }) +} + +// Set the Column from the function +func (m commsSMSLogMods) CreatedFunc(f func() time.Time) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Created = f + }) +} + +// Clear any values for the column +func (m commsSMSLogMods) UnsetCreated() CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Created = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsSMSLogMods) RandomCreated(f *faker.Faker) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Created = func() time.Time { + return random_time_Time(f) + } + }) +} + +// Set the model columns to this value +func (m commsSMSLogMods) Destination(val string) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Destination = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsSMSLogMods) DestinationFunc(f func() string) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Destination = f + }) +} + +// Clear any values for the column +func (m commsSMSLogMods) UnsetDestination() CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Destination = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsSMSLogMods) RandomDestination(f *faker.Faker) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Destination = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsSMSLogMods) Source(val string) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Source = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsSMSLogMods) SourceFunc(f func() string) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Source = f + }) +} + +// Clear any values for the column +func (m commsSMSLogMods) UnsetSource() CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Source = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsSMSLogMods) RandomSource(f *faker.Faker) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Source = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsSMSLogMods) Type(val enums.CommsSmsmessagetype) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Type = func() enums.CommsSmsmessagetype { return val } + }) +} + +// Set the Column from the function +func (m commsSMSLogMods) TypeFunc(f func() enums.CommsSmsmessagetype) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Type = f + }) +} + +// Clear any values for the column +func (m commsSMSLogMods) UnsetType() CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Type = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsSMSLogMods) RandomType(f *faker.Faker) CommsSMSLogMod { + return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { + o.Type = func() enums.CommsSmsmessagetype { + return random_enums_CommsSmsmessagetype(f) + } + }) +} + +func (m commsSMSLogMods) WithParentsCascading() CommsSMSLogMod { + return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { + if isDone, _ := commsSMSLogWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = commsSMSLogWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewCommsPhoneWithContext(ctx, CommsPhoneMods.WithParentsCascading()) + m.WithDestinationPhone(related).Apply(ctx, o) + } + { + + related := o.f.NewCommsPhoneWithContext(ctx, CommsPhoneMods.WithParentsCascading()) + m.WithSourcePhone(related).Apply(ctx, o) + } + }) +} + +func (m commsSMSLogMods) WithDestinationPhone(rel *CommsPhoneTemplate) CommsSMSLogMod { + return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { + o.r.DestinationPhone = &commsSMSLogRDestinationPhoneR{ + o: rel, + } + }) +} + +func (m commsSMSLogMods) WithNewDestinationPhone(mods ...CommsPhoneMod) CommsSMSLogMod { + return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { + related := o.f.NewCommsPhoneWithContext(ctx, mods...) + + m.WithDestinationPhone(related).Apply(ctx, o) + }) +} + +func (m commsSMSLogMods) WithExistingDestinationPhone(em *models.CommsPhone) CommsSMSLogMod { + return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { + o.r.DestinationPhone = &commsSMSLogRDestinationPhoneR{ + o: o.f.FromExistingCommsPhone(em), + } + }) +} + +func (m commsSMSLogMods) WithoutDestinationPhone() CommsSMSLogMod { + return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { + o.r.DestinationPhone = nil + }) +} + +func (m commsSMSLogMods) WithSourcePhone(rel *CommsPhoneTemplate) CommsSMSLogMod { + return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { + o.r.SourcePhone = &commsSMSLogRSourcePhoneR{ + o: rel, + } + }) +} + +func (m commsSMSLogMods) WithNewSourcePhone(mods ...CommsPhoneMod) CommsSMSLogMod { + return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { + related := o.f.NewCommsPhoneWithContext(ctx, mods...) + + m.WithSourcePhone(related).Apply(ctx, o) + }) +} + +func (m commsSMSLogMods) WithExistingSourcePhone(em *models.CommsPhone) CommsSMSLogMod { + return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { + o.r.SourcePhone = &commsSMSLogRSourcePhoneR{ + o: o.f.FromExistingCommsPhone(em), + } + }) +} + +func (m commsSMSLogMods) WithoutSourcePhone() CommsSMSLogMod { + return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { + o.r.SourcePhone = nil + }) +} diff --git a/db/factory/publicreport.image.bob.go b/db/factory/publicreport.image.bob.go index 3d292ad4..cf3044aa 100644 --- a/db/factory/publicreport.image.bob.go +++ b/db/factory/publicreport.image.bob.go @@ -9,7 +9,9 @@ import ( "time" models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" "github.com/stephenafamo/bob" @@ -39,6 +41,7 @@ type PublicreportImageTemplate struct { ID func() int32 ContentType func() string Created func() time.Time + Location func() null.Val[string] ResolutionX func() int32 ResolutionY func() int32 StorageUUID func() uuid.UUID @@ -135,6 +138,10 @@ func (o PublicreportImageTemplate) BuildSetter() *models.PublicreportImageSetter val := o.Created() m.Created = omit.From(val) } + if o.Location != nil { + val := o.Location() + m.Location = omitnull.FromNull(val) + } if o.ResolutionX != nil { val := o.ResolutionX() m.ResolutionX = omit.From(val) @@ -186,6 +193,9 @@ func (o PublicreportImageTemplate) Build() *models.PublicreportImage { if o.Created != nil { m.Created = o.Created() } + if o.Location != nil { + m.Location = o.Location() + } if o.ResolutionX != nil { m.ResolutionX = o.ResolutionX() } @@ -412,6 +422,7 @@ func (m publicreportImageMods) RandomizeAllColumns(f *faker.Faker) PublicreportI PublicreportImageMods.RandomID(f), PublicreportImageMods.RandomContentType(f), PublicreportImageMods.RandomCreated(f), + PublicreportImageMods.RandomLocation(f), PublicreportImageMods.RandomResolutionX(f), PublicreportImageMods.RandomResolutionY(f), PublicreportImageMods.RandomStorageUUID(f), @@ -513,6 +524,59 @@ func (m publicreportImageMods) RandomCreated(f *faker.Faker) PublicreportImageMo }) } +// Set the model columns to this value +func (m publicreportImageMods) Location(val null.Val[string]) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.Location = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m publicreportImageMods) LocationFunc(f func() null.Val[string]) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.Location = f + }) +} + +// Clear any values for the column +func (m publicreportImageMods) UnsetLocation() PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.Location = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m publicreportImageMods) RandomLocation(f *faker.Faker) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.Location = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m publicreportImageMods) RandomLocationNotNull(f *faker.Faker) PublicreportImageMod { + return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { + o.Location = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + // Set the model columns to this value func (m publicreportImageMods) ResolutionX(val int32) PublicreportImageMod { return PublicreportImageModFunc(func(_ context.Context, o *PublicreportImageTemplate) { diff --git a/db/migrations/00036_comms.sql b/db/migrations/00036_comms.sql new file mode 100644 index 00000000..1ab2697a --- /dev/null +++ b/db/migrations/00036_comms.sql @@ -0,0 +1,37 @@ +-- +goose Up +CREATE SCHEMA comms; +CREATE TYPE comms.SMSMessageType AS ENUM ( + 'report-subscription-confirmation', + 'report-status-scheduled', + 'report-status-complete' +); +CREATE TYPE comms.EmailMessageType AS ENUM ( + 'report-subscription-confirmation', + 'report-status-scheduled', + 'report-status-complete' +); +CREATE TABLE comms.phone ( + e164 TEXT NOT NULL, + is_subscribed BOOLEAN NOT NULL, + PRIMARY KEY (e164) +); +CREATE TABLE comms.sms_log ( + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + destination TEXT NOT NULL REFERENCES comms.phone(e164), + source TEXT NOT NULL REFERENCES comms.phone(e164), + type comms.SMSMessageType NOT NULL, + PRIMARY KEY (destination, source, type) +); +CREATE TABLE comms.email ( + address TEXT NOT NULL, + confirmed BOOLEAN NOT NULL, + is_subscribed BOOLEAN NOT NULL, + PRIMARY KEY(address) +); +CREATE TABLE comms.email_log ( + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + destination TEXT NOT NULL REFERENCES comms.email(address), + source TEXT NOT NULL REFERENCES comms.phone(e164), + type comms.EmailMessageType NOT NULL, + PRIMARY KEY(destination, source, type) +); diff --git a/db/models/bob_counts.bob.go b/db/models/bob_counts.bob.go index de00d405..2deaf5bb 100644 --- a/db/models/bob_counts.bob.go +++ b/db/models/bob_counts.bob.go @@ -22,6 +22,8 @@ var ( type preloadCounts struct { ArcgisUser arcgisuserCountPreloader + CommsEmail commsEmailCountPreloader + CommsPhone commsPhoneCountPreloader NoteAudio noteAudioCountPreloader NoteImage noteImageCountPreloader Organization organizationCountPreloader @@ -34,6 +36,8 @@ type preloadCounts struct { func getPreloadCount() preloadCounts { return preloadCounts{ ArcgisUser: buildArcgisUserCountPreloader(), + CommsEmail: buildCommsEmailCountPreloader(), + CommsPhone: buildCommsPhoneCountPreloader(), NoteAudio: buildNoteAudioCountPreloader(), NoteImage: buildNoteImageCountPreloader(), Organization: buildOrganizationCountPreloader(), @@ -46,6 +50,8 @@ func getPreloadCount() preloadCounts { type thenLoadCounts[Q orm.Loadable] struct { ArcgisUser arcgisuserCountThenLoader[Q] + CommsEmail commsEmailCountThenLoader[Q] + CommsPhone commsPhoneCountThenLoader[Q] NoteAudio noteAudioCountThenLoader[Q] NoteImage noteImageCountThenLoader[Q] Organization organizationCountThenLoader[Q] @@ -58,6 +64,8 @@ type thenLoadCounts[Q orm.Loadable] struct { func getThenLoadCount[Q orm.Loadable]() thenLoadCounts[Q] { return thenLoadCounts[Q]{ ArcgisUser: buildArcgisUserCountThenLoader[Q](), + CommsEmail: buildCommsEmailCountThenLoader[Q](), + CommsPhone: buildCommsPhoneCountThenLoader[Q](), NoteAudio: buildNoteAudioCountThenLoader[Q](), NoteImage: buildNoteImageCountThenLoader[Q](), Organization: buildOrganizationCountThenLoader[Q](), diff --git a/db/models/bob_joins.bob.go b/db/models/bob_joins.bob.go index f387e8d3..8df782cc 100644 --- a/db/models/bob_joins.bob.go +++ b/db/models/bob_joins.bob.go @@ -34,6 +34,10 @@ func (j joinSet[Q]) AliasedAs(alias string) joinSet[Q] { type joins[Q dialect.Joinable] struct { ArcgisUsers joinSet[arcgisuserJoins[Q]] ArcgisUserPrivileges joinSet[arcgisUserPrivilegeJoins[Q]] + CommsEmails joinSet[commsEmailJoins[Q]] + CommsEmailLogs joinSet[commsEmailLogJoins[Q]] + CommsPhones joinSet[commsPhoneJoins[Q]] + CommsSMSLogs joinSet[commsSMSLogJoins[Q]] FieldseekerContainerrelates joinSet[fieldseekerContainerrelateJoins[Q]] FieldseekerFieldscoutinglogs joinSet[fieldseekerFieldscoutinglogJoins[Q]] FieldseekerHabitatrelates joinSet[fieldseekerHabitatrelateJoins[Q]] @@ -94,6 +98,10 @@ func getJoins[Q dialect.Joinable]() joins[Q] { return joins[Q]{ ArcgisUsers: buildJoinSet[arcgisuserJoins[Q]](ArcgisUsers.Columns, buildArcgisUserJoins), ArcgisUserPrivileges: buildJoinSet[arcgisUserPrivilegeJoins[Q]](ArcgisUserPrivileges.Columns, buildArcgisUserPrivilegeJoins), + CommsEmails: buildJoinSet[commsEmailJoins[Q]](CommsEmails.Columns, buildCommsEmailJoins), + CommsEmailLogs: buildJoinSet[commsEmailLogJoins[Q]](CommsEmailLogs.Columns, buildCommsEmailLogJoins), + CommsPhones: buildJoinSet[commsPhoneJoins[Q]](CommsPhones.Columns, buildCommsPhoneJoins), + CommsSMSLogs: buildJoinSet[commsSMSLogJoins[Q]](CommsSMSLogs.Columns, buildCommsSMSLogJoins), FieldseekerContainerrelates: buildJoinSet[fieldseekerContainerrelateJoins[Q]](FieldseekerContainerrelates.Columns, buildFieldseekerContainerrelateJoins), FieldseekerFieldscoutinglogs: buildJoinSet[fieldseekerFieldscoutinglogJoins[Q]](FieldseekerFieldscoutinglogs.Columns, buildFieldseekerFieldscoutinglogJoins), FieldseekerHabitatrelates: buildJoinSet[fieldseekerHabitatrelateJoins[Q]](FieldseekerHabitatrelates.Columns, buildFieldseekerHabitatrelateJoins), diff --git a/db/models/bob_loaders.bob.go b/db/models/bob_loaders.bob.go index fe6d28c7..7d058f99 100644 --- a/db/models/bob_loaders.bob.go +++ b/db/models/bob_loaders.bob.go @@ -19,6 +19,10 @@ var Preload = getPreloaders() type preloaders struct { ArcgisUser arcgisuserPreloader ArcgisUserPrivilege arcgisUserPrivilegePreloader + CommsEmail commsEmailPreloader + CommsEmailLog commsEmailLogPreloader + CommsPhone commsPhonePreloader + CommsSMSLog commsSMSLogPreloader FieldseekerContainerrelate fieldseekerContainerrelatePreloader FieldseekerFieldscoutinglog fieldseekerFieldscoutinglogPreloader FieldseekerHabitatrelate fieldseekerHabitatrelatePreloader @@ -71,6 +75,10 @@ func getPreloaders() preloaders { return preloaders{ ArcgisUser: buildArcgisUserPreloader(), ArcgisUserPrivilege: buildArcgisUserPrivilegePreloader(), + CommsEmail: buildCommsEmailPreloader(), + CommsEmailLog: buildCommsEmailLogPreloader(), + CommsPhone: buildCommsPhonePreloader(), + CommsSMSLog: buildCommsSMSLogPreloader(), FieldseekerContainerrelate: buildFieldseekerContainerrelatePreloader(), FieldseekerFieldscoutinglog: buildFieldseekerFieldscoutinglogPreloader(), FieldseekerHabitatrelate: buildFieldseekerHabitatrelatePreloader(), @@ -129,6 +137,10 @@ var ( type thenLoaders[Q orm.Loadable] struct { ArcgisUser arcgisuserThenLoader[Q] ArcgisUserPrivilege arcgisUserPrivilegeThenLoader[Q] + CommsEmail commsEmailThenLoader[Q] + CommsEmailLog commsEmailLogThenLoader[Q] + CommsPhone commsPhoneThenLoader[Q] + CommsSMSLog commsSMSLogThenLoader[Q] FieldseekerContainerrelate fieldseekerContainerrelateThenLoader[Q] FieldseekerFieldscoutinglog fieldseekerFieldscoutinglogThenLoader[Q] FieldseekerHabitatrelate fieldseekerHabitatrelateThenLoader[Q] @@ -181,6 +193,10 @@ func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] { return thenLoaders[Q]{ ArcgisUser: buildArcgisUserThenLoader[Q](), ArcgisUserPrivilege: buildArcgisUserPrivilegeThenLoader[Q](), + CommsEmail: buildCommsEmailThenLoader[Q](), + CommsEmailLog: buildCommsEmailLogThenLoader[Q](), + CommsPhone: buildCommsPhoneThenLoader[Q](), + CommsSMSLog: buildCommsSMSLogThenLoader[Q](), FieldseekerContainerrelate: buildFieldseekerContainerrelateThenLoader[Q](), FieldseekerFieldscoutinglog: buildFieldseekerFieldscoutinglogThenLoader[Q](), FieldseekerHabitatrelate: buildFieldseekerHabitatrelateThenLoader[Q](), diff --git a/db/models/bob_where.bob.go b/db/models/bob_where.bob.go index 4b2c8b5a..69ddc8f4 100644 --- a/db/models/bob_where.bob.go +++ b/db/models/bob_where.bob.go @@ -19,6 +19,10 @@ var ( func Where[Q psql.Filterable]() struct { ArcgisUsers arcgisuserWhere[Q] ArcgisUserPrivileges arcgisUserPrivilegeWhere[Q] + CommsEmails commsEmailWhere[Q] + CommsEmailLogs commsEmailLogWhere[Q] + CommsPhones commsPhoneWhere[Q] + CommsSMSLogs commsSMSLogWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] FieldseekerHabitatrelates fieldseekerHabitatrelateWhere[Q] @@ -78,6 +82,10 @@ func Where[Q psql.Filterable]() struct { return struct { ArcgisUsers arcgisuserWhere[Q] ArcgisUserPrivileges arcgisUserPrivilegeWhere[Q] + CommsEmails commsEmailWhere[Q] + CommsEmailLogs commsEmailLogWhere[Q] + CommsPhones commsPhoneWhere[Q] + CommsSMSLogs commsSMSLogWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] FieldseekerHabitatrelates fieldseekerHabitatrelateWhere[Q] @@ -136,6 +144,10 @@ func Where[Q psql.Filterable]() struct { }{ ArcgisUsers: buildArcgisUserWhere[Q](ArcgisUsers.Columns), ArcgisUserPrivileges: buildArcgisUserPrivilegeWhere[Q](ArcgisUserPrivileges.Columns), + CommsEmails: buildCommsEmailWhere[Q](CommsEmails.Columns), + CommsEmailLogs: buildCommsEmailLogWhere[Q](CommsEmailLogs.Columns), + CommsPhones: buildCommsPhoneWhere[Q](CommsPhones.Columns), + CommsSMSLogs: buildCommsSMSLogWhere[Q](CommsSMSLogs.Columns), FieldseekerContainerrelates: buildFieldseekerContainerrelateWhere[Q](FieldseekerContainerrelates.Columns), FieldseekerFieldscoutinglogs: buildFieldseekerFieldscoutinglogWhere[Q](FieldseekerFieldscoutinglogs.Columns), FieldseekerHabitatrelates: buildFieldseekerHabitatrelateWhere[Q](FieldseekerHabitatrelates.Columns), diff --git a/db/models/comms.email.bob.go b/db/models/comms.email.bob.go new file mode 100644 index 00000000..3e05fefb --- /dev/null +++ b/db/models/comms.email.bob.go @@ -0,0 +1,737 @@ +// Code generated by BobGen psql v0.42.1. 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/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" +) + +// CommsEmail is an object representing the database table. +type CommsEmail struct { + Address string `db:"address,pk" ` + Confirmed bool `db:"confirmed" ` + IsSubscribed bool `db:"is_subscribed" ` + + R commsEmailR `db:"-" ` + + C commsEmailC `db:"-" ` +} + +// CommsEmailSlice is an alias for a slice of pointers to CommsEmail. +// This should almost always be used instead of []*CommsEmail. +type CommsEmailSlice []*CommsEmail + +// CommsEmails contains methods to work with the email table +var CommsEmails = psql.NewTablex[*CommsEmail, CommsEmailSlice, *CommsEmailSetter]("comms", "email", buildCommsEmailColumns("comms.email")) + +// CommsEmailsQuery is a query on the email table +type CommsEmailsQuery = *psql.ViewQuery[*CommsEmail, CommsEmailSlice] + +// commsEmailR is where relationships are stored. +type commsEmailR struct { + DestinationEmailLogs CommsEmailLogSlice // comms.email_log.email_log_destination_fkey +} + +func buildCommsEmailColumns(alias string) commsEmailColumns { + return commsEmailColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "address", "confirmed", "is_subscribed", + ).WithParent("comms.email"), + tableAlias: alias, + Address: psql.Quote(alias, "address"), + Confirmed: psql.Quote(alias, "confirmed"), + IsSubscribed: psql.Quote(alias, "is_subscribed"), + } +} + +type commsEmailColumns struct { + expr.ColumnsExpr + tableAlias string + Address psql.Expression + Confirmed psql.Expression + IsSubscribed psql.Expression +} + +func (c commsEmailColumns) Alias() string { + return c.tableAlias +} + +func (commsEmailColumns) AliasedAs(alias string) commsEmailColumns { + return buildCommsEmailColumns(alias) +} + +// CommsEmailSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type CommsEmailSetter struct { + Address omit.Val[string] `db:"address,pk" ` + Confirmed omit.Val[bool] `db:"confirmed" ` + IsSubscribed omit.Val[bool] `db:"is_subscribed" ` +} + +func (s CommsEmailSetter) SetColumns() []string { + vals := make([]string, 0, 3) + if s.Address.IsValue() { + vals = append(vals, "address") + } + if s.Confirmed.IsValue() { + vals = append(vals, "confirmed") + } + if s.IsSubscribed.IsValue() { + vals = append(vals, "is_subscribed") + } + return vals +} + +func (s CommsEmailSetter) Overwrite(t *CommsEmail) { + if s.Address.IsValue() { + t.Address = s.Address.MustGet() + } + if s.Confirmed.IsValue() { + t.Confirmed = s.Confirmed.MustGet() + } + if s.IsSubscribed.IsValue() { + t.IsSubscribed = s.IsSubscribed.MustGet() + } +} + +func (s *CommsEmailSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return CommsEmails.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, 3) + if s.Address.IsValue() { + vals[0] = psql.Arg(s.Address.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.Confirmed.IsValue() { + vals[1] = psql.Arg(s.Confirmed.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if s.IsSubscribed.IsValue() { + vals[2] = psql.Arg(s.IsSubscribed.MustGet()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s CommsEmailSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s CommsEmailSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 3) + + if s.Address.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "address")...), + psql.Arg(s.Address), + }}) + } + + if s.Confirmed.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "confirmed")...), + psql.Arg(s.Confirmed), + }}) + } + + if s.IsSubscribed.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "is_subscribed")...), + psql.Arg(s.IsSubscribed), + }}) + } + + return exprs +} + +// FindCommsEmail retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindCommsEmail(ctx context.Context, exec bob.Executor, AddressPK string, cols ...string) (*CommsEmail, error) { + if len(cols) == 0 { + return CommsEmails.Query( + sm.Where(CommsEmails.Columns.Address.EQ(psql.Arg(AddressPK))), + ).One(ctx, exec) + } + + return CommsEmails.Query( + sm.Where(CommsEmails.Columns.Address.EQ(psql.Arg(AddressPK))), + sm.Columns(CommsEmails.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// CommsEmailExists checks the presence of a single record by primary key +func CommsEmailExists(ctx context.Context, exec bob.Executor, AddressPK string) (bool, error) { + return CommsEmails.Query( + sm.Where(CommsEmails.Columns.Address.EQ(psql.Arg(AddressPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after CommsEmail is retrieved from the database +func (o *CommsEmail) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsEmails.AfterSelectHooks.RunHooks(ctx, exec, CommsEmailSlice{o}) + case bob.QueryTypeInsert: + ctx, err = CommsEmails.AfterInsertHooks.RunHooks(ctx, exec, CommsEmailSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = CommsEmails.AfterUpdateHooks.RunHooks(ctx, exec, CommsEmailSlice{o}) + case bob.QueryTypeDelete: + ctx, err = CommsEmails.AfterDeleteHooks.RunHooks(ctx, exec, CommsEmailSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the CommsEmail +func (o *CommsEmail) primaryKeyVals() bob.Expression { + return psql.Arg(o.Address) +} + +func (o *CommsEmail) pkEQ() dialect.Expression { + return psql.Quote("comms.email", "address").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 CommsEmail +func (o *CommsEmail) Update(ctx context.Context, exec bob.Executor, s *CommsEmailSetter) error { + v, err := CommsEmails.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 CommsEmail record with an executor +func (o *CommsEmail) Delete(ctx context.Context, exec bob.Executor) error { + _, err := CommsEmails.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the CommsEmail using the executor +func (o *CommsEmail) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := CommsEmails.Query( + sm.Where(CommsEmails.Columns.Address.EQ(psql.Arg(o.Address))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after CommsEmailSlice is retrieved from the database +func (o CommsEmailSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsEmails.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = CommsEmails.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = CommsEmails.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = CommsEmails.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o CommsEmailSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Quote("comms.email", "address").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 CommsEmailSlice) copyMatchingRows(from ...*CommsEmail) { + for i, old := range o { + for _, new := range from { + if new.Address != old.Address { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o CommsEmailSlice) 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 CommsEmails.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 *CommsEmail: + o.copyMatchingRows(retrieved) + case []*CommsEmail: + o.copyMatchingRows(retrieved...) + case CommsEmailSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsEmail or a slice of CommsEmail + // then run the AfterUpdateHooks on the slice + _, err = CommsEmails.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o CommsEmailSlice) 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 CommsEmails.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 *CommsEmail: + o.copyMatchingRows(retrieved) + case []*CommsEmail: + o.copyMatchingRows(retrieved...) + case CommsEmailSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsEmail or a slice of CommsEmail + // then run the AfterDeleteHooks on the slice + _, err = CommsEmails.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o CommsEmailSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals CommsEmailSetter) error { + if len(o) == 0 { + return nil + } + + _, err := CommsEmails.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o CommsEmailSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := CommsEmails.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o CommsEmailSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := CommsEmails.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// DestinationEmailLogs starts a query for related objects on comms.email_log +func (o *CommsEmail) DestinationEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { + return CommsEmailLogs.Query(append(mods, + sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(o.Address))), + )...) +} + +func (os CommsEmailSlice) DestinationEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { + pkAddress := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkAddress = append(pkAddress, o.Address) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkAddress), "text[]")), + )) + + return CommsEmailLogs.Query(append(mods, + sm.Where(psql.Group(CommsEmailLogs.Columns.Destination).OP("IN", PKArgExpr)), + )...) +} + +func insertCommsEmailDestinationEmailLogs0(ctx context.Context, exec bob.Executor, commsEmailLogs1 []*CommsEmailLogSetter, commsEmail0 *CommsEmail) (CommsEmailLogSlice, error) { + for i := range commsEmailLogs1 { + commsEmailLogs1[i].Destination = omit.From(commsEmail0.Address) + } + + ret, err := CommsEmailLogs.Insert(bob.ToMods(commsEmailLogs1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertCommsEmailDestinationEmailLogs0: %w", err) + } + + return ret, nil +} + +func attachCommsEmailDestinationEmailLogs0(ctx context.Context, exec bob.Executor, count int, commsEmailLogs1 CommsEmailLogSlice, commsEmail0 *CommsEmail) (CommsEmailLogSlice, error) { + setter := &CommsEmailLogSetter{ + Destination: omit.From(commsEmail0.Address), + } + + err := commsEmailLogs1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachCommsEmailDestinationEmailLogs0: %w", err) + } + + return commsEmailLogs1, nil +} + +func (commsEmail0 *CommsEmail) InsertDestinationEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLogSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + commsEmailLogs1, err := insertCommsEmailDestinationEmailLogs0(ctx, exec, related, commsEmail0) + if err != nil { + return err + } + + commsEmail0.R.DestinationEmailLogs = append(commsEmail0.R.DestinationEmailLogs, commsEmailLogs1...) + + for _, rel := range commsEmailLogs1 { + rel.R.DestinationEmail = commsEmail0 + } + return nil +} + +func (commsEmail0 *CommsEmail) AttachDestinationEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLog) error { + if len(related) == 0 { + return nil + } + + var err error + commsEmailLogs1 := CommsEmailLogSlice(related) + + _, err = attachCommsEmailDestinationEmailLogs0(ctx, exec, len(related), commsEmailLogs1, commsEmail0) + if err != nil { + return err + } + + commsEmail0.R.DestinationEmailLogs = append(commsEmail0.R.DestinationEmailLogs, commsEmailLogs1...) + + for _, rel := range related { + rel.R.DestinationEmail = commsEmail0 + } + + return nil +} + +type commsEmailWhere[Q psql.Filterable] struct { + Address psql.WhereMod[Q, string] + Confirmed psql.WhereMod[Q, bool] + IsSubscribed psql.WhereMod[Q, bool] +} + +func (commsEmailWhere[Q]) AliasedAs(alias string) commsEmailWhere[Q] { + return buildCommsEmailWhere[Q](buildCommsEmailColumns(alias)) +} + +func buildCommsEmailWhere[Q psql.Filterable](cols commsEmailColumns) commsEmailWhere[Q] { + return commsEmailWhere[Q]{ + Address: psql.Where[Q, string](cols.Address), + Confirmed: psql.Where[Q, bool](cols.Confirmed), + IsSubscribed: psql.Where[Q, bool](cols.IsSubscribed), + } +} + +func (o *CommsEmail) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "DestinationEmailLogs": + rels, ok := retrieved.(CommsEmailLogSlice) + if !ok { + return fmt.Errorf("commsEmail cannot load %T as %q", retrieved, name) + } + + o.R.DestinationEmailLogs = rels + + for _, rel := range rels { + if rel != nil { + rel.R.DestinationEmail = o + } + } + return nil + default: + return fmt.Errorf("commsEmail has no relationship %q", name) + } +} + +type commsEmailPreloader struct{} + +func buildCommsEmailPreloader() commsEmailPreloader { + return commsEmailPreloader{} +} + +type commsEmailThenLoader[Q orm.Loadable] struct { + DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildCommsEmailThenLoader[Q orm.Loadable]() commsEmailThenLoader[Q] { + type DestinationEmailLogsLoadInterface interface { + LoadDestinationEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return commsEmailThenLoader[Q]{ + DestinationEmailLogs: thenLoadBuilder[Q]( + "DestinationEmailLogs", + func(ctx context.Context, exec bob.Executor, retrieved DestinationEmailLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadDestinationEmailLogs(ctx, exec, mods...) + }, + ), + } +} + +// LoadDestinationEmailLogs loads the commsEmail's DestinationEmailLogs into the .R struct +func (o *CommsEmail) LoadDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.DestinationEmailLogs = nil + + related, err := o.DestinationEmailLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.DestinationEmail = o + } + + o.R.DestinationEmailLogs = related + return nil +} + +// LoadDestinationEmailLogs loads the commsEmail's DestinationEmailLogs into the .R struct +func (os CommsEmailSlice) LoadDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsEmailLogs, err := os.DestinationEmailLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.DestinationEmailLogs = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsEmailLogs { + + if !(o.Address == rel.Destination) { + continue + } + + rel.R.DestinationEmail = o + + o.R.DestinationEmailLogs = append(o.R.DestinationEmailLogs, rel) + } + } + + return nil +} + +// commsEmailC is where relationship counts are stored. +type commsEmailC struct { + DestinationEmailLogs *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *CommsEmail) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "DestinationEmailLogs": + o.C.DestinationEmailLogs = &count + } + return nil +} + +type commsEmailCountPreloader struct { + DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildCommsEmailCountPreloader() commsEmailCountPreloader { + return commsEmailCountPreloader{ + DestinationEmailLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*CommsEmail]("DestinationEmailLogs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = CommsEmails.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(CommsEmailLogs.Name()), + sm.Where(psql.Quote(CommsEmailLogs.Alias(), "destination").EQ(psql.Quote(parent, "address"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type commsEmailCountThenLoader[Q orm.Loadable] struct { + DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildCommsEmailCountThenLoader[Q orm.Loadable]() commsEmailCountThenLoader[Q] { + type DestinationEmailLogsCountInterface interface { + LoadCountDestinationEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return commsEmailCountThenLoader[Q]{ + DestinationEmailLogs: countThenLoadBuilder[Q]( + "DestinationEmailLogs", + func(ctx context.Context, exec bob.Executor, retrieved DestinationEmailLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountDestinationEmailLogs(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountDestinationEmailLogs loads the count of DestinationEmailLogs into the C struct +func (o *CommsEmail) LoadCountDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.DestinationEmailLogs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.DestinationEmailLogs = &count + return nil +} + +// LoadCountDestinationEmailLogs loads the count of DestinationEmailLogs for a slice +func (os CommsEmailSlice) LoadCountDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountDestinationEmailLogs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +type commsEmailJoins[Q dialect.Joinable] struct { + typ string + DestinationEmailLogs modAs[Q, commsEmailLogColumns] +} + +func (j commsEmailJoins[Q]) aliasedAs(alias string) commsEmailJoins[Q] { + return buildCommsEmailJoins[Q](buildCommsEmailColumns(alias), j.typ) +} + +func buildCommsEmailJoins[Q dialect.Joinable](cols commsEmailColumns, typ string) commsEmailJoins[Q] { + return commsEmailJoins[Q]{ + typ: typ, + DestinationEmailLogs: modAs[Q, commsEmailLogColumns]{ + c: CommsEmailLogs.Columns, + f: func(to commsEmailLogColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsEmailLogs.Name().As(to.Alias())).On( + to.Destination.EQ(cols.Address), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/comms.email_log.bob.go b/db/models/comms.email_log.bob.go new file mode 100644 index 00000000..74cec9a0 --- /dev/null +++ b/db/models/comms.email_log.bob.go @@ -0,0 +1,848 @@ +// Code generated by BobGen psql v0.42.1. 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" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/aarondl/opt/omit" + "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" +) + +// CommsEmailLog is an object representing the database table. +type CommsEmailLog struct { + Created time.Time `db:"created" ` + Destination string `db:"destination,pk" ` + Source string `db:"source,pk" ` + Type enums.CommsEmailmessagetype `db:"type,pk" ` + + R commsEmailLogR `db:"-" ` +} + +// CommsEmailLogSlice is an alias for a slice of pointers to CommsEmailLog. +// This should almost always be used instead of []*CommsEmailLog. +type CommsEmailLogSlice []*CommsEmailLog + +// CommsEmailLogs contains methods to work with the email_log table +var CommsEmailLogs = psql.NewTablex[*CommsEmailLog, CommsEmailLogSlice, *CommsEmailLogSetter]("comms", "email_log", buildCommsEmailLogColumns("comms.email_log")) + +// CommsEmailLogsQuery is a query on the email_log table +type CommsEmailLogsQuery = *psql.ViewQuery[*CommsEmailLog, CommsEmailLogSlice] + +// commsEmailLogR is where relationships are stored. +type commsEmailLogR struct { + DestinationEmail *CommsEmail // comms.email_log.email_log_destination_fkey + SourcePhone *CommsPhone // comms.email_log.email_log_source_fkey +} + +func buildCommsEmailLogColumns(alias string) commsEmailLogColumns { + return commsEmailLogColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "created", "destination", "source", "type", + ).WithParent("comms.email_log"), + tableAlias: alias, + Created: psql.Quote(alias, "created"), + Destination: psql.Quote(alias, "destination"), + Source: psql.Quote(alias, "source"), + Type: psql.Quote(alias, "type"), + } +} + +type commsEmailLogColumns struct { + expr.ColumnsExpr + tableAlias string + Created psql.Expression + Destination psql.Expression + Source psql.Expression + Type psql.Expression +} + +func (c commsEmailLogColumns) Alias() string { + return c.tableAlias +} + +func (commsEmailLogColumns) AliasedAs(alias string) commsEmailLogColumns { + return buildCommsEmailLogColumns(alias) +} + +// CommsEmailLogSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type CommsEmailLogSetter struct { + Created omit.Val[time.Time] `db:"created" ` + Destination omit.Val[string] `db:"destination,pk" ` + Source omit.Val[string] `db:"source,pk" ` + Type omit.Val[enums.CommsEmailmessagetype] `db:"type,pk" ` +} + +func (s CommsEmailLogSetter) SetColumns() []string { + vals := make([]string, 0, 4) + if s.Created.IsValue() { + vals = append(vals, "created") + } + if s.Destination.IsValue() { + vals = append(vals, "destination") + } + if s.Source.IsValue() { + vals = append(vals, "source") + } + if s.Type.IsValue() { + vals = append(vals, "type") + } + return vals +} + +func (s CommsEmailLogSetter) Overwrite(t *CommsEmailLog) { + if s.Created.IsValue() { + t.Created = s.Created.MustGet() + } + if s.Destination.IsValue() { + t.Destination = s.Destination.MustGet() + } + if s.Source.IsValue() { + t.Source = s.Source.MustGet() + } + if s.Type.IsValue() { + t.Type = s.Type.MustGet() + } +} + +func (s *CommsEmailLogSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return CommsEmailLogs.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, 4) + if s.Created.IsValue() { + vals[0] = psql.Arg(s.Created.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.Destination.IsValue() { + vals[1] = psql.Arg(s.Destination.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if s.Source.IsValue() { + vals[2] = psql.Arg(s.Source.MustGet()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + if s.Type.IsValue() { + vals[3] = psql.Arg(s.Type.MustGet()) + } else { + vals[3] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s CommsEmailLogSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s CommsEmailLogSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 4) + + if s.Created.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "created")...), + psql.Arg(s.Created), + }}) + } + + if s.Destination.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "destination")...), + psql.Arg(s.Destination), + }}) + } + + if s.Source.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "source")...), + psql.Arg(s.Source), + }}) + } + + if s.Type.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "type")...), + psql.Arg(s.Type), + }}) + } + + return exprs +} + +// FindCommsEmailLog retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindCommsEmailLog(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsEmailmessagetype, cols ...string) (*CommsEmailLog, error) { + if len(cols) == 0 { + return CommsEmailLogs.Query( + sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), + sm.Where(CommsEmailLogs.Columns.Source.EQ(psql.Arg(SourcePK))), + sm.Where(CommsEmailLogs.Columns.Type.EQ(psql.Arg(TypePK))), + ).One(ctx, exec) + } + + return CommsEmailLogs.Query( + sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), + sm.Where(CommsEmailLogs.Columns.Source.EQ(psql.Arg(SourcePK))), + sm.Where(CommsEmailLogs.Columns.Type.EQ(psql.Arg(TypePK))), + sm.Columns(CommsEmailLogs.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// CommsEmailLogExists checks the presence of a single record by primary key +func CommsEmailLogExists(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsEmailmessagetype) (bool, error) { + return CommsEmailLogs.Query( + sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), + sm.Where(CommsEmailLogs.Columns.Source.EQ(psql.Arg(SourcePK))), + sm.Where(CommsEmailLogs.Columns.Type.EQ(psql.Arg(TypePK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after CommsEmailLog is retrieved from the database +func (o *CommsEmailLog) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsEmailLogs.AfterSelectHooks.RunHooks(ctx, exec, CommsEmailLogSlice{o}) + case bob.QueryTypeInsert: + ctx, err = CommsEmailLogs.AfterInsertHooks.RunHooks(ctx, exec, CommsEmailLogSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = CommsEmailLogs.AfterUpdateHooks.RunHooks(ctx, exec, CommsEmailLogSlice{o}) + case bob.QueryTypeDelete: + ctx, err = CommsEmailLogs.AfterDeleteHooks.RunHooks(ctx, exec, CommsEmailLogSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the CommsEmailLog +func (o *CommsEmailLog) primaryKeyVals() bob.Expression { + return psql.ArgGroup( + o.Destination, + o.Source, + o.Type, + ) +} + +func (o *CommsEmailLog) pkEQ() dialect.Expression { + return psql.Group(psql.Quote("comms.email_log", "destination"), psql.Quote("comms.email_log", "source"), psql.Quote("comms.email_log", "type")).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 CommsEmailLog +func (o *CommsEmailLog) Update(ctx context.Context, exec bob.Executor, s *CommsEmailLogSetter) error { + v, err := CommsEmailLogs.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 CommsEmailLog record with an executor +func (o *CommsEmailLog) Delete(ctx context.Context, exec bob.Executor) error { + _, err := CommsEmailLogs.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the CommsEmailLog using the executor +func (o *CommsEmailLog) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := CommsEmailLogs.Query( + sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(o.Destination))), + sm.Where(CommsEmailLogs.Columns.Source.EQ(psql.Arg(o.Source))), + sm.Where(CommsEmailLogs.Columns.Type.EQ(psql.Arg(o.Type))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after CommsEmailLogSlice is retrieved from the database +func (o CommsEmailLogSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsEmailLogs.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = CommsEmailLogs.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = CommsEmailLogs.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = CommsEmailLogs.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o CommsEmailLogSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Group(psql.Quote("comms.email_log", "destination"), psql.Quote("comms.email_log", "source"), psql.Quote("comms.email_log", "type")).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 CommsEmailLogSlice) copyMatchingRows(from ...*CommsEmailLog) { + for i, old := range o { + for _, new := range from { + if new.Destination != old.Destination { + continue + } + if new.Source != old.Source { + continue + } + if new.Type != old.Type { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o CommsEmailLogSlice) 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 CommsEmailLogs.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 *CommsEmailLog: + o.copyMatchingRows(retrieved) + case []*CommsEmailLog: + o.copyMatchingRows(retrieved...) + case CommsEmailLogSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsEmailLog or a slice of CommsEmailLog + // then run the AfterUpdateHooks on the slice + _, err = CommsEmailLogs.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o CommsEmailLogSlice) 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 CommsEmailLogs.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 *CommsEmailLog: + o.copyMatchingRows(retrieved) + case []*CommsEmailLog: + o.copyMatchingRows(retrieved...) + case CommsEmailLogSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsEmailLog or a slice of CommsEmailLog + // then run the AfterDeleteHooks on the slice + _, err = CommsEmailLogs.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o CommsEmailLogSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals CommsEmailLogSetter) error { + if len(o) == 0 { + return nil + } + + _, err := CommsEmailLogs.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o CommsEmailLogSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := CommsEmailLogs.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o CommsEmailLogSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := CommsEmailLogs.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// DestinationEmail starts a query for related objects on comms.email +func (o *CommsEmailLog) DestinationEmail(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailsQuery { + return CommsEmails.Query(append(mods, + sm.Where(CommsEmails.Columns.Address.EQ(psql.Arg(o.Destination))), + )...) +} + +func (os CommsEmailLogSlice) DestinationEmail(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailsQuery { + pkDestination := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkDestination = append(pkDestination, o.Destination) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkDestination), "text[]")), + )) + + return CommsEmails.Query(append(mods, + sm.Where(psql.Group(CommsEmails.Columns.Address).OP("IN", PKArgExpr)), + )...) +} + +// SourcePhone starts a query for related objects on comms.phone +func (o *CommsEmailLog) SourcePhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + return CommsPhones.Query(append(mods, + sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(o.Source))), + )...) +} + +func (os CommsEmailLogSlice) SourcePhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + pkSource := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkSource = append(pkSource, o.Source) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkSource), "text[]")), + )) + + return CommsPhones.Query(append(mods, + sm.Where(psql.Group(CommsPhones.Columns.E164).OP("IN", PKArgExpr)), + )...) +} + +func attachCommsEmailLogDestinationEmail0(ctx context.Context, exec bob.Executor, count int, commsEmailLog0 *CommsEmailLog, commsEmail1 *CommsEmail) (*CommsEmailLog, error) { + setter := &CommsEmailLogSetter{ + Destination: omit.From(commsEmail1.Address), + } + + err := commsEmailLog0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachCommsEmailLogDestinationEmail0: %w", err) + } + + return commsEmailLog0, nil +} + +func (commsEmailLog0 *CommsEmailLog) InsertDestinationEmail(ctx context.Context, exec bob.Executor, related *CommsEmailSetter) error { + var err error + + commsEmail1, err := CommsEmails.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachCommsEmailLogDestinationEmail0(ctx, exec, 1, commsEmailLog0, commsEmail1) + if err != nil { + return err + } + + commsEmailLog0.R.DestinationEmail = commsEmail1 + + commsEmail1.R.DestinationEmailLogs = append(commsEmail1.R.DestinationEmailLogs, commsEmailLog0) + + return nil +} + +func (commsEmailLog0 *CommsEmailLog) AttachDestinationEmail(ctx context.Context, exec bob.Executor, commsEmail1 *CommsEmail) error { + var err error + + _, err = attachCommsEmailLogDestinationEmail0(ctx, exec, 1, commsEmailLog0, commsEmail1) + if err != nil { + return err + } + + commsEmailLog0.R.DestinationEmail = commsEmail1 + + commsEmail1.R.DestinationEmailLogs = append(commsEmail1.R.DestinationEmailLogs, commsEmailLog0) + + return nil +} + +func attachCommsEmailLogSourcePhone0(ctx context.Context, exec bob.Executor, count int, commsEmailLog0 *CommsEmailLog, commsPhone1 *CommsPhone) (*CommsEmailLog, error) { + setter := &CommsEmailLogSetter{ + Source: omit.From(commsPhone1.E164), + } + + err := commsEmailLog0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachCommsEmailLogSourcePhone0: %w", err) + } + + return commsEmailLog0, nil +} + +func (commsEmailLog0 *CommsEmailLog) InsertSourcePhone(ctx context.Context, exec bob.Executor, related *CommsPhoneSetter) error { + var err error + + commsPhone1, err := CommsPhones.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachCommsEmailLogSourcePhone0(ctx, exec, 1, commsEmailLog0, commsPhone1) + if err != nil { + return err + } + + commsEmailLog0.R.SourcePhone = commsPhone1 + + commsPhone1.R.SourceEmailLogs = append(commsPhone1.R.SourceEmailLogs, commsEmailLog0) + + return nil +} + +func (commsEmailLog0 *CommsEmailLog) AttachSourcePhone(ctx context.Context, exec bob.Executor, commsPhone1 *CommsPhone) error { + var err error + + _, err = attachCommsEmailLogSourcePhone0(ctx, exec, 1, commsEmailLog0, commsPhone1) + if err != nil { + return err + } + + commsEmailLog0.R.SourcePhone = commsPhone1 + + commsPhone1.R.SourceEmailLogs = append(commsPhone1.R.SourceEmailLogs, commsEmailLog0) + + return nil +} + +type commsEmailLogWhere[Q psql.Filterable] struct { + Created psql.WhereMod[Q, time.Time] + Destination psql.WhereMod[Q, string] + Source psql.WhereMod[Q, string] + Type psql.WhereMod[Q, enums.CommsEmailmessagetype] +} + +func (commsEmailLogWhere[Q]) AliasedAs(alias string) commsEmailLogWhere[Q] { + return buildCommsEmailLogWhere[Q](buildCommsEmailLogColumns(alias)) +} + +func buildCommsEmailLogWhere[Q psql.Filterable](cols commsEmailLogColumns) commsEmailLogWhere[Q] { + return commsEmailLogWhere[Q]{ + Created: psql.Where[Q, time.Time](cols.Created), + Destination: psql.Where[Q, string](cols.Destination), + Source: psql.Where[Q, string](cols.Source), + Type: psql.Where[Q, enums.CommsEmailmessagetype](cols.Type), + } +} + +func (o *CommsEmailLog) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "DestinationEmail": + rel, ok := retrieved.(*CommsEmail) + if !ok { + return fmt.Errorf("commsEmailLog cannot load %T as %q", retrieved, name) + } + + o.R.DestinationEmail = rel + + if rel != nil { + rel.R.DestinationEmailLogs = CommsEmailLogSlice{o} + } + return nil + case "SourcePhone": + rel, ok := retrieved.(*CommsPhone) + if !ok { + return fmt.Errorf("commsEmailLog cannot load %T as %q", retrieved, name) + } + + o.R.SourcePhone = rel + + if rel != nil { + rel.R.SourceEmailLogs = CommsEmailLogSlice{o} + } + return nil + default: + return fmt.Errorf("commsEmailLog has no relationship %q", name) + } +} + +type commsEmailLogPreloader struct { + DestinationEmail func(...psql.PreloadOption) psql.Preloader + SourcePhone func(...psql.PreloadOption) psql.Preloader +} + +func buildCommsEmailLogPreloader() commsEmailLogPreloader { + return commsEmailLogPreloader{ + DestinationEmail: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*CommsEmail, CommsEmailSlice](psql.PreloadRel{ + Name: "DestinationEmail", + Sides: []psql.PreloadSide{ + { + From: CommsEmailLogs, + To: CommsEmails, + FromColumns: []string{"destination"}, + ToColumns: []string{"address"}, + }, + }, + }, CommsEmails.Columns.Names(), opts...) + }, + SourcePhone: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*CommsPhone, CommsPhoneSlice](psql.PreloadRel{ + Name: "SourcePhone", + Sides: []psql.PreloadSide{ + { + From: CommsEmailLogs, + To: CommsPhones, + FromColumns: []string{"source"}, + ToColumns: []string{"e164"}, + }, + }, + }, CommsPhones.Columns.Names(), opts...) + }, + } +} + +type commsEmailLogThenLoader[Q orm.Loadable] struct { + DestinationEmail func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SourcePhone func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildCommsEmailLogThenLoader[Q orm.Loadable]() commsEmailLogThenLoader[Q] { + type DestinationEmailLoadInterface interface { + LoadDestinationEmail(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type SourcePhoneLoadInterface interface { + LoadSourcePhone(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return commsEmailLogThenLoader[Q]{ + DestinationEmail: thenLoadBuilder[Q]( + "DestinationEmail", + func(ctx context.Context, exec bob.Executor, retrieved DestinationEmailLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadDestinationEmail(ctx, exec, mods...) + }, + ), + SourcePhone: thenLoadBuilder[Q]( + "SourcePhone", + func(ctx context.Context, exec bob.Executor, retrieved SourcePhoneLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadSourcePhone(ctx, exec, mods...) + }, + ), + } +} + +// LoadDestinationEmail loads the commsEmailLog's DestinationEmail into the .R struct +func (o *CommsEmailLog) LoadDestinationEmail(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.DestinationEmail = nil + + related, err := o.DestinationEmail(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.DestinationEmailLogs = CommsEmailLogSlice{o} + + o.R.DestinationEmail = related + return nil +} + +// LoadDestinationEmail loads the commsEmailLog's DestinationEmail into the .R struct +func (os CommsEmailLogSlice) LoadDestinationEmail(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsEmails, err := os.DestinationEmail(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsEmails { + + if !(o.Destination == rel.Address) { + continue + } + + rel.R.DestinationEmailLogs = append(rel.R.DestinationEmailLogs, o) + + o.R.DestinationEmail = rel + break + } + } + + return nil +} + +// LoadSourcePhone loads the commsEmailLog's SourcePhone into the .R struct +func (o *CommsEmailLog) LoadSourcePhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.SourcePhone = nil + + related, err := o.SourcePhone(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.SourceEmailLogs = CommsEmailLogSlice{o} + + o.R.SourcePhone = related + return nil +} + +// LoadSourcePhone loads the commsEmailLog's SourcePhone into the .R struct +func (os CommsEmailLogSlice) LoadSourcePhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsPhones, err := os.SourcePhone(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsPhones { + + if !(o.Source == rel.E164) { + continue + } + + rel.R.SourceEmailLogs = append(rel.R.SourceEmailLogs, o) + + o.R.SourcePhone = rel + break + } + } + + return nil +} + +type commsEmailLogJoins[Q dialect.Joinable] struct { + typ string + DestinationEmail modAs[Q, commsEmailColumns] + SourcePhone modAs[Q, commsPhoneColumns] +} + +func (j commsEmailLogJoins[Q]) aliasedAs(alias string) commsEmailLogJoins[Q] { + return buildCommsEmailLogJoins[Q](buildCommsEmailLogColumns(alias), j.typ) +} + +func buildCommsEmailLogJoins[Q dialect.Joinable](cols commsEmailLogColumns, typ string) commsEmailLogJoins[Q] { + return commsEmailLogJoins[Q]{ + typ: typ, + DestinationEmail: modAs[Q, commsEmailColumns]{ + c: CommsEmails.Columns, + f: func(to commsEmailColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsEmails.Name().As(to.Alias())).On( + to.Address.EQ(cols.Destination), + )) + } + + return mods + }, + }, + SourcePhone: modAs[Q, commsPhoneColumns]{ + c: CommsPhones.Columns, + f: func(to commsPhoneColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsPhones.Name().As(to.Alias())).On( + to.E164.EQ(cols.Source), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/comms.phone.bob.go b/db/models/comms.phone.bob.go new file mode 100644 index 00000000..ece6bcf3 --- /dev/null +++ b/db/models/comms.phone.bob.go @@ -0,0 +1,1220 @@ +// Code generated by BobGen psql v0.42.1. 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/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" +) + +// CommsPhone is an object representing the database table. +type CommsPhone struct { + E164 string `db:"e164,pk" ` + IsSubscribed bool `db:"is_subscribed" ` + + R commsPhoneR `db:"-" ` + + C commsPhoneC `db:"-" ` +} + +// CommsPhoneSlice is an alias for a slice of pointers to CommsPhone. +// This should almost always be used instead of []*CommsPhone. +type CommsPhoneSlice []*CommsPhone + +// CommsPhones contains methods to work with the phone table +var CommsPhones = psql.NewTablex[*CommsPhone, CommsPhoneSlice, *CommsPhoneSetter]("comms", "phone", buildCommsPhoneColumns("comms.phone")) + +// CommsPhonesQuery is a query on the phone table +type CommsPhonesQuery = *psql.ViewQuery[*CommsPhone, CommsPhoneSlice] + +// commsPhoneR is where relationships are stored. +type commsPhoneR struct { + SourceEmailLogs CommsEmailLogSlice // comms.email_log.email_log_source_fkey + DestinationSMSLogs CommsSMSLogSlice // comms.sms_log.sms_log_destination_fkey + SourceSMSLogs CommsSMSLogSlice // comms.sms_log.sms_log_source_fkey +} + +func buildCommsPhoneColumns(alias string) commsPhoneColumns { + return commsPhoneColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "e164", "is_subscribed", + ).WithParent("comms.phone"), + tableAlias: alias, + E164: psql.Quote(alias, "e164"), + IsSubscribed: psql.Quote(alias, "is_subscribed"), + } +} + +type commsPhoneColumns struct { + expr.ColumnsExpr + tableAlias string + E164 psql.Expression + IsSubscribed psql.Expression +} + +func (c commsPhoneColumns) Alias() string { + return c.tableAlias +} + +func (commsPhoneColumns) AliasedAs(alias string) commsPhoneColumns { + return buildCommsPhoneColumns(alias) +} + +// CommsPhoneSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type CommsPhoneSetter struct { + E164 omit.Val[string] `db:"e164,pk" ` + IsSubscribed omit.Val[bool] `db:"is_subscribed" ` +} + +func (s CommsPhoneSetter) SetColumns() []string { + vals := make([]string, 0, 2) + if s.E164.IsValue() { + vals = append(vals, "e164") + } + if s.IsSubscribed.IsValue() { + vals = append(vals, "is_subscribed") + } + return vals +} + +func (s CommsPhoneSetter) Overwrite(t *CommsPhone) { + if s.E164.IsValue() { + t.E164 = s.E164.MustGet() + } + if s.IsSubscribed.IsValue() { + t.IsSubscribed = s.IsSubscribed.MustGet() + } +} + +func (s *CommsPhoneSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return CommsPhones.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, 2) + if s.E164.IsValue() { + vals[0] = psql.Arg(s.E164.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.IsSubscribed.IsValue() { + vals[1] = psql.Arg(s.IsSubscribed.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s CommsPhoneSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s CommsPhoneSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 2) + + if s.E164.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "e164")...), + psql.Arg(s.E164), + }}) + } + + if s.IsSubscribed.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "is_subscribed")...), + psql.Arg(s.IsSubscribed), + }}) + } + + return exprs +} + +// FindCommsPhone retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindCommsPhone(ctx context.Context, exec bob.Executor, E164PK string, cols ...string) (*CommsPhone, error) { + if len(cols) == 0 { + return CommsPhones.Query( + sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(E164PK))), + ).One(ctx, exec) + } + + return CommsPhones.Query( + sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(E164PK))), + sm.Columns(CommsPhones.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// CommsPhoneExists checks the presence of a single record by primary key +func CommsPhoneExists(ctx context.Context, exec bob.Executor, E164PK string) (bool, error) { + return CommsPhones.Query( + sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(E164PK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after CommsPhone is retrieved from the database +func (o *CommsPhone) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsPhones.AfterSelectHooks.RunHooks(ctx, exec, CommsPhoneSlice{o}) + case bob.QueryTypeInsert: + ctx, err = CommsPhones.AfterInsertHooks.RunHooks(ctx, exec, CommsPhoneSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = CommsPhones.AfterUpdateHooks.RunHooks(ctx, exec, CommsPhoneSlice{o}) + case bob.QueryTypeDelete: + ctx, err = CommsPhones.AfterDeleteHooks.RunHooks(ctx, exec, CommsPhoneSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the CommsPhone +func (o *CommsPhone) primaryKeyVals() bob.Expression { + return psql.Arg(o.E164) +} + +func (o *CommsPhone) pkEQ() dialect.Expression { + return psql.Quote("comms.phone", "e164").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 CommsPhone +func (o *CommsPhone) Update(ctx context.Context, exec bob.Executor, s *CommsPhoneSetter) error { + v, err := CommsPhones.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 CommsPhone record with an executor +func (o *CommsPhone) Delete(ctx context.Context, exec bob.Executor) error { + _, err := CommsPhones.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the CommsPhone using the executor +func (o *CommsPhone) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := CommsPhones.Query( + sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(o.E164))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after CommsPhoneSlice is retrieved from the database +func (o CommsPhoneSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsPhones.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = CommsPhones.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = CommsPhones.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = CommsPhones.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o CommsPhoneSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Quote("comms.phone", "e164").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 CommsPhoneSlice) copyMatchingRows(from ...*CommsPhone) { + for i, old := range o { + for _, new := range from { + if new.E164 != old.E164 { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o CommsPhoneSlice) 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 CommsPhones.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 *CommsPhone: + o.copyMatchingRows(retrieved) + case []*CommsPhone: + o.copyMatchingRows(retrieved...) + case CommsPhoneSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsPhone or a slice of CommsPhone + // then run the AfterUpdateHooks on the slice + _, err = CommsPhones.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o CommsPhoneSlice) 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 CommsPhones.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 *CommsPhone: + o.copyMatchingRows(retrieved) + case []*CommsPhone: + o.copyMatchingRows(retrieved...) + case CommsPhoneSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsPhone or a slice of CommsPhone + // then run the AfterDeleteHooks on the slice + _, err = CommsPhones.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o CommsPhoneSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals CommsPhoneSetter) error { + if len(o) == 0 { + return nil + } + + _, err := CommsPhones.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o CommsPhoneSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := CommsPhones.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o CommsPhoneSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := CommsPhones.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// SourceEmailLogs starts a query for related objects on comms.email_log +func (o *CommsPhone) SourceEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { + return CommsEmailLogs.Query(append(mods, + sm.Where(CommsEmailLogs.Columns.Source.EQ(psql.Arg(o.E164))), + )...) +} + +func (os CommsPhoneSlice) SourceEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { + pkE164 := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkE164 = append(pkE164, o.E164) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkE164), "text[]")), + )) + + return CommsEmailLogs.Query(append(mods, + sm.Where(psql.Group(CommsEmailLogs.Columns.Source).OP("IN", PKArgExpr)), + )...) +} + +// DestinationSMSLogs starts a query for related objects on comms.sms_log +func (o *CommsPhone) DestinationSMSLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsSMSLogsQuery { + return CommsSMSLogs.Query(append(mods, + sm.Where(CommsSMSLogs.Columns.Destination.EQ(psql.Arg(o.E164))), + )...) +} + +func (os CommsPhoneSlice) DestinationSMSLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsSMSLogsQuery { + pkE164 := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkE164 = append(pkE164, o.E164) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkE164), "text[]")), + )) + + return CommsSMSLogs.Query(append(mods, + sm.Where(psql.Group(CommsSMSLogs.Columns.Destination).OP("IN", PKArgExpr)), + )...) +} + +// SourceSMSLogs starts a query for related objects on comms.sms_log +func (o *CommsPhone) SourceSMSLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsSMSLogsQuery { + return CommsSMSLogs.Query(append(mods, + sm.Where(CommsSMSLogs.Columns.Source.EQ(psql.Arg(o.E164))), + )...) +} + +func (os CommsPhoneSlice) SourceSMSLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsSMSLogsQuery { + pkE164 := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkE164 = append(pkE164, o.E164) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkE164), "text[]")), + )) + + return CommsSMSLogs.Query(append(mods, + sm.Where(psql.Group(CommsSMSLogs.Columns.Source).OP("IN", PKArgExpr)), + )...) +} + +func insertCommsPhoneSourceEmailLogs0(ctx context.Context, exec bob.Executor, commsEmailLogs1 []*CommsEmailLogSetter, commsPhone0 *CommsPhone) (CommsEmailLogSlice, error) { + for i := range commsEmailLogs1 { + commsEmailLogs1[i].Source = omit.From(commsPhone0.E164) + } + + ret, err := CommsEmailLogs.Insert(bob.ToMods(commsEmailLogs1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertCommsPhoneSourceEmailLogs0: %w", err) + } + + return ret, nil +} + +func attachCommsPhoneSourceEmailLogs0(ctx context.Context, exec bob.Executor, count int, commsEmailLogs1 CommsEmailLogSlice, commsPhone0 *CommsPhone) (CommsEmailLogSlice, error) { + setter := &CommsEmailLogSetter{ + Source: omit.From(commsPhone0.E164), + } + + err := commsEmailLogs1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachCommsPhoneSourceEmailLogs0: %w", err) + } + + return commsEmailLogs1, nil +} + +func (commsPhone0 *CommsPhone) InsertSourceEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLogSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + commsEmailLogs1, err := insertCommsPhoneSourceEmailLogs0(ctx, exec, related, commsPhone0) + if err != nil { + return err + } + + commsPhone0.R.SourceEmailLogs = append(commsPhone0.R.SourceEmailLogs, commsEmailLogs1...) + + for _, rel := range commsEmailLogs1 { + rel.R.SourcePhone = commsPhone0 + } + return nil +} + +func (commsPhone0 *CommsPhone) AttachSourceEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLog) error { + if len(related) == 0 { + return nil + } + + var err error + commsEmailLogs1 := CommsEmailLogSlice(related) + + _, err = attachCommsPhoneSourceEmailLogs0(ctx, exec, len(related), commsEmailLogs1, commsPhone0) + if err != nil { + return err + } + + commsPhone0.R.SourceEmailLogs = append(commsPhone0.R.SourceEmailLogs, commsEmailLogs1...) + + for _, rel := range related { + rel.R.SourcePhone = commsPhone0 + } + + return nil +} + +func insertCommsPhoneDestinationSMSLogs0(ctx context.Context, exec bob.Executor, commsSMSLogs1 []*CommsSMSLogSetter, commsPhone0 *CommsPhone) (CommsSMSLogSlice, error) { + for i := range commsSMSLogs1 { + commsSMSLogs1[i].Destination = omit.From(commsPhone0.E164) + } + + ret, err := CommsSMSLogs.Insert(bob.ToMods(commsSMSLogs1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertCommsPhoneDestinationSMSLogs0: %w", err) + } + + return ret, nil +} + +func attachCommsPhoneDestinationSMSLogs0(ctx context.Context, exec bob.Executor, count int, commsSMSLogs1 CommsSMSLogSlice, commsPhone0 *CommsPhone) (CommsSMSLogSlice, error) { + setter := &CommsSMSLogSetter{ + Destination: omit.From(commsPhone0.E164), + } + + err := commsSMSLogs1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachCommsPhoneDestinationSMSLogs0: %w", err) + } + + return commsSMSLogs1, nil +} + +func (commsPhone0 *CommsPhone) InsertDestinationSMSLogs(ctx context.Context, exec bob.Executor, related ...*CommsSMSLogSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + commsSMSLogs1, err := insertCommsPhoneDestinationSMSLogs0(ctx, exec, related, commsPhone0) + if err != nil { + return err + } + + commsPhone0.R.DestinationSMSLogs = append(commsPhone0.R.DestinationSMSLogs, commsSMSLogs1...) + + for _, rel := range commsSMSLogs1 { + rel.R.DestinationPhone = commsPhone0 + } + return nil +} + +func (commsPhone0 *CommsPhone) AttachDestinationSMSLogs(ctx context.Context, exec bob.Executor, related ...*CommsSMSLog) error { + if len(related) == 0 { + return nil + } + + var err error + commsSMSLogs1 := CommsSMSLogSlice(related) + + _, err = attachCommsPhoneDestinationSMSLogs0(ctx, exec, len(related), commsSMSLogs1, commsPhone0) + if err != nil { + return err + } + + commsPhone0.R.DestinationSMSLogs = append(commsPhone0.R.DestinationSMSLogs, commsSMSLogs1...) + + for _, rel := range related { + rel.R.DestinationPhone = commsPhone0 + } + + return nil +} + +func insertCommsPhoneSourceSMSLogs0(ctx context.Context, exec bob.Executor, commsSMSLogs1 []*CommsSMSLogSetter, commsPhone0 *CommsPhone) (CommsSMSLogSlice, error) { + for i := range commsSMSLogs1 { + commsSMSLogs1[i].Source = omit.From(commsPhone0.E164) + } + + ret, err := CommsSMSLogs.Insert(bob.ToMods(commsSMSLogs1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertCommsPhoneSourceSMSLogs0: %w", err) + } + + return ret, nil +} + +func attachCommsPhoneSourceSMSLogs0(ctx context.Context, exec bob.Executor, count int, commsSMSLogs1 CommsSMSLogSlice, commsPhone0 *CommsPhone) (CommsSMSLogSlice, error) { + setter := &CommsSMSLogSetter{ + Source: omit.From(commsPhone0.E164), + } + + err := commsSMSLogs1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachCommsPhoneSourceSMSLogs0: %w", err) + } + + return commsSMSLogs1, nil +} + +func (commsPhone0 *CommsPhone) InsertSourceSMSLogs(ctx context.Context, exec bob.Executor, related ...*CommsSMSLogSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + commsSMSLogs1, err := insertCommsPhoneSourceSMSLogs0(ctx, exec, related, commsPhone0) + if err != nil { + return err + } + + commsPhone0.R.SourceSMSLogs = append(commsPhone0.R.SourceSMSLogs, commsSMSLogs1...) + + for _, rel := range commsSMSLogs1 { + rel.R.SourcePhone = commsPhone0 + } + return nil +} + +func (commsPhone0 *CommsPhone) AttachSourceSMSLogs(ctx context.Context, exec bob.Executor, related ...*CommsSMSLog) error { + if len(related) == 0 { + return nil + } + + var err error + commsSMSLogs1 := CommsSMSLogSlice(related) + + _, err = attachCommsPhoneSourceSMSLogs0(ctx, exec, len(related), commsSMSLogs1, commsPhone0) + if err != nil { + return err + } + + commsPhone0.R.SourceSMSLogs = append(commsPhone0.R.SourceSMSLogs, commsSMSLogs1...) + + for _, rel := range related { + rel.R.SourcePhone = commsPhone0 + } + + return nil +} + +type commsPhoneWhere[Q psql.Filterable] struct { + E164 psql.WhereMod[Q, string] + IsSubscribed psql.WhereMod[Q, bool] +} + +func (commsPhoneWhere[Q]) AliasedAs(alias string) commsPhoneWhere[Q] { + return buildCommsPhoneWhere[Q](buildCommsPhoneColumns(alias)) +} + +func buildCommsPhoneWhere[Q psql.Filterable](cols commsPhoneColumns) commsPhoneWhere[Q] { + return commsPhoneWhere[Q]{ + E164: psql.Where[Q, string](cols.E164), + IsSubscribed: psql.Where[Q, bool](cols.IsSubscribed), + } +} + +func (o *CommsPhone) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "SourceEmailLogs": + rels, ok := retrieved.(CommsEmailLogSlice) + if !ok { + return fmt.Errorf("commsPhone cannot load %T as %q", retrieved, name) + } + + o.R.SourceEmailLogs = rels + + for _, rel := range rels { + if rel != nil { + rel.R.SourcePhone = o + } + } + return nil + case "DestinationSMSLogs": + rels, ok := retrieved.(CommsSMSLogSlice) + if !ok { + return fmt.Errorf("commsPhone cannot load %T as %q", retrieved, name) + } + + o.R.DestinationSMSLogs = rels + + for _, rel := range rels { + if rel != nil { + rel.R.DestinationPhone = o + } + } + return nil + case "SourceSMSLogs": + rels, ok := retrieved.(CommsSMSLogSlice) + if !ok { + return fmt.Errorf("commsPhone cannot load %T as %q", retrieved, name) + } + + o.R.SourceSMSLogs = rels + + for _, rel := range rels { + if rel != nil { + rel.R.SourcePhone = o + } + } + return nil + default: + return fmt.Errorf("commsPhone has no relationship %q", name) + } +} + +type commsPhonePreloader struct{} + +func buildCommsPhonePreloader() commsPhonePreloader { + return commsPhonePreloader{} +} + +type commsPhoneThenLoader[Q orm.Loadable] struct { + SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + DestinationSMSLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SourceSMSLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { + type SourceEmailLogsLoadInterface interface { + LoadSourceEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type DestinationSMSLogsLoadInterface interface { + LoadDestinationSMSLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type SourceSMSLogsLoadInterface interface { + LoadSourceSMSLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return commsPhoneThenLoader[Q]{ + SourceEmailLogs: thenLoadBuilder[Q]( + "SourceEmailLogs", + func(ctx context.Context, exec bob.Executor, retrieved SourceEmailLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadSourceEmailLogs(ctx, exec, mods...) + }, + ), + DestinationSMSLogs: thenLoadBuilder[Q]( + "DestinationSMSLogs", + func(ctx context.Context, exec bob.Executor, retrieved DestinationSMSLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadDestinationSMSLogs(ctx, exec, mods...) + }, + ), + SourceSMSLogs: thenLoadBuilder[Q]( + "SourceSMSLogs", + func(ctx context.Context, exec bob.Executor, retrieved SourceSMSLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadSourceSMSLogs(ctx, exec, mods...) + }, + ), + } +} + +// LoadSourceEmailLogs loads the commsPhone's SourceEmailLogs into the .R struct +func (o *CommsPhone) LoadSourceEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.SourceEmailLogs = nil + + related, err := o.SourceEmailLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.SourcePhone = o + } + + o.R.SourceEmailLogs = related + return nil +} + +// LoadSourceEmailLogs loads the commsPhone's SourceEmailLogs into the .R struct +func (os CommsPhoneSlice) LoadSourceEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsEmailLogs, err := os.SourceEmailLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.SourceEmailLogs = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsEmailLogs { + + if !(o.E164 == rel.Source) { + continue + } + + rel.R.SourcePhone = o + + o.R.SourceEmailLogs = append(o.R.SourceEmailLogs, rel) + } + } + + return nil +} + +// LoadDestinationSMSLogs loads the commsPhone's DestinationSMSLogs into the .R struct +func (o *CommsPhone) LoadDestinationSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.DestinationSMSLogs = nil + + related, err := o.DestinationSMSLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.DestinationPhone = o + } + + o.R.DestinationSMSLogs = related + return nil +} + +// LoadDestinationSMSLogs loads the commsPhone's DestinationSMSLogs into the .R struct +func (os CommsPhoneSlice) LoadDestinationSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsSMSLogs, err := os.DestinationSMSLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.DestinationSMSLogs = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsSMSLogs { + + if !(o.E164 == rel.Destination) { + continue + } + + rel.R.DestinationPhone = o + + o.R.DestinationSMSLogs = append(o.R.DestinationSMSLogs, rel) + } + } + + return nil +} + +// LoadSourceSMSLogs loads the commsPhone's SourceSMSLogs into the .R struct +func (o *CommsPhone) LoadSourceSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.SourceSMSLogs = nil + + related, err := o.SourceSMSLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.SourcePhone = o + } + + o.R.SourceSMSLogs = related + return nil +} + +// LoadSourceSMSLogs loads the commsPhone's SourceSMSLogs into the .R struct +func (os CommsPhoneSlice) LoadSourceSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsSMSLogs, err := os.SourceSMSLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.SourceSMSLogs = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsSMSLogs { + + if !(o.E164 == rel.Source) { + continue + } + + rel.R.SourcePhone = o + + o.R.SourceSMSLogs = append(o.R.SourceSMSLogs, rel) + } + } + + return nil +} + +// commsPhoneC is where relationship counts are stored. +type commsPhoneC struct { + SourceEmailLogs *int64 + DestinationSMSLogs *int64 + SourceSMSLogs *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *CommsPhone) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "SourceEmailLogs": + o.C.SourceEmailLogs = &count + case "DestinationSMSLogs": + o.C.DestinationSMSLogs = &count + case "SourceSMSLogs": + o.C.SourceSMSLogs = &count + } + return nil +} + +type commsPhoneCountPreloader struct { + SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + DestinationSMSLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + SourceSMSLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { + return commsPhoneCountPreloader{ + SourceEmailLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*CommsPhone]("SourceEmailLogs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = CommsPhones.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(CommsEmailLogs.Name()), + sm.Where(psql.Quote(CommsEmailLogs.Alias(), "source").EQ(psql.Quote(parent, "e164"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + DestinationSMSLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*CommsPhone]("DestinationSMSLogs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = CommsPhones.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(CommsSMSLogs.Name()), + sm.Where(psql.Quote(CommsSMSLogs.Alias(), "destination").EQ(psql.Quote(parent, "e164"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + SourceSMSLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*CommsPhone]("SourceSMSLogs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = CommsPhones.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(CommsSMSLogs.Name()), + sm.Where(psql.Quote(CommsSMSLogs.Alias(), "source").EQ(psql.Quote(parent, "e164"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type commsPhoneCountThenLoader[Q orm.Loadable] struct { + SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + DestinationSMSLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SourceSMSLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[Q] { + type SourceEmailLogsCountInterface interface { + LoadCountSourceEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type DestinationSMSLogsCountInterface interface { + LoadCountDestinationSMSLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type SourceSMSLogsCountInterface interface { + LoadCountSourceSMSLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return commsPhoneCountThenLoader[Q]{ + SourceEmailLogs: countThenLoadBuilder[Q]( + "SourceEmailLogs", + func(ctx context.Context, exec bob.Executor, retrieved SourceEmailLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountSourceEmailLogs(ctx, exec, mods...) + }, + ), + DestinationSMSLogs: countThenLoadBuilder[Q]( + "DestinationSMSLogs", + func(ctx context.Context, exec bob.Executor, retrieved DestinationSMSLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountDestinationSMSLogs(ctx, exec, mods...) + }, + ), + SourceSMSLogs: countThenLoadBuilder[Q]( + "SourceSMSLogs", + func(ctx context.Context, exec bob.Executor, retrieved SourceSMSLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountSourceSMSLogs(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountSourceEmailLogs loads the count of SourceEmailLogs into the C struct +func (o *CommsPhone) LoadCountSourceEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.SourceEmailLogs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.SourceEmailLogs = &count + return nil +} + +// LoadCountSourceEmailLogs loads the count of SourceEmailLogs for a slice +func (os CommsPhoneSlice) LoadCountSourceEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountSourceEmailLogs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountDestinationSMSLogs loads the count of DestinationSMSLogs into the C struct +func (o *CommsPhone) LoadCountDestinationSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.DestinationSMSLogs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.DestinationSMSLogs = &count + return nil +} + +// LoadCountDestinationSMSLogs loads the count of DestinationSMSLogs for a slice +func (os CommsPhoneSlice) LoadCountDestinationSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountDestinationSMSLogs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountSourceSMSLogs loads the count of SourceSMSLogs into the C struct +func (o *CommsPhone) LoadCountSourceSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.SourceSMSLogs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.SourceSMSLogs = &count + return nil +} + +// LoadCountSourceSMSLogs loads the count of SourceSMSLogs for a slice +func (os CommsPhoneSlice) LoadCountSourceSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountSourceSMSLogs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +type commsPhoneJoins[Q dialect.Joinable] struct { + typ string + SourceEmailLogs modAs[Q, commsEmailLogColumns] + DestinationSMSLogs modAs[Q, commsSMSLogColumns] + SourceSMSLogs modAs[Q, commsSMSLogColumns] +} + +func (j commsPhoneJoins[Q]) aliasedAs(alias string) commsPhoneJoins[Q] { + return buildCommsPhoneJoins[Q](buildCommsPhoneColumns(alias), j.typ) +} + +func buildCommsPhoneJoins[Q dialect.Joinable](cols commsPhoneColumns, typ string) commsPhoneJoins[Q] { + return commsPhoneJoins[Q]{ + typ: typ, + SourceEmailLogs: modAs[Q, commsEmailLogColumns]{ + c: CommsEmailLogs.Columns, + f: func(to commsEmailLogColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsEmailLogs.Name().As(to.Alias())).On( + to.Source.EQ(cols.E164), + )) + } + + return mods + }, + }, + DestinationSMSLogs: modAs[Q, commsSMSLogColumns]{ + c: CommsSMSLogs.Columns, + f: func(to commsSMSLogColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsSMSLogs.Name().As(to.Alias())).On( + to.Destination.EQ(cols.E164), + )) + } + + return mods + }, + }, + SourceSMSLogs: modAs[Q, commsSMSLogColumns]{ + c: CommsSMSLogs.Columns, + f: func(to commsSMSLogColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsSMSLogs.Name().As(to.Alias())).On( + to.Source.EQ(cols.E164), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/comms.sms_log.bob.go b/db/models/comms.sms_log.bob.go new file mode 100644 index 00000000..fd54020e --- /dev/null +++ b/db/models/comms.sms_log.bob.go @@ -0,0 +1,848 @@ +// Code generated by BobGen psql v0.42.1. 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" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/aarondl/opt/omit" + "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" +) + +// CommsSMSLog is an object representing the database table. +type CommsSMSLog struct { + Created time.Time `db:"created" ` + Destination string `db:"destination,pk" ` + Source string `db:"source,pk" ` + Type enums.CommsSmsmessagetype `db:"type,pk" ` + + R commsSMSLogR `db:"-" ` +} + +// CommsSMSLogSlice is an alias for a slice of pointers to CommsSMSLog. +// This should almost always be used instead of []*CommsSMSLog. +type CommsSMSLogSlice []*CommsSMSLog + +// CommsSMSLogs contains methods to work with the sms_log table +var CommsSMSLogs = psql.NewTablex[*CommsSMSLog, CommsSMSLogSlice, *CommsSMSLogSetter]("comms", "sms_log", buildCommsSMSLogColumns("comms.sms_log")) + +// CommsSMSLogsQuery is a query on the sms_log table +type CommsSMSLogsQuery = *psql.ViewQuery[*CommsSMSLog, CommsSMSLogSlice] + +// commsSMSLogR is where relationships are stored. +type commsSMSLogR struct { + DestinationPhone *CommsPhone // comms.sms_log.sms_log_destination_fkey + SourcePhone *CommsPhone // comms.sms_log.sms_log_source_fkey +} + +func buildCommsSMSLogColumns(alias string) commsSMSLogColumns { + return commsSMSLogColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "created", "destination", "source", "type", + ).WithParent("comms.sms_log"), + tableAlias: alias, + Created: psql.Quote(alias, "created"), + Destination: psql.Quote(alias, "destination"), + Source: psql.Quote(alias, "source"), + Type: psql.Quote(alias, "type"), + } +} + +type commsSMSLogColumns struct { + expr.ColumnsExpr + tableAlias string + Created psql.Expression + Destination psql.Expression + Source psql.Expression + Type psql.Expression +} + +func (c commsSMSLogColumns) Alias() string { + return c.tableAlias +} + +func (commsSMSLogColumns) AliasedAs(alias string) commsSMSLogColumns { + return buildCommsSMSLogColumns(alias) +} + +// CommsSMSLogSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type CommsSMSLogSetter struct { + Created omit.Val[time.Time] `db:"created" ` + Destination omit.Val[string] `db:"destination,pk" ` + Source omit.Val[string] `db:"source,pk" ` + Type omit.Val[enums.CommsSmsmessagetype] `db:"type,pk" ` +} + +func (s CommsSMSLogSetter) SetColumns() []string { + vals := make([]string, 0, 4) + if s.Created.IsValue() { + vals = append(vals, "created") + } + if s.Destination.IsValue() { + vals = append(vals, "destination") + } + if s.Source.IsValue() { + vals = append(vals, "source") + } + if s.Type.IsValue() { + vals = append(vals, "type") + } + return vals +} + +func (s CommsSMSLogSetter) Overwrite(t *CommsSMSLog) { + if s.Created.IsValue() { + t.Created = s.Created.MustGet() + } + if s.Destination.IsValue() { + t.Destination = s.Destination.MustGet() + } + if s.Source.IsValue() { + t.Source = s.Source.MustGet() + } + if s.Type.IsValue() { + t.Type = s.Type.MustGet() + } +} + +func (s *CommsSMSLogSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return CommsSMSLogs.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, 4) + if s.Created.IsValue() { + vals[0] = psql.Arg(s.Created.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.Destination.IsValue() { + vals[1] = psql.Arg(s.Destination.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if s.Source.IsValue() { + vals[2] = psql.Arg(s.Source.MustGet()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + if s.Type.IsValue() { + vals[3] = psql.Arg(s.Type.MustGet()) + } else { + vals[3] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s CommsSMSLogSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s CommsSMSLogSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 4) + + if s.Created.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "created")...), + psql.Arg(s.Created), + }}) + } + + if s.Destination.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "destination")...), + psql.Arg(s.Destination), + }}) + } + + if s.Source.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "source")...), + psql.Arg(s.Source), + }}) + } + + if s.Type.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "type")...), + psql.Arg(s.Type), + }}) + } + + return exprs +} + +// FindCommsSMSLog retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindCommsSMSLog(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsSmsmessagetype, cols ...string) (*CommsSMSLog, error) { + if len(cols) == 0 { + return CommsSMSLogs.Query( + sm.Where(CommsSMSLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), + sm.Where(CommsSMSLogs.Columns.Source.EQ(psql.Arg(SourcePK))), + sm.Where(CommsSMSLogs.Columns.Type.EQ(psql.Arg(TypePK))), + ).One(ctx, exec) + } + + return CommsSMSLogs.Query( + sm.Where(CommsSMSLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), + sm.Where(CommsSMSLogs.Columns.Source.EQ(psql.Arg(SourcePK))), + sm.Where(CommsSMSLogs.Columns.Type.EQ(psql.Arg(TypePK))), + sm.Columns(CommsSMSLogs.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// CommsSMSLogExists checks the presence of a single record by primary key +func CommsSMSLogExists(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsSmsmessagetype) (bool, error) { + return CommsSMSLogs.Query( + sm.Where(CommsSMSLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), + sm.Where(CommsSMSLogs.Columns.Source.EQ(psql.Arg(SourcePK))), + sm.Where(CommsSMSLogs.Columns.Type.EQ(psql.Arg(TypePK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after CommsSMSLog is retrieved from the database +func (o *CommsSMSLog) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsSMSLogs.AfterSelectHooks.RunHooks(ctx, exec, CommsSMSLogSlice{o}) + case bob.QueryTypeInsert: + ctx, err = CommsSMSLogs.AfterInsertHooks.RunHooks(ctx, exec, CommsSMSLogSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = CommsSMSLogs.AfterUpdateHooks.RunHooks(ctx, exec, CommsSMSLogSlice{o}) + case bob.QueryTypeDelete: + ctx, err = CommsSMSLogs.AfterDeleteHooks.RunHooks(ctx, exec, CommsSMSLogSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the CommsSMSLog +func (o *CommsSMSLog) primaryKeyVals() bob.Expression { + return psql.ArgGroup( + o.Destination, + o.Source, + o.Type, + ) +} + +func (o *CommsSMSLog) pkEQ() dialect.Expression { + return psql.Group(psql.Quote("comms.sms_log", "destination"), psql.Quote("comms.sms_log", "source"), psql.Quote("comms.sms_log", "type")).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 CommsSMSLog +func (o *CommsSMSLog) Update(ctx context.Context, exec bob.Executor, s *CommsSMSLogSetter) error { + v, err := CommsSMSLogs.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 CommsSMSLog record with an executor +func (o *CommsSMSLog) Delete(ctx context.Context, exec bob.Executor) error { + _, err := CommsSMSLogs.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the CommsSMSLog using the executor +func (o *CommsSMSLog) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := CommsSMSLogs.Query( + sm.Where(CommsSMSLogs.Columns.Destination.EQ(psql.Arg(o.Destination))), + sm.Where(CommsSMSLogs.Columns.Source.EQ(psql.Arg(o.Source))), + sm.Where(CommsSMSLogs.Columns.Type.EQ(psql.Arg(o.Type))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after CommsSMSLogSlice is retrieved from the database +func (o CommsSMSLogSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsSMSLogs.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = CommsSMSLogs.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = CommsSMSLogs.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = CommsSMSLogs.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o CommsSMSLogSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Group(psql.Quote("comms.sms_log", "destination"), psql.Quote("comms.sms_log", "source"), psql.Quote("comms.sms_log", "type")).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 CommsSMSLogSlice) copyMatchingRows(from ...*CommsSMSLog) { + for i, old := range o { + for _, new := range from { + if new.Destination != old.Destination { + continue + } + if new.Source != old.Source { + continue + } + if new.Type != old.Type { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o CommsSMSLogSlice) 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 CommsSMSLogs.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 *CommsSMSLog: + o.copyMatchingRows(retrieved) + case []*CommsSMSLog: + o.copyMatchingRows(retrieved...) + case CommsSMSLogSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsSMSLog or a slice of CommsSMSLog + // then run the AfterUpdateHooks on the slice + _, err = CommsSMSLogs.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o CommsSMSLogSlice) 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 CommsSMSLogs.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 *CommsSMSLog: + o.copyMatchingRows(retrieved) + case []*CommsSMSLog: + o.copyMatchingRows(retrieved...) + case CommsSMSLogSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsSMSLog or a slice of CommsSMSLog + // then run the AfterDeleteHooks on the slice + _, err = CommsSMSLogs.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o CommsSMSLogSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals CommsSMSLogSetter) error { + if len(o) == 0 { + return nil + } + + _, err := CommsSMSLogs.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o CommsSMSLogSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := CommsSMSLogs.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o CommsSMSLogSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := CommsSMSLogs.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// DestinationPhone starts a query for related objects on comms.phone +func (o *CommsSMSLog) DestinationPhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + return CommsPhones.Query(append(mods, + sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(o.Destination))), + )...) +} + +func (os CommsSMSLogSlice) DestinationPhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + pkDestination := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkDestination = append(pkDestination, o.Destination) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkDestination), "text[]")), + )) + + return CommsPhones.Query(append(mods, + sm.Where(psql.Group(CommsPhones.Columns.E164).OP("IN", PKArgExpr)), + )...) +} + +// SourcePhone starts a query for related objects on comms.phone +func (o *CommsSMSLog) SourcePhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + return CommsPhones.Query(append(mods, + sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(o.Source))), + )...) +} + +func (os CommsSMSLogSlice) SourcePhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + pkSource := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkSource = append(pkSource, o.Source) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkSource), "text[]")), + )) + + return CommsPhones.Query(append(mods, + sm.Where(psql.Group(CommsPhones.Columns.E164).OP("IN", PKArgExpr)), + )...) +} + +func attachCommsSMSLogDestinationPhone0(ctx context.Context, exec bob.Executor, count int, commsSMSLog0 *CommsSMSLog, commsPhone1 *CommsPhone) (*CommsSMSLog, error) { + setter := &CommsSMSLogSetter{ + Destination: omit.From(commsPhone1.E164), + } + + err := commsSMSLog0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachCommsSMSLogDestinationPhone0: %w", err) + } + + return commsSMSLog0, nil +} + +func (commsSMSLog0 *CommsSMSLog) InsertDestinationPhone(ctx context.Context, exec bob.Executor, related *CommsPhoneSetter) error { + var err error + + commsPhone1, err := CommsPhones.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachCommsSMSLogDestinationPhone0(ctx, exec, 1, commsSMSLog0, commsPhone1) + if err != nil { + return err + } + + commsSMSLog0.R.DestinationPhone = commsPhone1 + + commsPhone1.R.DestinationSMSLogs = append(commsPhone1.R.DestinationSMSLogs, commsSMSLog0) + + return nil +} + +func (commsSMSLog0 *CommsSMSLog) AttachDestinationPhone(ctx context.Context, exec bob.Executor, commsPhone1 *CommsPhone) error { + var err error + + _, err = attachCommsSMSLogDestinationPhone0(ctx, exec, 1, commsSMSLog0, commsPhone1) + if err != nil { + return err + } + + commsSMSLog0.R.DestinationPhone = commsPhone1 + + commsPhone1.R.DestinationSMSLogs = append(commsPhone1.R.DestinationSMSLogs, commsSMSLog0) + + return nil +} + +func attachCommsSMSLogSourcePhone0(ctx context.Context, exec bob.Executor, count int, commsSMSLog0 *CommsSMSLog, commsPhone1 *CommsPhone) (*CommsSMSLog, error) { + setter := &CommsSMSLogSetter{ + Source: omit.From(commsPhone1.E164), + } + + err := commsSMSLog0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachCommsSMSLogSourcePhone0: %w", err) + } + + return commsSMSLog0, nil +} + +func (commsSMSLog0 *CommsSMSLog) InsertSourcePhone(ctx context.Context, exec bob.Executor, related *CommsPhoneSetter) error { + var err error + + commsPhone1, err := CommsPhones.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachCommsSMSLogSourcePhone0(ctx, exec, 1, commsSMSLog0, commsPhone1) + if err != nil { + return err + } + + commsSMSLog0.R.SourcePhone = commsPhone1 + + commsPhone1.R.SourceSMSLogs = append(commsPhone1.R.SourceSMSLogs, commsSMSLog0) + + return nil +} + +func (commsSMSLog0 *CommsSMSLog) AttachSourcePhone(ctx context.Context, exec bob.Executor, commsPhone1 *CommsPhone) error { + var err error + + _, err = attachCommsSMSLogSourcePhone0(ctx, exec, 1, commsSMSLog0, commsPhone1) + if err != nil { + return err + } + + commsSMSLog0.R.SourcePhone = commsPhone1 + + commsPhone1.R.SourceSMSLogs = append(commsPhone1.R.SourceSMSLogs, commsSMSLog0) + + return nil +} + +type commsSMSLogWhere[Q psql.Filterable] struct { + Created psql.WhereMod[Q, time.Time] + Destination psql.WhereMod[Q, string] + Source psql.WhereMod[Q, string] + Type psql.WhereMod[Q, enums.CommsSmsmessagetype] +} + +func (commsSMSLogWhere[Q]) AliasedAs(alias string) commsSMSLogWhere[Q] { + return buildCommsSMSLogWhere[Q](buildCommsSMSLogColumns(alias)) +} + +func buildCommsSMSLogWhere[Q psql.Filterable](cols commsSMSLogColumns) commsSMSLogWhere[Q] { + return commsSMSLogWhere[Q]{ + Created: psql.Where[Q, time.Time](cols.Created), + Destination: psql.Where[Q, string](cols.Destination), + Source: psql.Where[Q, string](cols.Source), + Type: psql.Where[Q, enums.CommsSmsmessagetype](cols.Type), + } +} + +func (o *CommsSMSLog) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "DestinationPhone": + rel, ok := retrieved.(*CommsPhone) + if !ok { + return fmt.Errorf("commsSMSLog cannot load %T as %q", retrieved, name) + } + + o.R.DestinationPhone = rel + + if rel != nil { + rel.R.DestinationSMSLogs = CommsSMSLogSlice{o} + } + return nil + case "SourcePhone": + rel, ok := retrieved.(*CommsPhone) + if !ok { + return fmt.Errorf("commsSMSLog cannot load %T as %q", retrieved, name) + } + + o.R.SourcePhone = rel + + if rel != nil { + rel.R.SourceSMSLogs = CommsSMSLogSlice{o} + } + return nil + default: + return fmt.Errorf("commsSMSLog has no relationship %q", name) + } +} + +type commsSMSLogPreloader struct { + DestinationPhone func(...psql.PreloadOption) psql.Preloader + SourcePhone func(...psql.PreloadOption) psql.Preloader +} + +func buildCommsSMSLogPreloader() commsSMSLogPreloader { + return commsSMSLogPreloader{ + DestinationPhone: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*CommsPhone, CommsPhoneSlice](psql.PreloadRel{ + Name: "DestinationPhone", + Sides: []psql.PreloadSide{ + { + From: CommsSMSLogs, + To: CommsPhones, + FromColumns: []string{"destination"}, + ToColumns: []string{"e164"}, + }, + }, + }, CommsPhones.Columns.Names(), opts...) + }, + SourcePhone: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*CommsPhone, CommsPhoneSlice](psql.PreloadRel{ + Name: "SourcePhone", + Sides: []psql.PreloadSide{ + { + From: CommsSMSLogs, + To: CommsPhones, + FromColumns: []string{"source"}, + ToColumns: []string{"e164"}, + }, + }, + }, CommsPhones.Columns.Names(), opts...) + }, + } +} + +type commsSMSLogThenLoader[Q orm.Loadable] struct { + DestinationPhone func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SourcePhone func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildCommsSMSLogThenLoader[Q orm.Loadable]() commsSMSLogThenLoader[Q] { + type DestinationPhoneLoadInterface interface { + LoadDestinationPhone(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type SourcePhoneLoadInterface interface { + LoadSourcePhone(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return commsSMSLogThenLoader[Q]{ + DestinationPhone: thenLoadBuilder[Q]( + "DestinationPhone", + func(ctx context.Context, exec bob.Executor, retrieved DestinationPhoneLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadDestinationPhone(ctx, exec, mods...) + }, + ), + SourcePhone: thenLoadBuilder[Q]( + "SourcePhone", + func(ctx context.Context, exec bob.Executor, retrieved SourcePhoneLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadSourcePhone(ctx, exec, mods...) + }, + ), + } +} + +// LoadDestinationPhone loads the commsSMSLog's DestinationPhone into the .R struct +func (o *CommsSMSLog) LoadDestinationPhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.DestinationPhone = nil + + related, err := o.DestinationPhone(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.DestinationSMSLogs = CommsSMSLogSlice{o} + + o.R.DestinationPhone = related + return nil +} + +// LoadDestinationPhone loads the commsSMSLog's DestinationPhone into the .R struct +func (os CommsSMSLogSlice) LoadDestinationPhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsPhones, err := os.DestinationPhone(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsPhones { + + if !(o.Destination == rel.E164) { + continue + } + + rel.R.DestinationSMSLogs = append(rel.R.DestinationSMSLogs, o) + + o.R.DestinationPhone = rel + break + } + } + + return nil +} + +// LoadSourcePhone loads the commsSMSLog's SourcePhone into the .R struct +func (o *CommsSMSLog) LoadSourcePhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.SourcePhone = nil + + related, err := o.SourcePhone(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.SourceSMSLogs = CommsSMSLogSlice{o} + + o.R.SourcePhone = related + return nil +} + +// LoadSourcePhone loads the commsSMSLog's SourcePhone into the .R struct +func (os CommsSMSLogSlice) LoadSourcePhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsPhones, err := os.SourcePhone(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsPhones { + + if !(o.Source == rel.E164) { + continue + } + + rel.R.SourceSMSLogs = append(rel.R.SourceSMSLogs, o) + + o.R.SourcePhone = rel + break + } + } + + return nil +} + +type commsSMSLogJoins[Q dialect.Joinable] struct { + typ string + DestinationPhone modAs[Q, commsPhoneColumns] + SourcePhone modAs[Q, commsPhoneColumns] +} + +func (j commsSMSLogJoins[Q]) aliasedAs(alias string) commsSMSLogJoins[Q] { + return buildCommsSMSLogJoins[Q](buildCommsSMSLogColumns(alias), j.typ) +} + +func buildCommsSMSLogJoins[Q dialect.Joinable](cols commsSMSLogColumns, typ string) commsSMSLogJoins[Q] { + return commsSMSLogJoins[Q]{ + typ: typ, + DestinationPhone: modAs[Q, commsPhoneColumns]{ + c: CommsPhones.Columns, + f: func(to commsPhoneColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsPhones.Name().As(to.Alias())).On( + to.E164.EQ(cols.Destination), + )) + } + + return mods + }, + }, + SourcePhone: modAs[Q, commsPhoneColumns]{ + c: CommsPhones.Columns, + f: func(to commsPhoneColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsPhones.Name().As(to.Alias())).On( + to.E164.EQ(cols.Source), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/publicreport.image.bob.go b/db/models/publicreport.image.bob.go index 01aae3bd..0dc68ab3 100644 --- a/db/models/publicreport.image.bob.go +++ b/db/models/publicreport.image.bob.go @@ -10,7 +10,9 @@ import ( "strconv" "time" + "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/stephenafamo/bob" "github.com/stephenafamo/bob/dialect/psql" @@ -27,14 +29,15 @@ import ( // PublicreportImage is an object representing the database table. type PublicreportImage struct { - ID int32 `db:"id,pk" ` - ContentType string `db:"content_type" ` - Created time.Time `db:"created" ` - ResolutionX int32 `db:"resolution_x" ` - ResolutionY int32 `db:"resolution_y" ` - StorageUUID uuid.UUID `db:"storage_uuid" ` - StorageSize int64 `db:"storage_size" ` - UploadedFilename string `db:"uploaded_filename" ` + ID int32 `db:"id,pk" ` + ContentType string `db:"content_type" ` + Created time.Time `db:"created" ` + Location null.Val[string] `db:"location" ` + ResolutionX int32 `db:"resolution_x" ` + ResolutionY int32 `db:"resolution_y" ` + StorageUUID uuid.UUID `db:"storage_uuid" ` + StorageSize int64 `db:"storage_size" ` + UploadedFilename string `db:"uploaded_filename" ` R publicreportImageR `db:"-" ` @@ -61,12 +64,13 @@ type publicreportImageR struct { func buildPublicreportImageColumns(alias string) publicreportImageColumns { return publicreportImageColumns{ ColumnsExpr: expr.NewColumnsExpr( - "id", "content_type", "created", "resolution_x", "resolution_y", "storage_uuid", "storage_size", "uploaded_filename", + "id", "content_type", "created", "location", "resolution_x", "resolution_y", "storage_uuid", "storage_size", "uploaded_filename", ).WithParent("publicreport.image"), tableAlias: alias, ID: psql.Quote(alias, "id"), ContentType: psql.Quote(alias, "content_type"), Created: psql.Quote(alias, "created"), + Location: psql.Quote(alias, "location"), ResolutionX: psql.Quote(alias, "resolution_x"), ResolutionY: psql.Quote(alias, "resolution_y"), StorageUUID: psql.Quote(alias, "storage_uuid"), @@ -81,6 +85,7 @@ type publicreportImageColumns struct { ID psql.Expression ContentType psql.Expression Created psql.Expression + Location psql.Expression ResolutionX psql.Expression ResolutionY psql.Expression StorageUUID psql.Expression @@ -100,18 +105,19 @@ func (publicreportImageColumns) AliasedAs(alias string) publicreportImageColumns // All values are optional, and do not have to be set // Generated columns are not included type PublicreportImageSetter struct { - ID omit.Val[int32] `db:"id,pk" ` - ContentType omit.Val[string] `db:"content_type" ` - Created omit.Val[time.Time] `db:"created" ` - ResolutionX omit.Val[int32] `db:"resolution_x" ` - ResolutionY omit.Val[int32] `db:"resolution_y" ` - StorageUUID omit.Val[uuid.UUID] `db:"storage_uuid" ` - StorageSize omit.Val[int64] `db:"storage_size" ` - UploadedFilename omit.Val[string] `db:"uploaded_filename" ` + ID omit.Val[int32] `db:"id,pk" ` + ContentType omit.Val[string] `db:"content_type" ` + Created omit.Val[time.Time] `db:"created" ` + Location omitnull.Val[string] `db:"location" ` + ResolutionX omit.Val[int32] `db:"resolution_x" ` + ResolutionY omit.Val[int32] `db:"resolution_y" ` + StorageUUID omit.Val[uuid.UUID] `db:"storage_uuid" ` + StorageSize omit.Val[int64] `db:"storage_size" ` + UploadedFilename omit.Val[string] `db:"uploaded_filename" ` } func (s PublicreportImageSetter) SetColumns() []string { - vals := make([]string, 0, 8) + vals := make([]string, 0, 9) if s.ID.IsValue() { vals = append(vals, "id") } @@ -121,6 +127,9 @@ func (s PublicreportImageSetter) SetColumns() []string { if s.Created.IsValue() { vals = append(vals, "created") } + if !s.Location.IsUnset() { + vals = append(vals, "location") + } if s.ResolutionX.IsValue() { vals = append(vals, "resolution_x") } @@ -149,6 +158,9 @@ func (s PublicreportImageSetter) Overwrite(t *PublicreportImage) { if s.Created.IsValue() { t.Created = s.Created.MustGet() } + if !s.Location.IsUnset() { + t.Location = s.Location.MustGetNull() + } if s.ResolutionX.IsValue() { t.ResolutionX = s.ResolutionX.MustGet() } @@ -172,7 +184,7 @@ func (s *PublicreportImageSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 8) + vals := make([]bob.Expression, 9) if s.ID.IsValue() { vals[0] = psql.Arg(s.ID.MustGet()) } else { @@ -191,36 +203,42 @@ func (s *PublicreportImageSetter) Apply(q *dialect.InsertQuery) { vals[2] = psql.Raw("DEFAULT") } - if s.ResolutionX.IsValue() { - vals[3] = psql.Arg(s.ResolutionX.MustGet()) + if !s.Location.IsUnset() { + vals[3] = psql.Arg(s.Location.MustGetNull()) } else { vals[3] = psql.Raw("DEFAULT") } - if s.ResolutionY.IsValue() { - vals[4] = psql.Arg(s.ResolutionY.MustGet()) + if s.ResolutionX.IsValue() { + vals[4] = psql.Arg(s.ResolutionX.MustGet()) } else { vals[4] = psql.Raw("DEFAULT") } - if s.StorageUUID.IsValue() { - vals[5] = psql.Arg(s.StorageUUID.MustGet()) + if s.ResolutionY.IsValue() { + vals[5] = psql.Arg(s.ResolutionY.MustGet()) } else { vals[5] = psql.Raw("DEFAULT") } - if s.StorageSize.IsValue() { - vals[6] = psql.Arg(s.StorageSize.MustGet()) + if s.StorageUUID.IsValue() { + vals[6] = psql.Arg(s.StorageUUID.MustGet()) } else { vals[6] = psql.Raw("DEFAULT") } - if s.UploadedFilename.IsValue() { - vals[7] = psql.Arg(s.UploadedFilename.MustGet()) + if s.StorageSize.IsValue() { + vals[7] = psql.Arg(s.StorageSize.MustGet()) } else { vals[7] = psql.Raw("DEFAULT") } + if s.UploadedFilename.IsValue() { + vals[8] = psql.Arg(s.UploadedFilename.MustGet()) + } else { + vals[8] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -230,7 +248,7 @@ func (s PublicreportImageSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s PublicreportImageSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 8) + exprs := make([]bob.Expression, 0, 9) if s.ID.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -253,6 +271,13 @@ func (s PublicreportImageSetter) Expressions(prefix ...string) []bob.Expression }}) } + if !s.Location.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "location")...), + psql.Arg(s.Location), + }}) + } + if s.ResolutionX.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "resolution_x")...), @@ -798,6 +823,7 @@ type publicreportImageWhere[Q psql.Filterable] struct { ID psql.WhereMod[Q, int32] ContentType psql.WhereMod[Q, string] Created psql.WhereMod[Q, time.Time] + Location psql.WhereNullMod[Q, string] ResolutionX psql.WhereMod[Q, int32] ResolutionY psql.WhereMod[Q, int32] StorageUUID psql.WhereMod[Q, uuid.UUID] @@ -814,6 +840,7 @@ func buildPublicreportImageWhere[Q psql.Filterable](cols publicreportImageColumn ID: psql.Where[Q, int32](cols.ID), ContentType: psql.Where[Q, string](cols.ContentType), Created: psql.Where[Q, time.Time](cols.Created), + Location: psql.WhereNull[Q, string](cols.Location), ResolutionX: psql.Where[Q, int32](cols.ResolutionX), ResolutionY: psql.Where[Q, int32](cols.ResolutionY), StorageUUID: psql.Where[Q, uuid.UUID](cols.StorageUUID), diff --git a/htmlpage/static/img/rmo-banner-1440.png b/htmlpage/static/img/rmo-banner-1440.png new file mode 100644 index 0000000000000000000000000000000000000000..03b28559e2c681f3a36c9f2d6de563e4e2960aec GIT binary patch literal 33292 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

o>J@8T<#W9|*L{Be=yrbkS(%q- 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 XH4ofb9E~P^09Bx#u6{1-oD!M<_$;px literal 0 HcmV?d00001 diff --git a/htmlpage/static/img/rmo-logo-224.png b/htmlpage/static/img/rmo-logo-224.png new file mode 100644 index 0000000000000000000000000000000000000000..1b852fc1422918c5144ff3c2683cf9870101a44e GIT binary patch literal 49048 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%0AUGOYPM_oURD9DjunM_<>KzZ--u1)rO7 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 Cf8Uw_ literal 0 HcmV?d00001 diff --git a/main.go b/main.go index 8e3b5dcb..2f5d450d 100644 --- a/main.go +++ b/main.go @@ -65,6 +65,9 @@ func main() { defer cancel() background.NewOAuthTokenChannel = make(chan struct{}, 10) + queue.ChannelJobAudio = make(chan queue.JobAudio, 100) // Buffered channel to prevent blocking + queue.ChannelJobEmail = make(chan queue.JobEmail, 100) // Buffered channel to prevent blocking + queue.ChannelJobSMS = make(chan queue.JobSMS, 100) // Buffered channel to prevent blocking var waitGroup sync.WaitGroup @@ -77,9 +80,20 @@ func main() { waitGroup.Add(1) go func() { defer waitGroup.Done() - queue.StartAudioWorker(ctx) + background.StartWorkerAudio(ctx, queue.ChannelJobAudio) }() + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + background.StartWorkerEmail(ctx, queue.ChannelJobEmail) + }() + + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + background.StartWorkerSMS(ctx, queue.ChannelJobSMS) + }() server := &http.Server{ Addr: config.Bind, Handler: r, diff --git a/public-report/endpoint.go b/public-report/endpoint.go index 206778f9..397d536d 100644 --- a/public-report/endpoint.go +++ b/public-report/endpoint.go @@ -14,14 +14,22 @@ type ContextRegisterNotificationsComplete struct { type ContextRoot struct{} var ( - RegisterNotificationsComplete = buildTemplate("register-notifications-complete", "base") - Root = buildTemplate("root", "base") + PrivacyT = buildTemplate("privacy", "base") + RootT = buildTemplate("root", "base") + TermsT = buildTemplate("terms", "base") ) +func getPrivacy(w http.ResponseWriter, r *http.Request) { + htmlpage.RenderOrError( + w, + PrivacyT, + ContextRoot{}, + ) +} func getRoot(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, - Root, + RootT, ContextRoot{}, ) } @@ -30,6 +38,13 @@ func getRobots(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "User-agent: *\n") fmt.Fprint(w, "Allow: /\n") } +func getTerms(w http.ResponseWriter, r *http.Request) { + htmlpage.RenderOrError( + w, + TermsT, + ContextRoot{}, + ) +} // Respond with an error that is visible to the user func respondError(w http.ResponseWriter, m string, e error, s int) { diff --git a/public-report/page.go b/public-report/page.go index 4a4c5e56..0351e04f 100644 --- a/public-report/page.go +++ b/public-report/page.go @@ -10,9 +10,6 @@ import ( //go:embed template/* var embeddedFiles embed.FS -//go:embed static/* -var EmbeddedStaticFS embed.FS - var components = [...]string{"footer", "photo-upload", "photo-upload-header"} func buildTemplate(files ...string) *htmlpage.BuiltTemplate { diff --git a/public-report/quick.go b/public-report/quick.go index 354751ed..9a741a89 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -6,12 +6,13 @@ import ( "strconv" "time" - "github.com/Gleipnir-Technology/nidus-sync/comms" + "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/queue" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" @@ -26,14 +27,15 @@ type ContextQuickSubmitComplete struct { } var ( - Quick = buildTemplate("quick", "base") - QuickSubmitComplete = buildTemplate("quick-submit-complete", "base") + quickT = buildTemplate("quick", "base") + quickSubmitCompleteT = buildTemplate("quick-submit-complete", "base") + registerNotificationsCompleteT = buildTemplate("register-notifications-complete", "base") ) func getQuick(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, - Quick, + quickT, ContextQuick{}, ) } @@ -41,7 +43,7 @@ func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) { report := r.URL.Query().Get("report") htmlpage.RenderOrError( w, - QuickSubmitComplete, + quickSubmitCompleteT, ContextQuickSubmitComplete{ ReportID: report, }, @@ -51,7 +53,7 @@ func getRegisterNotificationsComplete(w http.ResponseWriter, r *http.Request) { report := r.URL.Query().Get("report") htmlpage.RenderOrError( w, - RegisterNotificationsComplete, + registerNotificationsCompleteT, ContextRegisterNotificationsComplete{ ReportID: report, }, @@ -181,16 +183,18 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { return } if email != "" { - err := comms.SendEmailReportConfirmation(email, report_id) - if err != nil { - log.Error().Err(err).Msg("Failed to send email") - } + queue.EnqueueJobEmail(queue.JobEmail{ + Destination: email, + Source: config.ForwardEmailReportAddress, + Type: enums.CommsEmailmessagetypeReportSubscriptionConfirmation, + }) } if phone != "" { - err := comms.SendSMS(phone, "testing 1 2 3") - if err != nil { - log.Error().Err(err).Msg("Failed to send SMS") - } + queue.EnqueueJobSMS(queue.JobSMS{ + Destination: phone, + Source: config.VoipMSNumber, + Type: enums.CommsSmsmessagetypeReportSubscriptionConfirmation, + }) } if rowcount == 0 { http.Redirect(w, r, fmt.Sprintf("/error?code=no-rows-affected&report=%s", report_id), http.StatusFound) diff --git a/public-report/routes.go b/public-report/routes.go index 9f5487e7..8de97310 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -8,6 +8,7 @@ import ( func Router() chi.Router { r := chi.NewRouter() r.Get("/", getRoot) + r.Get("/privacy", getPrivacy) r.Get("/robots.txt", getRobots) r.Get("/email/report/{report_id}/subscription-confirmation", getEmailReportSubscriptionConfirmation) r.Get("/nuisance", getNuisance) @@ -24,6 +25,7 @@ func Router() chi.Router { r.Get("/search", getSearch) r.Get("/status", getStatus) r.Get("/status/{report_id}", getStatusByID) + r.Get("/terms-of-service", getTerms) htmlpage.AddStaticRoute(r, "/static") return r } diff --git a/public-report/static/vendor/css/bootstrap.min.css b/public-report/static/vendor/css/bootstrap.min.css deleted file mode 100644 index edfbbb03..00000000 --- a/public-report/static/vendor/css/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -@charset "UTF-8";/*! - * Bootstrap v5.0.2 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0))}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-font-sans-serif);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + (.5rem + 2px));padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + (1rem + 2px));padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + (.75rem + 2px))}textarea.form-control-sm{min-height:calc(1.5em + (.5rem + 2px))}textarea.form-control-lg{min-height:calc(1.5em + (1rem + 2px))}.form-control-color{max-width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast:not(.showing):not(.show){opacity:0}.toast.hide{display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1060;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1050;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{color:#0d6efd!important}.text-secondary{color:#6c757d!important}.text-success{color:#198754!important}.text-info{color:#0dcaf0!important}.text-warning{color:#ffc107!important}.text-danger{color:#dc3545!important}.text-light{color:#f8f9fa!important}.text-dark{color:#212529!important}.text-white{color:#fff!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-reset{color:inherit!important}.bg-primary{background-color:#0d6efd!important}.bg-secondary{background-color:#6c757d!important}.bg-success{background-color:#198754!important}.bg-info{background-color:#0dcaf0!important}.bg-warning{background-color:#ffc107!important}.bg-danger{background-color:#dc3545!important}.bg-light{background-color:#f8f9fa!important}.bg-dark{background-color:#212529!important}.bg-body{background-color:#fff!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/public-report/static/vendor/js/bootstrap.bundle.min.js b/public-report/static/vendor/js/bootstrap.bundle.min.js deleted file mode 100644 index 68acb7a3..00000000 --- a/public-report/static/vendor/js/bootstrap.bundle.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v5.0.2 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]}},e=t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t},i=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i="#"+i.split("#")[1]),e=i&&"#"!==i?i.trim():null}return e},n=t=>{const e=i(t);return e&&document.querySelector(e)?e:null},s=t=>{const e=i(t);return e?document.querySelector(e):null},o=t=>{t.dispatchEvent(new Event("transitionend"))},r=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),a=e=>r(e)?e.jquery?e[0]:e:"string"==typeof e&&e.length>0?t.findOne(e):null,l=(t,e,i)=>{Object.keys(i).forEach(n=>{const s=i[n],o=e[n],a=o&&r(o)?"element":null==(l=o)?""+l:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)})},c=t=>!(!r(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),h=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),d=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?d(t.parentNode):null},u=()=>{},f=t=>t.offsetHeight,p=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},m=[],g=()=>"rtl"===document.documentElement.dir,_=t=>{var e;e=()=>{const e=p();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(m.length||document.addEventListener("DOMContentLoaded",()=>{m.forEach(t=>t())}),m.push(e)):e()},b=t=>{"function"==typeof t&&t()},v=(t,e,i=!0)=>{if(!i)return void b(t);const n=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let s=!1;const r=({target:i})=>{i===e&&(s=!0,e.removeEventListener("transitionend",r),b(t))};e.addEventListener("transitionend",r),setTimeout(()=>{s||o(e)},n)},y=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},w=/[^.]*(?=\..*)\.|.*/,E=/\..*/,A=/::\d+$/,T={};let O=1;const C={mouseenter:"mouseover",mouseleave:"mouseout"},k=/^(mouseenter|mouseleave)/i,L=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function x(t,e){return e&&`${e}::${O++}`||t.uidEvent||O++}function D(t){const e=x(t);return t.uidEvent=e,T[e]=T[e]||{},T[e]}function S(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=I(e,i,n),l=D(t),c=l[a]||(l[a]={}),h=S(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=x(r,e.replace(w,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&P.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function j(t,e,i,n,s){const o=S(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function M(t){return t=t.replace(E,""),C[t]||t}const P={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=I(e,i,n),a=r!==e,l=D(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void j(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach(i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach(o=>{if(o.includes(n)){const n=s[o];j(t,e,i,n.originalHandler,n.delegationSelector)}})}(t,l,i,e.slice(1))});const h=l[r]||{};Object.keys(h).forEach(i=>{const n=i.replace(A,"");if(!a||e.includes(n)){const e=h[i];j(t,l,r,e.originalHandler,e.delegationSelector)}})},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=p(),s=M(e),o=e!==s,r=L.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach(t=>{Object.defineProperty(d,t,{get:()=>i[t]})}),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},H=new Map;var R={set(t,e,i){H.has(t)||H.set(t,new Map);const n=H.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>H.has(t)&&H.get(t).get(e)||null,remove(t,e){if(!H.has(t))return;const i=H.get(t);i.delete(e),0===i.size&&H.delete(t)}};class B{constructor(t){(t=a(t))&&(this._element=t,R.set(this._element,this.constructor.DATA_KEY,this))}dispose(){R.remove(this._element,this.constructor.DATA_KEY),P.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach(t=>{this[t]=null})}_queueCallback(t,e,i=!0){v(t,e,i)}static getInstance(t){return R.get(t,this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.0.2"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return"bs."+this.NAME}static get EVENT_KEY(){return"."+this.DATA_KEY}}class W extends B{static get NAME(){return"alert"}close(t){const e=t?this._getRootElement(t):this._element,i=this._triggerCloseEvent(e);null===i||i.defaultPrevented||this._removeElement(e)}_getRootElement(t){return s(t)||t.closest(".alert")}_triggerCloseEvent(t){return P.trigger(t,"close.bs.alert")}_removeElement(t){t.classList.remove("show");const e=t.classList.contains("fade");this._queueCallback(()=>this._destroyElement(t),t,e)}_destroyElement(t){t.remove(),P.trigger(t,"closed.bs.alert")}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);"close"===t&&e[t](this)}))}static handleDismiss(t){return function(e){e&&e.preventDefault(),t.close(this)}}}P.on(document,"click.bs.alert.data-api",'[data-bs-dismiss="alert"]',W.handleDismiss(new W)),_(W);class q extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=q.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function z(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function $(t){return t.replace(/[A-Z]/g,t=>"-"+t.toLowerCase())}P.on(document,"click.bs.button.data-api",'[data-bs-toggle="button"]',t=>{t.preventDefault();const e=t.target.closest('[data-bs-toggle="button"]');q.getOrCreateInstance(e).toggle()}),_(q);const U={setDataAttribute(t,e,i){t.setAttribute("data-bs-"+$(e),i)},removeDataAttribute(t,e){t.removeAttribute("data-bs-"+$(e))},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter(t=>t.startsWith("bs")).forEach(i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=z(t.dataset[i])}),e},getDataAttribute:(t,e)=>z(t.getAttribute("data-bs-"+$(e))),offset(t){const e=t.getBoundingClientRect();return{top:e.top+document.body.scrollTop,left:e.left+document.body.scrollLeft}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},F={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},V={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},K="next",X="prev",Y="left",Q="right",G={ArrowLeft:Q,ArrowRight:Y};class Z extends B{constructor(e,i){super(e),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(i),this._indicatorsElement=t.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return F}static get NAME(){return"carousel"}next(){this._slide(K)}nextWhenVisible(){!document.hidden&&c(this._element)&&this.next()}prev(){this._slide(X)}pause(e){e||(this._isPaused=!0),t.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(o(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(e){this._activeElement=t.findOne(".active.carousel-item",this._element);const i=this._getItemIndex(this._activeElement);if(e>this._items.length-1||e<0)return;if(this._isSliding)return void P.one(this._element,"slid.bs.carousel",()=>this.to(e));if(i===e)return this.pause(),void this.cycle();const n=e>i?K:X;this._slide(n,this._items[e])}_getConfig(t){return t={...F,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},l("carousel",t,V),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?Q:Y)}_addEventListeners(){this._config.keyboard&&P.on(this._element,"keydown.bs.carousel",t=>this._keydown(t)),"hover"===this._config.pause&&(P.on(this._element,"mouseenter.bs.carousel",t=>this.pause(t)),P.on(this._element,"mouseleave.bs.carousel",t=>this.cycle(t))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const e=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType?this._pointerEvent||(this.touchStartX=t.touches[0].clientX):this.touchStartX=t.clientX},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType||(this.touchDeltaX=t.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(t=>this.cycle(t),500+this._config.interval))};t.find(".carousel-item img",this._element).forEach(t=>{P.on(t,"dragstart.bs.carousel",t=>t.preventDefault())}),this._pointerEvent?(P.on(this._element,"pointerdown.bs.carousel",t=>e(t)),P.on(this._element,"pointerup.bs.carousel",t=>n(t)),this._element.classList.add("pointer-event")):(P.on(this._element,"touchstart.bs.carousel",t=>e(t)),P.on(this._element,"touchmove.bs.carousel",t=>i(t)),P.on(this._element,"touchend.bs.carousel",t=>n(t)))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=G[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(e){return this._items=e&&e.parentNode?t.find(".carousel-item",e.parentNode):[],this._items.indexOf(e)}_getItemByOrder(t,e){const i=t===K;return y(this._items,e,i,this._config.wrap)}_triggerSlideEvent(e,i){const n=this._getItemIndex(e),s=this._getItemIndex(t.findOne(".active.carousel-item",this._element));return P.trigger(this._element,"slide.bs.carousel",{relatedTarget:e,direction:i,from:s,to:n})}_setActiveIndicatorElement(e){if(this._indicatorsElement){const i=t.findOne(".active",this._indicatorsElement);i.classList.remove("active"),i.removeAttribute("aria-current");const n=t.find("[data-bs-target]",this._indicatorsElement);for(let t=0;t{P.trigger(this._element,"slid.bs.carousel",{relatedTarget:r,direction:u,from:o,to:a})};if(this._element.classList.contains("slide")){r.classList.add(d),f(r),s.classList.add(h),r.classList.add(h);const t=()=>{r.classList.remove(h,d),r.classList.add("active"),s.classList.remove("active",d,h),this._isSliding=!1,setTimeout(p,0)};this._queueCallback(t,s,!0)}else s.classList.remove("active"),r.classList.add("active"),this._isSliding=!1,p();l&&this.cycle()}_directionToOrder(t){return[Q,Y].includes(t)?g()?t===Y?X:K:t===Y?K:X:t}_orderToDirection(t){return[K,X].includes(t)?g()?t===X?Y:Q:t===X?Q:Y:t}static carouselInterface(t,e){const i=Z.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){Z.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=s(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},n=this.getAttribute("data-bs-slide-to");n&&(i.interval=!1),Z.carouselInterface(e,i),n&&Z.getInstance(e).to(n),t.preventDefault()}}P.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",Z.dataApiClickHandler),P.on(window,"load.bs.carousel.data-api",()=>{const e=t.find('[data-bs-ride="carousel"]');for(let t=0,i=e.length;tt===this._element);null!==o&&r.length&&(this._selector=o,this._triggerArray.push(i))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}static get Default(){return J}static get NAME(){return"collapse"}toggle(){this._element.classList.contains("show")?this.hide():this.show()}show(){if(this._isTransitioning||this._element.classList.contains("show"))return;let e,i;this._parent&&(e=t.find(".show, .collapsing",this._parent).filter(t=>"string"==typeof this._config.parent?t.getAttribute("data-bs-parent")===this._config.parent:t.classList.contains("collapse")),0===e.length&&(e=null));const n=t.findOne(this._selector);if(e){const t=e.find(t=>n!==t);if(i=t?et.getInstance(t):null,i&&i._isTransitioning)return}if(P.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e&&e.forEach(t=>{n!==t&&et.collapseInterface(t,"hide"),i||R.set(t,"bs.collapse",null)});const s=this._getDimension();this._element.classList.remove("collapse"),this._element.classList.add("collapsing"),this._element.style[s]=0,this._triggerArray.length&&this._triggerArray.forEach(t=>{t.classList.remove("collapsed"),t.setAttribute("aria-expanded",!0)}),this.setTransitioning(!0);const o="scroll"+(s[0].toUpperCase()+s.slice(1));this._queueCallback(()=>{this._element.classList.remove("collapsing"),this._element.classList.add("collapse","show"),this._element.style[s]="",this.setTransitioning(!1),P.trigger(this._element,"shown.bs.collapse")},this._element,!0),this._element.style[s]=this._element[o]+"px"}hide(){if(this._isTransitioning||!this._element.classList.contains("show"))return;if(P.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=this._element.getBoundingClientRect()[t]+"px",f(this._element),this._element.classList.add("collapsing"),this._element.classList.remove("collapse","show");const e=this._triggerArray.length;if(e>0)for(let t=0;t{this.setTransitioning(!1),this._element.classList.remove("collapsing"),this._element.classList.add("collapse"),P.trigger(this._element,"hidden.bs.collapse")},this._element,!0)}setTransitioning(t){this._isTransitioning=t}_getConfig(t){return(t={...J,...t}).toggle=Boolean(t.toggle),l("collapse",t,tt),t}_getDimension(){return this._element.classList.contains("width")?"width":"height"}_getParent(){let{parent:e}=this._config;e=a(e);const i=`[data-bs-toggle="collapse"][data-bs-parent="${e}"]`;return t.find(i,e).forEach(t=>{const e=s(t);this._addAriaAndCollapsedClass(e,[t])}),e}_addAriaAndCollapsedClass(t,e){if(!t||!e.length)return;const i=t.classList.contains("show");e.forEach(t=>{i?t.classList.remove("collapsed"):t.classList.add("collapsed"),t.setAttribute("aria-expanded",i)})}static collapseInterface(t,e){let i=et.getInstance(t);const n={...J,...U.getDataAttributes(t),..."object"==typeof e&&e?e:{}};if(!i&&n.toggle&&"string"==typeof e&&/show|hide/.test(e)&&(n.toggle=!1),i||(i=new et(t,n)),"string"==typeof e){if(void 0===i[e])throw new TypeError(`No method named "${e}"`);i[e]()}}static jQueryInterface(t){return this.each((function(){et.collapseInterface(this,t)}))}}P.on(document,"click.bs.collapse.data-api",'[data-bs-toggle="collapse"]',(function(e){("A"===e.target.tagName||e.delegateTarget&&"A"===e.delegateTarget.tagName)&&e.preventDefault();const i=U.getDataAttributes(this),s=n(this);t.find(s).forEach(t=>{const e=et.getInstance(t);let n;e?(null===e._parent&&"string"==typeof i.parent&&(e._config.parent=i.parent,e._parent=e._getParent()),n="toggle"):n=i,et.collapseInterface(t,n)})})),_(et);var it="top",nt="bottom",st="right",ot="left",rt=[it,nt,st,ot],at=rt.reduce((function(t,e){return t.concat([e+"-start",e+"-end"])}),[]),lt=[].concat(rt,["auto"]).reduce((function(t,e){return t.concat([e,e+"-start",e+"-end"])}),[]),ct=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function ht(t){return t?(t.nodeName||"").toLowerCase():null}function dt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function ut(t){return t instanceof dt(t).Element||t instanceof Element}function ft(t){return t instanceof dt(t).HTMLElement||t instanceof HTMLElement}function pt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof dt(t).ShadowRoot||t instanceof ShadowRoot)}var mt={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];ft(s)&&ht(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});ft(n)&&ht(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function gt(t){return t.split("-")[0]}function _t(t){var e=t.getBoundingClientRect();return{width:e.width,height:e.height,top:e.top,right:e.right,bottom:e.bottom,left:e.left,x:e.left,y:e.top}}function bt(t){var e=_t(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function vt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&pt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function yt(t){return dt(t).getComputedStyle(t)}function wt(t){return["table","td","th"].indexOf(ht(t))>=0}function Et(t){return((ut(t)?t.ownerDocument:t.document)||window.document).documentElement}function At(t){return"html"===ht(t)?t:t.assignedSlot||t.parentNode||(pt(t)?t.host:null)||Et(t)}function Tt(t){return ft(t)&&"fixed"!==yt(t).position?t.offsetParent:null}function Ot(t){for(var e=dt(t),i=Tt(t);i&&wt(i)&&"static"===yt(i).position;)i=Tt(i);return i&&("html"===ht(i)||"body"===ht(i)&&"static"===yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&ft(t)&&"fixed"===yt(t).position)return null;for(var i=At(t);ft(i)&&["html","body"].indexOf(ht(i))<0;){var n=yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ct(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var kt=Math.max,Lt=Math.min,xt=Math.round;function Dt(t,e,i){return kt(t,Lt(e,i))}function St(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function It(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}var Nt={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=gt(i.placement),l=Ct(a),c=[ot,st].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return St("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:It(t,rt))}(s.padding,i),d=bt(o),u="y"===l?it:ot,f="y"===l?nt:st,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=Ot(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=Dt(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&vt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]},jt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function Mt(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.offsets,r=t.position,a=t.gpuAcceleration,l=t.adaptive,c=t.roundOffsets,h=!0===c?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:xt(xt(e*n)/n)||0,y:xt(xt(i*n)/n)||0}}(o):"function"==typeof c?c(o):o,d=h.x,u=void 0===d?0:d,f=h.y,p=void 0===f?0:f,m=o.hasOwnProperty("x"),g=o.hasOwnProperty("y"),_=ot,b=it,v=window;if(l){var y=Ot(i),w="clientHeight",E="clientWidth";y===dt(i)&&"static"!==yt(y=Et(i)).position&&(w="scrollHeight",E="scrollWidth"),y=y,s===it&&(b=nt,p-=y[w]-n.height,p*=a?1:-1),s===ot&&(_=st,u-=y[E]-n.width,u*=a?1:-1)}var A,T=Object.assign({position:r},l&&jt);return a?Object.assign({},T,((A={})[b]=g?"0":"",A[_]=m?"0":"",A.transform=(v.devicePixelRatio||1)<2?"translate("+u+"px, "+p+"px)":"translate3d("+u+"px, "+p+"px, 0)",A)):Object.assign({},T,((e={})[b]=g?p+"px":"",e[_]=m?u+"px":"",e.transform="",e))}var Pt={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:gt(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,Mt(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,Mt(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}},Ht={passive:!0},Rt={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=dt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,Ht)})),a&&l.addEventListener("resize",i.update,Ht),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,Ht)})),a&&l.removeEventListener("resize",i.update,Ht)}},data:{}},Bt={left:"right",right:"left",bottom:"top",top:"bottom"};function Wt(t){return t.replace(/left|right|bottom|top/g,(function(t){return Bt[t]}))}var qt={start:"end",end:"start"};function zt(t){return t.replace(/start|end/g,(function(t){return qt[t]}))}function $t(t){var e=dt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ut(t){return _t(Et(t)).left+$t(t).scrollLeft}function Ft(t){var e=yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Vt(t,e){var i;void 0===e&&(e=[]);var n=function t(e){return["html","body","#document"].indexOf(ht(e))>=0?e.ownerDocument.body:ft(e)&&Ft(e)?e:t(At(e))}(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=dt(n),r=s?[o].concat(o.visualViewport||[],Ft(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Vt(At(r)))}function Kt(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Xt(t,e){return"viewport"===e?Kt(function(t){var e=dt(t),i=Et(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+Ut(t),y:a}}(t)):ft(e)?function(t){var e=_t(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Kt(function(t){var e,i=Et(t),n=$t(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=kt(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=kt(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ut(t),l=-n.scrollTop;return"rtl"===yt(s||i).direction&&(a+=kt(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Et(t)))}function Yt(t){return t.split("-")[1]}function Qt(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?gt(s):null,r=s?Yt(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case it:e={x:a,y:i.y-n.height};break;case nt:e={x:a,y:i.y+i.height};break;case st:e={x:i.x+i.width,y:l};break;case ot:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ct(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case"start":e[c]=e[c]-(i[h]/2-n[h]/2);break;case"end":e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function Gt(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?"clippingParents":o,a=i.rootBoundary,l=void 0===a?"viewport":a,c=i.elementContext,h=void 0===c?"popper":c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=St("number"!=typeof p?p:It(p,rt)),g="popper"===h?"reference":"popper",_=t.elements.reference,b=t.rects.popper,v=t.elements[u?g:h],y=function(t,e,i){var n="clippingParents"===e?function(t){var e=Vt(At(t)),i=["absolute","fixed"].indexOf(yt(t).position)>=0&&ft(t)?Ot(t):t;return ut(i)?e.filter((function(t){return ut(t)&&vt(t,i)&&"body"!==ht(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Xt(t,i);return e.top=kt(n.top,e.top),e.right=Lt(n.right,e.right),e.bottom=Lt(n.bottom,e.bottom),e.left=kt(n.left,e.left),e}),Xt(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}(ut(v)?v:v.contextElement||Et(t.elements.popper),r,l),w=_t(_),E=Qt({reference:w,element:b,strategy:"absolute",placement:s}),A=Kt(Object.assign({},b,E)),T="popper"===h?A:w,O={top:y.top-T.top+m.top,bottom:T.bottom-y.bottom+m.bottom,left:y.left-T.left+m.left,right:T.right-y.right+m.right},C=t.modifiersData.offset;if("popper"===h&&C){var k=C[s];Object.keys(O).forEach((function(t){var e=[st,nt].indexOf(t)>=0?1:-1,i=[it,nt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function Zt(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?lt:l,h=Yt(n),d=h?a?at:at.filter((function(t){return Yt(t)===h})):rt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=Gt(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[gt(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}var Jt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=gt(g),b=l||(_!==g&&p?function(t){if("auto"===gt(t))return[];var e=Wt(t);return[zt(t),e,zt(e)]}(g):[Wt(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat("auto"===gt(i)?Zt(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=Gt(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),I=x?L?st:ot:L?nt:it;y[D]>w[D]&&(I=Wt(I));var N=Wt(I),j=[];if(o&&j.push(S[k]<=0),a&&j.push(S[I]<=0,S[N]<=0),j.every((function(t){return t}))){T=C,A=!1;break}E.set(C,j)}if(A)for(var M=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},P=p?3:1;P>0&&"break"!==M(P);P--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function te(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ee(t){return[it,st,nt,ot].some((function(e){return t[e]>=0}))}var ie={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=Gt(e,{elementContext:"reference"}),a=Gt(e,{altBoundary:!0}),l=te(r,n),c=te(a,s,o),h=ee(l),d=ee(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},ne={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=gt(t),s=[ot,it].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[ot,st].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},se={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Qt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},oe={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=Gt(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=gt(e.placement),b=Yt(e.placement),v=!b,y=Ct(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?it:ot,L="y"===y?nt:st,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],I=E[y]-g[L],N=f?-T[x]/2:0,j="start"===b?A[x]:T[x],M="start"===b?-T[x]:-A[x],P=e.elements.arrow,H=f&&P?bt(P):{width:0,height:0},R=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},B=R[k],W=R[L],q=Dt(0,A[x],H[x]),z=v?A[x]/2-N-q-B-O:j-q-B-O,$=v?-A[x]/2+N+q+W+O:M+q+W+O,U=e.elements.arrow&&Ot(e.elements.arrow),F=U?"y"===y?U.clientTop||0:U.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-F,X=E[y]+$-V;if(o){var Y=Dt(f?Lt(S,K):S,D,f?kt(I,X):I);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?it:ot,G="x"===y?nt:st,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=Dt(f?Lt(J,K):J,Z,f?kt(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function re(t,e,i){void 0===i&&(i=!1);var n,s,o=Et(e),r=_t(t),a=ft(e),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(a||!a&&!i)&&(("body"!==ht(e)||Ft(o))&&(l=(n=e)!==dt(n)&&ft(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:$t(n)),ft(e)?((c=_t(e)).x+=e.clientLeft,c.y+=e.clientTop):o&&(c.x=Ut(o))),{x:r.left+l.scrollLeft-c.x,y:r.top+l.scrollTop-c.y,width:r.width,height:r.height}}var ae={placement:"bottom",modifiers:[],strategy:"absolute"};function le(){for(var t=arguments.length,e=new Array(t),i=0;i"applyStyles"===t.name&&!1===t.enabled);this._popper=ue(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}"ontouchstart"in document.documentElement&&!t.closest(".navbar-nav")&&[].concat(...document.body.children).forEach(t=>P.on(t,"mouseover",u)),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.toggle("show"),this._element.classList.toggle("show"),P.trigger(this._element,"shown.bs.dropdown",e)}}hide(){if(h(this._element)||!this._menu.classList.contains("show"))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_addEventListeners(){P.on(this._element,"click.bs.dropdown",t=>{t.preventDefault(),this.toggle()})}_completeHide(t){P.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>P.off(t,"mouseover",u)),this._popper&&this._popper.destroy(),this._menu.classList.remove("show"),this._element.classList.remove("show"),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),P.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},l("dropdown",t,this.constructor.DefaultType),"object"==typeof t.reference&&!r(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError("dropdown".toUpperCase()+': Option "reference" provided type "object" without a required "getBoundingClientRect" method.');return t}_getMenuElement(){return t.next(this._element,".dropdown-menu")[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ve;if(t.classList.contains("dropstart"))return ye;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ge:me:e?be:_e}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:e,target:i}){const n=t.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(c);n.length&&y(n,i,"ArrowDown"===e,!n.includes(i)).focus()}static dropdownInterface(t,e){const i=Ae.getOrCreateInstance(t,e);if("string"==typeof e){if(void 0===i[e])throw new TypeError(`No method named "${e}"`);i[e]()}}static jQueryInterface(t){return this.each((function(){Ae.dropdownInterface(this,t)}))}static clearMenus(e){if(e&&(2===e.button||"keyup"===e.type&&"Tab"!==e.key))return;const i=t.find('[data-bs-toggle="dropdown"]');for(let t=0,n=i.length;tthis.matches('[data-bs-toggle="dropdown"]')?this:t.prev(this,'[data-bs-toggle="dropdown"]')[0];return"Escape"===e.key?(n().focus(),void Ae.clearMenus()):"ArrowUp"===e.key||"ArrowDown"===e.key?(i||n().click(),void Ae.getInstance(n())._selectMenuItem(e)):void(i&&"Space"!==e.key||Ae.clearMenus())}}P.on(document,"keydown.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',Ae.dataApiKeydownHandler),P.on(document,"keydown.bs.dropdown.data-api",".dropdown-menu",Ae.dataApiKeydownHandler),P.on(document,"click.bs.dropdown.data-api",Ae.clearMenus),P.on(document,"keyup.bs.dropdown.data-api",Ae.clearMenus),P.on(document,"click.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',(function(t){t.preventDefault(),Ae.dropdownInterface(this)})),_(Ae);class Te{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,"paddingRight",e=>e+t),this._setElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight",e=>e+t),this._setElementAttributes(".sticky-top","marginRight",e=>e-t)}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=i(Number.parseFloat(s))+"px"})}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight"),this._resetElementAttributes(".sticky-top","marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)})}_applyManipulationCallback(e,i){r(e)?i(e):t.find(e,this._element).forEach(i)}isOverflowing(){return this.getWidth()>0}}const Oe={isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},Ce={isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"};class ke{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&f(this._getElement()),this._getElement().classList.add("show"),this._emulateAnimation(()=>{b(t)})):b(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove("show"),this._emulateAnimation(()=>{this.dispose(),b(t)})):b(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className="modal-backdrop",this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...Oe,..."object"==typeof t?t:{}}).rootElement=a(t.rootElement),l("backdrop",t,Ce),t}_append(){this._isAppended||(this._config.rootElement.appendChild(this._getElement()),P.on(this._getElement(),"mousedown.bs.backdrop",()=>{b(this._config.clickCallback)}),this._isAppended=!0)}dispose(){this._isAppended&&(P.off(this._element,"mousedown.bs.backdrop"),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){v(t,this._getElement(),this._config.isAnimated)}}const Le={backdrop:!0,keyboard:!0,focus:!0},xe={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"};class De extends B{constructor(e,i){super(e),this._config=this._getConfig(i),this._dialog=t.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new Te}static get Default(){return Le}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||P.trigger(this._element,"show.bs.modal",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add("modal-open"),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),P.on(this._element,"click.dismiss.bs.modal",'[data-bs-dismiss="modal"]',t=>this.hide(t)),P.on(this._dialog,"mousedown.dismiss.bs.modal",()=>{P.one(this._element,"mouseup.dismiss.bs.modal",t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)})}),this._showBackdrop(()=>this._showElement(t)))}hide(t){if(t&&["A","AREA"].includes(t.target.tagName)&&t.preventDefault(),!this._isShown||this._isTransitioning)return;if(P.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const e=this._isAnimated();e&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),P.off(document,"focusin.bs.modal"),this._element.classList.remove("show"),P.off(this._element,"click.dismiss.bs.modal"),P.off(this._dialog,"mousedown.dismiss.bs.modal"),this._queueCallback(()=>this._hideModal(),this._element,e)}dispose(){[window,this._dialog].forEach(t=>P.off(t,".bs.modal")),this._backdrop.dispose(),super.dispose(),P.off(document,"focusin.bs.modal")}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new ke({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_getConfig(t){return t={...Le,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},l("modal",t,xe),t}_showElement(e){const i=this._isAnimated(),n=t.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,n&&(n.scrollTop=0),i&&f(this._element),this._element.classList.add("show"),this._config.focus&&this._enforceFocus(),this._queueCallback(()=>{this._config.focus&&this._element.focus(),this._isTransitioning=!1,P.trigger(this._element,"shown.bs.modal",{relatedTarget:e})},this._dialog,i)}_enforceFocus(){P.off(document,"focusin.bs.modal"),P.on(document,"focusin.bs.modal",t=>{document===t.target||this._element===t.target||this._element.contains(t.target)||this._element.focus()})}_setEscapeEvent(){this._isShown?P.on(this._element,"keydown.dismiss.bs.modal",t=>{this._config.keyboard&&"Escape"===t.key?(t.preventDefault(),this.hide()):this._config.keyboard||"Escape"!==t.key||this._triggerBackdropTransition()}):P.off(this._element,"keydown.dismiss.bs.modal")}_setResizeEvent(){this._isShown?P.on(window,"resize.bs.modal",()=>this._adjustDialog()):P.off(window,"resize.bs.modal")}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove("modal-open"),this._resetAdjustments(),this._scrollBar.reset(),P.trigger(this._element,"hidden.bs.modal")})}_showBackdrop(t){P.on(this._element,"click.dismiss.bs.modal",t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())}),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(P.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains("modal-static")||(n||(i.overflowY="hidden"),t.add("modal-static"),this._queueCallback(()=>{t.remove("modal-static"),n||this._queueCallback(()=>{i.overflowY=""},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!g()||i&&!t&&g())&&(this._element.style.paddingLeft=e+"px"),(i&&!t&&!g()||!i&&t&&g())&&(this._element.style.paddingRight=e+"px")}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=De.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}P.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=s(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),P.one(e,"show.bs.modal",t=>{t.defaultPrevented||P.one(e,"hidden.bs.modal",()=>{c(this)&&this.focus()})}),De.getOrCreateInstance(e).toggle(this)})),_(De);const Se={backdrop:!0,keyboard:!0,scroll:!1},Ie={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"};class Ne extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._addEventListeners()}static get NAME(){return"offcanvas"}static get Default(){return Se}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||P.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||((new Te).hide(),this._enforceFocusOnElement(this._element)),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add("show"),this._queueCallback(()=>{P.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})},this._element,!0))}hide(){this._isShown&&(P.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(P.off(document,"focusin.bs.offcanvas"),this._element.blur(),this._isShown=!1,this._element.classList.remove("show"),this._backdrop.hide(),this._queueCallback(()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new Te).reset(),P.trigger(this._element,"hidden.bs.offcanvas")},this._element,!0)))}dispose(){this._backdrop.dispose(),super.dispose(),P.off(document,"focusin.bs.offcanvas")}_getConfig(t){return t={...Se,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},l("offcanvas",t,Ie),t}_initializeBackDrop(){return new ke({isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_enforceFocusOnElement(t){P.off(document,"focusin.bs.offcanvas"),P.on(document,"focusin.bs.offcanvas",e=>{document===e.target||t===e.target||t.contains(e.target)||t.focus()}),t.focus()}_addEventListeners(){P.on(this._element,"click.dismiss.bs.offcanvas",'[data-bs-dismiss="offcanvas"]',()=>this.hide()),P.on(this._element,"keydown.dismiss.bs.offcanvas",t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()})}static jQueryInterface(t){return this.each((function(){const e=Ne.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}P.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(e){const i=s(this);if(["A","AREA"].includes(this.tagName)&&e.preventDefault(),h(this))return;P.one(i,"hidden.bs.offcanvas",()=>{c(this)&&this.focus()});const n=t.findOne(".offcanvas.show");n&&n!==i&&Ne.getInstance(n).hide(),Ne.getOrCreateInstance(i).toggle(this)})),P.on(window,"load.bs.offcanvas.data-api",()=>t.find(".offcanvas.show").forEach(t=>Ne.getOrCreateInstance(t).show())),_(Ne);const je=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Me=/^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i,Pe=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,He=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!je.has(i)||Boolean(Me.test(t.nodeValue)||Pe.test(t.nodeValue));const n=e.filter(t=>t instanceof RegExp);for(let t=0,e=n.length;t{He(t,a)||i.removeAttribute(t.nodeName)})}return n.body.innerHTML}const Be=new RegExp("(^|\\s)bs-tooltip\\S+","g"),We=new Set(["sanitize","allowList","sanitizeFn"]),qe={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},ze={AUTO:"auto",TOP:"top",RIGHT:g()?"left":"right",BOTTOM:"bottom",LEFT:g()?"right":"left"},$e={animation:!0,template:'

',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Ue={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"};class Fe extends B{constructor(t,e){if(void 0===fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return $e}static get NAME(){return"tooltip"}static get Event(){return Ue}static get DefaultType(){return qe}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains("show"))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),P.off(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.tip&&this.tip.remove(),this._popper&&this._popper.destroy(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=P.trigger(this._element,this.constructor.Event.SHOW),i=d(this._element),n=null===i?this._element.ownerDocument.documentElement.contains(this._element):i.contains(this._element);if(t.defaultPrevented||!n)return;const s=this.getTipElement(),o=e(this.constructor.NAME);s.setAttribute("id",o),this._element.setAttribute("aria-describedby",o),this.setContent(),this._config.animation&&s.classList.add("fade");const r="function"==typeof this._config.placement?this._config.placement.call(this,s,this._element):this._config.placement,a=this._getAttachment(r);this._addAttachmentClass(a);const{container:l}=this._config;R.set(s,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(l.appendChild(s),P.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=ue(this._element,s,this._getPopperConfig(a)),s.classList.add("show");const c="function"==typeof this._config.customClass?this._config.customClass():this._config.customClass;c&&s.classList.add(...c.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>{P.on(t,"mouseover",u)});const h=this.tip.classList.contains("fade");this._queueCallback(()=>{const t=this._hoverState;this._hoverState=null,P.trigger(this._element,this.constructor.Event.SHOWN),"out"===t&&this._leave(null,this)},this.tip,h)}hide(){if(!this._popper)return;const t=this.getTipElement();if(P.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove("show"),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>P.off(t,"mouseover",u)),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains("fade");this._queueCallback(()=>{this._isWithActiveTrigger()||("show"!==this._hoverState&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),P.trigger(this._element,this.constructor.Event.HIDDEN),this._popper&&(this._popper.destroy(),this._popper=null))},this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");return t.innerHTML=this._config.template,this.tip=t.children[0],this.tip}setContent(){const e=this.getTipElement();this.setElementContent(t.findOne(".tooltip-inner",e),this.getTitle()),e.classList.remove("fade","show")}setElementContent(t,e){if(null!==t)return r(e)?(e=a(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.appendChild(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Re(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){let t=this._element.getAttribute("data-bs-original-title");return t||(t="function"==typeof this._config.title?this._config.title.call(this._element):this._config.title),t}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){const i=this.constructor.DATA_KEY;return(e=e||R.get(t.delegateTarget,i))||(e=new this.constructor(t.delegateTarget,this._getDelegateConfig()),R.set(t.delegateTarget,i,e)),e}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add("bs-tooltip-"+this.updateAttachment(t))}_getAttachment(t){return ze[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach(t=>{if("click"===t)P.on(this._element,this.constructor.Event.CLICK,this._config.selector,t=>this.toggle(t));else if("manual"!==t){const e="hover"===t?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i="hover"===t?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;P.on(this._element,e,this._config.selector,t=>this._enter(t)),P.on(this._element,i,this._config.selector,t=>this._leave(t))}}),this._hideModalHandler=()=>{this._element&&this.hide()},P.on(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e.getTipElement().classList.contains("show")||"show"===e._hoverState?e._hoverState="show":(clearTimeout(e._timeout),e._hoverState="show",e._config.delay&&e._config.delay.show?e._timeout=setTimeout(()=>{"show"===e._hoverState&&e.show()},e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?"focus":"hover"]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState="out",e._config.delay&&e._config.delay.hide?e._timeout=setTimeout(()=>{"out"===e._hoverState&&e.hide()},e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach(t=>{We.has(t)&&delete e[t]}),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:a(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),l("tooltip",t,this.constructor.DefaultType),t.sanitize&&(t.template=Re(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};if(this._config)for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(Be);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}static jQueryInterface(t){return this.each((function(){const e=Fe.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}_(Fe);const Ve=new RegExp("(^|\\s)bs-popover\\S+","g"),Ke={...Fe.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},Xe={...Fe.DefaultType,content:"(string|element|function)"},Ye={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class Qe extends Fe{static get Default(){return Ke}static get NAME(){return"popover"}static get Event(){return Ye}static get DefaultType(){return Xe}isWithContent(){return this.getTitle()||this._getContent()}getTipElement(){return this.tip||(this.tip=super.getTipElement(),this.getTitle()||t.findOne(".popover-header",this.tip).remove(),this._getContent()||t.findOne(".popover-body",this.tip).remove()),this.tip}setContent(){const e=this.getTipElement();this.setElementContent(t.findOne(".popover-header",e),this.getTitle());let i=this._getContent();"function"==typeof i&&(i=i.call(this._element)),this.setElementContent(t.findOne(".popover-body",e),i),e.classList.remove("fade","show")}_addAttachmentClass(t){this.getTipElement().classList.add("bs-popover-"+this.updateAttachment(t))}_getContent(){return this._element.getAttribute("data-bs-content")||this._config.content}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(Ve);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}static jQueryInterface(t){return this.each((function(){const e=Qe.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}_(Qe);const Ge={offset:10,method:"auto",target:""},Ze={offset:"number",method:"string",target:"(string|element)"};class Je extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._selector=`${this._config.target} .nav-link, ${this._config.target} .list-group-item, ${this._config.target} .dropdown-item`,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,P.on(this._scrollElement,"scroll.bs.scrollspy",()=>this._process()),this.refresh(),this._process()}static get Default(){return Ge}static get NAME(){return"scrollspy"}refresh(){const e=this._scrollElement===this._scrollElement.window?"offset":"position",i="auto"===this._config.method?e:this._config.method,s="position"===i?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.find(this._selector).map(e=>{const o=n(e),r=o?t.findOne(o):null;if(r){const t=r.getBoundingClientRect();if(t.width||t.height)return[U[i](r).top+s,o]}return null}).filter(t=>t).sort((t,e)=>t[0]-e[0]).forEach(t=>{this._offsets.push(t[0]),this._targets.push(t[1])})}dispose(){P.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){if("string"!=typeof(t={...Ge,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target&&r(t.target)){let{id:i}=t.target;i||(i=e("scrollspy"),t.target.id=i),t.target="#"+i}return l("scrollspy",t,Ze),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${t}[data-bs-target="${e}"],${t}[href="${e}"]`),n=t.findOne(i.join(","));n.classList.contains("dropdown-item")?(t.findOne(".dropdown-toggle",n.closest(".dropdown")).classList.add("active"),n.classList.add("active")):(n.classList.add("active"),t.parents(n,".nav, .list-group").forEach(e=>{t.prev(e,".nav-link, .list-group-item").forEach(t=>t.classList.add("active")),t.prev(e,".nav-item").forEach(e=>{t.children(e,".nav-link").forEach(t=>t.classList.add("active"))})})),P.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:e})}_clear(){t.find(this._selector).filter(t=>t.classList.contains("active")).forEach(t=>t.classList.remove("active"))}static jQueryInterface(t){return this.each((function(){const e=Je.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(window,"load.bs.scrollspy.data-api",()=>{t.find('[data-bs-spy="scroll"]').forEach(t=>new Je(t))}),_(Je);class ti extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains("active"))return;let e;const i=s(this._element),n=this._element.closest(".nav, .list-group");if(n){const i="UL"===n.nodeName||"OL"===n.nodeName?":scope > li > .active":".active";e=t.find(i,n),e=e[e.length-1]}const o=e?P.trigger(e,"hide.bs.tab",{relatedTarget:this._element}):null;if(P.trigger(this._element,"show.bs.tab",{relatedTarget:e}).defaultPrevented||null!==o&&o.defaultPrevented)return;this._activate(this._element,n);const r=()=>{P.trigger(e,"hidden.bs.tab",{relatedTarget:this._element}),P.trigger(this._element,"shown.bs.tab",{relatedTarget:e})};i?this._activate(i,i.parentNode,r):r()}_activate(e,i,n){const s=(!i||"UL"!==i.nodeName&&"OL"!==i.nodeName?t.children(i,".active"):t.find(":scope > li > .active",i))[0],o=n&&s&&s.classList.contains("fade"),r=()=>this._transitionComplete(e,s,n);s&&o?(s.classList.remove("show"),this._queueCallback(r,e,!0)):r()}_transitionComplete(e,i,n){if(i){i.classList.remove("active");const e=t.findOne(":scope > .dropdown-menu .active",i.parentNode);e&&e.classList.remove("active"),"tab"===i.getAttribute("role")&&i.setAttribute("aria-selected",!1)}e.classList.add("active"),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!0),f(e),e.classList.contains("fade")&&e.classList.add("show");let s=e.parentNode;if(s&&"LI"===s.nodeName&&(s=s.parentNode),s&&s.classList.contains("dropdown-menu")){const i=e.closest(".dropdown");i&&t.find(".dropdown-toggle",i).forEach(t=>t.classList.add("active")),e.setAttribute("aria-expanded",!0)}n&&n()}static jQueryInterface(t){return this.each((function(){const e=ti.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),h(this)||ti.getOrCreateInstance(this).show()})),_(ti);const ei={animation:"boolean",autohide:"boolean",delay:"number"},ii={animation:!0,autohide:!0,delay:5e3};class ni extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return ei}static get Default(){return ii}static get NAME(){return"toast"}show(){P.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove("hide"),f(this._element),this._element.classList.add("showing"),this._queueCallback(()=>{this._element.classList.remove("showing"),this._element.classList.add("show"),P.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){this._element.classList.contains("show")&&(P.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.remove("show"),this._queueCallback(()=>{this._element.classList.add("hide"),P.trigger(this._element,"hidden.bs.toast")},this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),super.dispose()}_getConfig(t){return t={...ii,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},l("toast",t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){P.on(this._element,"click.dismiss.bs.toast",'[data-bs-dismiss="toast"]',()=>this.hide()),P.on(this._element,"mouseover.bs.toast",t=>this._onInteraction(t,!0)),P.on(this._element,"mouseout.bs.toast",t=>this._onInteraction(t,!1)),P.on(this._element,"focusin.bs.toast",t=>this._onInteraction(t,!0)),P.on(this._element,"focusout.bs.toast",t=>this._onInteraction(t,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ni.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return _(ni),{Alert:W,Button:q,Carousel:Z,Collapse:et,Dropdown:Ae,Modal:De,Offcanvas:Ne,Popover:Qe,ScrollSpy:Je,Tab:ti,Toast:ni,Tooltip:Fe}})); -//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/public-report/static/vendor/js/bootstrap.min.js b/public-report/static/vendor/js/bootstrap.min.js deleted file mode 100644 index aed031fd..00000000 --- a/public-report/static/vendor/js/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v5.0.2 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e(t.Popper)}(this,(function(t){"use strict";function e(t){if(t&&t.__esModule)return t;var e=Object.create(null);return t&&Object.keys(t).forEach((function(s){if("default"!==s){var i=Object.getOwnPropertyDescriptor(t,s);Object.defineProperty(e,s,i.get?i:{enumerable:!0,get:function(){return t[s]}})}})),e.default=t,Object.freeze(e)}var s=e(t);const i={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const s=[];let i=t.parentNode;for(;i&&i.nodeType===Node.ELEMENT_NODE&&3!==i.nodeType;)i.matches(e)&&s.push(i),i=i.parentNode;return s},prev(t,e){let s=t.previousElementSibling;for(;s;){if(s.matches(e))return[s];s=s.previousElementSibling}return[]},next(t,e){let s=t.nextElementSibling;for(;s;){if(s.matches(e))return[s];s=s.nextElementSibling}return[]}},n=t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t},o=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let s=t.getAttribute("href");if(!s||!s.includes("#")&&!s.startsWith("."))return null;s.includes("#")&&!s.startsWith("#")&&(s="#"+s.split("#")[1]),e=s&&"#"!==s?s.trim():null}return e},r=t=>{const e=o(t);return e&&document.querySelector(e)?e:null},a=t=>{const e=o(t);return e?document.querySelector(e):null},l=t=>{t.dispatchEvent(new Event("transitionend"))},c=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),h=t=>c(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?i.findOne(t):null,d=(t,e,s)=>{Object.keys(s).forEach(i=>{const n=s[i],o=e[i],r=o&&c(o)?"element":null==(a=o)?""+a:{}.toString.call(a).match(/\s([a-z]+)/i)[1].toLowerCase();var a;if(!new RegExp(n).test(r))throw new TypeError(`${t.toUpperCase()}: Option "${i}" provided type "${r}" but expected type "${n}".`)})},u=t=>!(!c(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),g=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),p=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?p(t.parentNode):null},f=()=>{},m=t=>t.offsetHeight,_=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},b=[],v=()=>"rtl"===document.documentElement.dir,y=t=>{var e;e=()=>{const e=_();if(e){const s=t.NAME,i=e.fn[s];e.fn[s]=t.jQueryInterface,e.fn[s].Constructor=t,e.fn[s].noConflict=()=>(e.fn[s]=i,t.jQueryInterface)}},"loading"===document.readyState?(b.length||document.addEventListener("DOMContentLoaded",()=>{b.forEach(t=>t())}),b.push(e)):e()},w=t=>{"function"==typeof t&&t()},E=(t,e,s=!0)=>{if(!s)return void w(t);const i=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:s}=window.getComputedStyle(t);const i=Number.parseFloat(e),n=Number.parseFloat(s);return i||n?(e=e.split(",")[0],s=s.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(s))):0})(e)+5;let n=!1;const o=({target:s})=>{s===e&&(n=!0,e.removeEventListener("transitionend",o),w(t))};e.addEventListener("transitionend",o),setTimeout(()=>{n||l(e)},i)},A=(t,e,s,i)=>{let n=t.indexOf(e);if(-1===n)return t[!s&&i?t.length-1:0];const o=t.length;return n+=s?1:-1,i&&(n=(n+o)%o),t[Math.max(0,Math.min(n,o-1))]},T=/[^.]*(?=\..*)\.|.*/,C=/\..*/,k=/::\d+$/,L={};let O=1;const D={mouseenter:"mouseover",mouseleave:"mouseout"},I=/^(mouseenter|mouseleave)/i,N=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function S(t,e){return e&&`${e}::${O++}`||t.uidEvent||O++}function x(t){const e=S(t);return t.uidEvent=e,L[e]=L[e]||{},L[e]}function M(t,e,s=null){const i=Object.keys(t);for(let n=0,o=i.length;nfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};i?i=t(i):s=t(s)}const[o,r,a]=P(e,s,i),l=x(t),c=l[a]||(l[a]={}),h=M(c,r,o?s:null);if(h)return void(h.oneOff=h.oneOff&&n);const d=S(r,e.replace(T,"")),u=o?function(t,e,s){return function i(n){const o=t.querySelectorAll(e);for(let{target:r}=n;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return n.delegateTarget=r,i.oneOff&&B.off(t,n.type,e,s),s.apply(r,[n]);return null}}(t,s,i):function(t,e){return function s(i){return i.delegateTarget=t,s.oneOff&&B.off(t,i.type,e),e.apply(t,[i])}}(t,s);u.delegationSelector=o?s:null,u.originalHandler=r,u.oneOff=n,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function H(t,e,s,i,n){const o=M(e[s],i,n);o&&(t.removeEventListener(s,o,Boolean(n)),delete e[s][o.uidEvent])}function R(t){return t=t.replace(C,""),D[t]||t}const B={on(t,e,s,i){j(t,e,s,i,!1)},one(t,e,s,i){j(t,e,s,i,!0)},off(t,e,s,i){if("string"!=typeof e||!t)return;const[n,o,r]=P(e,s,i),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void H(t,l,r,o,n?s:null)}c&&Object.keys(l).forEach(s=>{!function(t,e,s,i){const n=e[s]||{};Object.keys(n).forEach(o=>{if(o.includes(i)){const i=n[o];H(t,e,s,i.originalHandler,i.delegationSelector)}})}(t,l,s,e.slice(1))});const h=l[r]||{};Object.keys(h).forEach(s=>{const i=s.replace(k,"");if(!a||e.includes(i)){const e=h[s];H(t,l,r,e.originalHandler,e.delegationSelector)}})},trigger(t,e,s){if("string"!=typeof e||!t)return null;const i=_(),n=R(e),o=e!==n,r=N.has(n);let a,l=!0,c=!0,h=!1,d=null;return o&&i&&(a=i.Event(e,s),i(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(n,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==s&&Object.keys(s).forEach(t=>{Object.defineProperty(d,t,{get:()=>s[t]})}),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},$=new Map;var W={set(t,e,s){$.has(t)||$.set(t,new Map);const i=$.get(t);i.has(e)||0===i.size?i.set(e,s):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(i.keys())[0]}.`)},get:(t,e)=>$.has(t)&&$.get(t).get(e)||null,remove(t,e){if(!$.has(t))return;const s=$.get(t);s.delete(e),0===s.size&&$.delete(t)}};class q{constructor(t){(t=h(t))&&(this._element=t,W.set(this._element,this.constructor.DATA_KEY,this))}dispose(){W.remove(this._element,this.constructor.DATA_KEY),B.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach(t=>{this[t]=null})}_queueCallback(t,e,s=!0){E(t,e,s)}static getInstance(t){return W.get(t,this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.0.2"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return"bs."+this.NAME}static get EVENT_KEY(){return"."+this.DATA_KEY}}class z extends q{static get NAME(){return"alert"}close(t){const e=t?this._getRootElement(t):this._element,s=this._triggerCloseEvent(e);null===s||s.defaultPrevented||this._removeElement(e)}_getRootElement(t){return a(t)||t.closest(".alert")}_triggerCloseEvent(t){return B.trigger(t,"close.bs.alert")}_removeElement(t){t.classList.remove("show");const e=t.classList.contains("fade");this._queueCallback(()=>this._destroyElement(t),t,e)}_destroyElement(t){t.remove(),B.trigger(t,"closed.bs.alert")}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"close"===t&&e[t](this)}))}static handleDismiss(t){return function(e){e&&e.preventDefault(),t.close(this)}}}B.on(document,"click.bs.alert.data-api",'[data-bs-dismiss="alert"]',z.handleDismiss(new z)),y(z);class F extends q{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=F.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function U(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function K(t){return t.replace(/[A-Z]/g,t=>"-"+t.toLowerCase())}B.on(document,"click.bs.button.data-api",'[data-bs-toggle="button"]',t=>{t.preventDefault();const e=t.target.closest('[data-bs-toggle="button"]');F.getOrCreateInstance(e).toggle()}),y(F);const V={setDataAttribute(t,e,s){t.setAttribute("data-bs-"+K(e),s)},removeDataAttribute(t,e){t.removeAttribute("data-bs-"+K(e))},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter(t=>t.startsWith("bs")).forEach(s=>{let i=s.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=U(t.dataset[s])}),e},getDataAttribute:(t,e)=>U(t.getAttribute("data-bs-"+K(e))),offset(t){const e=t.getBoundingClientRect();return{top:e.top+document.body.scrollTop,left:e.left+document.body.scrollLeft}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},Q={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},X={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Y="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z};class et extends q{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=i.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return Q}static get NAME(){return"carousel"}next(){this._slide(Y)}nextWhenVisible(){!document.hidden&&u(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),i.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(l(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=i.findOne(".active.carousel-item",this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void B.one(this._element,"slid.bs.carousel",()=>this.to(t));if(e===t)return this.pause(),void this.cycle();const s=t>e?Y:G;this._slide(s,this._items[t])}_getConfig(t){return t={...Q,...V.getDataAttributes(this._element),..."object"==typeof t?t:{}},d("carousel",t,X),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&B.on(this._element,"keydown.bs.carousel",t=>this._keydown(t)),"hover"===this._config.pause&&(B.on(this._element,"mouseenter.bs.carousel",t=>this.pause(t)),B.on(this._element,"mouseleave.bs.carousel",t=>this.cycle(t))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType?this._pointerEvent||(this.touchStartX=t.touches[0].clientX):this.touchStartX=t.clientX},e=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},s=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType||(this.touchDeltaX=t.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(t=>this.cycle(t),500+this._config.interval))};i.find(".carousel-item img",this._element).forEach(t=>{B.on(t,"dragstart.bs.carousel",t=>t.preventDefault())}),this._pointerEvent?(B.on(this._element,"pointerdown.bs.carousel",e=>t(e)),B.on(this._element,"pointerup.bs.carousel",t=>s(t)),this._element.classList.add("pointer-event")):(B.on(this._element,"touchstart.bs.carousel",e=>t(e)),B.on(this._element,"touchmove.bs.carousel",t=>e(t)),B.on(this._element,"touchend.bs.carousel",t=>s(t)))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?i.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const s=t===Y;return A(this._items,e,s,this._config.wrap)}_triggerSlideEvent(t,e){const s=this._getItemIndex(t),n=this._getItemIndex(i.findOne(".active.carousel-item",this._element));return B.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:s})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=i.findOne(".active",this._indicatorsElement);e.classList.remove("active"),e.removeAttribute("aria-current");const s=i.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{B.trigger(this._element,"slid.bs.carousel",{relatedTarget:r,direction:u,from:o,to:a})};if(this._element.classList.contains("slide")){r.classList.add(d),m(r),n.classList.add(h),r.classList.add(h);const t=()=>{r.classList.remove(h,d),r.classList.add("active"),n.classList.remove("active",d,h),this._isSliding=!1,setTimeout(g,0)};this._queueCallback(t,n,!0)}else n.classList.remove("active"),r.classList.add("active"),this._isSliding=!1,g();l&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?v()?t===Z?G:Y:t===Z?Y:G:t}_orderToDirection(t){return[Y,G].includes(t)?v()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const s=et.getOrCreateInstance(t,e);let{_config:i}=s;"object"==typeof e&&(i={...i,...e});const n="string"==typeof e?e:i.slide;if("number"==typeof e)s.to(e);else if("string"==typeof n){if(void 0===s[n])throw new TypeError(`No method named "${n}"`);s[n]()}else i.interval&&i.ride&&(s.pause(),s.cycle())}static jQueryInterface(t){return this.each((function(){et.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=a(this);if(!e||!e.classList.contains("carousel"))return;const s={...V.getDataAttributes(e),...V.getDataAttributes(this)},i=this.getAttribute("data-bs-slide-to");i&&(s.interval=!1),et.carouselInterface(e,s),i&&et.getInstance(e).to(i),t.preventDefault()}}B.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",et.dataApiClickHandler),B.on(window,"load.bs.carousel.data-api",()=>{const t=i.find('[data-bs-ride="carousel"]');for(let e=0,s=t.length;et===this._element);null!==n&&o.length&&(this._selector=n,this._triggerArray.push(e))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}static get Default(){return st}static get NAME(){return"collapse"}toggle(){this._element.classList.contains("show")?this.hide():this.show()}show(){if(this._isTransitioning||this._element.classList.contains("show"))return;let t,e;this._parent&&(t=i.find(".show, .collapsing",this._parent).filter(t=>"string"==typeof this._config.parent?t.getAttribute("data-bs-parent")===this._config.parent:t.classList.contains("collapse")),0===t.length&&(t=null));const s=i.findOne(this._selector);if(t){const i=t.find(t=>s!==t);if(e=i?nt.getInstance(i):null,e&&e._isTransitioning)return}if(B.trigger(this._element,"show.bs.collapse").defaultPrevented)return;t&&t.forEach(t=>{s!==t&&nt.collapseInterface(t,"hide"),e||W.set(t,"bs.collapse",null)});const n=this._getDimension();this._element.classList.remove("collapse"),this._element.classList.add("collapsing"),this._element.style[n]=0,this._triggerArray.length&&this._triggerArray.forEach(t=>{t.classList.remove("collapsed"),t.setAttribute("aria-expanded",!0)}),this.setTransitioning(!0);const o="scroll"+(n[0].toUpperCase()+n.slice(1));this._queueCallback(()=>{this._element.classList.remove("collapsing"),this._element.classList.add("collapse","show"),this._element.style[n]="",this.setTransitioning(!1),B.trigger(this._element,"shown.bs.collapse")},this._element,!0),this._element.style[n]=this._element[o]+"px"}hide(){if(this._isTransitioning||!this._element.classList.contains("show"))return;if(B.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=this._element.getBoundingClientRect()[t]+"px",m(this._element),this._element.classList.add("collapsing"),this._element.classList.remove("collapse","show");const e=this._triggerArray.length;if(e>0)for(let t=0;t{this.setTransitioning(!1),this._element.classList.remove("collapsing"),this._element.classList.add("collapse"),B.trigger(this._element,"hidden.bs.collapse")},this._element,!0)}setTransitioning(t){this._isTransitioning=t}_getConfig(t){return(t={...st,...t}).toggle=Boolean(t.toggle),d("collapse",t,it),t}_getDimension(){return this._element.classList.contains("width")?"width":"height"}_getParent(){let{parent:t}=this._config;t=h(t);const e=`[data-bs-toggle="collapse"][data-bs-parent="${t}"]`;return i.find(e,t).forEach(t=>{const e=a(t);this._addAriaAndCollapsedClass(e,[t])}),t}_addAriaAndCollapsedClass(t,e){if(!t||!e.length)return;const s=t.classList.contains("show");e.forEach(t=>{s?t.classList.remove("collapsed"):t.classList.add("collapsed"),t.setAttribute("aria-expanded",s)})}static collapseInterface(t,e){let s=nt.getInstance(t);const i={...st,...V.getDataAttributes(t),..."object"==typeof e&&e?e:{}};if(!s&&i.toggle&&"string"==typeof e&&/show|hide/.test(e)&&(i.toggle=!1),s||(s=new nt(t,i)),"string"==typeof e){if(void 0===s[e])throw new TypeError(`No method named "${e}"`);s[e]()}}static jQueryInterface(t){return this.each((function(){nt.collapseInterface(this,t)}))}}B.on(document,"click.bs.collapse.data-api",'[data-bs-toggle="collapse"]',(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=V.getDataAttributes(this),s=r(this);i.find(s).forEach(t=>{const s=nt.getInstance(t);let i;s?(null===s._parent&&"string"==typeof e.parent&&(s._config.parent=e.parent,s._parent=s._getParent()),i="toggle"):i=e,nt.collapseInterface(t,i)})})),y(nt);const ot=new RegExp("ArrowUp|ArrowDown|Escape"),rt=v()?"top-end":"top-start",at=v()?"top-start":"top-end",lt=v()?"bottom-end":"bottom-start",ct=v()?"bottom-start":"bottom-end",ht=v()?"left-start":"right-start",dt=v()?"right-start":"left-start",ut={offset:[0,2],boundary:"clippingParents",reference:"toggle",display:"dynamic",popperConfig:null,autoClose:!0},gt={offset:"(array|string|function)",boundary:"(string|element)",reference:"(string|element|object)",display:"string",popperConfig:"(null|object|function)",autoClose:"(boolean|string)"};class pt extends q{constructor(t,e){super(t),this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}static get Default(){return ut}static get DefaultType(){return gt}static get NAME(){return"dropdown"}toggle(){g(this._element)||(this._element.classList.contains("show")?this.hide():this.show())}show(){if(g(this._element)||this._menu.classList.contains("show"))return;const t=pt.getParentFromElement(this._element),e={relatedTarget:this._element};if(!B.trigger(this._element,"show.bs.dropdown",e).defaultPrevented){if(this._inNavbar)V.setDataAttribute(this._menu,"popper","none");else{if(void 0===s)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:c(this._config.reference)?e=h(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find(t=>"applyStyles"===t.name&&!1===t.enabled);this._popper=s.createPopper(e,this._menu,i),n&&V.setDataAttribute(this._menu,"popper","static")}"ontouchstart"in document.documentElement&&!t.closest(".navbar-nav")&&[].concat(...document.body.children).forEach(t=>B.on(t,"mouseover",f)),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.toggle("show"),this._element.classList.toggle("show"),B.trigger(this._element,"shown.bs.dropdown",e)}}hide(){if(g(this._element)||!this._menu.classList.contains("show"))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_addEventListeners(){B.on(this._element,"click.bs.dropdown",t=>{t.preventDefault(),this.toggle()})}_completeHide(t){B.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>B.off(t,"mouseover",f)),this._popper&&this._popper.destroy(),this._menu.classList.remove("show"),this._element.classList.remove("show"),this._element.setAttribute("aria-expanded","false"),V.removeDataAttribute(this._menu,"popper"),B.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...V.getDataAttributes(this._element),...t},d("dropdown",t,this.constructor.DefaultType),"object"==typeof t.reference&&!c(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError("dropdown".toUpperCase()+': Option "reference" provided type "object" without a required "getBoundingClientRect" method.');return t}_getMenuElement(){return i.next(this._element,".dropdown-menu")[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ht;if(t.classList.contains("dropstart"))return dt;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?at:rt:e?ct:lt}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const s=i.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(u);s.length&&A(s,e,"ArrowDown"===t,!s.includes(e)).focus()}static dropdownInterface(t,e){const s=pt.getOrCreateInstance(t,e);if("string"==typeof e){if(void 0===s[e])throw new TypeError(`No method named "${e}"`);s[e]()}}static jQueryInterface(t){return this.each((function(){pt.dropdownInterface(this,t)}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=i.find('[data-bs-toggle="dropdown"]');for(let s=0,i=e.length;sthis.matches('[data-bs-toggle="dropdown"]')?this:i.prev(this,'[data-bs-toggle="dropdown"]')[0];return"Escape"===t.key?(s().focus(),void pt.clearMenus()):"ArrowUp"===t.key||"ArrowDown"===t.key?(e||s().click(),void pt.getInstance(s())._selectMenuItem(t)):void(e&&"Space"!==t.key||pt.clearMenus())}}B.on(document,"keydown.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',pt.dataApiKeydownHandler),B.on(document,"keydown.bs.dropdown.data-api",".dropdown-menu",pt.dataApiKeydownHandler),B.on(document,"click.bs.dropdown.data-api",pt.clearMenus),B.on(document,"keyup.bs.dropdown.data-api",pt.clearMenus),B.on(document,"click.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',(function(t){t.preventDefault(),pt.dropdownInterface(this)})),y(pt);class ft{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,"paddingRight",e=>e+t),this._setElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight",e=>e+t),this._setElementAttributes(".sticky-top","marginRight",e=>e-t)}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,s){const i=this.getWidth();this._applyManipulationCallback(t,t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+i)return;this._saveInitialAttribute(t,e);const n=window.getComputedStyle(t)[e];t.style[e]=s(Number.parseFloat(n))+"px"})}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight"),this._resetElementAttributes(".sticky-top","marginRight")}_saveInitialAttribute(t,e){const s=t.style[e];s&&V.setDataAttribute(t,e,s)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,t=>{const s=V.getDataAttribute(t,e);void 0===s?t.style.removeProperty(e):(V.removeDataAttribute(t,e),t.style[e]=s)})}_applyManipulationCallback(t,e){c(t)?e(t):i.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const mt={isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},_t={isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"};class bt{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&m(this._getElement()),this._getElement().classList.add("show"),this._emulateAnimation(()=>{w(t)})):w(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove("show"),this._emulateAnimation(()=>{this.dispose(),w(t)})):w(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className="modal-backdrop",this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...mt,..."object"==typeof t?t:{}}).rootElement=h(t.rootElement),d("backdrop",t,_t),t}_append(){this._isAppended||(this._config.rootElement.appendChild(this._getElement()),B.on(this._getElement(),"mousedown.bs.backdrop",()=>{w(this._config.clickCallback)}),this._isAppended=!0)}dispose(){this._isAppended&&(B.off(this._element,"mousedown.bs.backdrop"),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){E(t,this._getElement(),this._config.isAnimated)}}const vt={backdrop:!0,keyboard:!0,focus:!0},yt={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"};class wt extends q{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=i.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new ft}static get Default(){return vt}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||B.trigger(this._element,"show.bs.modal",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add("modal-open"),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),B.on(this._element,"click.dismiss.bs.modal",'[data-bs-dismiss="modal"]',t=>this.hide(t)),B.on(this._dialog,"mousedown.dismiss.bs.modal",()=>{B.one(this._element,"mouseup.dismiss.bs.modal",t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)})}),this._showBackdrop(()=>this._showElement(t)))}hide(t){if(t&&["A","AREA"].includes(t.target.tagName)&&t.preventDefault(),!this._isShown||this._isTransitioning)return;if(B.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const e=this._isAnimated();e&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),B.off(document,"focusin.bs.modal"),this._element.classList.remove("show"),B.off(this._element,"click.dismiss.bs.modal"),B.off(this._dialog,"mousedown.dismiss.bs.modal"),this._queueCallback(()=>this._hideModal(),this._element,e)}dispose(){[window,this._dialog].forEach(t=>B.off(t,".bs.modal")),this._backdrop.dispose(),super.dispose(),B.off(document,"focusin.bs.modal")}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bt({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_getConfig(t){return t={...vt,...V.getDataAttributes(this._element),..."object"==typeof t?t:{}},d("modal",t,yt),t}_showElement(t){const e=this._isAnimated(),s=i.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,s&&(s.scrollTop=0),e&&m(this._element),this._element.classList.add("show"),this._config.focus&&this._enforceFocus(),this._queueCallback(()=>{this._config.focus&&this._element.focus(),this._isTransitioning=!1,B.trigger(this._element,"shown.bs.modal",{relatedTarget:t})},this._dialog,e)}_enforceFocus(){B.off(document,"focusin.bs.modal"),B.on(document,"focusin.bs.modal",t=>{document===t.target||this._element===t.target||this._element.contains(t.target)||this._element.focus()})}_setEscapeEvent(){this._isShown?B.on(this._element,"keydown.dismiss.bs.modal",t=>{this._config.keyboard&&"Escape"===t.key?(t.preventDefault(),this.hide()):this._config.keyboard||"Escape"!==t.key||this._triggerBackdropTransition()}):B.off(this._element,"keydown.dismiss.bs.modal")}_setResizeEvent(){this._isShown?B.on(window,"resize.bs.modal",()=>this._adjustDialog()):B.off(window,"resize.bs.modal")}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove("modal-open"),this._resetAdjustments(),this._scrollBar.reset(),B.trigger(this._element,"hidden.bs.modal")})}_showBackdrop(t){B.on(this._element,"click.dismiss.bs.modal",t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())}),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(B.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:s}=this._element,i=e>document.documentElement.clientHeight;!i&&"hidden"===s.overflowY||t.contains("modal-static")||(i||(s.overflowY="hidden"),t.add("modal-static"),this._queueCallback(()=>{t.remove("modal-static"),i||this._queueCallback(()=>{s.overflowY=""},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),s=e>0;(!s&&t&&!v()||s&&!t&&v())&&(this._element.style.paddingLeft=e+"px"),(s&&!t&&!v()||!s&&t&&v())&&(this._element.style.paddingRight=e+"px")}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const s=wt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===s[t])throw new TypeError(`No method named "${t}"`);s[t](e)}}))}}B.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=a(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),B.one(e,"show.bs.modal",t=>{t.defaultPrevented||B.one(e,"hidden.bs.modal",()=>{u(this)&&this.focus()})}),wt.getOrCreateInstance(e).toggle(this)})),y(wt);const Et={backdrop:!0,keyboard:!0,scroll:!1},At={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"};class Tt extends q{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._addEventListeners()}static get NAME(){return"offcanvas"}static get Default(){return Et}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||B.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||((new ft).hide(),this._enforceFocusOnElement(this._element)),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add("show"),this._queueCallback(()=>{B.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})},this._element,!0))}hide(){this._isShown&&(B.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(B.off(document,"focusin.bs.offcanvas"),this._element.blur(),this._isShown=!1,this._element.classList.remove("show"),this._backdrop.hide(),this._queueCallback(()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new ft).reset(),B.trigger(this._element,"hidden.bs.offcanvas")},this._element,!0)))}dispose(){this._backdrop.dispose(),super.dispose(),B.off(document,"focusin.bs.offcanvas")}_getConfig(t){return t={...Et,...V.getDataAttributes(this._element),..."object"==typeof t?t:{}},d("offcanvas",t,At),t}_initializeBackDrop(){return new bt({isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_enforceFocusOnElement(t){B.off(document,"focusin.bs.offcanvas"),B.on(document,"focusin.bs.offcanvas",e=>{document===e.target||t===e.target||t.contains(e.target)||t.focus()}),t.focus()}_addEventListeners(){B.on(this._element,"click.dismiss.bs.offcanvas",'[data-bs-dismiss="offcanvas"]',()=>this.hide()),B.on(this._element,"keydown.dismiss.bs.offcanvas",t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()})}static jQueryInterface(t){return this.each((function(){const e=Tt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}B.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=a(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),g(this))return;B.one(e,"hidden.bs.offcanvas",()=>{u(this)&&this.focus()});const s=i.findOne(".offcanvas.show");s&&s!==e&&Tt.getInstance(s).hide(),Tt.getOrCreateInstance(e).toggle(this)})),B.on(window,"load.bs.offcanvas.data-api",()=>i.find(".offcanvas.show").forEach(t=>Tt.getOrCreateInstance(t).show())),y(Tt);const Ct=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),kt=/^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i,Lt=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Ot=(t,e)=>{const s=t.nodeName.toLowerCase();if(e.includes(s))return!Ct.has(s)||Boolean(kt.test(t.nodeValue)||Lt.test(t.nodeValue));const i=e.filter(t=>t instanceof RegExp);for(let t=0,e=i.length;t{Ot(t,a)||s.removeAttribute(t.nodeName)})}return i.body.innerHTML}const It=new RegExp("(^|\\s)bs-tooltip\\S+","g"),Nt=new Set(["sanitize","allowList","sanitizeFn"]),St={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},xt={AUTO:"auto",TOP:"top",RIGHT:v()?"left":"right",BOTTOM:"bottom",LEFT:v()?"right":"left"},Mt={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Pt={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"};class jt extends q{constructor(t,e){if(void 0===s)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return Mt}static get NAME(){return"tooltip"}static get Event(){return Pt}static get DefaultType(){return St}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains("show"))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),B.off(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.tip&&this.tip.remove(),this._popper&&this._popper.destroy(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=B.trigger(this._element,this.constructor.Event.SHOW),e=p(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;const o=this.getTipElement(),r=n(this.constructor.NAME);o.setAttribute("id",r),this._element.setAttribute("aria-describedby",r),this.setContent(),this._config.animation&&o.classList.add("fade");const a="function"==typeof this._config.placement?this._config.placement.call(this,o,this._element):this._config.placement,l=this._getAttachment(a);this._addAttachmentClass(l);const{container:c}=this._config;W.set(o,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(c.appendChild(o),B.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=s.createPopper(this._element,o,this._getPopperConfig(l)),o.classList.add("show");const h="function"==typeof this._config.customClass?this._config.customClass():this._config.customClass;h&&o.classList.add(...h.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>{B.on(t,"mouseover",f)});const d=this.tip.classList.contains("fade");this._queueCallback(()=>{const t=this._hoverState;this._hoverState=null,B.trigger(this._element,this.constructor.Event.SHOWN),"out"===t&&this._leave(null,this)},this.tip,d)}hide(){if(!this._popper)return;const t=this.getTipElement();if(B.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove("show"),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>B.off(t,"mouseover",f)),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains("fade");this._queueCallback(()=>{this._isWithActiveTrigger()||("show"!==this._hoverState&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),B.trigger(this._element,this.constructor.Event.HIDDEN),this._popper&&(this._popper.destroy(),this._popper=null))},this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");return t.innerHTML=this._config.template,this.tip=t.children[0],this.tip}setContent(){const t=this.getTipElement();this.setElementContent(i.findOne(".tooltip-inner",t),this.getTitle()),t.classList.remove("fade","show")}setElementContent(t,e){if(null!==t)return c(e)?(e=h(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.appendChild(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Dt(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){let t=this._element.getAttribute("data-bs-original-title");return t||(t="function"==typeof this._config.title?this._config.title.call(this._element):this._config.title),t}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){const s=this.constructor.DATA_KEY;return(e=e||W.get(t.delegateTarget,s))||(e=new this.constructor(t.delegateTarget,this._getDelegateConfig()),W.set(t.delegateTarget,s,e)),e}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add("bs-tooltip-"+this.updateAttachment(t))}_getAttachment(t){return xt[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach(t=>{if("click"===t)B.on(this._element,this.constructor.Event.CLICK,this._config.selector,t=>this.toggle(t));else if("manual"!==t){const e="hover"===t?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,s="hover"===t?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;B.on(this._element,e,this._config.selector,t=>this._enter(t)),B.on(this._element,s,this._config.selector,t=>this._leave(t))}}),this._hideModalHandler=()=>{this._element&&this.hide()},B.on(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e.getTipElement().classList.contains("show")||"show"===e._hoverState?e._hoverState="show":(clearTimeout(e._timeout),e._hoverState="show",e._config.delay&&e._config.delay.show?e._timeout=setTimeout(()=>{"show"===e._hoverState&&e.show()},e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?"focus":"hover"]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState="out",e._config.delay&&e._config.delay.hide?e._timeout=setTimeout(()=>{"out"===e._hoverState&&e.hide()},e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=V.getDataAttributes(this._element);return Object.keys(e).forEach(t=>{Nt.has(t)&&delete e[t]}),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:h(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),d("tooltip",t,this.constructor.DefaultType),t.sanitize&&(t.template=Dt(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};if(this._config)for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(It);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}static jQueryInterface(t){return this.each((function(){const e=jt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}y(jt);const Ht=new RegExp("(^|\\s)bs-popover\\S+","g"),Rt={...jt.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},Bt={...jt.DefaultType,content:"(string|element|function)"},$t={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class Wt extends jt{static get Default(){return Rt}static get NAME(){return"popover"}static get Event(){return $t}static get DefaultType(){return Bt}isWithContent(){return this.getTitle()||this._getContent()}getTipElement(){return this.tip||(this.tip=super.getTipElement(),this.getTitle()||i.findOne(".popover-header",this.tip).remove(),this._getContent()||i.findOne(".popover-body",this.tip).remove()),this.tip}setContent(){const t=this.getTipElement();this.setElementContent(i.findOne(".popover-header",t),this.getTitle());let e=this._getContent();"function"==typeof e&&(e=e.call(this._element)),this.setElementContent(i.findOne(".popover-body",t),e),t.classList.remove("fade","show")}_addAttachmentClass(t){this.getTipElement().classList.add("bs-popover-"+this.updateAttachment(t))}_getContent(){return this._element.getAttribute("data-bs-content")||this._config.content}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(Ht);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}static jQueryInterface(t){return this.each((function(){const e=Wt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}y(Wt);const qt={offset:10,method:"auto",target:""},zt={offset:"number",method:"string",target:"(string|element)"};class Ft extends q{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._selector=`${this._config.target} .nav-link, ${this._config.target} .list-group-item, ${this._config.target} .dropdown-item`,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,B.on(this._scrollElement,"scroll.bs.scrollspy",()=>this._process()),this.refresh(),this._process()}static get Default(){return qt}static get NAME(){return"scrollspy"}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":"position",e="auto"===this._config.method?t:this._config.method,s="position"===e?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),i.find(this._selector).map(t=>{const n=r(t),o=n?i.findOne(n):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[V[e](o).top+s,n]}return null}).filter(t=>t).sort((t,e)=>t[0]-e[0]).forEach(t=>{this._offsets.push(t[0]),this._targets.push(t[1])})}dispose(){B.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){if("string"!=typeof(t={...qt,...V.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target&&c(t.target)){let{id:e}=t.target;e||(e=n("scrollspy"),t.target.id=e),t.target="#"+e}return d("scrollspy",t,zt),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),s=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=s){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`),s=i.findOne(e.join(","));s.classList.contains("dropdown-item")?(i.findOne(".dropdown-toggle",s.closest(".dropdown")).classList.add("active"),s.classList.add("active")):(s.classList.add("active"),i.parents(s,".nav, .list-group").forEach(t=>{i.prev(t,".nav-link, .list-group-item").forEach(t=>t.classList.add("active")),i.prev(t,".nav-item").forEach(t=>{i.children(t,".nav-link").forEach(t=>t.classList.add("active"))})})),B.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){i.find(this._selector).filter(t=>t.classList.contains("active")).forEach(t=>t.classList.remove("active"))}static jQueryInterface(t){return this.each((function(){const e=Ft.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}B.on(window,"load.bs.scrollspy.data-api",()=>{i.find('[data-bs-spy="scroll"]').forEach(t=>new Ft(t))}),y(Ft);class Ut extends q{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains("active"))return;let t;const e=a(this._element),s=this._element.closest(".nav, .list-group");if(s){const e="UL"===s.nodeName||"OL"===s.nodeName?":scope > li > .active":".active";t=i.find(e,s),t=t[t.length-1]}const n=t?B.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(B.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==n&&n.defaultPrevented)return;this._activate(this._element,s);const o=()=>{B.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),B.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,s){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?i.children(e,".active"):i.find(":scope > li > .active",e))[0],o=s&&n&&n.classList.contains("fade"),r=()=>this._transitionComplete(t,n,s);n&&o?(n.classList.remove("show"),this._queueCallback(r,t,!0)):r()}_transitionComplete(t,e,s){if(e){e.classList.remove("active");const t=i.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove("active"),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add("active"),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),m(t),t.classList.contains("fade")&&t.classList.add("show");let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&i.find(".dropdown-toggle",e).forEach(t=>t.classList.add("active")),t.setAttribute("aria-expanded",!0)}s&&s()}static jQueryInterface(t){return this.each((function(){const e=Ut.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}B.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),g(this)||Ut.getOrCreateInstance(this).show()})),y(Ut);const Kt={animation:"boolean",autohide:"boolean",delay:"number"},Vt={animation:!0,autohide:!0,delay:5e3};class Qt extends q{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Kt}static get Default(){return Vt}static get NAME(){return"toast"}show(){B.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove("hide"),m(this._element),this._element.classList.add("showing"),this._queueCallback(()=>{this._element.classList.remove("showing"),this._element.classList.add("show"),B.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){this._element.classList.contains("show")&&(B.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.remove("show"),this._queueCallback(()=>{this._element.classList.add("hide"),B.trigger(this._element,"hidden.bs.toast")},this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),super.dispose()}_getConfig(t){return t={...Vt,...V.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},d("toast",t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const s=t.relatedTarget;this._element===s||this._element.contains(s)||this._maybeScheduleHide()}_setListeners(){B.on(this._element,"click.dismiss.bs.toast",'[data-bs-dismiss="toast"]',()=>this.hide()),B.on(this._element,"mouseover.bs.toast",t=>this._onInteraction(t,!0)),B.on(this._element,"mouseout.bs.toast",t=>this._onInteraction(t,!1)),B.on(this._element,"focusin.bs.toast",t=>this._onInteraction(t,!0)),B.on(this._element,"focusout.bs.toast",t=>this._onInteraction(t,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Qt.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return y(Qt),{Alert:z,Button:F,Carousel:et,Collapse:nt,Dropdown:pt,Modal:wt,Offcanvas:Tt,Popover:Wt,ScrollSpy:Ft,Tab:Ut,Toast:Qt,Tooltip:jt}})); -//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/public-report/template/privacy.html b/public-report/template/privacy.html new file mode 100644 index 00000000..2b40f0e3 --- /dev/null +++ b/public-report/template/privacy.html @@ -0,0 +1,212 @@ +{{template "base.html" .}} +{{define "title"}}Privacy Policy{{end}} +{{define "extraheader"}} +{{end}} +{{define "content"}} +

Privacy Policy

+

Last updated: January 20, 2026

+

This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.

+

We use Your Personal Data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy.

+

Interpretation and Definitions

+

Interpretation

+

The words whose initial letters are capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural.

+

Definitions

+

For the purposes of this Privacy Policy:

+
    +
  • +

    Account means a unique account created for You to access our Service or parts of our Service.

    +
  • +
  • +

    Affiliate means an entity that controls, is controlled by, or is under common control with a party, where "control" means ownership of 50% or more of the shares, equity interest or other securities entitled to vote for election of directors or other managing authority.

    +
  • +
  • +

    Company (referred to as either "the Company", "We", "Us" or "Our" in this Privacy Policy) refers to Gleipnir Technology LLC, 2726 S Quinn Ave, Gilbert, AZ.

    +
  • +
  • +

    Cookies are small files that are placed on Your computer, mobile device or any other device by a website, containing the details of Your browsing history on that website among its many uses.

    +
  • +
  • +

    Country refers to: Arizona, United States

    +
  • +
  • +

    Device means any device that can access the Service such as a computer, a cell phone or a digital tablet.

    +
  • +
  • +

    Personal Data (or "Personal Information") is any information that relates to an identified or identifiable individual.

    +

    We use "Personal Data" and "Personal Information" interchangeably unless a law uses a specific term.

    +
  • +
  • +

    Service refers to the Website.

    +
  • +
  • +

    Service Provider means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used.

    +
  • +
  • +

    Usage Data refers to data collected automatically, either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit).

    +
  • +
  • +

    Website refers to Report Mosquitoes Online, accessible from https://report.mosquitoes.online.

    +
  • +
  • +

    You means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable.

    +
  • +
+

Collecting and Using Your Personal Data

+

Types of Data Collected

+

Personal Data

+

While using Our Service, We may ask You to provide Us with certain personally identifiable information that can be used to contact or identify You. Personally identifiable information may include, but is not limited to:

+
    +
  • Email address
  • +
  • First name and last name
  • +
  • Phone number
  • +
  • Address, State, Province, ZIP/Postal code, City
  • +
+

Usage Data

+

Usage Data is collected automatically when using the Service.

+

Usage Data may include information such as Your Device's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent on those pages, unique device identifiers and other diagnostic data.

+

When You access the Service by or through a mobile device, We may collect certain information automatically, including, but not limited to, the type of mobile device You use, Your mobile device's unique ID, the IP address of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device identifiers and other diagnostic data.

+

We may also collect information that Your browser sends whenever You visit Our Service or when You access the Service by or through a mobile device.

+

Tracking Technologies and Cookies

+

We use Cookies and similar tracking technologies to track the activity on Our Service and store certain information. Tracking technologies We use include beacons, tags, and scripts to collect and track information and to improve and analyze Our Service. The technologies We use may include:

+
    +
  • Cookies or Browser Cookies. A cookie is a small file placed on Your Device. You can instruct Your browser to refuse all Cookies or to indicate when a Cookie is being sent. However, if You do not accept Cookies, You may not be able to use some parts of our Service.
  • +
+

Cookies can be "Persistent" or "Session" Cookies. Persistent Cookies remain on Your personal computer or mobile device when You go offline, while Session Cookies are deleted as soon as You close Your web browser.

+

Where required by law, we use non-essential cookies (such as analytics, advertising, and remarketing cookies) only with Your consent. You can withdraw or change Your consent at any time using Our cookie preferences tool (if available) or through Your browser/device settings. Withdrawing consent does not affect the lawfulness of processing based on consent before its withdrawal.

+

We use both Session and Persistent Cookies for the purposes set out below:

+
    +
  • +

    Necessary / Essential Cookies

    +

    Type: Session Cookies

    +

    Administered by: Us

    +

    Purpose: These Cookies are essential to provide You with services available through the Website and to enable You to use some of its features. They help to authenticate users and prevent fraudulent use of user accounts. Without these Cookies, the services that You have asked for cannot be provided, and We only use these Cookies to provide You with those services.

    +
  • +
  • +

    Functionality Cookies

    +

    Type: Persistent Cookies

    +

    Administered by: Us

    +

    Purpose: These Cookies allow Us to remember choices You make when You use the Website, such as remembering your login details or language preference. The purpose of these Cookies is to provide You with a more personal experience and to avoid You having to re-enter your preferences every time You use the Website.

    +
  • +
+

For more information about the cookies we use and your choices regarding cookies, please visit our Cookies Policy or the Cookies section of Our Privacy Policy.

+

Use of Your Personal Data

+

The Company may use Personal Data for the following purposes:

+
    +
  • +

    To provide and maintain our Service, including to monitor the usage of our Service.

    +
  • +
  • +

    To manage Your Account: to manage Your registration as a user of the Service. The Personal Data You provide can give You access to different functionalities of the Service that are available to You as a registered user.

    +
  • +
  • +

    For the performance of a contract: the development, compliance and undertaking of the purchase contract for the products, items or services You have purchased or of any other contract with Us through the Service.

    +
  • +
  • +

    To contact You: To contact You by email, telephone calls, SMS, or other equivalent forms of electronic communication, such as a mobile application's push notifications regarding updates or informative communications related to the functionalities, products or contracted services, including the security updates, when necessary or reasonable for their implementation.

    +
  • +
  • +

    To provide You with news, special offers, and general information about other goods, services and events which We offer that are similar to those that you have already purchased or inquired about unless You have opted not to receive such information.

    +
  • +
  • +

    To manage Your requests: To attend and manage Your requests to Us.

    +
  • +
  • +

    For business transfers: We may use Your Personal Data to evaluate or conduct a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Our assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which Personal Data held by Us about our Service users is among the assets transferred.

    +
  • +
  • +

    For other purposes: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, products, services, marketing and your experience.

    +
  • +
+

We may share Your Personal Data in the following situations:

+
    +
  • With Service Providers: We may share Your Personal Data with Service Providers to monitor and analyze the use of our Service, to contact You.
  • +
  • For business transfers: We may share or transfer Your Personal Data in connection with, or during negotiations of, any merger, sale of Company assets, financing, or acquisition of all or a portion of Our business to another company.
  • +
  • With Affiliates: We may share Your Personal Data with Our affiliates, in which case we will require those affiliates to honor this Privacy Policy. Affiliates include Our parent company and any other subsidiaries, joint venture partners or other companies that We control or that are under common control with Us.
  • +
  • With business partners: We may share Your Personal Data with Our business partners to offer You certain products, services or promotions.
  • +
  • With other users: If Our Service offers public areas, when You share Personal Data or otherwise interact in the public areas with other users, such information may be viewed by all users and may be publicly distributed outside.
  • +
  • With Your consent: We may disclose Your Personal Data for any other purpose with Your consent.
  • +
+

Retention of Your Personal Data

+

The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal obligations (for example, if We are required to retain Your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.

+

Where possible, We apply shorter retention periods and/or reduce identifiability by deleting, aggregating, or anonymizing data. Unless otherwise stated, the retention periods below are maximum periods ("up to") and We may delete or anonymize data sooner when it is no longer needed for the relevant purpose. We apply different retention periods to different categories of Personal Data based on the purpose of processing and legal obligations:

+
    +
  • +

    Account Information

    +
      +
    • User Accounts: retained for the duration of your account relationship plus up to 24 months after account closure to handle any post-termination issues or resolve disputes.
    • +
    +
  • +
  • +

    Customer Support Data

    +
      +
    • Support tickets and correspondence: up to 24 months from the date of ticket closure to resolve follow-up inquiries, track service quality, and defend against potential legal claims
    • +
    • Chat transcripts: up to 24 months for quality assurance and staff training purposes.
    • +
    +
  • +
  • +

    Usage Data

    +
      +
    • +

      Website analytics data (cookies, IP addresses, device identifiers): up to 24 months from the date of collection, which allows us to analyze trends while respecting privacy principles.

      +
    • +
    • +

      Server logs (IP addresses, access times): up to 24 months for security monitoring and troubleshooting purposes.

      +
    • +
    +
  • +
+

Usage Data is retained in accordance with the retention periods described above, and may be retained longer only where necessary for security, fraud prevention, or legal compliance.

+

We may retain Personal Data beyond the periods stated above for different reasons:

+
    +
  • Legal obligation: We are required by law to retain specific data (e.g., financial records for tax authorities).
  • +
  • Legal claims: Data is necessary to establish, exercise, or defend legal claims.
  • +
  • Your explicit request: You ask Us to retain specific information.
  • +
  • Technical limitations: Data exists in backup systems that are scheduled for routine deletion.
  • +
+

You may request information about how long We will retain Your Personal Data by contacting Us.

+

When retention periods expire, We securely delete or anonymize Personal Data according to the following procedures:

+
    +
  • Deletion: Personal Data is removed from Our systems and no longer actively processed.
  • +
  • Backup retention: Residual copies may remain in encrypted backups for a limited period consistent with our backup retention schedule and are not restored except where necessary for security, disaster recovery, or legal compliance.
  • +
  • Anonymization: In some cases, We convert Personal Data into anonymous statistical data that cannot be linked back to You. This anonymized data may be retained indefinitely for research and analytics.
  • +
+

Transfer of Your Personal Data

+

Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ from those from Your jurisdiction.

+

Where required by applicable law, We will ensure that international transfers of Your Personal Data are subject to appropriate safeguards and supplementary measures where appropriate. The Company will take all steps reasonably necessary to ensure that Your data is treated securely and in accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of Your data and other personal information.

+

Delete Your Personal Data

+

You have the right to delete or request that We assist in deleting the Personal Data that We have collected about You.

+

Our Service may give You the ability to delete certain information about You from within the Service.

+

You may update, amend, or delete Your information at any time by signing in to Your Account, if you have one, and visiting the account settings section that allows you to manage Your personal information. You may also contact Us to request access to, correct, or delete any Personal Data that You have provided to Us.

+

Please note, however, that We may need to retain certain information when we have a legal obligation or lawful basis to do so.

+

Disclosure of Your Personal Data

+

Business Transactions

+

If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy Policy.

+

Law enforcement

+

Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency).

+

Other legal requirements

+

The Company may disclose Your Personal Data in the good faith belief that such action is necessary to:

+
    +
  • Comply with a legal obligation
  • +
  • Protect and defend the rights or property of the Company
  • +
  • Prevent or investigate possible wrongdoing in connection with the Service
  • +
  • Protect the personal safety of Users of the Service or the public
  • +
  • Protect against legal liability
  • +
+

Security of Your Personal Data

+

The security of Your Personal Data is important to Us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While We strive to use commercially reasonable means to protect Your Personal Data, We cannot guarantee its absolute security.

+

Children's Privacy

+

Our Service does not address anyone under the age of 16. We do not knowingly collect personally identifiable information from anyone under the age of 16. If You are a parent or guardian and You are aware that Your child has provided Us with Personal Data, please contact Us. If We become aware that We have collected Personal Data from anyone under the age of 16 without verification of parental consent, We take steps to remove that information from Our servers.

+

If We need to rely on consent as a legal basis for processing Your information and Your country requires consent from a parent, We may require Your parent's consent before We collect and use that information.

+

Links to Other Websites

+

Our Service may contain links to other websites that are not operated by Us. If You click on a third party link, You will be directed to that third party's site. We strongly advise You to review the Privacy Policy of every site You visit.

+

We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.

+

Changes to this Privacy Policy

+

We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page.

+

We will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective and update the "Last updated" date at the top of this Privacy Policy.

+

You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.

+

Contact Us

+

If you have any questions about this Privacy Policy, You can contact us:

+
    +
  • By email: privacy@gleipnir.technology
  • +
+{{end}} diff --git a/public-report/template/terms.html b/public-report/template/terms.html new file mode 100644 index 00000000..a5e04dc9 --- /dev/null +++ b/public-report/template/terms.html @@ -0,0 +1,26 @@ +{{template "base.html" .}} +{{define "title"}}Privacy Policy{{end}} +{{define "extraheader"}} +{{end}} +{{define "content"}} +
+

Terms of Service

+

Look, we don't like having terms of service, and we're confident you don't find them interesting to read. But we have to have them as a business.

+

Service provider

+

Report Mosquitoes Online is provided by Gleipnir Technology LLC. By using the website you agree to these terms. If you don't agree, don't tell a computer to access our site.

+

Gleipnir Technology LLC is a company organized under the laws of the state of Arizona, USA, and operates under Arizona law.

+

Gleipnir Technology LLC is located at 2726 S Quinn Ave, Gilbert, AZ

+

What you can expect from us

+

We provide services free to the public. We'll occasionally make changes to these services. We won't notify members of the public, like you, of those changes. We may notify our customers, but we may not, since we may changes very frequently. In general, we have additional agreements beyond this one with entities that are our customers.

+

The data you provide to us is used for public health. That generally means passing some or all of your data on to our customers that work in mosquito abatement. Any information you give to us we may give to them. You can request at any time that we stop sharing your information and we will honor that request.

+

We will only contact you if you give us express permission to do so

+

We won't sell your information to marketers, data aggregators, or anyone who makes money off your data. We only share data with entities engaged in public health work.

+

We are so vehemently opposed to selling your data that we agree to a contractual obligation of at least $1000 USD in damages per person if your data is every sold by the Company, or by any company in the future that aquires a stake in the Company.

+

What we expect from you

+

Don't provide false data. This include shenanigans like using our system to send messages to other people's email address or phone.

+

Don't try to scrape/exfiltrate/steal our data. If you've got a legitimate use for our data, contact us, if you've got a worthy project we may be willing to work with you.

+

Don't try to break into our systems, infect them with malware, use us as a tool in a phishing campaign, or generally hack about. We like hackers, but we prefer to work with them intentionally.

+

Don't misrepresent who you are

+

You agree we can use any data you provide to us as we see fit. This may include doing nothing with it, but generally includes improving public health by fighting mosquitoes and mosquito-born illnesses.

+
+{{end}} diff --git a/queue/audio_processing.go b/queue/audio_processing.go index d03ceb6e..5664b3ee 100644 --- a/queue/audio_processing.go +++ b/queue/audio_processing.go @@ -1,121 +1,23 @@ package queue import ( - "context" - "errors" - "fmt" - "os" - "os/exec" - - "github.com/Gleipnir-Technology/nidus-sync/db" - "github.com/Gleipnir-Technology/nidus-sync/userfile" "github.com/google/uuid" "github.com/rs/zerolog/log" ) // AudioJob represents a job to process an audio file. -type AudioJob struct { +type JobAudio struct { AudioUUID uuid.UUID } -// audioJobChannel is the channel used to send audio processing jobs to the worker. -var audioJobChannel chan AudioJob - -// StartAudioWorker initializes the audio job channel and starts the worker goroutine. -func StartAudioWorker(ctx context.Context) { - buffer := 100 - audioJobChannel = make(chan AudioJob, buffer) // Buffered channel to prevent blocking - log.Info().Int("buffer depth", buffer).Msg("Started audio worker") - go func() { - for { - select { - case <-ctx.Done(): - log.Info().Msg("Audio worker shutting down.") - return - case job := <-audioJobChannel: - log.Info().Str("uuid", job.AudioUUID.String()).Msg("Processing audio job") - err := processAudioFile(job.AudioUUID) - if err != nil { - log.Error().Err(err).Str("uuid", job.AudioUUID.String()).Msg("Error processing audio file") - } - } - } - }() -} +var ChannelJobAudio chan JobAudio // EnqueueAudioJob sends an audio processing job to the worker. -func EnqueueAudioJob(job AudioJob) { +func EnqueueAudioJob(job JobAudio) { select { - case audioJobChannel <- job: + case ChannelJobAudio <- job: log.Info().Str("uuid", job.AudioUUID.String()).Msg("Enqueued audio job") default: log.Warn().Str("uuid", job.AudioUUID.String()).Msg("Audio job channel is full, dropping job") } } - -func processAudioFile(audioUUID uuid.UUID) error { - // Normalize audio - err := normalizeAudio(audioUUID) - if err != nil { - return fmt.Errorf("failed to normalize audio %s: %v", audioUUID, err) - } - - // Transcode to OGG - err = transcodeToOgg(audioUUID) - if err != nil { - return fmt.Errorf("failed to transcode audio %s to OGG: %v", audioUUID, err) - } - - EnqueueLabelStudioJob(LabelStudioJob{ - UUID: audioUUID, - }) - return nil -} - -func normalizeAudio(audioUUID uuid.UUID) error { - source := userfile.AudioFileContentPathRaw(audioUUID.String()) - _, err := os.Stat(source) - if errors.Is(err, os.ErrNotExist) { - log.Warn().Str("source", source).Msg("file doesn't exist, skipping normalization") - return nil - } - log.Info().Str("sourcce", source).Msg("Normalizing") - destination := userfile.AudioFileContentPathNormalized(audioUUID.String()) - // Use "ffmpeg" directly, assuming it's in the system PATH - cmd := exec.Command("ffmpeg", "-i", source, "-filter:a", "loudnorm", destination) - out, err := cmd.CombinedOutput() - if err != nil { - log.Printf("FFmpeg output for normalization: %s", out) - return fmt.Errorf("ffmpeg normalization failed: %v", err) - } - err = db.NoteAudioNormalized(audioUUID.String()) - if err != nil { - return fmt.Errorf("failed to update database for normalized audio %s: %v", audioUUID, err) - } - log.Info().Str("destination", destination).Msg("Normalized audio") - return nil -} - -func transcodeToOgg(audioUUID uuid.UUID) error { - source := userfile.AudioFileContentPathNormalized(audioUUID.String()) - _, err := os.Stat(source) - if errors.Is(err, os.ErrNotExist) { - log.Warn().Str("source", source).Msg("file doesn't exist, skipping OGG transcoding") - return nil - } - log.Info().Str("source", source).Msg("Transcoding to ogg") - destination := userfile.AudioFileContentPathOgg(audioUUID.String()) - // Use "ffmpeg" directly, assuming it's in the system PATH - cmd := exec.Command("ffmpeg", "-i", source, "-vn", "-acodec", "libvorbis", destination) - out, err := cmd.CombinedOutput() - if err != nil { - log.Error().Err(err).Bytes("out", out).Msg("FFmpeg output for OGG transcoding") - return fmt.Errorf("ffmpeg OGG transcoding failed: %v", err) - } - err = db.NoteAudioTranscodedToOgg(audioUUID.String()) - if err != nil { - return fmt.Errorf("failed to update database for OGG transcoded audio %s: %v", audioUUID, err) - } - log.Info().Str("destination", destination).Msg("Transcoded audio") - return nil -} diff --git a/queue/comms.go b/queue/comms.go new file mode 100644 index 00000000..d2dd624a --- /dev/null +++ b/queue/comms.go @@ -0,0 +1,38 @@ +package queue + +import ( + "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/rs/zerolog/log" +) + +type JobEmail struct { + Destination string + Source string + Type enums.CommsEmailmessagetype +} +type JobSMS struct { + Destination string + Source string + Type enums.CommsSmsmessagetype +} + +var ChannelJobEmail chan JobEmail +var ChannelJobSMS chan JobSMS + +func EnqueueJobEmail(job JobEmail) { + select { + case ChannelJobEmail <- job: + log.Info().Str("destination", job.Destination).Msg("Enqueued email job") + default: + log.Warn().Msg("email job channel is full, dropping job") + } +} + +func EnqueueJobSMS(job JobSMS) { + select { + case ChannelJobSMS <- job: + log.Info().Str("destination", job.Destination).Msg("Enqueued sms job") + default: + log.Warn().Msg("sms job channel is full, dropping job") + } +} From f4a88623afa8e357586e051943da9bb4749c2782 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 21 Jan 2026 03:30:03 +0000 Subject: [PATCH 0061/1453] Overhaul system for handling text messaging Move away from "SMS" as the operative word - we're going RCS. Move all comms processing to a separate goroutine Rename the DB tables --- api/api.go | 4 +- api/endpoint.go | 3 + api/twilio.go | 49 ++ background/arcgis.go | 6 +- background/audio.go | 30 +- background/background.go | 44 ++ background/comms.go | 86 ++- {queue => background}/label_studio.go | 16 +- comms/text.go | 62 +++ comms/{ => voipms}/sms.go | 0 config/config.go | 44 +- ...s.sms_log.bob.go => comms.text_log.bob.go} | 12 +- db/dbinfo/comms.email_log.bob.go | 2 +- ...s.sms_log.bob.go => comms.text_log.bob.go} | 66 +-- db/enums/enums.bob.go | 114 ++-- db/factory/bobfactory_context.bob.go | 16 +- db/factory/bobfactory_main.bob.go | 42 +- db/factory/bobfactory_random.bob.go | 8 +- db/factory/comms.email_log.bob.go | 14 +- db/factory/comms.phone.bob.go | 114 ++-- db/factory/comms.sms_log.bob.go | 523 ------------------ db/factory/comms.text_log.bob.go | 523 ++++++++++++++++++ db/migrations/00036_comms.sql | 20 +- db/models/bob_joins.bob.go | 4 +- db/models/bob_loaders.bob.go | 8 +- db/models/bob_where.bob.go | 6 +- db/models/comms.email_log.bob.go | 12 +- db/models/comms.phone.bob.go | 310 +++++------ ...s.sms_log.bob.go => comms.text_log.bob.go} | 346 ++++++------ go.mod | 9 +- go.sum | 25 + main.go | 36 +- public-report/quick.go | 23 +- queue/audio_processing.go | 23 - queue/comms.go | 38 -- 35 files changed, 1426 insertions(+), 1212 deletions(-) create mode 100644 api/twilio.go create mode 100644 background/background.go rename {queue => background}/label_studio.go (94%) create mode 100644 comms/text.go rename comms/{ => voipms}/sms.go (100%) rename db/dberrors/{comms.sms_log.bob.go => comms.text_log.bob.go} (51%) rename db/dbinfo/{comms.sms_log.bob.go => comms.text_log.bob.go} (64%) delete mode 100644 db/factory/comms.sms_log.bob.go create mode 100644 db/factory/comms.text_log.bob.go rename db/models/{comms.sms_log.bob.go => comms.text_log.bob.go} (51%) delete mode 100644 queue/audio_processing.go delete mode 100644 queue/comms.go diff --git a/api/api.go b/api/api.go index 3e58db81..60226a1a 100644 --- a/api/api.go +++ b/api/api.go @@ -11,10 +11,10 @@ import ( "strconv" "time" + "github.com/Gleipnir-Technology/nidus-sync/background" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/platform" - "github.com/Gleipnir-Technology/nidus-sync/queue" "github.com/Gleipnir-Technology/nidus-sync/userfile" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" @@ -74,7 +74,7 @@ func apiAudioContentPost(w http.ResponseWriter, r *http.Request, u *models.User) http.Error(w, "failed to write content file", http.StatusInternalServerError) } - queue.EnqueueAudioJob(queue.JobAudio{AudioUUID: audioUUID}) + background.AudioTranscode(audioUUID) w.WriteHeader(http.StatusOK) } diff --git a/api/endpoint.go b/api/endpoint.go index fd86109f..14d59cb1 100644 --- a/api/endpoint.go +++ b/api/endpoint.go @@ -21,6 +21,9 @@ func AddRoutes(r chi.Router) { // Unauthenticated endpoints r.Get("/district", apiGetDistrict) + r.Post("/twilio/message", twilioMessagePost) + r.Post("/twilio/status", twilioStatusPost) + r.Post("/twilio/text", twilioTextPost) r.Get("/webhook/fieldseeker", webhookFieldseeker) r.Post("/webhook/fieldseeker", webhookFieldseeker) } diff --git a/api/twilio.go b/api/twilio.go new file mode 100644 index 00000000..7323eb69 --- /dev/null +++ b/api/twilio.go @@ -0,0 +1,49 @@ +package api + +import ( + "fmt" + "net/http" + + "github.com/rs/zerolog/log" + "github.com/twilio/twilio-go/twiml" +) + +func twilioMessagePost(w http.ResponseWriter, r *http.Request) { + message_sid := r.PostFormValue("MessageSid") + log.Info().Str("sid", message_sid).Msg("Twilio Message POST") + fmt.Fprintf(w, "") +} +func twilioStatusPost(w http.ResponseWriter, r *http.Request) { + message_sid := r.PostFormValue("MessageSid") + message_status := r.PostFormValue("MessageStatus") + log.Info().Str("sid", message_sid).Str("status", message_status).Msg("Updated message status") + fmt.Fprintf(w, "") +} +func twilioTextPost(w http.ResponseWriter, r *http.Request) { + message_sid := r.PostFormValue("MessageSid") + account_sid := r.PostFormValue("AccountSid") + messaging_service_sid := r.PostFormValue("MessagingServiceSid") + from := r.PostFormValue("From") + to_ := r.PostFormValue("To") + body := r.PostFormValue("Body") + num_media := r.PostFormValue("NumMedia") + num_segments := r.PostFormValue("NumSegments") + media_content_type0 := r.PostFormValue("MediaContentType0") + media_url0 := r.PostFormValue("MediaUrl0") + from_city := r.PostFormValue("FromCity") + from_state := r.PostFormValue("FromState") + from_zip := r.PostFormValue("FromZip") + from_country := r.PostFormValue("FromCountry") + to_city := r.PostFormValue("ToCity") + to_state := r.PostFormValue("ToState") + to_zip := r.PostFormValue("ToZip") + to_country := r.PostFormValue("ToCountry") + log.Info().Str("message_sid", message_sid).Str("account_sid", account_sid).Str("messaging_service_sid", messaging_service_sid).Str("from", from).Str("to_", to_).Str("body", body).Str("num_media", num_media).Str("num_segments", num_segments).Str("media_content_type0", media_content_type0).Str("media_url0", media_url0).Str("from_city", from_city).Str("from_state", from_state).Str("from_zip", from_zip).Str("from_country", from_country).Str("to_city", to_city).Str("to_state", to_state).Str("to_zip", to_zip).Str("to_country", to_country).Msg("got text") + twiml, _ := twiml.Messages([]twiml.Element{ + &twiml.MessagingMessage{ + Body: "Hey there.", + }, + }) + w.Header().Set("Content-Type", "text/xml") + fmt.Fprintf(w, "%s", twiml) +} diff --git a/background/arcgis.go b/background/arcgis.go index d53177da..94aae8ca 100644 --- a/background/arcgis.go +++ b/background/arcgis.go @@ -51,7 +51,7 @@ type NoOAuthForOrg struct{} func (e NoOAuthForOrg) Error() string { return "No oauth available for organization" } -var NewOAuthTokenChannel chan struct{} +var newOAuthTokenChannel chan struct{} var CodeVerifier string = "random_secure_string_min_43_chars_long_should_be_stored_in_session" type OAuthTokenResponse struct { @@ -118,7 +118,7 @@ func IsSyncOngoing(org_id int32) bool { } // This is a goroutine that is in charge of getting Fieldseeker data and keeping it fresh. -func RefreshFieldseekerData(ctx context.Context, newOauthCh <-chan struct{}) { +func refreshFieldseekerData(ctx context.Context, newOauthCh <-chan struct{}) { syncStatusByOrg = make(map[int32]bool, 0) for { workerCtx, cancel := context.WithCancel(context.Background()) @@ -328,7 +328,7 @@ func updateArcgisUserData(ctx context.Context, user *models.User, access_token s maybeCreateWebhook(ctx, fieldseekerClient) downloadFieldseekerSchema(ctx, fieldseekerClient, arcgis_id) notification.ClearOauth(ctx, user) - NewOAuthTokenChannel <- struct{}{} + newOAuthTokenChannel <- struct{}{} } func updatePortalData(ctx context.Context, client *arcgis.ArcGIS, user_id int32) (*arcgis.PortalsResponse, error) { diff --git a/background/audio.go b/background/audio.go index b61a0546..ddd2b4e6 100644 --- a/background/audio.go +++ b/background/audio.go @@ -8,14 +8,26 @@ import ( "os/exec" "github.com/Gleipnir-Technology/nidus-sync/db" - "github.com/Gleipnir-Technology/nidus-sync/queue" "github.com/Gleipnir-Technology/nidus-sync/userfile" "github.com/google/uuid" "github.com/rs/zerolog/log" ) -// StartAudioWorker initializes the audio job channel and starts the worker goroutine. -func StartWorkerAudio(ctx context.Context, audioJobChannel chan queue.JobAudio) { +// AudioJob represents a job to process an audio file. +type jobAudio struct { + AudioUUID uuid.UUID +} + +var channelJobAudio chan jobAudio + +func AudioTranscode(audio_uuid uuid.UUID) { + enqueueAudioJob(jobAudio{ + AudioUUID: audio_uuid, + }) +} + +// startAudioWorker initializes the audio job channel and starts the worker goroutine. +func startWorkerAudio(ctx context.Context, audioJobChannel chan jobAudio) { go func() { for { select { @@ -33,6 +45,16 @@ func StartWorkerAudio(ctx context.Context, audioJobChannel chan queue.JobAudio) }() } +// EnqueueAudioJob sends an audio processing job to the worker. +func enqueueAudioJob(job jobAudio) { + select { + case channelJobAudio <- job: + log.Info().Str("uuid", job.AudioUUID.String()).Msg("Enqueued audio job") + default: + log.Warn().Str("uuid", job.AudioUUID.String()).Msg("Audio job channel is full, dropping job") + } +} + func normalizeAudio(audioUUID uuid.UUID) error { source := userfile.AudioFileContentPathRaw(audioUUID.String()) _, err := os.Stat(source) @@ -70,7 +92,7 @@ func processAudioFile(audioUUID uuid.UUID) error { return fmt.Errorf("failed to transcode audio %s to OGG: %v", audioUUID, err) } - queue.EnqueueLabelStudioJob(queue.LabelStudioJob{ + enqueueLabelStudioJob(jobLabelStudio{ UUID: audioUUID, }) return nil diff --git a/background/background.go b/background/background.go new file mode 100644 index 00000000..b800ac83 --- /dev/null +++ b/background/background.go @@ -0,0 +1,44 @@ +package background + +import ( + "context" + "sync" +) + +var waitGroup sync.WaitGroup + +func Start(ctx context.Context) { + newOAuthTokenChannel = make(chan struct{}, 10) + + channelJobAudio = make(chan jobAudio, 100) // Buffered channel to prevent blocking + channelJobEmail = make(chan jobEmail, 100) // Buffered channel to prevent blocking + channelJobText = make(chan jobText, 100) // Buffered channel to prevent blocking + + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + refreshFieldseekerData(ctx, newOAuthTokenChannel) + }() + + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + startWorkerAudio(ctx, channelJobAudio) + }() + + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + startWorkerEmail(ctx, channelJobEmail) + }() + + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + startWorkerText(ctx, channelJobText) + }() +} +func WaitForExit() { + + waitGroup.Wait() +} diff --git a/background/comms.go b/background/comms.go index fd1c6af4..2992a1fd 100644 --- a/background/comms.go +++ b/background/comms.go @@ -2,12 +2,67 @@ package background import ( "context" + "errors" + "fmt" - "github.com/Gleipnir-Technology/nidus-sync/queue" + "github.com/Gleipnir-Technology/nidus-sync/comms" + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/rs/zerolog/log" ) -func StartWorkerEmail(ctx context.Context, channel chan queue.JobEmail) { +var channelJobEmail chan jobEmail +var channelJobText chan jobText + +func ReportSubscriptionConfirmationEmail(destination string) { + enqueueJobEmail(jobEmail{ + Destination: destination, + Source: config.ForwardEmailReportAddress, + Type: enums.CommsMessagetypeemailReportSubscriptionConfirmation, + }) +} + +func ReportSubscriptionConfirmationText(destination comms.E164, report_id string) { + enqueueJobText(jobText{ + Destination: destination, + ReportID: report_id, + Source: config.RMOPhoneNumber, + Type: enums.CommsMessagetypetextReportSubscriptionConfirmation, + }) +} + +type jobEmail struct { + Destination string + ReportID string + Source string + Type enums.CommsMessagetypeemail +} +type jobText struct { + Destination comms.E164 + ReportID string + Source comms.E164 + Type enums.CommsMessagetypetext +} + +func enqueueJobEmail(job jobEmail) { + select { + case channelJobEmail <- job: + log.Info().Str("destination", job.Destination).Msg("Enqueued email job") + default: + log.Warn().Msg("email job channel is full, dropping job") + } +} + +func enqueueJobText(job jobText) { + select { + case channelJobText <- job: + log.Info().Msg("Enqueued text job") + default: + log.Warn().Msg("sms job channel is full, dropping job") + } +} + +func startWorkerEmail(ctx context.Context, channel chan jobEmail) { go func() { for { select { @@ -15,7 +70,7 @@ func StartWorkerEmail(ctx context.Context, channel chan queue.JobEmail) { log.Info().Msg("Email worker shutting down.") return case job := <-channel: - err := processJobEmail(job) + err := jobProcessEmail(job) if err != nil { log.Error().Err(err).Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Error processing audio file") } @@ -24,7 +79,7 @@ func StartWorkerEmail(ctx context.Context, channel chan queue.JobEmail) { }() } -func StartWorkerSMS(ctx context.Context, channel chan queue.JobSMS) { +func startWorkerText(ctx context.Context, channel chan jobText) { go func() { for { select { @@ -32,21 +87,34 @@ func StartWorkerSMS(ctx context.Context, channel chan queue.JobSMS) { log.Info().Msg("Email worker shutting down.") return case job := <-channel: - err := processJobSMS(job) + err := jobProcessText(job) if err != nil { - log.Error().Err(err).Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Error processing audio file") + log.Error().Err(err).Str("type", string(job.Type)).Msg("Error processing text message job") } } } }() } -func processJobEmail(job queue.JobEmail) error { +func jobProcessEmail(job jobEmail) error { log.Info().Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Pretend doing email job") return nil } -func processJobSMS(job queue.JobSMS) error { - log.Info().Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Pretend doing email job") +func jobProcessText(job jobText) error { + var message string + switch job.Type { + case enums.CommsMessagetypetextInitialContact: + message = "This is Report Mosquitoes Online. We just got your number. Text \"YES\" to get texts, or \"STOP\" to stap." + case enums.CommsMessagetypetextReportSubscriptionConfirmation: + message = "Thanks for submitting a mosquito report. Text for any questions. We'll send you updates as we get them." + default: + return errors.New("No idea what message to send") + } + err := comms.SendText(job.Source, job.Destination, message) + if err != nil { + log.Error().Err(err).Msg("Failed to send text message") + return fmt.Errorf("Failed to send message '%s' to '%s'", job.Type, job.Destination) + } return nil } diff --git a/queue/label_studio.go b/background/label_studio.go similarity index 94% rename from queue/label_studio.go rename to background/label_studio.go index 1cf1a1d2..fa8c34f5 100644 --- a/queue/label_studio.go +++ b/background/label_studio.go @@ -1,4 +1,4 @@ -package queue +package background import ( "context" @@ -16,15 +16,15 @@ import ( "github.com/google/uuid" ) -type LabelStudioJob struct { +type jobLabelStudio struct { UUID uuid.UUID } -var labelJobChannel chan LabelStudioJob +var channelJobLabelStudio chan jobLabelStudio -func EnqueueLabelStudioJob(job LabelStudioJob) { +func enqueueLabelStudioJob(job jobLabelStudio) { select { - case labelJobChannel <- job: + case channelJobLabelStudio <- job: log.Printf("Enqueued label job for UUID: %s", job.UUID) default: log.Printf("Label job channel is full, dropping job for UUID: %s", job.UUID) @@ -49,7 +49,7 @@ func StartLabelStudioWorker(ctx context.Context) error { return fmt.Errorf("Failed to create minio client: %v", err) } buffer := 100 - labelJobChannel = make(chan LabelStudioJob, buffer) // Buffered channel to prevent blocking + channelJobLabelStudio = make(chan jobLabelStudio, buffer) // Buffered channel to prevent blocking log.Printf("Started label studio worker with buffer depth %d", buffer) go func() { for { @@ -57,7 +57,7 @@ func StartLabelStudioWorker(ctx context.Context) error { case <-ctx.Done(): log.Println("Audio worker shutting down.") return - case job := <-labelJobChannel: + case job := <-channelJobLabelStudio: log.Printf("Processing label job for UUID: %s", job.UUID) err := processLabelTask(ctx, minioClient, minioBucket, labelStudioClient, project, job) if err != nil { @@ -99,7 +99,7 @@ func createLabelStudioClient() (*labelstudio.Client, error) { return labelStudioClient, nil } -func processLabelTask(ctx context.Context, minioClient *minio.Client, minioBucket string, labelStudioClient *labelstudio.Client, project *labelstudio.Project, job LabelStudioJob) error { +func processLabelTask(ctx context.Context, minioClient *minio.Client, minioBucket string, labelStudioClient *labelstudio.Client, project *labelstudio.Project, job jobLabelStudio) error { customer := os.Getenv("CUSTOMER") if customer == "" { return errors.New("You must specify a CUSTOMER env var") diff --git a/comms/text.go b/comms/text.go new file mode 100644 index 00000000..5dfce308 --- /dev/null +++ b/comms/text.go @@ -0,0 +1,62 @@ +package comms + +import ( + "encoding/json" + "fmt" + + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/nyaruka/phonenumbers" + "github.com/rs/zerolog/log" + "github.com/twilio/twilio-go" + twilioApi "github.com/twilio/twilio-go/rest/api/v2010" +) + +type E164 = phonenumbers.PhoneNumber + +func ParsePhoneNumber(input string) (*E164, error) { + return phonenumbers.Parse(input, "US") +} + +func SendText(source E164, destination E164, message string) error { + log.Info().Msg("Sending text message...") + // Make sure TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN exists in your environment + client := twilio.NewRestClient() + + params := &twilioApi.CreateMessageParams{} + params.SetMessagingServiceSid(config.TwilioMessagingServiceSID) + + params.SetBody(message) + dest := phonenumbers.Format(&destination, phonenumbers.E164) + params.SetTo(dest) + resp, err := client.Api.CreateMessage(params) + + if err != nil { + return fmt.Errorf("Failed to create message to %s: %w", dest, err) + } else { + if resp.Body != nil { + log.Info().Str("dest", dest).Str("body", *resp.Body).Msg("Text message response") + } else { + log.Info().Str("dest", dest).Msg("Text message response is nil") + } + } + return nil +} + +func sendSMS(destination, source, message string) error { + client := twilio.NewRestClientWithParams(twilio.ClientParams{ + Username: config.TwilioAccountSID, + Password: config.TwilioAuthToken, + }) + params := &twilioApi.CreateMessageParams{} + params.SetTo("+15558675309") + params.SetFrom("+15017250604") + params.SetBody("Hello from Go!") + + resp, err := client.Api.CreateMessage(params) + if err != nil { + return fmt.Errorf("Error sending SMS message: %w", err) + } + response, _ := json.Marshal(*resp) + log.Debug().Str("response", string(response)).Msg("Send SMS") + return nil +} diff --git a/comms/sms.go b/comms/voipms/sms.go similarity index 100% rename from comms/sms.go rename to comms/voipms/sms.go diff --git a/config/config.go b/config/config.go index 1bd04286..e82e04ea 100644 --- a/config/config.go +++ b/config/config.go @@ -5,6 +5,8 @@ import ( "net/url" "os" "strconv" + + "github.com/nyaruka/phonenumbers" ) var ( @@ -21,12 +23,14 @@ var ( ForwardEmailReportUsername string MapboxToken string PGDSN string + RMODomain string + RMOPhoneNumber phonenumbers.PhoneNumber URLReport string URLSync string URLTegola string - VoipMSPassword string - VoipMSNumber string - VoipMSUsername string + TwilioAuthToken string + TwilioAccountSID string + TwilioMessagingServiceSID string ) // Build the ArcGIS authorization URL with PKCE @@ -62,7 +66,7 @@ func MakeURLSync(path string) string { return fmt.Sprintf("https://%s%s", URLSync, path) } -func Parse() error { +func Parse() (err error) { Bind = os.Getenv("BIND") if Bind == "" { Bind = ":9001" @@ -118,6 +122,20 @@ func Parse() error { if PGDSN == "" { return fmt.Errorf("You must specify a non-empty POSTGRES_DSN") } + RMODomain = os.Getenv("RMO_DOMAIN") + if RMODomain == "" { + return fmt.Errorf("You must specify a non-empty RMO_DOMAIN") + } + rmo_phone_number := os.Getenv("RMO_PHONE_NUMBER") + if rmo_phone_number == "" { + return fmt.Errorf("You must specify a non-empty RMO_PHONE_NUMBER") + } + p, err := phonenumbers.Parse(rmo_phone_number, "US") + if err != nil { + return fmt.Errorf("Failed to parse '%s' as a valid phone number: %w", rmo_phone_number, err) + } + RMOPhoneNumber = *p + URLReport = os.Getenv("URL_REPORT") if URLReport == "" { return fmt.Errorf("You must specify a non-empty URL_REPORT") @@ -130,17 +148,17 @@ func Parse() error { if URLTegola == "" { return fmt.Errorf("You must specify a non-empty URL_TEGOLA") } - VoipMSNumber = os.Getenv("VOIPMS_NUMBER") - if VoipMSNumber == "" { - return fmt.Errorf("You must specify a non-empty VOIPMS_NUMBER") + TwilioAccountSID = os.Getenv("TWILIO_ACCOUNT_SID") + if TwilioAccountSID == "" { + return fmt.Errorf("You must specify a non-empty TWILIO_ACCOUNT_SID") } - VoipMSPassword = os.Getenv("VOIPMS_PASSWORD") - if VoipMSPassword == "" { - return fmt.Errorf("You must specify a non-empty VOIPMS_PASSWORD") + TwilioAuthToken = os.Getenv("TWILIO_AUTH_TOKEN") + if TwilioAuthToken == "" { + return fmt.Errorf("You must specify a non-empty TWILIO_AUTH_TOKEN") } - VoipMSUsername = os.Getenv("VOIPMS_USERNAME") - if VoipMSUsername == "" { - return fmt.Errorf("You must specify a non-empty VOIPMS_USERNAME") + TwilioMessagingServiceSID = os.Getenv("TWILIO_MESSAGING_SERVICE_SID") + if TwilioMessagingServiceSID == "" { + return fmt.Errorf("You must specify a non-empty TWILIO_MESSAGING_SERVICE_SID") } return nil } diff --git a/db/dberrors/comms.sms_log.bob.go b/db/dberrors/comms.text_log.bob.go similarity index 51% rename from db/dberrors/comms.sms_log.bob.go rename to db/dberrors/comms.text_log.bob.go index e4d154a4..4611eb7a 100644 --- a/db/dberrors/comms.sms_log.bob.go +++ b/db/dberrors/comms.text_log.bob.go @@ -3,15 +3,15 @@ package dberrors -var CommsSMSLogErrors = &commsSMSLogErrors{ - ErrUniqueSmsLogPkey: &UniqueConstraintError{ +var CommsTextLogErrors = &commsTextLogErrors{ + ErrUniqueTextLogPkey: &UniqueConstraintError{ schema: "comms", - table: "sms_log", + table: "text_log", columns: []string{"destination", "source", "type"}, - s: "sms_log_pkey", + s: "text_log_pkey", }, } -type commsSMSLogErrors struct { - ErrUniqueSmsLogPkey *UniqueConstraintError +type commsTextLogErrors struct { + ErrUniqueTextLogPkey *UniqueConstraintError } diff --git a/db/dbinfo/comms.email_log.bob.go b/db/dbinfo/comms.email_log.bob.go index 055042bf..5fd8a244 100644 --- a/db/dbinfo/comms.email_log.bob.go +++ b/db/dbinfo/comms.email_log.bob.go @@ -44,7 +44,7 @@ var CommsEmailLogs = Table[ }, Type: column{ Name: "type", - DBType: "comms.emailmessagetype", + DBType: "comms.messagetypeemail", Default: "", Comment: "", Nullable: false, diff --git a/db/dbinfo/comms.sms_log.bob.go b/db/dbinfo/comms.text_log.bob.go similarity index 64% rename from db/dbinfo/comms.sms_log.bob.go rename to db/dbinfo/comms.text_log.bob.go index 7f21a59a..308ca842 100644 --- a/db/dbinfo/comms.sms_log.bob.go +++ b/db/dbinfo/comms.text_log.bob.go @@ -5,16 +5,16 @@ package dbinfo import "github.com/aarondl/opt/null" -var CommsSMSLogs = Table[ - commsSMSLogColumns, - commsSMSLogIndexes, - commsSMSLogForeignKeys, - commsSMSLogUniques, - commsSMSLogChecks, +var CommsTextLogs = Table[ + commsTextLogColumns, + commsTextLogIndexes, + commsTextLogForeignKeys, + commsTextLogUniques, + commsTextLogChecks, ]{ Schema: "comms", - Name: "sms_log", - Columns: commsSMSLogColumns{ + Name: "text_log", + Columns: commsTextLogColumns{ Created: column{ Name: "created", DBType: "timestamp without time zone", @@ -44,7 +44,7 @@ var CommsSMSLogs = Table[ }, Type: column{ Name: "type", - DBType: "comms.smsmessagetype", + DBType: "comms.messagetypetext", Default: "", Comment: "", Nullable: false, @@ -52,10 +52,10 @@ var CommsSMSLogs = Table[ AutoIncr: false, }, }, - Indexes: commsSMSLogIndexes{ - SMSLogPkey: index{ + Indexes: commsTextLogIndexes{ + TextLogPkey: index{ Type: "btree", - Name: "sms_log_pkey", + Name: "text_log_pkey", Columns: []indexColumn{ { Name: "destination", @@ -82,23 +82,23 @@ var CommsSMSLogs = Table[ }, }, PrimaryKey: &constraint{ - Name: "sms_log_pkey", + Name: "text_log_pkey", Columns: []string{"destination", "source", "type"}, Comment: "", }, - ForeignKeys: commsSMSLogForeignKeys{ - CommsSMSLogSMSLogDestinationFkey: foreignKey{ + ForeignKeys: commsTextLogForeignKeys{ + CommsTextLogTextLogDestinationFkey: foreignKey{ constraint: constraint{ - Name: "comms.sms_log.sms_log_destination_fkey", + Name: "comms.text_log.text_log_destination_fkey", Columns: []string{"destination"}, Comment: "", }, ForeignTable: "comms.phone", ForeignColumns: []string{"e164"}, }, - CommsSMSLogSMSLogSourceFkey: foreignKey{ + CommsTextLogTextLogSourceFkey: foreignKey{ constraint: constraint{ - Name: "comms.sms_log.sms_log_source_fkey", + Name: "comms.text_log.text_log_source_fkey", Columns: []string{"source"}, Comment: "", }, @@ -110,48 +110,48 @@ var CommsSMSLogs = Table[ Comment: "", } -type commsSMSLogColumns struct { +type commsTextLogColumns struct { Created column Destination column Source column Type column } -func (c commsSMSLogColumns) AsSlice() []column { +func (c commsTextLogColumns) AsSlice() []column { return []column{ c.Created, c.Destination, c.Source, c.Type, } } -type commsSMSLogIndexes struct { - SMSLogPkey index +type commsTextLogIndexes struct { + TextLogPkey index } -func (i commsSMSLogIndexes) AsSlice() []index { +func (i commsTextLogIndexes) AsSlice() []index { return []index{ - i.SMSLogPkey, + i.TextLogPkey, } } -type commsSMSLogForeignKeys struct { - CommsSMSLogSMSLogDestinationFkey foreignKey - CommsSMSLogSMSLogSourceFkey foreignKey +type commsTextLogForeignKeys struct { + CommsTextLogTextLogDestinationFkey foreignKey + CommsTextLogTextLogSourceFkey foreignKey } -func (f commsSMSLogForeignKeys) AsSlice() []foreignKey { +func (f commsTextLogForeignKeys) AsSlice() []foreignKey { return []foreignKey{ - f.CommsSMSLogSMSLogDestinationFkey, f.CommsSMSLogSMSLogSourceFkey, + f.CommsTextLogTextLogDestinationFkey, f.CommsTextLogTextLogSourceFkey, } } -type commsSMSLogUniques struct{} +type commsTextLogUniques struct{} -func (u commsSMSLogUniques) AsSlice() []constraint { +func (u commsTextLogUniques) AsSlice() []constraint { return []constraint{} } -type commsSMSLogChecks struct{} +type commsTextLogChecks struct{} -func (c commsSMSLogChecks) AsSlice() []check { +func (c commsTextLogChecks) AsSlice() []check { return []check{} } diff --git a/db/enums/enums.bob.go b/db/enums/enums.bob.go index 22fde9b7..373cd454 100644 --- a/db/enums/enums.bob.go +++ b/db/enums/enums.bob.go @@ -193,32 +193,35 @@ func (e *Audiodatatype) Scan(value any) error { return nil } -// Enum values for CommsEmailmessagetype +// Enum values for CommsMessagetypeemail const ( - CommsEmailmessagetypeReportSubscriptionConfirmation CommsEmailmessagetype = "report-subscription-confirmation" - CommsEmailmessagetypeReportStatusScheduled CommsEmailmessagetype = "report-status-scheduled" - CommsEmailmessagetypeReportStatusComplete CommsEmailmessagetype = "report-status-complete" + CommsMessagetypeemailInitialContact CommsMessagetypeemail = "initial-contact" + CommsMessagetypeemailReportSubscriptionConfirmation CommsMessagetypeemail = "report-subscription-confirmation" + CommsMessagetypeemailReportStatusScheduled CommsMessagetypeemail = "report-status-scheduled" + CommsMessagetypeemailReportStatusComplete CommsMessagetypeemail = "report-status-complete" ) -func AllCommsEmailmessagetype() []CommsEmailmessagetype { - return []CommsEmailmessagetype{ - CommsEmailmessagetypeReportSubscriptionConfirmation, - CommsEmailmessagetypeReportStatusScheduled, - CommsEmailmessagetypeReportStatusComplete, +func AllCommsMessagetypeemail() []CommsMessagetypeemail { + return []CommsMessagetypeemail{ + CommsMessagetypeemailInitialContact, + CommsMessagetypeemailReportSubscriptionConfirmation, + CommsMessagetypeemailReportStatusScheduled, + CommsMessagetypeemailReportStatusComplete, } } -type CommsEmailmessagetype string +type CommsMessagetypeemail string -func (e CommsEmailmessagetype) String() string { +func (e CommsMessagetypeemail) String() string { return string(e) } -func (e CommsEmailmessagetype) Valid() bool { +func (e CommsMessagetypeemail) Valid() bool { switch e { - case CommsEmailmessagetypeReportSubscriptionConfirmation, - CommsEmailmessagetypeReportStatusScheduled, - CommsEmailmessagetypeReportStatusComplete: + case CommsMessagetypeemailInitialContact, + CommsMessagetypeemailReportSubscriptionConfirmation, + CommsMessagetypeemailReportStatusScheduled, + CommsMessagetypeemailReportStatusComplete: return true default: return false @@ -226,75 +229,78 @@ func (e CommsEmailmessagetype) Valid() bool { } // useful when testing in other packages -func (e CommsEmailmessagetype) All() []CommsEmailmessagetype { - return AllCommsEmailmessagetype() +func (e CommsMessagetypeemail) All() []CommsMessagetypeemail { + return AllCommsMessagetypeemail() } -func (e CommsEmailmessagetype) MarshalText() ([]byte, error) { +func (e CommsMessagetypeemail) MarshalText() ([]byte, error) { return []byte(e), nil } -func (e *CommsEmailmessagetype) UnmarshalText(text []byte) error { +func (e *CommsMessagetypeemail) UnmarshalText(text []byte) error { return e.Scan(text) } -func (e CommsEmailmessagetype) MarshalBinary() ([]byte, error) { +func (e CommsMessagetypeemail) MarshalBinary() ([]byte, error) { return []byte(e), nil } -func (e *CommsEmailmessagetype) UnmarshalBinary(data []byte) error { +func (e *CommsMessagetypeemail) UnmarshalBinary(data []byte) error { return e.Scan(data) } -func (e CommsEmailmessagetype) Value() (driver.Value, error) { +func (e CommsMessagetypeemail) Value() (driver.Value, error) { return string(e), nil } -func (e *CommsEmailmessagetype) Scan(value any) error { +func (e *CommsMessagetypeemail) Scan(value any) error { switch x := value.(type) { case string: - *e = CommsEmailmessagetype(x) + *e = CommsMessagetypeemail(x) case []byte: - *e = CommsEmailmessagetype(x) + *e = CommsMessagetypeemail(x) case nil: - return fmt.Errorf("cannot nil into CommsEmailmessagetype") + return fmt.Errorf("cannot nil into CommsMessagetypeemail") default: return fmt.Errorf("cannot scan type %T: %v", value, value) } if !e.Valid() { - return fmt.Errorf("invalid CommsEmailmessagetype value: %s", *e) + return fmt.Errorf("invalid CommsMessagetypeemail value: %s", *e) } return nil } -// Enum values for CommsSmsmessagetype +// Enum values for CommsMessagetypetext const ( - CommsSmsmessagetypeReportSubscriptionConfirmation CommsSmsmessagetype = "report-subscription-confirmation" - CommsSmsmessagetypeReportStatusScheduled CommsSmsmessagetype = "report-status-scheduled" - CommsSmsmessagetypeReportStatusComplete CommsSmsmessagetype = "report-status-complete" + CommsMessagetypetextInitialContact CommsMessagetypetext = "initial-contact" + CommsMessagetypetextReportSubscriptionConfirmation CommsMessagetypetext = "report-subscription-confirmation" + CommsMessagetypetextReportStatusScheduled CommsMessagetypetext = "report-status-scheduled" + CommsMessagetypetextReportStatusComplete CommsMessagetypetext = "report-status-complete" ) -func AllCommsSmsmessagetype() []CommsSmsmessagetype { - return []CommsSmsmessagetype{ - CommsSmsmessagetypeReportSubscriptionConfirmation, - CommsSmsmessagetypeReportStatusScheduled, - CommsSmsmessagetypeReportStatusComplete, +func AllCommsMessagetypetext() []CommsMessagetypetext { + return []CommsMessagetypetext{ + CommsMessagetypetextInitialContact, + CommsMessagetypetextReportSubscriptionConfirmation, + CommsMessagetypetextReportStatusScheduled, + CommsMessagetypetextReportStatusComplete, } } -type CommsSmsmessagetype string +type CommsMessagetypetext string -func (e CommsSmsmessagetype) String() string { +func (e CommsMessagetypetext) String() string { return string(e) } -func (e CommsSmsmessagetype) Valid() bool { +func (e CommsMessagetypetext) Valid() bool { switch e { - case CommsSmsmessagetypeReportSubscriptionConfirmation, - CommsSmsmessagetypeReportStatusScheduled, - CommsSmsmessagetypeReportStatusComplete: + case CommsMessagetypetextInitialContact, + CommsMessagetypetextReportSubscriptionConfirmation, + CommsMessagetypetextReportStatusScheduled, + CommsMessagetypetextReportStatusComplete: return true default: return false @@ -302,44 +308,44 @@ func (e CommsSmsmessagetype) Valid() bool { } // useful when testing in other packages -func (e CommsSmsmessagetype) All() []CommsSmsmessagetype { - return AllCommsSmsmessagetype() +func (e CommsMessagetypetext) All() []CommsMessagetypetext { + return AllCommsMessagetypetext() } -func (e CommsSmsmessagetype) MarshalText() ([]byte, error) { +func (e CommsMessagetypetext) MarshalText() ([]byte, error) { return []byte(e), nil } -func (e *CommsSmsmessagetype) UnmarshalText(text []byte) error { +func (e *CommsMessagetypetext) UnmarshalText(text []byte) error { return e.Scan(text) } -func (e CommsSmsmessagetype) MarshalBinary() ([]byte, error) { +func (e CommsMessagetypetext) MarshalBinary() ([]byte, error) { return []byte(e), nil } -func (e *CommsSmsmessagetype) UnmarshalBinary(data []byte) error { +func (e *CommsMessagetypetext) UnmarshalBinary(data []byte) error { return e.Scan(data) } -func (e CommsSmsmessagetype) Value() (driver.Value, error) { +func (e CommsMessagetypetext) Value() (driver.Value, error) { return string(e), nil } -func (e *CommsSmsmessagetype) Scan(value any) error { +func (e *CommsMessagetypetext) Scan(value any) error { switch x := value.(type) { case string: - *e = CommsSmsmessagetype(x) + *e = CommsMessagetypetext(x) case []byte: - *e = CommsSmsmessagetype(x) + *e = CommsMessagetypetext(x) case nil: - return fmt.Errorf("cannot nil into CommsSmsmessagetype") + return fmt.Errorf("cannot nil into CommsMessagetypetext") default: return fmt.Errorf("cannot scan type %T: %v", value, value) } if !e.Valid() { - return fmt.Errorf("invalid CommsSmsmessagetype value: %s", *e) + return fmt.Errorf("invalid CommsMessagetypetext value: %s", *e) } return nil diff --git a/db/factory/bobfactory_context.bob.go b/db/factory/bobfactory_context.bob.go index ae8248ec..ac0e7e05 100644 --- a/db/factory/bobfactory_context.bob.go +++ b/db/factory/bobfactory_context.bob.go @@ -27,15 +27,15 @@ var ( commsEmailLogRelSourcePhoneCtx = newContextual[bool]("comms.email_log.comms.phone.comms.email_log.email_log_source_fkey") // Relationship Contexts for comms.phone - commsPhoneWithParentsCascadingCtx = newContextual[bool]("commsPhoneWithParentsCascading") - commsPhoneRelSourceEmailLogsCtx = newContextual[bool]("comms.email_log.comms.phone.comms.email_log.email_log_source_fkey") - commsPhoneRelDestinationSMSLogsCtx = newContextual[bool]("comms.phone.comms.sms_log.comms.sms_log.sms_log_destination_fkey") - commsPhoneRelSourceSMSLogsCtx = newContextual[bool]("comms.phone.comms.sms_log.comms.sms_log.sms_log_source_fkey") + commsPhoneWithParentsCascadingCtx = newContextual[bool]("commsPhoneWithParentsCascading") + commsPhoneRelSourceEmailLogsCtx = newContextual[bool]("comms.email_log.comms.phone.comms.email_log.email_log_source_fkey") + commsPhoneRelDestinationTextLogsCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_destination_fkey") + commsPhoneRelSourceTextLogsCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_source_fkey") - // Relationship Contexts for comms.sms_log - commsSMSLogWithParentsCascadingCtx = newContextual[bool]("commsSMSLogWithParentsCascading") - commsSMSLogRelDestinationPhoneCtx = newContextual[bool]("comms.phone.comms.sms_log.comms.sms_log.sms_log_destination_fkey") - commsSMSLogRelSourcePhoneCtx = newContextual[bool]("comms.phone.comms.sms_log.comms.sms_log.sms_log_source_fkey") + // Relationship Contexts for comms.text_log + commsTextLogWithParentsCascadingCtx = newContextual[bool]("commsTextLogWithParentsCascading") + commsTextLogRelDestinationPhoneCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_destination_fkey") + commsTextLogRelSourcePhoneCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_source_fkey") // Relationship Contexts for fieldseeker.containerrelate fieldseekerContainerrelateWithParentsCascadingCtx = newContextual[bool]("fieldseekerContainerrelateWithParentsCascading") diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index e74cd371..e49d9b54 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -23,7 +23,7 @@ type Factory struct { baseCommsEmailMods CommsEmailModSlice baseCommsEmailLogMods CommsEmailLogModSlice baseCommsPhoneMods CommsPhoneModSlice - baseCommsSMSLogMods CommsSMSLogModSlice + baseCommsTextLogMods CommsTextLogModSlice baseFieldseekerContainerrelateMods FieldseekerContainerrelateModSlice baseFieldseekerFieldscoutinglogMods FieldseekerFieldscoutinglogModSlice baseFieldseekerHabitatrelateMods FieldseekerHabitatrelateModSlice @@ -213,7 +213,7 @@ func (f *Factory) FromExistingCommsEmailLog(m *models.CommsEmailLog) *CommsEmail o.Created = func() time.Time { return m.Created } o.Destination = func() string { return m.Destination } o.Source = func() string { return m.Source } - o.Type = func() enums.CommsEmailmessagetype { return m.Type } + o.Type = func() enums.CommsMessagetypeemail { return m.Type } ctx := context.Background() if m.R.DestinationEmail != nil { @@ -252,46 +252,46 @@ func (f *Factory) FromExistingCommsPhone(m *models.CommsPhone) *CommsPhoneTempla if len(m.R.SourceEmailLogs) > 0 { CommsPhoneMods.AddExistingSourceEmailLogs(m.R.SourceEmailLogs...).Apply(ctx, o) } - if len(m.R.DestinationSMSLogs) > 0 { - CommsPhoneMods.AddExistingDestinationSMSLogs(m.R.DestinationSMSLogs...).Apply(ctx, o) + if len(m.R.DestinationTextLogs) > 0 { + CommsPhoneMods.AddExistingDestinationTextLogs(m.R.DestinationTextLogs...).Apply(ctx, o) } - if len(m.R.SourceSMSLogs) > 0 { - CommsPhoneMods.AddExistingSourceSMSLogs(m.R.SourceSMSLogs...).Apply(ctx, o) + if len(m.R.SourceTextLogs) > 0 { + CommsPhoneMods.AddExistingSourceTextLogs(m.R.SourceTextLogs...).Apply(ctx, o) } return o } -func (f *Factory) NewCommsSMSLog(mods ...CommsSMSLogMod) *CommsSMSLogTemplate { - return f.NewCommsSMSLogWithContext(context.Background(), mods...) +func (f *Factory) NewCommsTextLog(mods ...CommsTextLogMod) *CommsTextLogTemplate { + return f.NewCommsTextLogWithContext(context.Background(), mods...) } -func (f *Factory) NewCommsSMSLogWithContext(ctx context.Context, mods ...CommsSMSLogMod) *CommsSMSLogTemplate { - o := &CommsSMSLogTemplate{f: f} +func (f *Factory) NewCommsTextLogWithContext(ctx context.Context, mods ...CommsTextLogMod) *CommsTextLogTemplate { + o := &CommsTextLogTemplate{f: f} if f != nil { - f.baseCommsSMSLogMods.Apply(ctx, o) + f.baseCommsTextLogMods.Apply(ctx, o) } - CommsSMSLogModSlice(mods).Apply(ctx, o) + CommsTextLogModSlice(mods).Apply(ctx, o) return o } -func (f *Factory) FromExistingCommsSMSLog(m *models.CommsSMSLog) *CommsSMSLogTemplate { - o := &CommsSMSLogTemplate{f: f, alreadyPersisted: true} +func (f *Factory) FromExistingCommsTextLog(m *models.CommsTextLog) *CommsTextLogTemplate { + o := &CommsTextLogTemplate{f: f, alreadyPersisted: true} o.Created = func() time.Time { return m.Created } o.Destination = func() string { return m.Destination } o.Source = func() string { return m.Source } - o.Type = func() enums.CommsSmsmessagetype { return m.Type } + o.Type = func() enums.CommsMessagetypetext { return m.Type } ctx := context.Background() if m.R.DestinationPhone != nil { - CommsSMSLogMods.WithExistingDestinationPhone(m.R.DestinationPhone).Apply(ctx, o) + CommsTextLogMods.WithExistingDestinationPhone(m.R.DestinationPhone).Apply(ctx, o) } if m.R.SourcePhone != nil { - CommsSMSLogMods.WithExistingSourcePhone(m.R.SourcePhone).Apply(ctx, o) + CommsTextLogMods.WithExistingSourcePhone(m.R.SourcePhone).Apply(ctx, o) } return o @@ -3198,12 +3198,12 @@ func (f *Factory) AddBaseCommsPhoneMod(mods ...CommsPhoneMod) { f.baseCommsPhoneMods = append(f.baseCommsPhoneMods, mods...) } -func (f *Factory) ClearBaseCommsSMSLogMods() { - f.baseCommsSMSLogMods = nil +func (f *Factory) ClearBaseCommsTextLogMods() { + f.baseCommsTextLogMods = nil } -func (f *Factory) AddBaseCommsSMSLogMod(mods ...CommsSMSLogMod) { - f.baseCommsSMSLogMods = append(f.baseCommsSMSLogMods, mods...) +func (f *Factory) AddBaseCommsTextLogMod(mods ...CommsTextLogMod) { + f.baseCommsTextLogMods = append(f.baseCommsTextLogMods, mods...) } func (f *Factory) ClearBaseFieldseekerContainerrelateMods() { diff --git a/db/factory/bobfactory_random.bob.go b/db/factory/bobfactory_random.bob.go index 38bd82d3..120f1c21 100644 --- a/db/factory/bobfactory_random.bob.go +++ b/db/factory/bobfactory_random.bob.go @@ -89,22 +89,22 @@ func random_enums_Audiodatatype(f *faker.Faker, limits ...string) enums.Audiodat return all[f.IntBetween(0, len(all)-1)] } -func random_enums_CommsEmailmessagetype(f *faker.Faker, limits ...string) enums.CommsEmailmessagetype { +func random_enums_CommsMessagetypeemail(f *faker.Faker, limits ...string) enums.CommsMessagetypeemail { if f == nil { f = &defaultFaker } - var e enums.CommsEmailmessagetype + var e enums.CommsMessagetypeemail all := e.All() return all[f.IntBetween(0, len(all)-1)] } -func random_enums_CommsSmsmessagetype(f *faker.Faker, limits ...string) enums.CommsSmsmessagetype { +func random_enums_CommsMessagetypetext(f *faker.Faker, limits ...string) enums.CommsMessagetypetext { if f == nil { f = &defaultFaker } - var e enums.CommsSmsmessagetype + var e enums.CommsMessagetypetext all := e.All() return all[f.IntBetween(0, len(all)-1)] } diff --git a/db/factory/comms.email_log.bob.go b/db/factory/comms.email_log.bob.go index 1a643a2b..07f72bdb 100644 --- a/db/factory/comms.email_log.bob.go +++ b/db/factory/comms.email_log.bob.go @@ -39,7 +39,7 @@ type CommsEmailLogTemplate struct { Created func() time.Time Destination func() string Source func() string - Type func() enums.CommsEmailmessagetype + Type func() enums.CommsMessagetypeemail r commsEmailLogR f *Factory @@ -172,7 +172,7 @@ func ensureCreatableCommsEmailLog(m *models.CommsEmailLogSetter) { m.Source = omit.From(val) } if !(m.Type.IsValue()) { - val := random_enums_CommsEmailmessagetype(nil) + val := random_enums_CommsMessagetypeemail(nil) m.Type = omit.From(val) } } @@ -413,14 +413,14 @@ func (m commsEmailLogMods) RandomSource(f *faker.Faker) CommsEmailLogMod { } // Set the model columns to this value -func (m commsEmailLogMods) Type(val enums.CommsEmailmessagetype) CommsEmailLogMod { +func (m commsEmailLogMods) Type(val enums.CommsMessagetypeemail) CommsEmailLogMod { return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { - o.Type = func() enums.CommsEmailmessagetype { return val } + o.Type = func() enums.CommsMessagetypeemail { return val } }) } // Set the Column from the function -func (m commsEmailLogMods) TypeFunc(f func() enums.CommsEmailmessagetype) CommsEmailLogMod { +func (m commsEmailLogMods) TypeFunc(f func() enums.CommsMessagetypeemail) CommsEmailLogMod { return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { o.Type = f }) @@ -437,8 +437,8 @@ func (m commsEmailLogMods) UnsetType() CommsEmailLogMod { // if faker is nil, a default faker is used func (m commsEmailLogMods) RandomType(f *faker.Faker) CommsEmailLogMod { return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { - o.Type = func() enums.CommsEmailmessagetype { - return random_enums_CommsEmailmessagetype(f) + o.Type = func() enums.CommsMessagetypeemail { + return random_enums_CommsMessagetypeemail(f) } }) } diff --git a/db/factory/comms.phone.bob.go b/db/factory/comms.phone.bob.go index bc823c95..26b8eabf 100644 --- a/db/factory/comms.phone.bob.go +++ b/db/factory/comms.phone.bob.go @@ -44,22 +44,22 @@ type CommsPhoneTemplate struct { } type commsPhoneR struct { - SourceEmailLogs []*commsPhoneRSourceEmailLogsR - DestinationSMSLogs []*commsPhoneRDestinationSMSLogsR - SourceSMSLogs []*commsPhoneRSourceSMSLogsR + SourceEmailLogs []*commsPhoneRSourceEmailLogsR + DestinationTextLogs []*commsPhoneRDestinationTextLogsR + SourceTextLogs []*commsPhoneRSourceTextLogsR } type commsPhoneRSourceEmailLogsR struct { number int o *CommsEmailLogTemplate } -type commsPhoneRDestinationSMSLogsR struct { +type commsPhoneRDestinationTextLogsR struct { number int - o *CommsSMSLogTemplate + o *CommsTextLogTemplate } -type commsPhoneRSourceSMSLogsR struct { +type commsPhoneRSourceTextLogsR struct { number int - o *CommsSMSLogTemplate + o *CommsTextLogTemplate } // Apply mods to the CommsPhoneTemplate @@ -85,9 +85,9 @@ func (t CommsPhoneTemplate) setModelRels(o *models.CommsPhone) { o.R.SourceEmailLogs = rel } - if t.r.DestinationSMSLogs != nil { - rel := models.CommsSMSLogSlice{} - for _, r := range t.r.DestinationSMSLogs { + if t.r.DestinationTextLogs != nil { + rel := models.CommsTextLogSlice{} + for _, r := range t.r.DestinationTextLogs { related := r.o.BuildMany(r.number) for _, rel := range related { rel.Destination = o.E164 // h2 @@ -95,12 +95,12 @@ func (t CommsPhoneTemplate) setModelRels(o *models.CommsPhone) { } rel = append(rel, related...) } - o.R.DestinationSMSLogs = rel + o.R.DestinationTextLogs = rel } - if t.r.SourceSMSLogs != nil { - rel := models.CommsSMSLogSlice{} - for _, r := range t.r.SourceSMSLogs { + if t.r.SourceTextLogs != nil { + rel := models.CommsTextLogSlice{} + for _, r := range t.r.SourceTextLogs { related := r.o.BuildMany(r.number) for _, rel := range related { rel.Source = o.E164 // h2 @@ -108,7 +108,7 @@ func (t CommsPhoneTemplate) setModelRels(o *models.CommsPhone) { } rel = append(rel, related...) } - o.R.SourceSMSLogs = rel + o.R.SourceTextLogs = rel } } @@ -209,19 +209,19 @@ func (o *CommsPhoneTemplate) insertOptRels(ctx context.Context, exec bob.Executo } } - isDestinationSMSLogsDone, _ := commsPhoneRelDestinationSMSLogsCtx.Value(ctx) - if !isDestinationSMSLogsDone && o.r.DestinationSMSLogs != nil { - ctx = commsPhoneRelDestinationSMSLogsCtx.WithValue(ctx, true) - for _, r := range o.r.DestinationSMSLogs { + isDestinationTextLogsDone, _ := commsPhoneRelDestinationTextLogsCtx.Value(ctx) + if !isDestinationTextLogsDone && o.r.DestinationTextLogs != nil { + ctx = commsPhoneRelDestinationTextLogsCtx.WithValue(ctx, true) + for _, r := range o.r.DestinationTextLogs { if r.o.alreadyPersisted { - m.R.DestinationSMSLogs = append(m.R.DestinationSMSLogs, r.o.Build()) + m.R.DestinationTextLogs = append(m.R.DestinationTextLogs, r.o.Build()) } else { rel1, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachDestinationSMSLogs(ctx, exec, rel1...) + err = m.AttachDestinationTextLogs(ctx, exec, rel1...) if err != nil { return err } @@ -229,19 +229,19 @@ func (o *CommsPhoneTemplate) insertOptRels(ctx context.Context, exec bob.Executo } } - isSourceSMSLogsDone, _ := commsPhoneRelSourceSMSLogsCtx.Value(ctx) - if !isSourceSMSLogsDone && o.r.SourceSMSLogs != nil { - ctx = commsPhoneRelSourceSMSLogsCtx.WithValue(ctx, true) - for _, r := range o.r.SourceSMSLogs { + isSourceTextLogsDone, _ := commsPhoneRelSourceTextLogsCtx.Value(ctx) + if !isSourceTextLogsDone && o.r.SourceTextLogs != nil { + ctx = commsPhoneRelSourceTextLogsCtx.WithValue(ctx, true) + for _, r := range o.r.SourceTextLogs { if r.o.alreadyPersisted { - m.R.SourceSMSLogs = append(m.R.SourceSMSLogs, r.o.Build()) + m.R.SourceTextLogs = append(m.R.SourceTextLogs, r.o.Build()) } else { rel2, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachSourceSMSLogs(ctx, exec, rel2...) + err = m.AttachSourceTextLogs(ctx, exec, rel2...) if err != nil { return err } @@ -465,98 +465,98 @@ func (m commsPhoneMods) WithoutSourceEmailLogs() CommsPhoneMod { }) } -func (m commsPhoneMods) WithDestinationSMSLogs(number int, related *CommsSMSLogTemplate) CommsPhoneMod { +func (m commsPhoneMods) WithDestinationTextLogs(number int, related *CommsTextLogTemplate) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - o.r.DestinationSMSLogs = []*commsPhoneRDestinationSMSLogsR{{ + o.r.DestinationTextLogs = []*commsPhoneRDestinationTextLogsR{{ number: number, o: related, }} }) } -func (m commsPhoneMods) WithNewDestinationSMSLogs(number int, mods ...CommsSMSLogMod) CommsPhoneMod { +func (m commsPhoneMods) WithNewDestinationTextLogs(number int, mods ...CommsTextLogMod) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - related := o.f.NewCommsSMSLogWithContext(ctx, mods...) - m.WithDestinationSMSLogs(number, related).Apply(ctx, o) + related := o.f.NewCommsTextLogWithContext(ctx, mods...) + m.WithDestinationTextLogs(number, related).Apply(ctx, o) }) } -func (m commsPhoneMods) AddDestinationSMSLogs(number int, related *CommsSMSLogTemplate) CommsPhoneMod { +func (m commsPhoneMods) AddDestinationTextLogs(number int, related *CommsTextLogTemplate) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - o.r.DestinationSMSLogs = append(o.r.DestinationSMSLogs, &commsPhoneRDestinationSMSLogsR{ + o.r.DestinationTextLogs = append(o.r.DestinationTextLogs, &commsPhoneRDestinationTextLogsR{ number: number, o: related, }) }) } -func (m commsPhoneMods) AddNewDestinationSMSLogs(number int, mods ...CommsSMSLogMod) CommsPhoneMod { +func (m commsPhoneMods) AddNewDestinationTextLogs(number int, mods ...CommsTextLogMod) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - related := o.f.NewCommsSMSLogWithContext(ctx, mods...) - m.AddDestinationSMSLogs(number, related).Apply(ctx, o) + related := o.f.NewCommsTextLogWithContext(ctx, mods...) + m.AddDestinationTextLogs(number, related).Apply(ctx, o) }) } -func (m commsPhoneMods) AddExistingDestinationSMSLogs(existingModels ...*models.CommsSMSLog) CommsPhoneMod { +func (m commsPhoneMods) AddExistingDestinationTextLogs(existingModels ...*models.CommsTextLog) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { for _, em := range existingModels { - o.r.DestinationSMSLogs = append(o.r.DestinationSMSLogs, &commsPhoneRDestinationSMSLogsR{ - o: o.f.FromExistingCommsSMSLog(em), + o.r.DestinationTextLogs = append(o.r.DestinationTextLogs, &commsPhoneRDestinationTextLogsR{ + o: o.f.FromExistingCommsTextLog(em), }) } }) } -func (m commsPhoneMods) WithoutDestinationSMSLogs() CommsPhoneMod { +func (m commsPhoneMods) WithoutDestinationTextLogs() CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - o.r.DestinationSMSLogs = nil + o.r.DestinationTextLogs = nil }) } -func (m commsPhoneMods) WithSourceSMSLogs(number int, related *CommsSMSLogTemplate) CommsPhoneMod { +func (m commsPhoneMods) WithSourceTextLogs(number int, related *CommsTextLogTemplate) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - o.r.SourceSMSLogs = []*commsPhoneRSourceSMSLogsR{{ + o.r.SourceTextLogs = []*commsPhoneRSourceTextLogsR{{ number: number, o: related, }} }) } -func (m commsPhoneMods) WithNewSourceSMSLogs(number int, mods ...CommsSMSLogMod) CommsPhoneMod { +func (m commsPhoneMods) WithNewSourceTextLogs(number int, mods ...CommsTextLogMod) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - related := o.f.NewCommsSMSLogWithContext(ctx, mods...) - m.WithSourceSMSLogs(number, related).Apply(ctx, o) + related := o.f.NewCommsTextLogWithContext(ctx, mods...) + m.WithSourceTextLogs(number, related).Apply(ctx, o) }) } -func (m commsPhoneMods) AddSourceSMSLogs(number int, related *CommsSMSLogTemplate) CommsPhoneMod { +func (m commsPhoneMods) AddSourceTextLogs(number int, related *CommsTextLogTemplate) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - o.r.SourceSMSLogs = append(o.r.SourceSMSLogs, &commsPhoneRSourceSMSLogsR{ + o.r.SourceTextLogs = append(o.r.SourceTextLogs, &commsPhoneRSourceTextLogsR{ number: number, o: related, }) }) } -func (m commsPhoneMods) AddNewSourceSMSLogs(number int, mods ...CommsSMSLogMod) CommsPhoneMod { +func (m commsPhoneMods) AddNewSourceTextLogs(number int, mods ...CommsTextLogMod) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - related := o.f.NewCommsSMSLogWithContext(ctx, mods...) - m.AddSourceSMSLogs(number, related).Apply(ctx, o) + related := o.f.NewCommsTextLogWithContext(ctx, mods...) + m.AddSourceTextLogs(number, related).Apply(ctx, o) }) } -func (m commsPhoneMods) AddExistingSourceSMSLogs(existingModels ...*models.CommsSMSLog) CommsPhoneMod { +func (m commsPhoneMods) AddExistingSourceTextLogs(existingModels ...*models.CommsTextLog) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { for _, em := range existingModels { - o.r.SourceSMSLogs = append(o.r.SourceSMSLogs, &commsPhoneRSourceSMSLogsR{ - o: o.f.FromExistingCommsSMSLog(em), + o.r.SourceTextLogs = append(o.r.SourceTextLogs, &commsPhoneRSourceTextLogsR{ + o: o.f.FromExistingCommsTextLog(em), }) } }) } -func (m commsPhoneMods) WithoutSourceSMSLogs() CommsPhoneMod { +func (m commsPhoneMods) WithoutSourceTextLogs() CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - o.r.SourceSMSLogs = nil + o.r.SourceTextLogs = nil }) } diff --git a/db/factory/comms.sms_log.bob.go b/db/factory/comms.sms_log.bob.go deleted file mode 100644 index 516fe9ff..00000000 --- a/db/factory/comms.sms_log.bob.go +++ /dev/null @@ -1,523 +0,0 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. -// This file is meant to be re-generated in place and/or deleted at any time. - -package factory - -import ( - "context" - "testing" - "time" - - enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" - models "github.com/Gleipnir-Technology/nidus-sync/db/models" - "github.com/aarondl/opt/omit" - "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" -) - -type CommsSMSLogMod interface { - Apply(context.Context, *CommsSMSLogTemplate) -} - -type CommsSMSLogModFunc func(context.Context, *CommsSMSLogTemplate) - -func (f CommsSMSLogModFunc) Apply(ctx context.Context, n *CommsSMSLogTemplate) { - f(ctx, n) -} - -type CommsSMSLogModSlice []CommsSMSLogMod - -func (mods CommsSMSLogModSlice) Apply(ctx context.Context, n *CommsSMSLogTemplate) { - for _, f := range mods { - f.Apply(ctx, n) - } -} - -// CommsSMSLogTemplate is an object representing the database table. -// all columns are optional and should be set by mods -type CommsSMSLogTemplate struct { - Created func() time.Time - Destination func() string - Source func() string - Type func() enums.CommsSmsmessagetype - - r commsSMSLogR - f *Factory - - alreadyPersisted bool -} - -type commsSMSLogR struct { - DestinationPhone *commsSMSLogRDestinationPhoneR - SourcePhone *commsSMSLogRSourcePhoneR -} - -type commsSMSLogRDestinationPhoneR struct { - o *CommsPhoneTemplate -} -type commsSMSLogRSourcePhoneR struct { - o *CommsPhoneTemplate -} - -// Apply mods to the CommsSMSLogTemplate -func (o *CommsSMSLogTemplate) Apply(ctx context.Context, mods ...CommsSMSLogMod) { - for _, mod := range mods { - mod.Apply(ctx, o) - } -} - -// setModelRels creates and sets the relationships on *models.CommsSMSLog -// according to the relationships in the template. Nothing is inserted into the db -func (t CommsSMSLogTemplate) setModelRels(o *models.CommsSMSLog) { - if t.r.DestinationPhone != nil { - rel := t.r.DestinationPhone.o.Build() - rel.R.DestinationSMSLogs = append(rel.R.DestinationSMSLogs, o) - o.Destination = rel.E164 // h2 - o.R.DestinationPhone = rel - } - - if t.r.SourcePhone != nil { - rel := t.r.SourcePhone.o.Build() - rel.R.SourceSMSLogs = append(rel.R.SourceSMSLogs, o) - o.Source = rel.E164 // h2 - o.R.SourcePhone = rel - } -} - -// BuildSetter returns an *models.CommsSMSLogSetter -// this does nothing with the relationship templates -func (o CommsSMSLogTemplate) BuildSetter() *models.CommsSMSLogSetter { - m := &models.CommsSMSLogSetter{} - - if o.Created != nil { - val := o.Created() - m.Created = omit.From(val) - } - if o.Destination != nil { - val := o.Destination() - m.Destination = omit.From(val) - } - if o.Source != nil { - val := o.Source() - m.Source = omit.From(val) - } - if o.Type != nil { - val := o.Type() - m.Type = omit.From(val) - } - - return m -} - -// BuildManySetter returns an []*models.CommsSMSLogSetter -// this does nothing with the relationship templates -func (o CommsSMSLogTemplate) BuildManySetter(number int) []*models.CommsSMSLogSetter { - m := make([]*models.CommsSMSLogSetter, number) - - for i := range m { - m[i] = o.BuildSetter() - } - - return m -} - -// Build returns an *models.CommsSMSLog -// Related objects are also created and placed in the .R field -// NOTE: Objects are not inserted into the database. Use CommsSMSLogTemplate.Create -func (o CommsSMSLogTemplate) Build() *models.CommsSMSLog { - m := &models.CommsSMSLog{} - - if o.Created != nil { - m.Created = o.Created() - } - if o.Destination != nil { - m.Destination = o.Destination() - } - if o.Source != nil { - m.Source = o.Source() - } - if o.Type != nil { - m.Type = o.Type() - } - - o.setModelRels(m) - - return m -} - -// BuildMany returns an models.CommsSMSLogSlice -// Related objects are also created and placed in the .R field -// NOTE: Objects are not inserted into the database. Use CommsSMSLogTemplate.CreateMany -func (o CommsSMSLogTemplate) BuildMany(number int) models.CommsSMSLogSlice { - m := make(models.CommsSMSLogSlice, number) - - for i := range m { - m[i] = o.Build() - } - - return m -} - -func ensureCreatableCommsSMSLog(m *models.CommsSMSLogSetter) { - if !(m.Created.IsValue()) { - val := random_time_Time(nil) - m.Created = omit.From(val) - } - if !(m.Destination.IsValue()) { - val := random_string(nil) - m.Destination = omit.From(val) - } - if !(m.Source.IsValue()) { - val := random_string(nil) - m.Source = omit.From(val) - } - if !(m.Type.IsValue()) { - val := random_enums_CommsSmsmessagetype(nil) - m.Type = omit.From(val) - } -} - -// insertOptRels creates and inserts any optional the relationships on *models.CommsSMSLog -// according to the relationships in the template. -// any required relationship should have already exist on the model -func (o *CommsSMSLogTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsSMSLog) error { - var err error - - return err -} - -// Create builds a commsSMSLog and inserts it into the database -// Relations objects are also inserted and placed in the .R field -func (o *CommsSMSLogTemplate) Create(ctx context.Context, exec bob.Executor) (*models.CommsSMSLog, error) { - var err error - opt := o.BuildSetter() - ensureCreatableCommsSMSLog(opt) - - if o.r.DestinationPhone == nil { - CommsSMSLogMods.WithNewDestinationPhone().Apply(ctx, o) - } - - var rel0 *models.CommsPhone - - if o.r.DestinationPhone.o.alreadyPersisted { - rel0 = o.r.DestinationPhone.o.Build() - } else { - rel0, err = o.r.DestinationPhone.o.Create(ctx, exec) - if err != nil { - return nil, err - } - } - - opt.Destination = omit.From(rel0.E164) - - if o.r.SourcePhone == nil { - CommsSMSLogMods.WithNewSourcePhone().Apply(ctx, o) - } - - var rel1 *models.CommsPhone - - if o.r.SourcePhone.o.alreadyPersisted { - rel1 = o.r.SourcePhone.o.Build() - } else { - rel1, err = o.r.SourcePhone.o.Create(ctx, exec) - if err != nil { - return nil, err - } - } - - opt.Source = omit.From(rel1.E164) - - m, err := models.CommsSMSLogs.Insert(opt).One(ctx, exec) - if err != nil { - return nil, err - } - - m.R.DestinationPhone = rel0 - m.R.SourcePhone = rel1 - - if err := o.insertOptRels(ctx, exec, m); err != nil { - return nil, err - } - return m, err -} - -// MustCreate builds a commsSMSLog and inserts it into the database -// Relations objects are also inserted and placed in the .R field -// panics if an error occurs -func (o *CommsSMSLogTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.CommsSMSLog { - m, err := o.Create(ctx, exec) - if err != nil { - panic(err) - } - return m -} - -// CreateOrFail builds a commsSMSLog 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 *CommsSMSLogTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.CommsSMSLog { - tb.Helper() - m, err := o.Create(ctx, exec) - if err != nil { - tb.Fatal(err) - return nil - } - return m -} - -// CreateMany builds multiple commsSMSLogs and inserts them into the database -// Relations objects are also inserted and placed in the .R field -func (o CommsSMSLogTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.CommsSMSLogSlice, error) { - var err error - m := make(models.CommsSMSLogSlice, number) - - for i := range m { - m[i], err = o.Create(ctx, exec) - if err != nil { - return nil, err - } - } - - return m, nil -} - -// MustCreateMany builds multiple commsSMSLogs and inserts them into the database -// Relations objects are also inserted and placed in the .R field -// panics if an error occurs -func (o CommsSMSLogTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.CommsSMSLogSlice { - m, err := o.CreateMany(ctx, exec, number) - if err != nil { - panic(err) - } - return m -} - -// CreateManyOrFail builds multiple commsSMSLogs 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 CommsSMSLogTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.CommsSMSLogSlice { - tb.Helper() - m, err := o.CreateMany(ctx, exec, number) - if err != nil { - tb.Fatal(err) - return nil - } - return m -} - -// CommsSMSLog has methods that act as mods for the CommsSMSLogTemplate -var CommsSMSLogMods commsSMSLogMods - -type commsSMSLogMods struct{} - -func (m commsSMSLogMods) RandomizeAllColumns(f *faker.Faker) CommsSMSLogMod { - return CommsSMSLogModSlice{ - CommsSMSLogMods.RandomCreated(f), - CommsSMSLogMods.RandomDestination(f), - CommsSMSLogMods.RandomSource(f), - CommsSMSLogMods.RandomType(f), - } -} - -// Set the model columns to this value -func (m commsSMSLogMods) Created(val time.Time) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Created = func() time.Time { return val } - }) -} - -// Set the Column from the function -func (m commsSMSLogMods) CreatedFunc(f func() time.Time) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Created = f - }) -} - -// Clear any values for the column -func (m commsSMSLogMods) UnsetCreated() CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Created = nil - }) -} - -// Generates a random value for the column using the given faker -// if faker is nil, a default faker is used -func (m commsSMSLogMods) RandomCreated(f *faker.Faker) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Created = func() time.Time { - return random_time_Time(f) - } - }) -} - -// Set the model columns to this value -func (m commsSMSLogMods) Destination(val string) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Destination = func() string { return val } - }) -} - -// Set the Column from the function -func (m commsSMSLogMods) DestinationFunc(f func() string) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Destination = f - }) -} - -// Clear any values for the column -func (m commsSMSLogMods) UnsetDestination() CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Destination = nil - }) -} - -// Generates a random value for the column using the given faker -// if faker is nil, a default faker is used -func (m commsSMSLogMods) RandomDestination(f *faker.Faker) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Destination = func() string { - return random_string(f) - } - }) -} - -// Set the model columns to this value -func (m commsSMSLogMods) Source(val string) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Source = func() string { return val } - }) -} - -// Set the Column from the function -func (m commsSMSLogMods) SourceFunc(f func() string) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Source = f - }) -} - -// Clear any values for the column -func (m commsSMSLogMods) UnsetSource() CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Source = nil - }) -} - -// Generates a random value for the column using the given faker -// if faker is nil, a default faker is used -func (m commsSMSLogMods) RandomSource(f *faker.Faker) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Source = func() string { - return random_string(f) - } - }) -} - -// Set the model columns to this value -func (m commsSMSLogMods) Type(val enums.CommsSmsmessagetype) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Type = func() enums.CommsSmsmessagetype { return val } - }) -} - -// Set the Column from the function -func (m commsSMSLogMods) TypeFunc(f func() enums.CommsSmsmessagetype) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Type = f - }) -} - -// Clear any values for the column -func (m commsSMSLogMods) UnsetType() CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Type = nil - }) -} - -// Generates a random value for the column using the given faker -// if faker is nil, a default faker is used -func (m commsSMSLogMods) RandomType(f *faker.Faker) CommsSMSLogMod { - return CommsSMSLogModFunc(func(_ context.Context, o *CommsSMSLogTemplate) { - o.Type = func() enums.CommsSmsmessagetype { - return random_enums_CommsSmsmessagetype(f) - } - }) -} - -func (m commsSMSLogMods) WithParentsCascading() CommsSMSLogMod { - return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { - if isDone, _ := commsSMSLogWithParentsCascadingCtx.Value(ctx); isDone { - return - } - ctx = commsSMSLogWithParentsCascadingCtx.WithValue(ctx, true) - { - - related := o.f.NewCommsPhoneWithContext(ctx, CommsPhoneMods.WithParentsCascading()) - m.WithDestinationPhone(related).Apply(ctx, o) - } - { - - related := o.f.NewCommsPhoneWithContext(ctx, CommsPhoneMods.WithParentsCascading()) - m.WithSourcePhone(related).Apply(ctx, o) - } - }) -} - -func (m commsSMSLogMods) WithDestinationPhone(rel *CommsPhoneTemplate) CommsSMSLogMod { - return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { - o.r.DestinationPhone = &commsSMSLogRDestinationPhoneR{ - o: rel, - } - }) -} - -func (m commsSMSLogMods) WithNewDestinationPhone(mods ...CommsPhoneMod) CommsSMSLogMod { - return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { - related := o.f.NewCommsPhoneWithContext(ctx, mods...) - - m.WithDestinationPhone(related).Apply(ctx, o) - }) -} - -func (m commsSMSLogMods) WithExistingDestinationPhone(em *models.CommsPhone) CommsSMSLogMod { - return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { - o.r.DestinationPhone = &commsSMSLogRDestinationPhoneR{ - o: o.f.FromExistingCommsPhone(em), - } - }) -} - -func (m commsSMSLogMods) WithoutDestinationPhone() CommsSMSLogMod { - return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { - o.r.DestinationPhone = nil - }) -} - -func (m commsSMSLogMods) WithSourcePhone(rel *CommsPhoneTemplate) CommsSMSLogMod { - return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { - o.r.SourcePhone = &commsSMSLogRSourcePhoneR{ - o: rel, - } - }) -} - -func (m commsSMSLogMods) WithNewSourcePhone(mods ...CommsPhoneMod) CommsSMSLogMod { - return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { - related := o.f.NewCommsPhoneWithContext(ctx, mods...) - - m.WithSourcePhone(related).Apply(ctx, o) - }) -} - -func (m commsSMSLogMods) WithExistingSourcePhone(em *models.CommsPhone) CommsSMSLogMod { - return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { - o.r.SourcePhone = &commsSMSLogRSourcePhoneR{ - o: o.f.FromExistingCommsPhone(em), - } - }) -} - -func (m commsSMSLogMods) WithoutSourcePhone() CommsSMSLogMod { - return CommsSMSLogModFunc(func(ctx context.Context, o *CommsSMSLogTemplate) { - o.r.SourcePhone = nil - }) -} diff --git a/db/factory/comms.text_log.bob.go b/db/factory/comms.text_log.bob.go new file mode 100644 index 00000000..91d5a2f3 --- /dev/null +++ b/db/factory/comms.text_log.bob.go @@ -0,0 +1,523 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/omit" + "github.com/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type CommsTextLogMod interface { + Apply(context.Context, *CommsTextLogTemplate) +} + +type CommsTextLogModFunc func(context.Context, *CommsTextLogTemplate) + +func (f CommsTextLogModFunc) Apply(ctx context.Context, n *CommsTextLogTemplate) { + f(ctx, n) +} + +type CommsTextLogModSlice []CommsTextLogMod + +func (mods CommsTextLogModSlice) Apply(ctx context.Context, n *CommsTextLogTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// CommsTextLogTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type CommsTextLogTemplate struct { + Created func() time.Time + Destination func() string + Source func() string + Type func() enums.CommsMessagetypetext + + r commsTextLogR + f *Factory + + alreadyPersisted bool +} + +type commsTextLogR struct { + DestinationPhone *commsTextLogRDestinationPhoneR + SourcePhone *commsTextLogRSourcePhoneR +} + +type commsTextLogRDestinationPhoneR struct { + o *CommsPhoneTemplate +} +type commsTextLogRSourcePhoneR struct { + o *CommsPhoneTemplate +} + +// Apply mods to the CommsTextLogTemplate +func (o *CommsTextLogTemplate) Apply(ctx context.Context, mods ...CommsTextLogMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.CommsTextLog +// according to the relationships in the template. Nothing is inserted into the db +func (t CommsTextLogTemplate) setModelRels(o *models.CommsTextLog) { + if t.r.DestinationPhone != nil { + rel := t.r.DestinationPhone.o.Build() + rel.R.DestinationTextLogs = append(rel.R.DestinationTextLogs, o) + o.Destination = rel.E164 // h2 + o.R.DestinationPhone = rel + } + + if t.r.SourcePhone != nil { + rel := t.r.SourcePhone.o.Build() + rel.R.SourceTextLogs = append(rel.R.SourceTextLogs, o) + o.Source = rel.E164 // h2 + o.R.SourcePhone = rel + } +} + +// BuildSetter returns an *models.CommsTextLogSetter +// this does nothing with the relationship templates +func (o CommsTextLogTemplate) BuildSetter() *models.CommsTextLogSetter { + m := &models.CommsTextLogSetter{} + + if o.Created != nil { + val := o.Created() + m.Created = omit.From(val) + } + if o.Destination != nil { + val := o.Destination() + m.Destination = omit.From(val) + } + if o.Source != nil { + val := o.Source() + m.Source = omit.From(val) + } + if o.Type != nil { + val := o.Type() + m.Type = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.CommsTextLogSetter +// this does nothing with the relationship templates +func (o CommsTextLogTemplate) BuildManySetter(number int) []*models.CommsTextLogSetter { + m := make([]*models.CommsTextLogSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.CommsTextLog +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsTextLogTemplate.Create +func (o CommsTextLogTemplate) Build() *models.CommsTextLog { + m := &models.CommsTextLog{} + + if o.Created != nil { + m.Created = o.Created() + } + if o.Destination != nil { + m.Destination = o.Destination() + } + if o.Source != nil { + m.Source = o.Source() + } + if o.Type != nil { + m.Type = o.Type() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.CommsTextLogSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsTextLogTemplate.CreateMany +func (o CommsTextLogTemplate) BuildMany(number int) models.CommsTextLogSlice { + m := make(models.CommsTextLogSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableCommsTextLog(m *models.CommsTextLogSetter) { + if !(m.Created.IsValue()) { + val := random_time_Time(nil) + m.Created = omit.From(val) + } + if !(m.Destination.IsValue()) { + val := random_string(nil) + m.Destination = omit.From(val) + } + if !(m.Source.IsValue()) { + val := random_string(nil) + m.Source = omit.From(val) + } + if !(m.Type.IsValue()) { + val := random_enums_CommsMessagetypetext(nil) + m.Type = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.CommsTextLog +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *CommsTextLogTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsTextLog) error { + var err error + + return err +} + +// Create builds a commsTextLog and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *CommsTextLogTemplate) Create(ctx context.Context, exec bob.Executor) (*models.CommsTextLog, error) { + var err error + opt := o.BuildSetter() + ensureCreatableCommsTextLog(opt) + + if o.r.DestinationPhone == nil { + CommsTextLogMods.WithNewDestinationPhone().Apply(ctx, o) + } + + var rel0 *models.CommsPhone + + if o.r.DestinationPhone.o.alreadyPersisted { + rel0 = o.r.DestinationPhone.o.Build() + } else { + rel0, err = o.r.DestinationPhone.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.Destination = omit.From(rel0.E164) + + if o.r.SourcePhone == nil { + CommsTextLogMods.WithNewSourcePhone().Apply(ctx, o) + } + + var rel1 *models.CommsPhone + + if o.r.SourcePhone.o.alreadyPersisted { + rel1 = o.r.SourcePhone.o.Build() + } else { + rel1, err = o.r.SourcePhone.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.Source = omit.From(rel1.E164) + + m, err := models.CommsTextLogs.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + m.R.DestinationPhone = rel0 + m.R.SourcePhone = rel1 + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a commsTextLog and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *CommsTextLogTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.CommsTextLog { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a commsTextLog 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 *CommsTextLogTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.CommsTextLog { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple commsTextLogs and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o CommsTextLogTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.CommsTextLogSlice, error) { + var err error + m := make(models.CommsTextLogSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple commsTextLogs and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o CommsTextLogTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.CommsTextLogSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple commsTextLogs 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 CommsTextLogTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.CommsTextLogSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CommsTextLog has methods that act as mods for the CommsTextLogTemplate +var CommsTextLogMods commsTextLogMods + +type commsTextLogMods struct{} + +func (m commsTextLogMods) RandomizeAllColumns(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModSlice{ + CommsTextLogMods.RandomCreated(f), + CommsTextLogMods.RandomDestination(f), + CommsTextLogMods.RandomSource(f), + CommsTextLogMods.RandomType(f), + } +} + +// Set the model columns to this value +func (m commsTextLogMods) Created(val time.Time) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Created = func() time.Time { return val } + }) +} + +// Set the Column from the function +func (m commsTextLogMods) CreatedFunc(f func() time.Time) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Created = f + }) +} + +// Clear any values for the column +func (m commsTextLogMods) UnsetCreated() CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Created = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextLogMods) RandomCreated(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Created = func() time.Time { + return random_time_Time(f) + } + }) +} + +// Set the model columns to this value +func (m commsTextLogMods) Destination(val string) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Destination = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsTextLogMods) DestinationFunc(f func() string) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Destination = f + }) +} + +// Clear any values for the column +func (m commsTextLogMods) UnsetDestination() CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Destination = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextLogMods) RandomDestination(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Destination = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsTextLogMods) Source(val string) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Source = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsTextLogMods) SourceFunc(f func() string) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Source = f + }) +} + +// Clear any values for the column +func (m commsTextLogMods) UnsetSource() CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Source = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextLogMods) RandomSource(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Source = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsTextLogMods) Type(val enums.CommsMessagetypetext) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Type = func() enums.CommsMessagetypetext { return val } + }) +} + +// Set the Column from the function +func (m commsTextLogMods) TypeFunc(f func() enums.CommsMessagetypetext) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Type = f + }) +} + +// Clear any values for the column +func (m commsTextLogMods) UnsetType() CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Type = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextLogMods) RandomType(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Type = func() enums.CommsMessagetypetext { + return random_enums_CommsMessagetypetext(f) + } + }) +} + +func (m commsTextLogMods) WithParentsCascading() CommsTextLogMod { + return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { + if isDone, _ := commsTextLogWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = commsTextLogWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewCommsPhoneWithContext(ctx, CommsPhoneMods.WithParentsCascading()) + m.WithDestinationPhone(related).Apply(ctx, o) + } + { + + related := o.f.NewCommsPhoneWithContext(ctx, CommsPhoneMods.WithParentsCascading()) + m.WithSourcePhone(related).Apply(ctx, o) + } + }) +} + +func (m commsTextLogMods) WithDestinationPhone(rel *CommsPhoneTemplate) CommsTextLogMod { + return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { + o.r.DestinationPhone = &commsTextLogRDestinationPhoneR{ + o: rel, + } + }) +} + +func (m commsTextLogMods) WithNewDestinationPhone(mods ...CommsPhoneMod) CommsTextLogMod { + return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { + related := o.f.NewCommsPhoneWithContext(ctx, mods...) + + m.WithDestinationPhone(related).Apply(ctx, o) + }) +} + +func (m commsTextLogMods) WithExistingDestinationPhone(em *models.CommsPhone) CommsTextLogMod { + return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { + o.r.DestinationPhone = &commsTextLogRDestinationPhoneR{ + o: o.f.FromExistingCommsPhone(em), + } + }) +} + +func (m commsTextLogMods) WithoutDestinationPhone() CommsTextLogMod { + return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { + o.r.DestinationPhone = nil + }) +} + +func (m commsTextLogMods) WithSourcePhone(rel *CommsPhoneTemplate) CommsTextLogMod { + return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { + o.r.SourcePhone = &commsTextLogRSourcePhoneR{ + o: rel, + } + }) +} + +func (m commsTextLogMods) WithNewSourcePhone(mods ...CommsPhoneMod) CommsTextLogMod { + return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { + related := o.f.NewCommsPhoneWithContext(ctx, mods...) + + m.WithSourcePhone(related).Apply(ctx, o) + }) +} + +func (m commsTextLogMods) WithExistingSourcePhone(em *models.CommsPhone) CommsTextLogMod { + return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { + o.r.SourcePhone = &commsTextLogRSourcePhoneR{ + o: o.f.FromExistingCommsPhone(em), + } + }) +} + +func (m commsTextLogMods) WithoutSourcePhone() CommsTextLogMod { + return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { + o.r.SourcePhone = nil + }) +} diff --git a/db/migrations/00036_comms.sql b/db/migrations/00036_comms.sql index 1ab2697a..4c4b3740 100644 --- a/db/migrations/00036_comms.sql +++ b/db/migrations/00036_comms.sql @@ -1,11 +1,13 @@ -- +goose Up CREATE SCHEMA comms; -CREATE TYPE comms.SMSMessageType AS ENUM ( +CREATE TYPE comms.MessageTypeText AS ENUM ( + 'initial-contact', 'report-subscription-confirmation', 'report-status-scheduled', 'report-status-complete' ); -CREATE TYPE comms.EmailMessageType AS ENUM ( +CREATE TYPE comms.MessageTypeEmail AS ENUM ( + 'initial-contact', 'report-subscription-confirmation', 'report-status-scheduled', 'report-status-complete' @@ -15,11 +17,11 @@ CREATE TABLE comms.phone ( is_subscribed BOOLEAN NOT NULL, PRIMARY KEY (e164) ); -CREATE TABLE comms.sms_log ( +CREATE TABLE comms.text_log ( created TIMESTAMP WITHOUT TIME ZONE NOT NULL, destination TEXT NOT NULL REFERENCES comms.phone(e164), source TEXT NOT NULL REFERENCES comms.phone(e164), - type comms.SMSMessageType NOT NULL, + type comms.MessageTypeText NOT NULL, PRIMARY KEY (destination, source, type) ); CREATE TABLE comms.email ( @@ -32,6 +34,14 @@ CREATE TABLE comms.email_log ( created TIMESTAMP WITHOUT TIME ZONE NOT NULL, destination TEXT NOT NULL REFERENCES comms.email(address), source TEXT NOT NULL REFERENCES comms.phone(e164), - type comms.EmailMessageType NOT NULL, + type comms.MessageTypeEmail NOT NULL, PRIMARY KEY(destination, source, type) ); +-- +goose Down +DROP TABLE comms.email_log; +DROP TABLE comms.email; +DROP TABLE comms.text_log; +DROP TABLE comms.phone; +DROP TYPE comms.MessageTypeEmail; +DROP TYPE comms.MessageTypeText; +DROP SCHEMA comms; diff --git a/db/models/bob_joins.bob.go b/db/models/bob_joins.bob.go index 8df782cc..e3a3c0af 100644 --- a/db/models/bob_joins.bob.go +++ b/db/models/bob_joins.bob.go @@ -37,7 +37,7 @@ type joins[Q dialect.Joinable] struct { CommsEmails joinSet[commsEmailJoins[Q]] CommsEmailLogs joinSet[commsEmailLogJoins[Q]] CommsPhones joinSet[commsPhoneJoins[Q]] - CommsSMSLogs joinSet[commsSMSLogJoins[Q]] + CommsTextLogs joinSet[commsTextLogJoins[Q]] FieldseekerContainerrelates joinSet[fieldseekerContainerrelateJoins[Q]] FieldseekerFieldscoutinglogs joinSet[fieldseekerFieldscoutinglogJoins[Q]] FieldseekerHabitatrelates joinSet[fieldseekerHabitatrelateJoins[Q]] @@ -101,7 +101,7 @@ func getJoins[Q dialect.Joinable]() joins[Q] { CommsEmails: buildJoinSet[commsEmailJoins[Q]](CommsEmails.Columns, buildCommsEmailJoins), CommsEmailLogs: buildJoinSet[commsEmailLogJoins[Q]](CommsEmailLogs.Columns, buildCommsEmailLogJoins), CommsPhones: buildJoinSet[commsPhoneJoins[Q]](CommsPhones.Columns, buildCommsPhoneJoins), - CommsSMSLogs: buildJoinSet[commsSMSLogJoins[Q]](CommsSMSLogs.Columns, buildCommsSMSLogJoins), + CommsTextLogs: buildJoinSet[commsTextLogJoins[Q]](CommsTextLogs.Columns, buildCommsTextLogJoins), FieldseekerContainerrelates: buildJoinSet[fieldseekerContainerrelateJoins[Q]](FieldseekerContainerrelates.Columns, buildFieldseekerContainerrelateJoins), FieldseekerFieldscoutinglogs: buildJoinSet[fieldseekerFieldscoutinglogJoins[Q]](FieldseekerFieldscoutinglogs.Columns, buildFieldseekerFieldscoutinglogJoins), FieldseekerHabitatrelates: buildJoinSet[fieldseekerHabitatrelateJoins[Q]](FieldseekerHabitatrelates.Columns, buildFieldseekerHabitatrelateJoins), diff --git a/db/models/bob_loaders.bob.go b/db/models/bob_loaders.bob.go index 7d058f99..405ee6d6 100644 --- a/db/models/bob_loaders.bob.go +++ b/db/models/bob_loaders.bob.go @@ -22,7 +22,7 @@ type preloaders struct { CommsEmail commsEmailPreloader CommsEmailLog commsEmailLogPreloader CommsPhone commsPhonePreloader - CommsSMSLog commsSMSLogPreloader + CommsTextLog commsTextLogPreloader FieldseekerContainerrelate fieldseekerContainerrelatePreloader FieldseekerFieldscoutinglog fieldseekerFieldscoutinglogPreloader FieldseekerHabitatrelate fieldseekerHabitatrelatePreloader @@ -78,7 +78,7 @@ func getPreloaders() preloaders { CommsEmail: buildCommsEmailPreloader(), CommsEmailLog: buildCommsEmailLogPreloader(), CommsPhone: buildCommsPhonePreloader(), - CommsSMSLog: buildCommsSMSLogPreloader(), + CommsTextLog: buildCommsTextLogPreloader(), FieldseekerContainerrelate: buildFieldseekerContainerrelatePreloader(), FieldseekerFieldscoutinglog: buildFieldseekerFieldscoutinglogPreloader(), FieldseekerHabitatrelate: buildFieldseekerHabitatrelatePreloader(), @@ -140,7 +140,7 @@ type thenLoaders[Q orm.Loadable] struct { CommsEmail commsEmailThenLoader[Q] CommsEmailLog commsEmailLogThenLoader[Q] CommsPhone commsPhoneThenLoader[Q] - CommsSMSLog commsSMSLogThenLoader[Q] + CommsTextLog commsTextLogThenLoader[Q] FieldseekerContainerrelate fieldseekerContainerrelateThenLoader[Q] FieldseekerFieldscoutinglog fieldseekerFieldscoutinglogThenLoader[Q] FieldseekerHabitatrelate fieldseekerHabitatrelateThenLoader[Q] @@ -196,7 +196,7 @@ func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] { CommsEmail: buildCommsEmailThenLoader[Q](), CommsEmailLog: buildCommsEmailLogThenLoader[Q](), CommsPhone: buildCommsPhoneThenLoader[Q](), - CommsSMSLog: buildCommsSMSLogThenLoader[Q](), + CommsTextLog: buildCommsTextLogThenLoader[Q](), FieldseekerContainerrelate: buildFieldseekerContainerrelateThenLoader[Q](), FieldseekerFieldscoutinglog: buildFieldseekerFieldscoutinglogThenLoader[Q](), FieldseekerHabitatrelate: buildFieldseekerHabitatrelateThenLoader[Q](), diff --git a/db/models/bob_where.bob.go b/db/models/bob_where.bob.go index 69ddc8f4..9ce4788e 100644 --- a/db/models/bob_where.bob.go +++ b/db/models/bob_where.bob.go @@ -22,7 +22,7 @@ func Where[Q psql.Filterable]() struct { CommsEmails commsEmailWhere[Q] CommsEmailLogs commsEmailLogWhere[Q] CommsPhones commsPhoneWhere[Q] - CommsSMSLogs commsSMSLogWhere[Q] + CommsTextLogs commsTextLogWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] FieldseekerHabitatrelates fieldseekerHabitatrelateWhere[Q] @@ -85,7 +85,7 @@ func Where[Q psql.Filterable]() struct { CommsEmails commsEmailWhere[Q] CommsEmailLogs commsEmailLogWhere[Q] CommsPhones commsPhoneWhere[Q] - CommsSMSLogs commsSMSLogWhere[Q] + CommsTextLogs commsTextLogWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] FieldseekerHabitatrelates fieldseekerHabitatrelateWhere[Q] @@ -147,7 +147,7 @@ func Where[Q psql.Filterable]() struct { CommsEmails: buildCommsEmailWhere[Q](CommsEmails.Columns), CommsEmailLogs: buildCommsEmailLogWhere[Q](CommsEmailLogs.Columns), CommsPhones: buildCommsPhoneWhere[Q](CommsPhones.Columns), - CommsSMSLogs: buildCommsSMSLogWhere[Q](CommsSMSLogs.Columns), + CommsTextLogs: buildCommsTextLogWhere[Q](CommsTextLogs.Columns), FieldseekerContainerrelates: buildFieldseekerContainerrelateWhere[Q](FieldseekerContainerrelates.Columns), FieldseekerFieldscoutinglogs: buildFieldseekerFieldscoutinglogWhere[Q](FieldseekerFieldscoutinglogs.Columns), FieldseekerHabitatrelates: buildFieldseekerHabitatrelateWhere[Q](FieldseekerHabitatrelates.Columns), diff --git a/db/models/comms.email_log.bob.go b/db/models/comms.email_log.bob.go index 74cec9a0..fb8edb87 100644 --- a/db/models/comms.email_log.bob.go +++ b/db/models/comms.email_log.bob.go @@ -28,7 +28,7 @@ type CommsEmailLog struct { Created time.Time `db:"created" ` Destination string `db:"destination,pk" ` Source string `db:"source,pk" ` - Type enums.CommsEmailmessagetype `db:"type,pk" ` + Type enums.CommsMessagetypeemail `db:"type,pk" ` R commsEmailLogR `db:"-" ` } @@ -86,7 +86,7 @@ type CommsEmailLogSetter struct { Created omit.Val[time.Time] `db:"created" ` Destination omit.Val[string] `db:"destination,pk" ` Source omit.Val[string] `db:"source,pk" ` - Type omit.Val[enums.CommsEmailmessagetype] `db:"type,pk" ` + Type omit.Val[enums.CommsMessagetypeemail] `db:"type,pk" ` } func (s CommsEmailLogSetter) SetColumns() []string { @@ -196,7 +196,7 @@ func (s CommsEmailLogSetter) Expressions(prefix ...string) []bob.Expression { // FindCommsEmailLog retrieves a single record by primary key // If cols is empty Find will return all columns. -func FindCommsEmailLog(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsEmailmessagetype, cols ...string) (*CommsEmailLog, error) { +func FindCommsEmailLog(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsMessagetypeemail, cols ...string) (*CommsEmailLog, error) { if len(cols) == 0 { return CommsEmailLogs.Query( sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), @@ -214,7 +214,7 @@ func FindCommsEmailLog(ctx context.Context, exec bob.Executor, DestinationPK str } // CommsEmailLogExists checks the presence of a single record by primary key -func CommsEmailLogExists(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsEmailmessagetype) (bool, error) { +func CommsEmailLogExists(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsMessagetypeemail) (bool, error) { return CommsEmailLogs.Query( sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), sm.Where(CommsEmailLogs.Columns.Source.EQ(psql.Arg(SourcePK))), @@ -583,7 +583,7 @@ type commsEmailLogWhere[Q psql.Filterable] struct { Created psql.WhereMod[Q, time.Time] Destination psql.WhereMod[Q, string] Source psql.WhereMod[Q, string] - Type psql.WhereMod[Q, enums.CommsEmailmessagetype] + Type psql.WhereMod[Q, enums.CommsMessagetypeemail] } func (commsEmailLogWhere[Q]) AliasedAs(alias string) commsEmailLogWhere[Q] { @@ -595,7 +595,7 @@ func buildCommsEmailLogWhere[Q psql.Filterable](cols commsEmailLogColumns) comms Created: psql.Where[Q, time.Time](cols.Created), Destination: psql.Where[Q, string](cols.Destination), Source: psql.Where[Q, string](cols.Source), - Type: psql.Where[Q, enums.CommsEmailmessagetype](cols.Type), + Type: psql.Where[Q, enums.CommsMessagetypeemail](cols.Type), } } diff --git a/db/models/comms.phone.bob.go b/db/models/comms.phone.bob.go index ece6bcf3..185a680c 100644 --- a/db/models/comms.phone.bob.go +++ b/db/models/comms.phone.bob.go @@ -43,9 +43,9 @@ type CommsPhonesQuery = *psql.ViewQuery[*CommsPhone, CommsPhoneSlice] // commsPhoneR is where relationships are stored. type commsPhoneR struct { - SourceEmailLogs CommsEmailLogSlice // comms.email_log.email_log_source_fkey - DestinationSMSLogs CommsSMSLogSlice // comms.sms_log.sms_log_destination_fkey - SourceSMSLogs CommsSMSLogSlice // comms.sms_log.sms_log_source_fkey + SourceEmailLogs CommsEmailLogSlice // comms.email_log.email_log_source_fkey + DestinationTextLogs CommsTextLogSlice // comms.text_log.text_log_destination_fkey + SourceTextLogs CommsTextLogSlice // comms.text_log.text_log_source_fkey } func buildCommsPhoneColumns(alias string) commsPhoneColumns { @@ -396,14 +396,14 @@ func (os CommsPhoneSlice) SourceEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) )...) } -// DestinationSMSLogs starts a query for related objects on comms.sms_log -func (o *CommsPhone) DestinationSMSLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsSMSLogsQuery { - return CommsSMSLogs.Query(append(mods, - sm.Where(CommsSMSLogs.Columns.Destination.EQ(psql.Arg(o.E164))), +// DestinationTextLogs starts a query for related objects on comms.text_log +func (o *CommsPhone) DestinationTextLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsTextLogsQuery { + return CommsTextLogs.Query(append(mods, + sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(o.E164))), )...) } -func (os CommsPhoneSlice) DestinationSMSLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsSMSLogsQuery { +func (os CommsPhoneSlice) DestinationTextLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsTextLogsQuery { pkE164 := make(pgtypes.Array[string], 0, len(os)) for _, o := range os { if o == nil { @@ -415,19 +415,19 @@ func (os CommsPhoneSlice) DestinationSMSLogs(mods ...bob.Mod[*dialect.SelectQuer psql.F("unnest", psql.Cast(psql.Arg(pkE164), "text[]")), )) - return CommsSMSLogs.Query(append(mods, - sm.Where(psql.Group(CommsSMSLogs.Columns.Destination).OP("IN", PKArgExpr)), + return CommsTextLogs.Query(append(mods, + sm.Where(psql.Group(CommsTextLogs.Columns.Destination).OP("IN", PKArgExpr)), )...) } -// SourceSMSLogs starts a query for related objects on comms.sms_log -func (o *CommsPhone) SourceSMSLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsSMSLogsQuery { - return CommsSMSLogs.Query(append(mods, - sm.Where(CommsSMSLogs.Columns.Source.EQ(psql.Arg(o.E164))), +// SourceTextLogs starts a query for related objects on comms.text_log +func (o *CommsPhone) SourceTextLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsTextLogsQuery { + return CommsTextLogs.Query(append(mods, + sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(o.E164))), )...) } -func (os CommsPhoneSlice) SourceSMSLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsSMSLogsQuery { +func (os CommsPhoneSlice) SourceTextLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsTextLogsQuery { pkE164 := make(pgtypes.Array[string], 0, len(os)) for _, o := range os { if o == nil { @@ -439,8 +439,8 @@ func (os CommsPhoneSlice) SourceSMSLogs(mods ...bob.Mod[*dialect.SelectQuery]) C psql.F("unnest", psql.Cast(psql.Arg(pkE164), "text[]")), )) - return CommsSMSLogs.Query(append(mods, - sm.Where(psql.Group(CommsSMSLogs.Columns.Source).OP("IN", PKArgExpr)), + return CommsTextLogs.Query(append(mods, + sm.Where(psql.Group(CommsTextLogs.Columns.Source).OP("IN", PKArgExpr)), )...) } @@ -512,66 +512,66 @@ func (commsPhone0 *CommsPhone) AttachSourceEmailLogs(ctx context.Context, exec b return nil } -func insertCommsPhoneDestinationSMSLogs0(ctx context.Context, exec bob.Executor, commsSMSLogs1 []*CommsSMSLogSetter, commsPhone0 *CommsPhone) (CommsSMSLogSlice, error) { - for i := range commsSMSLogs1 { - commsSMSLogs1[i].Destination = omit.From(commsPhone0.E164) +func insertCommsPhoneDestinationTextLogs0(ctx context.Context, exec bob.Executor, commsTextLogs1 []*CommsTextLogSetter, commsPhone0 *CommsPhone) (CommsTextLogSlice, error) { + for i := range commsTextLogs1 { + commsTextLogs1[i].Destination = omit.From(commsPhone0.E164) } - ret, err := CommsSMSLogs.Insert(bob.ToMods(commsSMSLogs1...)).All(ctx, exec) + ret, err := CommsTextLogs.Insert(bob.ToMods(commsTextLogs1...)).All(ctx, exec) if err != nil { - return ret, fmt.Errorf("insertCommsPhoneDestinationSMSLogs0: %w", err) + return ret, fmt.Errorf("insertCommsPhoneDestinationTextLogs0: %w", err) } return ret, nil } -func attachCommsPhoneDestinationSMSLogs0(ctx context.Context, exec bob.Executor, count int, commsSMSLogs1 CommsSMSLogSlice, commsPhone0 *CommsPhone) (CommsSMSLogSlice, error) { - setter := &CommsSMSLogSetter{ +func attachCommsPhoneDestinationTextLogs0(ctx context.Context, exec bob.Executor, count int, commsTextLogs1 CommsTextLogSlice, commsPhone0 *CommsPhone) (CommsTextLogSlice, error) { + setter := &CommsTextLogSetter{ Destination: omit.From(commsPhone0.E164), } - err := commsSMSLogs1.UpdateAll(ctx, exec, *setter) + err := commsTextLogs1.UpdateAll(ctx, exec, *setter) if err != nil { - return nil, fmt.Errorf("attachCommsPhoneDestinationSMSLogs0: %w", err) + return nil, fmt.Errorf("attachCommsPhoneDestinationTextLogs0: %w", err) } - return commsSMSLogs1, nil + return commsTextLogs1, nil } -func (commsPhone0 *CommsPhone) InsertDestinationSMSLogs(ctx context.Context, exec bob.Executor, related ...*CommsSMSLogSetter) error { +func (commsPhone0 *CommsPhone) InsertDestinationTextLogs(ctx context.Context, exec bob.Executor, related ...*CommsTextLogSetter) error { if len(related) == 0 { return nil } var err error - commsSMSLogs1, err := insertCommsPhoneDestinationSMSLogs0(ctx, exec, related, commsPhone0) + commsTextLogs1, err := insertCommsPhoneDestinationTextLogs0(ctx, exec, related, commsPhone0) if err != nil { return err } - commsPhone0.R.DestinationSMSLogs = append(commsPhone0.R.DestinationSMSLogs, commsSMSLogs1...) + commsPhone0.R.DestinationTextLogs = append(commsPhone0.R.DestinationTextLogs, commsTextLogs1...) - for _, rel := range commsSMSLogs1 { + for _, rel := range commsTextLogs1 { rel.R.DestinationPhone = commsPhone0 } return nil } -func (commsPhone0 *CommsPhone) AttachDestinationSMSLogs(ctx context.Context, exec bob.Executor, related ...*CommsSMSLog) error { +func (commsPhone0 *CommsPhone) AttachDestinationTextLogs(ctx context.Context, exec bob.Executor, related ...*CommsTextLog) error { if len(related) == 0 { return nil } var err error - commsSMSLogs1 := CommsSMSLogSlice(related) + commsTextLogs1 := CommsTextLogSlice(related) - _, err = attachCommsPhoneDestinationSMSLogs0(ctx, exec, len(related), commsSMSLogs1, commsPhone0) + _, err = attachCommsPhoneDestinationTextLogs0(ctx, exec, len(related), commsTextLogs1, commsPhone0) if err != nil { return err } - commsPhone0.R.DestinationSMSLogs = append(commsPhone0.R.DestinationSMSLogs, commsSMSLogs1...) + commsPhone0.R.DestinationTextLogs = append(commsPhone0.R.DestinationTextLogs, commsTextLogs1...) for _, rel := range related { rel.R.DestinationPhone = commsPhone0 @@ -580,66 +580,66 @@ func (commsPhone0 *CommsPhone) AttachDestinationSMSLogs(ctx context.Context, exe return nil } -func insertCommsPhoneSourceSMSLogs0(ctx context.Context, exec bob.Executor, commsSMSLogs1 []*CommsSMSLogSetter, commsPhone0 *CommsPhone) (CommsSMSLogSlice, error) { - for i := range commsSMSLogs1 { - commsSMSLogs1[i].Source = omit.From(commsPhone0.E164) +func insertCommsPhoneSourceTextLogs0(ctx context.Context, exec bob.Executor, commsTextLogs1 []*CommsTextLogSetter, commsPhone0 *CommsPhone) (CommsTextLogSlice, error) { + for i := range commsTextLogs1 { + commsTextLogs1[i].Source = omit.From(commsPhone0.E164) } - ret, err := CommsSMSLogs.Insert(bob.ToMods(commsSMSLogs1...)).All(ctx, exec) + ret, err := CommsTextLogs.Insert(bob.ToMods(commsTextLogs1...)).All(ctx, exec) if err != nil { - return ret, fmt.Errorf("insertCommsPhoneSourceSMSLogs0: %w", err) + return ret, fmt.Errorf("insertCommsPhoneSourceTextLogs0: %w", err) } return ret, nil } -func attachCommsPhoneSourceSMSLogs0(ctx context.Context, exec bob.Executor, count int, commsSMSLogs1 CommsSMSLogSlice, commsPhone0 *CommsPhone) (CommsSMSLogSlice, error) { - setter := &CommsSMSLogSetter{ +func attachCommsPhoneSourceTextLogs0(ctx context.Context, exec bob.Executor, count int, commsTextLogs1 CommsTextLogSlice, commsPhone0 *CommsPhone) (CommsTextLogSlice, error) { + setter := &CommsTextLogSetter{ Source: omit.From(commsPhone0.E164), } - err := commsSMSLogs1.UpdateAll(ctx, exec, *setter) + err := commsTextLogs1.UpdateAll(ctx, exec, *setter) if err != nil { - return nil, fmt.Errorf("attachCommsPhoneSourceSMSLogs0: %w", err) + return nil, fmt.Errorf("attachCommsPhoneSourceTextLogs0: %w", err) } - return commsSMSLogs1, nil + return commsTextLogs1, nil } -func (commsPhone0 *CommsPhone) InsertSourceSMSLogs(ctx context.Context, exec bob.Executor, related ...*CommsSMSLogSetter) error { +func (commsPhone0 *CommsPhone) InsertSourceTextLogs(ctx context.Context, exec bob.Executor, related ...*CommsTextLogSetter) error { if len(related) == 0 { return nil } var err error - commsSMSLogs1, err := insertCommsPhoneSourceSMSLogs0(ctx, exec, related, commsPhone0) + commsTextLogs1, err := insertCommsPhoneSourceTextLogs0(ctx, exec, related, commsPhone0) if err != nil { return err } - commsPhone0.R.SourceSMSLogs = append(commsPhone0.R.SourceSMSLogs, commsSMSLogs1...) + commsPhone0.R.SourceTextLogs = append(commsPhone0.R.SourceTextLogs, commsTextLogs1...) - for _, rel := range commsSMSLogs1 { + for _, rel := range commsTextLogs1 { rel.R.SourcePhone = commsPhone0 } return nil } -func (commsPhone0 *CommsPhone) AttachSourceSMSLogs(ctx context.Context, exec bob.Executor, related ...*CommsSMSLog) error { +func (commsPhone0 *CommsPhone) AttachSourceTextLogs(ctx context.Context, exec bob.Executor, related ...*CommsTextLog) error { if len(related) == 0 { return nil } var err error - commsSMSLogs1 := CommsSMSLogSlice(related) + commsTextLogs1 := CommsTextLogSlice(related) - _, err = attachCommsPhoneSourceSMSLogs0(ctx, exec, len(related), commsSMSLogs1, commsPhone0) + _, err = attachCommsPhoneSourceTextLogs0(ctx, exec, len(related), commsTextLogs1, commsPhone0) if err != nil { return err } - commsPhone0.R.SourceSMSLogs = append(commsPhone0.R.SourceSMSLogs, commsSMSLogs1...) + commsPhone0.R.SourceTextLogs = append(commsPhone0.R.SourceTextLogs, commsTextLogs1...) for _, rel := range related { rel.R.SourcePhone = commsPhone0 @@ -684,13 +684,13 @@ func (o *CommsPhone) Preload(name string, retrieved any) error { } } return nil - case "DestinationSMSLogs": - rels, ok := retrieved.(CommsSMSLogSlice) + case "DestinationTextLogs": + rels, ok := retrieved.(CommsTextLogSlice) if !ok { return fmt.Errorf("commsPhone cannot load %T as %q", retrieved, name) } - o.R.DestinationSMSLogs = rels + o.R.DestinationTextLogs = rels for _, rel := range rels { if rel != nil { @@ -698,13 +698,13 @@ func (o *CommsPhone) Preload(name string, retrieved any) error { } } return nil - case "SourceSMSLogs": - rels, ok := retrieved.(CommsSMSLogSlice) + case "SourceTextLogs": + rels, ok := retrieved.(CommsTextLogSlice) if !ok { return fmt.Errorf("commsPhone cannot load %T as %q", retrieved, name) } - o.R.SourceSMSLogs = rels + o.R.SourceTextLogs = rels for _, rel := range rels { if rel != nil { @@ -724,20 +724,20 @@ func buildCommsPhonePreloader() commsPhonePreloader { } type commsPhoneThenLoader[Q orm.Loadable] struct { - SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - DestinationSMSLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - SourceSMSLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { type SourceEmailLogsLoadInterface interface { LoadSourceEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } - type DestinationSMSLogsLoadInterface interface { - LoadDestinationSMSLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type DestinationTextLogsLoadInterface interface { + LoadDestinationTextLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } - type SourceSMSLogsLoadInterface interface { - LoadSourceSMSLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type SourceTextLogsLoadInterface interface { + LoadSourceTextLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } return commsPhoneThenLoader[Q]{ @@ -747,16 +747,16 @@ func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { return retrieved.LoadSourceEmailLogs(ctx, exec, mods...) }, ), - DestinationSMSLogs: thenLoadBuilder[Q]( - "DestinationSMSLogs", - func(ctx context.Context, exec bob.Executor, retrieved DestinationSMSLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadDestinationSMSLogs(ctx, exec, mods...) + DestinationTextLogs: thenLoadBuilder[Q]( + "DestinationTextLogs", + func(ctx context.Context, exec bob.Executor, retrieved DestinationTextLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadDestinationTextLogs(ctx, exec, mods...) }, ), - SourceSMSLogs: thenLoadBuilder[Q]( - "SourceSMSLogs", - func(ctx context.Context, exec bob.Executor, retrieved SourceSMSLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadSourceSMSLogs(ctx, exec, mods...) + SourceTextLogs: thenLoadBuilder[Q]( + "SourceTextLogs", + func(ctx context.Context, exec bob.Executor, retrieved SourceTextLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadSourceTextLogs(ctx, exec, mods...) }, ), } @@ -823,16 +823,16 @@ func (os CommsPhoneSlice) LoadSourceEmailLogs(ctx context.Context, exec bob.Exec return nil } -// LoadDestinationSMSLogs loads the commsPhone's DestinationSMSLogs into the .R struct -func (o *CommsPhone) LoadDestinationSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadDestinationTextLogs loads the commsPhone's DestinationTextLogs into the .R struct +func (o *CommsPhone) LoadDestinationTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } // Reset the relationship - o.R.DestinationSMSLogs = nil + o.R.DestinationTextLogs = nil - related, err := o.DestinationSMSLogs(mods...).All(ctx, exec) + related, err := o.DestinationTextLogs(mods...).All(ctx, exec) if err != nil { return err } @@ -841,17 +841,17 @@ func (o *CommsPhone) LoadDestinationSMSLogs(ctx context.Context, exec bob.Execut rel.R.DestinationPhone = o } - o.R.DestinationSMSLogs = related + o.R.DestinationTextLogs = related return nil } -// LoadDestinationSMSLogs loads the commsPhone's DestinationSMSLogs into the .R struct -func (os CommsPhoneSlice) LoadDestinationSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadDestinationTextLogs loads the commsPhone's DestinationTextLogs into the .R struct +func (os CommsPhoneSlice) LoadDestinationTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } - commsSMSLogs, err := os.DestinationSMSLogs(mods...).All(ctx, exec) + commsTextLogs, err := os.DestinationTextLogs(mods...).All(ctx, exec) if err != nil { return err } @@ -861,7 +861,7 @@ func (os CommsPhoneSlice) LoadDestinationSMSLogs(ctx context.Context, exec bob.E continue } - o.R.DestinationSMSLogs = nil + o.R.DestinationTextLogs = nil } for _, o := range os { @@ -869,7 +869,7 @@ func (os CommsPhoneSlice) LoadDestinationSMSLogs(ctx context.Context, exec bob.E continue } - for _, rel := range commsSMSLogs { + for _, rel := range commsTextLogs { if !(o.E164 == rel.Destination) { continue @@ -877,23 +877,23 @@ func (os CommsPhoneSlice) LoadDestinationSMSLogs(ctx context.Context, exec bob.E rel.R.DestinationPhone = o - o.R.DestinationSMSLogs = append(o.R.DestinationSMSLogs, rel) + o.R.DestinationTextLogs = append(o.R.DestinationTextLogs, rel) } } return nil } -// LoadSourceSMSLogs loads the commsPhone's SourceSMSLogs into the .R struct -func (o *CommsPhone) LoadSourceSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadSourceTextLogs loads the commsPhone's SourceTextLogs into the .R struct +func (o *CommsPhone) LoadSourceTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } // Reset the relationship - o.R.SourceSMSLogs = nil + o.R.SourceTextLogs = nil - related, err := o.SourceSMSLogs(mods...).All(ctx, exec) + related, err := o.SourceTextLogs(mods...).All(ctx, exec) if err != nil { return err } @@ -902,17 +902,17 @@ func (o *CommsPhone) LoadSourceSMSLogs(ctx context.Context, exec bob.Executor, m rel.R.SourcePhone = o } - o.R.SourceSMSLogs = related + o.R.SourceTextLogs = related return nil } -// LoadSourceSMSLogs loads the commsPhone's SourceSMSLogs into the .R struct -func (os CommsPhoneSlice) LoadSourceSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadSourceTextLogs loads the commsPhone's SourceTextLogs into the .R struct +func (os CommsPhoneSlice) LoadSourceTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } - commsSMSLogs, err := os.SourceSMSLogs(mods...).All(ctx, exec) + commsTextLogs, err := os.SourceTextLogs(mods...).All(ctx, exec) if err != nil { return err } @@ -922,7 +922,7 @@ func (os CommsPhoneSlice) LoadSourceSMSLogs(ctx context.Context, exec bob.Execut continue } - o.R.SourceSMSLogs = nil + o.R.SourceTextLogs = nil } for _, o := range os { @@ -930,7 +930,7 @@ func (os CommsPhoneSlice) LoadSourceSMSLogs(ctx context.Context, exec bob.Execut continue } - for _, rel := range commsSMSLogs { + for _, rel := range commsTextLogs { if !(o.E164 == rel.Source) { continue @@ -938,7 +938,7 @@ func (os CommsPhoneSlice) LoadSourceSMSLogs(ctx context.Context, exec bob.Execut rel.R.SourcePhone = o - o.R.SourceSMSLogs = append(o.R.SourceSMSLogs, rel) + o.R.SourceTextLogs = append(o.R.SourceTextLogs, rel) } } @@ -947,9 +947,9 @@ func (os CommsPhoneSlice) LoadSourceSMSLogs(ctx context.Context, exec bob.Execut // commsPhoneC is where relationship counts are stored. type commsPhoneC struct { - SourceEmailLogs *int64 - DestinationSMSLogs *int64 - SourceSMSLogs *int64 + SourceEmailLogs *int64 + DestinationTextLogs *int64 + SourceTextLogs *int64 } // PreloadCount sets a count in the C struct by name @@ -961,18 +961,18 @@ func (o *CommsPhone) PreloadCount(name string, count int64) error { switch name { case "SourceEmailLogs": o.C.SourceEmailLogs = &count - case "DestinationSMSLogs": - o.C.DestinationSMSLogs = &count - case "SourceSMSLogs": - o.C.SourceSMSLogs = &count + case "DestinationTextLogs": + o.C.DestinationTextLogs = &count + case "SourceTextLogs": + o.C.SourceTextLogs = &count } return nil } type commsPhoneCountPreloader struct { - SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader - DestinationSMSLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader - SourceSMSLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader } func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { @@ -994,8 +994,8 @@ func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { return psql.Group(psql.Select(subqueryMods...).Expression) }) }, - DestinationSMSLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { - return countPreloader[*CommsPhone]("DestinationSMSLogs", func(parent string) bob.Expression { + DestinationTextLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*CommsPhone]("DestinationTextLogs", func(parent string) bob.Expression { // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) if parent == "" { parent = CommsPhones.Alias() @@ -1004,15 +1004,15 @@ func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { subqueryMods := []bob.Mod[*dialect.SelectQuery]{ sm.Columns(psql.Raw("count(*)")), - sm.From(CommsSMSLogs.Name()), - sm.Where(psql.Quote(CommsSMSLogs.Alias(), "destination").EQ(psql.Quote(parent, "e164"))), + sm.From(CommsTextLogs.Name()), + sm.Where(psql.Quote(CommsTextLogs.Alias(), "destination").EQ(psql.Quote(parent, "e164"))), } subqueryMods = append(subqueryMods, mods...) return psql.Group(psql.Select(subqueryMods...).Expression) }) }, - SourceSMSLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { - return countPreloader[*CommsPhone]("SourceSMSLogs", func(parent string) bob.Expression { + SourceTextLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*CommsPhone]("SourceTextLogs", func(parent string) bob.Expression { // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) if parent == "" { parent = CommsPhones.Alias() @@ -1021,8 +1021,8 @@ func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { subqueryMods := []bob.Mod[*dialect.SelectQuery]{ sm.Columns(psql.Raw("count(*)")), - sm.From(CommsSMSLogs.Name()), - sm.Where(psql.Quote(CommsSMSLogs.Alias(), "source").EQ(psql.Quote(parent, "e164"))), + sm.From(CommsTextLogs.Name()), + sm.Where(psql.Quote(CommsTextLogs.Alias(), "source").EQ(psql.Quote(parent, "e164"))), } subqueryMods = append(subqueryMods, mods...) return psql.Group(psql.Select(subqueryMods...).Expression) @@ -1032,20 +1032,20 @@ func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { } type commsPhoneCountThenLoader[Q orm.Loadable] struct { - SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - DestinationSMSLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - SourceSMSLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[Q] { type SourceEmailLogsCountInterface interface { LoadCountSourceEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } - type DestinationSMSLogsCountInterface interface { - LoadCountDestinationSMSLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type DestinationTextLogsCountInterface interface { + LoadCountDestinationTextLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } - type SourceSMSLogsCountInterface interface { - LoadCountSourceSMSLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type SourceTextLogsCountInterface interface { + LoadCountSourceTextLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } return commsPhoneCountThenLoader[Q]{ @@ -1055,16 +1055,16 @@ func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[ return retrieved.LoadCountSourceEmailLogs(ctx, exec, mods...) }, ), - DestinationSMSLogs: countThenLoadBuilder[Q]( - "DestinationSMSLogs", - func(ctx context.Context, exec bob.Executor, retrieved DestinationSMSLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadCountDestinationSMSLogs(ctx, exec, mods...) + DestinationTextLogs: countThenLoadBuilder[Q]( + "DestinationTextLogs", + func(ctx context.Context, exec bob.Executor, retrieved DestinationTextLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountDestinationTextLogs(ctx, exec, mods...) }, ), - SourceSMSLogs: countThenLoadBuilder[Q]( - "SourceSMSLogs", - func(ctx context.Context, exec bob.Executor, retrieved SourceSMSLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadCountSourceSMSLogs(ctx, exec, mods...) + SourceTextLogs: countThenLoadBuilder[Q]( + "SourceTextLogs", + func(ctx context.Context, exec bob.Executor, retrieved SourceTextLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountSourceTextLogs(ctx, exec, mods...) }, ), } @@ -1100,29 +1100,29 @@ func (os CommsPhoneSlice) LoadCountSourceEmailLogs(ctx context.Context, exec bob return nil } -// LoadCountDestinationSMSLogs loads the count of DestinationSMSLogs into the C struct -func (o *CommsPhone) LoadCountDestinationSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadCountDestinationTextLogs loads the count of DestinationTextLogs into the C struct +func (o *CommsPhone) LoadCountDestinationTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } - count, err := o.DestinationSMSLogs(mods...).Count(ctx, exec) + count, err := o.DestinationTextLogs(mods...).Count(ctx, exec) if err != nil { return err } - o.C.DestinationSMSLogs = &count + o.C.DestinationTextLogs = &count return nil } -// LoadCountDestinationSMSLogs loads the count of DestinationSMSLogs for a slice -func (os CommsPhoneSlice) LoadCountDestinationSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadCountDestinationTextLogs loads the count of DestinationTextLogs for a slice +func (os CommsPhoneSlice) LoadCountDestinationTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } for _, o := range os { - if err := o.LoadCountDestinationSMSLogs(ctx, exec, mods...); err != nil { + if err := o.LoadCountDestinationTextLogs(ctx, exec, mods...); err != nil { return err } } @@ -1130,29 +1130,29 @@ func (os CommsPhoneSlice) LoadCountDestinationSMSLogs(ctx context.Context, exec return nil } -// LoadCountSourceSMSLogs loads the count of SourceSMSLogs into the C struct -func (o *CommsPhone) LoadCountSourceSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadCountSourceTextLogs loads the count of SourceTextLogs into the C struct +func (o *CommsPhone) LoadCountSourceTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } - count, err := o.SourceSMSLogs(mods...).Count(ctx, exec) + count, err := o.SourceTextLogs(mods...).Count(ctx, exec) if err != nil { return err } - o.C.SourceSMSLogs = &count + o.C.SourceTextLogs = &count return nil } -// LoadCountSourceSMSLogs loads the count of SourceSMSLogs for a slice -func (os CommsPhoneSlice) LoadCountSourceSMSLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadCountSourceTextLogs loads the count of SourceTextLogs for a slice +func (os CommsPhoneSlice) LoadCountSourceTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } for _, o := range os { - if err := o.LoadCountSourceSMSLogs(ctx, exec, mods...); err != nil { + if err := o.LoadCountSourceTextLogs(ctx, exec, mods...); err != nil { return err } } @@ -1161,10 +1161,10 @@ func (os CommsPhoneSlice) LoadCountSourceSMSLogs(ctx context.Context, exec bob.E } type commsPhoneJoins[Q dialect.Joinable] struct { - typ string - SourceEmailLogs modAs[Q, commsEmailLogColumns] - DestinationSMSLogs modAs[Q, commsSMSLogColumns] - SourceSMSLogs modAs[Q, commsSMSLogColumns] + typ string + SourceEmailLogs modAs[Q, commsEmailLogColumns] + DestinationTextLogs modAs[Q, commsTextLogColumns] + SourceTextLogs modAs[Q, commsTextLogColumns] } func (j commsPhoneJoins[Q]) aliasedAs(alias string) commsPhoneJoins[Q] { @@ -1188,13 +1188,13 @@ func buildCommsPhoneJoins[Q dialect.Joinable](cols commsPhoneColumns, typ string return mods }, }, - DestinationSMSLogs: modAs[Q, commsSMSLogColumns]{ - c: CommsSMSLogs.Columns, - f: func(to commsSMSLogColumns) bob.Mod[Q] { + DestinationTextLogs: modAs[Q, commsTextLogColumns]{ + c: CommsTextLogs.Columns, + f: func(to commsTextLogColumns) bob.Mod[Q] { mods := make(mods.QueryMods[Q], 0, 1) { - mods = append(mods, dialect.Join[Q](typ, CommsSMSLogs.Name().As(to.Alias())).On( + mods = append(mods, dialect.Join[Q](typ, CommsTextLogs.Name().As(to.Alias())).On( to.Destination.EQ(cols.E164), )) } @@ -1202,13 +1202,13 @@ func buildCommsPhoneJoins[Q dialect.Joinable](cols commsPhoneColumns, typ string return mods }, }, - SourceSMSLogs: modAs[Q, commsSMSLogColumns]{ - c: CommsSMSLogs.Columns, - f: func(to commsSMSLogColumns) bob.Mod[Q] { + SourceTextLogs: modAs[Q, commsTextLogColumns]{ + c: CommsTextLogs.Columns, + f: func(to commsTextLogColumns) bob.Mod[Q] { mods := make(mods.QueryMods[Q], 0, 1) { - mods = append(mods, dialect.Join[Q](typ, CommsSMSLogs.Name().As(to.Alias())).On( + mods = append(mods, dialect.Join[Q](typ, CommsTextLogs.Name().As(to.Alias())).On( to.Source.EQ(cols.E164), )) } diff --git a/db/models/comms.sms_log.bob.go b/db/models/comms.text_log.bob.go similarity index 51% rename from db/models/comms.sms_log.bob.go rename to db/models/comms.text_log.bob.go index fd54020e..d717c547 100644 --- a/db/models/comms.sms_log.bob.go +++ b/db/models/comms.text_log.bob.go @@ -23,37 +23,37 @@ import ( "github.com/stephenafamo/bob/types/pgtypes" ) -// CommsSMSLog is an object representing the database table. -type CommsSMSLog struct { - Created time.Time `db:"created" ` - Destination string `db:"destination,pk" ` - Source string `db:"source,pk" ` - Type enums.CommsSmsmessagetype `db:"type,pk" ` +// CommsTextLog is an object representing the database table. +type CommsTextLog struct { + Created time.Time `db:"created" ` + Destination string `db:"destination,pk" ` + Source string `db:"source,pk" ` + Type enums.CommsMessagetypetext `db:"type,pk" ` - R commsSMSLogR `db:"-" ` + R commsTextLogR `db:"-" ` } -// CommsSMSLogSlice is an alias for a slice of pointers to CommsSMSLog. -// This should almost always be used instead of []*CommsSMSLog. -type CommsSMSLogSlice []*CommsSMSLog +// CommsTextLogSlice is an alias for a slice of pointers to CommsTextLog. +// This should almost always be used instead of []*CommsTextLog. +type CommsTextLogSlice []*CommsTextLog -// CommsSMSLogs contains methods to work with the sms_log table -var CommsSMSLogs = psql.NewTablex[*CommsSMSLog, CommsSMSLogSlice, *CommsSMSLogSetter]("comms", "sms_log", buildCommsSMSLogColumns("comms.sms_log")) +// CommsTextLogs contains methods to work with the text_log table +var CommsTextLogs = psql.NewTablex[*CommsTextLog, CommsTextLogSlice, *CommsTextLogSetter]("comms", "text_log", buildCommsTextLogColumns("comms.text_log")) -// CommsSMSLogsQuery is a query on the sms_log table -type CommsSMSLogsQuery = *psql.ViewQuery[*CommsSMSLog, CommsSMSLogSlice] +// CommsTextLogsQuery is a query on the text_log table +type CommsTextLogsQuery = *psql.ViewQuery[*CommsTextLog, CommsTextLogSlice] -// commsSMSLogR is where relationships are stored. -type commsSMSLogR struct { - DestinationPhone *CommsPhone // comms.sms_log.sms_log_destination_fkey - SourcePhone *CommsPhone // comms.sms_log.sms_log_source_fkey +// commsTextLogR is where relationships are stored. +type commsTextLogR struct { + DestinationPhone *CommsPhone // comms.text_log.text_log_destination_fkey + SourcePhone *CommsPhone // comms.text_log.text_log_source_fkey } -func buildCommsSMSLogColumns(alias string) commsSMSLogColumns { - return commsSMSLogColumns{ +func buildCommsTextLogColumns(alias string) commsTextLogColumns { + return commsTextLogColumns{ ColumnsExpr: expr.NewColumnsExpr( "created", "destination", "source", "type", - ).WithParent("comms.sms_log"), + ).WithParent("comms.text_log"), tableAlias: alias, Created: psql.Quote(alias, "created"), Destination: psql.Quote(alias, "destination"), @@ -62,7 +62,7 @@ func buildCommsSMSLogColumns(alias string) commsSMSLogColumns { } } -type commsSMSLogColumns struct { +type commsTextLogColumns struct { expr.ColumnsExpr tableAlias string Created psql.Expression @@ -71,25 +71,25 @@ type commsSMSLogColumns struct { Type psql.Expression } -func (c commsSMSLogColumns) Alias() string { +func (c commsTextLogColumns) Alias() string { return c.tableAlias } -func (commsSMSLogColumns) AliasedAs(alias string) commsSMSLogColumns { - return buildCommsSMSLogColumns(alias) +func (commsTextLogColumns) AliasedAs(alias string) commsTextLogColumns { + return buildCommsTextLogColumns(alias) } -// CommsSMSLogSetter is used for insert/upsert/update operations +// CommsTextLogSetter is used for insert/upsert/update operations // All values are optional, and do not have to be set // Generated columns are not included -type CommsSMSLogSetter struct { - Created omit.Val[time.Time] `db:"created" ` - Destination omit.Val[string] `db:"destination,pk" ` - Source omit.Val[string] `db:"source,pk" ` - Type omit.Val[enums.CommsSmsmessagetype] `db:"type,pk" ` +type CommsTextLogSetter struct { + Created omit.Val[time.Time] `db:"created" ` + Destination omit.Val[string] `db:"destination,pk" ` + Source omit.Val[string] `db:"source,pk" ` + Type omit.Val[enums.CommsMessagetypetext] `db:"type,pk" ` } -func (s CommsSMSLogSetter) SetColumns() []string { +func (s CommsTextLogSetter) SetColumns() []string { vals := make([]string, 0, 4) if s.Created.IsValue() { vals = append(vals, "created") @@ -106,7 +106,7 @@ func (s CommsSMSLogSetter) SetColumns() []string { return vals } -func (s CommsSMSLogSetter) Overwrite(t *CommsSMSLog) { +func (s CommsTextLogSetter) Overwrite(t *CommsTextLog) { if s.Created.IsValue() { t.Created = s.Created.MustGet() } @@ -121,9 +121,9 @@ func (s CommsSMSLogSetter) Overwrite(t *CommsSMSLog) { } } -func (s *CommsSMSLogSetter) Apply(q *dialect.InsertQuery) { +func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) { q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { - return CommsSMSLogs.BeforeInsertHooks.RunHooks(ctx, exec, s) + return CommsTextLogs.BeforeInsertHooks.RunHooks(ctx, exec, s) }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { @@ -156,11 +156,11 @@ func (s *CommsSMSLogSetter) Apply(q *dialect.InsertQuery) { })) } -func (s CommsSMSLogSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { +func (s CommsTextLogSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { return um.Set(s.Expressions()...) } -func (s CommsSMSLogSetter) Expressions(prefix ...string) []bob.Expression { +func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression { exprs := make([]bob.Expression, 0, 4) if s.Created.IsValue() { @@ -194,54 +194,54 @@ func (s CommsSMSLogSetter) Expressions(prefix ...string) []bob.Expression { return exprs } -// FindCommsSMSLog retrieves a single record by primary key +// FindCommsTextLog retrieves a single record by primary key // If cols is empty Find will return all columns. -func FindCommsSMSLog(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsSmsmessagetype, cols ...string) (*CommsSMSLog, error) { +func FindCommsTextLog(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsMessagetypetext, cols ...string) (*CommsTextLog, error) { if len(cols) == 0 { - return CommsSMSLogs.Query( - sm.Where(CommsSMSLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), - sm.Where(CommsSMSLogs.Columns.Source.EQ(psql.Arg(SourcePK))), - sm.Where(CommsSMSLogs.Columns.Type.EQ(psql.Arg(TypePK))), + return CommsTextLogs.Query( + sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), + sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(SourcePK))), + sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(TypePK))), ).One(ctx, exec) } - return CommsSMSLogs.Query( - sm.Where(CommsSMSLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), - sm.Where(CommsSMSLogs.Columns.Source.EQ(psql.Arg(SourcePK))), - sm.Where(CommsSMSLogs.Columns.Type.EQ(psql.Arg(TypePK))), - sm.Columns(CommsSMSLogs.Columns.Only(cols...)), + return CommsTextLogs.Query( + sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), + sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(SourcePK))), + sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(TypePK))), + sm.Columns(CommsTextLogs.Columns.Only(cols...)), ).One(ctx, exec) } -// CommsSMSLogExists checks the presence of a single record by primary key -func CommsSMSLogExists(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsSmsmessagetype) (bool, error) { - return CommsSMSLogs.Query( - sm.Where(CommsSMSLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), - sm.Where(CommsSMSLogs.Columns.Source.EQ(psql.Arg(SourcePK))), - sm.Where(CommsSMSLogs.Columns.Type.EQ(psql.Arg(TypePK))), +// CommsTextLogExists checks the presence of a single record by primary key +func CommsTextLogExists(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsMessagetypetext) (bool, error) { + return CommsTextLogs.Query( + sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), + sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(SourcePK))), + sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(TypePK))), ).Exists(ctx, exec) } -// AfterQueryHook is called after CommsSMSLog is retrieved from the database -func (o *CommsSMSLog) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { +// AfterQueryHook is called after CommsTextLog is retrieved from the database +func (o *CommsTextLog) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { var err error switch queryType { case bob.QueryTypeSelect: - ctx, err = CommsSMSLogs.AfterSelectHooks.RunHooks(ctx, exec, CommsSMSLogSlice{o}) + ctx, err = CommsTextLogs.AfterSelectHooks.RunHooks(ctx, exec, CommsTextLogSlice{o}) case bob.QueryTypeInsert: - ctx, err = CommsSMSLogs.AfterInsertHooks.RunHooks(ctx, exec, CommsSMSLogSlice{o}) + ctx, err = CommsTextLogs.AfterInsertHooks.RunHooks(ctx, exec, CommsTextLogSlice{o}) case bob.QueryTypeUpdate: - ctx, err = CommsSMSLogs.AfterUpdateHooks.RunHooks(ctx, exec, CommsSMSLogSlice{o}) + ctx, err = CommsTextLogs.AfterUpdateHooks.RunHooks(ctx, exec, CommsTextLogSlice{o}) case bob.QueryTypeDelete: - ctx, err = CommsSMSLogs.AfterDeleteHooks.RunHooks(ctx, exec, CommsSMSLogSlice{o}) + ctx, err = CommsTextLogs.AfterDeleteHooks.RunHooks(ctx, exec, CommsTextLogSlice{o}) } return err } -// primaryKeyVals returns the primary key values of the CommsSMSLog -func (o *CommsSMSLog) primaryKeyVals() bob.Expression { +// primaryKeyVals returns the primary key values of the CommsTextLog +func (o *CommsTextLog) primaryKeyVals() bob.Expression { return psql.ArgGroup( o.Destination, o.Source, @@ -249,15 +249,15 @@ func (o *CommsSMSLog) primaryKeyVals() bob.Expression { ) } -func (o *CommsSMSLog) pkEQ() dialect.Expression { - return psql.Group(psql.Quote("comms.sms_log", "destination"), psql.Quote("comms.sms_log", "source"), psql.Quote("comms.sms_log", "type")).EQ(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { +func (o *CommsTextLog) pkEQ() dialect.Expression { + return psql.Group(psql.Quote("comms.text_log", "destination"), psql.Quote("comms.text_log", "source"), psql.Quote("comms.text_log", "type")).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 CommsSMSLog -func (o *CommsSMSLog) Update(ctx context.Context, exec bob.Executor, s *CommsSMSLogSetter) error { - v, err := CommsSMSLogs.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) +// Update uses an executor to update the CommsTextLog +func (o *CommsTextLog) Update(ctx context.Context, exec bob.Executor, s *CommsTextLogSetter) error { + v, err := CommsTextLogs.Update(s.UpdateMod(), um.Where(o.pkEQ())).One(ctx, exec) if err != nil { return err } @@ -268,18 +268,18 @@ func (o *CommsSMSLog) Update(ctx context.Context, exec bob.Executor, s *CommsSMS return nil } -// Delete deletes a single CommsSMSLog record with an executor -func (o *CommsSMSLog) Delete(ctx context.Context, exec bob.Executor) error { - _, err := CommsSMSLogs.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) +// Delete deletes a single CommsTextLog record with an executor +func (o *CommsTextLog) Delete(ctx context.Context, exec bob.Executor) error { + _, err := CommsTextLogs.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) return err } -// Reload refreshes the CommsSMSLog using the executor -func (o *CommsSMSLog) Reload(ctx context.Context, exec bob.Executor) error { - o2, err := CommsSMSLogs.Query( - sm.Where(CommsSMSLogs.Columns.Destination.EQ(psql.Arg(o.Destination))), - sm.Where(CommsSMSLogs.Columns.Source.EQ(psql.Arg(o.Source))), - sm.Where(CommsSMSLogs.Columns.Type.EQ(psql.Arg(o.Type))), +// Reload refreshes the CommsTextLog using the executor +func (o *CommsTextLog) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := CommsTextLogs.Query( + sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(o.Destination))), + sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(o.Source))), + sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(o.Type))), ).One(ctx, exec) if err != nil { return err @@ -290,30 +290,30 @@ func (o *CommsSMSLog) Reload(ctx context.Context, exec bob.Executor) error { return nil } -// AfterQueryHook is called after CommsSMSLogSlice is retrieved from the database -func (o CommsSMSLogSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { +// AfterQueryHook is called after CommsTextLogSlice is retrieved from the database +func (o CommsTextLogSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { var err error switch queryType { case bob.QueryTypeSelect: - ctx, err = CommsSMSLogs.AfterSelectHooks.RunHooks(ctx, exec, o) + ctx, err = CommsTextLogs.AfterSelectHooks.RunHooks(ctx, exec, o) case bob.QueryTypeInsert: - ctx, err = CommsSMSLogs.AfterInsertHooks.RunHooks(ctx, exec, o) + ctx, err = CommsTextLogs.AfterInsertHooks.RunHooks(ctx, exec, o) case bob.QueryTypeUpdate: - ctx, err = CommsSMSLogs.AfterUpdateHooks.RunHooks(ctx, exec, o) + ctx, err = CommsTextLogs.AfterUpdateHooks.RunHooks(ctx, exec, o) case bob.QueryTypeDelete: - ctx, err = CommsSMSLogs.AfterDeleteHooks.RunHooks(ctx, exec, o) + ctx, err = CommsTextLogs.AfterDeleteHooks.RunHooks(ctx, exec, o) } return err } -func (o CommsSMSLogSlice) pkIN() dialect.Expression { +func (o CommsTextLogSlice) pkIN() dialect.Expression { if len(o) == 0 { return psql.Raw("NULL") } - return psql.Group(psql.Quote("comms.sms_log", "destination"), psql.Quote("comms.sms_log", "source"), psql.Quote("comms.sms_log", "type")).In(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return psql.Group(psql.Quote("comms.text_log", "destination"), psql.Quote("comms.text_log", "source"), psql.Quote("comms.text_log", "type")).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() @@ -325,7 +325,7 @@ func (o CommsSMSLogSlice) pkIN() dialect.Expression { // 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 CommsSMSLogSlice) copyMatchingRows(from ...*CommsSMSLog) { +func (o CommsTextLogSlice) copyMatchingRows(from ...*CommsTextLog) { for i, old := range o { for _, new := range from { if new.Destination != old.Destination { @@ -345,25 +345,25 @@ func (o CommsSMSLogSlice) copyMatchingRows(from ...*CommsSMSLog) { } // UpdateMod modifies an update query with "WHERE primary_key IN (o...)" -func (o CommsSMSLogSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { +func (o CommsTextLogSlice) 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 CommsSMSLogs.BeforeUpdateHooks.RunHooks(ctx, exec, o) + return CommsTextLogs.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 *CommsSMSLog: + case *CommsTextLog: o.copyMatchingRows(retrieved) - case []*CommsSMSLog: + case []*CommsTextLog: o.copyMatchingRows(retrieved...) - case CommsSMSLogSlice: + case CommsTextLogSlice: o.copyMatchingRows(retrieved...) default: - // If the retrieved value is not a CommsSMSLog or a slice of CommsSMSLog + // If the retrieved value is not a CommsTextLog or a slice of CommsTextLog // then run the AfterUpdateHooks on the slice - _, err = CommsSMSLogs.AfterUpdateHooks.RunHooks(ctx, exec, o) + _, err = CommsTextLogs.AfterUpdateHooks.RunHooks(ctx, exec, o) } return err @@ -374,25 +374,25 @@ func (o CommsSMSLogSlice) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } // DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" -func (o CommsSMSLogSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { +func (o CommsTextLogSlice) 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 CommsSMSLogs.BeforeDeleteHooks.RunHooks(ctx, exec, o) + return CommsTextLogs.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 *CommsSMSLog: + case *CommsTextLog: o.copyMatchingRows(retrieved) - case []*CommsSMSLog: + case []*CommsTextLog: o.copyMatchingRows(retrieved...) - case CommsSMSLogSlice: + case CommsTextLogSlice: o.copyMatchingRows(retrieved...) default: - // If the retrieved value is not a CommsSMSLog or a slice of CommsSMSLog + // If the retrieved value is not a CommsTextLog or a slice of CommsTextLog // then run the AfterDeleteHooks on the slice - _, err = CommsSMSLogs.AfterDeleteHooks.RunHooks(ctx, exec, o) + _, err = CommsTextLogs.AfterDeleteHooks.RunHooks(ctx, exec, o) } return err @@ -402,30 +402,30 @@ func (o CommsSMSLogSlice) DeleteMod() bob.Mod[*dialect.DeleteQuery] { }) } -func (o CommsSMSLogSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals CommsSMSLogSetter) error { +func (o CommsTextLogSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals CommsTextLogSetter) error { if len(o) == 0 { return nil } - _, err := CommsSMSLogs.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + _, err := CommsTextLogs.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) return err } -func (o CommsSMSLogSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { +func (o CommsTextLogSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { if len(o) == 0 { return nil } - _, err := CommsSMSLogs.Delete(o.DeleteMod()).Exec(ctx, exec) + _, err := CommsTextLogs.Delete(o.DeleteMod()).Exec(ctx, exec) return err } -func (o CommsSMSLogSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { +func (o CommsTextLogSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { if len(o) == 0 { return nil } - o2, err := CommsSMSLogs.Query(sm.Where(o.pkIN())).All(ctx, exec) + o2, err := CommsTextLogs.Query(sm.Where(o.pkIN())).All(ctx, exec) if err != nil { return err } @@ -436,13 +436,13 @@ func (o CommsSMSLogSlice) ReloadAll(ctx context.Context, exec bob.Executor) erro } // DestinationPhone starts a query for related objects on comms.phone -func (o *CommsSMSLog) DestinationPhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { +func (o *CommsTextLog) DestinationPhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { return CommsPhones.Query(append(mods, sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(o.Destination))), )...) } -func (os CommsSMSLogSlice) DestinationPhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { +func (os CommsTextLogSlice) DestinationPhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { pkDestination := make(pgtypes.Array[string], 0, len(os)) for _, o := range os { if o == nil { @@ -460,13 +460,13 @@ func (os CommsSMSLogSlice) DestinationPhone(mods ...bob.Mod[*dialect.SelectQuery } // SourcePhone starts a query for related objects on comms.phone -func (o *CommsSMSLog) SourcePhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { +func (o *CommsTextLog) SourcePhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { return CommsPhones.Query(append(mods, sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(o.Source))), )...) } -func (os CommsSMSLogSlice) SourcePhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { +func (os CommsTextLogSlice) SourcePhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { pkSource := make(pgtypes.Array[string], 0, len(os)) for _, o := range os { if o == nil { @@ -483,20 +483,20 @@ func (os CommsSMSLogSlice) SourcePhone(mods ...bob.Mod[*dialect.SelectQuery]) Co )...) } -func attachCommsSMSLogDestinationPhone0(ctx context.Context, exec bob.Executor, count int, commsSMSLog0 *CommsSMSLog, commsPhone1 *CommsPhone) (*CommsSMSLog, error) { - setter := &CommsSMSLogSetter{ +func attachCommsTextLogDestinationPhone0(ctx context.Context, exec bob.Executor, count int, commsTextLog0 *CommsTextLog, commsPhone1 *CommsPhone) (*CommsTextLog, error) { + setter := &CommsTextLogSetter{ Destination: omit.From(commsPhone1.E164), } - err := commsSMSLog0.Update(ctx, exec, setter) + err := commsTextLog0.Update(ctx, exec, setter) if err != nil { - return nil, fmt.Errorf("attachCommsSMSLogDestinationPhone0: %w", err) + return nil, fmt.Errorf("attachCommsTextLogDestinationPhone0: %w", err) } - return commsSMSLog0, nil + return commsTextLog0, nil } -func (commsSMSLog0 *CommsSMSLog) InsertDestinationPhone(ctx context.Context, exec bob.Executor, related *CommsPhoneSetter) error { +func (commsTextLog0 *CommsTextLog) InsertDestinationPhone(ctx context.Context, exec bob.Executor, related *CommsPhoneSetter) error { var err error commsPhone1, err := CommsPhones.Insert(related).One(ctx, exec) @@ -504,47 +504,47 @@ func (commsSMSLog0 *CommsSMSLog) InsertDestinationPhone(ctx context.Context, exe return fmt.Errorf("inserting related objects: %w", err) } - _, err = attachCommsSMSLogDestinationPhone0(ctx, exec, 1, commsSMSLog0, commsPhone1) + _, err = attachCommsTextLogDestinationPhone0(ctx, exec, 1, commsTextLog0, commsPhone1) if err != nil { return err } - commsSMSLog0.R.DestinationPhone = commsPhone1 + commsTextLog0.R.DestinationPhone = commsPhone1 - commsPhone1.R.DestinationSMSLogs = append(commsPhone1.R.DestinationSMSLogs, commsSMSLog0) + commsPhone1.R.DestinationTextLogs = append(commsPhone1.R.DestinationTextLogs, commsTextLog0) return nil } -func (commsSMSLog0 *CommsSMSLog) AttachDestinationPhone(ctx context.Context, exec bob.Executor, commsPhone1 *CommsPhone) error { +func (commsTextLog0 *CommsTextLog) AttachDestinationPhone(ctx context.Context, exec bob.Executor, commsPhone1 *CommsPhone) error { var err error - _, err = attachCommsSMSLogDestinationPhone0(ctx, exec, 1, commsSMSLog0, commsPhone1) + _, err = attachCommsTextLogDestinationPhone0(ctx, exec, 1, commsTextLog0, commsPhone1) if err != nil { return err } - commsSMSLog0.R.DestinationPhone = commsPhone1 + commsTextLog0.R.DestinationPhone = commsPhone1 - commsPhone1.R.DestinationSMSLogs = append(commsPhone1.R.DestinationSMSLogs, commsSMSLog0) + commsPhone1.R.DestinationTextLogs = append(commsPhone1.R.DestinationTextLogs, commsTextLog0) return nil } -func attachCommsSMSLogSourcePhone0(ctx context.Context, exec bob.Executor, count int, commsSMSLog0 *CommsSMSLog, commsPhone1 *CommsPhone) (*CommsSMSLog, error) { - setter := &CommsSMSLogSetter{ +func attachCommsTextLogSourcePhone0(ctx context.Context, exec bob.Executor, count int, commsTextLog0 *CommsTextLog, commsPhone1 *CommsPhone) (*CommsTextLog, error) { + setter := &CommsTextLogSetter{ Source: omit.From(commsPhone1.E164), } - err := commsSMSLog0.Update(ctx, exec, setter) + err := commsTextLog0.Update(ctx, exec, setter) if err != nil { - return nil, fmt.Errorf("attachCommsSMSLogSourcePhone0: %w", err) + return nil, fmt.Errorf("attachCommsTextLogSourcePhone0: %w", err) } - return commsSMSLog0, nil + return commsTextLog0, nil } -func (commsSMSLog0 *CommsSMSLog) InsertSourcePhone(ctx context.Context, exec bob.Executor, related *CommsPhoneSetter) error { +func (commsTextLog0 *CommsTextLog) InsertSourcePhone(ctx context.Context, exec bob.Executor, related *CommsPhoneSetter) error { var err error commsPhone1, err := CommsPhones.Insert(related).One(ctx, exec) @@ -552,54 +552,54 @@ func (commsSMSLog0 *CommsSMSLog) InsertSourcePhone(ctx context.Context, exec bob return fmt.Errorf("inserting related objects: %w", err) } - _, err = attachCommsSMSLogSourcePhone0(ctx, exec, 1, commsSMSLog0, commsPhone1) + _, err = attachCommsTextLogSourcePhone0(ctx, exec, 1, commsTextLog0, commsPhone1) if err != nil { return err } - commsSMSLog0.R.SourcePhone = commsPhone1 + commsTextLog0.R.SourcePhone = commsPhone1 - commsPhone1.R.SourceSMSLogs = append(commsPhone1.R.SourceSMSLogs, commsSMSLog0) + commsPhone1.R.SourceTextLogs = append(commsPhone1.R.SourceTextLogs, commsTextLog0) return nil } -func (commsSMSLog0 *CommsSMSLog) AttachSourcePhone(ctx context.Context, exec bob.Executor, commsPhone1 *CommsPhone) error { +func (commsTextLog0 *CommsTextLog) AttachSourcePhone(ctx context.Context, exec bob.Executor, commsPhone1 *CommsPhone) error { var err error - _, err = attachCommsSMSLogSourcePhone0(ctx, exec, 1, commsSMSLog0, commsPhone1) + _, err = attachCommsTextLogSourcePhone0(ctx, exec, 1, commsTextLog0, commsPhone1) if err != nil { return err } - commsSMSLog0.R.SourcePhone = commsPhone1 + commsTextLog0.R.SourcePhone = commsPhone1 - commsPhone1.R.SourceSMSLogs = append(commsPhone1.R.SourceSMSLogs, commsSMSLog0) + commsPhone1.R.SourceTextLogs = append(commsPhone1.R.SourceTextLogs, commsTextLog0) return nil } -type commsSMSLogWhere[Q psql.Filterable] struct { +type commsTextLogWhere[Q psql.Filterable] struct { Created psql.WhereMod[Q, time.Time] Destination psql.WhereMod[Q, string] Source psql.WhereMod[Q, string] - Type psql.WhereMod[Q, enums.CommsSmsmessagetype] + Type psql.WhereMod[Q, enums.CommsMessagetypetext] } -func (commsSMSLogWhere[Q]) AliasedAs(alias string) commsSMSLogWhere[Q] { - return buildCommsSMSLogWhere[Q](buildCommsSMSLogColumns(alias)) +func (commsTextLogWhere[Q]) AliasedAs(alias string) commsTextLogWhere[Q] { + return buildCommsTextLogWhere[Q](buildCommsTextLogColumns(alias)) } -func buildCommsSMSLogWhere[Q psql.Filterable](cols commsSMSLogColumns) commsSMSLogWhere[Q] { - return commsSMSLogWhere[Q]{ +func buildCommsTextLogWhere[Q psql.Filterable](cols commsTextLogColumns) commsTextLogWhere[Q] { + return commsTextLogWhere[Q]{ Created: psql.Where[Q, time.Time](cols.Created), Destination: psql.Where[Q, string](cols.Destination), Source: psql.Where[Q, string](cols.Source), - Type: psql.Where[Q, enums.CommsSmsmessagetype](cols.Type), + Type: psql.Where[Q, enums.CommsMessagetypetext](cols.Type), } } -func (o *CommsSMSLog) Preload(name string, retrieved any) error { +func (o *CommsTextLog) Preload(name string, retrieved any) error { if o == nil { return nil } @@ -608,45 +608,45 @@ func (o *CommsSMSLog) Preload(name string, retrieved any) error { case "DestinationPhone": rel, ok := retrieved.(*CommsPhone) if !ok { - return fmt.Errorf("commsSMSLog cannot load %T as %q", retrieved, name) + return fmt.Errorf("commsTextLog cannot load %T as %q", retrieved, name) } o.R.DestinationPhone = rel if rel != nil { - rel.R.DestinationSMSLogs = CommsSMSLogSlice{o} + rel.R.DestinationTextLogs = CommsTextLogSlice{o} } return nil case "SourcePhone": rel, ok := retrieved.(*CommsPhone) if !ok { - return fmt.Errorf("commsSMSLog cannot load %T as %q", retrieved, name) + return fmt.Errorf("commsTextLog cannot load %T as %q", retrieved, name) } o.R.SourcePhone = rel if rel != nil { - rel.R.SourceSMSLogs = CommsSMSLogSlice{o} + rel.R.SourceTextLogs = CommsTextLogSlice{o} } return nil default: - return fmt.Errorf("commsSMSLog has no relationship %q", name) + return fmt.Errorf("commsTextLog has no relationship %q", name) } } -type commsSMSLogPreloader struct { +type commsTextLogPreloader struct { DestinationPhone func(...psql.PreloadOption) psql.Preloader SourcePhone func(...psql.PreloadOption) psql.Preloader } -func buildCommsSMSLogPreloader() commsSMSLogPreloader { - return commsSMSLogPreloader{ +func buildCommsTextLogPreloader() commsTextLogPreloader { + return commsTextLogPreloader{ DestinationPhone: func(opts ...psql.PreloadOption) psql.Preloader { return psql.Preload[*CommsPhone, CommsPhoneSlice](psql.PreloadRel{ Name: "DestinationPhone", Sides: []psql.PreloadSide{ { - From: CommsSMSLogs, + From: CommsTextLogs, To: CommsPhones, FromColumns: []string{"destination"}, ToColumns: []string{"e164"}, @@ -659,7 +659,7 @@ func buildCommsSMSLogPreloader() commsSMSLogPreloader { Name: "SourcePhone", Sides: []psql.PreloadSide{ { - From: CommsSMSLogs, + From: CommsTextLogs, To: CommsPhones, FromColumns: []string{"source"}, ToColumns: []string{"e164"}, @@ -670,12 +670,12 @@ func buildCommsSMSLogPreloader() commsSMSLogPreloader { } } -type commsSMSLogThenLoader[Q orm.Loadable] struct { +type commsTextLogThenLoader[Q orm.Loadable] struct { DestinationPhone func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] SourcePhone func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } -func buildCommsSMSLogThenLoader[Q orm.Loadable]() commsSMSLogThenLoader[Q] { +func buildCommsTextLogThenLoader[Q orm.Loadable]() commsTextLogThenLoader[Q] { type DestinationPhoneLoadInterface interface { LoadDestinationPhone(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -683,7 +683,7 @@ func buildCommsSMSLogThenLoader[Q orm.Loadable]() commsSMSLogThenLoader[Q] { LoadSourcePhone(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } - return commsSMSLogThenLoader[Q]{ + return commsTextLogThenLoader[Q]{ DestinationPhone: thenLoadBuilder[Q]( "DestinationPhone", func(ctx context.Context, exec bob.Executor, retrieved DestinationPhoneLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -699,8 +699,8 @@ func buildCommsSMSLogThenLoader[Q orm.Loadable]() commsSMSLogThenLoader[Q] { } } -// LoadDestinationPhone loads the commsSMSLog's DestinationPhone into the .R struct -func (o *CommsSMSLog) LoadDestinationPhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadDestinationPhone loads the commsTextLog's DestinationPhone into the .R struct +func (o *CommsTextLog) LoadDestinationPhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } @@ -713,14 +713,14 @@ func (o *CommsSMSLog) LoadDestinationPhone(ctx context.Context, exec bob.Executo return err } - related.R.DestinationSMSLogs = CommsSMSLogSlice{o} + related.R.DestinationTextLogs = CommsTextLogSlice{o} o.R.DestinationPhone = related return nil } -// LoadDestinationPhone loads the commsSMSLog's DestinationPhone into the .R struct -func (os CommsSMSLogSlice) LoadDestinationPhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadDestinationPhone loads the commsTextLog's DestinationPhone into the .R struct +func (os CommsTextLogSlice) LoadDestinationPhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } @@ -741,7 +741,7 @@ func (os CommsSMSLogSlice) LoadDestinationPhone(ctx context.Context, exec bob.Ex continue } - rel.R.DestinationSMSLogs = append(rel.R.DestinationSMSLogs, o) + rel.R.DestinationTextLogs = append(rel.R.DestinationTextLogs, o) o.R.DestinationPhone = rel break @@ -751,8 +751,8 @@ func (os CommsSMSLogSlice) LoadDestinationPhone(ctx context.Context, exec bob.Ex return nil } -// LoadSourcePhone loads the commsSMSLog's SourcePhone into the .R struct -func (o *CommsSMSLog) LoadSourcePhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadSourcePhone loads the commsTextLog's SourcePhone into the .R struct +func (o *CommsTextLog) LoadSourcePhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } @@ -765,14 +765,14 @@ func (o *CommsSMSLog) LoadSourcePhone(ctx context.Context, exec bob.Executor, mo return err } - related.R.SourceSMSLogs = CommsSMSLogSlice{o} + related.R.SourceTextLogs = CommsTextLogSlice{o} o.R.SourcePhone = related return nil } -// LoadSourcePhone loads the commsSMSLog's SourcePhone into the .R struct -func (os CommsSMSLogSlice) LoadSourcePhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadSourcePhone loads the commsTextLog's SourcePhone into the .R struct +func (os CommsTextLogSlice) LoadSourcePhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } @@ -793,7 +793,7 @@ func (os CommsSMSLogSlice) LoadSourcePhone(ctx context.Context, exec bob.Executo continue } - rel.R.SourceSMSLogs = append(rel.R.SourceSMSLogs, o) + rel.R.SourceTextLogs = append(rel.R.SourceTextLogs, o) o.R.SourcePhone = rel break @@ -803,18 +803,18 @@ func (os CommsSMSLogSlice) LoadSourcePhone(ctx context.Context, exec bob.Executo return nil } -type commsSMSLogJoins[Q dialect.Joinable] struct { +type commsTextLogJoins[Q dialect.Joinable] struct { typ string DestinationPhone modAs[Q, commsPhoneColumns] SourcePhone modAs[Q, commsPhoneColumns] } -func (j commsSMSLogJoins[Q]) aliasedAs(alias string) commsSMSLogJoins[Q] { - return buildCommsSMSLogJoins[Q](buildCommsSMSLogColumns(alias), j.typ) +func (j commsTextLogJoins[Q]) aliasedAs(alias string) commsTextLogJoins[Q] { + return buildCommsTextLogJoins[Q](buildCommsTextLogColumns(alias), j.typ) } -func buildCommsSMSLogJoins[Q dialect.Joinable](cols commsSMSLogColumns, typ string) commsSMSLogJoins[Q] { - return commsSMSLogJoins[Q]{ +func buildCommsTextLogJoins[Q dialect.Joinable](cols commsTextLogColumns, typ string) commsTextLogJoins[Q] { + return commsTextLogJoins[Q]{ typ: typ, DestinationPhone: modAs[Q, commsPhoneColumns]{ c: CommsPhones.Columns, diff --git a/go.mod b/go.mod index ef56dc10..cde54c4f 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( require ( github.com/ajg/form v1.5.1 // indirect + github.com/beevik/etree v1.1.0 // indirect github.com/dsoprea/go-exif/v3 v3.0.1 // indirect github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20221012074422-4f3f7e934102 // indirect @@ -43,7 +44,9 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect + github.com/golang/mock v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect @@ -55,8 +58,10 @@ require ( github.com/mfridman/interpolate v0.0.2 // indirect github.com/minio/crc64nvme v1.1.0 // indirect github.com/minio/md5-simd v1.1.2 // indirect + github.com/nyaruka/phonenumbers v1.6.8 // indirect github.com/pganalyze/pg_query_go/v6 v6.1.0 // indirect github.com/philhofer/fwd v1.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect github.com/rs/xid v1.6.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect @@ -69,13 +74,15 @@ require ( github.com/tidwall/rtree v1.3.1 // indirect github.com/tidwall/sjson v1.2.4 // indirect github.com/tinylib/msgp v1.3.0 // indirect + github.com/twilio/twilio-go v1.29.1 // indirect github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.29.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c6543560..41a400eb 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/alexedwards/scs/v2 v2.9.0 h1:xa05mVpwTBm1iLeTMNFfAWpKUm4fXAW7CeAViqBV github.com/alexedwards/scs/v2 v2.9.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= +github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -101,10 +103,14 @@ github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -143,6 +149,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= @@ -179,6 +186,9 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nyaruka/phonenumbers v1.6.8 h1:k7HAJ/LeBkXE0vfbajITzTCZD0z0j+epdBNx43yTygk= +github.com/nyaruka/phonenumbers v1.6.8/go.mod h1:IUu45lj2bSeYXQuxDyyuzOrdV10tyRa1YSsfH8EKN5c= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -263,12 +273,15 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/twilio/twilio-go v1.29.1 h1:4dx1d9EhRUhL5ubzYrDROERAiw55s7mBE6/w3q2epVg= +github.com/twilio/twilio-go v1.29.1/go.mod h1:FpgNWMoD8CFnmukpKq9RNpUSGXC0BwnbeKZj2YHlIkw= github.com/uber/h3-go/v4 v4.4.0 h1:sCHcZHvIKEbdt4rY5ZVs2HDNlCy2wXeJ98vAbz+iLok= github.com/uber/h3-go/v4 v4.4.0/go.mod h1:c94kwXZNHVWkZGIN+y9dV81YVEttypqJpOjsmXGr68Y= github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfPe7z7go8Dvv1AJQDI3eQ/5xith3q2mFlo= github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM= github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4= github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= @@ -285,6 +298,7 @@ go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXe go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= @@ -292,6 +306,7 @@ golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -302,6 +317,7 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -309,6 +325,7 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= @@ -318,6 +335,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -346,16 +365,22 @@ golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/main.go b/main.go index 2f5d450d..f3c432e1 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,6 @@ import ( "os" "os/signal" "runtime/debug" - "sync" "syscall" "time" @@ -16,7 +15,6 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/public-report" - "github.com/Gleipnir-Technology/nidus-sync/queue" nidussync "github.com/Gleipnir-Technology/nidus-sync/sync" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" @@ -64,36 +62,7 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - background.NewOAuthTokenChannel = make(chan struct{}, 10) - queue.ChannelJobAudio = make(chan queue.JobAudio, 100) // Buffered channel to prevent blocking - queue.ChannelJobEmail = make(chan queue.JobEmail, 100) // Buffered channel to prevent blocking - queue.ChannelJobSMS = make(chan queue.JobSMS, 100) // Buffered channel to prevent blocking - - var waitGroup sync.WaitGroup - - waitGroup.Add(1) - go func() { - defer waitGroup.Done() - background.RefreshFieldseekerData(ctx, background.NewOAuthTokenChannel) - }() - - waitGroup.Add(1) - go func() { - defer waitGroup.Done() - background.StartWorkerAudio(ctx, queue.ChannelJobAudio) - }() - - waitGroup.Add(1) - go func() { - defer waitGroup.Done() - background.StartWorkerEmail(ctx, queue.ChannelJobEmail) - }() - - waitGroup.Add(1) - go func() { - defer waitGroup.Done() - background.StartWorkerSMS(ctx, queue.ChannelJobSMS) - }() + background.Start(ctx) server := &http.Server{ Addr: config.Bind, Handler: r, @@ -119,8 +88,7 @@ func main() { } cancel() - - waitGroup.Wait() + background.WaitForExit() log.Info().Msg("Shutdown complete") } diff --git a/public-report/quick.go b/public-report/quick.go index 9a741a89..561ddaae 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -6,13 +6,13 @@ import ( "strconv" "time" - "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/background" + "github.com/Gleipnir-Technology/nidus-sync/comms" "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/queue" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" @@ -157,16 +157,17 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { } consent := r.PostFormValue("consent") email := r.PostFormValue("email") - phone := r.PostFormValue("phone") + phone_str := r.PostFormValue("phone") report_id := r.PostFormValue("report_id") if consent != "on" { respondError(w, "You must consent", nil, http.StatusBadRequest) return } - if email == "" && phone == "" { + if email == "" && phone_str == "" { http.Redirect(w, r, fmt.Sprintf("/quick-submit-complete?report=%s", report_id), http.StatusFound) return } + phone, err := comms.ParsePhoneNumber(phone_str) result, err := psql.Update( um.Table("publicreport.quick"), um.SetCol("reporter_email").ToArg(email), @@ -183,18 +184,10 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { return } if email != "" { - queue.EnqueueJobEmail(queue.JobEmail{ - Destination: email, - Source: config.ForwardEmailReportAddress, - Type: enums.CommsEmailmessagetypeReportSubscriptionConfirmation, - }) + background.ReportSubscriptionConfirmationEmail(email) } - if phone != "" { - queue.EnqueueJobSMS(queue.JobSMS{ - Destination: phone, - Source: config.VoipMSNumber, - Type: enums.CommsSmsmessagetypeReportSubscriptionConfirmation, - }) + if phone_str != "" { + background.ReportSubscriptionConfirmationText(*phone, report_id) } if rowcount == 0 { http.Redirect(w, r, fmt.Sprintf("/error?code=no-rows-affected&report=%s", report_id), http.StatusFound) diff --git a/queue/audio_processing.go b/queue/audio_processing.go deleted file mode 100644 index 5664b3ee..00000000 --- a/queue/audio_processing.go +++ /dev/null @@ -1,23 +0,0 @@ -package queue - -import ( - "github.com/google/uuid" - "github.com/rs/zerolog/log" -) - -// AudioJob represents a job to process an audio file. -type JobAudio struct { - AudioUUID uuid.UUID -} - -var ChannelJobAudio chan JobAudio - -// EnqueueAudioJob sends an audio processing job to the worker. -func EnqueueAudioJob(job JobAudio) { - select { - case ChannelJobAudio <- job: - log.Info().Str("uuid", job.AudioUUID.String()).Msg("Enqueued audio job") - default: - log.Warn().Str("uuid", job.AudioUUID.String()).Msg("Audio job channel is full, dropping job") - } -} diff --git a/queue/comms.go b/queue/comms.go deleted file mode 100644 index d2dd624a..00000000 --- a/queue/comms.go +++ /dev/null @@ -1,38 +0,0 @@ -package queue - -import ( - "github.com/Gleipnir-Technology/nidus-sync/db/enums" - "github.com/rs/zerolog/log" -) - -type JobEmail struct { - Destination string - Source string - Type enums.CommsEmailmessagetype -} -type JobSMS struct { - Destination string - Source string - Type enums.CommsSmsmessagetype -} - -var ChannelJobEmail chan JobEmail -var ChannelJobSMS chan JobSMS - -func EnqueueJobEmail(job JobEmail) { - select { - case ChannelJobEmail <- job: - log.Info().Str("destination", job.Destination).Msg("Enqueued email job") - default: - log.Warn().Msg("email job channel is full, dropping job") - } -} - -func EnqueueJobSMS(job JobSMS) { - select { - case ChannelJobSMS <- job: - log.Info().Str("destination", job.Destination).Msg("Enqueued sms job") - default: - log.Warn().Msg("sms job channel is full, dropping job") - } -} From 9d2253a4a235319ec46631aa153e8ae169ebd198 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 21 Jan 2026 15:15:24 +0000 Subject: [PATCH 0062/1453] Get search map overlay working again. --- htmlpage/static/js/map-locator.js | 7 - htmlpage/static/js/map-multipoint.js | 220 +++++++++++++++++++++++++++ public-report/search.go | 6 +- public-report/template/search.html | 37 ++--- 4 files changed, 243 insertions(+), 27 deletions(-) create mode 100644 htmlpage/static/js/map-multipoint.js diff --git a/htmlpage/static/js/map-locator.js b/htmlpage/static/js/map-locator.js index d9c362b2..5c641110 100644 --- a/htmlpage/static/js/map-locator.js +++ b/htmlpage/static/js/map-locator.js @@ -206,10 +206,3 @@ class MapLocator extends HTMLElement { } customElements.define('map-locator', MapLocator); - - - -function mapLoad(MAPBOX_ACCESS_TOKEN) { - return new Promise((resolve, reject) => { - }); -} diff --git a/htmlpage/static/js/map-multipoint.js b/htmlpage/static/js/map-multipoint.js new file mode 100644 index 00000000..1e15b108 --- /dev/null +++ b/htmlpage/static/js/map-multipoint.js @@ -0,0 +1,220 @@ +var map = null; +// A map that shows multiple single point locations. +// Points have additional detail popups. +class MapMultipoint extends HTMLElement { + constructor() { + super(); + + // Create a shadow DOM + this.attachShadow({mode: "open" }); + + // Initial render + this.render(); + + this._map = null; + // markers shown on the map. Should be none or 1, generally. + this._markers = null; + } + + // Lifecycle: when element is added to the DOM + connectedCallback() { + // Initialize the map when the element is added to the DOM + setTimeout(() => this._initializeMap(), 0); + } + + disconnectedCallback() { + if (this._map) { + this._map.remove(); + } + } + + // Lifecycle: watch these attributes for changes + static get observedAttributes() { + return ['api-key', 'latitude', 'longitude', 'zoom']; + } + + // Lifecycle: respond to attribute changes + attributeChangedCallback(name, oldValue, newValue) { + // Only handle if map exists and values actually changed + if (!this._map || oldValue === newValue) return; + + if (name === 'latitude' || name === 'longitude') { + if (this.hasAttribute('latitude') && this.hasAttribute('longitude')) { + const lat = Number(this.getAttribute('latitude')); + const lng = Number(this.getAttribute('longitude')); + this._map.setCenter([lat, lng]); + } + } + + if (name === 'zoom') { + this._map.setZoom(Number(newValue)); + } + } + + _initializeMap() { + const apiKey = this.getAttribute("api-key"); + const lat = Number(this.getAttribute('latitude') || 36.2); + const lng = Number(this.getAttribute('longitude') || -119.2); + const organization_id = Number(this.getAttribute("organization-id") || 0); + const tegola = this.getAttribute("tegola") + const zoom = Number(this.getAttribute('zoom') || 15); + + mapboxgl.accessToken = apiKey; + const mapElement = this.shadowRoot.querySelector("#map"); + this._map = new mapboxgl.Map({ + container: mapElement, + center: { + lat: lat, + lng: lng, + }, + style: 'mapbox://styles/mapbox/streets-v12', // style URL + zoom: zoom, + }); + this._map.addControl(new mapboxgl.GeolocateControl({ + positionOptions: { + enableHighAccuracy: true + }, + trackUserLocation: true, + showUserHeading: true + })); + this._map.addControl(new mapboxgl.NavigationControl()); + this._map.on("load", () => { + this.dispatchEvent(new CustomEvent('load'), { + bubbles: true, + composed: true, // Allows event to cross shadow DOM boundary + detail: { + map: this + } + }); + }); + } + + async _fetchAddressSuggestions(text) { + try { + const url = `https://api.mapbox.com/search/geocode/v6/forward?q=${encodeURIComponent(text)}&access_token=${this._apiKey}`; + + const response = await fetch(url); + const data = await response.json(); + return data; + } catch (error) { + console.error('Error fetching geocoding suggestions:', error); + } + } + + _renderSuggestions(suggestions) { + console.log("Rendering suggestions", suggestions); + this._suggestions.innerHTML = suggestions.map((item, index) => { + if (item.properties.place_formatted != "") { + return ` +
+
${item.properties.name || item.properties.full_address}
+
${item.properties.place_formatted}
+
` + } else { + return ` +
+
${item.properties.name || item.properties.full_address}
+
${item.properties.place_formatted}
+
` + } + }).join(''); + + // Add click listeners to suggestions + this.shadowRoot.querySelectorAll('.suggestion-item').forEach(el => { + el.addEventListener('click', e => { + const index = parseInt(el.dataset.index); + const suggestion = suggestions[index]; + this.value = suggestion.properties.full_address; + this._suggestions.innerHTML = ''; + + // Dispatch custom event + this.dispatchEvent(new CustomEvent('address-selected', { + bubbles: true, + composed: true, // Allows event to cross shadow DOM boundary + detail: { + location: suggestion + } + })); + }); + }); + } + + // Initial render of component + render() { + this.shadowRoot.innerHTML = ` + + +
+
+
+ `; + } + + addLayer(a) { + return this._map.addLayer(a); + } + addSource(a, b) { + return this._map.addSource(a, b); + } + jumpTo(args) { + return this._map.jumpTo(args); + } + on(a, b) { + return this._map.on(a, b); + } + queryRenderedFeatures(a) { + return this._map.queryRenderedFeatures(a); + } + + setMarker(coords) { + console.log("Setting map marker", coords); + this._map.jumpTo({ + center: coords, + zoom: 14, + }); + this._markers.forEach((marker) => marker.remove()); + + const marker = new mapboxgl.Marker({ + color: "#FF0000", + draggable: true + }).setLngLat(coords).addTo(map); + marker.on('dragend', function(e) { + const markerDraggedEvent = new CustomEvent("markerdragend", { + detail: { + marker: marker + } + }); + mapContainer.dispatchEvent(markerDraggedEvent); + }); + this._markers = [marker]; + } +} + +customElements.define('map-multipoint', MapMultipoint); diff --git a/public-report/search.go b/public-report/search.go index d92748e0..1ed6a637 100644 --- a/public-report/search.go +++ b/public-report/search.go @@ -7,8 +7,9 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/htmlpage" ) -type ContextSearch struct { +type ContentSearch struct { MapboxToken string + URLTegola string } var ( @@ -19,8 +20,9 @@ func getSearch(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, Search, - ContextSearch{ + ContentSearch{ MapboxToken: config.MapboxToken, + URLTegola: config.URLTegola, }, ) } diff --git a/public-report/template/search.html b/public-report/template/search.html index 725a764c..71e9f67b 100644 --- a/public-report/template/search.html +++ b/public-report/template/search.html @@ -6,7 +6,7 @@ - + + `; + + // Create the table + let tableHTML = ` + + + + + + + + + + + + `; + + // Generate rows for each report + if (this._reports.length > 0) { + this._reports.forEach(report => { + const typeClass = this.getTypeClass(report.type); + const statusClass = this.getStatusClass(report.status); + const formattedId = this.formatId(report.id); + const relativeTime = this.formatRelativeTime(report.created); + + tableHTML += ` + + + + + + + + `; + }); + } else { + tableHTML += ` + + `; + } + + tableHTML += ` + +
Report IDReportedTypeAddressStatus
${formattedId}${relativeTime}${report.type}${report.address || 'N/A'}${report.status}
No reports
+ `; + + // Set the shadow DOM content + this.shadowRoot.innerHTML = style + tableHTML; + } +} + +// Register the custom element +customElements.define('report-table', ReportTable); diff --git a/public-report/template/search.html b/public-report/template/search.html index 71e9f67b..9cb7c704 100644 --- a/public-report/template/search.html +++ b/public-report/template/search.html @@ -7,14 +7,8 @@ + + +
+
+
+ `; + } + + jumpTo(args) { + this._map.jumpTo(args); + } + + setMarker(coords) { + console.log("Setting map marker", coords); + this._map.jumpTo({ + center: coords, + zoom: 14, + }); + this._markers.forEach((marker) => marker.remove()); + + const marker = new mapboxgl.Marker({ + color: "#FF0000", + draggable: true + }).setLngLat(coords).addTo(map); + marker.on('dragend', function(e) { + const markerDraggedEvent = new CustomEvent("markerdragend", { + detail: { + marker: marker + } + }); + mapContainer.dispatchEvent(markerDraggedEvent); + }); + this._markers = [marker]; + } +} + +customElements.define('map-single-point', MapSinglePoint); diff --git a/public-report/image.go b/public-report/image.go new file mode 100644 index 00000000..b14acd49 --- /dev/null +++ b/public-report/image.go @@ -0,0 +1,18 @@ +package publicreport + +import ( + "net/http" + + "github.com/Gleipnir-Technology/nidus-sync/userfile" + "github.com/go-chi/chi/v5" +) + +// ServeImageByUUID reads an image with the given UUID from disk and writes it to the HTTP response +func getImageByUUID(w http.ResponseWriter, r *http.Request) { + uid := chi.URLParam(r, "uuid") + if uid == "" { + http.NotFound(w, r) + return + } + userfile.PublicImageFileToResponse(w, uid) +} diff --git a/public-report/routes.go b/public-report/routes.go index 8de97310..d843af5f 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -11,6 +11,7 @@ func Router() chi.Router { r.Get("/privacy", getPrivacy) r.Get("/robots.txt", getRobots) r.Get("/email/report/{report_id}/subscription-confirmation", getEmailReportSubscriptionConfirmation) + r.Get("/image/{uuid}", getImageByUUID) r.Get("/nuisance", getNuisance) r.Post("/nuisance-submit", postNuisance) r.Get("/nuisance-submit-complete", getNuisanceSubmitComplete) diff --git a/public-report/status.go b/public-report/status.go index 5d4c870e..524ec641 100644 --- a/public-report/status.go +++ b/public-report/status.go @@ -32,14 +32,19 @@ type Contact struct { Name string Phone string } +type Image struct { + URL string +} type Report struct { Address string + Comments string Created time.Time ID string + Images []Image Location string // GeoJSON Reporter Contact SiteOwner Contact - Updated time.Time + Type string } type ContentStatus struct { @@ -131,7 +136,6 @@ func contentFromNuisance(ctx context.Context, report_id string) (result ContentS result.Report.ID = report_id result.Report.Address = nuisance.Address result.Report.Created = nuisance.Created - result.Report.Updated = nuisance.Created result.Report.Reporter.Email = nuisance.ReporterEmail result.Report.Reporter.Name = nuisance.ReporterName result.Report.Reporter.Phone = nuisance.ReporterPhone @@ -162,14 +166,26 @@ func contentFromQuick(ctx context.Context, report_id string) (result ContentStat if err != nil { return result, fmt.Errorf("Failed to query nuisance %s: %w", report_id, err) } + + images, err := quick.Images().All(ctx, db.PGInstance.BobDB) + if err != nil { + return result, fmt.Errorf("Failed to get images %s: %w", report_id, err) + } + result.Report.ID = report_id result.Report.Address = quick.Address + result.Report.Comments = quick.Comments result.Report.Created = quick.Created - result.Report.Updated = quick.Created result.Report.Reporter.Email = quick.ReporterEmail result.Report.Reporter.Name = "-" result.Report.Reporter.Phone = quick.ReporterPhone + result.Report.Type = "Quick" + for _, image := range images { + result.Report.Images = append(result.Report.Images, Image{ + URL: fmt.Sprintf("https://%s/image/%s", config.RMODomain, image.StorageUUID), + }) + } type LocationGeoJSON struct { Location string } diff --git a/public-report/template/status-by-id.html b/public-report/template/status-by-id.html index 4235572f..9517ffce 100644 --- a/public-report/template/status-by-id.html +++ b/public-report/template/status-by-id.html @@ -51,12 +51,12 @@
- Created: - {{.Report.Created|timeSince}} + Type: + {{.Report.Type}}
- Last Updated: - July 17, 2023 - 2:45 PM + Created: + {{.Report.Created|timeSince}}
Next Step: @@ -94,8 +94,9 @@
+ -
+
Report Detail
@@ -103,6 +104,7 @@

Foo:Bar

+
@@ -116,6 +118,21 @@
+
+
+
Images
+
+
+ {{ if gt (len .Report.Images) 0 }} + {{ range .Report.Images }} + + {{ end }} + {{ else }} +

None

+ {{ end }} +
+
+
diff --git a/userfile/userfile.go b/userfile/userfile.go index 794450e0..aceea6ea 100644 --- a/userfile/userfile.go +++ b/userfile/userfile.go @@ -3,6 +3,7 @@ package userfile import ( "fmt" "io" + "net/http" "os" "github.com/Gleipnir-Technology/nidus-sync/config" @@ -78,6 +79,43 @@ func PublicImageFileContentWrite(uid uuid.UUID, body io.Reader) error { log.Info().Str("filepath", filepath).Msg("Saved public report image file content") return nil } + func PublicImageFileContentPathRaw(uid string) string { return fmt.Sprintf("%s/%s.raw", config.FilesDirectoryPublic, uid) } + +func PublicImageFileToResponse(w http.ResponseWriter, uid string) { + image_path := PublicImageFileContentPathRaw(uid) + + // Open the file + file, err := os.Open(image_path) + if err != nil { + if os.IsNotExist(err) { + http.Error(w, "Image not found", http.StatusNotFound) + } else { + http.Error(w, "Failed to retrieve image", http.StatusInternalServerError) + } + return + } + defer file.Close() + + // Get file info for Content-Length header + fileInfo, err := file.Stat() + if err != nil { + http.Error(w, "Failed to get image information", http.StatusInternalServerError) + return + } + + // Set appropriate headers + w.Header().Set("Content-Type", "image/png") + w.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Size())) + + // Copy file contents to response writer + _, err = io.Copy(w, file) + if err != nil { + // Note: At this point, we've already started writing the response, + // so we can't change the status code anymore. The best we can do + // is log the error and abandon the connection. + return + } +} From b94d09696e65c4790b3e932e3047af3a8333bc6c Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 21 Jan 2026 21:06:35 +0000 Subject: [PATCH 0069/1453] Initial working marker display in shadow dom --- ...creport_image_with_json_by_quick_id.bob.go | 112 ++++++++++++++++++ ...report_image_with_json_by_quick_id.bob.sql | 18 +++ ...blicreport_image_with_json_by_quick_id.sql | 15 +++ htmlpage/html.go | 8 ++ ...ap-single-point.js => map-with-markers.js} | 45 +++---- public-report/status.go | 16 +-- public-report/template/status-by-id.html | 33 +++++- 7 files changed, 208 insertions(+), 39 deletions(-) create mode 100644 db/sql/publicreport_image_with_json_by_quick_id.bob.go create mode 100644 db/sql/publicreport_image_with_json_by_quick_id.bob.sql create mode 100644 db/sql/publicreport_image_with_json_by_quick_id.sql rename htmlpage/static/js/{map-single-point.js => map-with-markers.js} (72%) diff --git a/db/sql/publicreport_image_with_json_by_quick_id.bob.go b/db/sql/publicreport_image_with_json_by_quick_id.bob.go new file mode 100644 index 00000000..19c257f8 --- /dev/null +++ b/db/sql/publicreport_image_with_json_by_quick_id.bob.go @@ -0,0 +1,112 @@ +// Code generated by BobGen psql v0.42.1. 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" + "time" + + "github.com/aarondl/opt/null" + "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/orm" + "github.com/stephenafamo/scan" +) + +//go:embed publicreport_image_with_json_by_quick_id.bob.sql +var formattedQueries_publicreport_image_with_json_by_quick_id string + +var publicreportImageWithJSONByQuickIDSQL = formattedQueries_publicreport_image_with_json_by_quick_id[172:965] + +type PublicreportImageWithJSONByQuickIDQuery = orm.ModQuery[*dialect.SelectQuery, publicreportImageWithJSONByQuickID, PublicreportImageWithJSONByQuickIDRow, []PublicreportImageWithJSONByQuickIDRow, publicreportImageWithJSONByQuickIDTransformer] + +func PublicreportImageWithJSONByQuickID(QuickID int32) *PublicreportImageWithJSONByQuickIDQuery { + var expressionTypArgs publicreportImageWithJSONByQuickID + + expressionTypArgs.QuickID = psql.Arg(QuickID) + + return &PublicreportImageWithJSONByQuickIDQuery{ + Query: orm.Query[publicreportImageWithJSONByQuickID, PublicreportImageWithJSONByQuickIDRow, []PublicreportImageWithJSONByQuickIDRow, publicreportImageWithJSONByQuickIDTransformer]{ + ExecQuery: orm.ExecQuery[publicreportImageWithJSONByQuickID]{ + BaseQuery: bob.BaseQuery[publicreportImageWithJSONByQuickID]{ + Expression: expressionTypArgs, + Dialect: dialect.Dialect, + QueryType: bob.QueryTypeSelect, + }, + }, + Scanner: func(context.Context, []string) (func(*scan.Row) (any, error), func(any) (PublicreportImageWithJSONByQuickIDRow, error)) { + return func(row *scan.Row) (any, error) { + var t PublicreportImageWithJSONByQuickIDRow + row.ScheduleScanByIndex(0, &t.ID) + row.ScheduleScanByIndex(1, &t.ContentType) + row.ScheduleScanByIndex(2, &t.Created) + row.ScheduleScanByIndex(3, &t.Location) + row.ScheduleScanByIndex(4, &t.LocationJSON) + row.ScheduleScanByIndex(5, &t.ResolutionX) + row.ScheduleScanByIndex(6, &t.ResolutionY) + row.ScheduleScanByIndex(7, &t.StorageUUID) + row.ScheduleScanByIndex(8, &t.StorageSize) + row.ScheduleScanByIndex(9, &t.UploadedFilename) + return &t, nil + }, func(v any) (PublicreportImageWithJSONByQuickIDRow, error) { + return *(v.(*PublicreportImageWithJSONByQuickIDRow)), nil + } + }, + }, + Mod: bob.ModFunc[*dialect.SelectQuery](func(q *dialect.SelectQuery) { + q.AppendSelect(expressionTypArgs.subExpr(9, 549)) + q.SetTable(expressionTypArgs.subExpr(555, 742)) + q.AppendWhere(expressionTypArgs.subExpr(750, 792)) + }), + } +} + +type PublicreportImageWithJSONByQuickIDRow = struct { + ID int32 `db:"id"` + ContentType string `db:"content_type"` + Created time.Time `db:"created"` + Location null.Val[string] `db:"location"` + LocationJSON string `db:"location_json"` + ResolutionX int32 `db:"resolution_x"` + ResolutionY int32 `db:"resolution_y"` + StorageUUID uuid.UUID `db:"storage_uuid"` + StorageSize int64 `db:"storage_size"` + UploadedFilename string `db:"uploaded_filename"` +} + +type publicreportImageWithJSONByQuickIDTransformer = bob.SliceTransformer[PublicreportImageWithJSONByQuickIDRow, []PublicreportImageWithJSONByQuickIDRow] + +type publicreportImageWithJSONByQuickID struct { + QuickID bob.Expression +} + +func (o publicreportImageWithJSONByQuickID) args() iter.Seq[orm.ArgWithPosition] { + return func(yield func(arg orm.ArgWithPosition) bool) { + if !yield(orm.ArgWithPosition{ + Name: "quickID", + Start: 790, + Stop: 792, + Expression: o.QuickID, + }) { + return + } + } +} + +func (o publicreportImageWithJSONByQuickID) raw(from, to int) string { + return publicreportImageWithJSONByQuickIDSQL[from:to] +} + +func (o publicreportImageWithJSONByQuickID) subExpr(from, to int) bob.Expression { + return orm.ArgsToExpression(publicreportImageWithJSONByQuickIDSQL, from, to, o.args()) +} + +func (o publicreportImageWithJSONByQuickID) WriteSQL(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return o.subExpr(0, len(publicreportImageWithJSONByQuickIDSQL)).WriteSQL(ctx, w, d, start) +} diff --git a/db/sql/publicreport_image_with_json_by_quick_id.bob.sql b/db/sql/publicreport_image_with_json_by_quick_id.bob.sql new file mode 100644 index 00000000..d0e37c01 --- /dev/null +++ b/db/sql/publicreport_image_with_json_by_quick_id.bob.sql @@ -0,0 +1,18 @@ +-- Code generated by BobGen psql v0.42.1. DO NOT EDIT. +-- This file is meant to be re-generated in place and/or deleted at any time. + +-- PublicreportImageWithJSONByQuickID +SELECT + "publicreport.image"."id" AS "id", + "publicreport.image"."content_type" AS "content_type", + "publicreport.image"."created" AS "created", + "publicreport.image"."location" AS "location", + ST_AsGeoJSON("publicreport.image"."location") AS "location_json", + "publicreport.image"."resolution_x" AS "resolution_x", + "publicreport.image"."resolution_y" AS "resolution_y", + "publicreport.image"."storage_uuid" AS "storage_uuid", + "publicreport.image"."storage_size" AS "storage_size", + "publicreport.image"."uploaded_filename" AS "uploaded_filename" +FROM "publicreport"."image" AS "publicreport.image" +INNER JOIN "publicreport"."quick_image" AS "publicreport.quick_image" ON ("publicreport.image"."id" = "publicreport.quick_image"."image_id") +WHERE ("publicreport.quick_image"."quick_id" = $1); diff --git a/db/sql/publicreport_image_with_json_by_quick_id.sql b/db/sql/publicreport_image_with_json_by_quick_id.sql new file mode 100644 index 00000000..e8595bd8 --- /dev/null +++ b/db/sql/publicreport_image_with_json_by_quick_id.sql @@ -0,0 +1,15 @@ +-- PublicreportImageWithJSONByQuickID +SELECT + "publicreport.image"."id" AS "id", + "publicreport.image"."content_type" AS "content_type", + "publicreport.image"."created" AS "created", + "publicreport.image"."location" AS "location", + ST_AsGeoJSON("publicreport.image"."location") AS "location_json", + "publicreport.image"."resolution_x" AS "resolution_x", + "publicreport.image"."resolution_y" AS "resolution_y", + "publicreport.image"."storage_uuid" AS "storage_uuid", + "publicreport.image"."storage_size" AS "storage_size", + "publicreport.image"."uploaded_filename" AS "uploaded_filename" +FROM "publicreport"."image" AS "publicreport.image" +INNER JOIN "publicreport"."quick_image" AS "publicreport.quick_image" ON ("publicreport.image"."id" = "publicreport.quick_image"."image_id") +WHERE ("publicreport.quick_image"."quick_id" = $1) diff --git a/htmlpage/html.go b/htmlpage/html.go index 8c8a4f56..a3882f55 100644 --- a/htmlpage/html.go +++ b/htmlpage/html.go @@ -110,6 +110,8 @@ func bigNumber(n int) string { func makeFuncMap() template.FuncMap { funcMap := template.FuncMap{ "bigNumber": bigNumber, + "html": unescapeHTML, + "json": unescapeJS, "GISStatement": gisStatement, "latLngDisplay": latLngDisplay, "publicReportID": publicReportID, @@ -291,6 +293,12 @@ func timeSince(t time.Time) string { return fmt.Sprintf("%d days ago", int(days)) } } +func unescapeHTML(s string) template.HTML { + return template.HTML(s) +} +func unescapeJS(s string) template.JS { + return template.JS(s) +} func uuidShort(uuid uuid.UUID) string { s := uuid.String() if len(s) < 7 { diff --git a/htmlpage/static/js/map-single-point.js b/htmlpage/static/js/map-with-markers.js similarity index 72% rename from htmlpage/static/js/map-single-point.js rename to htmlpage/static/js/map-with-markers.js index e191c68a..a47f58dd 100644 --- a/htmlpage/static/js/map-single-point.js +++ b/htmlpage/static/js/map-with-markers.js @@ -1,6 +1,6 @@ var map = null; -// A map that just shows a single point location, and can't be moved -class MapSinglePoint extends HTMLElement { +// A map that just shows a bunch of markers, it can't change them +class MapWithMarkers extends HTMLElement { constructor() { super(); @@ -10,8 +10,10 @@ class MapSinglePoint extends HTMLElement { // Initial render this.render(); + this._map = null; + // markers shown on the map. Should be none or 1, generally. - this._markers = null; + this._markers = []; } // Lifecycle: when element is added to the DOM @@ -35,7 +37,7 @@ class MapSinglePoint extends HTMLElement { mapboxgl.accessToken = apiKey; const mapElement = this.shadowRoot.querySelector("#map"); - map = new mapboxgl.Map({ + this._map = new mapboxgl.Map({ container: mapElement, center: { lat: lat, @@ -44,7 +46,8 @@ class MapSinglePoint extends HTMLElement { style: 'mapbox://styles/mapbox/streets-v12', // style URL zoom: zoom, }); - map.on("load", function() { + this._map.on("load", () => { + console.log("map loaded"); this.dispatchEvent(new CustomEvent('load'), { bubbles: true, composed: true, // Allows event to cross shadow DOM boundary @@ -53,11 +56,13 @@ class MapSinglePoint extends HTMLElement { } }); }); + this._markers = []; } // Initial render of component render() { this.shadowRoot.innerHTML = ` + + {{end}} {{define "content"}}
@@ -101,7 +121,9 @@
Report Detail
-

Foo:Bar

+ {{ if not (eq .Report.Comments "") }} +

Comments:{{ .Report.Comments }}

+ {{ end }}
@@ -111,10 +133,9 @@
Location Map
- + zoom="14"/>
@@ -134,6 +155,7 @@
+
{{end}} From 486a2d98c238168cc76431c92a25dc6b703cb78e Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 21 Jan 2026 21:10:21 +0000 Subject: [PATCH 0070/1453] Show location markers for images too --- htmlpage/static/js/map-with-markers.js | 5 ++++- public-report/template/status-by-id.html | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/htmlpage/static/js/map-with-markers.js b/htmlpage/static/js/map-with-markers.js index a47f58dd..3225e2db 100644 --- a/htmlpage/static/js/map-with-markers.js +++ b/htmlpage/static/js/map-with-markers.js @@ -103,7 +103,10 @@ class MapWithMarkers extends HTMLElement { console.log("Add marker", coords, color); const el = document.createElement("div"); el.id = "marker"; - const marker = new mapboxgl.Marker().setLngLat(coords).addTo(this._map); + const marker = new mapboxgl.Marker({ + color: color, + scale: 1.5, + }).setLngLat(coords).addTo(this._map); this._markers.push(marker); } } diff --git a/public-report/template/status-by-id.html b/public-report/template/status-by-id.html index 2114de6f..35bebe4f 100644 --- a/public-report/template/status-by-id.html +++ b/public-report/template/status-by-id.html @@ -54,7 +54,10 @@ function onLoad() { center: GEOJSON_LOCATION.coordinates, zoom: 14, }); - map.addMarker(GEOJSON_LOCATION.coordinates, "#FF0000"); + map.addMarker(GEOJSON_LOCATION.coordinates, "#00FF00"); + GEOJSON_IMAGE_LOCATIONS.forEach((image) => { + map.addMarker(image.coordinates, "#FF0000"); + }); }); } document.addEventListener("DOMContentLoaded", onLoad); From 61d8d14fc295655bf62973c18b867227a3eccd0f Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 03:27:32 +0000 Subject: [PATCH 0071/1453] Bunch of work around assigning reports to districts I added some DB schema to track logos and to relate reports to organizations. I reworked how GPS data comes from EXIF data on images because it wasn't working for JPEGs. I might have broken PNGs in the process. Also made the config options for domain names more standardized. --- api/api.go | 40 - api/district.go | 74 ++ api/{endpoint.go => routes.go} | 1 + arcgis-go | 2 +- background/arcgis.go | 4 +- comms/email.go | 8 +- config/config.go | 86 +- db/bobgen.yaml | 3 + db/dbinfo/organization.bob.go | 12 +- db/dbinfo/publicreport.nuisance.bob.go | 32 +- db/dbinfo/publicreport.pool.bob.go | 32 +- db/dbinfo/publicreport.quick.bob.go | 52 +- db/factory/bobfactory_context.bob.go | 6 + db/factory/bobfactory_main.bob.go | 24 + db/factory/organization.bob.go | 325 ++++++- db/factory/publicreport.nuisance.bob.go | 134 ++- db/factory/publicreport.pool.bob.go | 133 ++- db/factory/publicreport.quick.bob.go | 153 +++- db/migrations/00037_publicreport_district.sql | 8 + db/migrations/00038_organization_logo.sql | 2 + db/models/bob_joins.bob.go | 2 + db/models/bob_loaders.bob.go | 4 + db/models/organization.bob.go | 833 +++++++++++++++++- db/models/publicreport.nuisance.bob.go | 269 +++++- db/models/publicreport.pool.bob.go | 226 ++++- db/models/publicreport.quick.bob.go | 350 ++++++-- go.mod | 11 +- go.sum | 224 +++++ main.go | 6 +- platform/district.go | 34 +- public-report/endpoint.go | 3 - public-report/image-upload.go | 85 +- public-report/quick.go | 118 ++- public-report/search.go | 2 +- public-report/status.go | 4 +- .../template/quick-submit-complete.html | 5 + public-report/template/status-by-id.html | 4 +- sync/dash.go | 2 +- sync/mock.go | 2 +- sync/oauth.go | 31 +- userfile/userfile.go | 20 +- 41 files changed, 3029 insertions(+), 337 deletions(-) create mode 100644 api/district.go rename api/{endpoint.go => routes.go} (95%) create mode 100644 db/migrations/00037_publicreport_district.sql create mode 100644 db/migrations/00038_organization_logo.sql diff --git a/api/api.go b/api/api.go index 60226a1a..3b255c50 100644 --- a/api/api.go +++ b/api/api.go @@ -78,46 +78,6 @@ func apiAudioContentPost(w http.ResponseWriter, r *http.Request, u *models.User) w.WriteHeader(http.StatusOK) } -func apiGetDistrict(w http.ResponseWriter, r *http.Request) { - var latStr, lngStr string - err := r.ParseForm() - if err != nil { - render.Render(w, r, errRender(fmt.Errorf("Failed to parse GET form: %w", err))) - return - } else { - latStr = r.FormValue("lat") - lngStr = r.FormValue("lng") - } - lat, err := strconv.ParseFloat(latStr, 64) - if err != nil { - render.Render(w, r, errRender(fmt.Errorf("Failed to parse lat as float: %w", err))) - return - } - lng, err := strconv.ParseFloat(lngStr, 64) - if err != nil { - render.Render(w, r, errRender(fmt.Errorf("Failed to parse lng as float: %w", err))) - return - } - district, err := platform.DistrictForLocation(r.Context(), lng, lat) - if err != nil { - render.Render(w, r, errRender(fmt.Errorf("Failed to get district: %w", err))) - return - } - if district == nil { - http.NotFound(w, r) - return - } - d := ResponseDistrict{ - Agency: district.Agency.GetOr(""), - Manager: district.GeneralMG.GetOr(""), - Phone: district.Phone1.GetOr(""), - Website: district.Website.GetOr(""), - } - if err := render.Render(w, r, d); err != nil { - render.Render(w, r, errRender(err)) - } -} - func handleClientIos(w http.ResponseWriter, r *http.Request, u *models.User) { var sinceStr string err := r.ParseForm() diff --git a/api/district.go b/api/district.go new file mode 100644 index 00000000..360e473b --- /dev/null +++ b/api/district.go @@ -0,0 +1,74 @@ +package api + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/Gleipnir-Technology/nidus-sync/db" + "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/Gleipnir-Technology/nidus-sync/platform" + "github.com/Gleipnir-Technology/nidus-sync/userfile" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +func apiGetDistrict(w http.ResponseWriter, r *http.Request) { + var latStr, lngStr string + err := r.ParseForm() + if err != nil { + render.Render(w, r, errRender(fmt.Errorf("Failed to parse GET form: %w", err))) + return + } else { + latStr = r.FormValue("lat") + lngStr = r.FormValue("lng") + } + lat, err := strconv.ParseFloat(latStr, 64) + if err != nil { + render.Render(w, r, errRender(fmt.Errorf("Failed to parse lat as float: %w", err))) + return + } + lng, err := strconv.ParseFloat(lngStr, 64) + if err != nil { + render.Render(w, r, errRender(fmt.Errorf("Failed to parse lng as float: %w", err))) + return + } + district, _, err := platform.DistrictForLocation(r.Context(), lng, lat) + if err != nil { + render.Render(w, r, errRender(fmt.Errorf("Failed to get district: %w", err))) + return + } + if district == nil { + http.NotFound(w, r) + return + } + d := ResponseDistrict{ + Agency: district.Agency.GetOr(""), + Manager: district.GeneralMG.GetOr(""), + Phone: district.Phone1.GetOr(""), + Website: district.Website.GetOr(""), + } + if err := render.Render(w, r, d); err != nil { + render.Render(w, r, errRender(err)) + } +} + +func apiGetDistrictLogo(w http.ResponseWriter, r *http.Request) { + id_str := chi.URLParam(r, "id") + org_id, err := strconv.ParseInt(id_str, 10, 32) + if err != nil { + render.Render(w, r, errRender(fmt.Errorf("%s is not a recognized organization ID: %w", id_str, err))) + return + } + ctx := r.Context() + org, err := models.FindOrganization(ctx, db.PGInstance.BobDB, int32(org_id)) + if err != nil { + http.Error(w, "Organization not found", http.StatusNotFound) + return + } + if org.LogoUUID.IsNull() { + http.Error(w, "Logo not found", http.StatusNotFound) + return + } + userfile.ImageFileContentWriteLogo(w, org.LogoUUID.MustGet()) +} diff --git a/api/endpoint.go b/api/routes.go similarity index 95% rename from api/endpoint.go rename to api/routes.go index 14d59cb1..81458890 100644 --- a/api/endpoint.go +++ b/api/routes.go @@ -21,6 +21,7 @@ func AddRoutes(r chi.Router) { // Unauthenticated endpoints r.Get("/district", apiGetDistrict) + r.Get("/district/{id}/logo", apiGetDistrictLogo) r.Post("/twilio/message", twilioMessagePost) r.Post("/twilio/status", twilioStatusPost) r.Post("/twilio/text", twilioTextPost) diff --git a/arcgis-go b/arcgis-go index af786fab..09fda12a 160000 --- a/arcgis-go +++ b/arcgis-go @@ -1 +1 @@ -Subproject commit af786fabcc08ed506a23718a71aa0dd52ce047ac +Subproject commit 09fda12abb8af08b4d95ea519fdda73b5399bf63 diff --git a/background/arcgis.go b/background/arcgis.go index 94aae8ca..0e7e166a 100644 --- a/background/arcgis.go +++ b/background/arcgis.go @@ -72,7 +72,7 @@ func HandleOauthAccessCode(ctx context.Context, user *models.User, code string) "grant_type": []string{"authorization_code"}, "code": []string{code}, "client_id": []string{config.ClientID}, - "redirect_uri": []string{config.RedirectURL()}, + "redirect_uri": []string{config.ArcGISOauthRedirectURL()}, } req, err := http.NewRequest("POST", baseURL, strings.NewReader(form.Encode())) @@ -652,7 +652,7 @@ func refreshRefreshToken(ctx context.Context, oauth *models.OauthToken) error { form := url.Values{ "grant_type": []string{"exchange_refresh_token"}, "client_id": []string{config.ClientID}, - "redirect_uri": []string{config.RedirectURL()}, + "redirect_uri": []string{config.ArcGISOauthRedirectURL()}, "refresh_token": []string{oauth.RefreshToken}, } diff --git a/comms/email.go b/comms/email.go index 64d7f970..a2e2a992 100644 --- a/comms/email.go +++ b/comms/email.go @@ -106,10 +106,10 @@ type emailResponse struct { func contentEmailSubscriptionConfirmation(report_id string) contentEmailReportConfirmation { return contentEmailReportConfirmation{ - URLLogo: fmt.Sprintf("https://%s/static/img/nidus-logo-no-lettering-64.png", config.URLReport), - URLReportStatus: fmt.Sprintf("https://%s/status/%s", config.URLReport, report_id), - URLReportUnsubscribe: fmt.Sprintf("https://%s/report/%s/unsubscribe", config.URLReport, report_id), - URLViewInBrowser: fmt.Sprintf("https://%s/email/report/%s/subscription-confirmation", config.URLReport, report_id), + URLLogo: config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png"), + URLReportStatus: config.MakeURLReport("/status/%s", report_id), + URLReportUnsubscribe: config.MakeURLReport("/report/%s/unsubscribe", report_id), + URLViewInBrowser: config.MakeURLReport("/email/report/%s/subscription-confirmation", report_id), } } diff --git a/config/config.go b/config/config.go index e82e04ea..51a72f85 100644 --- a/config/config.go +++ b/config/config.go @@ -2,9 +2,7 @@ package config import ( "fmt" - "net/url" "os" - "strconv" "github.com/nyaruka/phonenumbers" ) @@ -13,7 +11,11 @@ var ( Bind string ClientID string ClientSecret string + DomainRMO string + DomainNidus string + DomainTegola string Environment string + FilesDirectoryLogo string FilesDirectoryPublic string FilesDirectoryUser string FieldseekerSchemaDirectory string @@ -23,47 +25,29 @@ var ( ForwardEmailReportUsername string MapboxToken string PGDSN string - RMODomain string RMOPhoneNumber phonenumbers.PhoneNumber - URLReport string - URLSync string - URLTegola string TwilioAuthToken string TwilioAccountSID string TwilioMessagingServiceSID string ) -// Build the ArcGIS authorization URL with PKCE -func BuildArcGISAuthURL(clientID string) string { - baseURL := "https://www.arcgis.com/sharing/rest/oauth2/authorize/" - - params := url.Values{} - params.Add("client_id", clientID) - params.Add("redirect_uri", RedirectURL()) - params.Add("response_type", "code") - //params.Add("code_challenge", generateCodeChallenge(codeVerifier)) - //params.Add("code_challenge_method", "S256") - - // See https://developers.arcgis.com/rest/users-groups-and-items/token/ - // expiration is defined in minutes - var expiration int - if IsProductionEnvironment() { - // 2 weeks is the maximum allowed - expiration = 20160 - } else { - expiration = 20 - } - params.Add("expiration", strconv.Itoa(expiration)) - - return baseURL + "?" + params.Encode() -} - func IsProductionEnvironment() bool { return Environment == "PRODUCTION" } -func MakeURLSync(path string) string { - return fmt.Sprintf("https://%s%s", URLSync, path) +func makeURL(domain, path string, args ...interface{}) string { + pattern := "https://" + domain + path + return fmt.Sprintf(pattern, args...) +} + +func MakeURLNidus(path string, args ...interface{}) string { + return makeURL(DomainNidus, path, args...) +} +func MakeURLReport(path string, args ...interface{}) string { + return makeURL(DomainRMO, path, args...) +} +func MakeURLTegola(path string, args ...interface{}) string { + return makeURL(DomainTegola, path, args...) } func Parse() (err error) { @@ -79,6 +63,18 @@ func Parse() (err error) { if ClientSecret == "" { return fmt.Errorf("You must specify a non-empty ARCGIS_CLIENT_SECRET") } + DomainNidus = os.Getenv("DOMAIN_NIDUS") + if DomainNidus == "" { + return fmt.Errorf("You must specify a non-empty DOMAIN_NIDUS") + } + DomainRMO = os.Getenv("DOMAIN_RMO") + if DomainRMO == "" { + return fmt.Errorf("You must specify a non-empty DOMAIN_RMO") + } + DomainTegola = os.Getenv("DOMAIN_TEGOLA") + if DomainTegola == "" { + return fmt.Errorf("You must specify a non-empty DOMAIN_TEGOLA") + } Environment = os.Getenv("ENVIRONMENT") if Environment == "" { return fmt.Errorf("You must specify a non-empty ENVIRONMENT") @@ -90,6 +86,10 @@ func Parse() (err error) { if FieldseekerSchemaDirectory == "" { return fmt.Errorf("You must specify a non-empty FIELDSEEKER_SCHEMA_DIRECTORY") } + FilesDirectoryLogo = os.Getenv("FILES_DIRECTORY_LOGO") + if FilesDirectoryLogo == "" { + return fmt.Errorf("You must specify a non-empty FILES_DIRECTORY_LOGO") + } FilesDirectoryPublic = os.Getenv("FILES_DIRECTORY_PUBLIC") if FilesDirectoryPublic == "" { return fmt.Errorf("You must specify a non-empty FILES_DIRECTORY_PUBLIC") @@ -122,10 +122,6 @@ func Parse() (err error) { if PGDSN == "" { return fmt.Errorf("You must specify a non-empty POSTGRES_DSN") } - RMODomain = os.Getenv("RMO_DOMAIN") - if RMODomain == "" { - return fmt.Errorf("You must specify a non-empty RMO_DOMAIN") - } rmo_phone_number := os.Getenv("RMO_PHONE_NUMBER") if rmo_phone_number == "" { return fmt.Errorf("You must specify a non-empty RMO_PHONE_NUMBER") @@ -136,18 +132,6 @@ func Parse() (err error) { } RMOPhoneNumber = *p - URLReport = os.Getenv("URL_REPORT") - if URLReport == "" { - return fmt.Errorf("You must specify a non-empty URL_REPORT") - } - URLSync = os.Getenv("URL_SYNC") - if URLSync == "" { - return fmt.Errorf("You must specify a non-empty URL_SYNC") - } - URLTegola = os.Getenv("URL_TEGOLA") - if URLTegola == "" { - return fmt.Errorf("You must specify a non-empty URL_TEGOLA") - } TwilioAccountSID = os.Getenv("TWILIO_ACCOUNT_SID") if TwilioAccountSID == "" { return fmt.Errorf("You must specify a non-empty TWILIO_ACCOUNT_SID") @@ -163,6 +147,6 @@ func Parse() (err error) { return nil } -func RedirectURL() string { - return MakeURLSync("/arcgis/oauth/callback") +func ArcGISOauthRedirectURL() string { + return MakeURLNidus("/arcgis/oauth/callback") } diff --git a/db/bobgen.yaml b/db/bobgen.yaml index 1480da9e..92053a07 100644 --- a/db/bobgen.yaml +++ b/db/bobgen.yaml @@ -4,6 +4,9 @@ aliases: up_singular: "ArcgisUser" down_plural: "arcgisusers" down_singular: "arcgisuser" + organization: + relationships: + publicreport.pool.pool_organization_id_fkey: "PublicreportPool" user_: up_plural: "Users" up_singular: "User" diff --git a/db/dbinfo/organization.bob.go b/db/dbinfo/organization.bob.go index cd42ae32..5013dc15 100644 --- a/db/dbinfo/organization.bob.go +++ b/db/dbinfo/organization.bob.go @@ -78,6 +78,15 @@ var Organizations = Table[ Generated: false, AutoIncr: false, }, + LogoUUID: column{ + Name: "logo_uuid", + DBType: "uuid", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, }, Indexes: organizationIndexes{ OrganizationPkey: index{ @@ -172,11 +181,12 @@ type organizationColumns struct { FieldseekerURL column ImportDistrictGid column Website column + LogoUUID column } func (c organizationColumns) AsSlice() []column { return []column{ - c.ID, c.Name, c.ArcgisID, c.ArcgisName, c.FieldseekerURL, c.ImportDistrictGid, c.Website, + c.ID, c.Name, c.ArcgisID, c.ArcgisName, c.FieldseekerURL, c.ImportDistrictGid, c.Website, c.LogoUUID, } } diff --git a/db/dbinfo/publicreport.nuisance.bob.go b/db/dbinfo/publicreport.nuisance.bob.go index 625162eb..20debe36 100644 --- a/db/dbinfo/publicreport.nuisance.bob.go +++ b/db/dbinfo/publicreport.nuisance.bob.go @@ -258,6 +258,15 @@ var PublicreportNuisances = Table[ Generated: false, AutoIncr: false, }, + OrganizationID: column{ + Name: "organization_id", + DBType: "integer", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, }, Indexes: publicreportNuisanceIndexes{ NuisancePkey: index{ @@ -300,7 +309,17 @@ var PublicreportNuisances = Table[ Columns: []string{"id"}, Comment: "", }, - + ForeignKeys: publicreportNuisanceForeignKeys{ + PublicreportNuisanceNuisanceOrganizationIDFkey: foreignKey{ + constraint: constraint{ + Name: "publicreport.nuisance.nuisance_organization_id_fkey", + Columns: []string{"organization_id"}, + Comment: "", + }, + ForeignTable: "organization", + ForeignColumns: []string{"id"}, + }, + }, Uniques: publicreportNuisanceUniques{ NuisancePublicIDKey: constraint{ Name: "nuisance_public_id_key", @@ -340,11 +359,12 @@ type publicreportNuisanceColumns struct { Address column Location column Status column + OrganizationID column } func (c publicreportNuisanceColumns) AsSlice() []column { return []column{ - c.ID, c.AdditionalInfo, c.Created, c.Duration, c.Email, c.InspectionType, c.SourceLocation, c.PreferredDateRange, c.PreferredTime, c.RequestCall, c.Severity, c.SourceContainer, c.SourceDescription, c.SourceRoof, c.SourceStagnant, c.TimeOfDayDay, c.TimeOfDayEarly, c.TimeOfDayEvening, c.TimeOfDayNight, c.PublicID, c.ReporterAddress, c.ReporterEmail, c.ReporterName, c.ReporterPhone, c.Address, c.Location, c.Status, + c.ID, c.AdditionalInfo, c.Created, c.Duration, c.Email, c.InspectionType, c.SourceLocation, c.PreferredDateRange, c.PreferredTime, c.RequestCall, c.Severity, c.SourceContainer, c.SourceDescription, c.SourceRoof, c.SourceStagnant, c.TimeOfDayDay, c.TimeOfDayEarly, c.TimeOfDayEvening, c.TimeOfDayNight, c.PublicID, c.ReporterAddress, c.ReporterEmail, c.ReporterName, c.ReporterPhone, c.Address, c.Location, c.Status, c.OrganizationID, } } @@ -359,10 +379,14 @@ func (i publicreportNuisanceIndexes) AsSlice() []index { } } -type publicreportNuisanceForeignKeys struct{} +type publicreportNuisanceForeignKeys struct { + PublicreportNuisanceNuisanceOrganizationIDFkey foreignKey +} func (f publicreportNuisanceForeignKeys) AsSlice() []foreignKey { - return []foreignKey{} + return []foreignKey{ + f.PublicreportNuisanceNuisanceOrganizationIDFkey, + } } type publicreportNuisanceUniques struct { diff --git a/db/dbinfo/publicreport.pool.bob.go b/db/dbinfo/publicreport.pool.bob.go index 045b24d7..a749fdba 100644 --- a/db/dbinfo/publicreport.pool.bob.go +++ b/db/dbinfo/publicreport.pool.bob.go @@ -285,6 +285,15 @@ var PublicreportPools = Table[ Generated: false, AutoIncr: false, }, + OrganizationID: column{ + Name: "organization_id", + DBType: "integer", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, }, Indexes: publicreportPoolIndexes{ PoolPkey: index{ @@ -327,7 +336,17 @@ var PublicreportPools = Table[ Columns: []string{"id"}, Comment: "", }, - + ForeignKeys: publicreportPoolForeignKeys{ + PublicreportPoolPoolOrganizationIDFkey: foreignKey{ + constraint: constraint{ + Name: "publicreport.pool.pool_organization_id_fkey", + Columns: []string{"organization_id"}, + Comment: "", + }, + ForeignTable: "organization", + ForeignColumns: []string{"id"}, + }, + }, Uniques: publicreportPoolUniques{ PoolPublicIDKey: constraint{ Name: "pool_public_id_key", @@ -370,11 +389,12 @@ type publicreportPoolColumns struct { ReporterPhone column Subscribe column Status column + OrganizationID 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, c.Status, + 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, c.Status, c.OrganizationID, } } @@ -389,10 +409,14 @@ func (i publicreportPoolIndexes) AsSlice() []index { } } -type publicreportPoolForeignKeys struct{} +type publicreportPoolForeignKeys struct { + PublicreportPoolPoolOrganizationIDFkey foreignKey +} func (f publicreportPoolForeignKeys) AsSlice() []foreignKey { - return []foreignKey{} + return []foreignKey{ + f.PublicreportPoolPoolOrganizationIDFkey, + } } type publicreportPoolUniques struct { diff --git a/db/dbinfo/publicreport.quick.bob.go b/db/dbinfo/publicreport.quick.bob.go index 77afb678..a986b5b8 100644 --- a/db/dbinfo/publicreport.quick.bob.go +++ b/db/dbinfo/publicreport.quick.bob.go @@ -105,6 +105,15 @@ var PublicreportQuicks = Table[ Generated: false, AutoIncr: false, }, + OrganizationID: column{ + Name: "organization_id", + DBType: "integer", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, }, Indexes: publicreportQuickIndexes{ QuickPkey: index{ @@ -147,7 +156,17 @@ var PublicreportQuicks = Table[ Columns: []string{"id"}, Comment: "", }, - + ForeignKeys: publicreportQuickForeignKeys{ + PublicreportQuickQuickOrganizationIDFkey: foreignKey{ + constraint: constraint{ + Name: "publicreport.quick.quick_organization_id_fkey", + Columns: []string{"organization_id"}, + Comment: "", + }, + ForeignTable: "organization", + ForeignColumns: []string{"id"}, + }, + }, Uniques: publicreportQuickUniques{ QuickPublicIDKey: constraint{ Name: "quick_public_id_key", @@ -160,21 +179,22 @@ var PublicreportQuicks = Table[ } type publicreportQuickColumns struct { - ID column - Created column - Comments column - Location column - H3cell column - PublicID column - ReporterEmail column - ReporterPhone column - Address column - Status column + ID column + Created column + Comments column + Location column + H3cell column + PublicID column + ReporterEmail column + ReporterPhone column + Address column + Status column + OrganizationID column } func (c publicreportQuickColumns) AsSlice() []column { return []column{ - c.ID, c.Created, c.Comments, c.Location, c.H3cell, c.PublicID, c.ReporterEmail, c.ReporterPhone, c.Address, c.Status, + c.ID, c.Created, c.Comments, c.Location, c.H3cell, c.PublicID, c.ReporterEmail, c.ReporterPhone, c.Address, c.Status, c.OrganizationID, } } @@ -189,10 +209,14 @@ func (i publicreportQuickIndexes) AsSlice() []index { } } -type publicreportQuickForeignKeys struct{} +type publicreportQuickForeignKeys struct { + PublicreportQuickQuickOrganizationIDFkey foreignKey +} func (f publicreportQuickForeignKeys) AsSlice() []foreignKey { - return []foreignKey{} + return []foreignKey{ + f.PublicreportQuickQuickOrganizationIDFkey, + } } type publicreportQuickUniques struct { diff --git a/db/factory/bobfactory_context.bob.go b/db/factory/bobfactory_context.bob.go index ac0e7e05..6c9c023b 100644 --- a/db/factory/bobfactory_context.bob.go +++ b/db/factory/bobfactory_context.bob.go @@ -240,6 +240,9 @@ var ( organizationRelNoteAudiosCtx = newContextual[bool]("note_audio.organization.note_audio.note_audio_organization_id_fkey") organizationRelNoteImagesCtx = newContextual[bool]("note_image.organization.note_image.note_image_organization_id_fkey") organizationRelImportDistrictGidDistrictCtx = newContextual[bool]("import.district.organization.organization.organization_import_district_gid_fkey") + organizationRelNuisancesCtx = newContextual[bool]("organization.publicreport.nuisance.publicreport.nuisance.nuisance_organization_id_fkey") + organizationRelPublicreportPoolCtx = newContextual[bool]("organization.publicreport.pool.publicreport.pool.pool_organization_id_fkey") + organizationRelQuicksCtx = newContextual[bool]("organization.publicreport.quick.publicreport.quick.quick_organization_id_fkey") organizationRelUserCtx = newContextual[bool]("organization.user_.user_.user__organization_id_fkey") // Relationship Contexts for publicreport.image @@ -254,9 +257,11 @@ var ( // Relationship Contexts for publicreport.nuisance publicreportNuisanceWithParentsCascadingCtx = newContextual[bool]("publicreportNuisanceWithParentsCascading") + publicreportNuisanceRelOrganizationCtx = newContextual[bool]("organization.publicreport.nuisance.publicreport.nuisance.nuisance_organization_id_fkey") // Relationship Contexts for publicreport.pool publicreportPoolWithParentsCascadingCtx = newContextual[bool]("publicreportPoolWithParentsCascading") + publicreportPoolRelOrganizationCtx = newContextual[bool]("organization.publicreport.pool.publicreport.pool.pool_organization_id_fkey") publicreportPoolRelImagesCtx = newContextual[bool]("publicreport.image.publicreport.pool.publicreport.pool_image.pool_image_image_id_fkeypublicreport.pool_image.pool_image_pool_id_fkey") // Relationship Contexts for publicreport.pool_image @@ -266,6 +271,7 @@ var ( // Relationship Contexts for publicreport.quick publicreportQuickWithParentsCascadingCtx = newContextual[bool]("publicreportQuickWithParentsCascading") + publicreportQuickRelOrganizationCtx = newContextual[bool]("organization.publicreport.quick.publicreport.quick.quick_organization_id_fkey") publicreportQuickRelImagesCtx = newContextual[bool]("publicreport.image.publicreport.quick.publicreport.quick_image.quick_image_image_id_fkeypublicreport.quick_image.quick_image_quick_id_fkey") // Relationship Contexts for publicreport.quick_image diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index e49d9b54..d1f5124a 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -2550,6 +2550,7 @@ func (f *Factory) FromExistingOrganization(m *models.Organization) *Organization o.FieldseekerURL = func() null.Val[string] { return m.FieldseekerURL } o.ImportDistrictGid = func() null.Val[int32] { return m.ImportDistrictGid } o.Website = func() null.Val[string] { return m.Website } + o.LogoUUID = func() null.Val[uuid.UUID] { return m.LogoUUID } ctx := context.Background() if len(m.R.Containerrelates) > 0 { @@ -2648,6 +2649,15 @@ func (f *Factory) FromExistingOrganization(m *models.Organization) *Organization if m.R.ImportDistrictGidDistrict != nil { OrganizationMods.WithExistingImportDistrictGidDistrict(m.R.ImportDistrictGidDistrict).Apply(ctx, o) } + if len(m.R.Nuisances) > 0 { + OrganizationMods.AddExistingNuisances(m.R.Nuisances...).Apply(ctx, o) + } + if len(m.R.PublicreportPool) > 0 { + OrganizationMods.AddExistingPublicreportPool(m.R.PublicreportPool...).Apply(ctx, o) + } + if len(m.R.Quicks) > 0 { + OrganizationMods.AddExistingQuicks(m.R.Quicks...).Apply(ctx, o) + } if len(m.R.User) > 0 { OrganizationMods.AddExistingUser(m.R.User...).Apply(ctx, o) } @@ -2775,6 +2785,12 @@ func (f *Factory) FromExistingPublicreportNuisance(m *models.PublicreportNuisanc o.Address = func() string { return m.Address } o.Location = func() null.Val[string] { return m.Location } o.Status = func() enums.PublicreportReportstatustype { return m.Status } + o.OrganizationID = func() null.Val[int32] { return m.OrganizationID } + + ctx := context.Background() + if m.R.Organization != nil { + PublicreportNuisanceMods.WithExistingOrganization(m.R.Organization).Apply(ctx, o) + } return o } @@ -2828,8 +2844,12 @@ func (f *Factory) FromExistingPublicreportPool(m *models.PublicreportPool) *Publ o.ReporterPhone = func() string { return m.ReporterPhone } o.Subscribe = func() bool { return m.Subscribe } o.Status = func() enums.PublicreportReportstatustype { return m.Status } + o.OrganizationID = func() null.Val[int32] { return m.OrganizationID } ctx := context.Background() + if m.R.Organization != nil { + PublicreportPoolMods.WithExistingOrganization(m.R.Organization).Apply(ctx, o) + } if len(m.R.Images) > 0 { PublicreportPoolMods.AddExistingImages(m.R.Images...).Apply(ctx, o) } @@ -2899,8 +2919,12 @@ func (f *Factory) FromExistingPublicreportQuick(m *models.PublicreportQuick) *Pu o.ReporterPhone = func() string { return m.ReporterPhone } o.Address = func() string { return m.Address } o.Status = func() enums.PublicreportReportstatustype { return m.Status } + o.OrganizationID = func() null.Val[int32] { return m.OrganizationID } ctx := context.Background() + if m.R.Organization != nil { + PublicreportQuickMods.WithExistingOrganization(m.R.Organization).Apply(ctx, o) + } if len(m.R.Images) > 0 { PublicreportQuickMods.AddExistingImages(m.R.Images...).Apply(ctx, o) } diff --git a/db/factory/organization.bob.go b/db/factory/organization.bob.go index 957d1c96..a30832a3 100644 --- a/db/factory/organization.bob.go +++ b/db/factory/organization.bob.go @@ -11,6 +11,7 @@ import ( "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" + "github.com/google/uuid" "github.com/jaswdr/faker/v2" "github.com/stephenafamo/bob" ) @@ -43,6 +44,7 @@ type OrganizationTemplate struct { FieldseekerURL func() null.Val[string] ImportDistrictGid func() null.Val[int32] Website func() null.Val[string] + LogoUUID func() null.Val[uuid.UUID] r organizationR f *Factory @@ -83,6 +85,9 @@ type organizationR struct { NoteAudios []*organizationRNoteAudiosR NoteImages []*organizationRNoteImagesR ImportDistrictGidDistrict *organizationRImportDistrictGidDistrictR + Nuisances []*organizationRNuisancesR + PublicreportPool []*organizationRPublicreportPoolR + Quicks []*organizationRQuicksR User []*organizationRUserR } @@ -213,6 +218,18 @@ type organizationRNoteImagesR struct { type organizationRImportDistrictGidDistrictR struct { o *ImportDistrictTemplate } +type organizationRNuisancesR struct { + number int + o *PublicreportNuisanceTemplate +} +type organizationRPublicreportPoolR struct { + number int + o *PublicreportPoolTemplate +} +type organizationRQuicksR struct { + number int + o *PublicreportQuickTemplate +} type organizationRUserR struct { number int o *UserTemplate @@ -638,6 +655,45 @@ func (t OrganizationTemplate) setModelRels(o *models.Organization) { o.R.ImportDistrictGidDistrict = rel } + if t.r.Nuisances != nil { + rel := models.PublicreportNuisanceSlice{} + for _, r := range t.r.Nuisances { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.OrganizationID = null.From(o.ID) // h2 + rel.R.Organization = o + } + rel = append(rel, related...) + } + o.R.Nuisances = rel + } + + if t.r.PublicreportPool != nil { + rel := models.PublicreportPoolSlice{} + for _, r := range t.r.PublicreportPool { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.OrganizationID = null.From(o.ID) // h2 + rel.R.Organization = o + } + rel = append(rel, related...) + } + o.R.PublicreportPool = rel + } + + if t.r.Quicks != nil { + rel := models.PublicreportQuickSlice{} + for _, r := range t.r.Quicks { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.OrganizationID = null.From(o.ID) // h2 + rel.R.Organization = o + } + rel = append(rel, related...) + } + o.R.Quicks = rel + } + if t.r.User != nil { rel := models.UserSlice{} for _, r := range t.r.User { @@ -685,6 +741,10 @@ func (o OrganizationTemplate) BuildSetter() *models.OrganizationSetter { val := o.Website() m.Website = omitnull.FromNull(val) } + if o.LogoUUID != nil { + val := o.LogoUUID() + m.LogoUUID = omitnull.FromNull(val) + } return m } @@ -728,6 +788,9 @@ func (o OrganizationTemplate) Build() *models.Organization { if o.Website != nil { m.Website = o.Website() } + if o.LogoUUID != nil { + m.LogoUUID = o.LogoUUID() + } o.setModelRels(m) @@ -1399,6 +1462,66 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu } + isNuisancesDone, _ := organizationRelNuisancesCtx.Value(ctx) + if !isNuisancesDone && o.r.Nuisances != nil { + ctx = organizationRelNuisancesCtx.WithValue(ctx, true) + for _, r := range o.r.Nuisances { + if r.o.alreadyPersisted { + m.R.Nuisances = append(m.R.Nuisances, r.o.Build()) + } else { + rel32, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachNuisances(ctx, exec, rel32...) + if err != nil { + return err + } + } + } + } + + isPublicreportPoolDone, _ := organizationRelPublicreportPoolCtx.Value(ctx) + if !isPublicreportPoolDone && o.r.PublicreportPool != nil { + ctx = organizationRelPublicreportPoolCtx.WithValue(ctx, true) + for _, r := range o.r.PublicreportPool { + if r.o.alreadyPersisted { + m.R.PublicreportPool = append(m.R.PublicreportPool, r.o.Build()) + } else { + rel33, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachPublicreportPool(ctx, exec, rel33...) + if err != nil { + return err + } + } + } + } + + isQuicksDone, _ := organizationRelQuicksCtx.Value(ctx) + if !isQuicksDone && o.r.Quicks != nil { + ctx = organizationRelQuicksCtx.WithValue(ctx, true) + for _, r := range o.r.Quicks { + if r.o.alreadyPersisted { + m.R.Quicks = append(m.R.Quicks, r.o.Build()) + } else { + rel34, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachQuicks(ctx, exec, rel34...) + if err != nil { + return err + } + } + } + } + isUserDone, _ := organizationRelUserCtx.Value(ctx) if !isUserDone && o.r.User != nil { ctx = organizationRelUserCtx.WithValue(ctx, true) @@ -1406,12 +1529,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.User = append(m.R.User, r.o.Build()) } else { - rel32, err := r.o.CreateMany(ctx, exec, r.number) + rel35, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachUser(ctx, exec, rel32...) + err = m.AttachUser(ctx, exec, rel35...) if err != nil { return err } @@ -1518,6 +1641,7 @@ func (m organizationMods) RandomizeAllColumns(f *faker.Faker) OrganizationMod { OrganizationMods.RandomFieldseekerURL(f), OrganizationMods.RandomImportDistrictGid(f), OrganizationMods.RandomWebsite(f), + OrganizationMods.RandomLogoUUID(f), } } @@ -1848,6 +1972,59 @@ func (m organizationMods) RandomWebsiteNotNull(f *faker.Faker) OrganizationMod { }) } +// Set the model columns to this value +func (m organizationMods) LogoUUID(val null.Val[uuid.UUID]) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.LogoUUID = func() null.Val[uuid.UUID] { return val } + }) +} + +// Set the Column from the function +func (m organizationMods) LogoUUIDFunc(f func() null.Val[uuid.UUID]) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.LogoUUID = f + }) +} + +// Clear any values for the column +func (m organizationMods) UnsetLogoUUID() OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.LogoUUID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m organizationMods) RandomLogoUUID(f *faker.Faker) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.LogoUUID = func() null.Val[uuid.UUID] { + if f == nil { + f = &defaultFaker + } + + val := random_uuid_UUID(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m organizationMods) RandomLogoUUIDNotNull(f *faker.Faker) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.LogoUUID = func() null.Val[uuid.UUID] { + if f == nil { + f = &defaultFaker + } + + val := random_uuid_UUID(f) + return null.From(val) + } + }) +} + func (m organizationMods) WithParentsCascading() OrganizationMod { return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { if isDone, _ := organizationWithParentsCascadingCtx.Value(ctx); isDone { @@ -3380,6 +3557,150 @@ func (m organizationMods) WithoutNoteImages() OrganizationMod { }) } +func (m organizationMods) WithNuisances(number int, related *PublicreportNuisanceTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Nuisances = []*organizationRNuisancesR{{ + number: number, + o: related, + }} + }) +} + +func (m organizationMods) WithNewNuisances(number int, mods ...PublicreportNuisanceMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewPublicreportNuisanceWithContext(ctx, mods...) + m.WithNuisances(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddNuisances(number int, related *PublicreportNuisanceTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Nuisances = append(o.r.Nuisances, &organizationRNuisancesR{ + number: number, + o: related, + }) + }) +} + +func (m organizationMods) AddNewNuisances(number int, mods ...PublicreportNuisanceMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewPublicreportNuisanceWithContext(ctx, mods...) + m.AddNuisances(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddExistingNuisances(existingModels ...*models.PublicreportNuisance) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + for _, em := range existingModels { + o.r.Nuisances = append(o.r.Nuisances, &organizationRNuisancesR{ + o: o.f.FromExistingPublicreportNuisance(em), + }) + } + }) +} + +func (m organizationMods) WithoutNuisances() OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Nuisances = nil + }) +} + +func (m organizationMods) WithPublicreportPool(number int, related *PublicreportPoolTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.PublicreportPool = []*organizationRPublicreportPoolR{{ + number: number, + o: related, + }} + }) +} + +func (m organizationMods) WithNewPublicreportPool(number int, mods ...PublicreportPoolMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewPublicreportPoolWithContext(ctx, mods...) + m.WithPublicreportPool(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddPublicreportPool(number int, related *PublicreportPoolTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.PublicreportPool = append(o.r.PublicreportPool, &organizationRPublicreportPoolR{ + number: number, + o: related, + }) + }) +} + +func (m organizationMods) AddNewPublicreportPool(number int, mods ...PublicreportPoolMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewPublicreportPoolWithContext(ctx, mods...) + m.AddPublicreportPool(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddExistingPublicreportPool(existingModels ...*models.PublicreportPool) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + for _, em := range existingModels { + o.r.PublicreportPool = append(o.r.PublicreportPool, &organizationRPublicreportPoolR{ + o: o.f.FromExistingPublicreportPool(em), + }) + } + }) +} + +func (m organizationMods) WithoutPublicreportPool() OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.PublicreportPool = nil + }) +} + +func (m organizationMods) WithQuicks(number int, related *PublicreportQuickTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Quicks = []*organizationRQuicksR{{ + number: number, + o: related, + }} + }) +} + +func (m organizationMods) WithNewQuicks(number int, mods ...PublicreportQuickMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewPublicreportQuickWithContext(ctx, mods...) + m.WithQuicks(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddQuicks(number int, related *PublicreportQuickTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Quicks = append(o.r.Quicks, &organizationRQuicksR{ + number: number, + o: related, + }) + }) +} + +func (m organizationMods) AddNewQuicks(number int, mods ...PublicreportQuickMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewPublicreportQuickWithContext(ctx, mods...) + m.AddQuicks(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddExistingQuicks(existingModels ...*models.PublicreportQuick) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + for _, em := range existingModels { + o.r.Quicks = append(o.r.Quicks, &organizationRQuicksR{ + o: o.f.FromExistingPublicreportQuick(em), + }) + } + }) +} + +func (m organizationMods) WithoutQuicks() OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Quicks = nil + }) +} + func (m organizationMods) WithUser(number int, related *UserTemplate) OrganizationMod { return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { o.r.User = []*organizationRUserR{{ diff --git a/db/factory/publicreport.nuisance.bob.go b/db/factory/publicreport.nuisance.bob.go index 3590e4e1..faccd615 100644 --- a/db/factory/publicreport.nuisance.bob.go +++ b/db/factory/publicreport.nuisance.bob.go @@ -65,12 +65,22 @@ type PublicreportNuisanceTemplate struct { Address func() string Location func() null.Val[string] Status func() enums.PublicreportReportstatustype + OrganizationID func() null.Val[int32] + r publicreportNuisanceR f *Factory alreadyPersisted bool } +type publicreportNuisanceR struct { + Organization *publicreportNuisanceROrganizationR +} + +type publicreportNuisanceROrganizationR struct { + o *OrganizationTemplate +} + // Apply mods to the PublicreportNuisanceTemplate func (o *PublicreportNuisanceTemplate) Apply(ctx context.Context, mods ...PublicreportNuisanceMod) { for _, mod := range mods { @@ -80,7 +90,14 @@ func (o *PublicreportNuisanceTemplate) Apply(ctx context.Context, mods ...Public // setModelRels creates and sets the relationships on *models.PublicreportNuisance // according to the relationships in the template. Nothing is inserted into the db -func (t PublicreportNuisanceTemplate) setModelRels(o *models.PublicreportNuisance) {} +func (t PublicreportNuisanceTemplate) setModelRels(o *models.PublicreportNuisance) { + if t.r.Organization != nil { + rel := t.r.Organization.o.Build() + rel.R.Nuisances = append(rel.R.Nuisances, o) + o.OrganizationID = null.From(rel.ID) // h2 + o.R.Organization = rel + } +} // BuildSetter returns an *models.PublicreportNuisanceSetter // this does nothing with the relationship templates @@ -195,6 +212,10 @@ func (o PublicreportNuisanceTemplate) BuildSetter() *models.PublicreportNuisance val := o.Status() m.Status = omit.From(val) } + if o.OrganizationID != nil { + val := o.OrganizationID() + m.OrganizationID = omitnull.FromNull(val) + } return m } @@ -298,6 +319,9 @@ func (o PublicreportNuisanceTemplate) Build() *models.PublicreportNuisance { if o.Status != nil { m.Status = o.Status() } + if o.OrganizationID != nil { + m.OrganizationID = o.OrganizationID() + } o.setModelRels(m) @@ -426,6 +450,25 @@ func ensureCreatablePublicreportNuisance(m *models.PublicreportNuisanceSetter) { func (o *PublicreportNuisanceTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.PublicreportNuisance) error { var err error + isOrganizationDone, _ := publicreportNuisanceRelOrganizationCtx.Value(ctx) + if !isOrganizationDone && o.r.Organization != nil { + ctx = publicreportNuisanceRelOrganizationCtx.WithValue(ctx, true) + if o.r.Organization.o.alreadyPersisted { + m.R.Organization = o.r.Organization.o.Build() + } else { + var rel0 *models.Organization + rel0, err = o.r.Organization.o.Create(ctx, exec) + if err != nil { + return err + } + err = m.AttachOrganization(ctx, exec, rel0) + if err != nil { + return err + } + } + + } + return err } @@ -545,6 +588,7 @@ func (m publicreportNuisanceMods) RandomizeAllColumns(f *faker.Faker) Publicrepo PublicreportNuisanceMods.RandomAddress(f), PublicreportNuisanceMods.RandomLocation(f), PublicreportNuisanceMods.RandomStatus(f), + PublicreportNuisanceMods.RandomOrganizationID(f), } } @@ -1407,11 +1451,99 @@ func (m publicreportNuisanceMods) RandomStatus(f *faker.Faker) PublicreportNuisa }) } +// Set the model columns to this value +func (m publicreportNuisanceMods) OrganizationID(val null.Val[int32]) PublicreportNuisanceMod { + return PublicreportNuisanceModFunc(func(_ context.Context, o *PublicreportNuisanceTemplate) { + o.OrganizationID = func() null.Val[int32] { return val } + }) +} + +// Set the Column from the function +func (m publicreportNuisanceMods) OrganizationIDFunc(f func() null.Val[int32]) PublicreportNuisanceMod { + return PublicreportNuisanceModFunc(func(_ context.Context, o *PublicreportNuisanceTemplate) { + o.OrganizationID = f + }) +} + +// Clear any values for the column +func (m publicreportNuisanceMods) UnsetOrganizationID() PublicreportNuisanceMod { + return PublicreportNuisanceModFunc(func(_ context.Context, o *PublicreportNuisanceTemplate) { + o.OrganizationID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m publicreportNuisanceMods) RandomOrganizationID(f *faker.Faker) PublicreportNuisanceMod { + return PublicreportNuisanceModFunc(func(_ context.Context, o *PublicreportNuisanceTemplate) { + o.OrganizationID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m publicreportNuisanceMods) RandomOrganizationIDNotNull(f *faker.Faker) PublicreportNuisanceMod { + return PublicreportNuisanceModFunc(func(_ context.Context, o *PublicreportNuisanceTemplate) { + o.OrganizationID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + func (m publicreportNuisanceMods) WithParentsCascading() PublicreportNuisanceMod { return PublicreportNuisanceModFunc(func(ctx context.Context, o *PublicreportNuisanceTemplate) { if isDone, _ := publicreportNuisanceWithParentsCascadingCtx.Value(ctx); isDone { return } ctx = publicreportNuisanceWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewOrganizationWithContext(ctx, OrganizationMods.WithParentsCascading()) + m.WithOrganization(related).Apply(ctx, o) + } + }) +} + +func (m publicreportNuisanceMods) WithOrganization(rel *OrganizationTemplate) PublicreportNuisanceMod { + return PublicreportNuisanceModFunc(func(ctx context.Context, o *PublicreportNuisanceTemplate) { + o.r.Organization = &publicreportNuisanceROrganizationR{ + o: rel, + } + }) +} + +func (m publicreportNuisanceMods) WithNewOrganization(mods ...OrganizationMod) PublicreportNuisanceMod { + return PublicreportNuisanceModFunc(func(ctx context.Context, o *PublicreportNuisanceTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + + m.WithOrganization(related).Apply(ctx, o) + }) +} + +func (m publicreportNuisanceMods) WithExistingOrganization(em *models.Organization) PublicreportNuisanceMod { + return PublicreportNuisanceModFunc(func(ctx context.Context, o *PublicreportNuisanceTemplate) { + o.r.Organization = &publicreportNuisanceROrganizationR{ + o: o.f.FromExistingOrganization(em), + } + }) +} + +func (m publicreportNuisanceMods) WithoutOrganization() PublicreportNuisanceMod { + return PublicreportNuisanceModFunc(func(ctx context.Context, o *PublicreportNuisanceTemplate) { + o.r.Organization = nil }) } diff --git a/db/factory/publicreport.pool.bob.go b/db/factory/publicreport.pool.bob.go index 72b2f50e..103060a5 100644 --- a/db/factory/publicreport.pool.bob.go +++ b/db/factory/publicreport.pool.bob.go @@ -68,6 +68,7 @@ type PublicreportPoolTemplate struct { ReporterPhone func() string Subscribe func() bool Status func() enums.PublicreportReportstatustype + OrganizationID func() null.Val[int32] r publicreportPoolR f *Factory @@ -76,9 +77,13 @@ type PublicreportPoolTemplate struct { } type publicreportPoolR struct { - Images []*publicreportPoolRImagesR + Organization *publicreportPoolROrganizationR + Images []*publicreportPoolRImagesR } +type publicreportPoolROrganizationR struct { + o *OrganizationTemplate +} type publicreportPoolRImagesR struct { number int o *PublicreportImageTemplate @@ -94,6 +99,13 @@ func (o *PublicreportPoolTemplate) Apply(ctx context.Context, mods ...Publicrepo // setModelRels creates and sets the relationships on *models.PublicreportPool // according to the relationships in the template. Nothing is inserted into the db func (t PublicreportPoolTemplate) setModelRels(o *models.PublicreportPool) { + if t.r.Organization != nil { + rel := t.r.Organization.o.Build() + rel.R.PublicreportPool = append(rel.R.PublicreportPool, o) + o.OrganizationID = null.From(rel.ID) // h2 + o.R.Organization = rel + } + if t.r.Images != nil { rel := models.PublicreportImageSlice{} for _, r := range t.r.Images { @@ -232,6 +244,10 @@ func (o PublicreportPoolTemplate) BuildSetter() *models.PublicreportPoolSetter { val := o.Status() m.Status = omit.From(val) } + if o.OrganizationID != nil { + val := o.OrganizationID() + m.OrganizationID = omitnull.FromNull(val) + } return m } @@ -344,6 +360,9 @@ func (o PublicreportPoolTemplate) Build() *models.PublicreportPool { if o.Status != nil { m.Status = o.Status() } + if o.OrganizationID != nil { + m.OrganizationID = o.OrganizationID() + } o.setModelRels(m) @@ -480,6 +499,25 @@ func ensureCreatablePublicreportPool(m *models.PublicreportPoolSetter) { func (o *PublicreportPoolTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.PublicreportPool) error { var err error + isOrganizationDone, _ := publicreportPoolRelOrganizationCtx.Value(ctx) + if !isOrganizationDone && o.r.Organization != nil { + ctx = publicreportPoolRelOrganizationCtx.WithValue(ctx, true) + if o.r.Organization.o.alreadyPersisted { + m.R.Organization = o.r.Organization.o.Build() + } else { + var rel0 *models.Organization + rel0, err = o.r.Organization.o.Create(ctx, exec) + if err != nil { + return err + } + err = m.AttachOrganization(ctx, exec, rel0) + if err != nil { + return err + } + } + + } + isImagesDone, _ := publicreportPoolRelImagesCtx.Value(ctx) if !isImagesDone && o.r.Images != nil { ctx = publicreportPoolRelImagesCtx.WithValue(ctx, true) @@ -487,12 +525,12 @@ func (o *PublicreportPoolTemplate) insertOptRels(ctx context.Context, exec bob.E if r.o.alreadyPersisted { m.R.Images = append(m.R.Images, r.o.Build()) } else { - rel0, err := r.o.CreateMany(ctx, exec, r.number) + rel1, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachImages(ctx, exec, rel0...) + err = m.AttachImages(ctx, exec, rel1...) if err != nil { return err } @@ -622,6 +660,7 @@ func (m publicreportPoolMods) RandomizeAllColumns(f *faker.Faker) PublicreportPo PublicreportPoolMods.RandomReporterPhone(f), PublicreportPoolMods.RandomSubscribe(f), PublicreportPoolMods.RandomStatus(f), + PublicreportPoolMods.RandomOrganizationID(f), } } @@ -1599,12 +1638,100 @@ func (m publicreportPoolMods) RandomStatus(f *faker.Faker) PublicreportPoolMod { }) } +// Set the model columns to this value +func (m publicreportPoolMods) OrganizationID(val null.Val[int32]) PublicreportPoolMod { + return PublicreportPoolModFunc(func(_ context.Context, o *PublicreportPoolTemplate) { + o.OrganizationID = func() null.Val[int32] { return val } + }) +} + +// Set the Column from the function +func (m publicreportPoolMods) OrganizationIDFunc(f func() null.Val[int32]) PublicreportPoolMod { + return PublicreportPoolModFunc(func(_ context.Context, o *PublicreportPoolTemplate) { + o.OrganizationID = f + }) +} + +// Clear any values for the column +func (m publicreportPoolMods) UnsetOrganizationID() PublicreportPoolMod { + return PublicreportPoolModFunc(func(_ context.Context, o *PublicreportPoolTemplate) { + o.OrganizationID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m publicreportPoolMods) RandomOrganizationID(f *faker.Faker) PublicreportPoolMod { + return PublicreportPoolModFunc(func(_ context.Context, o *PublicreportPoolTemplate) { + o.OrganizationID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m publicreportPoolMods) RandomOrganizationIDNotNull(f *faker.Faker) PublicreportPoolMod { + return PublicreportPoolModFunc(func(_ context.Context, o *PublicreportPoolTemplate) { + o.OrganizationID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + func (m publicreportPoolMods) WithParentsCascading() PublicreportPoolMod { return PublicreportPoolModFunc(func(ctx context.Context, o *PublicreportPoolTemplate) { if isDone, _ := publicreportPoolWithParentsCascadingCtx.Value(ctx); isDone { return } ctx = publicreportPoolWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewOrganizationWithContext(ctx, OrganizationMods.WithParentsCascading()) + m.WithOrganization(related).Apply(ctx, o) + } + }) +} + +func (m publicreportPoolMods) WithOrganization(rel *OrganizationTemplate) PublicreportPoolMod { + return PublicreportPoolModFunc(func(ctx context.Context, o *PublicreportPoolTemplate) { + o.r.Organization = &publicreportPoolROrganizationR{ + o: rel, + } + }) +} + +func (m publicreportPoolMods) WithNewOrganization(mods ...OrganizationMod) PublicreportPoolMod { + return PublicreportPoolModFunc(func(ctx context.Context, o *PublicreportPoolTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + + m.WithOrganization(related).Apply(ctx, o) + }) +} + +func (m publicreportPoolMods) WithExistingOrganization(em *models.Organization) PublicreportPoolMod { + return PublicreportPoolModFunc(func(ctx context.Context, o *PublicreportPoolTemplate) { + o.r.Organization = &publicreportPoolROrganizationR{ + o: o.f.FromExistingOrganization(em), + } + }) +} + +func (m publicreportPoolMods) WithoutOrganization() PublicreportPoolMod { + return PublicreportPoolModFunc(func(ctx context.Context, o *PublicreportPoolTemplate) { + o.r.Organization = nil }) } diff --git a/db/factory/publicreport.quick.bob.go b/db/factory/publicreport.quick.bob.go index 9f372444..30ff2b16 100644 --- a/db/factory/publicreport.quick.bob.go +++ b/db/factory/publicreport.quick.bob.go @@ -38,16 +38,17 @@ func (mods PublicreportQuickModSlice) Apply(ctx context.Context, n *Publicreport // PublicreportQuickTemplate is an object representing the database table. // all columns are optional and should be set by mods type PublicreportQuickTemplate struct { - ID func() int32 - Created func() time.Time - Comments func() string - Location func() null.Val[string] - H3cell func() null.Val[string] - PublicID func() string - ReporterEmail func() string - ReporterPhone func() string - Address func() string - Status func() enums.PublicreportReportstatustype + ID func() int32 + Created func() time.Time + Comments func() string + Location func() null.Val[string] + H3cell func() null.Val[string] + PublicID func() string + ReporterEmail func() string + ReporterPhone func() string + Address func() string + Status func() enums.PublicreportReportstatustype + OrganizationID func() null.Val[int32] r publicreportQuickR f *Factory @@ -56,9 +57,13 @@ type PublicreportQuickTemplate struct { } type publicreportQuickR struct { - Images []*publicreportQuickRImagesR + Organization *publicreportQuickROrganizationR + Images []*publicreportQuickRImagesR } +type publicreportQuickROrganizationR struct { + o *OrganizationTemplate +} type publicreportQuickRImagesR struct { number int o *PublicreportImageTemplate @@ -74,6 +79,13 @@ func (o *PublicreportQuickTemplate) Apply(ctx context.Context, mods ...Publicrep // setModelRels creates and sets the relationships on *models.PublicreportQuick // according to the relationships in the template. Nothing is inserted into the db func (t PublicreportQuickTemplate) setModelRels(o *models.PublicreportQuick) { + if t.r.Organization != nil { + rel := t.r.Organization.o.Build() + rel.R.Quicks = append(rel.R.Quicks, o) + o.OrganizationID = null.From(rel.ID) // h2 + o.R.Organization = rel + } + if t.r.Images != nil { rel := models.PublicreportImageSlice{} for _, r := range t.r.Images { @@ -132,6 +144,10 @@ func (o PublicreportQuickTemplate) BuildSetter() *models.PublicreportQuickSetter val := o.Status() m.Status = omit.From(val) } + if o.OrganizationID != nil { + val := o.OrganizationID() + m.OrganizationID = omitnull.FromNull(val) + } return m } @@ -184,6 +200,9 @@ func (o PublicreportQuickTemplate) Build() *models.PublicreportQuick { if o.Status != nil { m.Status = o.Status() } + if o.OrganizationID != nil { + m.OrganizationID = o.OrganizationID() + } o.setModelRels(m) @@ -240,6 +259,25 @@ func ensureCreatablePublicreportQuick(m *models.PublicreportQuickSetter) { func (o *PublicreportQuickTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.PublicreportQuick) error { var err error + isOrganizationDone, _ := publicreportQuickRelOrganizationCtx.Value(ctx) + if !isOrganizationDone && o.r.Organization != nil { + ctx = publicreportQuickRelOrganizationCtx.WithValue(ctx, true) + if o.r.Organization.o.alreadyPersisted { + m.R.Organization = o.r.Organization.o.Build() + } else { + var rel0 *models.Organization + rel0, err = o.r.Organization.o.Create(ctx, exec) + if err != nil { + return err + } + err = m.AttachOrganization(ctx, exec, rel0) + if err != nil { + return err + } + } + + } + isImagesDone, _ := publicreportQuickRelImagesCtx.Value(ctx) if !isImagesDone && o.r.Images != nil { ctx = publicreportQuickRelImagesCtx.WithValue(ctx, true) @@ -247,12 +285,12 @@ func (o *PublicreportQuickTemplate) insertOptRels(ctx context.Context, exec bob. if r.o.alreadyPersisted { m.R.Images = append(m.R.Images, r.o.Build()) } else { - rel0, err := r.o.CreateMany(ctx, exec, r.number) + rel1, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachImages(ctx, exec, rel0...) + err = m.AttachImages(ctx, exec, rel1...) if err != nil { return err } @@ -362,6 +400,7 @@ func (m publicreportQuickMods) RandomizeAllColumns(f *faker.Faker) PublicreportQ PublicreportQuickMods.RandomReporterPhone(f), PublicreportQuickMods.RandomAddress(f), PublicreportQuickMods.RandomStatus(f), + PublicreportQuickMods.RandomOrganizationID(f), } } @@ -719,12 +758,100 @@ func (m publicreportQuickMods) RandomStatus(f *faker.Faker) PublicreportQuickMod }) } +// Set the model columns to this value +func (m publicreportQuickMods) OrganizationID(val null.Val[int32]) PublicreportQuickMod { + return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) { + o.OrganizationID = func() null.Val[int32] { return val } + }) +} + +// Set the Column from the function +func (m publicreportQuickMods) OrganizationIDFunc(f func() null.Val[int32]) PublicreportQuickMod { + return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) { + o.OrganizationID = f + }) +} + +// Clear any values for the column +func (m publicreportQuickMods) UnsetOrganizationID() PublicreportQuickMod { + return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) { + o.OrganizationID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m publicreportQuickMods) RandomOrganizationID(f *faker.Faker) PublicreportQuickMod { + return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) { + o.OrganizationID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m publicreportQuickMods) RandomOrganizationIDNotNull(f *faker.Faker) PublicreportQuickMod { + return PublicreportQuickModFunc(func(_ context.Context, o *PublicreportQuickTemplate) { + o.OrganizationID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + func (m publicreportQuickMods) WithParentsCascading() PublicreportQuickMod { return PublicreportQuickModFunc(func(ctx context.Context, o *PublicreportQuickTemplate) { if isDone, _ := publicreportQuickWithParentsCascadingCtx.Value(ctx); isDone { return } ctx = publicreportQuickWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewOrganizationWithContext(ctx, OrganizationMods.WithParentsCascading()) + m.WithOrganization(related).Apply(ctx, o) + } + }) +} + +func (m publicreportQuickMods) WithOrganization(rel *OrganizationTemplate) PublicreportQuickMod { + return PublicreportQuickModFunc(func(ctx context.Context, o *PublicreportQuickTemplate) { + o.r.Organization = &publicreportQuickROrganizationR{ + o: rel, + } + }) +} + +func (m publicreportQuickMods) WithNewOrganization(mods ...OrganizationMod) PublicreportQuickMod { + return PublicreportQuickModFunc(func(ctx context.Context, o *PublicreportQuickTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + + m.WithOrganization(related).Apply(ctx, o) + }) +} + +func (m publicreportQuickMods) WithExistingOrganization(em *models.Organization) PublicreportQuickMod { + return PublicreportQuickModFunc(func(ctx context.Context, o *PublicreportQuickTemplate) { + o.r.Organization = &publicreportQuickROrganizationR{ + o: o.f.FromExistingOrganization(em), + } + }) +} + +func (m publicreportQuickMods) WithoutOrganization() PublicreportQuickMod { + return PublicreportQuickModFunc(func(ctx context.Context, o *PublicreportQuickTemplate) { + o.r.Organization = nil }) } diff --git a/db/migrations/00037_publicreport_district.sql b/db/migrations/00037_publicreport_district.sql new file mode 100644 index 00000000..c351abd4 --- /dev/null +++ b/db/migrations/00037_publicreport_district.sql @@ -0,0 +1,8 @@ +-- +goose Up +ALTER TABLE publicreport.quick ADD COLUMN organization_id INTEGER REFERENCES "public"."organization"(id); +ALTER TABLE publicreport.pool ADD COLUMN organization_id INTEGER REFERENCES "public"."organization"(id); +ALTER TABLE publicreport.nuisance ADD COLUMN organization_id INTEGER REFERENCES "public"."organization"(id); +-- +goose Down +ALTER TABLE publicreport.nuisance DROP COLUMN organization_id; +ALTER TABLE publicreport.pool DROP COLUMN organization_id; +ALTER TABLE publicreport.quick DROP COLUMN organization_id; diff --git a/db/migrations/00038_organization_logo.sql b/db/migrations/00038_organization_logo.sql new file mode 100644 index 00000000..8da51a4a --- /dev/null +++ b/db/migrations/00038_organization_logo.sql @@ -0,0 +1,2 @@ +-- +goose UP +ALTER TABLE public.organization ADD COLUMN logo_uuid UUID; diff --git a/db/models/bob_joins.bob.go b/db/models/bob_joins.bob.go index e3a3c0af..4455b426 100644 --- a/db/models/bob_joins.bob.go +++ b/db/models/bob_joins.bob.go @@ -79,6 +79,7 @@ type joins[Q dialect.Joinable] struct { Organizations joinSet[organizationJoins[Q]] PublicreportImages joinSet[publicreportImageJoins[Q]] PublicreportImageExifs joinSet[publicreportImageExifJoins[Q]] + PublicreportNuisances joinSet[publicreportNuisanceJoins[Q]] PublicreportPools joinSet[publicreportPoolJoins[Q]] PublicreportPoolImages joinSet[publicreportPoolImageJoins[Q]] PublicreportQuicks joinSet[publicreportQuickJoins[Q]] @@ -143,6 +144,7 @@ func getJoins[Q dialect.Joinable]() joins[Q] { Organizations: buildJoinSet[organizationJoins[Q]](Organizations.Columns, buildOrganizationJoins), PublicreportImages: buildJoinSet[publicreportImageJoins[Q]](PublicreportImages.Columns, buildPublicreportImageJoins), PublicreportImageExifs: buildJoinSet[publicreportImageExifJoins[Q]](PublicreportImageExifs.Columns, buildPublicreportImageExifJoins), + PublicreportNuisances: buildJoinSet[publicreportNuisanceJoins[Q]](PublicreportNuisances.Columns, buildPublicreportNuisanceJoins), PublicreportPools: buildJoinSet[publicreportPoolJoins[Q]](PublicreportPools.Columns, buildPublicreportPoolJoins), PublicreportPoolImages: buildJoinSet[publicreportPoolImageJoins[Q]](PublicreportPoolImages.Columns, buildPublicreportPoolImageJoins), PublicreportQuicks: buildJoinSet[publicreportQuickJoins[Q]](PublicreportQuicks.Columns, buildPublicreportQuickJoins), diff --git a/db/models/bob_loaders.bob.go b/db/models/bob_loaders.bob.go index 405ee6d6..1b3475de 100644 --- a/db/models/bob_loaders.bob.go +++ b/db/models/bob_loaders.bob.go @@ -64,6 +64,7 @@ type preloaders struct { Organization organizationPreloader PublicreportImage publicreportImagePreloader PublicreportImageExif publicreportImageExifPreloader + PublicreportNuisance publicreportNuisancePreloader PublicreportPool publicreportPoolPreloader PublicreportPoolImage publicreportPoolImagePreloader PublicreportQuick publicreportQuickPreloader @@ -120,6 +121,7 @@ func getPreloaders() preloaders { Organization: buildOrganizationPreloader(), PublicreportImage: buildPublicreportImagePreloader(), PublicreportImageExif: buildPublicreportImageExifPreloader(), + PublicreportNuisance: buildPublicreportNuisancePreloader(), PublicreportPool: buildPublicreportPoolPreloader(), PublicreportPoolImage: buildPublicreportPoolImagePreloader(), PublicreportQuick: buildPublicreportQuickPreloader(), @@ -182,6 +184,7 @@ type thenLoaders[Q orm.Loadable] struct { Organization organizationThenLoader[Q] PublicreportImage publicreportImageThenLoader[Q] PublicreportImageExif publicreportImageExifThenLoader[Q] + PublicreportNuisance publicreportNuisanceThenLoader[Q] PublicreportPool publicreportPoolThenLoader[Q] PublicreportPoolImage publicreportPoolImageThenLoader[Q] PublicreportQuick publicreportQuickThenLoader[Q] @@ -238,6 +241,7 @@ func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] { Organization: buildOrganizationThenLoader[Q](), PublicreportImage: buildPublicreportImageThenLoader[Q](), PublicreportImageExif: buildPublicreportImageExifThenLoader[Q](), + PublicreportNuisance: buildPublicreportNuisanceThenLoader[Q](), PublicreportPool: buildPublicreportPoolThenLoader[Q](), PublicreportPoolImage: buildPublicreportPoolImageThenLoader[Q](), PublicreportQuick: buildPublicreportQuickThenLoader[Q](), diff --git a/db/models/organization.bob.go b/db/models/organization.bob.go index c735bef2..af84e294 100644 --- a/db/models/organization.bob.go +++ b/db/models/organization.bob.go @@ -11,6 +11,7 @@ import ( "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" + "github.com/google/uuid" "github.com/stephenafamo/bob" "github.com/stephenafamo/bob/dialect/psql" "github.com/stephenafamo/bob/dialect/psql/dialect" @@ -25,13 +26,14 @@ import ( // Organization is an object representing the database table. type Organization struct { - ID int32 `db:"id,pk" ` - Name string `db:"name" ` - ArcgisID null.Val[string] `db:"arcgis_id" ` - ArcgisName null.Val[string] `db:"arcgis_name" ` - FieldseekerURL null.Val[string] `db:"fieldseeker_url" ` - ImportDistrictGid null.Val[int32] `db:"import_district_gid" ` - Website null.Val[string] `db:"website" ` + ID int32 `db:"id,pk" ` + Name string `db:"name" ` + ArcgisID null.Val[string] `db:"arcgis_id" ` + ArcgisName null.Val[string] `db:"arcgis_name" ` + FieldseekerURL null.Val[string] `db:"fieldseeker_url" ` + ImportDistrictGid null.Val[int32] `db:"import_district_gid" ` + Website null.Val[string] `db:"website" ` + LogoUUID null.Val[uuid.UUID] `db:"logo_uuid" ` R organizationR `db:"-" ` @@ -82,13 +84,16 @@ type organizationR struct { NoteAudios NoteAudioSlice // note_audio.note_audio_organization_id_fkey NoteImages NoteImageSlice // note_image.note_image_organization_id_fkey ImportDistrictGidDistrict *ImportDistrict // organization.organization_import_district_gid_fkey + Nuisances PublicreportNuisanceSlice // publicreport.nuisance.nuisance_organization_id_fkey + PublicreportPool PublicreportPoolSlice // publicreport.pool.pool_organization_id_fkey + Quicks PublicreportQuickSlice // publicreport.quick.quick_organization_id_fkey User UserSlice // user_.user__organization_id_fkey } func buildOrganizationColumns(alias string) organizationColumns { return organizationColumns{ ColumnsExpr: expr.NewColumnsExpr( - "id", "name", "arcgis_id", "arcgis_name", "fieldseeker_url", "import_district_gid", "website", + "id", "name", "arcgis_id", "arcgis_name", "fieldseeker_url", "import_district_gid", "website", "logo_uuid", ).WithParent("organization"), tableAlias: alias, ID: psql.Quote(alias, "id"), @@ -98,6 +103,7 @@ func buildOrganizationColumns(alias string) organizationColumns { FieldseekerURL: psql.Quote(alias, "fieldseeker_url"), ImportDistrictGid: psql.Quote(alias, "import_district_gid"), Website: psql.Quote(alias, "website"), + LogoUUID: psql.Quote(alias, "logo_uuid"), } } @@ -111,6 +117,7 @@ type organizationColumns struct { FieldseekerURL psql.Expression ImportDistrictGid psql.Expression Website psql.Expression + LogoUUID psql.Expression } func (c organizationColumns) Alias() string { @@ -125,17 +132,18 @@ func (organizationColumns) AliasedAs(alias string) organizationColumns { // All values are optional, and do not have to be set // Generated columns are not included type OrganizationSetter struct { - ID omit.Val[int32] `db:"id,pk" ` - Name omit.Val[string] `db:"name" ` - ArcgisID omitnull.Val[string] `db:"arcgis_id" ` - ArcgisName omitnull.Val[string] `db:"arcgis_name" ` - FieldseekerURL omitnull.Val[string] `db:"fieldseeker_url" ` - ImportDistrictGid omitnull.Val[int32] `db:"import_district_gid" ` - Website omitnull.Val[string] `db:"website" ` + ID omit.Val[int32] `db:"id,pk" ` + Name omit.Val[string] `db:"name" ` + ArcgisID omitnull.Val[string] `db:"arcgis_id" ` + ArcgisName omitnull.Val[string] `db:"arcgis_name" ` + FieldseekerURL omitnull.Val[string] `db:"fieldseeker_url" ` + ImportDistrictGid omitnull.Val[int32] `db:"import_district_gid" ` + Website omitnull.Val[string] `db:"website" ` + LogoUUID omitnull.Val[uuid.UUID] `db:"logo_uuid" ` } func (s OrganizationSetter) SetColumns() []string { - vals := make([]string, 0, 7) + vals := make([]string, 0, 8) if s.ID.IsValue() { vals = append(vals, "id") } @@ -157,6 +165,9 @@ func (s OrganizationSetter) SetColumns() []string { if !s.Website.IsUnset() { vals = append(vals, "website") } + if !s.LogoUUID.IsUnset() { + vals = append(vals, "logo_uuid") + } return vals } @@ -182,6 +193,9 @@ func (s OrganizationSetter) Overwrite(t *Organization) { if !s.Website.IsUnset() { t.Website = s.Website.MustGetNull() } + if !s.LogoUUID.IsUnset() { + t.LogoUUID = s.LogoUUID.MustGetNull() + } } func (s *OrganizationSetter) Apply(q *dialect.InsertQuery) { @@ -190,7 +204,7 @@ func (s *OrganizationSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 7) + vals := make([]bob.Expression, 8) if s.ID.IsValue() { vals[0] = psql.Arg(s.ID.MustGet()) } else { @@ -233,6 +247,12 @@ func (s *OrganizationSetter) Apply(q *dialect.InsertQuery) { vals[6] = psql.Raw("DEFAULT") } + if !s.LogoUUID.IsUnset() { + vals[7] = psql.Arg(s.LogoUUID.MustGetNull()) + } else { + vals[7] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -242,7 +262,7 @@ func (s OrganizationSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s OrganizationSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 7) + exprs := make([]bob.Expression, 0, 8) if s.ID.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -293,6 +313,13 @@ func (s OrganizationSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if !s.LogoUUID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "logo_uuid")...), + psql.Arg(s.LogoUUID), + }}) + } + return exprs } @@ -1287,6 +1314,78 @@ func (os OrganizationSlice) ImportDistrictGidDistrict(mods ...bob.Mod[*dialect.S )...) } +// Nuisances starts a query for related objects on publicreport.nuisance +func (o *Organization) Nuisances(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportNuisancesQuery { + return PublicreportNuisances.Query(append(mods, + sm.Where(PublicreportNuisances.Columns.OrganizationID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os OrganizationSlice) Nuisances(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportNuisancesQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return PublicreportNuisances.Query(append(mods, + sm.Where(psql.Group(PublicreportNuisances.Columns.OrganizationID).OP("IN", PKArgExpr)), + )...) +} + +// PublicreportPool starts a query for related objects on publicreport.pool +func (o *Organization) PublicreportPool(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportPoolsQuery { + return PublicreportPools.Query(append(mods, + sm.Where(PublicreportPools.Columns.OrganizationID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os OrganizationSlice) PublicreportPool(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportPoolsQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return PublicreportPools.Query(append(mods, + sm.Where(psql.Group(PublicreportPools.Columns.OrganizationID).OP("IN", PKArgExpr)), + )...) +} + +// Quicks starts a query for related objects on publicreport.quick +func (o *Organization) Quicks(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportQuicksQuery { + return PublicreportQuicks.Query(append(mods, + sm.Where(PublicreportQuicks.Columns.OrganizationID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os OrganizationSlice) Quicks(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportQuicksQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return PublicreportQuicks.Query(append(mods, + sm.Where(psql.Group(PublicreportQuicks.Columns.OrganizationID).OP("IN", PKArgExpr)), + )...) +} + // User starts a query for related objects on user_ func (o *Organization) User(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { return Users.Query(append(mods, @@ -3467,6 +3566,210 @@ func (organization0 *Organization) AttachImportDistrictGidDistrict(ctx context.C return nil } +func insertOrganizationNuisances0(ctx context.Context, exec bob.Executor, publicreportNuisances1 []*PublicreportNuisanceSetter, organization0 *Organization) (PublicreportNuisanceSlice, error) { + for i := range publicreportNuisances1 { + publicreportNuisances1[i].OrganizationID = omitnull.From(organization0.ID) + } + + ret, err := PublicreportNuisances.Insert(bob.ToMods(publicreportNuisances1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertOrganizationNuisances0: %w", err) + } + + return ret, nil +} + +func attachOrganizationNuisances0(ctx context.Context, exec bob.Executor, count int, publicreportNuisances1 PublicreportNuisanceSlice, organization0 *Organization) (PublicreportNuisanceSlice, error) { + setter := &PublicreportNuisanceSetter{ + OrganizationID: omitnull.From(organization0.ID), + } + + err := publicreportNuisances1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachOrganizationNuisances0: %w", err) + } + + return publicreportNuisances1, nil +} + +func (organization0 *Organization) InsertNuisances(ctx context.Context, exec bob.Executor, related ...*PublicreportNuisanceSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + publicreportNuisances1, err := insertOrganizationNuisances0(ctx, exec, related, organization0) + if err != nil { + return err + } + + organization0.R.Nuisances = append(organization0.R.Nuisances, publicreportNuisances1...) + + for _, rel := range publicreportNuisances1 { + rel.R.Organization = organization0 + } + return nil +} + +func (organization0 *Organization) AttachNuisances(ctx context.Context, exec bob.Executor, related ...*PublicreportNuisance) error { + if len(related) == 0 { + return nil + } + + var err error + publicreportNuisances1 := PublicreportNuisanceSlice(related) + + _, err = attachOrganizationNuisances0(ctx, exec, len(related), publicreportNuisances1, organization0) + if err != nil { + return err + } + + organization0.R.Nuisances = append(organization0.R.Nuisances, publicreportNuisances1...) + + for _, rel := range related { + rel.R.Organization = organization0 + } + + return nil +} + +func insertOrganizationPublicreportPool0(ctx context.Context, exec bob.Executor, publicreportPools1 []*PublicreportPoolSetter, organization0 *Organization) (PublicreportPoolSlice, error) { + for i := range publicreportPools1 { + publicreportPools1[i].OrganizationID = omitnull.From(organization0.ID) + } + + ret, err := PublicreportPools.Insert(bob.ToMods(publicreportPools1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertOrganizationPublicreportPool0: %w", err) + } + + return ret, nil +} + +func attachOrganizationPublicreportPool0(ctx context.Context, exec bob.Executor, count int, publicreportPools1 PublicreportPoolSlice, organization0 *Organization) (PublicreportPoolSlice, error) { + setter := &PublicreportPoolSetter{ + OrganizationID: omitnull.From(organization0.ID), + } + + err := publicreportPools1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachOrganizationPublicreportPool0: %w", err) + } + + return publicreportPools1, nil +} + +func (organization0 *Organization) InsertPublicreportPool(ctx context.Context, exec bob.Executor, related ...*PublicreportPoolSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + publicreportPools1, err := insertOrganizationPublicreportPool0(ctx, exec, related, organization0) + if err != nil { + return err + } + + organization0.R.PublicreportPool = append(organization0.R.PublicreportPool, publicreportPools1...) + + for _, rel := range publicreportPools1 { + rel.R.Organization = organization0 + } + return nil +} + +func (organization0 *Organization) AttachPublicreportPool(ctx context.Context, exec bob.Executor, related ...*PublicreportPool) error { + if len(related) == 0 { + return nil + } + + var err error + publicreportPools1 := PublicreportPoolSlice(related) + + _, err = attachOrganizationPublicreportPool0(ctx, exec, len(related), publicreportPools1, organization0) + if err != nil { + return err + } + + organization0.R.PublicreportPool = append(organization0.R.PublicreportPool, publicreportPools1...) + + for _, rel := range related { + rel.R.Organization = organization0 + } + + return nil +} + +func insertOrganizationQuicks0(ctx context.Context, exec bob.Executor, publicreportQuicks1 []*PublicreportQuickSetter, organization0 *Organization) (PublicreportQuickSlice, error) { + for i := range publicreportQuicks1 { + publicreportQuicks1[i].OrganizationID = omitnull.From(organization0.ID) + } + + ret, err := PublicreportQuicks.Insert(bob.ToMods(publicreportQuicks1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertOrganizationQuicks0: %w", err) + } + + return ret, nil +} + +func attachOrganizationQuicks0(ctx context.Context, exec bob.Executor, count int, publicreportQuicks1 PublicreportQuickSlice, organization0 *Organization) (PublicreportQuickSlice, error) { + setter := &PublicreportQuickSetter{ + OrganizationID: omitnull.From(organization0.ID), + } + + err := publicreportQuicks1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachOrganizationQuicks0: %w", err) + } + + return publicreportQuicks1, nil +} + +func (organization0 *Organization) InsertQuicks(ctx context.Context, exec bob.Executor, related ...*PublicreportQuickSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + publicreportQuicks1, err := insertOrganizationQuicks0(ctx, exec, related, organization0) + if err != nil { + return err + } + + organization0.R.Quicks = append(organization0.R.Quicks, publicreportQuicks1...) + + for _, rel := range publicreportQuicks1 { + rel.R.Organization = organization0 + } + return nil +} + +func (organization0 *Organization) AttachQuicks(ctx context.Context, exec bob.Executor, related ...*PublicreportQuick) error { + if len(related) == 0 { + return nil + } + + var err error + publicreportQuicks1 := PublicreportQuickSlice(related) + + _, err = attachOrganizationQuicks0(ctx, exec, len(related), publicreportQuicks1, organization0) + if err != nil { + return err + } + + organization0.R.Quicks = append(organization0.R.Quicks, publicreportQuicks1...) + + for _, rel := range related { + rel.R.Organization = organization0 + } + + return nil +} + func insertOrganizationUser0(ctx context.Context, exec bob.Executor, users1 []*UserSetter, organization0 *Organization) (UserSlice, error) { for i := range users1 { users1[i].OrganizationID = omit.From(organization0.ID) @@ -3543,6 +3846,7 @@ type organizationWhere[Q psql.Filterable] struct { FieldseekerURL psql.WhereNullMod[Q, string] ImportDistrictGid psql.WhereNullMod[Q, int32] Website psql.WhereNullMod[Q, string] + LogoUUID psql.WhereNullMod[Q, uuid.UUID] } func (organizationWhere[Q]) AliasedAs(alias string) organizationWhere[Q] { @@ -3558,6 +3862,7 @@ func buildOrganizationWhere[Q psql.Filterable](cols organizationColumns) organiz FieldseekerURL: psql.WhereNull[Q, string](cols.FieldseekerURL), ImportDistrictGid: psql.WhereNull[Q, int32](cols.ImportDistrictGid), Website: psql.WhereNull[Q, string](cols.Website), + LogoUUID: psql.WhereNull[Q, uuid.UUID](cols.LogoUUID), } } @@ -4013,6 +4318,48 @@ func (o *Organization) Preload(name string, retrieved any) error { rel.R.ImportDistrictGidOrganization = o } return nil + case "Nuisances": + rels, ok := retrieved.(PublicreportNuisanceSlice) + if !ok { + return fmt.Errorf("organization cannot load %T as %q", retrieved, name) + } + + o.R.Nuisances = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Organization = o + } + } + return nil + case "PublicreportPool": + rels, ok := retrieved.(PublicreportPoolSlice) + if !ok { + return fmt.Errorf("organization cannot load %T as %q", retrieved, name) + } + + o.R.PublicreportPool = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Organization = o + } + } + return nil + case "Quicks": + rels, ok := retrieved.(PublicreportQuickSlice) + if !ok { + return fmt.Errorf("organization cannot load %T as %q", retrieved, name) + } + + o.R.Quicks = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Organization = o + } + } + return nil case "User": rels, ok := retrieved.(UserSlice) if !ok { @@ -4087,6 +4434,9 @@ type organizationThenLoader[Q orm.Loadable] struct { NoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] NoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] ImportDistrictGidDistrict func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Nuisances func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + PublicreportPool func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Quicks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] User func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } @@ -4187,6 +4537,15 @@ func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { type ImportDistrictGidDistrictLoadInterface interface { LoadImportDistrictGidDistrict(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type NuisancesLoadInterface interface { + LoadNuisances(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PublicreportPoolLoadInterface interface { + LoadPublicreportPool(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type QuicksLoadInterface interface { + LoadQuicks(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type UserLoadInterface interface { LoadUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -4384,6 +4743,24 @@ func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { return retrieved.LoadImportDistrictGidDistrict(ctx, exec, mods...) }, ), + Nuisances: thenLoadBuilder[Q]( + "Nuisances", + func(ctx context.Context, exec bob.Executor, retrieved NuisancesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadNuisances(ctx, exec, mods...) + }, + ), + PublicreportPool: thenLoadBuilder[Q]( + "PublicreportPool", + func(ctx context.Context, exec bob.Executor, retrieved PublicreportPoolLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadPublicreportPool(ctx, exec, mods...) + }, + ), + Quicks: thenLoadBuilder[Q]( + "Quicks", + func(ctx context.Context, exec bob.Executor, retrieved QuicksLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadQuicks(ctx, exec, mods...) + }, + ), User: thenLoadBuilder[Q]( "User", func(ctx context.Context, exec bob.Executor, retrieved UserLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -6339,6 +6716,198 @@ func (os OrganizationSlice) LoadImportDistrictGidDistrict(ctx context.Context, e return nil } +// LoadNuisances loads the organization's Nuisances into the .R struct +func (o *Organization) LoadNuisances(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Nuisances = nil + + related, err := o.Nuisances(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Organization = o + } + + o.R.Nuisances = related + return nil +} + +// LoadNuisances loads the organization's Nuisances into the .R struct +func (os OrganizationSlice) LoadNuisances(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + publicreportNuisances, err := os.Nuisances(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.Nuisances = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range publicreportNuisances { + + if !rel.OrganizationID.IsValue() { + continue + } + if !(rel.OrganizationID.IsValue() && o.ID == rel.OrganizationID.MustGet()) { + continue + } + + rel.R.Organization = o + + o.R.Nuisances = append(o.R.Nuisances, rel) + } + } + + return nil +} + +// LoadPublicreportPool loads the organization's PublicreportPool into the .R struct +func (o *Organization) LoadPublicreportPool(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.PublicreportPool = nil + + related, err := o.PublicreportPool(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Organization = o + } + + o.R.PublicreportPool = related + return nil +} + +// LoadPublicreportPool loads the organization's PublicreportPool into the .R struct +func (os OrganizationSlice) LoadPublicreportPool(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + publicreportPools, err := os.PublicreportPool(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.PublicreportPool = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range publicreportPools { + + if !rel.OrganizationID.IsValue() { + continue + } + if !(rel.OrganizationID.IsValue() && o.ID == rel.OrganizationID.MustGet()) { + continue + } + + rel.R.Organization = o + + o.R.PublicreportPool = append(o.R.PublicreportPool, rel) + } + } + + return nil +} + +// LoadQuicks loads the organization's Quicks into the .R struct +func (o *Organization) LoadQuicks(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Quicks = nil + + related, err := o.Quicks(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Organization = o + } + + o.R.Quicks = related + return nil +} + +// LoadQuicks loads the organization's Quicks into the .R struct +func (os OrganizationSlice) LoadQuicks(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + publicreportQuicks, err := os.Quicks(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.Quicks = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range publicreportQuicks { + + if !rel.OrganizationID.IsValue() { + continue + } + if !(rel.OrganizationID.IsValue() && o.ID == rel.OrganizationID.MustGet()) { + continue + } + + rel.R.Organization = o + + o.R.Quicks = append(o.R.Quicks, rel) + } + } + + return nil +} + // LoadUser loads the organization's User into the .R struct func (o *Organization) LoadUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -6433,6 +7002,9 @@ type organizationC struct { H3Aggregations *int64 NoteAudios *int64 NoteImages *int64 + Nuisances *int64 + PublicreportPool *int64 + Quicks *int64 User *int64 } @@ -6505,6 +7077,12 @@ func (o *Organization) PreloadCount(name string, count int64) error { o.C.NoteAudios = &count case "NoteImages": o.C.NoteImages = &count + case "Nuisances": + o.C.Nuisances = &count + case "PublicreportPool": + o.C.PublicreportPool = &count + case "Quicks": + o.C.Quicks = &count case "User": o.C.User = &count } @@ -6543,6 +7121,9 @@ type organizationCountPreloader struct { H3Aggregations func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader NoteAudios func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader NoteImages func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Nuisances func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + PublicreportPool func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Quicks func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader User func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader } @@ -7075,6 +7656,57 @@ func buildOrganizationCountPreloader() organizationCountPreloader { return psql.Group(psql.Select(subqueryMods...).Expression) }) }, + Nuisances: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Nuisances", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(PublicreportNuisances.Name()), + sm.Where(psql.Quote(PublicreportNuisances.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + PublicreportPool: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("PublicreportPool", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(PublicreportPools.Name()), + sm.Where(psql.Quote(PublicreportPools.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + Quicks: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Quicks", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(PublicreportQuicks.Name()), + sm.Where(psql.Quote(PublicreportQuicks.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, User: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { return countPreloader[*Organization]("User", func(parent string) bob.Expression { // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) @@ -7127,6 +7759,9 @@ type organizationCountThenLoader[Q orm.Loadable] struct { H3Aggregations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] NoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] NoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Nuisances func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + PublicreportPool func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Quicks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] User func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } @@ -7224,6 +7859,15 @@ func buildOrganizationCountThenLoader[Q orm.Loadable]() organizationCountThenLoa type NoteImagesCountInterface interface { LoadCountNoteImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type NuisancesCountInterface interface { + LoadCountNuisances(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type PublicreportPoolCountInterface interface { + LoadCountPublicreportPool(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type QuicksCountInterface interface { + LoadCountQuicks(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type UserCountInterface interface { LoadCountUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -7415,6 +8059,24 @@ func buildOrganizationCountThenLoader[Q orm.Loadable]() organizationCountThenLoa return retrieved.LoadCountNoteImages(ctx, exec, mods...) }, ), + Nuisances: countThenLoadBuilder[Q]( + "Nuisances", + func(ctx context.Context, exec bob.Executor, retrieved NuisancesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountNuisances(ctx, exec, mods...) + }, + ), + PublicreportPool: countThenLoadBuilder[Q]( + "PublicreportPool", + func(ctx context.Context, exec bob.Executor, retrieved PublicreportPoolCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountPublicreportPool(ctx, exec, mods...) + }, + ), + Quicks: countThenLoadBuilder[Q]( + "Quicks", + func(ctx context.Context, exec bob.Executor, retrieved QuicksCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountQuicks(ctx, exec, mods...) + }, + ), User: countThenLoadBuilder[Q]( "User", func(ctx context.Context, exec bob.Executor, retrieved UserCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -8354,6 +9016,96 @@ func (os OrganizationSlice) LoadCountNoteImages(ctx context.Context, exec bob.Ex return nil } +// LoadCountNuisances loads the count of Nuisances into the C struct +func (o *Organization) LoadCountNuisances(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Nuisances(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Nuisances = &count + return nil +} + +// LoadCountNuisances loads the count of Nuisances for a slice +func (os OrganizationSlice) LoadCountNuisances(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountNuisances(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountPublicreportPool loads the count of PublicreportPool into the C struct +func (o *Organization) LoadCountPublicreportPool(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.PublicreportPool(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.PublicreportPool = &count + return nil +} + +// LoadCountPublicreportPool loads the count of PublicreportPool for a slice +func (os OrganizationSlice) LoadCountPublicreportPool(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountPublicreportPool(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +// LoadCountQuicks loads the count of Quicks into the C struct +func (o *Organization) LoadCountQuicks(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Quicks(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Quicks = &count + return nil +} + +// LoadCountQuicks loads the count of Quicks for a slice +func (os OrganizationSlice) LoadCountQuicks(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountQuicks(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + // LoadCountUser loads the count of User into the C struct func (o *Organization) LoadCountUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -8418,6 +9170,9 @@ type organizationJoins[Q dialect.Joinable] struct { NoteAudios modAs[Q, noteAudioColumns] NoteImages modAs[Q, noteImageColumns] ImportDistrictGidDistrict modAs[Q, importDistrictColumns] + Nuisances modAs[Q, publicreportNuisanceColumns] + PublicreportPool modAs[Q, publicreportPoolColumns] + Quicks modAs[Q, publicreportQuickColumns] User modAs[Q, userColumns] } @@ -8876,6 +9631,48 @@ func buildOrganizationJoins[Q dialect.Joinable](cols organizationColumns, typ st return mods }, }, + Nuisances: modAs[Q, publicreportNuisanceColumns]{ + c: PublicreportNuisances.Columns, + f: func(to publicreportNuisanceColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, PublicreportNuisances.Name().As(to.Alias())).On( + to.OrganizationID.EQ(cols.ID), + )) + } + + return mods + }, + }, + PublicreportPool: 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.OrganizationID.EQ(cols.ID), + )) + } + + return mods + }, + }, + Quicks: modAs[Q, publicreportQuickColumns]{ + c: PublicreportQuicks.Columns, + f: func(to publicreportQuickColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, PublicreportQuicks.Name().As(to.Alias())).On( + to.OrganizationID.EQ(cols.ID), + )) + } + + return mods + }, + }, User: modAs[Q, userColumns]{ c: Users.Columns, f: func(to userColumns) bob.Mod[Q] { diff --git a/db/models/publicreport.nuisance.bob.go b/db/models/publicreport.nuisance.bob.go index 031d4bf0..0797594d 100644 --- a/db/models/publicreport.nuisance.bob.go +++ b/db/models/publicreport.nuisance.bob.go @@ -5,6 +5,7 @@ package models import ( "context" + "fmt" "io" "time" @@ -19,6 +20,9 @@ import ( "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" ) // PublicreportNuisance is an object representing the database table. @@ -50,6 +54,9 @@ type PublicreportNuisance struct { Address string `db:"address" ` Location null.Val[string] `db:"location" ` Status enums.PublicreportReportstatustype `db:"status" ` + OrganizationID null.Val[int32] `db:"organization_id" ` + + R publicreportNuisanceR `db:"-" ` } // PublicreportNuisanceSlice is an alias for a slice of pointers to PublicreportNuisance. @@ -62,10 +69,15 @@ var PublicreportNuisances = psql.NewTablex[*PublicreportNuisance, PublicreportNu // PublicreportNuisancesQuery is a query on the nuisance table type PublicreportNuisancesQuery = *psql.ViewQuery[*PublicreportNuisance, PublicreportNuisanceSlice] +// publicreportNuisanceR is where relationships are stored. +type publicreportNuisanceR struct { + Organization *Organization // publicreport.nuisance.nuisance_organization_id_fkey +} + func buildPublicreportNuisanceColumns(alias string) publicreportNuisanceColumns { return publicreportNuisanceColumns{ ColumnsExpr: expr.NewColumnsExpr( - "id", "additional_info", "created", "duration", "email", "inspection_type", "source_location", "preferred_date_range", "preferred_time", "request_call", "severity", "source_container", "source_description", "source_roof", "source_stagnant", "time_of_day_day", "time_of_day_early", "time_of_day_evening", "time_of_day_night", "public_id", "reporter_address", "reporter_email", "reporter_name", "reporter_phone", "address", "location", "status", + "id", "additional_info", "created", "duration", "email", "inspection_type", "source_location", "preferred_date_range", "preferred_time", "request_call", "severity", "source_container", "source_description", "source_roof", "source_stagnant", "time_of_day_day", "time_of_day_early", "time_of_day_evening", "time_of_day_night", "public_id", "reporter_address", "reporter_email", "reporter_name", "reporter_phone", "address", "location", "status", "organization_id", ).WithParent("publicreport.nuisance"), tableAlias: alias, ID: psql.Quote(alias, "id"), @@ -95,6 +107,7 @@ func buildPublicreportNuisanceColumns(alias string) publicreportNuisanceColumns Address: psql.Quote(alias, "address"), Location: psql.Quote(alias, "location"), Status: psql.Quote(alias, "status"), + OrganizationID: psql.Quote(alias, "organization_id"), } } @@ -128,6 +141,7 @@ type publicreportNuisanceColumns struct { Address psql.Expression Location psql.Expression Status psql.Expression + OrganizationID psql.Expression } func (c publicreportNuisanceColumns) Alias() string { @@ -169,10 +183,11 @@ type PublicreportNuisanceSetter struct { Address omit.Val[string] `db:"address" ` Location omitnull.Val[string] `db:"location" ` Status omit.Val[enums.PublicreportReportstatustype] `db:"status" ` + OrganizationID omitnull.Val[int32] `db:"organization_id" ` } func (s PublicreportNuisanceSetter) SetColumns() []string { - vals := make([]string, 0, 27) + vals := make([]string, 0, 28) if s.ID.IsValue() { vals = append(vals, "id") } @@ -254,6 +269,9 @@ func (s PublicreportNuisanceSetter) SetColumns() []string { if s.Status.IsValue() { vals = append(vals, "status") } + if !s.OrganizationID.IsUnset() { + vals = append(vals, "organization_id") + } return vals } @@ -339,6 +357,9 @@ func (s PublicreportNuisanceSetter) Overwrite(t *PublicreportNuisance) { if s.Status.IsValue() { t.Status = s.Status.MustGet() } + if !s.OrganizationID.IsUnset() { + t.OrganizationID = s.OrganizationID.MustGetNull() + } } func (s *PublicreportNuisanceSetter) Apply(q *dialect.InsertQuery) { @@ -347,7 +368,7 @@ func (s *PublicreportNuisanceSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 27) + vals := make([]bob.Expression, 28) if s.ID.IsValue() { vals[0] = psql.Arg(s.ID.MustGet()) } else { @@ -510,6 +531,12 @@ func (s *PublicreportNuisanceSetter) Apply(q *dialect.InsertQuery) { vals[26] = psql.Raw("DEFAULT") } + if !s.OrganizationID.IsUnset() { + vals[27] = psql.Arg(s.OrganizationID.MustGetNull()) + } else { + vals[27] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -519,7 +546,7 @@ func (s PublicreportNuisanceSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s PublicreportNuisanceSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 27) + exprs := make([]bob.Expression, 0, 28) if s.ID.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -710,6 +737,13 @@ func (s PublicreportNuisanceSetter) Expressions(prefix ...string) []bob.Expressi }}) } + if !s.OrganizationID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "organization_id")...), + psql.Arg(s.OrganizationID), + }}) + } + return exprs } @@ -771,6 +805,7 @@ func (o *PublicreportNuisance) Update(ctx context.Context, exec bob.Executor, s return err } + o.R = v.R *o = *v return nil @@ -790,7 +825,7 @@ func (o *PublicreportNuisance) Reload(ctx context.Context, exec bob.Executor) er if err != nil { return err } - + o2.R = o.R *o = *o2 return nil @@ -837,7 +872,7 @@ func (o PublicreportNuisanceSlice) copyMatchingRows(from ...*PublicreportNuisanc if new.ID != old.ID { continue } - + new.R = old.R o[i] = new break } @@ -935,6 +970,78 @@ func (o PublicreportNuisanceSlice) ReloadAll(ctx context.Context, exec bob.Execu return nil } +// Organization starts a query for related objects on organization +func (o *PublicreportNuisance) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + return Organizations.Query(append(mods, + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(o.OrganizationID))), + )...) +} + +func (os PublicreportNuisanceSlice) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + pkOrganizationID := make(pgtypes.Array[null.Val[int32]], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkOrganizationID = append(pkOrganizationID, o.OrganizationID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkOrganizationID), "integer[]")), + )) + + return Organizations.Query(append(mods, + sm.Where(psql.Group(Organizations.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + +func attachPublicreportNuisanceOrganization0(ctx context.Context, exec bob.Executor, count int, publicreportNuisance0 *PublicreportNuisance, organization1 *Organization) (*PublicreportNuisance, error) { + setter := &PublicreportNuisanceSetter{ + OrganizationID: omitnull.From(organization1.ID), + } + + err := publicreportNuisance0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachPublicreportNuisanceOrganization0: %w", err) + } + + return publicreportNuisance0, nil +} + +func (publicreportNuisance0 *PublicreportNuisance) InsertOrganization(ctx context.Context, exec bob.Executor, related *OrganizationSetter) error { + var err error + + organization1, err := Organizations.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachPublicreportNuisanceOrganization0(ctx, exec, 1, publicreportNuisance0, organization1) + if err != nil { + return err + } + + publicreportNuisance0.R.Organization = organization1 + + organization1.R.Nuisances = append(organization1.R.Nuisances, publicreportNuisance0) + + return nil +} + +func (publicreportNuisance0 *PublicreportNuisance) AttachOrganization(ctx context.Context, exec bob.Executor, organization1 *Organization) error { + var err error + + _, err = attachPublicreportNuisanceOrganization0(ctx, exec, 1, publicreportNuisance0, organization1) + if err != nil { + return err + } + + publicreportNuisance0.R.Organization = organization1 + + organization1.R.Nuisances = append(organization1.R.Nuisances, publicreportNuisance0) + + return nil +} + type publicreportNuisanceWhere[Q psql.Filterable] struct { ID psql.WhereMod[Q, int32] AdditionalInfo psql.WhereMod[Q, string] @@ -963,6 +1070,7 @@ type publicreportNuisanceWhere[Q psql.Filterable] struct { Address psql.WhereMod[Q, string] Location psql.WhereNullMod[Q, string] Status psql.WhereMod[Q, enums.PublicreportReportstatustype] + OrganizationID psql.WhereNullMod[Q, int32] } func (publicreportNuisanceWhere[Q]) AliasedAs(alias string) publicreportNuisanceWhere[Q] { @@ -998,5 +1106,154 @@ func buildPublicreportNuisanceWhere[Q psql.Filterable](cols publicreportNuisance Address: psql.Where[Q, string](cols.Address), Location: psql.WhereNull[Q, string](cols.Location), Status: psql.Where[Q, enums.PublicreportReportstatustype](cols.Status), + OrganizationID: psql.WhereNull[Q, int32](cols.OrganizationID), + } +} + +func (o *PublicreportNuisance) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "Organization": + rel, ok := retrieved.(*Organization) + if !ok { + return fmt.Errorf("publicreportNuisance cannot load %T as %q", retrieved, name) + } + + o.R.Organization = rel + + if rel != nil { + rel.R.Nuisances = PublicreportNuisanceSlice{o} + } + return nil + default: + return fmt.Errorf("publicreportNuisance has no relationship %q", name) + } +} + +type publicreportNuisancePreloader struct { + Organization func(...psql.PreloadOption) psql.Preloader +} + +func buildPublicreportNuisancePreloader() publicreportNuisancePreloader { + return publicreportNuisancePreloader{ + Organization: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*Organization, OrganizationSlice](psql.PreloadRel{ + Name: "Organization", + Sides: []psql.PreloadSide{ + { + From: PublicreportNuisances, + To: Organizations, + FromColumns: []string{"organization_id"}, + ToColumns: []string{"id"}, + }, + }, + }, Organizations.Columns.Names(), opts...) + }, + } +} + +type publicreportNuisanceThenLoader[Q orm.Loadable] struct { + Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildPublicreportNuisanceThenLoader[Q orm.Loadable]() publicreportNuisanceThenLoader[Q] { + type OrganizationLoadInterface interface { + LoadOrganization(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return publicreportNuisanceThenLoader[Q]{ + Organization: thenLoadBuilder[Q]( + "Organization", + func(ctx context.Context, exec bob.Executor, retrieved OrganizationLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadOrganization(ctx, exec, mods...) + }, + ), + } +} + +// LoadOrganization loads the publicreportNuisance's Organization into the .R struct +func (o *PublicreportNuisance) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Organization = nil + + related, err := o.Organization(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.Nuisances = PublicreportNuisanceSlice{o} + + o.R.Organization = related + return nil +} + +// LoadOrganization loads the publicreportNuisance's Organization into the .R struct +func (os PublicreportNuisanceSlice) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + organizations, err := os.Organization(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range organizations { + if !o.OrganizationID.IsValue() { + continue + } + + if !(o.OrganizationID.IsValue() && o.OrganizationID.MustGet() == rel.ID) { + continue + } + + rel.R.Nuisances = append(rel.R.Nuisances, o) + + o.R.Organization = rel + break + } + } + + return nil +} + +type publicreportNuisanceJoins[Q dialect.Joinable] struct { + typ string + Organization modAs[Q, organizationColumns] +} + +func (j publicreportNuisanceJoins[Q]) aliasedAs(alias string) publicreportNuisanceJoins[Q] { + return buildPublicreportNuisanceJoins[Q](buildPublicreportNuisanceColumns(alias), j.typ) +} + +func buildPublicreportNuisanceJoins[Q dialect.Joinable](cols publicreportNuisanceColumns, typ string) publicreportNuisanceJoins[Q] { + return publicreportNuisanceJoins[Q]{ + typ: typ, + Organization: modAs[Q, organizationColumns]{ + c: Organizations.Columns, + f: func(to organizationColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Organizations.Name().As(to.Alias())).On( + to.ID.EQ(cols.OrganizationID), + )) + } + + return mods + }, + }, } } diff --git a/db/models/publicreport.pool.bob.go b/db/models/publicreport.pool.bob.go index 0a5a9d8e..3b5f7c08 100644 --- a/db/models/publicreport.pool.bob.go +++ b/db/models/publicreport.pool.bob.go @@ -59,6 +59,7 @@ type PublicreportPool struct { ReporterPhone string `db:"reporter_phone" ` Subscribe bool `db:"subscribe" ` Status enums.PublicreportReportstatustype `db:"status" ` + OrganizationID null.Val[int32] `db:"organization_id" ` R publicreportPoolR `db:"-" ` @@ -77,13 +78,14 @@ type PublicreportPoolsQuery = *psql.ViewQuery[*PublicreportPool, PublicreportPoo // publicreportPoolR is where relationships are stored. type publicreportPoolR struct { - Images PublicreportImageSlice // publicreport.pool_image.pool_image_image_id_fkeypublicreport.pool_image.pool_image_pool_id_fkey + Organization *Organization // publicreport.pool.pool_organization_id_fkey + Images PublicreportImageSlice // publicreport.pool_image.pool_image_image_id_fkeypublicreport.pool_image.pool_image_pool_id_fkey } func buildPublicreportPoolColumns(alias string) publicreportPoolColumns { return publicreportPoolColumns{ ColumnsExpr: expr.NewColumnsExpr( - "id", "access_comments", "access_gate", "access_fence", "access_locked", "access_dog", "access_other", "address", "address_country", "address_post_code", "address_place", "address_street", "address_region", "comments", "created", "h3cell", "has_adult", "has_larvae", "has_pupae", "location", "map_zoom", "owner_email", "owner_name", "owner_phone", "public_id", "reporter_email", "reporter_name", "reporter_phone", "subscribe", "status", + "id", "access_comments", "access_gate", "access_fence", "access_locked", "access_dog", "access_other", "address", "address_country", "address_post_code", "address_place", "address_street", "address_region", "comments", "created", "h3cell", "has_adult", "has_larvae", "has_pupae", "location", "map_zoom", "owner_email", "owner_name", "owner_phone", "public_id", "reporter_email", "reporter_name", "reporter_phone", "subscribe", "status", "organization_id", ).WithParent("publicreport.pool"), tableAlias: alias, ID: psql.Quote(alias, "id"), @@ -116,6 +118,7 @@ func buildPublicreportPoolColumns(alias string) publicreportPoolColumns { ReporterPhone: psql.Quote(alias, "reporter_phone"), Subscribe: psql.Quote(alias, "subscribe"), Status: psql.Quote(alias, "status"), + OrganizationID: psql.Quote(alias, "organization_id"), } } @@ -152,6 +155,7 @@ type publicreportPoolColumns struct { ReporterPhone psql.Expression Subscribe psql.Expression Status psql.Expression + OrganizationID psql.Expression } func (c publicreportPoolColumns) Alias() string { @@ -196,10 +200,11 @@ type PublicreportPoolSetter struct { ReporterPhone omit.Val[string] `db:"reporter_phone" ` Subscribe omit.Val[bool] `db:"subscribe" ` Status omit.Val[enums.PublicreportReportstatustype] `db:"status" ` + OrganizationID omitnull.Val[int32] `db:"organization_id" ` } func (s PublicreportPoolSetter) SetColumns() []string { - vals := make([]string, 0, 30) + vals := make([]string, 0, 31) if s.ID.IsValue() { vals = append(vals, "id") } @@ -290,6 +295,9 @@ func (s PublicreportPoolSetter) SetColumns() []string { if s.Status.IsValue() { vals = append(vals, "status") } + if !s.OrganizationID.IsUnset() { + vals = append(vals, "organization_id") + } return vals } @@ -384,6 +392,9 @@ func (s PublicreportPoolSetter) Overwrite(t *PublicreportPool) { if s.Status.IsValue() { t.Status = s.Status.MustGet() } + if !s.OrganizationID.IsUnset() { + t.OrganizationID = s.OrganizationID.MustGetNull() + } } func (s *PublicreportPoolSetter) Apply(q *dialect.InsertQuery) { @@ -392,7 +403,7 @@ func (s *PublicreportPoolSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 30) + vals := make([]bob.Expression, 31) if s.ID.IsValue() { vals[0] = psql.Arg(s.ID.MustGet()) } else { @@ -573,6 +584,12 @@ func (s *PublicreportPoolSetter) Apply(q *dialect.InsertQuery) { vals[29] = psql.Raw("DEFAULT") } + if !s.OrganizationID.IsUnset() { + vals[30] = psql.Arg(s.OrganizationID.MustGetNull()) + } else { + vals[30] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -582,7 +599,7 @@ func (s PublicreportPoolSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s PublicreportPoolSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 30) + exprs := make([]bob.Expression, 0, 31) if s.ID.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -794,6 +811,13 @@ func (s PublicreportPoolSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if !s.OrganizationID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "organization_id")...), + psql.Arg(s.OrganizationID), + }}) + } + return exprs } @@ -1020,6 +1044,30 @@ func (o PublicreportPoolSlice) ReloadAll(ctx context.Context, exec bob.Executor) return nil } +// Organization starts a query for related objects on organization +func (o *PublicreportPool) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + return Organizations.Query(append(mods, + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(o.OrganizationID))), + )...) +} + +func (os PublicreportPoolSlice) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + pkOrganizationID := make(pgtypes.Array[null.Val[int32]], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkOrganizationID = append(pkOrganizationID, o.OrganizationID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkOrganizationID), "integer[]")), + )) + + return Organizations.Query(append(mods, + sm.Where(psql.Group(Organizations.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + // Images starts a query for related objects on publicreport.image func (o *PublicreportPool) Images(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { return PublicreportImages.Query(append(mods, @@ -1049,6 +1097,54 @@ func (os PublicreportPoolSlice) Images(mods ...bob.Mod[*dialect.SelectQuery]) Pu )...) } +func attachPublicreportPoolOrganization0(ctx context.Context, exec bob.Executor, count int, publicreportPool0 *PublicreportPool, organization1 *Organization) (*PublicreportPool, error) { + setter := &PublicreportPoolSetter{ + OrganizationID: omitnull.From(organization1.ID), + } + + err := publicreportPool0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachPublicreportPoolOrganization0: %w", err) + } + + return publicreportPool0, nil +} + +func (publicreportPool0 *PublicreportPool) InsertOrganization(ctx context.Context, exec bob.Executor, related *OrganizationSetter) error { + var err error + + organization1, err := Organizations.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachPublicreportPoolOrganization0(ctx, exec, 1, publicreportPool0, organization1) + if err != nil { + return err + } + + publicreportPool0.R.Organization = organization1 + + organization1.R.PublicreportPool = append(organization1.R.PublicreportPool, publicreportPool0) + + return nil +} + +func (publicreportPool0 *PublicreportPool) AttachOrganization(ctx context.Context, exec bob.Executor, organization1 *Organization) error { + var err error + + _, err = attachPublicreportPoolOrganization0(ctx, exec, 1, publicreportPool0, organization1) + if err != nil { + return err + } + + publicreportPool0.R.Organization = organization1 + + organization1.R.PublicreportPool = append(organization1.R.PublicreportPool, publicreportPool0) + + return nil +} + func attachPublicreportPoolImages0(ctx context.Context, exec bob.Executor, count int, publicreportPool0 *PublicreportPool, publicreportImages2 PublicreportImageSlice) (PublicreportPoolImageSlice, error) { setters := make([]*PublicreportPoolImageSetter, count) for i := range count { @@ -1145,6 +1241,7 @@ type publicreportPoolWhere[Q psql.Filterable] struct { ReporterPhone psql.WhereMod[Q, string] Subscribe psql.WhereMod[Q, bool] Status psql.WhereMod[Q, enums.PublicreportReportstatustype] + OrganizationID psql.WhereNullMod[Q, int32] } func (publicreportPoolWhere[Q]) AliasedAs(alias string) publicreportPoolWhere[Q] { @@ -1183,6 +1280,7 @@ func buildPublicreportPoolWhere[Q psql.Filterable](cols publicreportPoolColumns) ReporterPhone: psql.Where[Q, string](cols.ReporterPhone), Subscribe: psql.Where[Q, bool](cols.Subscribe), Status: psql.Where[Q, enums.PublicreportReportstatustype](cols.Status), + OrganizationID: psql.WhereNull[Q, int32](cols.OrganizationID), } } @@ -1192,6 +1290,18 @@ func (o *PublicreportPool) Preload(name string, retrieved any) error { } switch name { + case "Organization": + rel, ok := retrieved.(*Organization) + if !ok { + return fmt.Errorf("publicreportPool cannot load %T as %q", retrieved, name) + } + + o.R.Organization = rel + + if rel != nil { + rel.R.PublicreportPool = PublicreportPoolSlice{o} + } + return nil case "Images": rels, ok := retrieved.(PublicreportImageSlice) if !ok { @@ -1211,22 +1321,48 @@ func (o *PublicreportPool) Preload(name string, retrieved any) error { } } -type publicreportPoolPreloader struct{} +type publicreportPoolPreloader struct { + Organization func(...psql.PreloadOption) psql.Preloader +} func buildPublicreportPoolPreloader() publicreportPoolPreloader { - return publicreportPoolPreloader{} + return publicreportPoolPreloader{ + Organization: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*Organization, OrganizationSlice](psql.PreloadRel{ + Name: "Organization", + Sides: []psql.PreloadSide{ + { + From: PublicreportPools, + To: Organizations, + FromColumns: []string{"organization_id"}, + ToColumns: []string{"id"}, + }, + }, + }, Organizations.Columns.Names(), opts...) + }, + } } type publicreportPoolThenLoader[Q orm.Loadable] struct { - Images func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Images func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildPublicreportPoolThenLoader[Q orm.Loadable]() publicreportPoolThenLoader[Q] { + type OrganizationLoadInterface interface { + LoadOrganization(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type ImagesLoadInterface interface { LoadImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } return publicreportPoolThenLoader[Q]{ + Organization: thenLoadBuilder[Q]( + "Organization", + func(ctx context.Context, exec bob.Executor, retrieved OrganizationLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadOrganization(ctx, exec, mods...) + }, + ), Images: thenLoadBuilder[Q]( "Images", func(ctx context.Context, exec bob.Executor, retrieved ImagesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -1236,6 +1372,61 @@ func buildPublicreportPoolThenLoader[Q orm.Loadable]() publicreportPoolThenLoade } } +// LoadOrganization loads the publicreportPool's Organization into the .R struct +func (o *PublicreportPool) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Organization = nil + + related, err := o.Organization(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.PublicreportPool = PublicreportPoolSlice{o} + + o.R.Organization = related + return nil +} + +// LoadOrganization loads the publicreportPool's Organization into the .R struct +func (os PublicreportPoolSlice) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + organizations, err := os.Organization(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range organizations { + if !o.OrganizationID.IsValue() { + continue + } + + if !(o.OrganizationID.IsValue() && o.OrganizationID.MustGet() == rel.ID) { + continue + } + + rel.R.PublicreportPool = append(rel.R.PublicreportPool, o) + + o.R.Organization = rel + break + } + } + + return nil +} + // LoadImages loads the publicreportPool's Images into the .R struct func (o *PublicreportPool) LoadImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -1414,8 +1605,9 @@ func (os PublicreportPoolSlice) LoadCountImages(ctx context.Context, exec bob.Ex } type publicreportPoolJoins[Q dialect.Joinable] struct { - typ string - Images modAs[Q, publicreportImageColumns] + typ string + Organization modAs[Q, organizationColumns] + Images modAs[Q, publicreportImageColumns] } func (j publicreportPoolJoins[Q]) aliasedAs(alias string) publicreportPoolJoins[Q] { @@ -1425,6 +1617,20 @@ func (j publicreportPoolJoins[Q]) aliasedAs(alias string) publicreportPoolJoins[ func buildPublicreportPoolJoins[Q dialect.Joinable](cols publicreportPoolColumns, typ string) publicreportPoolJoins[Q] { return publicreportPoolJoins[Q]{ typ: typ, + Organization: modAs[Q, organizationColumns]{ + c: Organizations.Columns, + f: func(to organizationColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Organizations.Name().As(to.Alias())).On( + to.ID.EQ(cols.OrganizationID), + )) + } + + return mods + }, + }, Images: modAs[Q, publicreportImageColumns]{ c: PublicreportImages.Columns, f: func(to publicreportImageColumns) bob.Mod[Q] { diff --git a/db/models/publicreport.quick.bob.go b/db/models/publicreport.quick.bob.go index c6514bb6..0486aebb 100644 --- a/db/models/publicreport.quick.bob.go +++ b/db/models/publicreport.quick.bob.go @@ -29,16 +29,17 @@ import ( // PublicreportQuick is an object representing the database table. type PublicreportQuick struct { - ID int32 `db:"id,pk" ` - Created time.Time `db:"created" ` - Comments string `db:"comments" ` - Location null.Val[string] `db:"location" ` - H3cell null.Val[string] `db:"h3cell" ` - PublicID string `db:"public_id" ` - ReporterEmail string `db:"reporter_email" ` - ReporterPhone string `db:"reporter_phone" ` - Address string `db:"address" ` - Status enums.PublicreportReportstatustype `db:"status" ` + ID int32 `db:"id,pk" ` + Created time.Time `db:"created" ` + Comments string `db:"comments" ` + Location null.Val[string] `db:"location" ` + H3cell null.Val[string] `db:"h3cell" ` + PublicID string `db:"public_id" ` + ReporterEmail string `db:"reporter_email" ` + ReporterPhone string `db:"reporter_phone" ` + Address string `db:"address" ` + Status enums.PublicreportReportstatustype `db:"status" ` + OrganizationID null.Val[int32] `db:"organization_id" ` R publicreportQuickR `db:"-" ` @@ -57,41 +58,44 @@ type PublicreportQuicksQuery = *psql.ViewQuery[*PublicreportQuick, PublicreportQ // publicreportQuickR is where relationships are stored. type publicreportQuickR struct { - Images PublicreportImageSlice // publicreport.quick_image.quick_image_image_id_fkeypublicreport.quick_image.quick_image_quick_id_fkey + Organization *Organization // publicreport.quick.quick_organization_id_fkey + Images PublicreportImageSlice // publicreport.quick_image.quick_image_image_id_fkeypublicreport.quick_image.quick_image_quick_id_fkey } func buildPublicreportQuickColumns(alias string) publicreportQuickColumns { return publicreportQuickColumns{ ColumnsExpr: expr.NewColumnsExpr( - "id", "created", "comments", "location", "h3cell", "public_id", "reporter_email", "reporter_phone", "address", "status", + "id", "created", "comments", "location", "h3cell", "public_id", "reporter_email", "reporter_phone", "address", "status", "organization_id", ).WithParent("publicreport.quick"), - tableAlias: alias, - ID: psql.Quote(alias, "id"), - Created: psql.Quote(alias, "created"), - Comments: psql.Quote(alias, "comments"), - Location: psql.Quote(alias, "location"), - H3cell: psql.Quote(alias, "h3cell"), - PublicID: psql.Quote(alias, "public_id"), - ReporterEmail: psql.Quote(alias, "reporter_email"), - ReporterPhone: psql.Quote(alias, "reporter_phone"), - Address: psql.Quote(alias, "address"), - Status: psql.Quote(alias, "status"), + tableAlias: alias, + ID: psql.Quote(alias, "id"), + Created: psql.Quote(alias, "created"), + Comments: psql.Quote(alias, "comments"), + Location: psql.Quote(alias, "location"), + H3cell: psql.Quote(alias, "h3cell"), + PublicID: psql.Quote(alias, "public_id"), + ReporterEmail: psql.Quote(alias, "reporter_email"), + ReporterPhone: psql.Quote(alias, "reporter_phone"), + Address: psql.Quote(alias, "address"), + Status: psql.Quote(alias, "status"), + OrganizationID: psql.Quote(alias, "organization_id"), } } type publicreportQuickColumns struct { expr.ColumnsExpr - tableAlias string - ID psql.Expression - Created psql.Expression - Comments psql.Expression - Location psql.Expression - H3cell psql.Expression - PublicID psql.Expression - ReporterEmail psql.Expression - ReporterPhone psql.Expression - Address psql.Expression - Status psql.Expression + tableAlias string + ID psql.Expression + Created psql.Expression + Comments psql.Expression + Location psql.Expression + H3cell psql.Expression + PublicID psql.Expression + ReporterEmail psql.Expression + ReporterPhone psql.Expression + Address psql.Expression + Status psql.Expression + OrganizationID psql.Expression } func (c publicreportQuickColumns) Alias() string { @@ -106,20 +110,21 @@ func (publicreportQuickColumns) AliasedAs(alias string) publicreportQuickColumns // All values are optional, and do not have to be set // Generated columns are not included type PublicreportQuickSetter struct { - ID omit.Val[int32] `db:"id,pk" ` - Created omit.Val[time.Time] `db:"created" ` - Comments omit.Val[string] `db:"comments" ` - Location omitnull.Val[string] `db:"location" ` - H3cell omitnull.Val[string] `db:"h3cell" ` - PublicID omit.Val[string] `db:"public_id" ` - ReporterEmail omit.Val[string] `db:"reporter_email" ` - ReporterPhone omit.Val[string] `db:"reporter_phone" ` - Address omit.Val[string] `db:"address" ` - Status omit.Val[enums.PublicreportReportstatustype] `db:"status" ` + ID omit.Val[int32] `db:"id,pk" ` + Created omit.Val[time.Time] `db:"created" ` + Comments omit.Val[string] `db:"comments" ` + Location omitnull.Val[string] `db:"location" ` + H3cell omitnull.Val[string] `db:"h3cell" ` + PublicID omit.Val[string] `db:"public_id" ` + ReporterEmail omit.Val[string] `db:"reporter_email" ` + ReporterPhone omit.Val[string] `db:"reporter_phone" ` + Address omit.Val[string] `db:"address" ` + Status omit.Val[enums.PublicreportReportstatustype] `db:"status" ` + OrganizationID omitnull.Val[int32] `db:"organization_id" ` } func (s PublicreportQuickSetter) SetColumns() []string { - vals := make([]string, 0, 10) + vals := make([]string, 0, 11) if s.ID.IsValue() { vals = append(vals, "id") } @@ -150,6 +155,9 @@ func (s PublicreportQuickSetter) SetColumns() []string { if s.Status.IsValue() { vals = append(vals, "status") } + if !s.OrganizationID.IsUnset() { + vals = append(vals, "organization_id") + } return vals } @@ -184,6 +192,9 @@ func (s PublicreportQuickSetter) Overwrite(t *PublicreportQuick) { if s.Status.IsValue() { t.Status = s.Status.MustGet() } + if !s.OrganizationID.IsUnset() { + t.OrganizationID = s.OrganizationID.MustGetNull() + } } func (s *PublicreportQuickSetter) Apply(q *dialect.InsertQuery) { @@ -192,7 +203,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) { - vals := make([]bob.Expression, 10) + vals := make([]bob.Expression, 11) if s.ID.IsValue() { vals[0] = psql.Arg(s.ID.MustGet()) } else { @@ -253,6 +264,12 @@ func (s *PublicreportQuickSetter) Apply(q *dialect.InsertQuery) { vals[9] = psql.Raw("DEFAULT") } + if !s.OrganizationID.IsUnset() { + vals[10] = psql.Arg(s.OrganizationID.MustGetNull()) + } else { + vals[10] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -262,7 +279,7 @@ func (s PublicreportQuickSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s PublicreportQuickSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 10) + exprs := make([]bob.Expression, 0, 11) if s.ID.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -334,6 +351,13 @@ func (s PublicreportQuickSetter) Expressions(prefix ...string) []bob.Expression }}) } + if !s.OrganizationID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "organization_id")...), + psql.Arg(s.OrganizationID), + }}) + } + return exprs } @@ -560,6 +584,30 @@ func (o PublicreportQuickSlice) ReloadAll(ctx context.Context, exec bob.Executor return nil } +// Organization starts a query for related objects on organization +func (o *PublicreportQuick) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + return Organizations.Query(append(mods, + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(o.OrganizationID))), + )...) +} + +func (os PublicreportQuickSlice) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + pkOrganizationID := make(pgtypes.Array[null.Val[int32]], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkOrganizationID = append(pkOrganizationID, o.OrganizationID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkOrganizationID), "integer[]")), + )) + + return Organizations.Query(append(mods, + sm.Where(psql.Group(Organizations.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + // Images starts a query for related objects on publicreport.image func (o *PublicreportQuick) Images(mods ...bob.Mod[*dialect.SelectQuery]) PublicreportImagesQuery { return PublicreportImages.Query(append(mods, @@ -589,6 +637,54 @@ func (os PublicreportQuickSlice) Images(mods ...bob.Mod[*dialect.SelectQuery]) P )...) } +func attachPublicreportQuickOrganization0(ctx context.Context, exec bob.Executor, count int, publicreportQuick0 *PublicreportQuick, organization1 *Organization) (*PublicreportQuick, error) { + setter := &PublicreportQuickSetter{ + OrganizationID: omitnull.From(organization1.ID), + } + + err := publicreportQuick0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachPublicreportQuickOrganization0: %w", err) + } + + return publicreportQuick0, nil +} + +func (publicreportQuick0 *PublicreportQuick) InsertOrganization(ctx context.Context, exec bob.Executor, related *OrganizationSetter) error { + var err error + + organization1, err := Organizations.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachPublicreportQuickOrganization0(ctx, exec, 1, publicreportQuick0, organization1) + if err != nil { + return err + } + + publicreportQuick0.R.Organization = organization1 + + organization1.R.Quicks = append(organization1.R.Quicks, publicreportQuick0) + + return nil +} + +func (publicreportQuick0 *PublicreportQuick) AttachOrganization(ctx context.Context, exec bob.Executor, organization1 *Organization) error { + var err error + + _, err = attachPublicreportQuickOrganization0(ctx, exec, 1, publicreportQuick0, organization1) + if err != nil { + return err + } + + publicreportQuick0.R.Organization = organization1 + + organization1.R.Quicks = append(organization1.R.Quicks, publicreportQuick0) + + return nil +} + func attachPublicreportQuickImages0(ctx context.Context, exec bob.Executor, count int, publicreportQuick0 *PublicreportQuick, publicreportImages2 PublicreportImageSlice) (PublicreportQuickImageSlice, error) { setters := make([]*PublicreportQuickImageSetter, count) for i := range count { @@ -655,16 +751,17 @@ func (publicreportQuick0 *PublicreportQuick) AttachImages(ctx context.Context, e } type publicreportQuickWhere[Q psql.Filterable] struct { - ID psql.WhereMod[Q, int32] - Created psql.WhereMod[Q, time.Time] - Comments psql.WhereMod[Q, string] - Location psql.WhereNullMod[Q, string] - H3cell psql.WhereNullMod[Q, string] - PublicID psql.WhereMod[Q, string] - ReporterEmail psql.WhereMod[Q, string] - ReporterPhone psql.WhereMod[Q, string] - Address psql.WhereMod[Q, string] - Status psql.WhereMod[Q, enums.PublicreportReportstatustype] + ID psql.WhereMod[Q, int32] + Created psql.WhereMod[Q, time.Time] + Comments psql.WhereMod[Q, string] + Location psql.WhereNullMod[Q, string] + H3cell psql.WhereNullMod[Q, string] + PublicID psql.WhereMod[Q, string] + ReporterEmail psql.WhereMod[Q, string] + ReporterPhone psql.WhereMod[Q, string] + Address psql.WhereMod[Q, string] + Status psql.WhereMod[Q, enums.PublicreportReportstatustype] + OrganizationID psql.WhereNullMod[Q, int32] } func (publicreportQuickWhere[Q]) AliasedAs(alias string) publicreportQuickWhere[Q] { @@ -673,16 +770,17 @@ func (publicreportQuickWhere[Q]) AliasedAs(alias string) publicreportQuickWhere[ func buildPublicreportQuickWhere[Q psql.Filterable](cols publicreportQuickColumns) publicreportQuickWhere[Q] { return publicreportQuickWhere[Q]{ - ID: psql.Where[Q, int32](cols.ID), - Created: psql.Where[Q, time.Time](cols.Created), - Comments: psql.Where[Q, string](cols.Comments), - Location: psql.WhereNull[Q, string](cols.Location), - H3cell: psql.WhereNull[Q, string](cols.H3cell), - PublicID: psql.Where[Q, string](cols.PublicID), - ReporterEmail: psql.Where[Q, string](cols.ReporterEmail), - ReporterPhone: psql.Where[Q, string](cols.ReporterPhone), - Address: psql.Where[Q, string](cols.Address), - Status: psql.Where[Q, enums.PublicreportReportstatustype](cols.Status), + ID: psql.Where[Q, int32](cols.ID), + Created: psql.Where[Q, time.Time](cols.Created), + Comments: psql.Where[Q, string](cols.Comments), + Location: psql.WhereNull[Q, string](cols.Location), + H3cell: psql.WhereNull[Q, string](cols.H3cell), + PublicID: psql.Where[Q, string](cols.PublicID), + ReporterEmail: psql.Where[Q, string](cols.ReporterEmail), + ReporterPhone: psql.Where[Q, string](cols.ReporterPhone), + Address: psql.Where[Q, string](cols.Address), + Status: psql.Where[Q, enums.PublicreportReportstatustype](cols.Status), + OrganizationID: psql.WhereNull[Q, int32](cols.OrganizationID), } } @@ -692,6 +790,18 @@ func (o *PublicreportQuick) Preload(name string, retrieved any) error { } switch name { + case "Organization": + rel, ok := retrieved.(*Organization) + if !ok { + return fmt.Errorf("publicreportQuick cannot load %T as %q", retrieved, name) + } + + o.R.Organization = rel + + if rel != nil { + rel.R.Quicks = PublicreportQuickSlice{o} + } + return nil case "Images": rels, ok := retrieved.(PublicreportImageSlice) if !ok { @@ -711,22 +821,48 @@ func (o *PublicreportQuick) Preload(name string, retrieved any) error { } } -type publicreportQuickPreloader struct{} +type publicreportQuickPreloader struct { + Organization func(...psql.PreloadOption) psql.Preloader +} func buildPublicreportQuickPreloader() publicreportQuickPreloader { - return publicreportQuickPreloader{} + return publicreportQuickPreloader{ + Organization: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*Organization, OrganizationSlice](psql.PreloadRel{ + Name: "Organization", + Sides: []psql.PreloadSide{ + { + From: PublicreportQuicks, + To: Organizations, + FromColumns: []string{"organization_id"}, + ToColumns: []string{"id"}, + }, + }, + }, Organizations.Columns.Names(), opts...) + }, + } } type publicreportQuickThenLoader[Q orm.Loadable] struct { - Images func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Images func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildPublicreportQuickThenLoader[Q orm.Loadable]() publicreportQuickThenLoader[Q] { + type OrganizationLoadInterface interface { + LoadOrganization(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type ImagesLoadInterface interface { LoadImages(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } return publicreportQuickThenLoader[Q]{ + Organization: thenLoadBuilder[Q]( + "Organization", + func(ctx context.Context, exec bob.Executor, retrieved OrganizationLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadOrganization(ctx, exec, mods...) + }, + ), Images: thenLoadBuilder[Q]( "Images", func(ctx context.Context, exec bob.Executor, retrieved ImagesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -736,6 +872,61 @@ func buildPublicreportQuickThenLoader[Q orm.Loadable]() publicreportQuickThenLoa } } +// LoadOrganization loads the publicreportQuick's Organization into the .R struct +func (o *PublicreportQuick) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Organization = nil + + related, err := o.Organization(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.Quicks = PublicreportQuickSlice{o} + + o.R.Organization = related + return nil +} + +// LoadOrganization loads the publicreportQuick's Organization into the .R struct +func (os PublicreportQuickSlice) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + organizations, err := os.Organization(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range organizations { + if !o.OrganizationID.IsValue() { + continue + } + + if !(o.OrganizationID.IsValue() && o.OrganizationID.MustGet() == rel.ID) { + continue + } + + rel.R.Quicks = append(rel.R.Quicks, o) + + o.R.Organization = rel + break + } + } + + return nil +} + // LoadImages loads the publicreportQuick's Images into the .R struct func (o *PublicreportQuick) LoadImages(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -914,8 +1105,9 @@ func (os PublicreportQuickSlice) LoadCountImages(ctx context.Context, exec bob.E } type publicreportQuickJoins[Q dialect.Joinable] struct { - typ string - Images modAs[Q, publicreportImageColumns] + typ string + Organization modAs[Q, organizationColumns] + Images modAs[Q, publicreportImageColumns] } func (j publicreportQuickJoins[Q]) aliasedAs(alias string) publicreportQuickJoins[Q] { @@ -925,6 +1117,20 @@ func (j publicreportQuickJoins[Q]) aliasedAs(alias string) publicreportQuickJoin func buildPublicreportQuickJoins[Q dialect.Joinable](cols publicreportQuickColumns, typ string) publicreportQuickJoins[Q] { return publicreportQuickJoins[Q]{ typ: typ, + Organization: modAs[Q, organizationColumns]{ + c: Organizations.Columns, + f: func(to organizationColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Organizations.Name().As(to.Alias())).On( + to.ID.EQ(cols.OrganizationID), + )) + } + + return mods + }, + }, Images: modAs[Q, publicreportImageColumns]{ c: PublicreportImages.Columns, f: func(to publicreportImageColumns) bob.Mod[Q] { diff --git a/go.mod b/go.mod index cde54c4f..bc3b05ca 100644 --- a/go.mod +++ b/go.mod @@ -34,13 +34,19 @@ require ( require ( github.com/ajg/form v1.5.1 // indirect github.com/beevik/etree v1.1.0 // indirect + github.com/dsoprea/go-exif-extra v0.0.0-20210513050430-a8c8bb657ad5 // indirect github.com/dsoprea/go-exif/v3 v3.0.1 // indirect + github.com/dsoprea/go-heic-exif-extractor/v2 v2.0.0-20210512044107-62067e44c235 // indirect github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20221012074422-4f3f7e934102 // indirect github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c // indirect + github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512044023-23bdd883ee8e // indirect + github.com/dsoprea/go-tiff-image-structure/v2 v2.0.0-20210512044046-dc78da6a809b // indirect github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect + github.com/dsoprea/go-webp-image-structure v0.0.0-20210512044215-f98af2b0401e // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/evanoberholster/imagemeta v0.3.1 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect @@ -64,20 +70,23 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect github.com/sethvargo/go-retry v0.3.0 // indirect github.com/stretchr/testify v1.11.1 // indirect github.com/tetratelabs/wazero v1.9.0 // indirect github.com/tidwall/geoindex v1.4.4 // indirect github.com/tidwall/gjson v1.12.1 // indirect github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/rtree v1.3.1 // indirect github.com/tidwall/sjson v1.2.4 // indirect github.com/tinylib/msgp v1.3.0 // indirect github.com/twilio/twilio-go v1.29.1 // indirect github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect go.uber.org/multierr v1.11.0 // indirect + go4.org v0.0.0-20200411211856-f5505b9728dd // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect + golang.org/x/image v0.0.0-20200618115811-c13761719519 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect diff --git a/go.sum b/go.sum index 41a400eb..85b48017 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,26 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Gleipnir-Technology/arcgis-go v0.0.5 h1:7UdgFZv7bnmLqkvGLivKurLKICmwZGWctPxESjDjeA8= github.com/Gleipnir-Technology/arcgis-go v0.0.5/go.mod h1:Stx2sn5Lvuyhy4SaTQpbLNCAfenboDINi/UU5gQvz4k= github.com/Gleipnir-Technology/go-geojson2h3/v2 v2.0.0 h1:6OMVxoiX9r7dEkIyYYKtSu7I2UDq64dww4JxJTo3p78= @@ -22,6 +41,11 @@ github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -45,16 +69,25 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dsoprea/go-exif-extra v0.0.0-20210513050430-a8c8bb657ad5 h1:F5HkHi38eOQECRJTJIV1Amn+J5q2atJf2feGPJ8/v+U= +github.com/dsoprea/go-exif-extra v0.0.0-20210513050430-a8c8bb657ad5/go.mod h1:9RNGsP4bsNJg7MDbFLmCw1lovrpGB2xCOjzyugh+4iY= github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E= +github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4/go.mod h1:9EXlPeHfblFFnwu5UOqmP2eoZfJyAZ2Ri/Vki33ajO0= github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8= +github.com/dsoprea/go-exif/v3 v3.0.0-20200717071058-9393e7afd446/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= github.com/dsoprea/go-exif/v3 v3.0.0-20210428042052-dca55bf8ca15/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= +github.com/dsoprea/go-exif/v3 v3.0.0-20210512043655-120bcdb2a55e/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= +github.com/dsoprea/go-exif/v3 v3.0.0-20210512055020-8213cfabc61b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= github.com/dsoprea/go-exif/v3 v3.0.0-20221003160559-cf5cd88aa559/go.mod h1:rW6DMEv25U9zCtE5ukC7ttBRllXj7g7TAHl7tQrT5No= github.com/dsoprea/go-exif/v3 v3.0.0-20221003171958-de6cb6e380a8/go.mod h1:akyZEJZ/k5bmbC9gA612ZLQkcED8enS9vuTiuAkENr0= github.com/dsoprea/go-exif/v3 v3.0.1 h1:/IE4iW7gvY7BablV1XY0unqhMv26EYpOquVMwoBo/wc= github.com/dsoprea/go-exif/v3 v3.0.1/go.mod h1:10HkA1Wz3h398cDP66L+Is9kKDmlqlIJGPv8pk4EWvc= +github.com/dsoprea/go-heic-exif-extractor/v2 v2.0.0-20210512044107-62067e44c235 h1:a/XFkZdudAjXegNFRIf5vFjsF9LgFbiR5kjfHJZfIRA= +github.com/dsoprea/go-heic-exif-extractor/v2 v2.0.0-20210512044107-62067e44c235/go.mod h1:3mWA3lvkafMCuqoYKYXx/9YQgonsiAXf+KPSNlB/ZtI= github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb h1:gwjJjUr6FY7zAWVEueFPrcRHhd9+IK81TcItbqw2du4= github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM= +github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20210512043942-b434301c6836/go.mod h1:WaARaUjQuSuDCDFAiU/GwzfxMTJBulfEhqEA2Tx6B4Y= github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20221012074422-4f3f7e934102 h1:gmTXQdSuuuORRFPTS2uaYpAXU5oUNkXdeYSlZe5NvsE= github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20221012074422-4f3f7e934102/go.mod h1:WaARaUjQuSuDCDFAiU/GwzfxMTJBulfEhqEA2Tx6B4Y= github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= @@ -63,6 +96,12 @@ github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6 github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c h1:7j5aWACOzROpr+dvMtu8GnI97g9ShLWD72XIELMgn+c= github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c/go.mod h1:pqKB+ijp27cEcrHxhXVgUUMlSDRuGJJp1E+20Lj5H0E= +github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d h1:8+qI8ant/vZkNSsbwSjIR6XJfWcDVTg/qx/3pRUUZNA= +github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d/go.mod h1:yTR3tKgyk20phAFg6IE9ulMA5NjEDD2wyx+okRFLVtw= +github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512044023-23bdd883ee8e h1:7xT+Xgi019P9KVdcl+QUnSrgKCwIZWqkPNk5GygFTsw= +github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512044023-23bdd883ee8e/go.mod h1:scnx0wQSM7UiCMK66dSdiPZvL2hl6iF5DvpZ7uT59MY= +github.com/dsoprea/go-tiff-image-structure/v2 v2.0.0-20210512044046-dc78da6a809b h1:r5TpplS/qPIevKTw6B9ft3p00FP6Flp3lXizranDiVQ= +github.com/dsoprea/go-tiff-image-structure/v2 v2.0.0-20210512044046-dc78da6a809b/go.mod h1:O3HgJ0u+ZTGEk2HZq4/7OvE8QPXWrDM2I9hrD8Qq0o4= github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf h1:/w4QxepU4AHh3AuO6/g8y/YIIHH5+aKP3Bj8sg5cqhU= github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8= github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU= @@ -70,10 +109,17 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LV github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU= github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw= github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8= +github.com/dsoprea/go-webp-image-structure v0.0.0-20210512044215-f98af2b0401e h1:Hj7L0xxjASwligRr2F2qy7i4UOk422xcZDvWQSU4m8I= +github.com/dsoprea/go-webp-image-structure v0.0.0-20210512044215-f98af2b0401e/go.mod h1:SRCiMUH7zHuGUQAEnxmURDSsXUIQZfCrQiIkywxxnSs= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807/go.mod h1:Xoiu5VdKMvbRgHuY7+z64lhu/7lvax/22nzASF6GrO8= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanoberholster/imagemeta v0.3.1 h1:E4GUjXcvlVMjP9joN25+bBNf3Al3MTTfMqCrDOCW+LE= +github.com/evanoberholster/imagemeta v0.3.1/go.mod h1:V0vtDJmjTqvwAYO8r+u33NRVIMXQb0qSqEfImoKEiXM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= @@ -88,6 +134,8 @@ github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWE github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -109,14 +157,42 @@ github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgR github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= @@ -130,8 +206,12 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jaswdr/faker/v2 v2.8.1 h1:2AcPgHDBXYQregFUH9LgVZKfFupc4SIquYhp29sf5wQ= github.com/jaswdr/faker/v2 v2.8.1/go.mod h1:jZq+qzNQr8/P+5fHd9t3txe2GNPnthrTfohtnJ7B+68= +github.com/jdeng/goheif v0.0.0-20200323230657-a0d6a8b3e68f/go.mod h1:G7IyA3/eR9IFmUIPdyP3c0l4ZaqEvXAk876WfaQ8plc= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -205,10 +285,12 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM= github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmYrbRiUgv+g37W5suLLLxwwniTSc= github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -216,6 +298,9 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= @@ -263,6 +348,8 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/rtree v1.3.1 h1:xu3vJPKJrmGce7YJcFUCoqLrp9DTUEJBnVgdPSXHgHs= github.com/tidwall/rtree v1.3.1/go.mod h1:S+JSsqPTI8LfWA4xHBo5eXzie8WJLVFeppAutSegl6M= github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc= @@ -281,10 +368,15 @@ github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfP github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM= github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4= github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY= +github.com/wayneashleyberry/terminal-dimensions v1.0.0/go.mod h1:PW2XrtV6KmKOPhuf7wbtcmw1/IFnC39mryRET2XbxeE= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= @@ -297,24 +389,67 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU= +go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -324,14 +459,35 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -355,7 +511,10 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -363,8 +522,32 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= @@ -372,6 +555,39 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= @@ -392,6 +608,11 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= @@ -400,3 +621,6 @@ modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go index f3c432e1..4b73dfac 100644 --- a/main.go +++ b/main.go @@ -52,11 +52,11 @@ func main() { sr := nidussync.Router() hr.Map("", sr) // default hr.Map("*", sr) // default - hr.Map(config.URLReport, publicreport.Router()) // report.mosquitoes.online - hr.Map(config.URLSync, sr) + hr.Map(config.DomainRMO, publicreport.Router()) // report.mosquitoes.online + hr.Map(config.DomainNidus, sr) r.Mount("/", hr) - log.Info().Str("report url", config.URLReport).Str("sync url", config.URLSync).Msg("Serving at URLs") + log.Info().Str("report url", config.DomainRMO).Str("sync url", config.DomainNidus).Msg("Serving at URLs") // Start up background processes ctx, cancel := context.WithCancel(context.Background()) diff --git a/platform/district.go b/platform/district.go index d9c821ba..076848d5 100644 --- a/platform/district.go +++ b/platform/district.go @@ -2,30 +2,50 @@ package platform import ( "context" + "errors" "fmt" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/rs/zerolog/log" "github.com/stephenafamo/bob/dialect/psql" "github.com/stephenafamo/bob/dialect/psql/sm" ) -func DistrictForLocation(ctx context.Context, lng float64, lat float64) (*models.ImportDistrict, error) { - rows, err := models.ImportDistricts.Query( +func DistrictForLocation(ctx context.Context, lng float64, lat float64) (*models.ImportDistrict, *models.Organization, error) { + districts, err := models.ImportDistricts.Query( sm.Where( psql.F("ST_Contains", psql.Raw("geom_4326"), psql.F("ST_SetSRID", psql.F("ST_MakePoint", psql.Arg(lng), psql.Arg(lat)), psql.Arg(4326))), ), ).All(ctx, db.PGInstance.BobDB) + + log.Debug().Int("len", len(districts)).Float64("lng", lng).Float64("lat", lat).Msg("Attempting district match") if err != nil { - return nil, fmt.Errorf("failed to query district: %w", err) + return nil, nil, fmt.Errorf("failed to query district: %w", err) } - switch len(rows) { + switch len(districts) { case 0: - return nil, nil + return nil, nil, nil case 1: - return rows[0], nil + district := districts[0] + organizations, err := models.Organizations.Query( + sm.Where( + models.Organizations.Columns.ImportDistrictGid.EQ(psql.Arg(district.Gid)), + ), + ).All(ctx, db.PGInstance.BobDB) + if err != nil { + return nil, nil, fmt.Errorf("failed to query organization: %w", err) + } + switch len(organizations) { + case 0: + return nil, nil, nil + case 1: + return district, organizations[0], nil + default: + return nil, nil, errors.New("too many organizations") + } default: - return nil, nil + return nil, nil, errors.New("too many districts") } } diff --git a/public-report/endpoint.go b/public-report/endpoint.go index 397d536d..ef923f1c 100644 --- a/public-report/endpoint.go +++ b/public-report/endpoint.go @@ -8,9 +8,6 @@ import ( "github.com/rs/zerolog/log" ) -type ContextRegisterNotificationsComplete struct { - ReportID string -} type ContextRoot struct{} var ( diff --git a/public-report/image-upload.go b/public-report/image-upload.go index ea40a0cd..b566f06e 100644 --- a/public-report/image-upload.go +++ b/public-report/image-upload.go @@ -13,21 +13,27 @@ import ( "net/http" "time" - "github.com/aarondl/opt/omit" - "github.com/dsoprea/go-exif/v3" - exifcommon "github.com/dsoprea/go-exif/v3/common" - //"github.com/dsoprea/go-jpeg-image-structure/v2" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/userfile" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/rs/zerolog/log" + "github.com/rwcarlsen/goexif/exif" "github.com/stephenafamo/bob" "github.com/stephenafamo/bob/dialect/psql" "github.com/stephenafamo/bob/dialect/psql/um" + //exif "github.com/rwcarlsen/goexif/exif" + //"github.com/dsoprea/go-exif-extra/format" ) +type GPS struct { + Latitude float64 + Longitude float64 +} + type ExifCollection struct { - GPS *exif.GpsInfo + GPS *GPS Tags map[string]string } @@ -42,43 +48,30 @@ type ImageUpload struct { UUID uuid.UUID } -func extractExif(file_bytes []byte) (result ExifCollection, err error) { +func extractExif(content_type string, file_bytes []byte) (result ExifCollection, err error) { + /* + Using "github.com/evanoberholster/imagemeta" + meta, err := imagemeta.Decode(bytes.NewReader(file_bytes)) + if err != nil { + return result, fmt.Errorf("Failed to decode image meta: %w", err) + } + result.GPS = &GPS{ + Latitude: meta.GPS.Latitude(), + Longitude: meta.GPS.Longitude(), + } + return result, err + */ - raw_exif, err := exif.SearchAndExtractExifWithReader(bytes.NewReader(file_bytes)) + exif, err := exif.Decode(bytes.NewReader(file_bytes)) if err != nil { - return result, fmt.Errorf("Failed to find exif: %w", err) + return result, fmt.Errorf("Failed to decode image meta: %w", err) } - im, err := exifcommon.NewIfdMappingWithStandard() - if err != nil { - return result, fmt.Errorf("Failed to create new idf mapping: %w", err) + lat, lng, _ := exif.LatLong() + result.GPS = &GPS{ + Latitude: lat, + Longitude: lng, } - ti := exif.NewTagIndex() - _, index, err := exif.Collect(im, ti, raw_exif) - if err != nil { - return result, fmt.Errorf("Failed to collect exif: %w", err) - } - ifd, err := index.RootIfd.ChildWithIfdPath(exifcommon.IfdGpsInfoStandardIfdIdentity) - if err != nil { - return result, fmt.Errorf("Failed to find gps exif: %w", err) - } - gi, err := ifd.GpsInfo() - if err != nil { - log.Info().Err(err).Msg("Failed to get GPS info for uploaded image") - result.GPS = nil - } else { - result.GPS = gi - } - result.Tags = make(map[string]string, 0) - - tags, _, err := exif.GetFlatExifData(raw_exif, &exif.ScanOptions{}) - if err != nil { - return result, fmt.Errorf("Failed to gather flat exif: %w", err) - } - for _, t := range tags { - result.Tags[t.TagName] = t.Formatted - } - log.Info().Str("GPS", fmt.Sprintf("%s", gi)).Int("count", len(result.Tags)).Msg("Extracted exif tags") - return result, nil + return result, err } func extractImageUpload(headers *multipart.FileHeader) (upload ImageUpload, err error) { @@ -91,11 +84,12 @@ func extractImageUpload(headers *multipart.FileHeader) (upload ImageUpload, err file_bytes, err := io.ReadAll(file) content_type := http.DetectContentType(file_bytes) - exif, err := extractExif(file_bytes) + exif, err := extractExif(content_type, file_bytes) if err != nil { //return upload, fmt.Errorf("Failed to extract EXIF data: %w", err) log.Warn().Err(err).Msg("Failed to extract EXIF data") } + log.Debug().Float64("lat", exif.GPS.Latitude).Float64("lng", exif.GPS.Longitude).Msg("extracted GPS from exif") i, format, err := image.Decode(bytes.NewReader(file_bytes)) if err != nil { @@ -143,7 +137,8 @@ func saveImageUploads(ctx context.Context, tx bob.Tx, uploads []ImageUpload) (mo ContentType: omit.From(u.ContentType), Created: omit.From(time.Now()), - //Location: omitnull.From(nil), + //Location: psql.Raw("NULL"), + Location: omitnull.FromPtr[string](nil), ResolutionX: omit.From(int32(u.Bounds.Max.X)), ResolutionY: omit.From(int32(u.Bounds.Max.Y)), StorageUUID: omit.From(u.UUID), @@ -158,7 +153,7 @@ func saveImageUploads(ctx context.Context, tx bob.Tx, uploads []ImageUpload) (mo if u.Exif.GPS != nil { _, err = psql.Update( um.Table("publicreport.image"), - um.SetCol("location").To(fmt.Sprintf("ST_GeometryFromText('Point(%f %f)')", u.Exif.GPS.Longitude.Decimal(), u.Exif.GPS.Latitude.Decimal())), + um.SetCol("location").To(fmt.Sprintf("ST_GeometryFromText('Point(%f %f)')", u.Exif.GPS.Longitude, u.Exif.GPS.Latitude)), um.Where(psql.Quote("id").EQ(psql.Arg(image.ID))), ).Exec(ctx, tx) } @@ -171,9 +166,11 @@ func saveImageUploads(ctx context.Context, tx bob.Tx, uploads []ImageUpload) (mo Value: omit.From(v), }) } - _, err = models.PublicreportImageExifs.Insert(bob.ToMods(exif_setters...)).Exec(ctx, tx) - if err != nil { - return images, fmt.Errorf("Failed to create photo exif records: %w", err) + if len(exif_setters) > 0 { + _, err = models.PublicreportImageExifs.Insert(bob.ToMods(exif_setters...)).Exec(ctx, tx) + if err != nil { + return images, fmt.Errorf("Failed to create photo exif records: %w", err) + } } images = append(images, image) log.Info().Int32("id", image.ID).Int("tags", len(u.Exif.Tags)).Msg("Saved an uploaded file to the database") diff --git a/public-report/quick.go b/public-report/quick.go index 561ddaae..bf2106c8 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -1,6 +1,7 @@ package publicreport import ( + "context" "fmt" "net/http" "strconv" @@ -8,11 +9,13 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/background" "github.com/Gleipnir-Technology/nidus-sync/comms" + "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/platform" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" @@ -21,10 +24,18 @@ import ( "github.com/stephenafamo/bob/dialect/psql/um" ) -type ContextQuick struct{} -type ContextQuickSubmitComplete struct { +type ContentQuick struct{} +type ContentQuickSubmitComplete struct { + District *District ReportID string } +type ContentRegisterNotificationsComplete struct { + ReportID string +} +type District struct { + LogoURL string + Name string +} var ( quickT = buildTemplate("quick", "base") @@ -36,16 +47,45 @@ func getQuick(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, quickT, - ContextQuick{}, + ContentQuick{}, ) } func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) { - report := r.URL.Query().Get("report") + ctx := r.Context() + report_id := r.URL.Query().Get("report") + report, err := models.PublicreportQuicks.Query( + models.SelectWhere.PublicreportQuicks.PublicID.EQ(report_id), + ).One(ctx, db.PGInstance.BobDB) + if err != nil { + respondError(w, "Failed to get report", err, http.StatusInternalServerError) + return + } + var district *District + if !report.OrganizationID.IsNull() { + org_id := report.OrganizationID.MustGet() + org, err := models.Organizations.Query( + models.Preload.Organization.ImportDistrictGidDistrict(), + models.SelectWhere.Organizations.ID.EQ(org_id), + ).One(ctx, db.PGInstance.BobDB) + if err != nil { + respondError(w, "Failed to get org", err, http.StatusInternalServerError) + return + } + d := org.R.ImportDistrictGidDistrict + log.Debug().Int32("org_id", org.ID).Int32("d_gid", d.Gid).Msg("Getting district") + if d != nil { + district = &District{ + LogoURL: config.MakeURLNidus("/api/district/%d/logo", org_id), + Name: d.Agency.GetOr("Unknown"), + } + } + } htmlpage.RenderOrError( w, quickSubmitCompleteT, - ContextQuickSubmitComplete{ - ReportID: report, + ContentQuickSubmitComplete{ + District: district, + ReportID: report.PublicID, }, ) } @@ -54,11 +94,38 @@ func getRegisterNotificationsComplete(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, registerNotificationsCompleteT, - ContextRegisterNotificationsComplete{ + ContentRegisterNotificationsComplete{ ReportID: report, }, ) } +func matchDistrict(ctx context.Context, longitude, latitude float64, images []ImageUpload) (*int32, error) { + for _, image := range images { + if image.Exif.GPS == nil { + continue + } + _, org, err := platform.DistrictForLocation(ctx, image.Exif.GPS.Longitude, image.Exif.GPS.Latitude) + if err != nil { + log.Warn().Err(err).Msg("Failed to get district for location") + continue + } + if org != nil { + return &org.ID, nil + } + } + _, org, err := platform.DistrictForLocation(ctx, longitude, latitude) + if err != nil { + log.Warn().Err(err).Msg("Failed to get district for location") + return nil, fmt.Errorf("Failed to get district for location: %w", err) + } + if org == nil { + log.Debug().Err(err).Float64("lng", longitude).Float64("lat", latitude).Msg("No district match by report location") + return nil, nil + } + log.Debug().Err(err).Int32("org_id", org.ID).Float64("lng", longitude).Float64("lat", latitude).Msg("Found district match by report location") + return &org.ID, nil +} + func postQuick(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(32 << 10) // 32 MB buffer if err != nil { @@ -92,11 +159,30 @@ func postQuick(w http.ResponseWriter, r *http.Request) { } defer tx.Rollback(ctx) + uploads, err := extractImageUploads(r) + log.Info().Int("len", len(uploads)).Msg("extracted uploads") + if err != nil { + respondError(w, "Failed to extract image uploads", err, http.StatusInternalServerError) + return + } + images, err := saveImageUploads(ctx, tx, uploads) + if err != nil { + respondError(w, "Failed to save image uploads", err, http.StatusInternalServerError) + return + } + + organization_id, err := matchDistrict(ctx, longitude, latitude, uploads) + if err != nil { + log.Warn().Err(err).Msg("Failed to match district") + } + + log.Info().Int("len", len(images)).Msg("saved uploads") c, err := h3utils.GetCell(longitude, latitude, 15) setter := models.PublicreportQuickSetter{ - Address: omit.From(""), - Created: omit.From(time.Now()), - Comments: omit.From(comments), + Address: omit.From(""), + Created: omit.From(time.Now()), + Comments: omit.From(comments), + OrganizationID: omitnull.FromPtr(organization_id), //Location: omitnull.From(fmt.Sprintf("ST_GeometryFromText(Point(%s %s))", longitude, latitude)), H3cell: omitnull.From(c.String()), PublicID: omit.From(u), @@ -119,18 +205,6 @@ func postQuick(w http.ResponseWriter, r *http.Request) { return } log.Info().Float64("latitude", latitude).Float64("longitude", longitude).Msg("Got upload") - uploads, err := extractImageUploads(r) - log.Info().Int("len", len(uploads)).Msg("extracted uploads") - if err != nil { - respondError(w, "Failed to extract image uploads", err, http.StatusInternalServerError) - return - } - images, err := saveImageUploads(ctx, tx, uploads) - if err != nil { - respondError(w, "Failed to save image uploads", err, http.StatusInternalServerError) - return - } - log.Info().Int("len", len(images)).Msg("saved uploads") if len(images) > 0 { setters := make([]*models.PublicreportQuickImageSetter, 0) for _, image := range images { diff --git a/public-report/search.go b/public-report/search.go index 1ed6a637..e66e1566 100644 --- a/public-report/search.go +++ b/public-report/search.go @@ -22,7 +22,7 @@ func getSearch(w http.ResponseWriter, r *http.Request) { Search, ContentSearch{ MapboxToken: config.MapboxToken, - URLTegola: config.URLTegola, + URLTegola: config.MakeURLTegola("/"), }, ) } diff --git a/public-report/status.go b/public-report/status.go index f9cd6641..04a10351 100644 --- a/public-report/status.go +++ b/public-report/status.go @@ -38,6 +38,7 @@ type Report struct { Address string Comments string Created time.Time + District string ID string Images []Image Location string // GeoJSON @@ -175,6 +176,7 @@ func contentFromQuick(ctx context.Context, report_id string) (result ContentStat result.Report.Address = quick.Address result.Report.Comments = quick.Comments result.Report.Created = quick.Created + result.Report.District = "Unknown" result.Report.Reporter.Email = quick.ReporterEmail result.Report.Reporter.Name = "-" result.Report.Reporter.Phone = quick.ReporterPhone @@ -183,7 +185,7 @@ func contentFromQuick(ctx context.Context, report_id string) (result ContentStat for _, image := range images { result.Report.Images = append(result.Report.Images, Image{ Location: image.LocationJSON, - URL: fmt.Sprintf("https://%s/image/%s", config.RMODomain, image.StorageUUID), + URL: config.MakeURLReport("/image/%s", image.StorageUUID), }) } type LocationGeoJSON struct { diff --git a/public-report/template/quick-submit-complete.html b/public-report/template/quick-submit-complete.html index da711c4b..333059d6 100644 --- a/public-report/template/quick-submit-complete.html +++ b/public-report/template/quick-submit-complete.html @@ -29,6 +29,11 @@ {{.ReportID|publicReportID}}

Please save this ID for your reference.

+ {{ if not (eq .District nil) }} +

Your report has been assigned to

+

{{ .District.Name }}

+ + {{ end }}

diff --git a/public-report/template/status-by-id.html b/public-report/template/status-by-id.html index 35bebe4f..3daf7b16 100644 --- a/public-report/template/status-by-id.html +++ b/public-report/template/status-by-id.html @@ -82,8 +82,8 @@ document.addEventListener("DOMContentLoaded", onLoad); {{.Report.Created|timeSince}}
- Next Step: - July 19, 2023 (Estimated) + District: + {{.Report.District}}
diff --git a/sync/dash.go b/sync/dash.go index 11858361..c0233739 100644 --- a/sync/dash.go +++ b/sync/dash.go @@ -284,7 +284,7 @@ func dashboard(ctx context.Context, w http.ResponseWriter, user *models.User) { } data := ContextDashboard{ Config: Config{ - URLTegola: config.URLTegola, + URLTegola: config.MakeURLTegola("/"), }, CountTraps: int(trapCount), CountMosquitoSources: int(sourceCount), diff --git a/sync/mock.go b/sync/mock.go index 0425e17d..923c3840 100644 --- a/sync/mock.go +++ b/sync/mock.go @@ -49,7 +49,7 @@ func getQRCodeReport(w http.ResponseWriter, r *http.Request) { if code == "" { respondError(w, "There should always be a code", nil, http.StatusBadRequest) } - content := config.MakeURLSync("/report/" + code) + content := config.MakeURLNidus("/report/%s", code) // Get optional size parameter (default to 256) size := 256 if sizeStr := r.URL.Query().Get("size"); sizeStr != "" { diff --git a/sync/oauth.go b/sync/oauth.go index cac03063..25129363 100644 --- a/sync/oauth.go +++ b/sync/oauth.go @@ -2,6 +2,8 @@ package sync import ( "net/http" + "net/url" + "strconv" "github.com/Gleipnir-Technology/nidus-sync/auth" "github.com/Gleipnir-Technology/nidus-sync/background" @@ -19,8 +21,33 @@ type ContextOauthPrompt struct { User User } +// Build the ArcGIS authorization URL with PKCE +func buildArcGISAuthURL(clientID string) string { + baseURL := "https://www.arcgis.com/sharing/rest/oauth2/authorize/" + + params := url.Values{} + params.Add("client_id", clientID) + params.Add("redirect_uri", config.ArcGISOauthRedirectURL()) + params.Add("response_type", "code") + //params.Add("code_challenge", generateCodeChallenge(codeVerifier)) + //params.Add("code_challenge_method", "S256") + + // See https://developers.arcgis.com/rest/users-groups-and-items/token/ + // expiration is defined in minutes + var expiration int + if config.IsProductionEnvironment() { + // 2 weeks is the maximum allowed + expiration = 20160 + } else { + expiration = 20 + } + params.Add("expiration", strconv.Itoa(expiration)) + + return baseURL + "?" + params.Encode() +} + func getArcgisOauthBegin(w http.ResponseWriter, r *http.Request) { - authURL := config.BuildArcGISAuthURL(config.ClientID) + authURL := buildArcGISAuthURL(config.ClientID) http.Redirect(w, r, authURL, http.StatusFound) } @@ -41,7 +68,7 @@ func getArcgisOauthCallback(w http.ResponseWriter, r *http.Request) { respondError(w, "Failed to handle access code", err, http.StatusInternalServerError) return } - http.Redirect(w, r, config.MakeURLSync("/"), http.StatusFound) + http.Redirect(w, r, config.MakeURLNidus("/"), http.StatusFound) } func getOAuthRefresh(w http.ResponseWriter, r *http.Request) { diff --git a/userfile/userfile.go b/userfile/userfile.go index aceea6ea..fc9f20d6 100644 --- a/userfile/userfile.go +++ b/userfile/userfile.go @@ -41,11 +41,17 @@ func AudioFileContentWrite(audioUUID uuid.UUID, body io.Reader) error { log.Info().Str("filepath", filepath).Msg("Save audio file content") return nil } -func ImageFileContentPathRaw(uid string) string { - return fmt.Sprintf("%s/%s.raw", config.FilesDirectoryUser, uid) +func ImageFileContentPathRawUser(uid string) string { + return imageFileContentPath(config.FilesDirectoryUser, uid, "raw") +} +func imageFileContentPathLogoPng(uid string) string { + return imageFileContentPath(config.FilesDirectoryLogo, uid, "png") +} +func imageFileContentPath(dir string, uid string, ext string) string { + return fmt.Sprintf("%s/%s.%s", dir, uid, ext) } func ImageFileContentWrite(uid uuid.UUID, body io.Reader) error { - filepath := ImageFileContentPathRaw(uid.String()) + filepath := ImageFileContentPathRawUser(uid.String()) // Create file in configured directory dst, err := os.Create(filepath) @@ -61,6 +67,11 @@ func ImageFileContentWrite(uid uuid.UUID, body io.Reader) error { } return nil } +func ImageFileContentWriteLogo(w http.ResponseWriter, uid uuid.UUID) { + image_path := imageFileContentPathLogoPng(uid.String()) + writeFileContent(w, image_path) +} + func PublicImageFileContentWrite(uid uuid.UUID, body io.Reader) error { // Create file in configured directory filepath := PublicImageFileContentPathRaw(uid.String()) @@ -86,7 +97,10 @@ func PublicImageFileContentPathRaw(uid string) string { func PublicImageFileToResponse(w http.ResponseWriter, uid string) { image_path := PublicImageFileContentPathRaw(uid) + writeFileContent(w, image_path) +} +func writeFileContent(w http.ResponseWriter, image_path string) { // Open the file file, err := os.Open(image_path) if err != nil { From ad7ddf285cdf8d5e1ab45fa65f0f1c8888df7469 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 03:29:19 +0000 Subject: [PATCH 0072/1453] go mod tidy, update arcgis-go --- go.mod | 32 +------ go.sum | 282 +-------------------------------------------------------- 2 files changed, 5 insertions(+), 309 deletions(-) diff --git a/go.mod b/go.mod index bc3b05ca..00c6eb34 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Gleipnir-Technology/nidus-sync go 1.24.9 require ( - github.com/Gleipnir-Technology/arcgis-go v0.0.5 + github.com/Gleipnir-Technology/arcgis-go v0.0.6 github.com/Gleipnir-Technology/go-geojson2h3/v2 v2.0.0 github.com/aarondl/opt v0.0.0-20250607033636-982744e1bd65 github.com/alexedwards/scs/pgxstore v0.0.0-20251002162104-209de6e426de @@ -13,45 +13,31 @@ require ( github.com/go-chi/hostrouter v0.3.0 github.com/go-chi/render v1.0.3 github.com/gofrs/uuid/v5 v5.4.0 - github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.7.6 github.com/jaswdr/faker/v2 v2.8.1 github.com/lib/pq v1.10.9 github.com/minio/minio-go/v7 v7.0.97 + github.com/nyaruka/phonenumbers v1.6.8 github.com/pressly/goose/v3 v3.26.0 github.com/rs/zerolog v1.34.0 + github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd github.com/shopspring/decimal v1.4.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/stephenafamo/bob v0.42.0 github.com/stephenafamo/scan v0.7.0 github.com/tidwall/geojson v1.4.5 + github.com/twilio/twilio-go v1.29.1 github.com/uber/h3-go/v4 v4.4.0 - github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 golang.org/x/crypto v0.42.0 ) require ( github.com/ajg/form v1.5.1 // indirect github.com/beevik/etree v1.1.0 // indirect - github.com/dsoprea/go-exif-extra v0.0.0-20210513050430-a8c8bb657ad5 // indirect - github.com/dsoprea/go-exif/v3 v3.0.1 // indirect - github.com/dsoprea/go-heic-exif-extractor/v2 v2.0.0-20210512044107-62067e44c235 // indirect - github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect - github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20221012074422-4f3f7e934102 // indirect - github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect - github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c // indirect - github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512044023-23bdd883ee8e // indirect - github.com/dsoprea/go-tiff-image-structure/v2 v2.0.0-20210512044046-dc78da6a809b // indirect - github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect - github.com/dsoprea/go-webp-image-structure v0.0.0-20210512044215-f98af2b0401e // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/evanoberholster/imagemeta v0.3.1 // indirect - github.com/go-errors/errors v1.4.2 // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect - github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect github.com/golang/mock v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -64,16 +50,11 @@ require ( github.com/mfridman/interpolate v0.0.2 // indirect github.com/minio/crc64nvme v1.1.0 // indirect github.com/minio/md5-simd v1.1.2 // indirect - github.com/nyaruka/phonenumbers v1.6.8 // indirect - github.com/pganalyze/pg_query_go/v6 v6.1.0 // indirect github.com/philhofer/fwd v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect github.com/rs/xid v1.6.0 // indirect - github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect github.com/sethvargo/go-retry v0.3.0 // indirect - github.com/stretchr/testify v1.11.1 // indirect - github.com/tetratelabs/wazero v1.9.0 // indirect github.com/tidwall/geoindex v1.4.4 // indirect github.com/tidwall/gjson v1.12.1 // indirect github.com/tidwall/match v1.1.1 // indirect @@ -81,17 +62,12 @@ require ( github.com/tidwall/rtree v1.3.1 // indirect github.com/tidwall/sjson v1.2.4 // indirect github.com/tinylib/msgp v1.3.0 // indirect - github.com/twilio/twilio-go v1.29.1 // indirect - github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect go.uber.org/multierr v1.11.0 // indirect - go4.org v0.0.0-20200411211856-f5505b9728dd // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect - golang.org/x/image v0.0.0-20200618115811-c13761719519 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.29.0 // indirect google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 85b48017..eb0c9254 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,7 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Gleipnir-Technology/arcgis-go v0.0.5 h1:7UdgFZv7bnmLqkvGLivKurLKICmwZGWctPxESjDjeA8= github.com/Gleipnir-Technology/arcgis-go v0.0.5/go.mod h1:Stx2sn5Lvuyhy4SaTQpbLNCAfenboDINi/UU5gQvz4k= github.com/Gleipnir-Technology/go-geojson2h3/v2 v2.0.0 h1:6OMVxoiX9r7dEkIyYYKtSu7I2UDq64dww4JxJTo3p78= @@ -41,11 +22,6 @@ github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -69,57 +45,10 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dsoprea/go-exif-extra v0.0.0-20210513050430-a8c8bb657ad5 h1:F5HkHi38eOQECRJTJIV1Amn+J5q2atJf2feGPJ8/v+U= -github.com/dsoprea/go-exif-extra v0.0.0-20210513050430-a8c8bb657ad5/go.mod h1:9RNGsP4bsNJg7MDbFLmCw1lovrpGB2xCOjzyugh+4iY= -github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E= -github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4/go.mod h1:9EXlPeHfblFFnwu5UOqmP2eoZfJyAZ2Ri/Vki33ajO0= -github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8= -github.com/dsoprea/go-exif/v3 v3.0.0-20200717071058-9393e7afd446/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= -github.com/dsoprea/go-exif/v3 v3.0.0-20210428042052-dca55bf8ca15/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= -github.com/dsoprea/go-exif/v3 v3.0.0-20210512043655-120bcdb2a55e/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= -github.com/dsoprea/go-exif/v3 v3.0.0-20210512055020-8213cfabc61b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= -github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= -github.com/dsoprea/go-exif/v3 v3.0.0-20221003160559-cf5cd88aa559/go.mod h1:rW6DMEv25U9zCtE5ukC7ttBRllXj7g7TAHl7tQrT5No= -github.com/dsoprea/go-exif/v3 v3.0.0-20221003171958-de6cb6e380a8/go.mod h1:akyZEJZ/k5bmbC9gA612ZLQkcED8enS9vuTiuAkENr0= -github.com/dsoprea/go-exif/v3 v3.0.1 h1:/IE4iW7gvY7BablV1XY0unqhMv26EYpOquVMwoBo/wc= -github.com/dsoprea/go-exif/v3 v3.0.1/go.mod h1:10HkA1Wz3h398cDP66L+Is9kKDmlqlIJGPv8pk4EWvc= -github.com/dsoprea/go-heic-exif-extractor/v2 v2.0.0-20210512044107-62067e44c235 h1:a/XFkZdudAjXegNFRIf5vFjsF9LgFbiR5kjfHJZfIRA= -github.com/dsoprea/go-heic-exif-extractor/v2 v2.0.0-20210512044107-62067e44c235/go.mod h1:3mWA3lvkafMCuqoYKYXx/9YQgonsiAXf+KPSNlB/ZtI= -github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb h1:gwjJjUr6FY7zAWVEueFPrcRHhd9+IK81TcItbqw2du4= -github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM= -github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20210512043942-b434301c6836/go.mod h1:WaARaUjQuSuDCDFAiU/GwzfxMTJBulfEhqEA2Tx6B4Y= -github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20221012074422-4f3f7e934102 h1:gmTXQdSuuuORRFPTS2uaYpAXU5oUNkXdeYSlZe5NvsE= -github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20221012074422-4f3f7e934102/go.mod h1:WaARaUjQuSuDCDFAiU/GwzfxMTJBulfEhqEA2Tx6B4Y= -github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= -github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= -github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg= -github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= -github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c h1:7j5aWACOzROpr+dvMtu8GnI97g9ShLWD72XIELMgn+c= -github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c/go.mod h1:pqKB+ijp27cEcrHxhXVgUUMlSDRuGJJp1E+20Lj5H0E= -github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d h1:8+qI8ant/vZkNSsbwSjIR6XJfWcDVTg/qx/3pRUUZNA= -github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d/go.mod h1:yTR3tKgyk20phAFg6IE9ulMA5NjEDD2wyx+okRFLVtw= -github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512044023-23bdd883ee8e h1:7xT+Xgi019P9KVdcl+QUnSrgKCwIZWqkPNk5GygFTsw= -github.com/dsoprea/go-png-image-structure/v2 v2.0.0-20210512044023-23bdd883ee8e/go.mod h1:scnx0wQSM7UiCMK66dSdiPZvL2hl6iF5DvpZ7uT59MY= -github.com/dsoprea/go-tiff-image-structure/v2 v2.0.0-20210512044046-dc78da6a809b h1:r5TpplS/qPIevKTw6B9ft3p00FP6Flp3lXizranDiVQ= -github.com/dsoprea/go-tiff-image-structure/v2 v2.0.0-20210512044046-dc78da6a809b/go.mod h1:O3HgJ0u+ZTGEk2HZq4/7OvE8QPXWrDM2I9hrD8Qq0o4= -github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf h1:/w4QxepU4AHh3AuO6/g8y/YIIHH5+aKP3Bj8sg5cqhU= -github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8= -github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU= -github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LVjRU0RNUuMDqkPTxcALio0LWPFPXxxFCvVGVAwEpFc= -github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU= -github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw= -github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8= -github.com/dsoprea/go-webp-image-structure v0.0.0-20210512044215-f98af2b0401e h1:Hj7L0xxjASwligRr2F2qy7i4UOk422xcZDvWQSU4m8I= -github.com/dsoprea/go-webp-image-structure v0.0.0-20210512044215-f98af2b0401e/go.mod h1:SRCiMUH7zHuGUQAEnxmURDSsXUIQZfCrQiIkywxxnSs= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807/go.mod h1:Xoiu5VdKMvbRgHuY7+z64lhu/7lvax/22nzASF6GrO8= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanoberholster/imagemeta v0.3.1 h1:E4GUjXcvlVMjP9joN25+bBNf3Al3MTTfMqCrDOCW+LE= -github.com/evanoberholster/imagemeta v0.3.1/go.mod h1:V0vtDJmjTqvwAYO8r+u33NRVIMXQb0qSqEfImoKEiXM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= @@ -129,13 +58,6 @@ github.com/go-chi/hostrouter v0.3.0 h1:75it1eO3FvkG8te1CvU6Kvr3WzAZNEBbo8xIrxUKL github.com/go-chi/hostrouter v0.3.0/go.mod h1:KLB+7PH/ceOr6FCmMyWD2Dmql/clpOe+y7I7CUeTkaQ= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= -github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -144,8 +66,6 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo= -github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= @@ -153,46 +73,12 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= -github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= @@ -206,12 +92,6 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jaswdr/faker/v2 v2.8.1 h1:2AcPgHDBXYQregFUH9LgVZKfFupc4SIquYhp29sf5wQ= github.com/jaswdr/faker/v2 v2.8.1/go.mod h1:jZq+qzNQr8/P+5fHd9t3txe2GNPnthrTfohtnJ7B+68= -github.com/jdeng/goheif v0.0.0-20200323230657-a0d6a8b3e68f/go.mod h1:G7IyA3/eR9IFmUIPdyP3c0l4ZaqEvXAk876WfaQ8plc= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -229,6 +109,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q= github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= @@ -285,12 +166,10 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM= github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmYrbRiUgv+g37W5suLLLxwwniTSc= github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -300,7 +179,6 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= @@ -346,7 +224,6 @@ github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8= github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -368,15 +245,10 @@ github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfP github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM= github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4= github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY= -github.com/wayneashleyberry/terminal-dimensions v1.0.0/go.mod h1:PW2XrtV6KmKOPhuf7wbtcmw1/IFnC39mryRET2XbxeE= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= @@ -389,116 +261,41 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU= -go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -511,10 +308,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -522,76 +316,14 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -601,18 +333,9 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= @@ -621,6 +344,3 @@ modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 480aaf0d0cc5739a0b05cad903255d9cf18cf61d Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 03:39:31 +0000 Subject: [PATCH 0073/1453] Update arcgis-go version --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index eb0c9254..18176087 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Gleipnir-Technology/arcgis-go v0.0.5 h1:7UdgFZv7bnmLqkvGLivKurLKICmwZGWctPxESjDjeA8= -github.com/Gleipnir-Technology/arcgis-go v0.0.5/go.mod h1:Stx2sn5Lvuyhy4SaTQpbLNCAfenboDINi/UU5gQvz4k= +github.com/Gleipnir-Technology/arcgis-go v0.0.6 h1:h21+ijW5NNAgRoBn2ktJwfmbgFX6f/CdlUojB4xQCWo= +github.com/Gleipnir-Technology/arcgis-go v0.0.6/go.mod h1:Stx2sn5Lvuyhy4SaTQpbLNCAfenboDINi/UU5gQvz4k= github.com/Gleipnir-Technology/go-geojson2h3/v2 v2.0.0 h1:6OMVxoiX9r7dEkIyYYKtSu7I2UDq64dww4JxJTo3p78= github.com/Gleipnir-Technology/go-geojson2h3/v2 v2.0.0/go.mod h1:W77HoRoEXUWAc24AbDHIaSH2U4vJNWgNRpEuySXzqDs= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= From 3573127bf1be7fb0507d58d5b34326e8277b8c75 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 03:41:08 +0000 Subject: [PATCH 0074/1453] Update vendor hash for building on Nix --- default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default.nix b/default.nix index bdfcb5c6..eedd831e 100644 --- a/default.nix +++ b/default.nix @@ -9,5 +9,5 @@ pkgs.buildGoModule rec { subPackages = []; version = "0.0.11"; # Needs to be updated after every modification of go.mod/go.sum - vendorHash = "sha256-fU0FPvuDC3rwQ4ygQYA3sH48o8PaK5VqqowwdUsVNaA="; + vendorHash = "sha256-Z1kpPmQytPAaKXQDlD8ILNyPchZornVTO+enOcS2cEQ="; } From b7eb79d4f72d1f164cbfbcf55d21f9ceb7837069 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 03:57:50 +0000 Subject: [PATCH 0075/1453] Remove old Voip.ms text integration It's all Twilio now. --- comms/voipms/sms.go | 97 --------------------------------------------- 1 file changed, 97 deletions(-) delete mode 100644 comms/voipms/sms.go diff --git a/comms/voipms/sms.go b/comms/voipms/sms.go deleted file mode 100644 index cd8a4acc..00000000 --- a/comms/voipms/sms.go +++ /dev/null @@ -1,97 +0,0 @@ -package comms - -import ( - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "net/url" - - "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/rs/zerolog/log" -) - -var VOIP_MS_API = "https://voip.ms/api/v1/rest.php" - -type VoipMSResponse struct { - Status string `json:"status"` - SMS int `json:"sms"` -} - -func SendMMS(to string, content string, media ...string) error { - if len(content) > 2048 { - return errors.New("Message content is more than 160 characters") - } - params := url.Values{} - params.Add("api_password", config.VoipMSPassword) - params.Add("api_username", config.VoipMSUsername) - params.Add("method", "sendMMS") - params.Add("did", config.VoipMSNumber) - params.Add("dst", to) - params.Add("message", content) - for i, med := range media { - // These should be one of: - // 1. A full URL that the service cat GET - // 2. A base64-encoded image starting with "data:image/png;base64,iVBORw0KGgoAAAANSUh..." - params.Add(fmt.Sprintf("media%d", i+1), med) - } - params.Add(fmt.Sprintf("media%d", len(media)+1), "") - - response, err := makeVoipMSRequest(params) - if err != nil { - return fmt.Errorf("Failed to send MMS: %w", err) - } - log.Info().Str("status", response.Status).Int("sms", response.SMS).Msg("Sent MMS message") - return nil -} - -func SendSMS(to string, content string) error { - if len(content) > 160 { - return errors.New("Message content is more than 160 characters") - } - params := url.Values{} - params.Add("api_password", config.VoipMSPassword) - params.Add("api_username", config.VoipMSUsername) - params.Add("method", "sendSMS") - params.Add("did", config.VoipMSNumber) - params.Add("dst", to) - params.Add("message", content) - - response, err := makeVoipMSRequest(params) - if err != nil { - return fmt.Errorf("Failed to send SMS: %w", err) - } - log.Info().Str("status", response.Status).Int("sms", response.SMS).Msg("Sent MMS message") - return nil -} - -func makeVoipMSRequest(params url.Values) (VoipMSResponse, error) { - result := VoipMSResponse{} - // Construct the URL with query parameters - full_url := VOIP_MS_API + "?" + params.Encode() - - // Make the HTTP request - resp, err := http.Get(full_url) - if err != nil { - log.Warn().Err(err).Str("url", full_url).Msg("Failed to make request to Voip.MS") - return result, fmt.Errorf("Error making request: %w", err) - } - defer resp.Body.Close() - - // Read the response body - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Warn().Err(err).Str("url", full_url).Msg("Failed to read Voip.MS response body") - return result, fmt.Errorf("Failed to read response: %w", err) - } - log.Info().Str("response", string(body)).Msg("Response from Voip.MS") - - // Parse the JSON response - var response VoipMSResponse - err = json.Unmarshal(body, &response) - if err != nil { - return result, fmt.Errorf("Failed to unmarshal JSON response: %w", err) - } - return response, nil -} From f38381eaf0f3901ef955ccf6f4e84988ed32ded9 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 04:32:21 +0000 Subject: [PATCH 0076/1453] Fix embedded email templates --- comms/template.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/comms/template.go b/comms/template.go index 0ec5e8cd..5eaa4d00 100644 --- a/comms/template.go +++ b/comms/template.go @@ -96,7 +96,8 @@ func buildTemplate(name string) *builtTemplate { func parseEmbeddedHTML(embeddedFiles embed.FS, subdir string, file string) *templatehtml.Template { // Remap the file names to embedded paths - embeddedFilePaths := []string{strings.TrimPrefix(file, subdir)} + to_trim := subdir + "/" + embeddedFilePaths := []string{strings.TrimPrefix(file, to_trim)} name := path.Base(embeddedFilePaths[0]) log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template") return templatehtml.Must( @@ -104,7 +105,8 @@ func parseEmbeddedHTML(embeddedFiles embed.FS, subdir string, file string) *temp } func parseEmbeddedTXT(embeddedFiles embed.FS, subdir string, file string) *templatetxt.Template { // Remap the file names to embedded paths - embeddedFilePaths := []string{strings.TrimPrefix(file, subdir)} + to_trim := subdir + "/" + embeddedFilePaths := []string{strings.TrimPrefix(file, to_trim)} name := path.Base(embeddedFilePaths[0]) log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template") return templatetxt.Must( From 56e1e5127917ac2da981421d093d76f1e3fb11ea Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 04:50:20 +0000 Subject: [PATCH 0077/1453] fix up instructions on district import After doing it in prod. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b10a53ba..936e7461 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,14 @@ You'll need a number of environment variables for configuring things; ### Districts -There's a table containing district information in the database, `public.district`. It was created with: +There's a table containing district information in the database, `import.district`. It was created with: ``` -shp2pgsql -s 3857 -c -D -I CA_districts.shp public.district | psql -d nidus-sync psql -ALTER TABLE district ADD COLUMN geom_4326 geometry(MultiPolygon,4326) GENERATED ALWAYS AS (ST_Transform(geom, 4326)) STORED; +CREATE SCHEMA import; +shp2pgsql -s 3857 -c -D -I CA_districts.shp import.district | psql -d nidus-sync +psql +ALTER TABLE import.district ADD COLUMN geom_4326 geometry(MultiPolygon,4326) GENERATED ALWAYS AS (ST_Transform(geom, 4326)) STORED; ``` ## Hacking From d478f39800143e637c556a75dfe3c2785219cfd5 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 04:50:37 +0000 Subject: [PATCH 0078/1453] fix tegola URL passthrough after my config changes --- public-report/template/search.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public-report/template/search.html b/public-report/template/search.html index 9cb7c704..a5377aef 100644 --- a/public-report/template/search.html +++ b/public-report/template/search.html @@ -80,7 +80,7 @@ function onLoad() { map.addSource('tegola-mosquito', { 'type': 'vector', 'tiles': [ - 'https://{{.URLTegola}}/maps/mosquito/{z}/{x}/{y}' + '{{.URLTegola}}/maps/mosquito/{z}/{x}/{y}' ] }); map.addLayer({ From 964ef49a783ca4e5a855e3824aed1edeca62089c Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 05:04:46 +0000 Subject: [PATCH 0079/1453] Fix tegola URL harder --- htmlpage/static/js/map-aggregate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htmlpage/static/js/map-aggregate.js b/htmlpage/static/js/map-aggregate.js index 52146995..94e38374 100644 --- a/htmlpage/static/js/map-aggregate.js +++ b/htmlpage/static/js/map-aggregate.js @@ -82,7 +82,7 @@ class MapAggregate extends HTMLElement { map.addSource('tegola', { 'type': 'vector', 'tiles': [ - `https://${tegola}/maps/nidus/{z}/{x}/{y}?organization_id=${organization_id}` + `${tegola}/maps/nidus/{z}/{x}/{y}?organization_id=${organization_id}` ] }); map.addInteraction('nidus-mouseenter-interaction', { From 1aa19ce7071b12c462fd463409a44dbe73ea5c83 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 05:12:48 +0000 Subject: [PATCH 0080/1453] More fixes to tegola URLs --- htmlpage/static/js/map-aggregate.js | 2 +- public-report/template/search.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/htmlpage/static/js/map-aggregate.js b/htmlpage/static/js/map-aggregate.js index 94e38374..60881d97 100644 --- a/htmlpage/static/js/map-aggregate.js +++ b/htmlpage/static/js/map-aggregate.js @@ -82,7 +82,7 @@ class MapAggregate extends HTMLElement { map.addSource('tegola', { 'type': 'vector', 'tiles': [ - `${tegola}/maps/nidus/{z}/{x}/{y}?organization_id=${organization_id}` + `${tegola}maps/nidus/{z}/{x}/{y}?organization_id=${organization_id}` ] }); map.addInteraction('nidus-mouseenter-interaction', { diff --git a/public-report/template/search.html b/public-report/template/search.html index a5377aef..c585bec6 100644 --- a/public-report/template/search.html +++ b/public-report/template/search.html @@ -80,7 +80,7 @@ function onLoad() { map.addSource('tegola-mosquito', { 'type': 'vector', 'tiles': [ - '{{.URLTegola}}/maps/mosquito/{z}/{x}/{y}' + '{{.URLTegola}}maps/mosquito/{z}/{x}/{y}' ] }); map.addLayer({ From 5d8649ffe5f24684520e79d7d9d0c80848b5b47e Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 17:53:10 +0000 Subject: [PATCH 0081/1453] Send down h3 cell with API response I broke this at some point while doing a refactor and didn't notice until now. --- api/types.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/api/types.go b/api/types.go index 1f23fdc9..cac810a6 100644 --- a/api/types.go +++ b/api/types.go @@ -5,9 +5,11 @@ import ( "time" "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/Gleipnir-Technology/nidus-sync/h3utils" "github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/aarondl/opt/null" "github.com/go-chi/render" + "github.com/rs/zerolog/log" ) type H3Cell uint64 @@ -171,13 +173,18 @@ func (rtd ResponseMosquitoSource) Render(w http.ResponseWriter, r *http.Request) func NewResponseMosquitoSource(ms platform.MosquitoSource) ResponseMosquitoSource { pl := ms.PointLocation + h3cell, err := h3utils.ToCell(pl.H3cell.GetOr("0")) + if err != nil { + log.Warn().Err(err).Msg("Failed to convert h3 cell") + h3cell = 0 + } return ResponseMosquitoSource{ - Active: toBool16(pl.Active), - Access: pl.Accessdesc.GetOr(""), - Comments: pl.Comments.GetOr(""), - Created: formatTime(pl.Creationdate), - Description: pl.Description.GetOr(""), - //H3Cell: pl.H3Cell, + Active: toBool16(pl.Active), + Access: pl.Accessdesc.GetOr(""), + Comments: pl.Comments.GetOr(""), + Created: formatTime(pl.Creationdate), + Description: pl.Description.GetOr(""), + H3Cell: int64(h3cell), ID: pl.Globalid.String(), LastInspectionDate: formatTime(pl.Lastinspectdate), Habitat: pl.Habitat.GetOr(""), From aeaf45fa2ba728fded36f4c98abb737dd0468825 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 18:37:00 +0000 Subject: [PATCH 0082/1453] Add privacy page for Nidus --- comms/template/report-subscription-confirmation.html | 2 +- public-report/endpoint.go | 8 ++++---- public-report/template/component/footer.html | 2 +- public-report/template/privacy.html | 2 +- public-report/template/terms.html | 6 +++--- sync/routes.go | 2 ++ 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/comms/template/report-subscription-confirmation.html b/comms/template/report-subscription-confirmation.html index 8fe723b1..88ef306a 100644 --- a/comms/template/report-subscription-confirmation.html +++ b/comms/template/report-subscription-confirmation.html @@ -92,7 +92,7 @@ diff --git a/public-report/endpoint.go b/public-report/endpoint.go index ef923f1c..b866c45f 100644 --- a/public-report/endpoint.go +++ b/public-report/endpoint.go @@ -8,7 +8,7 @@ import ( "github.com/rs/zerolog/log" ) -type ContextRoot struct{} +type ContentRoot struct{} var ( PrivacyT = buildTemplate("privacy", "base") @@ -20,14 +20,14 @@ func getPrivacy(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, PrivacyT, - ContextRoot{}, + ContentRoot{}, ) } func getRoot(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, RootT, - ContextRoot{}, + ContentRoot{}, ) } @@ -39,7 +39,7 @@ func getTerms(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, TermsT, - ContextRoot{}, + ContentRoot{}, ) } diff --git a/public-report/template/component/footer.html b/public-report/template/component/footer.html index 910cb8d7..eddc65aa 100644 --- a/public-report/template/component/footer.html +++ b/public-report/template/component/footer.html @@ -3,7 +3,7 @@
-

© 2025 Gleipnir Technology

+

© 2025 Gleipnir LLC

Contact: support@mosquitoes.online

diff --git a/public-report/template/privacy.html b/public-report/template/privacy.html index 2b40f0e3..3dec5541 100644 --- a/public-report/template/privacy.html +++ b/public-report/template/privacy.html @@ -20,7 +20,7 @@

Affiliate means an entity that controls, is controlled by, or is under common control with a party, where "control" means ownership of 50% or more of the shares, equity interest or other securities entitled to vote for election of directors or other managing authority.

  • -

    Company (referred to as either "the Company", "We", "Us" or "Our" in this Privacy Policy) refers to Gleipnir Technology LLC, 2726 S Quinn Ave, Gilbert, AZ.

    +

    Company (referred to as either "the Company", "We", "Us" or "Our" in this Privacy Policy) refers to Gleipnir LLC, 2726 S Quinn Ave, Gilbert, AZ.

  • Cookies are small files that are placed on Your computer, mobile device or any other device by a website, containing the details of Your browsing history on that website among its many uses.

    diff --git a/public-report/template/terms.html b/public-report/template/terms.html index a5e04dc9..4866cec6 100644 --- a/public-report/template/terms.html +++ b/public-report/template/terms.html @@ -7,9 +7,9 @@

    Terms of Service

    Look, we don't like having terms of service, and we're confident you don't find them interesting to read. But we have to have them as a business.

    Service provider

    -

    Report Mosquitoes Online is provided by Gleipnir Technology LLC. By using the website you agree to these terms. If you don't agree, don't tell a computer to access our site.

    -

    Gleipnir Technology LLC is a company organized under the laws of the state of Arizona, USA, and operates under Arizona law.

    -

    Gleipnir Technology LLC is located at 2726 S Quinn Ave, Gilbert, AZ

    +

    Report Mosquitoes Online is provided by Gleipnir LLC. By using the website you agree to these terms. If you don't agree, don't tell a computer to access our site.

    +

    Gleipnir LLC is a company organized under the laws of the state of Arizona, USA, and operates under Arizona law.

    +

    Gleipnir LLC is located at 2726 S Quinn Ave, Gilbert, AZ

    What you can expect from us

    We provide services free to the public. We'll occasionally make changes to these services. We won't notify members of the public, like you, of those changes. We may notify our customers, but we may not, since we may changes very frequently. In general, we have additional agreements beyond this one with entities that are our customers.

    The data you provide to us is used for public health. That generally means passing some or all of your data on to our customers that work in mosquito abatement. Any information you give to us we may give to them. You can request at any time that we stop sharing your information and we will honor that request.

    diff --git a/sync/routes.go b/sync/routes.go index 6fa8ae8d..f980dcdb 100644 --- a/sync/routes.go +++ b/sync/routes.go @@ -49,6 +49,8 @@ func Router() chi.Router { r.Get("/oauth/refresh", getOAuthRefresh) + r.Get("/privacy", getPrivacy) + r.Get("/qr-code/report/{code}", getQRCodeReport) r.Get("/signin", getSignin) r.Post("/signin", postSignin) From be86a5d950071d9a8017663bfa9386ee9808c763 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 18:43:36 +0000 Subject: [PATCH 0083/1453] Actually add the privacy page, make values variables So I can do different things at some point in dev/prod --- sync/privacy.go | 32 ++++++ sync/template/privacy.html | 214 +++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 sync/privacy.go create mode 100644 sync/template/privacy.html diff --git a/sync/privacy.go b/sync/privacy.go new file mode 100644 index 00000000..9631a1aa --- /dev/null +++ b/sync/privacy.go @@ -0,0 +1,32 @@ +package sync + +import ( + "net/http" + + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/htmlpage" +) + +type ContentPrivacy struct { + Address string + Company string + Site string + URLSync string +} + +var ( + PrivacyT = buildTemplate("privacy", "base") +) + +func getPrivacy(w http.ResponseWriter, r *http.Request) { + htmlpage.RenderOrError( + w, + PrivacyT, + ContentPrivacy{ + Address: "2726 S Quinn Ave, Gilbert, AZ, USA", + Company: "Gleipnir LLC", + Site: "Nidus Sync", + URLSync: config.MakeURLNidus("/"), + }, + ) +} diff --git a/sync/template/privacy.html b/sync/template/privacy.html new file mode 100644 index 00000000..4070ab89 --- /dev/null +++ b/sync/template/privacy.html @@ -0,0 +1,214 @@ +{{template "base.html" .}} +{{define "title"}}Privacy Policy{{end}} +{{define "extraheader"}} +{{end}} +{{define "content"}} +
    +

    Privacy Policy

    +

    Last updated: January 20, 2026

    +

    This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.

    +

    We use Your Personal Data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy.

    +

    Interpretation and Definitions

    +

    Interpretation

    +

    The words whose initial letters are capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural.

    +

    Definitions

    +

    For the purposes of this Privacy Policy:

    +
      +
    • +

      Account means a unique account created for You to access our Service or parts of our Service.

      +
    • +
    • +

      Affiliate means an entity that controls, is controlled by, or is under common control with a party, where "control" means ownership of 50% or more of the shares, equity interest or other securities entitled to vote for election of directors or other managing authority.

      +
    • +
    • +

      Company (referred to as either "the Company", "We", "Us" or "Our" in this Privacy Policy) refers to {{.Company}}, {{.Address}}

      +
    • +
    • +

      Cookies are small files that are placed on Your computer, mobile device or any other device by a website, containing the details of Your browsing history on that website among its many uses.

      +
    • +
    • +

      Country refers to: Arizona, United States

      +
    • +
    • +

      Device means any device that can access the Service such as a computer, a cell phone or a digital tablet.

      +
    • +
    • +

      Personal Data (or "Personal Information") is any information that relates to an identified or identifiable individual.

      +

      We use "Personal Data" and "Personal Information" interchangeably unless a law uses a specific term.

      +
    • +
    • +

      Service refers to the Website.

      +
    • +
    • +

      Service Provider means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used.

      +
    • +
    • +

      Usage Data refers to data collected automatically, either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit).

      +
    • +
    • +

      Website refers to {{.Site}}, accessible from {{.URLSync}}.

      +
    • +
    • +

      You means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable.

      +
    • +
    +

    Collecting and Using Your Personal Data

    +

    Types of Data Collected

    +

    Personal Data

    +

    While using Our Service, We may ask You to provide Us with certain personally identifiable information that can be used to contact or identify You. Personally identifiable information may include, but is not limited to:

    +
      +
    • Email address
    • +
    • First name and last name
    • +
    • Phone number
    • +
    • Address, State, Province, ZIP/Postal code, City
    • +
    +

    Usage Data

    +

    Usage Data is collected automatically when using the Service.

    +

    Usage Data may include information such as Your Device's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent on those pages, unique device identifiers and other diagnostic data.

    +

    When You access the Service by or through a mobile device, We may collect certain information automatically, including, but not limited to, the type of mobile device You use, Your mobile device's unique ID, the IP address of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device identifiers and other diagnostic data.

    +

    We may also collect information that Your browser sends whenever You visit Our Service or when You access the Service by or through a mobile device.

    +

    Tracking Technologies and Cookies

    +

    We use Cookies and similar tracking technologies to track the activity on Our Service and store certain information. Tracking technologies We use include beacons, tags, and scripts to collect and track information and to improve and analyze Our Service. The technologies We use may include:

    +
      +
    • Cookies or Browser Cookies. A cookie is a small file placed on Your Device. You can instruct Your browser to refuse all Cookies or to indicate when a Cookie is being sent. However, if You do not accept Cookies, You may not be able to use some parts of our Service.
    • +
    +

    Cookies can be "Persistent" or "Session" Cookies. Persistent Cookies remain on Your personal computer or mobile device when You go offline, while Session Cookies are deleted as soon as You close Your web browser.

    +

    Where required by law, we use non-essential cookies (such as analytics, advertising, and remarketing cookies) only with Your consent. You can withdraw or change Your consent at any time using Our cookie preferences tool (if available) or through Your browser/device settings. Withdrawing consent does not affect the lawfulness of processing based on consent before its withdrawal.

    +

    We use both Session and Persistent Cookies for the purposes set out below:

    +
      +
    • +

      Necessary / Essential Cookies

      +

      Type: Session Cookies

      +

      Administered by: Us

      +

      Purpose: These Cookies are essential to provide You with services available through the Website and to enable You to use some of its features. They help to authenticate users and prevent fraudulent use of user accounts. Without these Cookies, the services that You have asked for cannot be provided, and We only use these Cookies to provide You with those services.

      +
    • +
    • +

      Functionality Cookies

      +

      Type: Persistent Cookies

      +

      Administered by: Us

      +

      Purpose: These Cookies allow Us to remember choices You make when You use the Website, such as remembering your login details or language preference. The purpose of these Cookies is to provide You with a more personal experience and to avoid You having to re-enter your preferences every time You use the Website.

      +
    • +
    +

    For more information about the cookies we use and your choices regarding cookies, please visit our Cookies Policy or the Cookies section of Our Privacy Policy.

    +

    Use of Your Personal Data

    +

    The Company may use Personal Data for the following purposes:

    +
      +
    • +

      To provide and maintain our Service, including to monitor the usage of our Service.

      +
    • +
    • +

      To manage Your Account: to manage Your registration as a user of the Service. The Personal Data You provide can give You access to different functionalities of the Service that are available to You as a registered user.

      +
    • +
    • +

      For the performance of a contract: the development, compliance and undertaking of the purchase contract for the products, items or services You have purchased or of any other contract with Us through the Service.

      +
    • +
    • +

      To contact You: To contact You by email, telephone calls, SMS, or other equivalent forms of electronic communication, such as a mobile application's push notifications regarding updates or informative communications related to the functionalities, products or contracted services, including the security updates, when necessary or reasonable for their implementation.

      +
    • +
    • +

      To provide You with news, special offers, and general information about other goods, services and events which We offer that are similar to those that you have already purchased or inquired about unless You have opted not to receive such information.

      +
    • +
    • +

      To manage Your requests: To attend and manage Your requests to Us.

      +
    • +
    • +

      For business transfers: We may use Your Personal Data to evaluate or conduct a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Our assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which Personal Data held by Us about our Service users is among the assets transferred.

      +
    • +
    • +

      For other purposes: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, products, services, marketing and your experience.

      +
    • +
    +

    We may share Your Personal Data in the following situations:

    +
      +
    • With Service Providers: We may share Your Personal Data with Service Providers to monitor and analyze the use of our Service, to contact You.
    • +
    • For business transfers: We may share or transfer Your Personal Data in connection with, or during negotiations of, any merger, sale of Company assets, financing, or acquisition of all or a portion of Our business to another company.
    • +
    • With Affiliates: We may share Your Personal Data with Our affiliates, in which case we will require those affiliates to honor this Privacy Policy. Affiliates include Our parent company and any other subsidiaries, joint venture partners or other companies that We control or that are under common control with Us.
    • +
    • With business partners: We may share Your Personal Data with Our business partners to offer You certain products, services or promotions.
    • +
    • With other users: If Our Service offers public areas, when You share Personal Data or otherwise interact in the public areas with other users, such information may be viewed by all users and may be publicly distributed outside.
    • +
    • With Your consent: We may disclose Your Personal Data for any other purpose with Your consent.
    • +
    +

    Retention of Your Personal Data

    +

    The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal obligations (for example, if We are required to retain Your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.

    +

    Where possible, We apply shorter retention periods and/or reduce identifiability by deleting, aggregating, or anonymizing data. Unless otherwise stated, the retention periods below are maximum periods ("up to") and We may delete or anonymize data sooner when it is no longer needed for the relevant purpose. We apply different retention periods to different categories of Personal Data based on the purpose of processing and legal obligations:

    +
      +
    • +

      Account Information

      +
        +
      • User Accounts: retained for the duration of your account relationship plus up to 24 months after account closure to handle any post-termination issues or resolve disputes.
      • +
      +
    • +
    • +

      Customer Support Data

      +
        +
      • Support tickets and correspondence: up to 24 months from the date of ticket closure to resolve follow-up inquiries, track service quality, and defend against potential legal claims
      • +
      • Chat transcripts: up to 24 months for quality assurance and staff training purposes.
      • +
      +
    • +
    • +

      Usage Data

      +
        +
      • +

        Website analytics data (cookies, IP addresses, device identifiers): up to 24 months from the date of collection, which allows us to analyze trends while respecting privacy principles.

        +
      • +
      • +

        Server logs (IP addresses, access times): up to 24 months for security monitoring and troubleshooting purposes.

        +
      • +
      +
    • +
    +

    Usage Data is retained in accordance with the retention periods described above, and may be retained longer only where necessary for security, fraud prevention, or legal compliance.

    +

    We may retain Personal Data beyond the periods stated above for different reasons:

    +
      +
    • Legal obligation: We are required by law to retain specific data (e.g., financial records for tax authorities).
    • +
    • Legal claims: Data is necessary to establish, exercise, or defend legal claims.
    • +
    • Your explicit request: You ask Us to retain specific information.
    • +
    • Technical limitations: Data exists in backup systems that are scheduled for routine deletion.
    • +
    +

    You may request information about how long We will retain Your Personal Data by contacting Us.

    +

    When retention periods expire, We securely delete or anonymize Personal Data according to the following procedures:

    +
      +
    • Deletion: Personal Data is removed from Our systems and no longer actively processed.
    • +
    • Backup retention: Residual copies may remain in encrypted backups for a limited period consistent with our backup retention schedule and are not restored except where necessary for security, disaster recovery, or legal compliance.
    • +
    • Anonymization: In some cases, We convert Personal Data into anonymous statistical data that cannot be linked back to You. This anonymized data may be retained indefinitely for research and analytics.
    • +
    +

    Transfer of Your Personal Data

    +

    Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ from those from Your jurisdiction.

    +

    Where required by applicable law, We will ensure that international transfers of Your Personal Data are subject to appropriate safeguards and supplementary measures where appropriate. The Company will take all steps reasonably necessary to ensure that Your data is treated securely and in accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of Your data and other personal information.

    +

    Delete Your Personal Data

    +

    You have the right to delete or request that We assist in deleting the Personal Data that We have collected about You.

    +

    Our Service may give You the ability to delete certain information about You from within the Service.

    +

    You may update, amend, or delete Your information at any time by signing in to Your Account, if you have one, and visiting the account settings section that allows you to manage Your personal information. You may also contact Us to request access to, correct, or delete any Personal Data that You have provided to Us.

    +

    Please note, however, that We may need to retain certain information when we have a legal obligation or lawful basis to do so.

    +

    Disclosure of Your Personal Data

    +

    Business Transactions

    +

    If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy Policy.

    +

    Law enforcement

    +

    Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency).

    +

    Other legal requirements

    +

    The Company may disclose Your Personal Data in the good faith belief that such action is necessary to:

    +
      +
    • Comply with a legal obligation
    • +
    • Protect and defend the rights or property of the Company
    • +
    • Prevent or investigate possible wrongdoing in connection with the Service
    • +
    • Protect the personal safety of Users of the Service or the public
    • +
    • Protect against legal liability
    • +
    +

    Security of Your Personal Data

    +

    The security of Your Personal Data is important to Us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While We strive to use commercially reasonable means to protect Your Personal Data, We cannot guarantee its absolute security.

    +

    Children's Privacy

    +

    Our Service does not address anyone under the age of 16. We do not knowingly collect personally identifiable information from anyone under the age of 16. If You are a parent or guardian and You are aware that Your child has provided Us with Personal Data, please contact Us. If We become aware that We have collected Personal Data from anyone under the age of 16 without verification of parental consent, We take steps to remove that information from Our servers.

    +

    If We need to rely on consent as a legal basis for processing Your information and Your country requires consent from a parent, We may require Your parent's consent before We collect and use that information.

    +

    Links to Other Websites

    +

    Our Service may contain links to other websites that are not operated by Us. If You click on a third party link, You will be directed to that third party's site. We strongly advise You to review the Privacy Policy of every site You visit.

    +

    We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.

    +

    Changes to this Privacy Policy

    +

    We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page.

    +

    We will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective and update the "Last updated" date at the top of this Privacy Policy.

    +

    You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.

    +

    Contact Us

    +

    If you have any questions about this Privacy Policy, You can contact us:

    +
      +
    • By email: privacy@gleipnir.technology
    • +
    +
    +{{end}} From 1e51c5ce9e74d31260406e378321e1b32263fa62 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 18:44:01 +0000 Subject: [PATCH 0084/1453] Add notes on what to grant Tegola --- tools/grant-tegola.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tools/grant-tegola.sql diff --git a/tools/grant-tegola.sql b/tools/grant-tegola.sql new file mode 100644 index 00000000..71ce1096 --- /dev/null +++ b/tools/grant-tegola.sql @@ -0,0 +1,2 @@ +GRANT SELECT ON import.district TO tegola; +GRANT USAGE on SCHEMA import TO "tegola"; From f7d40c6d70b137213fdff854076138419a835c42 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Thu, 22 Jan 2026 19:26:01 +0000 Subject: [PATCH 0085/1453] Update privace policy for Report Mosquitoes Online Remove section on children - they may submit information, we don't actually know the age of our users. Add template variables, fix container layout. --- public-report/endpoint.go | 14 +- public-report/template/privacy.html | 411 ++++++++++++++-------------- 2 files changed, 218 insertions(+), 207 deletions(-) diff --git a/public-report/endpoint.go b/public-report/endpoint.go index b866c45f..6e694697 100644 --- a/public-report/endpoint.go +++ b/public-report/endpoint.go @@ -4,10 +4,17 @@ import ( "fmt" "net/http" + "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/rs/zerolog/log" ) +type ContentPrivacy struct { + Address string + Company string + Site string + URLReport string +} type ContentRoot struct{} var ( @@ -20,7 +27,12 @@ func getPrivacy(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, PrivacyT, - ContentRoot{}, + ContentPrivacy{ + Address: "2726 S Quinn Ave, Gilbert, AZ, USA", + Company: "Gleipnir LLC", + Site: "Report Mosquitoes Online", + URLReport: config.MakeURLReport("/"), + }, ) } func getRoot(w http.ResponseWriter, r *http.Request) { diff --git a/public-report/template/privacy.html b/public-report/template/privacy.html index 3dec5541..543ca5bb 100644 --- a/public-report/template/privacy.html +++ b/public-report/template/privacy.html @@ -3,210 +3,209 @@ {{define "extraheader"}} {{end}} {{define "content"}} -

    Privacy Policy

    -

    Last updated: January 20, 2026

    -

    This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.

    -

    We use Your Personal Data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy.

    -

    Interpretation and Definitions

    -

    Interpretation

    -

    The words whose initial letters are capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural.

    -

    Definitions

    -

    For the purposes of this Privacy Policy:

    -
      -
    • -

      Account means a unique account created for You to access our Service or parts of our Service.

      -
    • -
    • -

      Affiliate means an entity that controls, is controlled by, or is under common control with a party, where "control" means ownership of 50% or more of the shares, equity interest or other securities entitled to vote for election of directors or other managing authority.

      -
    • -
    • -

      Company (referred to as either "the Company", "We", "Us" or "Our" in this Privacy Policy) refers to Gleipnir LLC, 2726 S Quinn Ave, Gilbert, AZ.

      -
    • -
    • -

      Cookies are small files that are placed on Your computer, mobile device or any other device by a website, containing the details of Your browsing history on that website among its many uses.

      -
    • -
    • -

      Country refers to: Arizona, United States

      -
    • -
    • -

      Device means any device that can access the Service such as a computer, a cell phone or a digital tablet.

      -
    • -
    • -

      Personal Data (or "Personal Information") is any information that relates to an identified or identifiable individual.

      -

      We use "Personal Data" and "Personal Information" interchangeably unless a law uses a specific term.

      -
    • -
    • -

      Service refers to the Website.

      -
    • -
    • -

      Service Provider means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used.

      -
    • -
    • -

      Usage Data refers to data collected automatically, either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit).

      -
    • -
    • -

      Website refers to Report Mosquitoes Online, accessible from https://report.mosquitoes.online.

      -
    • -
    • -

      You means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable.

      -
    • -
    -

    Collecting and Using Your Personal Data

    -

    Types of Data Collected

    -

    Personal Data

    -

    While using Our Service, We may ask You to provide Us with certain personally identifiable information that can be used to contact or identify You. Personally identifiable information may include, but is not limited to:

    -
      -
    • Email address
    • -
    • First name and last name
    • -
    • Phone number
    • -
    • Address, State, Province, ZIP/Postal code, City
    • -
    -

    Usage Data

    -

    Usage Data is collected automatically when using the Service.

    -

    Usage Data may include information such as Your Device's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent on those pages, unique device identifiers and other diagnostic data.

    -

    When You access the Service by or through a mobile device, We may collect certain information automatically, including, but not limited to, the type of mobile device You use, Your mobile device's unique ID, the IP address of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device identifiers and other diagnostic data.

    -

    We may also collect information that Your browser sends whenever You visit Our Service or when You access the Service by or through a mobile device.

    -

    Tracking Technologies and Cookies

    -

    We use Cookies and similar tracking technologies to track the activity on Our Service and store certain information. Tracking technologies We use include beacons, tags, and scripts to collect and track information and to improve and analyze Our Service. The technologies We use may include:

    -
      -
    • Cookies or Browser Cookies. A cookie is a small file placed on Your Device. You can instruct Your browser to refuse all Cookies or to indicate when a Cookie is being sent. However, if You do not accept Cookies, You may not be able to use some parts of our Service.
    • -
    -

    Cookies can be "Persistent" or "Session" Cookies. Persistent Cookies remain on Your personal computer or mobile device when You go offline, while Session Cookies are deleted as soon as You close Your web browser.

    -

    Where required by law, we use non-essential cookies (such as analytics, advertising, and remarketing cookies) only with Your consent. You can withdraw or change Your consent at any time using Our cookie preferences tool (if available) or through Your browser/device settings. Withdrawing consent does not affect the lawfulness of processing based on consent before its withdrawal.

    -

    We use both Session and Persistent Cookies for the purposes set out below:

    -
      -
    • -

      Necessary / Essential Cookies

      -

      Type: Session Cookies

      -

      Administered by: Us

      -

      Purpose: These Cookies are essential to provide You with services available through the Website and to enable You to use some of its features. They help to authenticate users and prevent fraudulent use of user accounts. Without these Cookies, the services that You have asked for cannot be provided, and We only use these Cookies to provide You with those services.

      -
    • -
    • -

      Functionality Cookies

      -

      Type: Persistent Cookies

      -

      Administered by: Us

      -

      Purpose: These Cookies allow Us to remember choices You make when You use the Website, such as remembering your login details or language preference. The purpose of these Cookies is to provide You with a more personal experience and to avoid You having to re-enter your preferences every time You use the Website.

      -
    • -
    -

    For more information about the cookies we use and your choices regarding cookies, please visit our Cookies Policy or the Cookies section of Our Privacy Policy.

    -

    Use of Your Personal Data

    -

    The Company may use Personal Data for the following purposes:

    -
      -
    • -

      To provide and maintain our Service, including to monitor the usage of our Service.

      -
    • -
    • -

      To manage Your Account: to manage Your registration as a user of the Service. The Personal Data You provide can give You access to different functionalities of the Service that are available to You as a registered user.

      -
    • -
    • -

      For the performance of a contract: the development, compliance and undertaking of the purchase contract for the products, items or services You have purchased or of any other contract with Us through the Service.

      -
    • -
    • -

      To contact You: To contact You by email, telephone calls, SMS, or other equivalent forms of electronic communication, such as a mobile application's push notifications regarding updates or informative communications related to the functionalities, products or contracted services, including the security updates, when necessary or reasonable for their implementation.

      -
    • -
    • -

      To provide You with news, special offers, and general information about other goods, services and events which We offer that are similar to those that you have already purchased or inquired about unless You have opted not to receive such information.

      -
    • -
    • -

      To manage Your requests: To attend and manage Your requests to Us.

      -
    • -
    • -

      For business transfers: We may use Your Personal Data to evaluate or conduct a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Our assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which Personal Data held by Us about our Service users is among the assets transferred.

      -
    • -
    • -

      For other purposes: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, products, services, marketing and your experience.

      -
    • -
    -

    We may share Your Personal Data in the following situations:

    -
      -
    • With Service Providers: We may share Your Personal Data with Service Providers to monitor and analyze the use of our Service, to contact You.
    • -
    • For business transfers: We may share or transfer Your Personal Data in connection with, or during negotiations of, any merger, sale of Company assets, financing, or acquisition of all or a portion of Our business to another company.
    • -
    • With Affiliates: We may share Your Personal Data with Our affiliates, in which case we will require those affiliates to honor this Privacy Policy. Affiliates include Our parent company and any other subsidiaries, joint venture partners or other companies that We control or that are under common control with Us.
    • -
    • With business partners: We may share Your Personal Data with Our business partners to offer You certain products, services or promotions.
    • -
    • With other users: If Our Service offers public areas, when You share Personal Data or otherwise interact in the public areas with other users, such information may be viewed by all users and may be publicly distributed outside.
    • -
    • With Your consent: We may disclose Your Personal Data for any other purpose with Your consent.
    • -
    -

    Retention of Your Personal Data

    -

    The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal obligations (for example, if We are required to retain Your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.

    -

    Where possible, We apply shorter retention periods and/or reduce identifiability by deleting, aggregating, or anonymizing data. Unless otherwise stated, the retention periods below are maximum periods ("up to") and We may delete or anonymize data sooner when it is no longer needed for the relevant purpose. We apply different retention periods to different categories of Personal Data based on the purpose of processing and legal obligations:

    -
      -
    • -

      Account Information

      -
        -
      • User Accounts: retained for the duration of your account relationship plus up to 24 months after account closure to handle any post-termination issues or resolve disputes.
      • -
      -
    • -
    • -

      Customer Support Data

      -
        -
      • Support tickets and correspondence: up to 24 months from the date of ticket closure to resolve follow-up inquiries, track service quality, and defend against potential legal claims
      • -
      • Chat transcripts: up to 24 months for quality assurance and staff training purposes.
      • -
      -
    • -
    • -

      Usage Data

      -
        -
      • -

        Website analytics data (cookies, IP addresses, device identifiers): up to 24 months from the date of collection, which allows us to analyze trends while respecting privacy principles.

        -
      • -
      • -

        Server logs (IP addresses, access times): up to 24 months for security monitoring and troubleshooting purposes.

        -
      • -
      -
    • -
    -

    Usage Data is retained in accordance with the retention periods described above, and may be retained longer only where necessary for security, fraud prevention, or legal compliance.

    -

    We may retain Personal Data beyond the periods stated above for different reasons:

    -
      -
    • Legal obligation: We are required by law to retain specific data (e.g., financial records for tax authorities).
    • -
    • Legal claims: Data is necessary to establish, exercise, or defend legal claims.
    • -
    • Your explicit request: You ask Us to retain specific information.
    • -
    • Technical limitations: Data exists in backup systems that are scheduled for routine deletion.
    • -
    -

    You may request information about how long We will retain Your Personal Data by contacting Us.

    -

    When retention periods expire, We securely delete or anonymize Personal Data according to the following procedures:

    -
      -
    • Deletion: Personal Data is removed from Our systems and no longer actively processed.
    • -
    • Backup retention: Residual copies may remain in encrypted backups for a limited period consistent with our backup retention schedule and are not restored except where necessary for security, disaster recovery, or legal compliance.
    • -
    • Anonymization: In some cases, We convert Personal Data into anonymous statistical data that cannot be linked back to You. This anonymized data may be retained indefinitely for research and analytics.
    • -
    -

    Transfer of Your Personal Data

    -

    Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ from those from Your jurisdiction.

    -

    Where required by applicable law, We will ensure that international transfers of Your Personal Data are subject to appropriate safeguards and supplementary measures where appropriate. The Company will take all steps reasonably necessary to ensure that Your data is treated securely and in accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of Your data and other personal information.

    -

    Delete Your Personal Data

    -

    You have the right to delete or request that We assist in deleting the Personal Data that We have collected about You.

    -

    Our Service may give You the ability to delete certain information about You from within the Service.

    -

    You may update, amend, or delete Your information at any time by signing in to Your Account, if you have one, and visiting the account settings section that allows you to manage Your personal information. You may also contact Us to request access to, correct, or delete any Personal Data that You have provided to Us.

    -

    Please note, however, that We may need to retain certain information when we have a legal obligation or lawful basis to do so.

    -

    Disclosure of Your Personal Data

    -

    Business Transactions

    -

    If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy Policy.

    -

    Law enforcement

    -

    Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency).

    -

    Other legal requirements

    -

    The Company may disclose Your Personal Data in the good faith belief that such action is necessary to:

    -
      -
    • Comply with a legal obligation
    • -
    • Protect and defend the rights or property of the Company
    • -
    • Prevent or investigate possible wrongdoing in connection with the Service
    • -
    • Protect the personal safety of Users of the Service or the public
    • -
    • Protect against legal liability
    • -
    -

    Security of Your Personal Data

    -

    The security of Your Personal Data is important to Us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While We strive to use commercially reasonable means to protect Your Personal Data, We cannot guarantee its absolute security.

    -

    Children's Privacy

    -

    Our Service does not address anyone under the age of 16. We do not knowingly collect personally identifiable information from anyone under the age of 16. If You are a parent or guardian and You are aware that Your child has provided Us with Personal Data, please contact Us. If We become aware that We have collected Personal Data from anyone under the age of 16 without verification of parental consent, We take steps to remove that information from Our servers.

    -

    If We need to rely on consent as a legal basis for processing Your information and Your country requires consent from a parent, We may require Your parent's consent before We collect and use that information.

    -

    Links to Other Websites

    -

    Our Service may contain links to other websites that are not operated by Us. If You click on a third party link, You will be directed to that third party's site. We strongly advise You to review the Privacy Policy of every site You visit.

    -

    We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.

    -

    Changes to this Privacy Policy

    -

    We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page.

    -

    We will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective and update the "Last updated" date at the top of this Privacy Policy.

    -

    You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.

    -

    Contact Us

    -

    If you have any questions about this Privacy Policy, You can contact us:

    -
      -
    • By email: privacy@gleipnir.technology
    • -
    +
    +

    Privacy Policy

    +

    Last updated: January 20, 2026

    +

    This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.

    +

    We use Your Personal Data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy.

    +

    Interpretation and Definitions

    +

    Interpretation

    +

    The words whose initial letters are capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural.

    +

    Definitions

    +

    For the purposes of this Privacy Policy:

    +
      +
    • +

      Account means a unique account created for You to access our Service or parts of our Service.

      +
    • +
    • +

      Affiliate means an entity that controls, is controlled by, or is under common control with a party, where "control" means ownership of 50% or more of the shares, equity interest or other securities entitled to vote for election of directors or other managing authority.

      +
    • +
    • +

      Company (referred to as either "the Company", "We", "Us" or "Our" in this Privacy Policy) refers to {{.Company}}, {{.Address}}

      +
    • +
    • +

      Cookies are small files that are placed on Your computer, mobile device or any other device by a website, containing the details of Your browsing history on that website among its many uses.

      +
    • +
    • +

      Country refers to: Arizona, United States

      +
    • +
    • +

      Device means any device that can access the Service such as a computer, a cell phone or a digital tablet.

      +
    • +
    • +

      Personal Data (or "Personal Information") is any information that relates to an identified or identifiable individual.

      +

      We use "Personal Data" and "Personal Information" interchangeably unless a law uses a specific term.

      +
    • +
    • +

      Service refers to the Website.

      +
    • +
    • +

      Service Provider means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used.

      +
    • +
    • +

      Usage Data refers to data collected automatically, either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit).

      +
    • +
    • +

      Website refers to {{.Site}} accessible from {{.URLReport}}.

      +
    • +
    • +

      You means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable.

      +
    • +
    +

    Collecting and Using Your Personal Data

    +

    Types of Data Collected

    +

    Personal Data

    +

    While using Our Service, We may ask You to provide Us with certain personally identifiable information that can be used to contact or identify You. Personally identifiable information may include, but is not limited to:

    +
      +
    • Email address
    • +
    • First name and last name
    • +
    • Phone number
    • +
    • Address, State, Province, ZIP/Postal code, City
    • +
    +

    Usage Data

    +

    Usage Data is collected automatically when using the Service.

    +

    Usage Data may include information such as Your Device's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent on those pages, unique device identifiers and other diagnostic data.

    +

    When You access the Service by or through a mobile device, We may collect certain information automatically, including, but not limited to, the type of mobile device You use, Your mobile device's unique ID, the IP address of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device identifiers and other diagnostic data.

    +

    We may also collect information that Your browser sends whenever You visit Our Service or when You access the Service by or through a mobile device.

    +

    Tracking Technologies and Cookies

    +

    We use Cookies and similar tracking technologies to track the activity on Our Service and store certain information. Tracking technologies We use include beacons, tags, and scripts to collect and track information and to improve and analyze Our Service. The technologies We use may include:

    +
      +
    • Cookies or Browser Cookies. A cookie is a small file placed on Your Device. You can instruct Your browser to refuse all Cookies or to indicate when a Cookie is being sent. However, if You do not accept Cookies, You may not be able to use some parts of our Service.
    • +
    +

    Cookies can be "Persistent" or "Session" Cookies. Persistent Cookies remain on Your personal computer or mobile device when You go offline, while Session Cookies are deleted as soon as You close Your web browser.

    +

    Where required by law, we use non-essential cookies (such as analytics, advertising, and remarketing cookies) only with Your consent. You can withdraw or change Your consent at any time using Our cookie preferences tool (if available) or through Your browser/device settings. Withdrawing consent does not affect the lawfulness of processing based on consent before its withdrawal.

    +

    We use both Session and Persistent Cookies for the purposes set out below:

    +
      +
    • +

      Necessary / Essential Cookies

      +

      Type: Session Cookies

      +

      Administered by: Us

      +

      Purpose: These Cookies are essential to provide You with services available through the Website and to enable You to use some of its features. They help to authenticate users and prevent fraudulent use of user accounts. Without these Cookies, the services that You have asked for cannot be provided, and We only use these Cookies to provide You with those services.

      +
    • +
    • +

      Functionality Cookies

      +

      Type: Persistent Cookies

      +

      Administered by: Us

      +

      Purpose: These Cookies allow Us to remember choices You make when You use the Website, such as remembering your login details or language preference. The purpose of these Cookies is to provide You with a more personal experience and to avoid You having to re-enter your preferences every time You use the Website.

      +
    • +
    +

    For more information about the cookies we use and your choices regarding cookies, please visit our Cookies Policy or the Cookies section of Our Privacy Policy.

    +

    Use of Your Personal Data

    +

    The Company may use Personal Data for the following purposes:

    +
      +
    • +

      To provide and maintain our Service, including to monitor the usage of our Service.

      +
    • +
    • +

      To manage Your Account: to manage Your registration as a user of the Service. The Personal Data You provide can give You access to different functionalities of the Service that are available to You as a registered user.

      +
    • +
    • +

      For the performance of a contract: the development, compliance and undertaking of the purchase contract for the products, items or services You have purchased or of any other contract with Us through the Service.

      +
    • +
    • +

      To contact You: To contact You by email, telephone calls, SMS, or other equivalent forms of electronic communication, such as a mobile application's push notifications regarding updates or informative communications related to the functionalities, products or contracted services, including the security updates, when necessary or reasonable for their implementation.

      +
    • +
    • +

      To provide You with news, special offers, and general information about other goods, services and events which We offer that are similar to those that you have already purchased or inquired about unless You have opted not to receive such information.

      +
    • +
    • +

      To manage Your requests: To attend and manage Your requests to Us.

      +
    • +
    • +

      For business transfers: We may use Your Personal Data to evaluate or conduct a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Our assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which Personal Data held by Us about our Service users is among the assets transferred.

      +
    • +
    • +

      For other purposes: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, products, services, marketing and your experience.

      +
    • +
    +

    We may share Your Personal Data in the following situations:

    +
      +
    • With Service Providers: We may share Your Personal Data with Service Providers to monitor and analyze the use of our Service, to contact You.
    • +
    • For business transfers: We may share or transfer Your Personal Data in connection with, or during negotiations of, any merger, sale of Company assets, financing, or acquisition of all or a portion of Our business to another company.
    • +
    • With Affiliates: We may share Your Personal Data with Our affiliates, in which case we will require those affiliates to honor this Privacy Policy. Affiliates include Our parent company and any other subsidiaries, joint venture partners or other companies that We control or that are under common control with Us.
    • +
    • With business partners: We may share Your Personal Data with Our business partners to offer You certain products, services or promotions.
    • +
    • With other users: If Our Service offers public areas, when You share Personal Data or otherwise interact in the public areas with other users, such information may be viewed by all users and may be publicly distributed outside.
    • +
    • With Your consent: We may disclose Your Personal Data for any other purpose with Your consent.
    • +
    +

    Retention of Your Personal Data

    +

    The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal obligations (for example, if We are required to retain Your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.

    +

    Where possible, We apply shorter retention periods and/or reduce identifiability by deleting, aggregating, or anonymizing data. Unless otherwise stated, the retention periods below are maximum periods ("up to") and We may delete or anonymize data sooner when it is no longer needed for the relevant purpose. We apply different retention periods to different categories of Personal Data based on the purpose of processing and legal obligations:

    +
      +
    • +

      Account Information

      +
        +
      • User Accounts: retained for the duration of your account relationship plus up to 24 months after account closure to handle any post-termination issues or resolve disputes.
      • +
      +
    • +
    • +

      Customer Support Data

      +
        +
      • Support tickets and correspondence: up to 24 months from the date of ticket closure to resolve follow-up inquiries, track service quality, and defend against potential legal claims
      • +
      • Chat transcripts: up to 24 months for quality assurance and staff training purposes.
      • +
      +
    • +
    • +

      Usage Data

      +
        +
      • +

        Website analytics data (cookies, IP addresses, device identifiers): up to 24 months from the date of collection, which allows us to analyze trends while respecting privacy principles.

        +
      • +
      • +

        Server logs (IP addresses, access times): up to 24 months for security monitoring and troubleshooting purposes.

        +
      • +
      +
    • +
    +

    Usage Data is retained in accordance with the retention periods described above, and may be retained longer only where necessary for security, fraud prevention, or legal compliance.

    +

    We may retain Personal Data beyond the periods stated above for different reasons:

    +
      +
    • Legal obligation: We are required by law to retain specific data (e.g., financial records for tax authorities).
    • +
    • Legal claims: Data is necessary to establish, exercise, or defend legal claims.
    • +
    • Your explicit request: You ask Us to retain specific information.
    • +
    • Technical limitations: Data exists in backup systems that are scheduled for routine deletion.
    • +
    +

    You may request information about how long We will retain Your Personal Data by contacting Us.

    +

    When retention periods expire, We securely delete or anonymize Personal Data according to the following procedures:

    +
      +
    • Deletion: Personal Data is removed from Our systems and no longer actively processed.
    • +
    • Backup retention: Residual copies may remain in encrypted backups for a limited period consistent with our backup retention schedule and are not restored except where necessary for security, disaster recovery, or legal compliance.
    • +
    • Anonymization: In some cases, We convert Personal Data into anonymous statistical data that cannot be linked back to You. This anonymized data may be retained indefinitely for research and analytics.
    • +
    +

    Transfer of Your Personal Data

    +

    Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ from those from Your jurisdiction.

    +

    Where required by applicable law, We will ensure that international transfers of Your Personal Data are subject to appropriate safeguards and supplementary measures where appropriate. The Company will take all steps reasonably necessary to ensure that Your data is treated securely and in accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of Your data and other personal information.

    +

    Delete Your Personal Data

    +

    You have the right to delete or request that We assist in deleting the Personal Data that We have collected about You.

    +

    Our Service may give You the ability to delete certain information about You from within the Service.

    +

    You may update, amend, or delete Your information at any time by signing in to Your Account, if you have one, and visiting the account settings section that allows you to manage Your personal information. You may also contact Us to request access to, correct, or delete any Personal Data that You have provided to Us.

    +

    Please note, however, that We may need to retain certain information when we have a legal obligation or lawful basis to do so.

    +

    Disclosure of Your Personal Data

    +

    Business Transactions

    +

    If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy Policy.

    +

    Law enforcement

    +

    Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency).

    +

    Other legal requirements

    +

    The Company may disclose Your Personal Data in the good faith belief that such action is necessary to:

    +
      +
    • Comply with a legal obligation
    • +
    • Protect and defend the rights or property of the Company
    • +
    • Prevent or investigate possible wrongdoing in connection with the Service
    • +
    • Protect the personal safety of Users of the Service or the public
    • +
    • Protect against legal liability
    • +
    +

    Security of Your Personal Data

    +

    The security of Your Personal Data is important to Us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While We strive to use commercially reasonable means to protect Your Personal Data, We cannot guarantee its absolute security.

    +

    Links to Other Websites

    +

    Our Service may contain links to other websites that are not operated by Us. If You click on a third party link, You will be directed to that third party's site. We strongly advise You to review the Privacy Policy of every site You visit.

    +

    We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.

    +

    Changes to this Privacy Policy

    +

    We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page.

    +

    We will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective and update the "Last updated" date at the top of this Privacy Policy.

    +

    You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.

    +

    Contact Us

    +

    If you have any questions about this Privacy Policy, You can contact us:

    +
      +
    • By email: privacy@gleipnir.technology
    • +
    +
    {{end}} From aa7585563b5373e79f4f5ca3d3acaf8edddebbe2 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 23 Jan 2026 02:49:47 +0000 Subject: [PATCH 0086/1453] Fix erroneos copy-paste log message --- background/comms.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/background/comms.go b/background/comms.go index 2992a1fd..e5e81390 100644 --- a/background/comms.go +++ b/background/comms.go @@ -72,7 +72,7 @@ func startWorkerEmail(ctx context.Context, channel chan jobEmail) { case job := <-channel: err := jobProcessEmail(job) if err != nil { - log.Error().Err(err).Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Error processing audio file") + log.Error().Err(err).Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Error processing email") } } } From 44fdaa6c2be55d1fa56c6543585e8c30e9025ce0 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 23 Jan 2026 02:50:25 +0000 Subject: [PATCH 0087/1453] Add initial onboard email ...and patterns for how to do email stuff in the future. --- background/comms.go | 15 +++++- comms/email.go | 77 ++++++++++++++++++++++++----- comms/template/initial.html | 97 +++++++++++++++++++++++++++++++++++++ comms/template/initial.txt | 1 + config/config.go | 15 ++++-- public-report/email.go | 17 +++++++ public-report/quick.go | 2 +- public-report/report.go | 9 ---- public-report/routes.go | 1 + public-report/status.go | 2 +- 10 files changed, 205 insertions(+), 31 deletions(-) create mode 100644 comms/template/initial.html create mode 100644 comms/template/initial.txt create mode 100644 public-report/email.go diff --git a/background/comms.go b/background/comms.go index e5e81390..0af5277b 100644 --- a/background/comms.go +++ b/background/comms.go @@ -97,8 +97,19 @@ func startWorkerText(ctx context.Context, channel chan jobText) { } func jobProcessEmail(job jobEmail) error { - log.Info().Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Pretend doing email job") - return nil + switch job.Type { + case enums.CommsMessagetypeemailInitialContact: + return comms.SendEmailInitialContact(job.Destination) + default: + return errors.New("not implemented") + } + /* + case enums.CommsMessagetypeemailReportSubscriptionConfirmation: + case enums.CommsMessagetypeemailReportStatusScheduled: + case enums.CommsMessagetypeemailReportStatusComplete: + + } + */ } func jobProcessText(job jobText) error { diff --git a/comms/email.go b/comms/email.go index a2e2a992..26c43f1b 100644 --- a/comms/email.go +++ b/comms/email.go @@ -13,13 +13,39 @@ import ( "github.com/rs/zerolog/log" ) +func RenderEmailInitial(w http.ResponseWriter, destination string) { + content := newContentEmailInitial(destination) + renderOrError(w, initialT, content) +} + func RenderEmailReportConfirmation(w http.ResponseWriter, report_id string) { - content := contentEmailSubscriptionConfirmation(report_id) + content := newContentEmailSubscriptionConfirmation(report_id) renderOrError(w, reportConfirmationT, content) } + +func SendEmailInitialContact(destination string) error { + content := newContentEmailInitial(destination) + text, html, err := renderEmailTemplates(reportConfirmationT, content) + if err != nil { + return fmt.Errorf("Failed to render email temlate: %w", err) + } + resp, err := sendEmail(emailRequest{ + From: config.ForwardEmailReportAddress, + HTML: html, + Subject: "Welcome", + Text: text, + To: destination, + }) + if err != nil { + return fmt.Errorf("Failed to send email to %s: %w", err) + } + log.Info().Str("id", resp.ID).Str("to", destination).Msg("Sent initial contact email") + return nil +} + func SendEmailReportConfirmation(to string, report_id string) error { report_id_str := publicReportID(report_id) - content := contentEmailSubscriptionConfirmation(report_id) + content := newContentEmailSubscriptionConfirmation(report_id) text, html, err := renderEmailTemplates(reportConfirmationT, content) if err != nil { return fmt.Errorf("Failed to render template %s: %w", reportConfirmationT.name, err) @@ -39,6 +65,7 @@ func SendEmailReportConfirmation(to string, report_id string) error { } var ( + initialT = buildTemplate("initial") reportConfirmationT = buildTemplate("report-subscription-confirmation") ) @@ -50,11 +77,20 @@ type attachmentRequest struct { Content string `json:"content"` } +type contentEmailBase struct { + URLLogo string + URLUnsubscribe string + URLViewInBrowser string +} + type contentEmailReportConfirmation struct { - URLLogo string - URLReportStatus string - URLReportUnsubscribe string - URLViewInBrowser string + Base contentEmailBase + URLReportStatus string +} +type contentEmailInitial struct { + Base contentEmailBase + Destination string + URLSubscribe string } type emailRequest struct { @@ -104,13 +140,28 @@ type emailResponse struct { Message string `json:"message"` } -func contentEmailSubscriptionConfirmation(report_id string) contentEmailReportConfirmation { - return contentEmailReportConfirmation{ - URLLogo: config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png"), - URLReportStatus: config.MakeURLReport("/status/%s", report_id), - URLReportUnsubscribe: config.MakeURLReport("/report/%s/unsubscribe", report_id), - URLViewInBrowser: config.MakeURLReport("/email/report/%s/subscription-confirmation", report_id), - } +func newContentBase(b *contentEmailBase, url string) { + b.URLLogo = config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png") + b.URLUnsubscribe = config.MakeURLReport("/email/unsubscribe") + b.URLViewInBrowser = url +} + +func newContentEmailInitial(destination string) (result contentEmailInitial) { + newContentBase( + &result.Base, + config.MakeURLReport("/email/initial"), + ) + result.Destination = destination + result.URLSubscribe = config.MakeURLReport("/email/subscribe?email=%s", destination) + return result +} +func newContentEmailSubscriptionConfirmation(report_id string) (result contentEmailReportConfirmation) { + newContentBase( + &result.Base, + config.MakeURLReport("/email/report/%s/subscription-confirmation", report_id), + ) + result.URLReportStatus = config.MakeURLReport("/status/%s", report_id) + return result } func publicReportID(s string) string { diff --git a/comms/template/initial.html b/comms/template/initial.html new file mode 100644 index 00000000..821db38a --- /dev/null +++ b/comms/template/initial.html @@ -0,0 +1,97 @@ + + + + + + Welcome + + + +
    +
    + Email not displaying correctly? View it in your browser +
    + +
    + + +
    + +
    +

    Welcome

    + +

    We're sending you this email because it's the first time we've gotten this email address ({{.Destination}}).

    + +

    If you'd rather not receive emails from us you can reply with "Unsubscribe" in the subject or body of the email. You can also use the "Unsubscribe" feature of your mail client, if it supports list unsubscribes.

    + +

    If instead you'd like to confirm that you're willing to receive emails at this address, you can do so by clicking below:

    + + +
    + + +
    + + diff --git a/comms/template/initial.txt b/comms/template/initial.txt new file mode 100644 index 00000000..eaf6a7cc --- /dev/null +++ b/comms/template/initial.txt @@ -0,0 +1 @@ +Welcome to Report Mosquitoes Online. diff --git a/config/config.go b/config/config.go index 51a72f85..584e5f0c 100644 --- a/config/config.go +++ b/config/config.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "net/url" "os" "github.com/nyaruka/phonenumbers" @@ -35,18 +36,22 @@ func IsProductionEnvironment() bool { return Environment == "PRODUCTION" } -func makeURL(domain, path string, args ...interface{}) string { +func makeURL(domain, path string, args ...string) string { + to_add := make([]any, 0) + for _, a := range args { + to_add = append(to_add, url.QueryEscape(a)) + } pattern := "https://" + domain + path - return fmt.Sprintf(pattern, args...) + return fmt.Sprintf(pattern, to_add...) } -func MakeURLNidus(path string, args ...interface{}) string { +func MakeURLNidus(path string, args ...string) string { return makeURL(DomainNidus, path, args...) } -func MakeURLReport(path string, args ...interface{}) string { +func MakeURLReport(path string, args ...string) string { return makeURL(DomainRMO, path, args...) } -func MakeURLTegola(path string, args ...interface{}) string { +func MakeURLTegola(path string, args ...string) string { return makeURL(DomainTegola, path, args...) } diff --git a/public-report/email.go b/public-report/email.go new file mode 100644 index 00000000..e39d9de0 --- /dev/null +++ b/public-report/email.go @@ -0,0 +1,17 @@ +package publicreport + +import ( + "net/http" + + "github.com/Gleipnir-Technology/nidus-sync/comms" + "github.com/go-chi/chi/v5" +) + +func getEmailInitial(w http.ResponseWriter, r *http.Request) { + email := chi.URLParam(r, "email") + comms.RenderEmailInitial(w, email) +} +func getEmailReportSubscriptionConfirmation(w http.ResponseWriter, r *http.Request) { + report_id := chi.URLParam(r, "report_id") + comms.RenderEmailReportConfirmation(w, report_id) +} diff --git a/public-report/quick.go b/public-report/quick.go index bf2106c8..0c344f41 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -75,7 +75,7 @@ func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) { log.Debug().Int32("org_id", org.ID).Int32("d_gid", d.Gid).Msg("Getting district") if d != nil { district = &District{ - LogoURL: config.MakeURLNidus("/api/district/%d/logo", org_id), + LogoURL: config.MakeURLNidus("/api/district/%s/logo", strconv.Itoa(int(org_id))), Name: d.Agency.GetOr("Unknown"), } } diff --git a/public-report/report.go b/public-report/report.go index 3a6960bd..b5baeb8f 100644 --- a/public-report/report.go +++ b/public-report/report.go @@ -4,11 +4,7 @@ import ( "crypto/rand" "fmt" "math/big" - "net/http" "strings" - - "github.com/Gleipnir-Technology/nidus-sync/comms" - "github.com/go-chi/chi/v5" ) // GenerateReportID creates a 12-character random string using only unambiguous @@ -35,8 +31,3 @@ func GenerateReportID() (string, error) { return builder.String(), nil } - -func getEmailReportSubscriptionConfirmation(w http.ResponseWriter, r *http.Request) { - report_id := chi.URLParam(r, "report_id") - comms.RenderEmailReportConfirmation(w, report_id) -} diff --git a/public-report/routes.go b/public-report/routes.go index d843af5f..5fefd950 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -10,6 +10,7 @@ func Router() chi.Router { r.Get("/", getRoot) r.Get("/privacy", getPrivacy) r.Get("/robots.txt", getRobots) + r.Get("/email/initial", getEmailInitial) r.Get("/email/report/{report_id}/subscription-confirmation", getEmailReportSubscriptionConfirmation) r.Get("/image/{uuid}", getImageByUUID) r.Get("/nuisance", getNuisance) diff --git a/public-report/status.go b/public-report/status.go index 04a10351..02b908be 100644 --- a/public-report/status.go +++ b/public-report/status.go @@ -185,7 +185,7 @@ func contentFromQuick(ctx context.Context, report_id string) (result ContentStat for _, image := range images { result.Report.Images = append(result.Report.Images, Image{ Location: image.LocationJSON, - URL: config.MakeURLReport("/image/%s", image.StorageUUID), + URL: config.MakeURLReport("/image/%s", image.StorageUUID.String()), }) } type LocationGeoJSON struct { From 5e6288ab9b8cc9542073491544d44471c44495dc Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 23 Jan 2026 03:32:06 +0000 Subject: [PATCH 0088/1453] Add beginnings of work to save emails to database Not tested yet --- background/comms.go | 6 +++--- comms/email.go | 50 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/background/comms.go b/background/comms.go index 0af5277b..b33de091 100644 --- a/background/comms.go +++ b/background/comms.go @@ -70,7 +70,7 @@ func startWorkerEmail(ctx context.Context, channel chan jobEmail) { log.Info().Msg("Email worker shutting down.") return case job := <-channel: - err := jobProcessEmail(job) + err := jobProcessEmail(ctx, job) if err != nil { log.Error().Err(err).Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Error processing email") } @@ -96,10 +96,10 @@ func startWorkerText(ctx context.Context, channel chan jobText) { }() } -func jobProcessEmail(job jobEmail) error { +func jobProcessEmail(ctx context.Context, job jobEmail) error { switch job.Type { case enums.CommsMessagetypeemailInitialContact: - return comms.SendEmailInitialContact(job.Destination) + return comms.SendEmailInitialContact(ctx, job.Destination) default: return errors.New("not implemented") } diff --git a/comms/email.go b/comms/email.go index 26c43f1b..56a34844 100644 --- a/comms/email.go +++ b/comms/email.go @@ -2,14 +2,20 @@ package comms import ( "bytes" + "context" "embed" "encoding/json" "fmt" "io" "net/http" + "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/htmlpage" + "github.com/aarondl/opt/omit" "github.com/rs/zerolog/log" ) @@ -23,19 +29,19 @@ func RenderEmailReportConfirmation(w http.ResponseWriter, report_id string) { renderOrError(w, reportConfirmationT, content) } -func SendEmailInitialContact(destination string) error { +func SendEmailInitialContact(ctx context.Context, destination string) error { content := newContentEmailInitial(destination) text, html, err := renderEmailTemplates(reportConfirmationT, content) if err != nil { return fmt.Errorf("Failed to render email temlate: %w", err) } - resp, err := sendEmail(emailRequest{ + resp, err := sendEmail(ctx, emailRequest{ From: config.ForwardEmailReportAddress, HTML: html, Subject: "Welcome", Text: text, To: destination, - }) + }, enums.CommsMessagetypeemailInitialContact) if err != nil { return fmt.Errorf("Failed to send email to %s: %w", err) } @@ -43,20 +49,20 @@ func SendEmailInitialContact(destination string) error { return nil } -func SendEmailReportConfirmation(to string, report_id string) error { +func SendEmailReportConfirmation(ctx context.Context, to string, report_id string) error { report_id_str := publicReportID(report_id) content := newContentEmailSubscriptionConfirmation(report_id) text, html, err := renderEmailTemplates(reportConfirmationT, content) if err != nil { return fmt.Errorf("Failed to render template %s: %w", reportConfirmationT.name, err) } - resp, err := sendEmail(emailRequest{ + resp, err := sendEmail(ctx, emailRequest{ From: config.ForwardEmailReportAddress, HTML: html, Subject: fmt.Sprintf("Mosquito Report Submission - %s", report_id_str), Text: text, To: to, - }) + }, enums.CommsMessagetypeemailReportSubscriptionConfirmation) if err != nil { return fmt.Errorf("Failed to send email report confirmation to %s for report %s: %w", to, report_id, err) } @@ -184,12 +190,42 @@ func renderOrError(w http.ResponseWriter, template *builtTemplate, context inter var FORWARDEMAIL_API = "https://api.forwardemail.net/v1/emails" -func sendEmail(email emailRequest) (response emailResponse, err error) { +func ensureInDB(ctx context.Context, destination string) (err error) { + _, err = models.FindCommsEmail(ctx, db.PGInstance.BobDB, destination) + if err != nil { + // assume it exists + log.Warn().Err(err).Msg("ElI, check what this error should look like") + return nil + } + _, err = models.CommsEmails.Insert(&models.CommsEmailSetter{ + Address: omit.From(destination), + Confirmed: omit.From(false), + IsSubscribed: omit.From(false), + }).One(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to insert new email: %w", err) + } + log.Info().Str("email", destination).Msg("Added email to the comms database") + return nil +} + +func insertEmailLog(ctx context.Context, email emailRequest, t enums.CommsMessagetypeemail) (err error) { + _, err = models.CommsEmailLogs.Insert(&models.CommsEmailLogSetter{ + Created: omit.From(time.Now()), + Destination: omit.From(email.To), + Source: omit.From(email.From), + Type: omit.From(t), + }).One(ctx, db.PGInstance.BobDB) + return err +} +func sendEmail(ctx context.Context, email emailRequest, t enums.CommsMessagetypeemail) (response emailResponse, err error) { + ensureInDB(ctx, email.To) payload, err := json.Marshal(email) if err != nil { return response, fmt.Errorf("Failed to marshal email request: %w", err) } + insertEmailLog(ctx, email, t) req, _ := http.NewRequest("POST", FORWARDEMAIL_API, bytes.NewReader(payload)) req.SetBasicAuth(config.ForwardEmailAPIToken, "") req.Header.Add("Content-Type", "application/json") From 3fed4892585f5cf703c9fa5d384aa18edf96ba17 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 23 Jan 2026 03:59:17 +0000 Subject: [PATCH 0089/1453] Fix subscribe URL --- comms/template/initial.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comms/template/initial.html b/comms/template/initial.html index 821db38a..ef65f4ed 100644 --- a/comms/template/initial.html +++ b/comms/template/initial.html @@ -83,7 +83,7 @@

    If instead you'd like to confirm that you're willing to receive emails at this address, you can do so by clicking below:

  • From 196792810bb2cda8bdfa7428eb0ebc5fcf7f33f4 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 23 Jan 2026 20:36:16 +0000 Subject: [PATCH 0090/1453] Overhaul email sending system Add logging and saving templates to the database for historical accuracy. --- background/background.go | 8 +- background/email.go | 42 + background/{comms.go => text.go} | 57 -- comms/email.go | 245 ----- comms/email/db.go | 103 +++ comms/email/email.go | 108 +++ comms/email/initial.go | 74 ++ comms/email/job.go | 44 + .../email/report_subscription_confirmation.go | 80 ++ comms/email/template.go | 319 +++++++ .../template/initial-contact.html} | 10 +- comms/email/template/initial-contact.txt | 7 + .../report-subscription-confirmation.html | 0 .../report-subscription-confirmation.txt | 0 comms/template.go | 148 --- comms/template/initial.txt | 1 - ...mail.bob.go => comms.email_contact.bob.go} | 6 +- db/dberrors/comms.email_log.bob.go | 2 +- db/dberrors/comms.email_template.bob.go | 17 + ...mail.bob.go => comms.email_contact.bob.go} | 50 +- db/dbinfo/comms.email_log.bob.go | 112 ++- db/dbinfo/comms.email_template.bob.go | 162 ++++ db/factory/bobfactory_context.bob.go | 17 +- db/factory/bobfactory_main.bob.go | 93 +- db/factory/bobfactory_random.bob.go | 14 + db/factory/comms.email.bob.go | 434 --------- db/factory/comms.email_contact.bob.go | 478 ++++++++++ db/factory/comms.email_log.bob.go | 478 ++++++++-- db/factory/comms.email_template.bob.go | 672 ++++++++++++++ db/factory/comms.phone.bob.go | 94 +- db/migrations/00039_email_template.sql | 44 + db/models/bob_counts.bob.go | 84 +- db/models/bob_joins.bob.go | 6 +- db/models/bob_loaders.bob.go | 12 +- db/models/bob_where.bob.go | 9 +- db/models/comms.email.bob.go | 737 --------------- db/models/comms.email_contact.bob.go | 762 +++++++++++++++ db/models/comms.email_log.bob.go | 522 +++++++---- db/models/comms.email_template.bob.go | 869 ++++++++++++++++++ db/models/comms.phone.bob.go | 258 +----- main.go | 7 + public-report/email.go | 17 +- public-report/quick.go | 2 +- public-report/routes.go | 3 +- 44 files changed, 4846 insertions(+), 2361 deletions(-) create mode 100644 background/email.go rename background/{comms.go => text.go} (57%) delete mode 100644 comms/email.go create mode 100644 comms/email/db.go create mode 100644 comms/email/email.go create mode 100644 comms/email/initial.go create mode 100644 comms/email/job.go create mode 100644 comms/email/report_subscription_confirmation.go create mode 100644 comms/email/template.go rename comms/{template/initial.html => email/template/initial-contact.html} (84%) create mode 100644 comms/email/template/initial-contact.txt rename comms/{ => email}/template/report-subscription-confirmation.html (100%) rename comms/{ => email}/template/report-subscription-confirmation.txt (100%) delete mode 100644 comms/template.go delete mode 100644 comms/template/initial.txt rename db/dberrors/{comms.email.bob.go => comms.email_contact.bob.go} (72%) create mode 100644 db/dberrors/comms.email_template.bob.go rename db/dbinfo/{comms.email.bob.go => comms.email_contact.bob.go} (59%) create mode 100644 db/dbinfo/comms.email_template.bob.go delete mode 100644 db/factory/comms.email.bob.go create mode 100644 db/factory/comms.email_contact.bob.go create mode 100644 db/factory/comms.email_template.bob.go create mode 100644 db/migrations/00039_email_template.sql delete mode 100644 db/models/comms.email.bob.go create mode 100644 db/models/comms.email_contact.bob.go create mode 100644 db/models/comms.email_template.bob.go diff --git a/background/background.go b/background/background.go index b800ac83..6afdabb4 100644 --- a/background/background.go +++ b/background/background.go @@ -3,6 +3,8 @@ package background import ( "context" "sync" + + "github.com/Gleipnir-Technology/nidus-sync/comms/email" ) var waitGroup sync.WaitGroup @@ -10,9 +12,9 @@ var waitGroup sync.WaitGroup func Start(ctx context.Context) { newOAuthTokenChannel = make(chan struct{}, 10) - channelJobAudio = make(chan jobAudio, 100) // Buffered channel to prevent blocking - channelJobEmail = make(chan jobEmail, 100) // Buffered channel to prevent blocking - channelJobText = make(chan jobText, 100) // Buffered channel to prevent blocking + channelJobAudio = make(chan jobAudio, 100) // Buffered channel to prevent blocking + channelJobEmail = make(chan email.Job, 100) // Buffered channel to prevent blocking + channelJobText = make(chan jobText, 100) // Buffered channel to prevent blocking waitGroup.Add(1) go func() { diff --git a/background/email.go b/background/email.go new file mode 100644 index 00000000..ea05ac9f --- /dev/null +++ b/background/email.go @@ -0,0 +1,42 @@ +package background + +import ( + "context" + + "github.com/Gleipnir-Technology/nidus-sync/comms/email" + "github.com/rs/zerolog/log" +) + +var channelJobEmail chan email.Job + +func ReportSubscriptionConfirmationEmail(destination, report_id string) { + enqueueJobEmail(email.NewJobReportSubscriptionConfirmation( + destination, + report_id, + )) +} + +func enqueueJobEmail(job email.Job) { + select { + case channelJobEmail <- job: + return + default: + log.Warn().Msg("email job channel is full, dropping job") + } +} + +func startWorkerEmail(ctx context.Context, channel chan email.Job) { + go func() { + for { + select { + case <-ctx.Done(): + log.Info().Msg("Email worker shutting down.") + return + case job := <-channel: + err := email.Handle(ctx, job) + if err != nil { + } + } + } + }() +} diff --git a/background/comms.go b/background/text.go similarity index 57% rename from background/comms.go rename to background/text.go index b33de091..853f7041 100644 --- a/background/comms.go +++ b/background/text.go @@ -11,17 +11,8 @@ import ( "github.com/rs/zerolog/log" ) -var channelJobEmail chan jobEmail var channelJobText chan jobText -func ReportSubscriptionConfirmationEmail(destination string) { - enqueueJobEmail(jobEmail{ - Destination: destination, - Source: config.ForwardEmailReportAddress, - Type: enums.CommsMessagetypeemailReportSubscriptionConfirmation, - }) -} - func ReportSubscriptionConfirmationText(destination comms.E164, report_id string) { enqueueJobText(jobText{ Destination: destination, @@ -31,12 +22,6 @@ func ReportSubscriptionConfirmationText(destination comms.E164, report_id string }) } -type jobEmail struct { - Destination string - ReportID string - Source string - Type enums.CommsMessagetypeemail -} type jobText struct { Destination comms.E164 ReportID string @@ -44,15 +29,6 @@ type jobText struct { Type enums.CommsMessagetypetext } -func enqueueJobEmail(job jobEmail) { - select { - case channelJobEmail <- job: - log.Info().Str("destination", job.Destination).Msg("Enqueued email job") - default: - log.Warn().Msg("email job channel is full, dropping job") - } -} - func enqueueJobText(job jobText) { select { case channelJobText <- job: @@ -62,23 +38,6 @@ func enqueueJobText(job jobText) { } } -func startWorkerEmail(ctx context.Context, channel chan jobEmail) { - go func() { - for { - select { - case <-ctx.Done(): - log.Info().Msg("Email worker shutting down.") - return - case job := <-channel: - err := jobProcessEmail(ctx, job) - if err != nil { - log.Error().Err(err).Str("dest", job.Destination).Str("type", string(job.Type)).Msg("Error processing email") - } - } - } - }() -} - func startWorkerText(ctx context.Context, channel chan jobText) { go func() { for { @@ -96,22 +55,6 @@ func startWorkerText(ctx context.Context, channel chan jobText) { }() } -func jobProcessEmail(ctx context.Context, job jobEmail) error { - switch job.Type { - case enums.CommsMessagetypeemailInitialContact: - return comms.SendEmailInitialContact(ctx, job.Destination) - default: - return errors.New("not implemented") - } - /* - case enums.CommsMessagetypeemailReportSubscriptionConfirmation: - case enums.CommsMessagetypeemailReportStatusScheduled: - case enums.CommsMessagetypeemailReportStatusComplete: - - } - */ -} - func jobProcessText(job jobText) error { var message string switch job.Type { diff --git a/comms/email.go b/comms/email.go deleted file mode 100644 index 56a34844..00000000 --- a/comms/email.go +++ /dev/null @@ -1,245 +0,0 @@ -package comms - -import ( - "bytes" - "context" - "embed" - "encoding/json" - "fmt" - "io" - "net/http" - "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/htmlpage" - "github.com/aarondl/opt/omit" - "github.com/rs/zerolog/log" -) - -func RenderEmailInitial(w http.ResponseWriter, destination string) { - content := newContentEmailInitial(destination) - renderOrError(w, initialT, content) -} - -func RenderEmailReportConfirmation(w http.ResponseWriter, report_id string) { - content := newContentEmailSubscriptionConfirmation(report_id) - renderOrError(w, reportConfirmationT, content) -} - -func SendEmailInitialContact(ctx context.Context, destination string) error { - content := newContentEmailInitial(destination) - text, html, err := renderEmailTemplates(reportConfirmationT, content) - if err != nil { - return fmt.Errorf("Failed to render email temlate: %w", err) - } - resp, err := sendEmail(ctx, emailRequest{ - From: config.ForwardEmailReportAddress, - HTML: html, - Subject: "Welcome", - Text: text, - To: destination, - }, enums.CommsMessagetypeemailInitialContact) - if err != nil { - return fmt.Errorf("Failed to send email to %s: %w", err) - } - log.Info().Str("id", resp.ID).Str("to", destination).Msg("Sent initial contact email") - return nil -} - -func SendEmailReportConfirmation(ctx context.Context, to string, report_id string) error { - report_id_str := publicReportID(report_id) - content := newContentEmailSubscriptionConfirmation(report_id) - text, html, err := renderEmailTemplates(reportConfirmationT, content) - if err != nil { - return fmt.Errorf("Failed to render template %s: %w", reportConfirmationT.name, err) - } - resp, err := sendEmail(ctx, emailRequest{ - From: config.ForwardEmailReportAddress, - HTML: html, - Subject: fmt.Sprintf("Mosquito Report Submission - %s", report_id_str), - Text: text, - To: to, - }, enums.CommsMessagetypeemailReportSubscriptionConfirmation) - if err != nil { - return fmt.Errorf("Failed to send email report confirmation to %s for report %s: %w", to, report_id, err) - } - log.Info().Str("id", resp.ID).Str("to", to).Str("report_id", report_id).Msg("Sent report confirmation email") - return nil -} - -var ( - initialT = buildTemplate("initial") - reportConfirmationT = buildTemplate("report-subscription-confirmation") -) - -//go:embed template/* -var embeddedFiles embed.FS - -type attachmentRequest struct { - Filename string `json:"filename"` - Content string `json:"content"` -} - -type contentEmailBase struct { - URLLogo string - URLUnsubscribe string - URLViewInBrowser string -} - -type contentEmailReportConfirmation struct { - Base contentEmailBase - URLReportStatus string -} -type contentEmailInitial struct { - Base contentEmailBase - Destination string - URLSubscribe string -} - -type emailRequest struct { - From string `json:"from"` - To string `json:"to"` - CC []string `json:"cc,omitempty"` - BCC []string `json:"bcc,omitempty"` - Subject string `json:"subject"` - Text string `json:"text"` - HTML string `json:"html,omitempty"` - Attachments []attachmentRequest `json:"attachments,omitempty"` - Sender string `json:"sender"` - ReplyTo string `json:"replyTo,omitempty"` - InReplyTo string `json:"inReplyTo,omitempty"` - References []string `json:"references,omitempty"` -} - -type emailEnvelope struct { - From string `json:"from"` - To []string `json:"to"` -} - -type emailResponse struct { - IsRedacted bool `json:"is_redacted"` - CreatedAt string `json:"created_at"` - HardBounces []string `json:"hard_bounces"` - SoftBounces []string `json:"soft_bounces"` - IsBounce bool `json:"is_bounce"` - Alias string `json:"alias"` - Domain string `json:"domain"` - User string `json:"user"` - Status string `json:"status"` - IsLocked bool `json:"is_locked"` - Envelope emailEnvelope `json:"envelope"` - RequireTLS bool `json:"requireTLS"` - MessageID string `json:"messageId"` - Headers map[string]string `json:"headers"` - Date string `json:"date"` - Subject string `json:"subject"` - Accepted []string `json:"accepted"` - Deliveries []string `json:"deliveries"` - RejectedErrors []string `json:"rejectedErrors"` - ID string `json:"id"` - Object string `json:"object"` - UpdatedAt string `json:"updated_at"` - Link string `json:"link"` - Message string `json:"message"` -} - -func newContentBase(b *contentEmailBase, url string) { - b.URLLogo = config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png") - b.URLUnsubscribe = config.MakeURLReport("/email/unsubscribe") - b.URLViewInBrowser = url -} - -func newContentEmailInitial(destination string) (result contentEmailInitial) { - newContentBase( - &result.Base, - config.MakeURLReport("/email/initial"), - ) - result.Destination = destination - result.URLSubscribe = config.MakeURLReport("/email/subscribe?email=%s", destination) - return result -} -func newContentEmailSubscriptionConfirmation(report_id string) (result contentEmailReportConfirmation) { - newContentBase( - &result.Base, - config.MakeURLReport("/email/report/%s/subscription-confirmation", report_id), - ) - result.URLReportStatus = config.MakeURLReport("/status/%s", report_id) - return result -} - -func publicReportID(s string) string { - if len(s) != 12 { - return s - } - return s[0:4] + "-" + s[4:8] + "-" + s[8:12] -} - -func renderOrError(w http.ResponseWriter, template *builtTemplate, context interface{}) { - buf := &bytes.Buffer{} - err := template.executeTemplateHTML(buf, context) - if err != nil { - log.Error().Err(err).Str("name", template.name).Msg("Failed to render template") - htmlpage.RespondError(w, "Failed to render template", err, http.StatusInternalServerError) - return - } - buf.WriteTo(w) -} - -var FORWARDEMAIL_API = "https://api.forwardemail.net/v1/emails" - -func ensureInDB(ctx context.Context, destination string) (err error) { - _, err = models.FindCommsEmail(ctx, db.PGInstance.BobDB, destination) - if err != nil { - // assume it exists - log.Warn().Err(err).Msg("ElI, check what this error should look like") - return nil - } - _, err = models.CommsEmails.Insert(&models.CommsEmailSetter{ - Address: omit.From(destination), - Confirmed: omit.From(false), - IsSubscribed: omit.From(false), - }).One(ctx, db.PGInstance.BobDB) - if err != nil { - return fmt.Errorf("Failed to insert new email: %w", err) - } - log.Info().Str("email", destination).Msg("Added email to the comms database") - return nil -} - -func insertEmailLog(ctx context.Context, email emailRequest, t enums.CommsMessagetypeemail) (err error) { - _, err = models.CommsEmailLogs.Insert(&models.CommsEmailLogSetter{ - Created: omit.From(time.Now()), - Destination: omit.From(email.To), - Source: omit.From(email.From), - Type: omit.From(t), - }).One(ctx, db.PGInstance.BobDB) - return err -} -func sendEmail(ctx context.Context, email emailRequest, t enums.CommsMessagetypeemail) (response emailResponse, err error) { - ensureInDB(ctx, email.To) - payload, err := json.Marshal(email) - if err != nil { - return response, fmt.Errorf("Failed to marshal email request: %w", err) - } - - insertEmailLog(ctx, email, t) - req, _ := http.NewRequest("POST", FORWARDEMAIL_API, bytes.NewReader(payload)) - req.SetBasicAuth(config.ForwardEmailAPIToken, "") - req.Header.Add("Content-Type", "application/json") - - res, _ := http.DefaultClient.Do(req) - - defer res.Body.Close() - body, _ := io.ReadAll(res.Body) - - // Parse the JSON response - err = json.Unmarshal(body, &response) - if err != nil { - log.Warn().Str("status", res.Status).Str("response_body", string(body)).Msg("Attempted to send email but couldn't parse the resulting JSON") - return response, fmt.Errorf("Failed to unmarshal JSON response: %w", err) - } - return response, nil -} diff --git a/comms/email/db.go b/comms/email/db.go new file mode 100644 index 00000000..39d4dc59 --- /dev/null +++ b/comms/email/db.go @@ -0,0 +1,103 @@ +package email + +import ( + "context" + "crypto/sha256" + "database/sql" + "encoding/hex" + "fmt" + "sort" + "strings" + "time" + + "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/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/rs/zerolog/log" + "github.com/stephenafamo/bob/types/pgtypes" +) + +func convertToPGData(data map[string]string) pgtypes.HStore { + result := pgtypes.HStore{} + for k, v := range data { + result[k] = sql.Null[string]{V: v, Valid: true} + } + return result +} + +func ensureInDB(ctx context.Context, destination string) (err error) { + _, err = models.FindCommsEmailContact(ctx, db.PGInstance.BobDB, destination) + if err != nil { + // doesn't exist + if err.Error() == "sql: no rows in result set" { + public_id := fmt.Sprintf("%x", sha256.Sum256([]byte(destination))) + _, err = models.CommsEmailContacts.Insert(&models.CommsEmailContactSetter{ + Address: omit.From(destination), + Confirmed: omit.From(false), + IsSubscribed: omit.From(false), + PublicID: omit.From(public_id), + }).One(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to insert new email: %w", err) + } + log.Info().Str("email", destination).Msg("Added email to the comms database") + return nil + } + return fmt.Errorf("Unexpected error searching for contact: %w", err) + } + return nil +} + +func insertEmailLog(ctx context.Context, data map[string]string, destination string, public_id string, source string, subject string, template_id int32) (err error) { + data_for_insert := convertToPGData(data) + _, err = models.CommsEmailLogs.Insert(&models.CommsEmailLogSetter{ + //ID: + Created: omit.From(time.Now()), + DeliveryStatus: omit.From("initial"), + Destination: omit.From(destination), + PublicID: omit.From(public_id), + SentAt: omitnull.FromPtr[time.Time](nil), + Source: omit.From(source), + Subject: omit.From(subject), + TemplateID: omitnull.From(templateInitialID), + TemplateData: omit.From(data_for_insert), + Type: omit.From(enums.CommsMessagetypeemailInitialContact), + }).One(ctx, db.PGInstance.BobDB) + + return err +} +func generatePublicId(t enums.CommsMessagetypeemail, m map[string]string) string { + if m == nil || len(m) == 0 { + // Return hash of empty string for empty maps + emptyHash := sha256.Sum256([]byte("")) + return hex.EncodeToString(emptyHash[:]) + } + + // Get and sort keys for deterministic ordering + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + + // Build a string with all key-value pairs + var sb strings.Builder + // Add type first + sb.WriteString(fmt.Sprintf("type:%s,", t)) + for _, k := range keys { + sb.WriteString(k) + sb.WriteString(":") // Separator between key and value + sb.WriteString(m[k]) + sb.WriteString(",") // Separator between pairs + } + + // Compute SHA-256 hash + hasher := sha256.New() + hasher.Write([]byte(sb.String())) + hashBytes := hasher.Sum(nil) + + // Convert to hex string and return + return hex.EncodeToString(hashBytes) +} diff --git a/comms/email/email.go b/comms/email/email.go new file mode 100644 index 00000000..ed33d08d --- /dev/null +++ b/comms/email/email.go @@ -0,0 +1,108 @@ +package email + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/rs/zerolog/log" +) + +type attachmentRequest struct { + Filename string `json:"filename"` + Content string `json:"content"` +} + +type contentEmailBase struct { + URLLogo string + URLUnsubscribe string + URLViewInBrowser string +} + +type contentEmailReportConfirmation struct { + Base contentEmailBase + URLReportStatus string +} +type contentEmailInitial struct { + Base contentEmailBase + Destination string + URLSubscribe string +} + +type emailRequest struct { + From string `json:"from"` + To string `json:"to"` + CC []string `json:"cc,omitempty"` + BCC []string `json:"bcc,omitempty"` + Subject string `json:"subject"` + Text string `json:"text"` + HTML string `json:"html,omitempty"` + Attachments []attachmentRequest `json:"attachments,omitempty"` + Sender string `json:"sender"` + ReplyTo string `json:"replyTo,omitempty"` + InReplyTo string `json:"inReplyTo,omitempty"` + References []string `json:"references,omitempty"` +} + +type emailEnvelope struct { + From string `json:"from"` + To []string `json:"to"` +} + +type emailResponse struct { + IsRedacted bool `json:"is_redacted"` + CreatedAt string `json:"created_at"` + HardBounces []string `json:"hard_bounces"` + SoftBounces []string `json:"soft_bounces"` + IsBounce bool `json:"is_bounce"` + Alias string `json:"alias"` + Domain string `json:"domain"` + User string `json:"user"` + Status string `json:"status"` + IsLocked bool `json:"is_locked"` + Envelope emailEnvelope `json:"envelope"` + RequireTLS bool `json:"requireTLS"` + MessageID string `json:"messageId"` + Headers map[string]string `json:"headers"` + Date string `json:"date"` + Subject string `json:"subject"` + Accepted []string `json:"accepted"` + Deliveries []string `json:"deliveries"` + RejectedErrors []string `json:"rejectedErrors"` + ID string `json:"id"` + Object string `json:"object"` + UpdatedAt string `json:"updated_at"` + Link string `json:"link"` + Message string `json:"message"` +} + +var FORWARDEMAIL_API = "https://api.forwardemail.net/v1/emails" + +func sendEmail(ctx context.Context, email emailRequest, t enums.CommsMessagetypeemail) (response emailResponse, err error) { + payload, err := json.Marshal(email) + if err != nil { + return response, fmt.Errorf("Failed to marshal email request: %w", err) + } + + req, _ := http.NewRequest("POST", FORWARDEMAIL_API, bytes.NewReader(payload)) + req.SetBasicAuth(config.ForwardEmailAPIToken, "") + req.Header.Add("Content-Type", "application/json") + + res, _ := http.DefaultClient.Do(req) + + defer res.Body.Close() + body, _ := io.ReadAll(res.Body) + + // Parse the JSON response + err = json.Unmarshal(body, &response) + if err != nil { + log.Warn().Str("status", res.Status).Str("response_body", string(body)).Msg("Attempted to send email but couldn't parse the resulting JSON") + return response, fmt.Errorf("Failed to unmarshal JSON response: %w", err) + } + return response, nil +} diff --git a/comms/email/initial.go b/comms/email/initial.go new file mode 100644 index 00000000..fd42fa1d --- /dev/null +++ b/comms/email/initial.go @@ -0,0 +1,74 @@ +package email + +import ( + "context" + "fmt" + + "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/rs/zerolog/log" +) + +type jobInitial struct { + base jobEmailBase +} + +func (job jobInitial) Destination() string { + return job.base.destination +} + +func maybeSendInitialEmail(ctx context.Context, destination string) error { + err := ensureInDB(ctx, destination) + if err != nil { + return fmt.Errorf("Failed to add email recipient to database: %w", err) + } + rows, err := models.CommsEmailLogs.Query( + models.SelectWhere.CommsEmailLogs.Destination.EQ(destination), + models.SelectWhere.CommsEmailLogs.TemplateID.EQ(templateInitialID), + ).All(ctx, db.PGInstance.BobDB) + + // We already sent an initial email + if len(rows) > 0 { + return nil + } + + return sendEmailInitialContact(ctx, destination) +} +func sendEmailInitialContact(ctx context.Context, destination string) error { + //data := pgtypes.HStore{} + data := make(map[string]string, 0) + source := config.ForwardEmailReportAddress + data["destination"] = destination + data["source"] = source + data["url_logo"] = config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png") + data["url_subscribe"] = config.MakeURLReport("/email/subscribe?email=%s", destination) + data["url_unsubscribe"] = config.MakeURLReport("/email/unsubscribe") + public_id := generatePublicId(enums.CommsMessagetypeemailInitialContact, data) + data["url_browser"] = config.MakeURLReport("/email?id=%s", public_id) + + text, html, err := renderEmailTemplates(templateInitialID, data) + if err != nil { + return fmt.Errorf("Failed to render email temlates: %w", err) + } + + subject := "Welcome" + err = insertEmailLog(ctx, data, destination, public_id, source, subject, templateInitialID) + if err != nil { + return fmt.Errorf("Failed to store email log: %w", err) + } + resp, err := sendEmail(ctx, emailRequest{ + From: source, + HTML: html, + Subject: subject, + Text: text, + To: destination, + }, enums.CommsMessagetypeemailInitialContact) + + if err != nil { + return fmt.Errorf("Failed to send email to %s: %w", err) + } + log.Info().Str("id", resp.ID).Str("to", destination).Msg("Sent initial contact email") + return nil +} diff --git a/comms/email/job.go b/comms/email/job.go new file mode 100644 index 00000000..b2f3fec7 --- /dev/null +++ b/comms/email/job.go @@ -0,0 +1,44 @@ +package email + +import ( + "context" + "errors" + "fmt" + + "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/rs/zerolog/log" +) + +type Job interface { + destination() string + messageType() enums.CommsMessagetypeemail + renderHTML() (string, error) + renderTXT() (string, error) + subject() string +} + +type jobEmailBase struct { + destination string + source string +} + +func Handle(ctx context.Context, job Job) error { + var err error + switch job.messageType() { + case enums.CommsMessagetypeemailReportSubscriptionConfirmation: + err = sendEmailReportConfirmation(ctx, job) + default: + return errors.New("not implemented") + } + if err != nil { + log.Error().Err(err).Str("dest", job.destination()).Str("type", string(job.messageType())).Msg("Error processing email") + return fmt.Errorf("Failed to handle email: %w", err) + } + return nil + /* + case enums.CommsMessagetypeemailReportStatusScheduled: + case enums.CommsMessagetypeemailReportStatusComplete: + + } + */ +} diff --git a/comms/email/report_subscription_confirmation.go b/comms/email/report_subscription_confirmation.go new file mode 100644 index 00000000..c689859d --- /dev/null +++ b/comms/email/report_subscription_confirmation.go @@ -0,0 +1,80 @@ +package email + +import ( + "context" + "fmt" + + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/db/enums" + //"github.com/rs/zerolog/log" +) + +func NewJobReportSubscriptionConfirmation(destination, report_id string) Job { + return jobEmailReportSubscriptionConfirmation{ + dest: destination, + reportID: report_id, + } +} + +type jobEmailReportSubscriptionConfirmation struct { + dest string + reportID string +} + +func (job jobEmailReportSubscriptionConfirmation) destination() string { + return job.dest +} +func (job jobEmailReportSubscriptionConfirmation) messageType() enums.CommsMessagetypeemail { + return enums.CommsMessagetypeemailReportSubscriptionConfirmation +} +func (job jobEmailReportSubscriptionConfirmation) renderHTML() (string, error) { + _ = newContentEmailSubscriptionConfirmation(job) + return "", nil +} +func (job jobEmailReportSubscriptionConfirmation) renderTXT() (string, error) { + return "fake txt", nil +} +func (job jobEmailReportSubscriptionConfirmation) subject() string { + return "" +} + +func sendEmailReportConfirmation(ctx context.Context, job Job) error { + j, ok := job.(jobEmailReportSubscriptionConfirmation) + if !ok { + return fmt.Errorf("job is not for report subscription confirmation") + } + err := maybeSendInitialEmail(ctx, j.destination()) + if err != nil { + return fmt.Errorf("Failed to handle initial email: %w", err) + } + return nil + /* + report_id_str := publicReportID(report_id) + content := newContentEmailSubscriptionConfirmation(report_id) + text, html, err := renderEmailTemplates(reportConfirmationT, content) + if err != nil { + return fmt.Errorf("Failed to render template %s: %w", reportConfirmationT.name, err) + } + resp, err := sendEmail(ctx, emailRequest{ + From: config.ForwardEmailReportAddress, + HTML: html, + Subject: fmt.Sprintf("Mosquito Report Submission - %s", report_id_str), + Text: text, + To: to, + }, enums.CommsMessagetypeemailReportSubscriptionConfirmation) + if err != nil { + return fmt.Errorf("Failed to send email report confirmation to %s for report %s: %w", to, report_id, err) + } + log.Info().Str("id", resp.ID).Str("to", to).Str("report_id", report_id).Msg("Sent report confirmation email") + return nil + */ +} + +func newContentEmailSubscriptionConfirmation(job jobEmailReportSubscriptionConfirmation) (result contentEmailReportConfirmation) { + /*newContentBase( + &result.Base, + config.MakeURLReport("/email/report/%s/subscription-confirmation", job.reportID), + )*/ + result.URLReportStatus = config.MakeURLReport("/status/%s", job.reportID) + return result +} diff --git a/comms/email/template.go b/comms/email/template.go new file mode 100644 index 00000000..424619f6 --- /dev/null +++ b/comms/email/template.go @@ -0,0 +1,319 @@ +package email + +import ( + "bytes" + "context" + "crypto/sha256" + "embed" + "errors" + "fmt" + templatehtml "html/template" + "io" + "io/fs" + "path" + "path/filepath" + "strings" + templatetxt "text/template" + "time" + + "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/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/rs/zerolog/log" + "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/dialect/psql" + "github.com/stephenafamo/bob/dialect/psql/um" +) + +//go:embed template/* +var embeddedFiles embed.FS + +var ( + templateByID map[int32]*builtTemplate + templateInitialID int32 +) + +type templatePair struct { + baseName string + messageType enums.CommsMessagetypeemail + htmlContent string + txtContent string + htmlHash string + txtHash string +} + +func LoadTemplates() error { + all_templates, err := readTemplates(embeddedFiles) + if err != nil { + return fmt.Errorf("Failed to read templates: %w", err) + } + ctx := context.TODO() + tx, err := db.PGInstance.BobDB.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("Failed to start transaction: %w", err) + } + defer tx.Rollback(ctx) + templateByID = make(map[int32]*builtTemplate, 0) + for name, p := range all_templates { + template_id, err := templateDBID(tx, name, p) + if err != nil { + return fmt.Errorf("Failed to add '%s' to DB: %w", name, err) + } + template_html, err := templatehtml.New(name).Parse(p.htmlContent) + if err != nil { + return fmt.Errorf("Failed to parse HTML portion of '%s': %w", name, err) + } + template_txt, err := templatetxt.New(name).Parse(p.txtContent) + if err != nil { + return fmt.Errorf("Failed to parse HTML portion of '%s': %w", name, err) + } + built := builtTemplate{ + name: name, + templateHTML: template_html, + templateTXT: template_txt, + } + templateByID[template_id] = &built + log.Info().Int32("id", template_id).Str("name", name).Msg("Added template to cache") + } + templateInitialID, err = loadTemplateID(ctx, tx, enums.CommsMessagetypeemailInitialContact) + if err != nil { + return fmt.Errorf("Failed to load template ID: %s", err) + } + tx.Commit(ctx) + return nil +} + +func loadTemplateID(ctx context.Context, tx bob.Tx, t enums.CommsMessagetypeemail) (int32, error) { + templates, err := models.CommsEmailTemplates.Query( + models.SelectWhere.CommsEmailTemplates.MessageType.EQ(t), + models.SelectWhere.CommsEmailTemplates.Superceded.IsNull(), + ).All(ctx, tx) + if err != nil { + return 0, fmt.Errorf("Failed to query template '%s': %w", t, err) + } + switch len(templates) { + case 0: + return 0, fmt.Errorf("No matching templates for '%s", t) + case 1: + return templates[0].ID, nil + default: + return 0, fmt.Errorf("Found %d templates for '%s', should only have 1", len(templates), t) + } +} + +func readTemplates(filesystem embed.FS) (results map[string]*templatePair, err error) { + // First pass: read files and organize by base name + results = make(map[string]*templatePair) + + err = fs.WalkDir(filesystem, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + return nil + } + + // Read file content + content, err := filesystem.ReadFile(path) + if err != nil { + return fmt.Errorf("error reading template %s: %w", path, err) + } + + // Calculate hash + hash := fmt.Sprintf("%x", sha256.Sum256(content)) + + // Extract base name and extension + ext := strings.ToLower(filepath.Ext(path)) + baseName := strings.TrimSuffix(filepath.Base(path), ext) + + // Store in map by base name + if _, exists := results[baseName]; !exists { + t, err := messageTypeFromName(baseName) + if err != nil { + return fmt.Errorf("Cannot parse email templates: %w", err) + } + results[baseName] = &templatePair{ + baseName: baseName, + messageType: *t, + } + } + + // Add content based on extension + switch ext { + case ".html", ".htm": + results[baseName].htmlContent = string(content) + results[baseName].htmlHash = hash + case ".txt": + results[baseName].txtContent = string(content) + results[baseName].txtHash = hash + } + + return nil + }) + + if err != nil { + return results, fmt.Errorf("error walking template directory: %w", err) + } + + return results, nil +} + +func templateDBID(tx bob.Tx, name string, pair *templatePair) (int32, error) { + ctx := context.Background() + + // Skip incomplete pairs + if pair.htmlContent == "" { + return 0, fmt.Errorf("Bad template pair '%s': no html content") + } + if pair.txtContent == "" { + return 0, fmt.Errorf("Bad template pair '%s': no txt content") + } + + // Check if a template with these hashes already exists + rows, err := models.CommsEmailTemplates.Query( + models.SelectWhere.CommsEmailTemplates.ContentHashHTML.EQ(pair.htmlHash), + models.SelectWhere.CommsEmailTemplates.ContentHashTXT.EQ(pair.txtHash), + models.SelectWhere.CommsEmailTemplates.MessageType.EQ(pair.messageType), + ).All(ctx, tx) + if err != nil { + return 0, fmt.Errorf("Failed to query for existing template: %w", err) + } + if len(rows) > 1 { + return 0, fmt.Errorf("Got %d template rows, should only have 1", len(rows)) + } else if len(rows) == 1 { + return rows[0].ID, nil + } + + // Supercede previous templates of this type + _, err = psql.Update( + um.Table(models.CommsEmailTemplates.Alias()), + um.SetCol("superceded").ToArg(time.Now()), + //um.Where(models.CommsEmailTemplates.Columns.MessageType.EQ(psql.Arg(pair.messageType))), + um.Where(psql.Quote("message_type").EQ(psql.Arg(pair.messageType))), + //um.Where(models.CommsEmailTemplates.Columns.Superceded.IsNull()), + um.Where(psql.Quote("superceded").IsNull()), + ).Exec(ctx, tx) + if err != nil { + return 0, fmt.Errorf("error superceding templates: %w", err) + } + + new_template, err := models.CommsEmailTemplates.Insert(&models.CommsEmailTemplateSetter{ + ContentHTML: omit.From(pair.htmlContent), + ContentTXT: omit.From(pair.txtContent), + ContentHashHTML: omit.From(pair.htmlHash), + ContentHashTXT: omit.From(pair.txtHash), + Created: omit.From(time.Now()), + Superceded: omitnull.FromPtr[time.Time](nil), + MessageType: omit.From(pair.messageType), + }).One(ctx, tx) + if err != nil { + return 0, fmt.Errorf("Failed to insert new template: %w", err) + } + log.Info().Int32("id", new_template.ID).Str("type", string(pair.messageType)).Msg("Added new email template") + + return new_template.ID, nil +} + +type builtTemplate struct { + name string + templateHTML *templatehtml.Template + templateTXT *templatetxt.Template +} + +func (bt *builtTemplate) executeTemplateHTML(w io.Writer, content any) error { + if bt.templateHTML == nil { + file := templateFileHTML(bt.name) + templ, err := parseFromDiskHTML(file) + if err != nil { + return fmt.Errorf("Failed to parse template file: %w", err) + } + if templ == nil { + w.Write([]byte("Failed to read from disk: ")) + return errors.New("Template parsing failed") + } + //log.Debug().Str("name", templ.Name()).Msg("Parsed template") + return templ.ExecuteTemplate(w, bt.name, content) + } else { + return bt.templateHTML.ExecuteTemplate(w, bt.name, content) + } +} +func (bt *builtTemplate) executeTemplateTXT(w io.Writer, content any) error { + if bt.templateTXT == nil { + file := templateFileTXT(bt.name) + templ, err := parseFromDiskTXT(file) + if err != nil { + return fmt.Errorf("Failed to parse template file: %w", err) + } + if templ == nil { + w.Write([]byte("Failed to read from disk: ")) + return errors.New("Template parsing failed") + } + //log.Debug().Str("name", templ.Name()).Msg("Parsed template") + return templ.ExecuteTemplate(w, bt.name, content) + } else { + return bt.templateTXT.ExecuteTemplate(w, bt.name, content) + } +} +func templateFileHTML(name string) string { + return fmt.Sprintf("comms/template/%s.html", name) +} +func templateFileTXT(name string) string { + return fmt.Sprintf("comms/template/%s.txt", name) +} + +func messageTypeFromName(n string) (*enums.CommsMessagetypeemail, error) { + for _, t := range enums.AllCommsMessagetypeemail() { + if n == string(t) { + return &t, nil + } + } + return nil, fmt.Errorf("Unrecognized email type '%s'", n) +} + +func parseFromDiskHTML(file string) (*templatehtml.Template, error) { + name := path.Base(file) + //log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk") + templ, err := templatehtml.New(name).ParseFiles(file) + if err != nil { + return nil, fmt.Errorf("Failed to parse %s: %w", file, err) + } + return templ, nil +} + +func parseFromDiskTXT(file string) (*templatetxt.Template, error) { + name := path.Base(file) + //log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk") + templ, err := templatetxt.New(name).ParseFiles(file) + if err != nil { + return nil, fmt.Errorf("Failed to parse %s: %w", file, err) + } + return templ, nil +} + +func publicReportID(s string) string { + if len(s) != 12 { + return s + } + return s[0:4] + "-" + s[4:8] + "-" + s[8:12] +} + +func renderEmailTemplates(template_id int32, content map[string]string) (text string, html string, err error) { + buf_txt := &bytes.Buffer{} + t, ok := templateByID[template_id] + if !ok { + return "", "", fmt.Errorf("Failed to lookup template %d", template_id) + } + err = t.executeTemplateTXT(buf_txt, content) + if err != nil { + return "", "", fmt.Errorf("Failed to render TXT template: %w", err) + } + buf_html := &bytes.Buffer{} + err = t.executeTemplateHTML(buf_html, content) + if err != nil { + return "", "", fmt.Errorf("Failed to render HTML template: %w", err) + } + return buf_txt.String(), buf_html.String(), nil +} diff --git a/comms/template/initial.html b/comms/email/template/initial-contact.html similarity index 84% rename from comms/template/initial.html rename to comms/email/template/initial-contact.html index ef65f4ed..341e3ae4 100644 --- a/comms/template/initial.html +++ b/comms/email/template/initial-contact.html @@ -65,31 +65,31 @@
    - Email not displaying correctly? View it in your browser + Email not displaying correctly? View it in your browser
    - +

    Welcome

    -

    We're sending you this email because it's the first time we've gotten this email address ({{.Destination}}).

    +

    We're sending you this email because it's the first time we've gotten this email address ({{.destination}}).

    If you'd rather not receive emails from us you can reply with "Unsubscribe" in the subject or body of the email. You can also use the "Unsubscribe" feature of your mail client, if it supports list unsubscribes.

    If instead you'd like to confirm that you're willing to receive emails at this address, you can do so by clicking below:

    diff --git a/comms/email/template/initial-contact.txt b/comms/email/template/initial-contact.txt new file mode 100644 index 00000000..92c358cd --- /dev/null +++ b/comms/email/template/initial-contact.txt @@ -0,0 +1,7 @@ +We're sending you this email because it's the first time we've gotten this email address ({{.Destination}}). +If you'd rather not receive emails from us you can reply with "Unsubscribe" in the subject or body of the email. You can also use the "Unsubscribe" feature of your mail client, if it supports list unsubscribes. +If instead you'd like to confirm that you're willing to receive emails at this address, you can do so by openining the following URL in a web browser: {{.URLSubscribe}}. You can also confirm your willingness by replying to this email with 'Confirm' in the subject on the body of the email. + +Thank you, +Report Mosquitoes Online + diff --git a/comms/template/report-subscription-confirmation.html b/comms/email/template/report-subscription-confirmation.html similarity index 100% rename from comms/template/report-subscription-confirmation.html rename to comms/email/template/report-subscription-confirmation.html diff --git a/comms/template/report-subscription-confirmation.txt b/comms/email/template/report-subscription-confirmation.txt similarity index 100% rename from comms/template/report-subscription-confirmation.txt rename to comms/email/template/report-subscription-confirmation.txt diff --git a/comms/template.go b/comms/template.go deleted file mode 100644 index 5eaa4d00..00000000 --- a/comms/template.go +++ /dev/null @@ -1,148 +0,0 @@ -package comms - -import ( - "bytes" - "embed" - "errors" - "fmt" - templatehtml "html/template" - "io" - "os" - "path" - "strings" - templatetxt "text/template" - - "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/rs/zerolog/log" -) - -type builtTemplate struct { - name string - templateHTML *templatehtml.Template - templateTXT *templatetxt.Template -} - -func (bt *builtTemplate) executeTemplateHTML(w io.Writer, content any) error { - if bt.templateHTML == nil { - file := templateFileHTML(bt.name) - templ, err := parseFromDiskHTML(file) - if err != nil { - return fmt.Errorf("Failed to parse template file: %w", err) - } - if templ == nil { - w.Write([]byte("Failed to read from disk: ")) - return errors.New("Template parsing failed") - } - //log.Debug().Str("name", templ.Name()).Msg("Parsed template") - return templ.ExecuteTemplate(w, bt.name+".html", content) - } else { - return bt.templateHTML.ExecuteTemplate(w, bt.name+".html", content) - } -} -func (bt *builtTemplate) executeTemplateTXT(w io.Writer, content any) error { - if bt.templateTXT == nil { - file := templateFileTXT(bt.name) - templ, err := parseFromDiskTXT(file) - if err != nil { - return fmt.Errorf("Failed to parse template file: %w", err) - } - if templ == nil { - w.Write([]byte("Failed to read from disk: ")) - return errors.New("Template parsing failed") - } - //log.Debug().Str("name", templ.Name()).Msg("Parsed template") - return templ.ExecuteTemplate(w, bt.name+".txt", content) - } else { - return bt.templateTXT.ExecuteTemplate(w, bt.name+".txt", content) - } -} -func templateFileHTML(name string) string { - return fmt.Sprintf("comms/template/%s.html", name) -} -func templateFileTXT(name string) string { - return fmt.Sprintf("comms/template/%s.txt", name) -} - -func buildTemplate(name string) *builtTemplate { - files_on_disk := true - file_html := templateFileHTML(name) - file_txt := templateFileTXT(name) - for _, f := range []string{file_html, file_txt} { - _, err := os.Stat(f) - if err != nil { - files_on_disk = false - if !config.IsProductionEnvironment() { - log.Warn().Str("file", f).Msg("email template file is not on disk") - } - break - } - } - var result builtTemplate - if files_on_disk { - result = builtTemplate{ - name: name, - templateHTML: nil, - templateTXT: nil, - } - } else { - result = builtTemplate{ - name: name, - templateHTML: parseEmbeddedHTML(embeddedFiles, "comms", file_html), - templateTXT: parseEmbeddedTXT(embeddedFiles, "comms", file_txt), - } - } - return &result -} - -func parseEmbeddedHTML(embeddedFiles embed.FS, subdir string, file string) *templatehtml.Template { - // Remap the file names to embedded paths - to_trim := subdir + "/" - embeddedFilePaths := []string{strings.TrimPrefix(file, to_trim)} - name := path.Base(embeddedFilePaths[0]) - log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template") - return templatehtml.Must( - templatehtml.New(name).ParseFS(embeddedFiles, embeddedFilePaths...)) -} -func parseEmbeddedTXT(embeddedFiles embed.FS, subdir string, file string) *templatetxt.Template { - // Remap the file names to embedded paths - to_trim := subdir + "/" - embeddedFilePaths := []string{strings.TrimPrefix(file, to_trim)} - name := path.Base(embeddedFilePaths[0]) - log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template") - return templatetxt.Must( - templatetxt.New(name).ParseFS(embeddedFiles, embeddedFilePaths...)) -} - -func parseFromDiskHTML(file string) (*templatehtml.Template, error) { - name := path.Base(file) - //log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk") - templ, err := templatehtml.New(name).ParseFiles(file) - if err != nil { - return nil, fmt.Errorf("Failed to parse %s: %w", file, err) - } - return templ, nil -} - -func parseFromDiskTXT(file string) (*templatetxt.Template, error) { - name := path.Base(file) - //log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk") - templ, err := templatetxt.New(name).ParseFiles(file) - if err != nil { - return nil, fmt.Errorf("Failed to parse %s: %w", file, err) - } - return templ, nil -} - -func renderEmailTemplates(t *builtTemplate, content interface{}) (text string, html string, err error) { - buf_txt := &bytes.Buffer{} - err = t.executeTemplateTXT(buf_txt, content) - if err != nil { - return "", "", fmt.Errorf("Failed to render TXT template: %w", err) - } - buf_html := &bytes.Buffer{} - err = t.executeTemplateHTML(buf_html, content) - if err != nil { - return "", "", fmt.Errorf("Failed to render HTML template: %w", err) - } - return buf_txt.String(), buf_html.String(), nil -} diff --git a/comms/template/initial.txt b/comms/template/initial.txt deleted file mode 100644 index eaf6a7cc..00000000 --- a/comms/template/initial.txt +++ /dev/null @@ -1 +0,0 @@ -Welcome to Report Mosquitoes Online. diff --git a/db/dberrors/comms.email.bob.go b/db/dberrors/comms.email_contact.bob.go similarity index 72% rename from db/dberrors/comms.email.bob.go rename to db/dberrors/comms.email_contact.bob.go index 09f25a12..08568a2c 100644 --- a/db/dberrors/comms.email.bob.go +++ b/db/dberrors/comms.email_contact.bob.go @@ -3,15 +3,15 @@ package dberrors -var CommsEmailErrors = &commsEmailErrors{ +var CommsEmailContactErrors = &commsEmailContactErrors{ ErrUniqueEmailPkey: &UniqueConstraintError{ schema: "comms", - table: "email", + table: "email_contact", columns: []string{"address"}, s: "email_pkey", }, } -type commsEmailErrors struct { +type commsEmailContactErrors struct { ErrUniqueEmailPkey *UniqueConstraintError } diff --git a/db/dberrors/comms.email_log.bob.go b/db/dberrors/comms.email_log.bob.go index 6948b065..29f8370c 100644 --- a/db/dberrors/comms.email_log.bob.go +++ b/db/dberrors/comms.email_log.bob.go @@ -7,7 +7,7 @@ var CommsEmailLogErrors = &commsEmailLogErrors{ ErrUniqueEmailLogPkey: &UniqueConstraintError{ schema: "comms", table: "email_log", - columns: []string{"destination", "source", "type"}, + columns: []string{"id"}, s: "email_log_pkey", }, } diff --git a/db/dberrors/comms.email_template.bob.go b/db/dberrors/comms.email_template.bob.go new file mode 100644 index 00000000..d0fe76ed --- /dev/null +++ b/db/dberrors/comms.email_template.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var CommsEmailTemplateErrors = &commsEmailTemplateErrors{ + ErrUniqueEmailTemplatePkey: &UniqueConstraintError{ + schema: "comms", + table: "email_template", + columns: []string{"id"}, + s: "email_template_pkey", + }, +} + +type commsEmailTemplateErrors struct { + ErrUniqueEmailTemplatePkey *UniqueConstraintError +} diff --git a/db/dbinfo/comms.email.bob.go b/db/dbinfo/comms.email_contact.bob.go similarity index 59% rename from db/dbinfo/comms.email.bob.go rename to db/dbinfo/comms.email_contact.bob.go index 8e5d1b15..6dff7493 100644 --- a/db/dbinfo/comms.email.bob.go +++ b/db/dbinfo/comms.email_contact.bob.go @@ -5,16 +5,16 @@ package dbinfo import "github.com/aarondl/opt/null" -var CommsEmails = Table[ - commsEmailColumns, - commsEmailIndexes, - commsEmailForeignKeys, - commsEmailUniques, - commsEmailChecks, +var CommsEmailContacts = Table[ + commsEmailContactColumns, + commsEmailContactIndexes, + commsEmailContactForeignKeys, + commsEmailContactUniques, + commsEmailContactChecks, ]{ Schema: "comms", - Name: "email", - Columns: commsEmailColumns{ + Name: "email_contact", + Columns: commsEmailContactColumns{ Address: column{ Name: "address", DBType: "text", @@ -42,8 +42,17 @@ var CommsEmails = Table[ Generated: false, AutoIncr: false, }, + PublicID: column{ + Name: "public_id", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, }, - Indexes: commsEmailIndexes{ + Indexes: commsEmailContactIndexes{ EmailPkey: index{ Type: "btree", Name: "email_pkey", @@ -71,42 +80,43 @@ var CommsEmails = Table[ Comment: "", } -type commsEmailColumns struct { +type commsEmailContactColumns struct { Address column Confirmed column IsSubscribed column + PublicID column } -func (c commsEmailColumns) AsSlice() []column { +func (c commsEmailContactColumns) AsSlice() []column { return []column{ - c.Address, c.Confirmed, c.IsSubscribed, + c.Address, c.Confirmed, c.IsSubscribed, c.PublicID, } } -type commsEmailIndexes struct { +type commsEmailContactIndexes struct { EmailPkey index } -func (i commsEmailIndexes) AsSlice() []index { +func (i commsEmailContactIndexes) AsSlice() []index { return []index{ i.EmailPkey, } } -type commsEmailForeignKeys struct{} +type commsEmailContactForeignKeys struct{} -func (f commsEmailForeignKeys) AsSlice() []foreignKey { +func (f commsEmailContactForeignKeys) AsSlice() []foreignKey { return []foreignKey{} } -type commsEmailUniques struct{} +type commsEmailContactUniques struct{} -func (u commsEmailUniques) AsSlice() []constraint { +func (u commsEmailContactUniques) AsSlice() []constraint { return []constraint{} } -type commsEmailChecks struct{} +type commsEmailContactChecks struct{} -func (c commsEmailChecks) AsSlice() []check { +func (c commsEmailContactChecks) AsSlice() []check { return []check{} } diff --git a/db/dbinfo/comms.email_log.bob.go b/db/dbinfo/comms.email_log.bob.go index 5fd8a244..09a41454 100644 --- a/db/dbinfo/comms.email_log.bob.go +++ b/db/dbinfo/comms.email_log.bob.go @@ -15,6 +15,15 @@ var CommsEmailLogs = Table[ Schema: "comms", Name: "email_log", Columns: commsEmailLogColumns{ + ID: column{ + Name: "id", + DBType: "integer", + Default: "nextval('comms.email_log_id_seq'::regclass)", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, Created: column{ Name: "created", DBType: "timestamp without time zone", @@ -24,6 +33,15 @@ var CommsEmailLogs = Table[ Generated: false, AutoIncr: false, }, + DeliveryStatus: column{ + Name: "delivery_status", + DBType: "character varying", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, Destination: column{ Name: "destination", DBType: "text", @@ -33,6 +51,24 @@ var CommsEmailLogs = Table[ Generated: false, AutoIncr: false, }, + PublicID: column{ + Name: "public_id", + DBType: "character varying", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + SentAt: column{ + Name: "sent_at", + DBType: "timestamp without time zone", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, Source: column{ Name: "source", DBType: "text", @@ -42,6 +78,33 @@ var CommsEmailLogs = Table[ Generated: false, AutoIncr: false, }, + Subject: column{ + Name: "subject", + DBType: "character varying", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + TemplateID: column{ + Name: "template_id", + DBType: "integer", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + TemplateData: column{ + Name: "template_data", + DBType: "hstore", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, Type: column{ Name: "type", DBType: "comms.messagetypeemail", @@ -58,24 +121,14 @@ var CommsEmailLogs = Table[ Name: "email_log_pkey", Columns: []indexColumn{ { - Name: "destination", - Desc: null.FromCond(false, true), - IsExpression: false, - }, - { - Name: "source", - Desc: null.FromCond(false, true), - IsExpression: false, - }, - { - Name: "type", + Name: "id", Desc: null.FromCond(false, true), IsExpression: false, }, }, Unique: true, Comment: "", - NullsFirst: []bool{false, false, false}, + NullsFirst: []bool{false}, NullsDistinct: false, Where: "", Include: []string{}, @@ -83,7 +136,7 @@ var CommsEmailLogs = Table[ }, PrimaryKey: &constraint{ Name: "email_log_pkey", - Columns: []string{"destination", "source", "type"}, + Columns: []string{"id"}, Comment: "", }, ForeignKeys: commsEmailLogForeignKeys{ @@ -93,17 +146,17 @@ var CommsEmailLogs = Table[ Columns: []string{"destination"}, Comment: "", }, - ForeignTable: "comms.email", + ForeignTable: "comms.email_contact", ForeignColumns: []string{"address"}, }, - CommsEmailLogEmailLogSourceFkey: foreignKey{ + CommsEmailLogEmailLogTemplateIDFkey: foreignKey{ constraint: constraint{ - Name: "comms.email_log.email_log_source_fkey", - Columns: []string{"source"}, + Name: "comms.email_log.email_log_template_id_fkey", + Columns: []string{"template_id"}, Comment: "", }, - ForeignTable: "comms.phone", - ForeignColumns: []string{"e164"}, + ForeignTable: "comms.email_template", + ForeignColumns: []string{"id"}, }, }, @@ -111,15 +164,22 @@ var CommsEmailLogs = Table[ } type commsEmailLogColumns struct { - Created column - Destination column - Source column - Type column + ID column + Created column + DeliveryStatus column + Destination column + PublicID column + SentAt column + Source column + Subject column + TemplateID column + TemplateData column + Type column } func (c commsEmailLogColumns) AsSlice() []column { return []column{ - c.Created, c.Destination, c.Source, c.Type, + c.ID, c.Created, c.DeliveryStatus, c.Destination, c.PublicID, c.SentAt, c.Source, c.Subject, c.TemplateID, c.TemplateData, c.Type, } } @@ -135,12 +195,12 @@ func (i commsEmailLogIndexes) AsSlice() []index { type commsEmailLogForeignKeys struct { CommsEmailLogEmailLogDestinationFkey foreignKey - CommsEmailLogEmailLogSourceFkey foreignKey + CommsEmailLogEmailLogTemplateIDFkey foreignKey } func (f commsEmailLogForeignKeys) AsSlice() []foreignKey { return []foreignKey{ - f.CommsEmailLogEmailLogDestinationFkey, f.CommsEmailLogEmailLogSourceFkey, + f.CommsEmailLogEmailLogDestinationFkey, f.CommsEmailLogEmailLogTemplateIDFkey, } } diff --git a/db/dbinfo/comms.email_template.bob.go b/db/dbinfo/comms.email_template.bob.go new file mode 100644 index 00000000..b299e2e7 --- /dev/null +++ b/db/dbinfo/comms.email_template.bob.go @@ -0,0 +1,162 @@ +// Code generated by BobGen psql v0.42.1. 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 CommsEmailTemplates = Table[ + commsEmailTemplateColumns, + commsEmailTemplateIndexes, + commsEmailTemplateForeignKeys, + commsEmailTemplateUniques, + commsEmailTemplateChecks, +]{ + Schema: "comms", + Name: "email_template", + Columns: commsEmailTemplateColumns{ + ContentHTML: column{ + Name: "content_html", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + ContentTXT: column{ + Name: "content_txt", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + ContentHashHTML: column{ + Name: "content_hash_html", + DBType: "character varying", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + ContentHashTXT: column{ + Name: "content_hash_txt", + DBType: "character varying", + 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, + }, + ID: column{ + Name: "id", + DBType: "integer", + Default: "nextval('comms.email_template_id_seq'::regclass)", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Superceded: column{ + Name: "superceded", + DBType: "timestamp without time zone", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + MessageType: column{ + Name: "message_type", + DBType: "comms.messagetypeemail", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: commsEmailTemplateIndexes{ + EmailTemplatePkey: index{ + Type: "btree", + Name: "email_template_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: "email_template_pkey", + Columns: []string{"id"}, + Comment: "", + }, + + Comment: "", +} + +type commsEmailTemplateColumns struct { + ContentHTML column + ContentTXT column + ContentHashHTML column + ContentHashTXT column + Created column + ID column + Superceded column + MessageType column +} + +func (c commsEmailTemplateColumns) AsSlice() []column { + return []column{ + c.ContentHTML, c.ContentTXT, c.ContentHashHTML, c.ContentHashTXT, c.Created, c.ID, c.Superceded, c.MessageType, + } +} + +type commsEmailTemplateIndexes struct { + EmailTemplatePkey index +} + +func (i commsEmailTemplateIndexes) AsSlice() []index { + return []index{ + i.EmailTemplatePkey, + } +} + +type commsEmailTemplateForeignKeys struct{} + +func (f commsEmailTemplateForeignKeys) AsSlice() []foreignKey { + return []foreignKey{} +} + +type commsEmailTemplateUniques struct{} + +func (u commsEmailTemplateUniques) AsSlice() []constraint { + return []constraint{} +} + +type commsEmailTemplateChecks struct{} + +func (c commsEmailTemplateChecks) AsSlice() []check { + return []check{} +} diff --git a/db/factory/bobfactory_context.bob.go b/db/factory/bobfactory_context.bob.go index 6c9c023b..f105c35c 100644 --- a/db/factory/bobfactory_context.bob.go +++ b/db/factory/bobfactory_context.bob.go @@ -17,18 +17,21 @@ var ( arcgisUserPrivilegeWithParentsCascadingCtx = newContextual[bool]("arcgisUserPrivilegeWithParentsCascading") arcgisUserPrivilegeRelUserUserCtx = newContextual[bool]("arcgis.user_.arcgis.user_privilege.arcgis.user_privilege.user_privilege_user_id_fkey") - // Relationship Contexts for comms.email - commsEmailWithParentsCascadingCtx = newContextual[bool]("commsEmailWithParentsCascading") - commsEmailRelDestinationEmailLogsCtx = newContextual[bool]("comms.email.comms.email_log.comms.email_log.email_log_destination_fkey") + // Relationship Contexts for comms.email_contact + commsEmailContactWithParentsCascadingCtx = newContextual[bool]("commsEmailContactWithParentsCascading") + commsEmailContactRelDestinationEmailLogsCtx = newContextual[bool]("comms.email_contact.comms.email_log.comms.email_log.email_log_destination_fkey") // Relationship Contexts for comms.email_log - commsEmailLogWithParentsCascadingCtx = newContextual[bool]("commsEmailLogWithParentsCascading") - commsEmailLogRelDestinationEmailCtx = newContextual[bool]("comms.email.comms.email_log.comms.email_log.email_log_destination_fkey") - commsEmailLogRelSourcePhoneCtx = newContextual[bool]("comms.email_log.comms.phone.comms.email_log.email_log_source_fkey") + commsEmailLogWithParentsCascadingCtx = newContextual[bool]("commsEmailLogWithParentsCascading") + commsEmailLogRelDestinationEmailContactCtx = newContextual[bool]("comms.email_contact.comms.email_log.comms.email_log.email_log_destination_fkey") + commsEmailLogRelTemplateEmailTemplateCtx = newContextual[bool]("comms.email_log.comms.email_template.comms.email_log.email_log_template_id_fkey") + + // Relationship Contexts for comms.email_template + commsEmailTemplateWithParentsCascadingCtx = newContextual[bool]("commsEmailTemplateWithParentsCascading") + commsEmailTemplateRelTemplateEmailLogsCtx = newContextual[bool]("comms.email_log.comms.email_template.comms.email_log.email_log_template_id_fkey") // Relationship Contexts for comms.phone commsPhoneWithParentsCascadingCtx = newContextual[bool]("commsPhoneWithParentsCascading") - commsPhoneRelSourceEmailLogsCtx = newContextual[bool]("comms.email_log.comms.phone.comms.email_log.email_log_source_fkey") commsPhoneRelDestinationTextLogsCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_destination_fkey") commsPhoneRelSourceTextLogsCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_source_fkey") diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index d1f5124a..2f2ea083 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -15,13 +15,15 @@ import ( "github.com/lib/pq" "github.com/shopspring/decimal" "github.com/stephenafamo/bob/types" + "github.com/stephenafamo/bob/types/pgtypes" ) type Factory struct { baseArcgisUserMods ArcgisUserModSlice baseArcgisUserPrivilegeMods ArcgisUserPrivilegeModSlice - baseCommsEmailMods CommsEmailModSlice + baseCommsEmailContactMods CommsEmailContactModSlice baseCommsEmailLogMods CommsEmailLogModSlice + baseCommsEmailTemplateMods CommsEmailTemplateModSlice baseCommsPhoneMods CommsPhoneModSlice baseCommsTextLogMods CommsTextLogModSlice baseFieldseekerContainerrelateMods FieldseekerContainerrelateModSlice @@ -160,32 +162,33 @@ func (f *Factory) FromExistingArcgisUserPrivilege(m *models.ArcgisUserPrivilege) return o } -func (f *Factory) NewCommsEmail(mods ...CommsEmailMod) *CommsEmailTemplate { - return f.NewCommsEmailWithContext(context.Background(), mods...) +func (f *Factory) NewCommsEmailContact(mods ...CommsEmailContactMod) *CommsEmailContactTemplate { + return f.NewCommsEmailContactWithContext(context.Background(), mods...) } -func (f *Factory) NewCommsEmailWithContext(ctx context.Context, mods ...CommsEmailMod) *CommsEmailTemplate { - o := &CommsEmailTemplate{f: f} +func (f *Factory) NewCommsEmailContactWithContext(ctx context.Context, mods ...CommsEmailContactMod) *CommsEmailContactTemplate { + o := &CommsEmailContactTemplate{f: f} if f != nil { - f.baseCommsEmailMods.Apply(ctx, o) + f.baseCommsEmailContactMods.Apply(ctx, o) } - CommsEmailModSlice(mods).Apply(ctx, o) + CommsEmailContactModSlice(mods).Apply(ctx, o) return o } -func (f *Factory) FromExistingCommsEmail(m *models.CommsEmail) *CommsEmailTemplate { - o := &CommsEmailTemplate{f: f, alreadyPersisted: true} +func (f *Factory) FromExistingCommsEmailContact(m *models.CommsEmailContact) *CommsEmailContactTemplate { + o := &CommsEmailContactTemplate{f: f, alreadyPersisted: true} o.Address = func() string { return m.Address } o.Confirmed = func() bool { return m.Confirmed } o.IsSubscribed = func() bool { return m.IsSubscribed } + o.PublicID = func() string { return m.PublicID } ctx := context.Background() if len(m.R.DestinationEmailLogs) > 0 { - CommsEmailMods.AddExistingDestinationEmailLogs(m.R.DestinationEmailLogs...).Apply(ctx, o) + CommsEmailContactMods.AddExistingDestinationEmailLogs(m.R.DestinationEmailLogs...).Apply(ctx, o) } return o @@ -210,17 +213,60 @@ func (f *Factory) NewCommsEmailLogWithContext(ctx context.Context, mods ...Comms func (f *Factory) FromExistingCommsEmailLog(m *models.CommsEmailLog) *CommsEmailLogTemplate { o := &CommsEmailLogTemplate{f: f, alreadyPersisted: true} + o.ID = func() int32 { return m.ID } o.Created = func() time.Time { return m.Created } + o.DeliveryStatus = func() string { return m.DeliveryStatus } o.Destination = func() string { return m.Destination } + o.PublicID = func() string { return m.PublicID } + o.SentAt = func() null.Val[time.Time] { return m.SentAt } o.Source = func() string { return m.Source } + o.Subject = func() string { return m.Subject } + o.TemplateID = func() null.Val[int32] { return m.TemplateID } + o.TemplateData = func() pgtypes.HStore { return m.TemplateData } o.Type = func() enums.CommsMessagetypeemail { return m.Type } ctx := context.Background() - if m.R.DestinationEmail != nil { - CommsEmailLogMods.WithExistingDestinationEmail(m.R.DestinationEmail).Apply(ctx, o) + if m.R.DestinationEmailContact != nil { + CommsEmailLogMods.WithExistingDestinationEmailContact(m.R.DestinationEmailContact).Apply(ctx, o) } - if m.R.SourcePhone != nil { - CommsEmailLogMods.WithExistingSourcePhone(m.R.SourcePhone).Apply(ctx, o) + if m.R.TemplateEmailTemplate != nil { + CommsEmailLogMods.WithExistingTemplateEmailTemplate(m.R.TemplateEmailTemplate).Apply(ctx, o) + } + + return o +} + +func (f *Factory) NewCommsEmailTemplate(mods ...CommsEmailTemplateMod) *CommsEmailTemplateTemplate { + return f.NewCommsEmailTemplateWithContext(context.Background(), mods...) +} + +func (f *Factory) NewCommsEmailTemplateWithContext(ctx context.Context, mods ...CommsEmailTemplateMod) *CommsEmailTemplateTemplate { + o := &CommsEmailTemplateTemplate{f: f} + + if f != nil { + f.baseCommsEmailTemplateMods.Apply(ctx, o) + } + + CommsEmailTemplateModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingCommsEmailTemplate(m *models.CommsEmailTemplate) *CommsEmailTemplateTemplate { + o := &CommsEmailTemplateTemplate{f: f, alreadyPersisted: true} + + o.ContentHTML = func() string { return m.ContentHTML } + o.ContentTXT = func() string { return m.ContentTXT } + o.ContentHashHTML = func() string { return m.ContentHashHTML } + o.ContentHashTXT = func() string { return m.ContentHashTXT } + o.Created = func() time.Time { return m.Created } + o.ID = func() int32 { return m.ID } + o.Superceded = func() null.Val[time.Time] { return m.Superceded } + o.MessageType = func() enums.CommsMessagetypeemail { return m.MessageType } + + ctx := context.Background() + if len(m.R.TemplateEmailLogs) > 0 { + CommsEmailTemplateMods.AddExistingTemplateEmailLogs(m.R.TemplateEmailLogs...).Apply(ctx, o) } return o @@ -249,9 +295,6 @@ func (f *Factory) FromExistingCommsPhone(m *models.CommsPhone) *CommsPhoneTempla o.IsSubscribed = func() bool { return m.IsSubscribed } ctx := context.Background() - if len(m.R.SourceEmailLogs) > 0 { - CommsPhoneMods.AddExistingSourceEmailLogs(m.R.SourceEmailLogs...).Apply(ctx, o) - } if len(m.R.DestinationTextLogs) > 0 { CommsPhoneMods.AddExistingDestinationTextLogs(m.R.DestinationTextLogs...).Apply(ctx, o) } @@ -3198,12 +3241,12 @@ func (f *Factory) AddBaseArcgisUserPrivilegeMod(mods ...ArcgisUserPrivilegeMod) f.baseArcgisUserPrivilegeMods = append(f.baseArcgisUserPrivilegeMods, mods...) } -func (f *Factory) ClearBaseCommsEmailMods() { - f.baseCommsEmailMods = nil +func (f *Factory) ClearBaseCommsEmailContactMods() { + f.baseCommsEmailContactMods = nil } -func (f *Factory) AddBaseCommsEmailMod(mods ...CommsEmailMod) { - f.baseCommsEmailMods = append(f.baseCommsEmailMods, mods...) +func (f *Factory) AddBaseCommsEmailContactMod(mods ...CommsEmailContactMod) { + f.baseCommsEmailContactMods = append(f.baseCommsEmailContactMods, mods...) } func (f *Factory) ClearBaseCommsEmailLogMods() { @@ -3214,6 +3257,14 @@ func (f *Factory) AddBaseCommsEmailLogMod(mods ...CommsEmailLogMod) { f.baseCommsEmailLogMods = append(f.baseCommsEmailLogMods, mods...) } +func (f *Factory) ClearBaseCommsEmailTemplateMods() { + f.baseCommsEmailTemplateMods = nil +} + +func (f *Factory) AddBaseCommsEmailTemplateMod(mods ...CommsEmailTemplateMod) { + f.baseCommsEmailTemplateMods = append(f.baseCommsEmailTemplateMods, mods...) +} + func (f *Factory) ClearBaseCommsPhoneMods() { f.baseCommsPhoneMods = nil } diff --git a/db/factory/bobfactory_random.bob.go b/db/factory/bobfactory_random.bob.go index 120f1c21..dfa5ebc5 100644 --- a/db/factory/bobfactory_random.bob.go +++ b/db/factory/bobfactory_random.bob.go @@ -5,6 +5,7 @@ package factory import ( "bytes" + "database/sql" "encoding/json" "fmt" "math" @@ -18,6 +19,7 @@ import ( "github.com/lib/pq" "github.com/shopspring/decimal" "github.com/stephenafamo/bob/types" + "github.com/stephenafamo/bob/types/pgtypes" ) var defaultFaker = faker.New() @@ -281,6 +283,18 @@ func random_int64(f *faker.Faker, limits ...string) int64 { return f.Int64() } +func random_pgtypes_HStore(f *faker.Faker, limits ...string) pgtypes.HStore { + if f == nil { + f = &defaultFaker + } + + hs := make(pgtypes.HStore) + for range f.IntBetween(1, 5) { + hs[random_string(f)] = sql.Null[string]{V: random_string(f, limits...), Valid: f.Bool()} + } + return hs +} + func random_pq_BoolArray(f *faker.Faker, limits ...string) pq.BoolArray { if f == nil { f = &defaultFaker diff --git a/db/factory/comms.email.bob.go b/db/factory/comms.email.bob.go deleted file mode 100644 index 610aae07..00000000 --- a/db/factory/comms.email.bob.go +++ /dev/null @@ -1,434 +0,0 @@ -// Code generated by BobGen psql v0.42.1. 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/jaswdr/faker/v2" - "github.com/stephenafamo/bob" -) - -type CommsEmailMod interface { - Apply(context.Context, *CommsEmailTemplate) -} - -type CommsEmailModFunc func(context.Context, *CommsEmailTemplate) - -func (f CommsEmailModFunc) Apply(ctx context.Context, n *CommsEmailTemplate) { - f(ctx, n) -} - -type CommsEmailModSlice []CommsEmailMod - -func (mods CommsEmailModSlice) Apply(ctx context.Context, n *CommsEmailTemplate) { - for _, f := range mods { - f.Apply(ctx, n) - } -} - -// CommsEmailTemplate is an object representing the database table. -// all columns are optional and should be set by mods -type CommsEmailTemplate struct { - Address func() string - Confirmed func() bool - IsSubscribed func() bool - - r commsEmailR - f *Factory - - alreadyPersisted bool -} - -type commsEmailR struct { - DestinationEmailLogs []*commsEmailRDestinationEmailLogsR -} - -type commsEmailRDestinationEmailLogsR struct { - number int - o *CommsEmailLogTemplate -} - -// Apply mods to the CommsEmailTemplate -func (o *CommsEmailTemplate) Apply(ctx context.Context, mods ...CommsEmailMod) { - for _, mod := range mods { - mod.Apply(ctx, o) - } -} - -// setModelRels creates and sets the relationships on *models.CommsEmail -// according to the relationships in the template. Nothing is inserted into the db -func (t CommsEmailTemplate) setModelRels(o *models.CommsEmail) { - if t.r.DestinationEmailLogs != nil { - rel := models.CommsEmailLogSlice{} - for _, r := range t.r.DestinationEmailLogs { - related := r.o.BuildMany(r.number) - for _, rel := range related { - rel.Destination = o.Address // h2 - rel.R.DestinationEmail = o - } - rel = append(rel, related...) - } - o.R.DestinationEmailLogs = rel - } -} - -// BuildSetter returns an *models.CommsEmailSetter -// this does nothing with the relationship templates -func (o CommsEmailTemplate) BuildSetter() *models.CommsEmailSetter { - m := &models.CommsEmailSetter{} - - if o.Address != nil { - val := o.Address() - m.Address = omit.From(val) - } - if o.Confirmed != nil { - val := o.Confirmed() - m.Confirmed = omit.From(val) - } - if o.IsSubscribed != nil { - val := o.IsSubscribed() - m.IsSubscribed = omit.From(val) - } - - return m -} - -// BuildManySetter returns an []*models.CommsEmailSetter -// this does nothing with the relationship templates -func (o CommsEmailTemplate) BuildManySetter(number int) []*models.CommsEmailSetter { - m := make([]*models.CommsEmailSetter, number) - - for i := range m { - m[i] = o.BuildSetter() - } - - return m -} - -// Build returns an *models.CommsEmail -// Related objects are also created and placed in the .R field -// NOTE: Objects are not inserted into the database. Use CommsEmailTemplate.Create -func (o CommsEmailTemplate) Build() *models.CommsEmail { - m := &models.CommsEmail{} - - if o.Address != nil { - m.Address = o.Address() - } - if o.Confirmed != nil { - m.Confirmed = o.Confirmed() - } - if o.IsSubscribed != nil { - m.IsSubscribed = o.IsSubscribed() - } - - o.setModelRels(m) - - return m -} - -// BuildMany returns an models.CommsEmailSlice -// Related objects are also created and placed in the .R field -// NOTE: Objects are not inserted into the database. Use CommsEmailTemplate.CreateMany -func (o CommsEmailTemplate) BuildMany(number int) models.CommsEmailSlice { - m := make(models.CommsEmailSlice, number) - - for i := range m { - m[i] = o.Build() - } - - return m -} - -func ensureCreatableCommsEmail(m *models.CommsEmailSetter) { - if !(m.Address.IsValue()) { - val := random_string(nil) - m.Address = omit.From(val) - } - if !(m.Confirmed.IsValue()) { - val := random_bool(nil) - m.Confirmed = omit.From(val) - } - if !(m.IsSubscribed.IsValue()) { - val := random_bool(nil) - m.IsSubscribed = omit.From(val) - } -} - -// insertOptRels creates and inserts any optional the relationships on *models.CommsEmail -// according to the relationships in the template. -// any required relationship should have already exist on the model -func (o *CommsEmailTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsEmail) error { - var err error - - isDestinationEmailLogsDone, _ := commsEmailRelDestinationEmailLogsCtx.Value(ctx) - if !isDestinationEmailLogsDone && o.r.DestinationEmailLogs != nil { - ctx = commsEmailRelDestinationEmailLogsCtx.WithValue(ctx, true) - for _, r := range o.r.DestinationEmailLogs { - if r.o.alreadyPersisted { - m.R.DestinationEmailLogs = append(m.R.DestinationEmailLogs, r.o.Build()) - } else { - rel0, err := r.o.CreateMany(ctx, exec, r.number) - if err != nil { - return err - } - - err = m.AttachDestinationEmailLogs(ctx, exec, rel0...) - if err != nil { - return err - } - } - } - } - - return err -} - -// Create builds a commsEmail and inserts it into the database -// Relations objects are also inserted and placed in the .R field -func (o *CommsEmailTemplate) Create(ctx context.Context, exec bob.Executor) (*models.CommsEmail, error) { - var err error - opt := o.BuildSetter() - ensureCreatableCommsEmail(opt) - - m, err := models.CommsEmails.Insert(opt).One(ctx, exec) - if err != nil { - return nil, err - } - - if err := o.insertOptRels(ctx, exec, m); err != nil { - return nil, err - } - return m, err -} - -// MustCreate builds a commsEmail and inserts it into the database -// Relations objects are also inserted and placed in the .R field -// panics if an error occurs -func (o *CommsEmailTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.CommsEmail { - m, err := o.Create(ctx, exec) - if err != nil { - panic(err) - } - return m -} - -// CreateOrFail builds a commsEmail 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 *CommsEmailTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.CommsEmail { - tb.Helper() - m, err := o.Create(ctx, exec) - if err != nil { - tb.Fatal(err) - return nil - } - return m -} - -// CreateMany builds multiple commsEmails and inserts them into the database -// Relations objects are also inserted and placed in the .R field -func (o CommsEmailTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.CommsEmailSlice, error) { - var err error - m := make(models.CommsEmailSlice, number) - - for i := range m { - m[i], err = o.Create(ctx, exec) - if err != nil { - return nil, err - } - } - - return m, nil -} - -// MustCreateMany builds multiple commsEmails and inserts them into the database -// Relations objects are also inserted and placed in the .R field -// panics if an error occurs -func (o CommsEmailTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.CommsEmailSlice { - m, err := o.CreateMany(ctx, exec, number) - if err != nil { - panic(err) - } - return m -} - -// CreateManyOrFail builds multiple commsEmails 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 CommsEmailTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.CommsEmailSlice { - tb.Helper() - m, err := o.CreateMany(ctx, exec, number) - if err != nil { - tb.Fatal(err) - return nil - } - return m -} - -// CommsEmail has methods that act as mods for the CommsEmailTemplate -var CommsEmailMods commsEmailMods - -type commsEmailMods struct{} - -func (m commsEmailMods) RandomizeAllColumns(f *faker.Faker) CommsEmailMod { - return CommsEmailModSlice{ - CommsEmailMods.RandomAddress(f), - CommsEmailMods.RandomConfirmed(f), - CommsEmailMods.RandomIsSubscribed(f), - } -} - -// Set the model columns to this value -func (m commsEmailMods) Address(val string) CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.Address = func() string { return val } - }) -} - -// Set the Column from the function -func (m commsEmailMods) AddressFunc(f func() string) CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.Address = f - }) -} - -// Clear any values for the column -func (m commsEmailMods) UnsetAddress() CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.Address = nil - }) -} - -// Generates a random value for the column using the given faker -// if faker is nil, a default faker is used -func (m commsEmailMods) RandomAddress(f *faker.Faker) CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.Address = func() string { - return random_string(f) - } - }) -} - -// Set the model columns to this value -func (m commsEmailMods) Confirmed(val bool) CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.Confirmed = func() bool { return val } - }) -} - -// Set the Column from the function -func (m commsEmailMods) ConfirmedFunc(f func() bool) CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.Confirmed = f - }) -} - -// Clear any values for the column -func (m commsEmailMods) UnsetConfirmed() CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.Confirmed = nil - }) -} - -// Generates a random value for the column using the given faker -// if faker is nil, a default faker is used -func (m commsEmailMods) RandomConfirmed(f *faker.Faker) CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.Confirmed = func() bool { - return random_bool(f) - } - }) -} - -// Set the model columns to this value -func (m commsEmailMods) IsSubscribed(val bool) CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.IsSubscribed = func() bool { return val } - }) -} - -// Set the Column from the function -func (m commsEmailMods) IsSubscribedFunc(f func() bool) CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.IsSubscribed = f - }) -} - -// Clear any values for the column -func (m commsEmailMods) UnsetIsSubscribed() CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.IsSubscribed = nil - }) -} - -// Generates a random value for the column using the given faker -// if faker is nil, a default faker is used -func (m commsEmailMods) RandomIsSubscribed(f *faker.Faker) CommsEmailMod { - return CommsEmailModFunc(func(_ context.Context, o *CommsEmailTemplate) { - o.IsSubscribed = func() bool { - return random_bool(f) - } - }) -} - -func (m commsEmailMods) WithParentsCascading() CommsEmailMod { - return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { - if isDone, _ := commsEmailWithParentsCascadingCtx.Value(ctx); isDone { - return - } - ctx = commsEmailWithParentsCascadingCtx.WithValue(ctx, true) - }) -} - -func (m commsEmailMods) WithDestinationEmailLogs(number int, related *CommsEmailLogTemplate) CommsEmailMod { - return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { - o.r.DestinationEmailLogs = []*commsEmailRDestinationEmailLogsR{{ - number: number, - o: related, - }} - }) -} - -func (m commsEmailMods) WithNewDestinationEmailLogs(number int, mods ...CommsEmailLogMod) CommsEmailMod { - return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { - related := o.f.NewCommsEmailLogWithContext(ctx, mods...) - m.WithDestinationEmailLogs(number, related).Apply(ctx, o) - }) -} - -func (m commsEmailMods) AddDestinationEmailLogs(number int, related *CommsEmailLogTemplate) CommsEmailMod { - return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { - o.r.DestinationEmailLogs = append(o.r.DestinationEmailLogs, &commsEmailRDestinationEmailLogsR{ - number: number, - o: related, - }) - }) -} - -func (m commsEmailMods) AddNewDestinationEmailLogs(number int, mods ...CommsEmailLogMod) CommsEmailMod { - return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { - related := o.f.NewCommsEmailLogWithContext(ctx, mods...) - m.AddDestinationEmailLogs(number, related).Apply(ctx, o) - }) -} - -func (m commsEmailMods) AddExistingDestinationEmailLogs(existingModels ...*models.CommsEmailLog) CommsEmailMod { - return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { - for _, em := range existingModels { - o.r.DestinationEmailLogs = append(o.r.DestinationEmailLogs, &commsEmailRDestinationEmailLogsR{ - o: o.f.FromExistingCommsEmailLog(em), - }) - } - }) -} - -func (m commsEmailMods) WithoutDestinationEmailLogs() CommsEmailMod { - return CommsEmailModFunc(func(ctx context.Context, o *CommsEmailTemplate) { - o.r.DestinationEmailLogs = nil - }) -} diff --git a/db/factory/comms.email_contact.bob.go b/db/factory/comms.email_contact.bob.go new file mode 100644 index 00000000..11a7fe1d --- /dev/null +++ b/db/factory/comms.email_contact.bob.go @@ -0,0 +1,478 @@ +// Code generated by BobGen psql v0.42.1. 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/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type CommsEmailContactMod interface { + Apply(context.Context, *CommsEmailContactTemplate) +} + +type CommsEmailContactModFunc func(context.Context, *CommsEmailContactTemplate) + +func (f CommsEmailContactModFunc) Apply(ctx context.Context, n *CommsEmailContactTemplate) { + f(ctx, n) +} + +type CommsEmailContactModSlice []CommsEmailContactMod + +func (mods CommsEmailContactModSlice) Apply(ctx context.Context, n *CommsEmailContactTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// CommsEmailContactTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type CommsEmailContactTemplate struct { + Address func() string + Confirmed func() bool + IsSubscribed func() bool + PublicID func() string + + r commsEmailContactR + f *Factory + + alreadyPersisted bool +} + +type commsEmailContactR struct { + DestinationEmailLogs []*commsEmailContactRDestinationEmailLogsR +} + +type commsEmailContactRDestinationEmailLogsR struct { + number int + o *CommsEmailLogTemplate +} + +// Apply mods to the CommsEmailContactTemplate +func (o *CommsEmailContactTemplate) Apply(ctx context.Context, mods ...CommsEmailContactMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.CommsEmailContact +// according to the relationships in the template. Nothing is inserted into the db +func (t CommsEmailContactTemplate) setModelRels(o *models.CommsEmailContact) { + if t.r.DestinationEmailLogs != nil { + rel := models.CommsEmailLogSlice{} + for _, r := range t.r.DestinationEmailLogs { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.Destination = o.Address // h2 + rel.R.DestinationEmailContact = o + } + rel = append(rel, related...) + } + o.R.DestinationEmailLogs = rel + } +} + +// BuildSetter returns an *models.CommsEmailContactSetter +// this does nothing with the relationship templates +func (o CommsEmailContactTemplate) BuildSetter() *models.CommsEmailContactSetter { + m := &models.CommsEmailContactSetter{} + + if o.Address != nil { + val := o.Address() + m.Address = omit.From(val) + } + if o.Confirmed != nil { + val := o.Confirmed() + m.Confirmed = omit.From(val) + } + if o.IsSubscribed != nil { + val := o.IsSubscribed() + m.IsSubscribed = omit.From(val) + } + if o.PublicID != nil { + val := o.PublicID() + m.PublicID = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.CommsEmailContactSetter +// this does nothing with the relationship templates +func (o CommsEmailContactTemplate) BuildManySetter(number int) []*models.CommsEmailContactSetter { + m := make([]*models.CommsEmailContactSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.CommsEmailContact +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsEmailContactTemplate.Create +func (o CommsEmailContactTemplate) Build() *models.CommsEmailContact { + m := &models.CommsEmailContact{} + + if o.Address != nil { + m.Address = o.Address() + } + if o.Confirmed != nil { + m.Confirmed = o.Confirmed() + } + if o.IsSubscribed != nil { + m.IsSubscribed = o.IsSubscribed() + } + if o.PublicID != nil { + m.PublicID = o.PublicID() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.CommsEmailContactSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsEmailContactTemplate.CreateMany +func (o CommsEmailContactTemplate) BuildMany(number int) models.CommsEmailContactSlice { + m := make(models.CommsEmailContactSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableCommsEmailContact(m *models.CommsEmailContactSetter) { + if !(m.Address.IsValue()) { + val := random_string(nil) + m.Address = omit.From(val) + } + if !(m.Confirmed.IsValue()) { + val := random_bool(nil) + m.Confirmed = omit.From(val) + } + if !(m.IsSubscribed.IsValue()) { + val := random_bool(nil) + m.IsSubscribed = omit.From(val) + } + if !(m.PublicID.IsValue()) { + val := random_string(nil) + m.PublicID = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.CommsEmailContact +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *CommsEmailContactTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsEmailContact) error { + var err error + + isDestinationEmailLogsDone, _ := commsEmailContactRelDestinationEmailLogsCtx.Value(ctx) + if !isDestinationEmailLogsDone && o.r.DestinationEmailLogs != nil { + ctx = commsEmailContactRelDestinationEmailLogsCtx.WithValue(ctx, true) + for _, r := range o.r.DestinationEmailLogs { + if r.o.alreadyPersisted { + m.R.DestinationEmailLogs = append(m.R.DestinationEmailLogs, r.o.Build()) + } else { + rel0, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachDestinationEmailLogs(ctx, exec, rel0...) + if err != nil { + return err + } + } + } + } + + return err +} + +// Create builds a commsEmailContact and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *CommsEmailContactTemplate) Create(ctx context.Context, exec bob.Executor) (*models.CommsEmailContact, error) { + var err error + opt := o.BuildSetter() + ensureCreatableCommsEmailContact(opt) + + m, err := models.CommsEmailContacts.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a commsEmailContact and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *CommsEmailContactTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.CommsEmailContact { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a commsEmailContact 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 *CommsEmailContactTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.CommsEmailContact { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple commsEmailContacts and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o CommsEmailContactTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.CommsEmailContactSlice, error) { + var err error + m := make(models.CommsEmailContactSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple commsEmailContacts and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o CommsEmailContactTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.CommsEmailContactSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple commsEmailContacts 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 CommsEmailContactTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.CommsEmailContactSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CommsEmailContact has methods that act as mods for the CommsEmailContactTemplate +var CommsEmailContactMods commsEmailContactMods + +type commsEmailContactMods struct{} + +func (m commsEmailContactMods) RandomizeAllColumns(f *faker.Faker) CommsEmailContactMod { + return CommsEmailContactModSlice{ + CommsEmailContactMods.RandomAddress(f), + CommsEmailContactMods.RandomConfirmed(f), + CommsEmailContactMods.RandomIsSubscribed(f), + CommsEmailContactMods.RandomPublicID(f), + } +} + +// Set the model columns to this value +func (m commsEmailContactMods) Address(val string) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.Address = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailContactMods) AddressFunc(f func() string) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.Address = f + }) +} + +// Clear any values for the column +func (m commsEmailContactMods) UnsetAddress() CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.Address = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailContactMods) RandomAddress(f *faker.Faker) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.Address = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailContactMods) Confirmed(val bool) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.Confirmed = func() bool { return val } + }) +} + +// Set the Column from the function +func (m commsEmailContactMods) ConfirmedFunc(f func() bool) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.Confirmed = f + }) +} + +// Clear any values for the column +func (m commsEmailContactMods) UnsetConfirmed() CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.Confirmed = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailContactMods) RandomConfirmed(f *faker.Faker) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.Confirmed = func() bool { + return random_bool(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailContactMods) IsSubscribed(val bool) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.IsSubscribed = func() bool { return val } + }) +} + +// Set the Column from the function +func (m commsEmailContactMods) IsSubscribedFunc(f func() bool) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.IsSubscribed = f + }) +} + +// Clear any values for the column +func (m commsEmailContactMods) UnsetIsSubscribed() CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.IsSubscribed = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailContactMods) RandomIsSubscribed(f *faker.Faker) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.IsSubscribed = func() bool { + return random_bool(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailContactMods) PublicID(val string) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.PublicID = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailContactMods) PublicIDFunc(f func() string) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.PublicID = f + }) +} + +// Clear any values for the column +func (m commsEmailContactMods) UnsetPublicID() CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.PublicID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailContactMods) RandomPublicID(f *faker.Faker) CommsEmailContactMod { + return CommsEmailContactModFunc(func(_ context.Context, o *CommsEmailContactTemplate) { + o.PublicID = func() string { + return random_string(f) + } + }) +} + +func (m commsEmailContactMods) WithParentsCascading() CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + if isDone, _ := commsEmailContactWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = commsEmailContactWithParentsCascadingCtx.WithValue(ctx, true) + }) +} + +func (m commsEmailContactMods) WithDestinationEmailLogs(number int, related *CommsEmailLogTemplate) CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + o.r.DestinationEmailLogs = []*commsEmailContactRDestinationEmailLogsR{{ + number: number, + o: related, + }} + }) +} + +func (m commsEmailContactMods) WithNewDestinationEmailLogs(number int, mods ...CommsEmailLogMod) CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + related := o.f.NewCommsEmailLogWithContext(ctx, mods...) + m.WithDestinationEmailLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsEmailContactMods) AddDestinationEmailLogs(number int, related *CommsEmailLogTemplate) CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + o.r.DestinationEmailLogs = append(o.r.DestinationEmailLogs, &commsEmailContactRDestinationEmailLogsR{ + number: number, + o: related, + }) + }) +} + +func (m commsEmailContactMods) AddNewDestinationEmailLogs(number int, mods ...CommsEmailLogMod) CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + related := o.f.NewCommsEmailLogWithContext(ctx, mods...) + m.AddDestinationEmailLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsEmailContactMods) AddExistingDestinationEmailLogs(existingModels ...*models.CommsEmailLog) CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + for _, em := range existingModels { + o.r.DestinationEmailLogs = append(o.r.DestinationEmailLogs, &commsEmailContactRDestinationEmailLogsR{ + o: o.f.FromExistingCommsEmailLog(em), + }) + } + }) +} + +func (m commsEmailContactMods) WithoutDestinationEmailLogs() CommsEmailContactMod { + return CommsEmailContactModFunc(func(ctx context.Context, o *CommsEmailContactTemplate) { + o.r.DestinationEmailLogs = nil + }) +} diff --git a/db/factory/comms.email_log.bob.go b/db/factory/comms.email_log.bob.go index 07f72bdb..13d3e040 100644 --- a/db/factory/comms.email_log.bob.go +++ b/db/factory/comms.email_log.bob.go @@ -10,9 +10,12 @@ import ( enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" "github.com/stephenafamo/bob" + "github.com/stephenafamo/bob/types/pgtypes" ) type CommsEmailLogMod interface { @@ -36,10 +39,17 @@ func (mods CommsEmailLogModSlice) Apply(ctx context.Context, n *CommsEmailLogTem // CommsEmailLogTemplate is an object representing the database table. // all columns are optional and should be set by mods type CommsEmailLogTemplate struct { - Created func() time.Time - Destination func() string - Source func() string - Type func() enums.CommsMessagetypeemail + ID func() int32 + Created func() time.Time + DeliveryStatus func() string + Destination func() string + PublicID func() string + SentAt func() null.Val[time.Time] + Source func() string + Subject func() string + TemplateID func() null.Val[int32] + TemplateData func() pgtypes.HStore + Type func() enums.CommsMessagetypeemail r commsEmailLogR f *Factory @@ -48,15 +58,15 @@ type CommsEmailLogTemplate struct { } type commsEmailLogR struct { - DestinationEmail *commsEmailLogRDestinationEmailR - SourcePhone *commsEmailLogRSourcePhoneR + DestinationEmailContact *commsEmailLogRDestinationEmailContactR + TemplateEmailTemplate *commsEmailLogRTemplateEmailTemplateR } -type commsEmailLogRDestinationEmailR struct { - o *CommsEmailTemplate +type commsEmailLogRDestinationEmailContactR struct { + o *CommsEmailContactTemplate } -type commsEmailLogRSourcePhoneR struct { - o *CommsPhoneTemplate +type commsEmailLogRTemplateEmailTemplateR struct { + o *CommsEmailTemplateTemplate } // Apply mods to the CommsEmailLogTemplate @@ -69,18 +79,18 @@ func (o *CommsEmailLogTemplate) Apply(ctx context.Context, mods ...CommsEmailLog // setModelRels creates and sets the relationships on *models.CommsEmailLog // according to the relationships in the template. Nothing is inserted into the db func (t CommsEmailLogTemplate) setModelRels(o *models.CommsEmailLog) { - if t.r.DestinationEmail != nil { - rel := t.r.DestinationEmail.o.Build() + if t.r.DestinationEmailContact != nil { + rel := t.r.DestinationEmailContact.o.Build() rel.R.DestinationEmailLogs = append(rel.R.DestinationEmailLogs, o) o.Destination = rel.Address // h2 - o.R.DestinationEmail = rel + o.R.DestinationEmailContact = rel } - if t.r.SourcePhone != nil { - rel := t.r.SourcePhone.o.Build() - rel.R.SourceEmailLogs = append(rel.R.SourceEmailLogs, o) - o.Source = rel.E164 // h2 - o.R.SourcePhone = rel + if t.r.TemplateEmailTemplate != nil { + rel := t.r.TemplateEmailTemplate.o.Build() + rel.R.TemplateEmailLogs = append(rel.R.TemplateEmailLogs, o) + o.TemplateID = null.From(rel.ID) // h2 + o.R.TemplateEmailTemplate = rel } } @@ -89,18 +99,46 @@ func (t CommsEmailLogTemplate) setModelRels(o *models.CommsEmailLog) { func (o CommsEmailLogTemplate) BuildSetter() *models.CommsEmailLogSetter { m := &models.CommsEmailLogSetter{} + if o.ID != nil { + val := o.ID() + m.ID = omit.From(val) + } if o.Created != nil { val := o.Created() m.Created = omit.From(val) } + if o.DeliveryStatus != nil { + val := o.DeliveryStatus() + m.DeliveryStatus = omit.From(val) + } if o.Destination != nil { val := o.Destination() m.Destination = omit.From(val) } + if o.PublicID != nil { + val := o.PublicID() + m.PublicID = omit.From(val) + } + if o.SentAt != nil { + val := o.SentAt() + m.SentAt = omitnull.FromNull(val) + } if o.Source != nil { val := o.Source() m.Source = omit.From(val) } + if o.Subject != nil { + val := o.Subject() + m.Subject = omit.From(val) + } + if o.TemplateID != nil { + val := o.TemplateID() + m.TemplateID = omitnull.FromNull(val) + } + if o.TemplateData != nil { + val := o.TemplateData() + m.TemplateData = omit.From(val) + } if o.Type != nil { val := o.Type() m.Type = omit.From(val) @@ -127,15 +165,36 @@ func (o CommsEmailLogTemplate) BuildManySetter(number int) []*models.CommsEmailL func (o CommsEmailLogTemplate) Build() *models.CommsEmailLog { m := &models.CommsEmailLog{} + if o.ID != nil { + m.ID = o.ID() + } if o.Created != nil { m.Created = o.Created() } + if o.DeliveryStatus != nil { + m.DeliveryStatus = o.DeliveryStatus() + } if o.Destination != nil { m.Destination = o.Destination() } + if o.PublicID != nil { + m.PublicID = o.PublicID() + } + if o.SentAt != nil { + m.SentAt = o.SentAt() + } if o.Source != nil { m.Source = o.Source() } + if o.Subject != nil { + m.Subject = o.Subject() + } + if o.TemplateID != nil { + m.TemplateID = o.TemplateID() + } + if o.TemplateData != nil { + m.TemplateData = o.TemplateData() + } if o.Type != nil { m.Type = o.Type() } @@ -163,14 +222,30 @@ func ensureCreatableCommsEmailLog(m *models.CommsEmailLogSetter) { val := random_time_Time(nil) m.Created = omit.From(val) } + if !(m.DeliveryStatus.IsValue()) { + val := random_string(nil, "16") + m.DeliveryStatus = omit.From(val) + } if !(m.Destination.IsValue()) { val := random_string(nil) m.Destination = omit.From(val) } + if !(m.PublicID.IsValue()) { + val := random_string(nil, "64") + m.PublicID = omit.From(val) + } if !(m.Source.IsValue()) { val := random_string(nil) m.Source = omit.From(val) } + if !(m.Subject.IsValue()) { + val := random_string(nil, "255") + m.Subject = omit.From(val) + } + if !(m.TemplateData.IsValue()) { + val := random_pgtypes_HStore(nil) + m.TemplateData = omit.From(val) + } if !(m.Type.IsValue()) { val := random_enums_CommsMessagetypeemail(nil) m.Type = omit.From(val) @@ -183,6 +258,25 @@ func ensureCreatableCommsEmailLog(m *models.CommsEmailLogSetter) { func (o *CommsEmailLogTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsEmailLog) error { var err error + isTemplateEmailTemplateDone, _ := commsEmailLogRelTemplateEmailTemplateCtx.Value(ctx) + if !isTemplateEmailTemplateDone && o.r.TemplateEmailTemplate != nil { + ctx = commsEmailLogRelTemplateEmailTemplateCtx.WithValue(ctx, true) + if o.r.TemplateEmailTemplate.o.alreadyPersisted { + m.R.TemplateEmailTemplate = o.r.TemplateEmailTemplate.o.Build() + } else { + var rel1 *models.CommsEmailTemplate + rel1, err = o.r.TemplateEmailTemplate.o.Create(ctx, exec) + if err != nil { + return err + } + err = m.AttachTemplateEmailTemplate(ctx, exec, rel1) + if err != nil { + return err + } + } + + } + return err } @@ -193,16 +287,16 @@ func (o *CommsEmailLogTemplate) Create(ctx context.Context, exec bob.Executor) ( opt := o.BuildSetter() ensureCreatableCommsEmailLog(opt) - if o.r.DestinationEmail == nil { - CommsEmailLogMods.WithNewDestinationEmail().Apply(ctx, o) + if o.r.DestinationEmailContact == nil { + CommsEmailLogMods.WithNewDestinationEmailContact().Apply(ctx, o) } - var rel0 *models.CommsEmail + var rel0 *models.CommsEmailContact - if o.r.DestinationEmail.o.alreadyPersisted { - rel0 = o.r.DestinationEmail.o.Build() + if o.r.DestinationEmailContact.o.alreadyPersisted { + rel0 = o.r.DestinationEmailContact.o.Build() } else { - rel0, err = o.r.DestinationEmail.o.Create(ctx, exec) + rel0, err = o.r.DestinationEmailContact.o.Create(ctx, exec) if err != nil { return nil, err } @@ -210,30 +304,12 @@ func (o *CommsEmailLogTemplate) Create(ctx context.Context, exec bob.Executor) ( opt.Destination = omit.From(rel0.Address) - if o.r.SourcePhone == nil { - CommsEmailLogMods.WithNewSourcePhone().Apply(ctx, o) - } - - var rel1 *models.CommsPhone - - if o.r.SourcePhone.o.alreadyPersisted { - rel1 = o.r.SourcePhone.o.Build() - } else { - rel1, err = o.r.SourcePhone.o.Create(ctx, exec) - if err != nil { - return nil, err - } - } - - opt.Source = omit.From(rel1.E164) - m, err := models.CommsEmailLogs.Insert(opt).One(ctx, exec) if err != nil { return nil, err } - m.R.DestinationEmail = rel0 - m.R.SourcePhone = rel1 + m.R.DestinationEmailContact = rel0 if err := o.insertOptRels(ctx, exec, m); err != nil { return nil, err @@ -312,13 +388,51 @@ type commsEmailLogMods struct{} func (m commsEmailLogMods) RandomizeAllColumns(f *faker.Faker) CommsEmailLogMod { return CommsEmailLogModSlice{ + CommsEmailLogMods.RandomID(f), CommsEmailLogMods.RandomCreated(f), + CommsEmailLogMods.RandomDeliveryStatus(f), CommsEmailLogMods.RandomDestination(f), + CommsEmailLogMods.RandomPublicID(f), + CommsEmailLogMods.RandomSentAt(f), CommsEmailLogMods.RandomSource(f), + CommsEmailLogMods.RandomSubject(f), + CommsEmailLogMods.RandomTemplateID(f), + CommsEmailLogMods.RandomTemplateData(f), CommsEmailLogMods.RandomType(f), } } +// Set the model columns to this value +func (m commsEmailLogMods) ID(val int32) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.ID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m commsEmailLogMods) IDFunc(f func() int32) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m commsEmailLogMods) UnsetID() CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + 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 commsEmailLogMods) RandomID(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.ID = func() int32 { + return random_int32(f) + } + }) +} + // Set the model columns to this value func (m commsEmailLogMods) Created(val time.Time) CommsEmailLogMod { return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { @@ -350,6 +464,37 @@ func (m commsEmailLogMods) RandomCreated(f *faker.Faker) CommsEmailLogMod { }) } +// Set the model columns to this value +func (m commsEmailLogMods) DeliveryStatus(val string) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.DeliveryStatus = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailLogMods) DeliveryStatusFunc(f func() string) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.DeliveryStatus = f + }) +} + +// Clear any values for the column +func (m commsEmailLogMods) UnsetDeliveryStatus() CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.DeliveryStatus = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailLogMods) RandomDeliveryStatus(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.DeliveryStatus = func() string { + return random_string(f, "16") + } + }) +} + // Set the model columns to this value func (m commsEmailLogMods) Destination(val string) CommsEmailLogMod { return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { @@ -381,6 +526,90 @@ func (m commsEmailLogMods) RandomDestination(f *faker.Faker) CommsEmailLogMod { }) } +// Set the model columns to this value +func (m commsEmailLogMods) PublicID(val string) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.PublicID = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailLogMods) PublicIDFunc(f func() string) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.PublicID = f + }) +} + +// Clear any values for the column +func (m commsEmailLogMods) UnsetPublicID() CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.PublicID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailLogMods) RandomPublicID(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.PublicID = func() string { + return random_string(f, "64") + } + }) +} + +// Set the model columns to this value +func (m commsEmailLogMods) SentAt(val null.Val[time.Time]) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.SentAt = func() null.Val[time.Time] { return val } + }) +} + +// Set the Column from the function +func (m commsEmailLogMods) SentAtFunc(f func() null.Val[time.Time]) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.SentAt = f + }) +} + +// Clear any values for the column +func (m commsEmailLogMods) UnsetSentAt() CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.SentAt = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m commsEmailLogMods) RandomSentAt(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.SentAt = func() null.Val[time.Time] { + if f == nil { + f = &defaultFaker + } + + val := random_time_Time(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m commsEmailLogMods) RandomSentAtNotNull(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.SentAt = func() null.Val[time.Time] { + if f == nil { + f = &defaultFaker + } + + val := random_time_Time(f) + return null.From(val) + } + }) +} + // Set the model columns to this value func (m commsEmailLogMods) Source(val string) CommsEmailLogMod { return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { @@ -412,6 +641,121 @@ func (m commsEmailLogMods) RandomSource(f *faker.Faker) CommsEmailLogMod { }) } +// Set the model columns to this value +func (m commsEmailLogMods) Subject(val string) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Subject = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailLogMods) SubjectFunc(f func() string) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Subject = f + }) +} + +// Clear any values for the column +func (m commsEmailLogMods) UnsetSubject() CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Subject = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailLogMods) RandomSubject(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.Subject = func() string { + return random_string(f, "255") + } + }) +} + +// Set the model columns to this value +func (m commsEmailLogMods) TemplateID(val null.Val[int32]) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.TemplateID = func() null.Val[int32] { return val } + }) +} + +// Set the Column from the function +func (m commsEmailLogMods) TemplateIDFunc(f func() null.Val[int32]) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.TemplateID = f + }) +} + +// Clear any values for the column +func (m commsEmailLogMods) UnsetTemplateID() CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.TemplateID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m commsEmailLogMods) RandomTemplateID(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.TemplateID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m commsEmailLogMods) RandomTemplateIDNotNull(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.TemplateID = func() null.Val[int32] { + if f == nil { + f = &defaultFaker + } + + val := random_int32(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m commsEmailLogMods) TemplateData(val pgtypes.HStore) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.TemplateData = func() pgtypes.HStore { return val } + }) +} + +// Set the Column from the function +func (m commsEmailLogMods) TemplateDataFunc(f func() pgtypes.HStore) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.TemplateData = f + }) +} + +// Clear any values for the column +func (m commsEmailLogMods) UnsetTemplateData() CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.TemplateData = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailLogMods) RandomTemplateData(f *faker.Faker) CommsEmailLogMod { + return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { + o.TemplateData = func() pgtypes.HStore { + return random_pgtypes_HStore(f) + } + }) +} + // Set the model columns to this value func (m commsEmailLogMods) Type(val enums.CommsMessagetypeemail) CommsEmailLogMod { return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { @@ -451,73 +795,73 @@ func (m commsEmailLogMods) WithParentsCascading() CommsEmailLogMod { ctx = commsEmailLogWithParentsCascadingCtx.WithValue(ctx, true) { - related := o.f.NewCommsEmailWithContext(ctx, CommsEmailMods.WithParentsCascading()) - m.WithDestinationEmail(related).Apply(ctx, o) + related := o.f.NewCommsEmailContactWithContext(ctx, CommsEmailContactMods.WithParentsCascading()) + m.WithDestinationEmailContact(related).Apply(ctx, o) } { - related := o.f.NewCommsPhoneWithContext(ctx, CommsPhoneMods.WithParentsCascading()) - m.WithSourcePhone(related).Apply(ctx, o) + related := o.f.NewCommsEmailTemplateWithContext(ctx, CommsEmailTemplateMods.WithParentsCascading()) + m.WithTemplateEmailTemplate(related).Apply(ctx, o) } }) } -func (m commsEmailLogMods) WithDestinationEmail(rel *CommsEmailTemplate) CommsEmailLogMod { +func (m commsEmailLogMods) WithDestinationEmailContact(rel *CommsEmailContactTemplate) CommsEmailLogMod { return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { - o.r.DestinationEmail = &commsEmailLogRDestinationEmailR{ + o.r.DestinationEmailContact = &commsEmailLogRDestinationEmailContactR{ o: rel, } }) } -func (m commsEmailLogMods) WithNewDestinationEmail(mods ...CommsEmailMod) CommsEmailLogMod { +func (m commsEmailLogMods) WithNewDestinationEmailContact(mods ...CommsEmailContactMod) CommsEmailLogMod { return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { - related := o.f.NewCommsEmailWithContext(ctx, mods...) + related := o.f.NewCommsEmailContactWithContext(ctx, mods...) - m.WithDestinationEmail(related).Apply(ctx, o) + m.WithDestinationEmailContact(related).Apply(ctx, o) }) } -func (m commsEmailLogMods) WithExistingDestinationEmail(em *models.CommsEmail) CommsEmailLogMod { +func (m commsEmailLogMods) WithExistingDestinationEmailContact(em *models.CommsEmailContact) CommsEmailLogMod { return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { - o.r.DestinationEmail = &commsEmailLogRDestinationEmailR{ - o: o.f.FromExistingCommsEmail(em), + o.r.DestinationEmailContact = &commsEmailLogRDestinationEmailContactR{ + o: o.f.FromExistingCommsEmailContact(em), } }) } -func (m commsEmailLogMods) WithoutDestinationEmail() CommsEmailLogMod { +func (m commsEmailLogMods) WithoutDestinationEmailContact() CommsEmailLogMod { return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { - o.r.DestinationEmail = nil + o.r.DestinationEmailContact = nil }) } -func (m commsEmailLogMods) WithSourcePhone(rel *CommsPhoneTemplate) CommsEmailLogMod { +func (m commsEmailLogMods) WithTemplateEmailTemplate(rel *CommsEmailTemplateTemplate) CommsEmailLogMod { return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { - o.r.SourcePhone = &commsEmailLogRSourcePhoneR{ + o.r.TemplateEmailTemplate = &commsEmailLogRTemplateEmailTemplateR{ o: rel, } }) } -func (m commsEmailLogMods) WithNewSourcePhone(mods ...CommsPhoneMod) CommsEmailLogMod { +func (m commsEmailLogMods) WithNewTemplateEmailTemplate(mods ...CommsEmailTemplateMod) CommsEmailLogMod { return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { - related := o.f.NewCommsPhoneWithContext(ctx, mods...) + related := o.f.NewCommsEmailTemplateWithContext(ctx, mods...) - m.WithSourcePhone(related).Apply(ctx, o) + m.WithTemplateEmailTemplate(related).Apply(ctx, o) }) } -func (m commsEmailLogMods) WithExistingSourcePhone(em *models.CommsPhone) CommsEmailLogMod { +func (m commsEmailLogMods) WithExistingTemplateEmailTemplate(em *models.CommsEmailTemplate) CommsEmailLogMod { return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { - o.r.SourcePhone = &commsEmailLogRSourcePhoneR{ - o: o.f.FromExistingCommsPhone(em), + o.r.TemplateEmailTemplate = &commsEmailLogRTemplateEmailTemplateR{ + o: o.f.FromExistingCommsEmailTemplate(em), } }) } -func (m commsEmailLogMods) WithoutSourcePhone() CommsEmailLogMod { +func (m commsEmailLogMods) WithoutTemplateEmailTemplate() CommsEmailLogMod { return CommsEmailLogModFunc(func(ctx context.Context, o *CommsEmailLogTemplate) { - o.r.SourcePhone = nil + o.r.TemplateEmailTemplate = nil }) } diff --git a/db/factory/comms.email_template.bob.go b/db/factory/comms.email_template.bob.go new file mode 100644 index 00000000..dbfdd1f7 --- /dev/null +++ b/db/factory/comms.email_template.bob.go @@ -0,0 +1,672 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "github.com/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type CommsEmailTemplateMod interface { + Apply(context.Context, *CommsEmailTemplateTemplate) +} + +type CommsEmailTemplateModFunc func(context.Context, *CommsEmailTemplateTemplate) + +func (f CommsEmailTemplateModFunc) Apply(ctx context.Context, n *CommsEmailTemplateTemplate) { + f(ctx, n) +} + +type CommsEmailTemplateModSlice []CommsEmailTemplateMod + +func (mods CommsEmailTemplateModSlice) Apply(ctx context.Context, n *CommsEmailTemplateTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// CommsEmailTemplateTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type CommsEmailTemplateTemplate struct { + ContentHTML func() string + ContentTXT func() string + ContentHashHTML func() string + ContentHashTXT func() string + Created func() time.Time + ID func() int32 + Superceded func() null.Val[time.Time] + MessageType func() enums.CommsMessagetypeemail + + r commsEmailTemplateR + f *Factory + + alreadyPersisted bool +} + +type commsEmailTemplateR struct { + TemplateEmailLogs []*commsEmailTemplateRTemplateEmailLogsR +} + +type commsEmailTemplateRTemplateEmailLogsR struct { + number int + o *CommsEmailLogTemplate +} + +// Apply mods to the CommsEmailTemplateTemplate +func (o *CommsEmailTemplateTemplate) Apply(ctx context.Context, mods ...CommsEmailTemplateMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.CommsEmailTemplate +// according to the relationships in the template. Nothing is inserted into the db +func (t CommsEmailTemplateTemplate) setModelRels(o *models.CommsEmailTemplate) { + if t.r.TemplateEmailLogs != nil { + rel := models.CommsEmailLogSlice{} + for _, r := range t.r.TemplateEmailLogs { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.TemplateID = null.From(o.ID) // h2 + rel.R.TemplateEmailTemplate = o + } + rel = append(rel, related...) + } + o.R.TemplateEmailLogs = rel + } +} + +// BuildSetter returns an *models.CommsEmailTemplateSetter +// this does nothing with the relationship templates +func (o CommsEmailTemplateTemplate) BuildSetter() *models.CommsEmailTemplateSetter { + m := &models.CommsEmailTemplateSetter{} + + if o.ContentHTML != nil { + val := o.ContentHTML() + m.ContentHTML = omit.From(val) + } + if o.ContentTXT != nil { + val := o.ContentTXT() + m.ContentTXT = omit.From(val) + } + if o.ContentHashHTML != nil { + val := o.ContentHashHTML() + m.ContentHashHTML = omit.From(val) + } + if o.ContentHashTXT != nil { + val := o.ContentHashTXT() + m.ContentHashTXT = omit.From(val) + } + if o.Created != nil { + val := o.Created() + m.Created = omit.From(val) + } + if o.ID != nil { + val := o.ID() + m.ID = omit.From(val) + } + if o.Superceded != nil { + val := o.Superceded() + m.Superceded = omitnull.FromNull(val) + } + if o.MessageType != nil { + val := o.MessageType() + m.MessageType = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.CommsEmailTemplateSetter +// this does nothing with the relationship templates +func (o CommsEmailTemplateTemplate) BuildManySetter(number int) []*models.CommsEmailTemplateSetter { + m := make([]*models.CommsEmailTemplateSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.CommsEmailTemplate +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsEmailTemplateTemplate.Create +func (o CommsEmailTemplateTemplate) Build() *models.CommsEmailTemplate { + m := &models.CommsEmailTemplate{} + + if o.ContentHTML != nil { + m.ContentHTML = o.ContentHTML() + } + if o.ContentTXT != nil { + m.ContentTXT = o.ContentTXT() + } + if o.ContentHashHTML != nil { + m.ContentHashHTML = o.ContentHashHTML() + } + if o.ContentHashTXT != nil { + m.ContentHashTXT = o.ContentHashTXT() + } + if o.Created != nil { + m.Created = o.Created() + } + if o.ID != nil { + m.ID = o.ID() + } + if o.Superceded != nil { + m.Superceded = o.Superceded() + } + if o.MessageType != nil { + m.MessageType = o.MessageType() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.CommsEmailTemplateSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsEmailTemplateTemplate.CreateMany +func (o CommsEmailTemplateTemplate) BuildMany(number int) models.CommsEmailTemplateSlice { + m := make(models.CommsEmailTemplateSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableCommsEmailTemplate(m *models.CommsEmailTemplateSetter) { + if !(m.ContentHTML.IsValue()) { + val := random_string(nil) + m.ContentHTML = omit.From(val) + } + if !(m.ContentTXT.IsValue()) { + val := random_string(nil) + m.ContentTXT = omit.From(val) + } + if !(m.ContentHashHTML.IsValue()) { + val := random_string(nil, "64") + m.ContentHashHTML = omit.From(val) + } + if !(m.ContentHashTXT.IsValue()) { + val := random_string(nil, "64") + m.ContentHashTXT = omit.From(val) + } + if !(m.Created.IsValue()) { + val := random_time_Time(nil) + m.Created = omit.From(val) + } + if !(m.MessageType.IsValue()) { + val := random_enums_CommsMessagetypeemail(nil) + m.MessageType = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.CommsEmailTemplate +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *CommsEmailTemplateTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsEmailTemplate) error { + var err error + + isTemplateEmailLogsDone, _ := commsEmailTemplateRelTemplateEmailLogsCtx.Value(ctx) + if !isTemplateEmailLogsDone && o.r.TemplateEmailLogs != nil { + ctx = commsEmailTemplateRelTemplateEmailLogsCtx.WithValue(ctx, true) + for _, r := range o.r.TemplateEmailLogs { + if r.o.alreadyPersisted { + m.R.TemplateEmailLogs = append(m.R.TemplateEmailLogs, r.o.Build()) + } else { + rel0, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachTemplateEmailLogs(ctx, exec, rel0...) + if err != nil { + return err + } + } + } + } + + return err +} + +// Create builds a commsEmailTemplate and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *CommsEmailTemplateTemplate) Create(ctx context.Context, exec bob.Executor) (*models.CommsEmailTemplate, error) { + var err error + opt := o.BuildSetter() + ensureCreatableCommsEmailTemplate(opt) + + m, err := models.CommsEmailTemplates.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a commsEmailTemplate and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *CommsEmailTemplateTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.CommsEmailTemplate { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a commsEmailTemplate 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 *CommsEmailTemplateTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.CommsEmailTemplate { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple commsEmailTemplates and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o CommsEmailTemplateTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.CommsEmailTemplateSlice, error) { + var err error + m := make(models.CommsEmailTemplateSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple commsEmailTemplates and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o CommsEmailTemplateTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.CommsEmailTemplateSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple commsEmailTemplates 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 CommsEmailTemplateTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.CommsEmailTemplateSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CommsEmailTemplate has methods that act as mods for the CommsEmailTemplateTemplate +var CommsEmailTemplateMods commsEmailTemplateMods + +type commsEmailTemplateMods struct{} + +func (m commsEmailTemplateMods) RandomizeAllColumns(f *faker.Faker) CommsEmailTemplateMod { + return CommsEmailTemplateModSlice{ + CommsEmailTemplateMods.RandomContentHTML(f), + CommsEmailTemplateMods.RandomContentTXT(f), + CommsEmailTemplateMods.RandomContentHashHTML(f), + CommsEmailTemplateMods.RandomContentHashTXT(f), + CommsEmailTemplateMods.RandomCreated(f), + CommsEmailTemplateMods.RandomID(f), + CommsEmailTemplateMods.RandomSuperceded(f), + CommsEmailTemplateMods.RandomMessageType(f), + } +} + +// Set the model columns to this value +func (m commsEmailTemplateMods) ContentHTML(val string) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHTML = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailTemplateMods) ContentHTMLFunc(f func() string) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHTML = f + }) +} + +// Clear any values for the column +func (m commsEmailTemplateMods) UnsetContentHTML() CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHTML = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailTemplateMods) RandomContentHTML(f *faker.Faker) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHTML = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailTemplateMods) ContentTXT(val string) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentTXT = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailTemplateMods) ContentTXTFunc(f func() string) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentTXT = f + }) +} + +// Clear any values for the column +func (m commsEmailTemplateMods) UnsetContentTXT() CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentTXT = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailTemplateMods) RandomContentTXT(f *faker.Faker) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentTXT = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailTemplateMods) ContentHashHTML(val string) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHashHTML = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailTemplateMods) ContentHashHTMLFunc(f func() string) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHashHTML = f + }) +} + +// Clear any values for the column +func (m commsEmailTemplateMods) UnsetContentHashHTML() CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHashHTML = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailTemplateMods) RandomContentHashHTML(f *faker.Faker) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHashHTML = func() string { + return random_string(f, "64") + } + }) +} + +// Set the model columns to this value +func (m commsEmailTemplateMods) ContentHashTXT(val string) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHashTXT = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsEmailTemplateMods) ContentHashTXTFunc(f func() string) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHashTXT = f + }) +} + +// Clear any values for the column +func (m commsEmailTemplateMods) UnsetContentHashTXT() CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHashTXT = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailTemplateMods) RandomContentHashTXT(f *faker.Faker) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ContentHashTXT = func() string { + return random_string(f, "64") + } + }) +} + +// Set the model columns to this value +func (m commsEmailTemplateMods) Created(val time.Time) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.Created = func() time.Time { return val } + }) +} + +// Set the Column from the function +func (m commsEmailTemplateMods) CreatedFunc(f func() time.Time) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.Created = f + }) +} + +// Clear any values for the column +func (m commsEmailTemplateMods) UnsetCreated() CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.Created = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailTemplateMods) RandomCreated(f *faker.Faker) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.Created = func() time.Time { + return random_time_Time(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailTemplateMods) ID(val int32) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m commsEmailTemplateMods) IDFunc(f func() int32) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m commsEmailTemplateMods) UnsetID() CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + 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 commsEmailTemplateMods) RandomID(f *faker.Faker) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.ID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m commsEmailTemplateMods) Superceded(val null.Val[time.Time]) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.Superceded = func() null.Val[time.Time] { return val } + }) +} + +// Set the Column from the function +func (m commsEmailTemplateMods) SupercededFunc(f func() null.Val[time.Time]) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.Superceded = f + }) +} + +// Clear any values for the column +func (m commsEmailTemplateMods) UnsetSuperceded() CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.Superceded = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m commsEmailTemplateMods) RandomSuperceded(f *faker.Faker) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.Superceded = func() null.Val[time.Time] { + if f == nil { + f = &defaultFaker + } + + val := random_time_Time(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m commsEmailTemplateMods) RandomSupercededNotNull(f *faker.Faker) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.Superceded = func() null.Val[time.Time] { + if f == nil { + f = &defaultFaker + } + + val := random_time_Time(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m commsEmailTemplateMods) MessageType(val enums.CommsMessagetypeemail) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.MessageType = func() enums.CommsMessagetypeemail { return val } + }) +} + +// Set the Column from the function +func (m commsEmailTemplateMods) MessageTypeFunc(f func() enums.CommsMessagetypeemail) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.MessageType = f + }) +} + +// Clear any values for the column +func (m commsEmailTemplateMods) UnsetMessageType() CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.MessageType = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsEmailTemplateMods) RandomMessageType(f *faker.Faker) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(_ context.Context, o *CommsEmailTemplateTemplate) { + o.MessageType = func() enums.CommsMessagetypeemail { + return random_enums_CommsMessagetypeemail(f) + } + }) +} + +func (m commsEmailTemplateMods) WithParentsCascading() CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(ctx context.Context, o *CommsEmailTemplateTemplate) { + if isDone, _ := commsEmailTemplateWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = commsEmailTemplateWithParentsCascadingCtx.WithValue(ctx, true) + }) +} + +func (m commsEmailTemplateMods) WithTemplateEmailLogs(number int, related *CommsEmailLogTemplate) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(ctx context.Context, o *CommsEmailTemplateTemplate) { + o.r.TemplateEmailLogs = []*commsEmailTemplateRTemplateEmailLogsR{{ + number: number, + o: related, + }} + }) +} + +func (m commsEmailTemplateMods) WithNewTemplateEmailLogs(number int, mods ...CommsEmailLogMod) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(ctx context.Context, o *CommsEmailTemplateTemplate) { + related := o.f.NewCommsEmailLogWithContext(ctx, mods...) + m.WithTemplateEmailLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsEmailTemplateMods) AddTemplateEmailLogs(number int, related *CommsEmailLogTemplate) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(ctx context.Context, o *CommsEmailTemplateTemplate) { + o.r.TemplateEmailLogs = append(o.r.TemplateEmailLogs, &commsEmailTemplateRTemplateEmailLogsR{ + number: number, + o: related, + }) + }) +} + +func (m commsEmailTemplateMods) AddNewTemplateEmailLogs(number int, mods ...CommsEmailLogMod) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(ctx context.Context, o *CommsEmailTemplateTemplate) { + related := o.f.NewCommsEmailLogWithContext(ctx, mods...) + m.AddTemplateEmailLogs(number, related).Apply(ctx, o) + }) +} + +func (m commsEmailTemplateMods) AddExistingTemplateEmailLogs(existingModels ...*models.CommsEmailLog) CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(ctx context.Context, o *CommsEmailTemplateTemplate) { + for _, em := range existingModels { + o.r.TemplateEmailLogs = append(o.r.TemplateEmailLogs, &commsEmailTemplateRTemplateEmailLogsR{ + o: o.f.FromExistingCommsEmailLog(em), + }) + } + }) +} + +func (m commsEmailTemplateMods) WithoutTemplateEmailLogs() CommsEmailTemplateMod { + return CommsEmailTemplateModFunc(func(ctx context.Context, o *CommsEmailTemplateTemplate) { + o.r.TemplateEmailLogs = nil + }) +} diff --git a/db/factory/comms.phone.bob.go b/db/factory/comms.phone.bob.go index 26b8eabf..415c6957 100644 --- a/db/factory/comms.phone.bob.go +++ b/db/factory/comms.phone.bob.go @@ -44,15 +44,10 @@ type CommsPhoneTemplate struct { } type commsPhoneR struct { - SourceEmailLogs []*commsPhoneRSourceEmailLogsR DestinationTextLogs []*commsPhoneRDestinationTextLogsR SourceTextLogs []*commsPhoneRSourceTextLogsR } -type commsPhoneRSourceEmailLogsR struct { - number int - o *CommsEmailLogTemplate -} type commsPhoneRDestinationTextLogsR struct { number int o *CommsTextLogTemplate @@ -72,19 +67,6 @@ func (o *CommsPhoneTemplate) Apply(ctx context.Context, mods ...CommsPhoneMod) { // setModelRels creates and sets the relationships on *models.CommsPhone // according to the relationships in the template. Nothing is inserted into the db func (t CommsPhoneTemplate) setModelRels(o *models.CommsPhone) { - if t.r.SourceEmailLogs != nil { - rel := models.CommsEmailLogSlice{} - for _, r := range t.r.SourceEmailLogs { - related := r.o.BuildMany(r.number) - for _, rel := range related { - rel.Source = o.E164 // h2 - rel.R.SourcePhone = o - } - rel = append(rel, related...) - } - o.R.SourceEmailLogs = rel - } - if t.r.DestinationTextLogs != nil { rel := models.CommsTextLogSlice{} for _, r := range t.r.DestinationTextLogs { @@ -189,26 +171,6 @@ func ensureCreatableCommsPhone(m *models.CommsPhoneSetter) { func (o *CommsPhoneTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsPhone) error { var err error - isSourceEmailLogsDone, _ := commsPhoneRelSourceEmailLogsCtx.Value(ctx) - if !isSourceEmailLogsDone && o.r.SourceEmailLogs != nil { - ctx = commsPhoneRelSourceEmailLogsCtx.WithValue(ctx, true) - for _, r := range o.r.SourceEmailLogs { - if r.o.alreadyPersisted { - m.R.SourceEmailLogs = append(m.R.SourceEmailLogs, r.o.Build()) - } else { - rel0, err := r.o.CreateMany(ctx, exec, r.number) - if err != nil { - return err - } - - err = m.AttachSourceEmailLogs(ctx, exec, rel0...) - if err != nil { - return err - } - } - } - } - isDestinationTextLogsDone, _ := commsPhoneRelDestinationTextLogsCtx.Value(ctx) if !isDestinationTextLogsDone && o.r.DestinationTextLogs != nil { ctx = commsPhoneRelDestinationTextLogsCtx.WithValue(ctx, true) @@ -216,12 +178,12 @@ func (o *CommsPhoneTemplate) insertOptRels(ctx context.Context, exec bob.Executo if r.o.alreadyPersisted { m.R.DestinationTextLogs = append(m.R.DestinationTextLogs, r.o.Build()) } else { - rel1, err := r.o.CreateMany(ctx, exec, r.number) + rel0, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachDestinationTextLogs(ctx, exec, rel1...) + err = m.AttachDestinationTextLogs(ctx, exec, rel0...) if err != nil { return err } @@ -236,12 +198,12 @@ func (o *CommsPhoneTemplate) insertOptRels(ctx context.Context, exec bob.Executo if r.o.alreadyPersisted { m.R.SourceTextLogs = append(m.R.SourceTextLogs, r.o.Build()) } else { - rel2, err := r.o.CreateMany(ctx, exec, r.number) + rel1, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachSourceTextLogs(ctx, exec, rel2...) + err = m.AttachSourceTextLogs(ctx, exec, rel1...) if err != nil { return err } @@ -417,54 +379,6 @@ func (m commsPhoneMods) WithParentsCascading() CommsPhoneMod { }) } -func (m commsPhoneMods) WithSourceEmailLogs(number int, related *CommsEmailLogTemplate) CommsPhoneMod { - return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - o.r.SourceEmailLogs = []*commsPhoneRSourceEmailLogsR{{ - number: number, - o: related, - }} - }) -} - -func (m commsPhoneMods) WithNewSourceEmailLogs(number int, mods ...CommsEmailLogMod) CommsPhoneMod { - return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - related := o.f.NewCommsEmailLogWithContext(ctx, mods...) - m.WithSourceEmailLogs(number, related).Apply(ctx, o) - }) -} - -func (m commsPhoneMods) AddSourceEmailLogs(number int, related *CommsEmailLogTemplate) CommsPhoneMod { - return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - o.r.SourceEmailLogs = append(o.r.SourceEmailLogs, &commsPhoneRSourceEmailLogsR{ - number: number, - o: related, - }) - }) -} - -func (m commsPhoneMods) AddNewSourceEmailLogs(number int, mods ...CommsEmailLogMod) CommsPhoneMod { - return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - related := o.f.NewCommsEmailLogWithContext(ctx, mods...) - m.AddSourceEmailLogs(number, related).Apply(ctx, o) - }) -} - -func (m commsPhoneMods) AddExistingSourceEmailLogs(existingModels ...*models.CommsEmailLog) CommsPhoneMod { - return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - for _, em := range existingModels { - o.r.SourceEmailLogs = append(o.r.SourceEmailLogs, &commsPhoneRSourceEmailLogsR{ - o: o.f.FromExistingCommsEmailLog(em), - }) - } - }) -} - -func (m commsPhoneMods) WithoutSourceEmailLogs() CommsPhoneMod { - return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { - o.r.SourceEmailLogs = nil - }) -} - func (m commsPhoneMods) WithDestinationTextLogs(number int, related *CommsTextLogTemplate) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { o.r.DestinationTextLogs = []*commsPhoneRDestinationTextLogsR{{ diff --git a/db/migrations/00039_email_template.sql b/db/migrations/00039_email_template.sql new file mode 100644 index 00000000..ba42ffbe --- /dev/null +++ b/db/migrations/00039_email_template.sql @@ -0,0 +1,44 @@ +-- +goose Up +-- CREATE EXTENSION IF NOT EXISTS hstore; +ALTER TABLE comms.email RENAME TO email_contact; +ALTER TABLE comms.email_contact ADD COLUMN public_id TEXT; +UPDATE comms.email_contact SET public_id = ''; +ALTER TABLE comms.email_contact ALTER COLUMN public_id SET NOT NULL; +CREATE TABLE comms.email_template ( + content_html TEXT NOT NULL, + content_txt TEXT NOT NULL, + content_hash_html VARCHAR(64) NOT NULL, + content_hash_txt VARCHAR(64) NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + id SERIAL NOT NULL, + superceded TIMESTAMP WITHOUT TIME ZONE, + message_type comms.MessageTypeEmail NOT NULL, + PRIMARY KEY (id) +); +DROP TABLE comms.email_log; +CREATE TABLE comms.email_log ( + id SERIAL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + delivery_status VARCHAR(16) NOT NULL, + destination TEXT NOT NULL REFERENCES comms.email_contact(address), + public_id VARCHAR(64) NOT NULL, + sent_at TIMESTAMP WITHOUT TIME ZONE, + source TEXT NOT NULL, + subject VARCHAR(255) NOT NULL, + template_id INTEGER REFERENCES comms.email_template(id), + template_data HSTORE NOT NULL, + type comms.MessageTypeEmail NOT NULL, + PRIMARY KEY (id) +); +-- +goose Down +DROP TABLE comms.email_log; +CREATE TABLE comms.email_log ( + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + destination TEXT NOT NULL REFERENCES comms.email_contact(address), + source TEXT NOT NULL REFERENCES comms.phone(e164), + type comms.MessageTypeEmail NOT NULL, + PRIMARY KEY(destination, source, type) +); +DROP TABLE comms.email_template; +ALTER TABLE comms.email_contact DROP COLUMN public_id; +ALTER TABLE comms.email_contact RENAME TO email; diff --git a/db/models/bob_counts.bob.go b/db/models/bob_counts.bob.go index 2deaf5bb..41648f3a 100644 --- a/db/models/bob_counts.bob.go +++ b/db/models/bob_counts.bob.go @@ -21,58 +21,62 @@ var ( ) type preloadCounts struct { - ArcgisUser arcgisuserCountPreloader - CommsEmail commsEmailCountPreloader - CommsPhone commsPhoneCountPreloader - NoteAudio noteAudioCountPreloader - NoteImage noteImageCountPreloader - Organization organizationCountPreloader - PublicreportImage publicreportImageCountPreloader - PublicreportPool publicreportPoolCountPreloader - PublicreportQuick publicreportQuickCountPreloader - User userCountPreloader + ArcgisUser arcgisuserCountPreloader + CommsEmailContact commsEmailContactCountPreloader + CommsEmailTemplate commsEmailTemplateCountPreloader + CommsPhone commsPhoneCountPreloader + NoteAudio noteAudioCountPreloader + NoteImage noteImageCountPreloader + Organization organizationCountPreloader + PublicreportImage publicreportImageCountPreloader + PublicreportPool publicreportPoolCountPreloader + PublicreportQuick publicreportQuickCountPreloader + User userCountPreloader } func getPreloadCount() preloadCounts { return preloadCounts{ - ArcgisUser: buildArcgisUserCountPreloader(), - CommsEmail: buildCommsEmailCountPreloader(), - CommsPhone: buildCommsPhoneCountPreloader(), - NoteAudio: buildNoteAudioCountPreloader(), - NoteImage: buildNoteImageCountPreloader(), - Organization: buildOrganizationCountPreloader(), - PublicreportImage: buildPublicreportImageCountPreloader(), - PublicreportPool: buildPublicreportPoolCountPreloader(), - PublicreportQuick: buildPublicreportQuickCountPreloader(), - User: buildUserCountPreloader(), + ArcgisUser: buildArcgisUserCountPreloader(), + CommsEmailContact: buildCommsEmailContactCountPreloader(), + CommsEmailTemplate: buildCommsEmailTemplateCountPreloader(), + CommsPhone: buildCommsPhoneCountPreloader(), + NoteAudio: buildNoteAudioCountPreloader(), + NoteImage: buildNoteImageCountPreloader(), + Organization: buildOrganizationCountPreloader(), + PublicreportImage: buildPublicreportImageCountPreloader(), + PublicreportPool: buildPublicreportPoolCountPreloader(), + PublicreportQuick: buildPublicreportQuickCountPreloader(), + User: buildUserCountPreloader(), } } type thenLoadCounts[Q orm.Loadable] struct { - ArcgisUser arcgisuserCountThenLoader[Q] - CommsEmail commsEmailCountThenLoader[Q] - CommsPhone commsPhoneCountThenLoader[Q] - NoteAudio noteAudioCountThenLoader[Q] - NoteImage noteImageCountThenLoader[Q] - Organization organizationCountThenLoader[Q] - PublicreportImage publicreportImageCountThenLoader[Q] - PublicreportPool publicreportPoolCountThenLoader[Q] - PublicreportQuick publicreportQuickCountThenLoader[Q] - User userCountThenLoader[Q] + ArcgisUser arcgisuserCountThenLoader[Q] + CommsEmailContact commsEmailContactCountThenLoader[Q] + CommsEmailTemplate commsEmailTemplateCountThenLoader[Q] + CommsPhone commsPhoneCountThenLoader[Q] + NoteAudio noteAudioCountThenLoader[Q] + NoteImage noteImageCountThenLoader[Q] + Organization organizationCountThenLoader[Q] + PublicreportImage publicreportImageCountThenLoader[Q] + PublicreportPool publicreportPoolCountThenLoader[Q] + PublicreportQuick publicreportQuickCountThenLoader[Q] + User userCountThenLoader[Q] } func getThenLoadCount[Q orm.Loadable]() thenLoadCounts[Q] { return thenLoadCounts[Q]{ - ArcgisUser: buildArcgisUserCountThenLoader[Q](), - CommsEmail: buildCommsEmailCountThenLoader[Q](), - CommsPhone: buildCommsPhoneCountThenLoader[Q](), - NoteAudio: buildNoteAudioCountThenLoader[Q](), - NoteImage: buildNoteImageCountThenLoader[Q](), - Organization: buildOrganizationCountThenLoader[Q](), - PublicreportImage: buildPublicreportImageCountThenLoader[Q](), - PublicreportPool: buildPublicreportPoolCountThenLoader[Q](), - PublicreportQuick: buildPublicreportQuickCountThenLoader[Q](), - User: buildUserCountThenLoader[Q](), + ArcgisUser: buildArcgisUserCountThenLoader[Q](), + CommsEmailContact: buildCommsEmailContactCountThenLoader[Q](), + CommsEmailTemplate: buildCommsEmailTemplateCountThenLoader[Q](), + CommsPhone: buildCommsPhoneCountThenLoader[Q](), + NoteAudio: buildNoteAudioCountThenLoader[Q](), + NoteImage: buildNoteImageCountThenLoader[Q](), + Organization: buildOrganizationCountThenLoader[Q](), + PublicreportImage: buildPublicreportImageCountThenLoader[Q](), + PublicreportPool: buildPublicreportPoolCountThenLoader[Q](), + PublicreportQuick: buildPublicreportQuickCountThenLoader[Q](), + User: buildUserCountThenLoader[Q](), } } diff --git a/db/models/bob_joins.bob.go b/db/models/bob_joins.bob.go index 4455b426..e6ca18af 100644 --- a/db/models/bob_joins.bob.go +++ b/db/models/bob_joins.bob.go @@ -34,8 +34,9 @@ func (j joinSet[Q]) AliasedAs(alias string) joinSet[Q] { type joins[Q dialect.Joinable] struct { ArcgisUsers joinSet[arcgisuserJoins[Q]] ArcgisUserPrivileges joinSet[arcgisUserPrivilegeJoins[Q]] - CommsEmails joinSet[commsEmailJoins[Q]] + CommsEmailContacts joinSet[commsEmailContactJoins[Q]] CommsEmailLogs joinSet[commsEmailLogJoins[Q]] + CommsEmailTemplates joinSet[commsEmailTemplateJoins[Q]] CommsPhones joinSet[commsPhoneJoins[Q]] CommsTextLogs joinSet[commsTextLogJoins[Q]] FieldseekerContainerrelates joinSet[fieldseekerContainerrelateJoins[Q]] @@ -99,8 +100,9 @@ func getJoins[Q dialect.Joinable]() joins[Q] { return joins[Q]{ ArcgisUsers: buildJoinSet[arcgisuserJoins[Q]](ArcgisUsers.Columns, buildArcgisUserJoins), ArcgisUserPrivileges: buildJoinSet[arcgisUserPrivilegeJoins[Q]](ArcgisUserPrivileges.Columns, buildArcgisUserPrivilegeJoins), - CommsEmails: buildJoinSet[commsEmailJoins[Q]](CommsEmails.Columns, buildCommsEmailJoins), + CommsEmailContacts: buildJoinSet[commsEmailContactJoins[Q]](CommsEmailContacts.Columns, buildCommsEmailContactJoins), CommsEmailLogs: buildJoinSet[commsEmailLogJoins[Q]](CommsEmailLogs.Columns, buildCommsEmailLogJoins), + CommsEmailTemplates: buildJoinSet[commsEmailTemplateJoins[Q]](CommsEmailTemplates.Columns, buildCommsEmailTemplateJoins), CommsPhones: buildJoinSet[commsPhoneJoins[Q]](CommsPhones.Columns, buildCommsPhoneJoins), CommsTextLogs: buildJoinSet[commsTextLogJoins[Q]](CommsTextLogs.Columns, buildCommsTextLogJoins), FieldseekerContainerrelates: buildJoinSet[fieldseekerContainerrelateJoins[Q]](FieldseekerContainerrelates.Columns, buildFieldseekerContainerrelateJoins), diff --git a/db/models/bob_loaders.bob.go b/db/models/bob_loaders.bob.go index 1b3475de..aff45d78 100644 --- a/db/models/bob_loaders.bob.go +++ b/db/models/bob_loaders.bob.go @@ -19,8 +19,9 @@ var Preload = getPreloaders() type preloaders struct { ArcgisUser arcgisuserPreloader ArcgisUserPrivilege arcgisUserPrivilegePreloader - CommsEmail commsEmailPreloader + CommsEmailContact commsEmailContactPreloader CommsEmailLog commsEmailLogPreloader + CommsEmailTemplate commsEmailTemplatePreloader CommsPhone commsPhonePreloader CommsTextLog commsTextLogPreloader FieldseekerContainerrelate fieldseekerContainerrelatePreloader @@ -76,8 +77,9 @@ func getPreloaders() preloaders { return preloaders{ ArcgisUser: buildArcgisUserPreloader(), ArcgisUserPrivilege: buildArcgisUserPrivilegePreloader(), - CommsEmail: buildCommsEmailPreloader(), + CommsEmailContact: buildCommsEmailContactPreloader(), CommsEmailLog: buildCommsEmailLogPreloader(), + CommsEmailTemplate: buildCommsEmailTemplatePreloader(), CommsPhone: buildCommsPhonePreloader(), CommsTextLog: buildCommsTextLogPreloader(), FieldseekerContainerrelate: buildFieldseekerContainerrelatePreloader(), @@ -139,8 +141,9 @@ var ( type thenLoaders[Q orm.Loadable] struct { ArcgisUser arcgisuserThenLoader[Q] ArcgisUserPrivilege arcgisUserPrivilegeThenLoader[Q] - CommsEmail commsEmailThenLoader[Q] + CommsEmailContact commsEmailContactThenLoader[Q] CommsEmailLog commsEmailLogThenLoader[Q] + CommsEmailTemplate commsEmailTemplateThenLoader[Q] CommsPhone commsPhoneThenLoader[Q] CommsTextLog commsTextLogThenLoader[Q] FieldseekerContainerrelate fieldseekerContainerrelateThenLoader[Q] @@ -196,8 +199,9 @@ func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] { return thenLoaders[Q]{ ArcgisUser: buildArcgisUserThenLoader[Q](), ArcgisUserPrivilege: buildArcgisUserPrivilegeThenLoader[Q](), - CommsEmail: buildCommsEmailThenLoader[Q](), + CommsEmailContact: buildCommsEmailContactThenLoader[Q](), CommsEmailLog: buildCommsEmailLogThenLoader[Q](), + CommsEmailTemplate: buildCommsEmailTemplateThenLoader[Q](), CommsPhone: buildCommsPhoneThenLoader[Q](), CommsTextLog: buildCommsTextLogThenLoader[Q](), FieldseekerContainerrelate: buildFieldseekerContainerrelateThenLoader[Q](), diff --git a/db/models/bob_where.bob.go b/db/models/bob_where.bob.go index 9ce4788e..fad47793 100644 --- a/db/models/bob_where.bob.go +++ b/db/models/bob_where.bob.go @@ -19,8 +19,9 @@ var ( func Where[Q psql.Filterable]() struct { ArcgisUsers arcgisuserWhere[Q] ArcgisUserPrivileges arcgisUserPrivilegeWhere[Q] - CommsEmails commsEmailWhere[Q] + CommsEmailContacts commsEmailContactWhere[Q] CommsEmailLogs commsEmailLogWhere[Q] + CommsEmailTemplates commsEmailTemplateWhere[Q] CommsPhones commsPhoneWhere[Q] CommsTextLogs commsTextLogWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] @@ -82,8 +83,9 @@ func Where[Q psql.Filterable]() struct { return struct { ArcgisUsers arcgisuserWhere[Q] ArcgisUserPrivileges arcgisUserPrivilegeWhere[Q] - CommsEmails commsEmailWhere[Q] + CommsEmailContacts commsEmailContactWhere[Q] CommsEmailLogs commsEmailLogWhere[Q] + CommsEmailTemplates commsEmailTemplateWhere[Q] CommsPhones commsPhoneWhere[Q] CommsTextLogs commsTextLogWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] @@ -144,8 +146,9 @@ func Where[Q psql.Filterable]() struct { }{ ArcgisUsers: buildArcgisUserWhere[Q](ArcgisUsers.Columns), ArcgisUserPrivileges: buildArcgisUserPrivilegeWhere[Q](ArcgisUserPrivileges.Columns), - CommsEmails: buildCommsEmailWhere[Q](CommsEmails.Columns), + CommsEmailContacts: buildCommsEmailContactWhere[Q](CommsEmailContacts.Columns), CommsEmailLogs: buildCommsEmailLogWhere[Q](CommsEmailLogs.Columns), + CommsEmailTemplates: buildCommsEmailTemplateWhere[Q](CommsEmailTemplates.Columns), CommsPhones: buildCommsPhoneWhere[Q](CommsPhones.Columns), CommsTextLogs: buildCommsTextLogWhere[Q](CommsTextLogs.Columns), FieldseekerContainerrelates: buildFieldseekerContainerrelateWhere[Q](FieldseekerContainerrelates.Columns), diff --git a/db/models/comms.email.bob.go b/db/models/comms.email.bob.go deleted file mode 100644 index 3e05fefb..00000000 --- a/db/models/comms.email.bob.go +++ /dev/null @@ -1,737 +0,0 @@ -// Code generated by BobGen psql v0.42.1. 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/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" -) - -// CommsEmail is an object representing the database table. -type CommsEmail struct { - Address string `db:"address,pk" ` - Confirmed bool `db:"confirmed" ` - IsSubscribed bool `db:"is_subscribed" ` - - R commsEmailR `db:"-" ` - - C commsEmailC `db:"-" ` -} - -// CommsEmailSlice is an alias for a slice of pointers to CommsEmail. -// This should almost always be used instead of []*CommsEmail. -type CommsEmailSlice []*CommsEmail - -// CommsEmails contains methods to work with the email table -var CommsEmails = psql.NewTablex[*CommsEmail, CommsEmailSlice, *CommsEmailSetter]("comms", "email", buildCommsEmailColumns("comms.email")) - -// CommsEmailsQuery is a query on the email table -type CommsEmailsQuery = *psql.ViewQuery[*CommsEmail, CommsEmailSlice] - -// commsEmailR is where relationships are stored. -type commsEmailR struct { - DestinationEmailLogs CommsEmailLogSlice // comms.email_log.email_log_destination_fkey -} - -func buildCommsEmailColumns(alias string) commsEmailColumns { - return commsEmailColumns{ - ColumnsExpr: expr.NewColumnsExpr( - "address", "confirmed", "is_subscribed", - ).WithParent("comms.email"), - tableAlias: alias, - Address: psql.Quote(alias, "address"), - Confirmed: psql.Quote(alias, "confirmed"), - IsSubscribed: psql.Quote(alias, "is_subscribed"), - } -} - -type commsEmailColumns struct { - expr.ColumnsExpr - tableAlias string - Address psql.Expression - Confirmed psql.Expression - IsSubscribed psql.Expression -} - -func (c commsEmailColumns) Alias() string { - return c.tableAlias -} - -func (commsEmailColumns) AliasedAs(alias string) commsEmailColumns { - return buildCommsEmailColumns(alias) -} - -// CommsEmailSetter is used for insert/upsert/update operations -// All values are optional, and do not have to be set -// Generated columns are not included -type CommsEmailSetter struct { - Address omit.Val[string] `db:"address,pk" ` - Confirmed omit.Val[bool] `db:"confirmed" ` - IsSubscribed omit.Val[bool] `db:"is_subscribed" ` -} - -func (s CommsEmailSetter) SetColumns() []string { - vals := make([]string, 0, 3) - if s.Address.IsValue() { - vals = append(vals, "address") - } - if s.Confirmed.IsValue() { - vals = append(vals, "confirmed") - } - if s.IsSubscribed.IsValue() { - vals = append(vals, "is_subscribed") - } - return vals -} - -func (s CommsEmailSetter) Overwrite(t *CommsEmail) { - if s.Address.IsValue() { - t.Address = s.Address.MustGet() - } - if s.Confirmed.IsValue() { - t.Confirmed = s.Confirmed.MustGet() - } - if s.IsSubscribed.IsValue() { - t.IsSubscribed = s.IsSubscribed.MustGet() - } -} - -func (s *CommsEmailSetter) Apply(q *dialect.InsertQuery) { - q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { - return CommsEmails.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, 3) - if s.Address.IsValue() { - vals[0] = psql.Arg(s.Address.MustGet()) - } else { - vals[0] = psql.Raw("DEFAULT") - } - - if s.Confirmed.IsValue() { - vals[1] = psql.Arg(s.Confirmed.MustGet()) - } else { - vals[1] = psql.Raw("DEFAULT") - } - - if s.IsSubscribed.IsValue() { - vals[2] = psql.Arg(s.IsSubscribed.MustGet()) - } else { - vals[2] = psql.Raw("DEFAULT") - } - - return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") - })) -} - -func (s CommsEmailSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { - return um.Set(s.Expressions()...) -} - -func (s CommsEmailSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 3) - - if s.Address.IsValue() { - exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ - psql.Quote(append(prefix, "address")...), - psql.Arg(s.Address), - }}) - } - - if s.Confirmed.IsValue() { - exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ - psql.Quote(append(prefix, "confirmed")...), - psql.Arg(s.Confirmed), - }}) - } - - if s.IsSubscribed.IsValue() { - exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ - psql.Quote(append(prefix, "is_subscribed")...), - psql.Arg(s.IsSubscribed), - }}) - } - - return exprs -} - -// FindCommsEmail retrieves a single record by primary key -// If cols is empty Find will return all columns. -func FindCommsEmail(ctx context.Context, exec bob.Executor, AddressPK string, cols ...string) (*CommsEmail, error) { - if len(cols) == 0 { - return CommsEmails.Query( - sm.Where(CommsEmails.Columns.Address.EQ(psql.Arg(AddressPK))), - ).One(ctx, exec) - } - - return CommsEmails.Query( - sm.Where(CommsEmails.Columns.Address.EQ(psql.Arg(AddressPK))), - sm.Columns(CommsEmails.Columns.Only(cols...)), - ).One(ctx, exec) -} - -// CommsEmailExists checks the presence of a single record by primary key -func CommsEmailExists(ctx context.Context, exec bob.Executor, AddressPK string) (bool, error) { - return CommsEmails.Query( - sm.Where(CommsEmails.Columns.Address.EQ(psql.Arg(AddressPK))), - ).Exists(ctx, exec) -} - -// AfterQueryHook is called after CommsEmail is retrieved from the database -func (o *CommsEmail) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { - var err error - - switch queryType { - case bob.QueryTypeSelect: - ctx, err = CommsEmails.AfterSelectHooks.RunHooks(ctx, exec, CommsEmailSlice{o}) - case bob.QueryTypeInsert: - ctx, err = CommsEmails.AfterInsertHooks.RunHooks(ctx, exec, CommsEmailSlice{o}) - case bob.QueryTypeUpdate: - ctx, err = CommsEmails.AfterUpdateHooks.RunHooks(ctx, exec, CommsEmailSlice{o}) - case bob.QueryTypeDelete: - ctx, err = CommsEmails.AfterDeleteHooks.RunHooks(ctx, exec, CommsEmailSlice{o}) - } - - return err -} - -// primaryKeyVals returns the primary key values of the CommsEmail -func (o *CommsEmail) primaryKeyVals() bob.Expression { - return psql.Arg(o.Address) -} - -func (o *CommsEmail) pkEQ() dialect.Expression { - return psql.Quote("comms.email", "address").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 CommsEmail -func (o *CommsEmail) Update(ctx context.Context, exec bob.Executor, s *CommsEmailSetter) error { - v, err := CommsEmails.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 CommsEmail record with an executor -func (o *CommsEmail) Delete(ctx context.Context, exec bob.Executor) error { - _, err := CommsEmails.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) - return err -} - -// Reload refreshes the CommsEmail using the executor -func (o *CommsEmail) Reload(ctx context.Context, exec bob.Executor) error { - o2, err := CommsEmails.Query( - sm.Where(CommsEmails.Columns.Address.EQ(psql.Arg(o.Address))), - ).One(ctx, exec) - if err != nil { - return err - } - o2.R = o.R - *o = *o2 - - return nil -} - -// AfterQueryHook is called after CommsEmailSlice is retrieved from the database -func (o CommsEmailSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { - var err error - - switch queryType { - case bob.QueryTypeSelect: - ctx, err = CommsEmails.AfterSelectHooks.RunHooks(ctx, exec, o) - case bob.QueryTypeInsert: - ctx, err = CommsEmails.AfterInsertHooks.RunHooks(ctx, exec, o) - case bob.QueryTypeUpdate: - ctx, err = CommsEmails.AfterUpdateHooks.RunHooks(ctx, exec, o) - case bob.QueryTypeDelete: - ctx, err = CommsEmails.AfterDeleteHooks.RunHooks(ctx, exec, o) - } - - return err -} - -func (o CommsEmailSlice) pkIN() dialect.Expression { - if len(o) == 0 { - return psql.Raw("NULL") - } - - return psql.Quote("comms.email", "address").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 CommsEmailSlice) copyMatchingRows(from ...*CommsEmail) { - for i, old := range o { - for _, new := range from { - if new.Address != old.Address { - continue - } - new.R = old.R - o[i] = new - break - } - } -} - -// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" -func (o CommsEmailSlice) 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 CommsEmails.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 *CommsEmail: - o.copyMatchingRows(retrieved) - case []*CommsEmail: - o.copyMatchingRows(retrieved...) - case CommsEmailSlice: - o.copyMatchingRows(retrieved...) - default: - // If the retrieved value is not a CommsEmail or a slice of CommsEmail - // then run the AfterUpdateHooks on the slice - _, err = CommsEmails.AfterUpdateHooks.RunHooks(ctx, exec, o) - } - - return err - })) - - q.AppendWhere(o.pkIN()) - }) -} - -// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" -func (o CommsEmailSlice) 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 CommsEmails.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 *CommsEmail: - o.copyMatchingRows(retrieved) - case []*CommsEmail: - o.copyMatchingRows(retrieved...) - case CommsEmailSlice: - o.copyMatchingRows(retrieved...) - default: - // If the retrieved value is not a CommsEmail or a slice of CommsEmail - // then run the AfterDeleteHooks on the slice - _, err = CommsEmails.AfterDeleteHooks.RunHooks(ctx, exec, o) - } - - return err - })) - - q.AppendWhere(o.pkIN()) - }) -} - -func (o CommsEmailSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals CommsEmailSetter) error { - if len(o) == 0 { - return nil - } - - _, err := CommsEmails.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) - return err -} - -func (o CommsEmailSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { - if len(o) == 0 { - return nil - } - - _, err := CommsEmails.Delete(o.DeleteMod()).Exec(ctx, exec) - return err -} - -func (o CommsEmailSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { - if len(o) == 0 { - return nil - } - - o2, err := CommsEmails.Query(sm.Where(o.pkIN())).All(ctx, exec) - if err != nil { - return err - } - - o.copyMatchingRows(o2...) - - return nil -} - -// DestinationEmailLogs starts a query for related objects on comms.email_log -func (o *CommsEmail) DestinationEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { - return CommsEmailLogs.Query(append(mods, - sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(o.Address))), - )...) -} - -func (os CommsEmailSlice) DestinationEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { - pkAddress := make(pgtypes.Array[string], 0, len(os)) - for _, o := range os { - if o == nil { - continue - } - pkAddress = append(pkAddress, o.Address) - } - PKArgExpr := psql.Select(sm.Columns( - psql.F("unnest", psql.Cast(psql.Arg(pkAddress), "text[]")), - )) - - return CommsEmailLogs.Query(append(mods, - sm.Where(psql.Group(CommsEmailLogs.Columns.Destination).OP("IN", PKArgExpr)), - )...) -} - -func insertCommsEmailDestinationEmailLogs0(ctx context.Context, exec bob.Executor, commsEmailLogs1 []*CommsEmailLogSetter, commsEmail0 *CommsEmail) (CommsEmailLogSlice, error) { - for i := range commsEmailLogs1 { - commsEmailLogs1[i].Destination = omit.From(commsEmail0.Address) - } - - ret, err := CommsEmailLogs.Insert(bob.ToMods(commsEmailLogs1...)).All(ctx, exec) - if err != nil { - return ret, fmt.Errorf("insertCommsEmailDestinationEmailLogs0: %w", err) - } - - return ret, nil -} - -func attachCommsEmailDestinationEmailLogs0(ctx context.Context, exec bob.Executor, count int, commsEmailLogs1 CommsEmailLogSlice, commsEmail0 *CommsEmail) (CommsEmailLogSlice, error) { - setter := &CommsEmailLogSetter{ - Destination: omit.From(commsEmail0.Address), - } - - err := commsEmailLogs1.UpdateAll(ctx, exec, *setter) - if err != nil { - return nil, fmt.Errorf("attachCommsEmailDestinationEmailLogs0: %w", err) - } - - return commsEmailLogs1, nil -} - -func (commsEmail0 *CommsEmail) InsertDestinationEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLogSetter) error { - if len(related) == 0 { - return nil - } - - var err error - - commsEmailLogs1, err := insertCommsEmailDestinationEmailLogs0(ctx, exec, related, commsEmail0) - if err != nil { - return err - } - - commsEmail0.R.DestinationEmailLogs = append(commsEmail0.R.DestinationEmailLogs, commsEmailLogs1...) - - for _, rel := range commsEmailLogs1 { - rel.R.DestinationEmail = commsEmail0 - } - return nil -} - -func (commsEmail0 *CommsEmail) AttachDestinationEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLog) error { - if len(related) == 0 { - return nil - } - - var err error - commsEmailLogs1 := CommsEmailLogSlice(related) - - _, err = attachCommsEmailDestinationEmailLogs0(ctx, exec, len(related), commsEmailLogs1, commsEmail0) - if err != nil { - return err - } - - commsEmail0.R.DestinationEmailLogs = append(commsEmail0.R.DestinationEmailLogs, commsEmailLogs1...) - - for _, rel := range related { - rel.R.DestinationEmail = commsEmail0 - } - - return nil -} - -type commsEmailWhere[Q psql.Filterable] struct { - Address psql.WhereMod[Q, string] - Confirmed psql.WhereMod[Q, bool] - IsSubscribed psql.WhereMod[Q, bool] -} - -func (commsEmailWhere[Q]) AliasedAs(alias string) commsEmailWhere[Q] { - return buildCommsEmailWhere[Q](buildCommsEmailColumns(alias)) -} - -func buildCommsEmailWhere[Q psql.Filterable](cols commsEmailColumns) commsEmailWhere[Q] { - return commsEmailWhere[Q]{ - Address: psql.Where[Q, string](cols.Address), - Confirmed: psql.Where[Q, bool](cols.Confirmed), - IsSubscribed: psql.Where[Q, bool](cols.IsSubscribed), - } -} - -func (o *CommsEmail) Preload(name string, retrieved any) error { - if o == nil { - return nil - } - - switch name { - case "DestinationEmailLogs": - rels, ok := retrieved.(CommsEmailLogSlice) - if !ok { - return fmt.Errorf("commsEmail cannot load %T as %q", retrieved, name) - } - - o.R.DestinationEmailLogs = rels - - for _, rel := range rels { - if rel != nil { - rel.R.DestinationEmail = o - } - } - return nil - default: - return fmt.Errorf("commsEmail has no relationship %q", name) - } -} - -type commsEmailPreloader struct{} - -func buildCommsEmailPreloader() commsEmailPreloader { - return commsEmailPreloader{} -} - -type commsEmailThenLoader[Q orm.Loadable] struct { - DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] -} - -func buildCommsEmailThenLoader[Q orm.Loadable]() commsEmailThenLoader[Q] { - type DestinationEmailLogsLoadInterface interface { - LoadDestinationEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error - } - - return commsEmailThenLoader[Q]{ - DestinationEmailLogs: thenLoadBuilder[Q]( - "DestinationEmailLogs", - func(ctx context.Context, exec bob.Executor, retrieved DestinationEmailLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadDestinationEmailLogs(ctx, exec, mods...) - }, - ), - } -} - -// LoadDestinationEmailLogs loads the commsEmail's DestinationEmailLogs into the .R struct -func (o *CommsEmail) LoadDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { - if o == nil { - return nil - } - - // Reset the relationship - o.R.DestinationEmailLogs = nil - - related, err := o.DestinationEmailLogs(mods...).All(ctx, exec) - if err != nil { - return err - } - - for _, rel := range related { - rel.R.DestinationEmail = o - } - - o.R.DestinationEmailLogs = related - return nil -} - -// LoadDestinationEmailLogs loads the commsEmail's DestinationEmailLogs into the .R struct -func (os CommsEmailSlice) LoadDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { - if len(os) == 0 { - return nil - } - - commsEmailLogs, err := os.DestinationEmailLogs(mods...).All(ctx, exec) - if err != nil { - return err - } - - for _, o := range os { - if o == nil { - continue - } - - o.R.DestinationEmailLogs = nil - } - - for _, o := range os { - if o == nil { - continue - } - - for _, rel := range commsEmailLogs { - - if !(o.Address == rel.Destination) { - continue - } - - rel.R.DestinationEmail = o - - o.R.DestinationEmailLogs = append(o.R.DestinationEmailLogs, rel) - } - } - - return nil -} - -// commsEmailC is where relationship counts are stored. -type commsEmailC struct { - DestinationEmailLogs *int64 -} - -// PreloadCount sets a count in the C struct by name -func (o *CommsEmail) PreloadCount(name string, count int64) error { - if o == nil { - return nil - } - - switch name { - case "DestinationEmailLogs": - o.C.DestinationEmailLogs = &count - } - return nil -} - -type commsEmailCountPreloader struct { - DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader -} - -func buildCommsEmailCountPreloader() commsEmailCountPreloader { - return commsEmailCountPreloader{ - DestinationEmailLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { - return countPreloader[*CommsEmail]("DestinationEmailLogs", func(parent string) bob.Expression { - // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) - if parent == "" { - parent = CommsEmails.Alias() - } - - subqueryMods := []bob.Mod[*dialect.SelectQuery]{ - sm.Columns(psql.Raw("count(*)")), - - sm.From(CommsEmailLogs.Name()), - sm.Where(psql.Quote(CommsEmailLogs.Alias(), "destination").EQ(psql.Quote(parent, "address"))), - } - subqueryMods = append(subqueryMods, mods...) - return psql.Group(psql.Select(subqueryMods...).Expression) - }) - }, - } -} - -type commsEmailCountThenLoader[Q orm.Loadable] struct { - DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] -} - -func buildCommsEmailCountThenLoader[Q orm.Loadable]() commsEmailCountThenLoader[Q] { - type DestinationEmailLogsCountInterface interface { - LoadCountDestinationEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error - } - - return commsEmailCountThenLoader[Q]{ - DestinationEmailLogs: countThenLoadBuilder[Q]( - "DestinationEmailLogs", - func(ctx context.Context, exec bob.Executor, retrieved DestinationEmailLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadCountDestinationEmailLogs(ctx, exec, mods...) - }, - ), - } -} - -// LoadCountDestinationEmailLogs loads the count of DestinationEmailLogs into the C struct -func (o *CommsEmail) LoadCountDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { - if o == nil { - return nil - } - - count, err := o.DestinationEmailLogs(mods...).Count(ctx, exec) - if err != nil { - return err - } - - o.C.DestinationEmailLogs = &count - return nil -} - -// LoadCountDestinationEmailLogs loads the count of DestinationEmailLogs for a slice -func (os CommsEmailSlice) LoadCountDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { - if len(os) == 0 { - return nil - } - - for _, o := range os { - if err := o.LoadCountDestinationEmailLogs(ctx, exec, mods...); err != nil { - return err - } - } - - return nil -} - -type commsEmailJoins[Q dialect.Joinable] struct { - typ string - DestinationEmailLogs modAs[Q, commsEmailLogColumns] -} - -func (j commsEmailJoins[Q]) aliasedAs(alias string) commsEmailJoins[Q] { - return buildCommsEmailJoins[Q](buildCommsEmailColumns(alias), j.typ) -} - -func buildCommsEmailJoins[Q dialect.Joinable](cols commsEmailColumns, typ string) commsEmailJoins[Q] { - return commsEmailJoins[Q]{ - typ: typ, - DestinationEmailLogs: modAs[Q, commsEmailLogColumns]{ - c: CommsEmailLogs.Columns, - f: func(to commsEmailLogColumns) bob.Mod[Q] { - mods := make(mods.QueryMods[Q], 0, 1) - - { - mods = append(mods, dialect.Join[Q](typ, CommsEmailLogs.Name().As(to.Alias())).On( - to.Destination.EQ(cols.Address), - )) - } - - return mods - }, - }, - } -} diff --git a/db/models/comms.email_contact.bob.go b/db/models/comms.email_contact.bob.go new file mode 100644 index 00000000..ca468f6b --- /dev/null +++ b/db/models/comms.email_contact.bob.go @@ -0,0 +1,762 @@ +// Code generated by BobGen psql v0.42.1. 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/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" +) + +// CommsEmailContact is an object representing the database table. +type CommsEmailContact struct { + Address string `db:"address,pk" ` + Confirmed bool `db:"confirmed" ` + IsSubscribed bool `db:"is_subscribed" ` + PublicID string `db:"public_id" ` + + R commsEmailContactR `db:"-" ` + + C commsEmailContactC `db:"-" ` +} + +// CommsEmailContactSlice is an alias for a slice of pointers to CommsEmailContact. +// This should almost always be used instead of []*CommsEmailContact. +type CommsEmailContactSlice []*CommsEmailContact + +// CommsEmailContacts contains methods to work with the email_contact table +var CommsEmailContacts = psql.NewTablex[*CommsEmailContact, CommsEmailContactSlice, *CommsEmailContactSetter]("comms", "email_contact", buildCommsEmailContactColumns("comms.email_contact")) + +// CommsEmailContactsQuery is a query on the email_contact table +type CommsEmailContactsQuery = *psql.ViewQuery[*CommsEmailContact, CommsEmailContactSlice] + +// commsEmailContactR is where relationships are stored. +type commsEmailContactR struct { + DestinationEmailLogs CommsEmailLogSlice // comms.email_log.email_log_destination_fkey +} + +func buildCommsEmailContactColumns(alias string) commsEmailContactColumns { + return commsEmailContactColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "address", "confirmed", "is_subscribed", "public_id", + ).WithParent("comms.email_contact"), + tableAlias: alias, + Address: psql.Quote(alias, "address"), + Confirmed: psql.Quote(alias, "confirmed"), + IsSubscribed: psql.Quote(alias, "is_subscribed"), + PublicID: psql.Quote(alias, "public_id"), + } +} + +type commsEmailContactColumns struct { + expr.ColumnsExpr + tableAlias string + Address psql.Expression + Confirmed psql.Expression + IsSubscribed psql.Expression + PublicID psql.Expression +} + +func (c commsEmailContactColumns) Alias() string { + return c.tableAlias +} + +func (commsEmailContactColumns) AliasedAs(alias string) commsEmailContactColumns { + return buildCommsEmailContactColumns(alias) +} + +// CommsEmailContactSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type CommsEmailContactSetter struct { + Address omit.Val[string] `db:"address,pk" ` + Confirmed omit.Val[bool] `db:"confirmed" ` + IsSubscribed omit.Val[bool] `db:"is_subscribed" ` + PublicID omit.Val[string] `db:"public_id" ` +} + +func (s CommsEmailContactSetter) SetColumns() []string { + vals := make([]string, 0, 4) + if s.Address.IsValue() { + vals = append(vals, "address") + } + if s.Confirmed.IsValue() { + vals = append(vals, "confirmed") + } + if s.IsSubscribed.IsValue() { + vals = append(vals, "is_subscribed") + } + if s.PublicID.IsValue() { + vals = append(vals, "public_id") + } + return vals +} + +func (s CommsEmailContactSetter) Overwrite(t *CommsEmailContact) { + if s.Address.IsValue() { + t.Address = s.Address.MustGet() + } + if s.Confirmed.IsValue() { + t.Confirmed = s.Confirmed.MustGet() + } + if s.IsSubscribed.IsValue() { + t.IsSubscribed = s.IsSubscribed.MustGet() + } + if s.PublicID.IsValue() { + t.PublicID = s.PublicID.MustGet() + } +} + +func (s *CommsEmailContactSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return CommsEmailContacts.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, 4) + if s.Address.IsValue() { + vals[0] = psql.Arg(s.Address.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.Confirmed.IsValue() { + vals[1] = psql.Arg(s.Confirmed.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if s.IsSubscribed.IsValue() { + vals[2] = psql.Arg(s.IsSubscribed.MustGet()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + if s.PublicID.IsValue() { + vals[3] = psql.Arg(s.PublicID.MustGet()) + } else { + vals[3] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s CommsEmailContactSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s CommsEmailContactSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 4) + + if s.Address.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "address")...), + psql.Arg(s.Address), + }}) + } + + if s.Confirmed.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "confirmed")...), + psql.Arg(s.Confirmed), + }}) + } + + if s.IsSubscribed.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "is_subscribed")...), + psql.Arg(s.IsSubscribed), + }}) + } + + if s.PublicID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "public_id")...), + psql.Arg(s.PublicID), + }}) + } + + return exprs +} + +// FindCommsEmailContact retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindCommsEmailContact(ctx context.Context, exec bob.Executor, AddressPK string, cols ...string) (*CommsEmailContact, error) { + if len(cols) == 0 { + return CommsEmailContacts.Query( + sm.Where(CommsEmailContacts.Columns.Address.EQ(psql.Arg(AddressPK))), + ).One(ctx, exec) + } + + return CommsEmailContacts.Query( + sm.Where(CommsEmailContacts.Columns.Address.EQ(psql.Arg(AddressPK))), + sm.Columns(CommsEmailContacts.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// CommsEmailContactExists checks the presence of a single record by primary key +func CommsEmailContactExists(ctx context.Context, exec bob.Executor, AddressPK string) (bool, error) { + return CommsEmailContacts.Query( + sm.Where(CommsEmailContacts.Columns.Address.EQ(psql.Arg(AddressPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after CommsEmailContact is retrieved from the database +func (o *CommsEmailContact) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsEmailContacts.AfterSelectHooks.RunHooks(ctx, exec, CommsEmailContactSlice{o}) + case bob.QueryTypeInsert: + ctx, err = CommsEmailContacts.AfterInsertHooks.RunHooks(ctx, exec, CommsEmailContactSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = CommsEmailContacts.AfterUpdateHooks.RunHooks(ctx, exec, CommsEmailContactSlice{o}) + case bob.QueryTypeDelete: + ctx, err = CommsEmailContacts.AfterDeleteHooks.RunHooks(ctx, exec, CommsEmailContactSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the CommsEmailContact +func (o *CommsEmailContact) primaryKeyVals() bob.Expression { + return psql.Arg(o.Address) +} + +func (o *CommsEmailContact) pkEQ() dialect.Expression { + return psql.Quote("comms.email_contact", "address").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 CommsEmailContact +func (o *CommsEmailContact) Update(ctx context.Context, exec bob.Executor, s *CommsEmailContactSetter) error { + v, err := CommsEmailContacts.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 CommsEmailContact record with an executor +func (o *CommsEmailContact) Delete(ctx context.Context, exec bob.Executor) error { + _, err := CommsEmailContacts.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the CommsEmailContact using the executor +func (o *CommsEmailContact) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := CommsEmailContacts.Query( + sm.Where(CommsEmailContacts.Columns.Address.EQ(psql.Arg(o.Address))), + ).One(ctx, exec) + if err != nil { + return err + } + o2.R = o.R + *o = *o2 + + return nil +} + +// AfterQueryHook is called after CommsEmailContactSlice is retrieved from the database +func (o CommsEmailContactSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsEmailContacts.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = CommsEmailContacts.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = CommsEmailContacts.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = CommsEmailContacts.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o CommsEmailContactSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Quote("comms.email_contact", "address").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 CommsEmailContactSlice) copyMatchingRows(from ...*CommsEmailContact) { + for i, old := range o { + for _, new := range from { + if new.Address != old.Address { + continue + } + new.R = old.R + o[i] = new + break + } + } +} + +// UpdateMod modifies an update query with "WHERE primary_key IN (o...)" +func (o CommsEmailContactSlice) 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 CommsEmailContacts.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 *CommsEmailContact: + o.copyMatchingRows(retrieved) + case []*CommsEmailContact: + o.copyMatchingRows(retrieved...) + case CommsEmailContactSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsEmailContact or a slice of CommsEmailContact + // then run the AfterUpdateHooks on the slice + _, err = CommsEmailContacts.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o CommsEmailContactSlice) 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 CommsEmailContacts.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 *CommsEmailContact: + o.copyMatchingRows(retrieved) + case []*CommsEmailContact: + o.copyMatchingRows(retrieved...) + case CommsEmailContactSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsEmailContact or a slice of CommsEmailContact + // then run the AfterDeleteHooks on the slice + _, err = CommsEmailContacts.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o CommsEmailContactSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals CommsEmailContactSetter) error { + if len(o) == 0 { + return nil + } + + _, err := CommsEmailContacts.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o CommsEmailContactSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := CommsEmailContacts.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o CommsEmailContactSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := CommsEmailContacts.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// DestinationEmailLogs starts a query for related objects on comms.email_log +func (o *CommsEmailContact) DestinationEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { + return CommsEmailLogs.Query(append(mods, + sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(o.Address))), + )...) +} + +func (os CommsEmailContactSlice) DestinationEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { + pkAddress := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkAddress = append(pkAddress, o.Address) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkAddress), "text[]")), + )) + + return CommsEmailLogs.Query(append(mods, + sm.Where(psql.Group(CommsEmailLogs.Columns.Destination).OP("IN", PKArgExpr)), + )...) +} + +func insertCommsEmailContactDestinationEmailLogs0(ctx context.Context, exec bob.Executor, commsEmailLogs1 []*CommsEmailLogSetter, commsEmailContact0 *CommsEmailContact) (CommsEmailLogSlice, error) { + for i := range commsEmailLogs1 { + commsEmailLogs1[i].Destination = omit.From(commsEmailContact0.Address) + } + + ret, err := CommsEmailLogs.Insert(bob.ToMods(commsEmailLogs1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertCommsEmailContactDestinationEmailLogs0: %w", err) + } + + return ret, nil +} + +func attachCommsEmailContactDestinationEmailLogs0(ctx context.Context, exec bob.Executor, count int, commsEmailLogs1 CommsEmailLogSlice, commsEmailContact0 *CommsEmailContact) (CommsEmailLogSlice, error) { + setter := &CommsEmailLogSetter{ + Destination: omit.From(commsEmailContact0.Address), + } + + err := commsEmailLogs1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachCommsEmailContactDestinationEmailLogs0: %w", err) + } + + return commsEmailLogs1, nil +} + +func (commsEmailContact0 *CommsEmailContact) InsertDestinationEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLogSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + commsEmailLogs1, err := insertCommsEmailContactDestinationEmailLogs0(ctx, exec, related, commsEmailContact0) + if err != nil { + return err + } + + commsEmailContact0.R.DestinationEmailLogs = append(commsEmailContact0.R.DestinationEmailLogs, commsEmailLogs1...) + + for _, rel := range commsEmailLogs1 { + rel.R.DestinationEmailContact = commsEmailContact0 + } + return nil +} + +func (commsEmailContact0 *CommsEmailContact) AttachDestinationEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLog) error { + if len(related) == 0 { + return nil + } + + var err error + commsEmailLogs1 := CommsEmailLogSlice(related) + + _, err = attachCommsEmailContactDestinationEmailLogs0(ctx, exec, len(related), commsEmailLogs1, commsEmailContact0) + if err != nil { + return err + } + + commsEmailContact0.R.DestinationEmailLogs = append(commsEmailContact0.R.DestinationEmailLogs, commsEmailLogs1...) + + for _, rel := range related { + rel.R.DestinationEmailContact = commsEmailContact0 + } + + return nil +} + +type commsEmailContactWhere[Q psql.Filterable] struct { + Address psql.WhereMod[Q, string] + Confirmed psql.WhereMod[Q, bool] + IsSubscribed psql.WhereMod[Q, bool] + PublicID psql.WhereMod[Q, string] +} + +func (commsEmailContactWhere[Q]) AliasedAs(alias string) commsEmailContactWhere[Q] { + return buildCommsEmailContactWhere[Q](buildCommsEmailContactColumns(alias)) +} + +func buildCommsEmailContactWhere[Q psql.Filterable](cols commsEmailContactColumns) commsEmailContactWhere[Q] { + return commsEmailContactWhere[Q]{ + Address: psql.Where[Q, string](cols.Address), + Confirmed: psql.Where[Q, bool](cols.Confirmed), + IsSubscribed: psql.Where[Q, bool](cols.IsSubscribed), + PublicID: psql.Where[Q, string](cols.PublicID), + } +} + +func (o *CommsEmailContact) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "DestinationEmailLogs": + rels, ok := retrieved.(CommsEmailLogSlice) + if !ok { + return fmt.Errorf("commsEmailContact cannot load %T as %q", retrieved, name) + } + + o.R.DestinationEmailLogs = rels + + for _, rel := range rels { + if rel != nil { + rel.R.DestinationEmailContact = o + } + } + return nil + default: + return fmt.Errorf("commsEmailContact has no relationship %q", name) + } +} + +type commsEmailContactPreloader struct{} + +func buildCommsEmailContactPreloader() commsEmailContactPreloader { + return commsEmailContactPreloader{} +} + +type commsEmailContactThenLoader[Q orm.Loadable] struct { + DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildCommsEmailContactThenLoader[Q orm.Loadable]() commsEmailContactThenLoader[Q] { + type DestinationEmailLogsLoadInterface interface { + LoadDestinationEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return commsEmailContactThenLoader[Q]{ + DestinationEmailLogs: thenLoadBuilder[Q]( + "DestinationEmailLogs", + func(ctx context.Context, exec bob.Executor, retrieved DestinationEmailLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadDestinationEmailLogs(ctx, exec, mods...) + }, + ), + } +} + +// LoadDestinationEmailLogs loads the commsEmailContact's DestinationEmailLogs into the .R struct +func (o *CommsEmailContact) LoadDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.DestinationEmailLogs = nil + + related, err := o.DestinationEmailLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.DestinationEmailContact = o + } + + o.R.DestinationEmailLogs = related + return nil +} + +// LoadDestinationEmailLogs loads the commsEmailContact's DestinationEmailLogs into the .R struct +func (os CommsEmailContactSlice) LoadDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsEmailLogs, err := os.DestinationEmailLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.DestinationEmailLogs = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsEmailLogs { + + if !(o.Address == rel.Destination) { + continue + } + + rel.R.DestinationEmailContact = o + + o.R.DestinationEmailLogs = append(o.R.DestinationEmailLogs, rel) + } + } + + return nil +} + +// commsEmailContactC is where relationship counts are stored. +type commsEmailContactC struct { + DestinationEmailLogs *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *CommsEmailContact) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "DestinationEmailLogs": + o.C.DestinationEmailLogs = &count + } + return nil +} + +type commsEmailContactCountPreloader struct { + DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildCommsEmailContactCountPreloader() commsEmailContactCountPreloader { + return commsEmailContactCountPreloader{ + DestinationEmailLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*CommsEmailContact]("DestinationEmailLogs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = CommsEmailContacts.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(CommsEmailLogs.Name()), + sm.Where(psql.Quote(CommsEmailLogs.Alias(), "destination").EQ(psql.Quote(parent, "address"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type commsEmailContactCountThenLoader[Q orm.Loadable] struct { + DestinationEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildCommsEmailContactCountThenLoader[Q orm.Loadable]() commsEmailContactCountThenLoader[Q] { + type DestinationEmailLogsCountInterface interface { + LoadCountDestinationEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return commsEmailContactCountThenLoader[Q]{ + DestinationEmailLogs: countThenLoadBuilder[Q]( + "DestinationEmailLogs", + func(ctx context.Context, exec bob.Executor, retrieved DestinationEmailLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountDestinationEmailLogs(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountDestinationEmailLogs loads the count of DestinationEmailLogs into the C struct +func (o *CommsEmailContact) LoadCountDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.DestinationEmailLogs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.DestinationEmailLogs = &count + return nil +} + +// LoadCountDestinationEmailLogs loads the count of DestinationEmailLogs for a slice +func (os CommsEmailContactSlice) LoadCountDestinationEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountDestinationEmailLogs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +type commsEmailContactJoins[Q dialect.Joinable] struct { + typ string + DestinationEmailLogs modAs[Q, commsEmailLogColumns] +} + +func (j commsEmailContactJoins[Q]) aliasedAs(alias string) commsEmailContactJoins[Q] { + return buildCommsEmailContactJoins[Q](buildCommsEmailContactColumns(alias), j.typ) +} + +func buildCommsEmailContactJoins[Q dialect.Joinable](cols commsEmailContactColumns, typ string) commsEmailContactJoins[Q] { + return commsEmailContactJoins[Q]{ + typ: typ, + DestinationEmailLogs: modAs[Q, commsEmailLogColumns]{ + c: CommsEmailLogs.Columns, + f: func(to commsEmailLogColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsEmailLogs.Name().As(to.Alias())).On( + to.Destination.EQ(cols.Address), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/comms.email_log.bob.go b/db/models/comms.email_log.bob.go index fb8edb87..ef59ff78 100644 --- a/db/models/comms.email_log.bob.go +++ b/db/models/comms.email_log.bob.go @@ -10,7 +10,9 @@ import ( "time" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" "github.com/stephenafamo/bob" "github.com/stephenafamo/bob/dialect/psql" "github.com/stephenafamo/bob/dialect/psql/dialect" @@ -25,10 +27,17 @@ import ( // CommsEmailLog is an object representing the database table. type CommsEmailLog struct { - Created time.Time `db:"created" ` - Destination string `db:"destination,pk" ` - Source string `db:"source,pk" ` - Type enums.CommsMessagetypeemail `db:"type,pk" ` + ID int32 `db:"id,pk" ` + Created time.Time `db:"created" ` + DeliveryStatus string `db:"delivery_status" ` + Destination string `db:"destination" ` + PublicID string `db:"public_id" ` + SentAt null.Val[time.Time] `db:"sent_at" ` + Source string `db:"source" ` + Subject string `db:"subject" ` + TemplateID null.Val[int32] `db:"template_id" ` + TemplateData pgtypes.HStore `db:"template_data" ` + Type enums.CommsMessagetypeemail `db:"type" ` R commsEmailLogR `db:"-" ` } @@ -45,30 +54,44 @@ type CommsEmailLogsQuery = *psql.ViewQuery[*CommsEmailLog, CommsEmailLogSlice] // commsEmailLogR is where relationships are stored. type commsEmailLogR struct { - DestinationEmail *CommsEmail // comms.email_log.email_log_destination_fkey - SourcePhone *CommsPhone // comms.email_log.email_log_source_fkey + DestinationEmailContact *CommsEmailContact // comms.email_log.email_log_destination_fkey + TemplateEmailTemplate *CommsEmailTemplate // comms.email_log.email_log_template_id_fkey } func buildCommsEmailLogColumns(alias string) commsEmailLogColumns { return commsEmailLogColumns{ ColumnsExpr: expr.NewColumnsExpr( - "created", "destination", "source", "type", + "id", "created", "delivery_status", "destination", "public_id", "sent_at", "source", "subject", "template_id", "template_data", "type", ).WithParent("comms.email_log"), - tableAlias: alias, - Created: psql.Quote(alias, "created"), - Destination: psql.Quote(alias, "destination"), - Source: psql.Quote(alias, "source"), - Type: psql.Quote(alias, "type"), + tableAlias: alias, + ID: psql.Quote(alias, "id"), + Created: psql.Quote(alias, "created"), + DeliveryStatus: psql.Quote(alias, "delivery_status"), + Destination: psql.Quote(alias, "destination"), + PublicID: psql.Quote(alias, "public_id"), + SentAt: psql.Quote(alias, "sent_at"), + Source: psql.Quote(alias, "source"), + Subject: psql.Quote(alias, "subject"), + TemplateID: psql.Quote(alias, "template_id"), + TemplateData: psql.Quote(alias, "template_data"), + Type: psql.Quote(alias, "type"), } } type commsEmailLogColumns struct { expr.ColumnsExpr - tableAlias string - Created psql.Expression - Destination psql.Expression - Source psql.Expression - Type psql.Expression + tableAlias string + ID psql.Expression + Created psql.Expression + DeliveryStatus psql.Expression + Destination psql.Expression + PublicID psql.Expression + SentAt psql.Expression + Source psql.Expression + Subject psql.Expression + TemplateID psql.Expression + TemplateData psql.Expression + Type psql.Expression } func (c commsEmailLogColumns) Alias() string { @@ -83,23 +106,51 @@ func (commsEmailLogColumns) AliasedAs(alias string) commsEmailLogColumns { // All values are optional, and do not have to be set // Generated columns are not included type CommsEmailLogSetter struct { - Created omit.Val[time.Time] `db:"created" ` - Destination omit.Val[string] `db:"destination,pk" ` - Source omit.Val[string] `db:"source,pk" ` - Type omit.Val[enums.CommsMessagetypeemail] `db:"type,pk" ` + ID omit.Val[int32] `db:"id,pk" ` + Created omit.Val[time.Time] `db:"created" ` + DeliveryStatus omit.Val[string] `db:"delivery_status" ` + Destination omit.Val[string] `db:"destination" ` + PublicID omit.Val[string] `db:"public_id" ` + SentAt omitnull.Val[time.Time] `db:"sent_at" ` + Source omit.Val[string] `db:"source" ` + Subject omit.Val[string] `db:"subject" ` + TemplateID omitnull.Val[int32] `db:"template_id" ` + TemplateData omit.Val[pgtypes.HStore] `db:"template_data" ` + Type omit.Val[enums.CommsMessagetypeemail] `db:"type" ` } func (s CommsEmailLogSetter) SetColumns() []string { - vals := make([]string, 0, 4) + vals := make([]string, 0, 11) + if s.ID.IsValue() { + vals = append(vals, "id") + } if s.Created.IsValue() { vals = append(vals, "created") } + if s.DeliveryStatus.IsValue() { + vals = append(vals, "delivery_status") + } if s.Destination.IsValue() { vals = append(vals, "destination") } + if s.PublicID.IsValue() { + vals = append(vals, "public_id") + } + if !s.SentAt.IsUnset() { + vals = append(vals, "sent_at") + } if s.Source.IsValue() { vals = append(vals, "source") } + if s.Subject.IsValue() { + vals = append(vals, "subject") + } + if !s.TemplateID.IsUnset() { + vals = append(vals, "template_id") + } + if s.TemplateData.IsValue() { + vals = append(vals, "template_data") + } if s.Type.IsValue() { vals = append(vals, "type") } @@ -107,15 +158,36 @@ func (s CommsEmailLogSetter) SetColumns() []string { } func (s CommsEmailLogSetter) Overwrite(t *CommsEmailLog) { + if s.ID.IsValue() { + t.ID = s.ID.MustGet() + } if s.Created.IsValue() { t.Created = s.Created.MustGet() } + if s.DeliveryStatus.IsValue() { + t.DeliveryStatus = s.DeliveryStatus.MustGet() + } if s.Destination.IsValue() { t.Destination = s.Destination.MustGet() } + if s.PublicID.IsValue() { + t.PublicID = s.PublicID.MustGet() + } + if !s.SentAt.IsUnset() { + t.SentAt = s.SentAt.MustGetNull() + } if s.Source.IsValue() { t.Source = s.Source.MustGet() } + if s.Subject.IsValue() { + t.Subject = s.Subject.MustGet() + } + if !s.TemplateID.IsUnset() { + t.TemplateID = s.TemplateID.MustGetNull() + } + if s.TemplateData.IsValue() { + t.TemplateData = s.TemplateData.MustGet() + } if s.Type.IsValue() { t.Type = s.Type.MustGet() } @@ -127,31 +199,73 @@ func (s *CommsEmailLogSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 4) - if s.Created.IsValue() { - vals[0] = psql.Arg(s.Created.MustGet()) + vals := make([]bob.Expression, 11) + if s.ID.IsValue() { + vals[0] = psql.Arg(s.ID.MustGet()) } else { vals[0] = psql.Raw("DEFAULT") } - if s.Destination.IsValue() { - vals[1] = psql.Arg(s.Destination.MustGet()) + if s.Created.IsValue() { + vals[1] = psql.Arg(s.Created.MustGet()) } else { vals[1] = psql.Raw("DEFAULT") } - if s.Source.IsValue() { - vals[2] = psql.Arg(s.Source.MustGet()) + if s.DeliveryStatus.IsValue() { + vals[2] = psql.Arg(s.DeliveryStatus.MustGet()) } else { vals[2] = psql.Raw("DEFAULT") } - if s.Type.IsValue() { - vals[3] = psql.Arg(s.Type.MustGet()) + if s.Destination.IsValue() { + vals[3] = psql.Arg(s.Destination.MustGet()) } else { vals[3] = psql.Raw("DEFAULT") } + if s.PublicID.IsValue() { + vals[4] = psql.Arg(s.PublicID.MustGet()) + } else { + vals[4] = psql.Raw("DEFAULT") + } + + if !s.SentAt.IsUnset() { + vals[5] = psql.Arg(s.SentAt.MustGetNull()) + } else { + vals[5] = psql.Raw("DEFAULT") + } + + if s.Source.IsValue() { + vals[6] = psql.Arg(s.Source.MustGet()) + } else { + vals[6] = psql.Raw("DEFAULT") + } + + if s.Subject.IsValue() { + vals[7] = psql.Arg(s.Subject.MustGet()) + } else { + vals[7] = psql.Raw("DEFAULT") + } + + if !s.TemplateID.IsUnset() { + vals[8] = psql.Arg(s.TemplateID.MustGetNull()) + } else { + vals[8] = psql.Raw("DEFAULT") + } + + if s.TemplateData.IsValue() { + vals[9] = psql.Arg(s.TemplateData.MustGet()) + } else { + vals[9] = psql.Raw("DEFAULT") + } + + if s.Type.IsValue() { + vals[10] = psql.Arg(s.Type.MustGet()) + } else { + vals[10] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -161,7 +275,14 @@ func (s CommsEmailLogSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s CommsEmailLogSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 4) + exprs := make([]bob.Expression, 0, 11) + + if s.ID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "id")...), + psql.Arg(s.ID), + }}) + } if s.Created.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -170,6 +291,13 @@ func (s CommsEmailLogSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if s.DeliveryStatus.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "delivery_status")...), + psql.Arg(s.DeliveryStatus), + }}) + } + if s.Destination.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "destination")...), @@ -177,6 +305,20 @@ func (s CommsEmailLogSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if s.PublicID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "public_id")...), + psql.Arg(s.PublicID), + }}) + } + + if !s.SentAt.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "sent_at")...), + psql.Arg(s.SentAt), + }}) + } + if s.Source.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "source")...), @@ -184,6 +326,27 @@ func (s CommsEmailLogSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if s.Subject.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "subject")...), + psql.Arg(s.Subject), + }}) + } + + if !s.TemplateID.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "template_id")...), + psql.Arg(s.TemplateID), + }}) + } + + if s.TemplateData.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "template_data")...), + psql.Arg(s.TemplateData), + }}) + } + if s.Type.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "type")...), @@ -196,29 +359,23 @@ func (s CommsEmailLogSetter) Expressions(prefix ...string) []bob.Expression { // FindCommsEmailLog retrieves a single record by primary key // If cols is empty Find will return all columns. -func FindCommsEmailLog(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsMessagetypeemail, cols ...string) (*CommsEmailLog, error) { +func FindCommsEmailLog(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*CommsEmailLog, error) { if len(cols) == 0 { return CommsEmailLogs.Query( - sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), - sm.Where(CommsEmailLogs.Columns.Source.EQ(psql.Arg(SourcePK))), - sm.Where(CommsEmailLogs.Columns.Type.EQ(psql.Arg(TypePK))), + sm.Where(CommsEmailLogs.Columns.ID.EQ(psql.Arg(IDPK))), ).One(ctx, exec) } return CommsEmailLogs.Query( - sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), - sm.Where(CommsEmailLogs.Columns.Source.EQ(psql.Arg(SourcePK))), - sm.Where(CommsEmailLogs.Columns.Type.EQ(psql.Arg(TypePK))), + sm.Where(CommsEmailLogs.Columns.ID.EQ(psql.Arg(IDPK))), sm.Columns(CommsEmailLogs.Columns.Only(cols...)), ).One(ctx, exec) } // CommsEmailLogExists checks the presence of a single record by primary key -func CommsEmailLogExists(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsMessagetypeemail) (bool, error) { +func CommsEmailLogExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { return CommsEmailLogs.Query( - sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), - sm.Where(CommsEmailLogs.Columns.Source.EQ(psql.Arg(SourcePK))), - sm.Where(CommsEmailLogs.Columns.Type.EQ(psql.Arg(TypePK))), + sm.Where(CommsEmailLogs.Columns.ID.EQ(psql.Arg(IDPK))), ).Exists(ctx, exec) } @@ -242,15 +399,11 @@ func (o *CommsEmailLog) AfterQueryHook(ctx context.Context, exec bob.Executor, q // primaryKeyVals returns the primary key values of the CommsEmailLog func (o *CommsEmailLog) primaryKeyVals() bob.Expression { - return psql.ArgGroup( - o.Destination, - o.Source, - o.Type, - ) + return psql.Arg(o.ID) } func (o *CommsEmailLog) pkEQ() dialect.Expression { - return psql.Group(psql.Quote("comms.email_log", "destination"), psql.Quote("comms.email_log", "source"), psql.Quote("comms.email_log", "type")).EQ(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return psql.Quote("comms.email_log", "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) })) } @@ -277,9 +430,7 @@ func (o *CommsEmailLog) Delete(ctx context.Context, exec bob.Executor) error { // Reload refreshes the CommsEmailLog using the executor func (o *CommsEmailLog) Reload(ctx context.Context, exec bob.Executor) error { o2, err := CommsEmailLogs.Query( - sm.Where(CommsEmailLogs.Columns.Destination.EQ(psql.Arg(o.Destination))), - sm.Where(CommsEmailLogs.Columns.Source.EQ(psql.Arg(o.Source))), - sm.Where(CommsEmailLogs.Columns.Type.EQ(psql.Arg(o.Type))), + sm.Where(CommsEmailLogs.Columns.ID.EQ(psql.Arg(o.ID))), ).One(ctx, exec) if err != nil { return err @@ -313,7 +464,7 @@ func (o CommsEmailLogSlice) pkIN() dialect.Expression { return psql.Raw("NULL") } - return psql.Group(psql.Quote("comms.email_log", "destination"), psql.Quote("comms.email_log", "source"), psql.Quote("comms.email_log", "type")).In(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return psql.Quote("comms.email_log", "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() @@ -328,13 +479,7 @@ func (o CommsEmailLogSlice) pkIN() dialect.Expression { func (o CommsEmailLogSlice) copyMatchingRows(from ...*CommsEmailLog) { for i, old := range o { for _, new := range from { - if new.Destination != old.Destination { - continue - } - if new.Source != old.Source { - continue - } - if new.Type != old.Type { + if new.ID != old.ID { continue } new.R = old.R @@ -435,14 +580,14 @@ func (o CommsEmailLogSlice) ReloadAll(ctx context.Context, exec bob.Executor) er return nil } -// DestinationEmail starts a query for related objects on comms.email -func (o *CommsEmailLog) DestinationEmail(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailsQuery { - return CommsEmails.Query(append(mods, - sm.Where(CommsEmails.Columns.Address.EQ(psql.Arg(o.Destination))), +// DestinationEmailContact starts a query for related objects on comms.email_contact +func (o *CommsEmailLog) DestinationEmailContact(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailContactsQuery { + return CommsEmailContacts.Query(append(mods, + sm.Where(CommsEmailContacts.Columns.Address.EQ(psql.Arg(o.Destination))), )...) } -func (os CommsEmailLogSlice) DestinationEmail(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailsQuery { +func (os CommsEmailLogSlice) DestinationEmailContact(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailContactsQuery { pkDestination := make(pgtypes.Array[string], 0, len(os)) for _, o := range os { if o == nil { @@ -454,136 +599,143 @@ func (os CommsEmailLogSlice) DestinationEmail(mods ...bob.Mod[*dialect.SelectQue psql.F("unnest", psql.Cast(psql.Arg(pkDestination), "text[]")), )) - return CommsEmails.Query(append(mods, - sm.Where(psql.Group(CommsEmails.Columns.Address).OP("IN", PKArgExpr)), + return CommsEmailContacts.Query(append(mods, + sm.Where(psql.Group(CommsEmailContacts.Columns.Address).OP("IN", PKArgExpr)), )...) } -// SourcePhone starts a query for related objects on comms.phone -func (o *CommsEmailLog) SourcePhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { - return CommsPhones.Query(append(mods, - sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(o.Source))), +// TemplateEmailTemplate starts a query for related objects on comms.email_template +func (o *CommsEmailLog) TemplateEmailTemplate(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailTemplatesQuery { + return CommsEmailTemplates.Query(append(mods, + sm.Where(CommsEmailTemplates.Columns.ID.EQ(psql.Arg(o.TemplateID))), )...) } -func (os CommsEmailLogSlice) SourcePhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { - pkSource := make(pgtypes.Array[string], 0, len(os)) +func (os CommsEmailLogSlice) TemplateEmailTemplate(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailTemplatesQuery { + pkTemplateID := make(pgtypes.Array[null.Val[int32]], 0, len(os)) for _, o := range os { if o == nil { continue } - pkSource = append(pkSource, o.Source) + pkTemplateID = append(pkTemplateID, o.TemplateID) } PKArgExpr := psql.Select(sm.Columns( - psql.F("unnest", psql.Cast(psql.Arg(pkSource), "text[]")), + psql.F("unnest", psql.Cast(psql.Arg(pkTemplateID), "integer[]")), )) - return CommsPhones.Query(append(mods, - sm.Where(psql.Group(CommsPhones.Columns.E164).OP("IN", PKArgExpr)), + return CommsEmailTemplates.Query(append(mods, + sm.Where(psql.Group(CommsEmailTemplates.Columns.ID).OP("IN", PKArgExpr)), )...) } -func attachCommsEmailLogDestinationEmail0(ctx context.Context, exec bob.Executor, count int, commsEmailLog0 *CommsEmailLog, commsEmail1 *CommsEmail) (*CommsEmailLog, error) { +func attachCommsEmailLogDestinationEmailContact0(ctx context.Context, exec bob.Executor, count int, commsEmailLog0 *CommsEmailLog, commsEmailContact1 *CommsEmailContact) (*CommsEmailLog, error) { setter := &CommsEmailLogSetter{ - Destination: omit.From(commsEmail1.Address), + Destination: omit.From(commsEmailContact1.Address), } err := commsEmailLog0.Update(ctx, exec, setter) if err != nil { - return nil, fmt.Errorf("attachCommsEmailLogDestinationEmail0: %w", err) + return nil, fmt.Errorf("attachCommsEmailLogDestinationEmailContact0: %w", err) } return commsEmailLog0, nil } -func (commsEmailLog0 *CommsEmailLog) InsertDestinationEmail(ctx context.Context, exec bob.Executor, related *CommsEmailSetter) error { +func (commsEmailLog0 *CommsEmailLog) InsertDestinationEmailContact(ctx context.Context, exec bob.Executor, related *CommsEmailContactSetter) error { var err error - commsEmail1, err := CommsEmails.Insert(related).One(ctx, exec) + commsEmailContact1, err := CommsEmailContacts.Insert(related).One(ctx, exec) if err != nil { return fmt.Errorf("inserting related objects: %w", err) } - _, err = attachCommsEmailLogDestinationEmail0(ctx, exec, 1, commsEmailLog0, commsEmail1) + _, err = attachCommsEmailLogDestinationEmailContact0(ctx, exec, 1, commsEmailLog0, commsEmailContact1) if err != nil { return err } - commsEmailLog0.R.DestinationEmail = commsEmail1 + commsEmailLog0.R.DestinationEmailContact = commsEmailContact1 - commsEmail1.R.DestinationEmailLogs = append(commsEmail1.R.DestinationEmailLogs, commsEmailLog0) + commsEmailContact1.R.DestinationEmailLogs = append(commsEmailContact1.R.DestinationEmailLogs, commsEmailLog0) return nil } -func (commsEmailLog0 *CommsEmailLog) AttachDestinationEmail(ctx context.Context, exec bob.Executor, commsEmail1 *CommsEmail) error { +func (commsEmailLog0 *CommsEmailLog) AttachDestinationEmailContact(ctx context.Context, exec bob.Executor, commsEmailContact1 *CommsEmailContact) error { var err error - _, err = attachCommsEmailLogDestinationEmail0(ctx, exec, 1, commsEmailLog0, commsEmail1) + _, err = attachCommsEmailLogDestinationEmailContact0(ctx, exec, 1, commsEmailLog0, commsEmailContact1) if err != nil { return err } - commsEmailLog0.R.DestinationEmail = commsEmail1 + commsEmailLog0.R.DestinationEmailContact = commsEmailContact1 - commsEmail1.R.DestinationEmailLogs = append(commsEmail1.R.DestinationEmailLogs, commsEmailLog0) + commsEmailContact1.R.DestinationEmailLogs = append(commsEmailContact1.R.DestinationEmailLogs, commsEmailLog0) return nil } -func attachCommsEmailLogSourcePhone0(ctx context.Context, exec bob.Executor, count int, commsEmailLog0 *CommsEmailLog, commsPhone1 *CommsPhone) (*CommsEmailLog, error) { +func attachCommsEmailLogTemplateEmailTemplate0(ctx context.Context, exec bob.Executor, count int, commsEmailLog0 *CommsEmailLog, commsEmailTemplate1 *CommsEmailTemplate) (*CommsEmailLog, error) { setter := &CommsEmailLogSetter{ - Source: omit.From(commsPhone1.E164), + TemplateID: omitnull.From(commsEmailTemplate1.ID), } err := commsEmailLog0.Update(ctx, exec, setter) if err != nil { - return nil, fmt.Errorf("attachCommsEmailLogSourcePhone0: %w", err) + return nil, fmt.Errorf("attachCommsEmailLogTemplateEmailTemplate0: %w", err) } return commsEmailLog0, nil } -func (commsEmailLog0 *CommsEmailLog) InsertSourcePhone(ctx context.Context, exec bob.Executor, related *CommsPhoneSetter) error { +func (commsEmailLog0 *CommsEmailLog) InsertTemplateEmailTemplate(ctx context.Context, exec bob.Executor, related *CommsEmailTemplateSetter) error { var err error - commsPhone1, err := CommsPhones.Insert(related).One(ctx, exec) + commsEmailTemplate1, err := CommsEmailTemplates.Insert(related).One(ctx, exec) if err != nil { return fmt.Errorf("inserting related objects: %w", err) } - _, err = attachCommsEmailLogSourcePhone0(ctx, exec, 1, commsEmailLog0, commsPhone1) + _, err = attachCommsEmailLogTemplateEmailTemplate0(ctx, exec, 1, commsEmailLog0, commsEmailTemplate1) if err != nil { return err } - commsEmailLog0.R.SourcePhone = commsPhone1 + commsEmailLog0.R.TemplateEmailTemplate = commsEmailTemplate1 - commsPhone1.R.SourceEmailLogs = append(commsPhone1.R.SourceEmailLogs, commsEmailLog0) + commsEmailTemplate1.R.TemplateEmailLogs = append(commsEmailTemplate1.R.TemplateEmailLogs, commsEmailLog0) return nil } -func (commsEmailLog0 *CommsEmailLog) AttachSourcePhone(ctx context.Context, exec bob.Executor, commsPhone1 *CommsPhone) error { +func (commsEmailLog0 *CommsEmailLog) AttachTemplateEmailTemplate(ctx context.Context, exec bob.Executor, commsEmailTemplate1 *CommsEmailTemplate) error { var err error - _, err = attachCommsEmailLogSourcePhone0(ctx, exec, 1, commsEmailLog0, commsPhone1) + _, err = attachCommsEmailLogTemplateEmailTemplate0(ctx, exec, 1, commsEmailLog0, commsEmailTemplate1) if err != nil { return err } - commsEmailLog0.R.SourcePhone = commsPhone1 + commsEmailLog0.R.TemplateEmailTemplate = commsEmailTemplate1 - commsPhone1.R.SourceEmailLogs = append(commsPhone1.R.SourceEmailLogs, commsEmailLog0) + commsEmailTemplate1.R.TemplateEmailLogs = append(commsEmailTemplate1.R.TemplateEmailLogs, commsEmailLog0) return nil } type commsEmailLogWhere[Q psql.Filterable] struct { - Created psql.WhereMod[Q, time.Time] - Destination psql.WhereMod[Q, string] - Source psql.WhereMod[Q, string] - Type psql.WhereMod[Q, enums.CommsMessagetypeemail] + ID psql.WhereMod[Q, int32] + Created psql.WhereMod[Q, time.Time] + DeliveryStatus psql.WhereMod[Q, string] + Destination psql.WhereMod[Q, string] + PublicID psql.WhereMod[Q, string] + SentAt psql.WhereNullMod[Q, time.Time] + Source psql.WhereMod[Q, string] + Subject psql.WhereMod[Q, string] + TemplateID psql.WhereNullMod[Q, int32] + TemplateData psql.WhereMod[Q, pgtypes.HStore] + Type psql.WhereMod[Q, enums.CommsMessagetypeemail] } func (commsEmailLogWhere[Q]) AliasedAs(alias string) commsEmailLogWhere[Q] { @@ -592,10 +744,17 @@ func (commsEmailLogWhere[Q]) AliasedAs(alias string) commsEmailLogWhere[Q] { func buildCommsEmailLogWhere[Q psql.Filterable](cols commsEmailLogColumns) commsEmailLogWhere[Q] { return commsEmailLogWhere[Q]{ - Created: psql.Where[Q, time.Time](cols.Created), - Destination: psql.Where[Q, string](cols.Destination), - Source: psql.Where[Q, string](cols.Source), - Type: psql.Where[Q, enums.CommsMessagetypeemail](cols.Type), + ID: psql.Where[Q, int32](cols.ID), + Created: psql.Where[Q, time.Time](cols.Created), + DeliveryStatus: psql.Where[Q, string](cols.DeliveryStatus), + Destination: psql.Where[Q, string](cols.Destination), + PublicID: psql.Where[Q, string](cols.PublicID), + SentAt: psql.WhereNull[Q, time.Time](cols.SentAt), + Source: psql.Where[Q, string](cols.Source), + Subject: psql.Where[Q, string](cols.Subject), + TemplateID: psql.WhereNull[Q, int32](cols.TemplateID), + TemplateData: psql.Where[Q, pgtypes.HStore](cols.TemplateData), + Type: psql.Where[Q, enums.CommsMessagetypeemail](cols.Type), } } @@ -605,28 +764,28 @@ func (o *CommsEmailLog) Preload(name string, retrieved any) error { } switch name { - case "DestinationEmail": - rel, ok := retrieved.(*CommsEmail) + case "DestinationEmailContact": + rel, ok := retrieved.(*CommsEmailContact) if !ok { return fmt.Errorf("commsEmailLog cannot load %T as %q", retrieved, name) } - o.R.DestinationEmail = rel + o.R.DestinationEmailContact = rel if rel != nil { rel.R.DestinationEmailLogs = CommsEmailLogSlice{o} } return nil - case "SourcePhone": - rel, ok := retrieved.(*CommsPhone) + case "TemplateEmailTemplate": + rel, ok := retrieved.(*CommsEmailTemplate) if !ok { return fmt.Errorf("commsEmailLog cannot load %T as %q", retrieved, name) } - o.R.SourcePhone = rel + o.R.TemplateEmailTemplate = rel if rel != nil { - rel.R.SourceEmailLogs = CommsEmailLogSlice{o} + rel.R.TemplateEmailLogs = CommsEmailLogSlice{o} } return nil default: @@ -635,97 +794,97 @@ func (o *CommsEmailLog) Preload(name string, retrieved any) error { } type commsEmailLogPreloader struct { - DestinationEmail func(...psql.PreloadOption) psql.Preloader - SourcePhone func(...psql.PreloadOption) psql.Preloader + DestinationEmailContact func(...psql.PreloadOption) psql.Preloader + TemplateEmailTemplate func(...psql.PreloadOption) psql.Preloader } func buildCommsEmailLogPreloader() commsEmailLogPreloader { return commsEmailLogPreloader{ - DestinationEmail: func(opts ...psql.PreloadOption) psql.Preloader { - return psql.Preload[*CommsEmail, CommsEmailSlice](psql.PreloadRel{ - Name: "DestinationEmail", + DestinationEmailContact: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*CommsEmailContact, CommsEmailContactSlice](psql.PreloadRel{ + Name: "DestinationEmailContact", Sides: []psql.PreloadSide{ { From: CommsEmailLogs, - To: CommsEmails, + To: CommsEmailContacts, FromColumns: []string{"destination"}, ToColumns: []string{"address"}, }, }, - }, CommsEmails.Columns.Names(), opts...) + }, CommsEmailContacts.Columns.Names(), opts...) }, - SourcePhone: func(opts ...psql.PreloadOption) psql.Preloader { - return psql.Preload[*CommsPhone, CommsPhoneSlice](psql.PreloadRel{ - Name: "SourcePhone", + TemplateEmailTemplate: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*CommsEmailTemplate, CommsEmailTemplateSlice](psql.PreloadRel{ + Name: "TemplateEmailTemplate", Sides: []psql.PreloadSide{ { From: CommsEmailLogs, - To: CommsPhones, - FromColumns: []string{"source"}, - ToColumns: []string{"e164"}, + To: CommsEmailTemplates, + FromColumns: []string{"template_id"}, + ToColumns: []string{"id"}, }, }, - }, CommsPhones.Columns.Names(), opts...) + }, CommsEmailTemplates.Columns.Names(), opts...) }, } } type commsEmailLogThenLoader[Q orm.Loadable] struct { - DestinationEmail func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - SourcePhone func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + DestinationEmailContact func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + TemplateEmailTemplate func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommsEmailLogThenLoader[Q orm.Loadable]() commsEmailLogThenLoader[Q] { - type DestinationEmailLoadInterface interface { - LoadDestinationEmail(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type DestinationEmailContactLoadInterface interface { + LoadDestinationEmailContact(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } - type SourcePhoneLoadInterface interface { - LoadSourcePhone(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + type TemplateEmailTemplateLoadInterface interface { + LoadTemplateEmailTemplate(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } return commsEmailLogThenLoader[Q]{ - DestinationEmail: thenLoadBuilder[Q]( - "DestinationEmail", - func(ctx context.Context, exec bob.Executor, retrieved DestinationEmailLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadDestinationEmail(ctx, exec, mods...) + DestinationEmailContact: thenLoadBuilder[Q]( + "DestinationEmailContact", + func(ctx context.Context, exec bob.Executor, retrieved DestinationEmailContactLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadDestinationEmailContact(ctx, exec, mods...) }, ), - SourcePhone: thenLoadBuilder[Q]( - "SourcePhone", - func(ctx context.Context, exec bob.Executor, retrieved SourcePhoneLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadSourcePhone(ctx, exec, mods...) + TemplateEmailTemplate: thenLoadBuilder[Q]( + "TemplateEmailTemplate", + func(ctx context.Context, exec bob.Executor, retrieved TemplateEmailTemplateLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadTemplateEmailTemplate(ctx, exec, mods...) }, ), } } -// LoadDestinationEmail loads the commsEmailLog's DestinationEmail into the .R struct -func (o *CommsEmailLog) LoadDestinationEmail(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadDestinationEmailContact loads the commsEmailLog's DestinationEmailContact into the .R struct +func (o *CommsEmailLog) LoadDestinationEmailContact(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } // Reset the relationship - o.R.DestinationEmail = nil + o.R.DestinationEmailContact = nil - related, err := o.DestinationEmail(mods...).One(ctx, exec) + related, err := o.DestinationEmailContact(mods...).One(ctx, exec) if err != nil { return err } related.R.DestinationEmailLogs = CommsEmailLogSlice{o} - o.R.DestinationEmail = related + o.R.DestinationEmailContact = related return nil } -// LoadDestinationEmail loads the commsEmailLog's DestinationEmail into the .R struct -func (os CommsEmailLogSlice) LoadDestinationEmail(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadDestinationEmailContact loads the commsEmailLog's DestinationEmailContact into the .R struct +func (os CommsEmailLogSlice) LoadDestinationEmailContact(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } - commsEmails, err := os.DestinationEmail(mods...).All(ctx, exec) + commsEmailContacts, err := os.DestinationEmailContact(mods...).All(ctx, exec) if err != nil { return err } @@ -735,7 +894,7 @@ func (os CommsEmailLogSlice) LoadDestinationEmail(ctx context.Context, exec bob. continue } - for _, rel := range commsEmails { + for _, rel := range commsEmailContacts { if !(o.Destination == rel.Address) { continue @@ -743,7 +902,7 @@ func (os CommsEmailLogSlice) LoadDestinationEmail(ctx context.Context, exec bob. rel.R.DestinationEmailLogs = append(rel.R.DestinationEmailLogs, o) - o.R.DestinationEmail = rel + o.R.DestinationEmailContact = rel break } } @@ -751,33 +910,33 @@ func (os CommsEmailLogSlice) LoadDestinationEmail(ctx context.Context, exec bob. return nil } -// LoadSourcePhone loads the commsEmailLog's SourcePhone into the .R struct -func (o *CommsEmailLog) LoadSourcePhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadTemplateEmailTemplate loads the commsEmailLog's TemplateEmailTemplate into the .R struct +func (o *CommsEmailLog) LoadTemplateEmailTemplate(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { return nil } // Reset the relationship - o.R.SourcePhone = nil + o.R.TemplateEmailTemplate = nil - related, err := o.SourcePhone(mods...).One(ctx, exec) + related, err := o.TemplateEmailTemplate(mods...).One(ctx, exec) if err != nil { return err } - related.R.SourceEmailLogs = CommsEmailLogSlice{o} + related.R.TemplateEmailLogs = CommsEmailLogSlice{o} - o.R.SourcePhone = related + o.R.TemplateEmailTemplate = related return nil } -// LoadSourcePhone loads the commsEmailLog's SourcePhone into the .R struct -func (os CommsEmailLogSlice) LoadSourcePhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { +// LoadTemplateEmailTemplate loads the commsEmailLog's TemplateEmailTemplate into the .R struct +func (os CommsEmailLogSlice) LoadTemplateEmailTemplate(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if len(os) == 0 { return nil } - commsPhones, err := os.SourcePhone(mods...).All(ctx, exec) + commsEmailTemplates, err := os.TemplateEmailTemplate(mods...).All(ctx, exec) if err != nil { return err } @@ -787,15 +946,18 @@ func (os CommsEmailLogSlice) LoadSourcePhone(ctx context.Context, exec bob.Execu continue } - for _, rel := range commsPhones { - - if !(o.Source == rel.E164) { + for _, rel := range commsEmailTemplates { + if !o.TemplateID.IsValue() { continue } - rel.R.SourceEmailLogs = append(rel.R.SourceEmailLogs, o) + if !(o.TemplateID.IsValue() && o.TemplateID.MustGet() == rel.ID) { + continue + } - o.R.SourcePhone = rel + rel.R.TemplateEmailLogs = append(rel.R.TemplateEmailLogs, o) + + o.R.TemplateEmailTemplate = rel break } } @@ -804,9 +966,9 @@ func (os CommsEmailLogSlice) LoadSourcePhone(ctx context.Context, exec bob.Execu } type commsEmailLogJoins[Q dialect.Joinable] struct { - typ string - DestinationEmail modAs[Q, commsEmailColumns] - SourcePhone modAs[Q, commsPhoneColumns] + typ string + DestinationEmailContact modAs[Q, commsEmailContactColumns] + TemplateEmailTemplate modAs[Q, commsEmailTemplateColumns] } func (j commsEmailLogJoins[Q]) aliasedAs(alias string) commsEmailLogJoins[Q] { @@ -816,13 +978,13 @@ func (j commsEmailLogJoins[Q]) aliasedAs(alias string) commsEmailLogJoins[Q] { func buildCommsEmailLogJoins[Q dialect.Joinable](cols commsEmailLogColumns, typ string) commsEmailLogJoins[Q] { return commsEmailLogJoins[Q]{ typ: typ, - DestinationEmail: modAs[Q, commsEmailColumns]{ - c: CommsEmails.Columns, - f: func(to commsEmailColumns) bob.Mod[Q] { + DestinationEmailContact: modAs[Q, commsEmailContactColumns]{ + c: CommsEmailContacts.Columns, + f: func(to commsEmailContactColumns) bob.Mod[Q] { mods := make(mods.QueryMods[Q], 0, 1) { - mods = append(mods, dialect.Join[Q](typ, CommsEmails.Name().As(to.Alias())).On( + mods = append(mods, dialect.Join[Q](typ, CommsEmailContacts.Name().As(to.Alias())).On( to.Address.EQ(cols.Destination), )) } @@ -830,14 +992,14 @@ func buildCommsEmailLogJoins[Q dialect.Joinable](cols commsEmailLogColumns, typ return mods }, }, - SourcePhone: modAs[Q, commsPhoneColumns]{ - c: CommsPhones.Columns, - f: func(to commsPhoneColumns) bob.Mod[Q] { + TemplateEmailTemplate: modAs[Q, commsEmailTemplateColumns]{ + c: CommsEmailTemplates.Columns, + f: func(to commsEmailTemplateColumns) bob.Mod[Q] { mods := make(mods.QueryMods[Q], 0, 1) { - mods = append(mods, dialect.Join[Q](typ, CommsPhones.Name().As(to.Alias())).On( - to.E164.EQ(cols.Source), + mods = append(mods, dialect.Join[Q](typ, CommsEmailTemplates.Name().As(to.Alias())).On( + to.ID.EQ(cols.TemplateID), )) } diff --git a/db/models/comms.email_template.bob.go b/db/models/comms.email_template.bob.go new file mode 100644 index 00000000..ca95017d --- /dev/null +++ b/db/models/comms.email_template.bob.go @@ -0,0 +1,869 @@ +// Code generated by BobGen psql v0.42.1. 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" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/aarondl/opt/null" + "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" + "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" +) + +// CommsEmailTemplate is an object representing the database table. +type CommsEmailTemplate struct { + ContentHTML string `db:"content_html" ` + ContentTXT string `db:"content_txt" ` + ContentHashHTML string `db:"content_hash_html" ` + ContentHashTXT string `db:"content_hash_txt" ` + Created time.Time `db:"created" ` + ID int32 `db:"id,pk" ` + Superceded null.Val[time.Time] `db:"superceded" ` + MessageType enums.CommsMessagetypeemail `db:"message_type" ` + + R commsEmailTemplateR `db:"-" ` + + C commsEmailTemplateC `db:"-" ` +} + +// CommsEmailTemplateSlice is an alias for a slice of pointers to CommsEmailTemplate. +// This should almost always be used instead of []*CommsEmailTemplate. +type CommsEmailTemplateSlice []*CommsEmailTemplate + +// CommsEmailTemplates contains methods to work with the email_template table +var CommsEmailTemplates = psql.NewTablex[*CommsEmailTemplate, CommsEmailTemplateSlice, *CommsEmailTemplateSetter]("comms", "email_template", buildCommsEmailTemplateColumns("comms.email_template")) + +// CommsEmailTemplatesQuery is a query on the email_template table +type CommsEmailTemplatesQuery = *psql.ViewQuery[*CommsEmailTemplate, CommsEmailTemplateSlice] + +// commsEmailTemplateR is where relationships are stored. +type commsEmailTemplateR struct { + TemplateEmailLogs CommsEmailLogSlice // comms.email_log.email_log_template_id_fkey +} + +func buildCommsEmailTemplateColumns(alias string) commsEmailTemplateColumns { + return commsEmailTemplateColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "content_html", "content_txt", "content_hash_html", "content_hash_txt", "created", "id", "superceded", "message_type", + ).WithParent("comms.email_template"), + tableAlias: alias, + ContentHTML: psql.Quote(alias, "content_html"), + ContentTXT: psql.Quote(alias, "content_txt"), + ContentHashHTML: psql.Quote(alias, "content_hash_html"), + ContentHashTXT: psql.Quote(alias, "content_hash_txt"), + Created: psql.Quote(alias, "created"), + ID: psql.Quote(alias, "id"), + Superceded: psql.Quote(alias, "superceded"), + MessageType: psql.Quote(alias, "message_type"), + } +} + +type commsEmailTemplateColumns struct { + expr.ColumnsExpr + tableAlias string + ContentHTML psql.Expression + ContentTXT psql.Expression + ContentHashHTML psql.Expression + ContentHashTXT psql.Expression + Created psql.Expression + ID psql.Expression + Superceded psql.Expression + MessageType psql.Expression +} + +func (c commsEmailTemplateColumns) Alias() string { + return c.tableAlias +} + +func (commsEmailTemplateColumns) AliasedAs(alias string) commsEmailTemplateColumns { + return buildCommsEmailTemplateColumns(alias) +} + +// CommsEmailTemplateSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type CommsEmailTemplateSetter struct { + ContentHTML omit.Val[string] `db:"content_html" ` + ContentTXT omit.Val[string] `db:"content_txt" ` + ContentHashHTML omit.Val[string] `db:"content_hash_html" ` + ContentHashTXT omit.Val[string] `db:"content_hash_txt" ` + Created omit.Val[time.Time] `db:"created" ` + ID omit.Val[int32] `db:"id,pk" ` + Superceded omitnull.Val[time.Time] `db:"superceded" ` + MessageType omit.Val[enums.CommsMessagetypeemail] `db:"message_type" ` +} + +func (s CommsEmailTemplateSetter) SetColumns() []string { + vals := make([]string, 0, 8) + if s.ContentHTML.IsValue() { + vals = append(vals, "content_html") + } + if s.ContentTXT.IsValue() { + vals = append(vals, "content_txt") + } + if s.ContentHashHTML.IsValue() { + vals = append(vals, "content_hash_html") + } + if s.ContentHashTXT.IsValue() { + vals = append(vals, "content_hash_txt") + } + if s.Created.IsValue() { + vals = append(vals, "created") + } + if s.ID.IsValue() { + vals = append(vals, "id") + } + if !s.Superceded.IsUnset() { + vals = append(vals, "superceded") + } + if s.MessageType.IsValue() { + vals = append(vals, "message_type") + } + return vals +} + +func (s CommsEmailTemplateSetter) Overwrite(t *CommsEmailTemplate) { + if s.ContentHTML.IsValue() { + t.ContentHTML = s.ContentHTML.MustGet() + } + if s.ContentTXT.IsValue() { + t.ContentTXT = s.ContentTXT.MustGet() + } + if s.ContentHashHTML.IsValue() { + t.ContentHashHTML = s.ContentHashHTML.MustGet() + } + if s.ContentHashTXT.IsValue() { + t.ContentHashTXT = s.ContentHashTXT.MustGet() + } + if s.Created.IsValue() { + t.Created = s.Created.MustGet() + } + if s.ID.IsValue() { + t.ID = s.ID.MustGet() + } + if !s.Superceded.IsUnset() { + t.Superceded = s.Superceded.MustGetNull() + } + if s.MessageType.IsValue() { + t.MessageType = s.MessageType.MustGet() + } +} + +func (s *CommsEmailTemplateSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return CommsEmailTemplates.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, 8) + if s.ContentHTML.IsValue() { + vals[0] = psql.Arg(s.ContentHTML.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.ContentTXT.IsValue() { + vals[1] = psql.Arg(s.ContentTXT.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if s.ContentHashHTML.IsValue() { + vals[2] = psql.Arg(s.ContentHashHTML.MustGet()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + if s.ContentHashTXT.IsValue() { + vals[3] = psql.Arg(s.ContentHashTXT.MustGet()) + } else { + vals[3] = psql.Raw("DEFAULT") + } + + if s.Created.IsValue() { + vals[4] = psql.Arg(s.Created.MustGet()) + } else { + vals[4] = psql.Raw("DEFAULT") + } + + if s.ID.IsValue() { + vals[5] = psql.Arg(s.ID.MustGet()) + } else { + vals[5] = psql.Raw("DEFAULT") + } + + if !s.Superceded.IsUnset() { + vals[6] = psql.Arg(s.Superceded.MustGetNull()) + } else { + vals[6] = psql.Raw("DEFAULT") + } + + if s.MessageType.IsValue() { + vals[7] = psql.Arg(s.MessageType.MustGet()) + } else { + vals[7] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s CommsEmailTemplateSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s CommsEmailTemplateSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 8) + + if s.ContentHTML.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "content_html")...), + psql.Arg(s.ContentHTML), + }}) + } + + if s.ContentTXT.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "content_txt")...), + psql.Arg(s.ContentTXT), + }}) + } + + if s.ContentHashHTML.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "content_hash_html")...), + psql.Arg(s.ContentHashHTML), + }}) + } + + if s.ContentHashTXT.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "content_hash_txt")...), + psql.Arg(s.ContentHashTXT), + }}) + } + + if s.Created.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "created")...), + psql.Arg(s.Created), + }}) + } + + if s.ID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "id")...), + psql.Arg(s.ID), + }}) + } + + if !s.Superceded.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "superceded")...), + psql.Arg(s.Superceded), + }}) + } + + if s.MessageType.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "message_type")...), + psql.Arg(s.MessageType), + }}) + } + + return exprs +} + +// FindCommsEmailTemplate retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindCommsEmailTemplate(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*CommsEmailTemplate, error) { + if len(cols) == 0 { + return CommsEmailTemplates.Query( + sm.Where(CommsEmailTemplates.Columns.ID.EQ(psql.Arg(IDPK))), + ).One(ctx, exec) + } + + return CommsEmailTemplates.Query( + sm.Where(CommsEmailTemplates.Columns.ID.EQ(psql.Arg(IDPK))), + sm.Columns(CommsEmailTemplates.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// CommsEmailTemplateExists checks the presence of a single record by primary key +func CommsEmailTemplateExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { + return CommsEmailTemplates.Query( + sm.Where(CommsEmailTemplates.Columns.ID.EQ(psql.Arg(IDPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after CommsEmailTemplate is retrieved from the database +func (o *CommsEmailTemplate) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsEmailTemplates.AfterSelectHooks.RunHooks(ctx, exec, CommsEmailTemplateSlice{o}) + case bob.QueryTypeInsert: + ctx, err = CommsEmailTemplates.AfterInsertHooks.RunHooks(ctx, exec, CommsEmailTemplateSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = CommsEmailTemplates.AfterUpdateHooks.RunHooks(ctx, exec, CommsEmailTemplateSlice{o}) + case bob.QueryTypeDelete: + ctx, err = CommsEmailTemplates.AfterDeleteHooks.RunHooks(ctx, exec, CommsEmailTemplateSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the CommsEmailTemplate +func (o *CommsEmailTemplate) primaryKeyVals() bob.Expression { + return psql.Arg(o.ID) +} + +func (o *CommsEmailTemplate) pkEQ() dialect.Expression { + return psql.Quote("comms.email_template", "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 CommsEmailTemplate +func (o *CommsEmailTemplate) Update(ctx context.Context, exec bob.Executor, s *CommsEmailTemplateSetter) error { + v, err := CommsEmailTemplates.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 CommsEmailTemplate record with an executor +func (o *CommsEmailTemplate) Delete(ctx context.Context, exec bob.Executor) error { + _, err := CommsEmailTemplates.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the CommsEmailTemplate using the executor +func (o *CommsEmailTemplate) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := CommsEmailTemplates.Query( + sm.Where(CommsEmailTemplates.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 CommsEmailTemplateSlice is retrieved from the database +func (o CommsEmailTemplateSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsEmailTemplates.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = CommsEmailTemplates.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = CommsEmailTemplates.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = CommsEmailTemplates.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o CommsEmailTemplateSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Quote("comms.email_template", "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 CommsEmailTemplateSlice) copyMatchingRows(from ...*CommsEmailTemplate) { + 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 CommsEmailTemplateSlice) 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 CommsEmailTemplates.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 *CommsEmailTemplate: + o.copyMatchingRows(retrieved) + case []*CommsEmailTemplate: + o.copyMatchingRows(retrieved...) + case CommsEmailTemplateSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsEmailTemplate or a slice of CommsEmailTemplate + // then run the AfterUpdateHooks on the slice + _, err = CommsEmailTemplates.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o CommsEmailTemplateSlice) 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 CommsEmailTemplates.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 *CommsEmailTemplate: + o.copyMatchingRows(retrieved) + case []*CommsEmailTemplate: + o.copyMatchingRows(retrieved...) + case CommsEmailTemplateSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsEmailTemplate or a slice of CommsEmailTemplate + // then run the AfterDeleteHooks on the slice + _, err = CommsEmailTemplates.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o CommsEmailTemplateSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals CommsEmailTemplateSetter) error { + if len(o) == 0 { + return nil + } + + _, err := CommsEmailTemplates.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o CommsEmailTemplateSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := CommsEmailTemplates.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o CommsEmailTemplateSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := CommsEmailTemplates.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// TemplateEmailLogs starts a query for related objects on comms.email_log +func (o *CommsEmailTemplate) TemplateEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { + return CommsEmailLogs.Query(append(mods, + sm.Where(CommsEmailLogs.Columns.TemplateID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os CommsEmailTemplateSlice) TemplateEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return CommsEmailLogs.Query(append(mods, + sm.Where(psql.Group(CommsEmailLogs.Columns.TemplateID).OP("IN", PKArgExpr)), + )...) +} + +func insertCommsEmailTemplateTemplateEmailLogs0(ctx context.Context, exec bob.Executor, commsEmailLogs1 []*CommsEmailLogSetter, commsEmailTemplate0 *CommsEmailTemplate) (CommsEmailLogSlice, error) { + for i := range commsEmailLogs1 { + commsEmailLogs1[i].TemplateID = omitnull.From(commsEmailTemplate0.ID) + } + + ret, err := CommsEmailLogs.Insert(bob.ToMods(commsEmailLogs1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertCommsEmailTemplateTemplateEmailLogs0: %w", err) + } + + return ret, nil +} + +func attachCommsEmailTemplateTemplateEmailLogs0(ctx context.Context, exec bob.Executor, count int, commsEmailLogs1 CommsEmailLogSlice, commsEmailTemplate0 *CommsEmailTemplate) (CommsEmailLogSlice, error) { + setter := &CommsEmailLogSetter{ + TemplateID: omitnull.From(commsEmailTemplate0.ID), + } + + err := commsEmailLogs1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachCommsEmailTemplateTemplateEmailLogs0: %w", err) + } + + return commsEmailLogs1, nil +} + +func (commsEmailTemplate0 *CommsEmailTemplate) InsertTemplateEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLogSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + commsEmailLogs1, err := insertCommsEmailTemplateTemplateEmailLogs0(ctx, exec, related, commsEmailTemplate0) + if err != nil { + return err + } + + commsEmailTemplate0.R.TemplateEmailLogs = append(commsEmailTemplate0.R.TemplateEmailLogs, commsEmailLogs1...) + + for _, rel := range commsEmailLogs1 { + rel.R.TemplateEmailTemplate = commsEmailTemplate0 + } + return nil +} + +func (commsEmailTemplate0 *CommsEmailTemplate) AttachTemplateEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLog) error { + if len(related) == 0 { + return nil + } + + var err error + commsEmailLogs1 := CommsEmailLogSlice(related) + + _, err = attachCommsEmailTemplateTemplateEmailLogs0(ctx, exec, len(related), commsEmailLogs1, commsEmailTemplate0) + if err != nil { + return err + } + + commsEmailTemplate0.R.TemplateEmailLogs = append(commsEmailTemplate0.R.TemplateEmailLogs, commsEmailLogs1...) + + for _, rel := range related { + rel.R.TemplateEmailTemplate = commsEmailTemplate0 + } + + return nil +} + +type commsEmailTemplateWhere[Q psql.Filterable] struct { + ContentHTML psql.WhereMod[Q, string] + ContentTXT psql.WhereMod[Q, string] + ContentHashHTML psql.WhereMod[Q, string] + ContentHashTXT psql.WhereMod[Q, string] + Created psql.WhereMod[Q, time.Time] + ID psql.WhereMod[Q, int32] + Superceded psql.WhereNullMod[Q, time.Time] + MessageType psql.WhereMod[Q, enums.CommsMessagetypeemail] +} + +func (commsEmailTemplateWhere[Q]) AliasedAs(alias string) commsEmailTemplateWhere[Q] { + return buildCommsEmailTemplateWhere[Q](buildCommsEmailTemplateColumns(alias)) +} + +func buildCommsEmailTemplateWhere[Q psql.Filterable](cols commsEmailTemplateColumns) commsEmailTemplateWhere[Q] { + return commsEmailTemplateWhere[Q]{ + ContentHTML: psql.Where[Q, string](cols.ContentHTML), + ContentTXT: psql.Where[Q, string](cols.ContentTXT), + ContentHashHTML: psql.Where[Q, string](cols.ContentHashHTML), + ContentHashTXT: psql.Where[Q, string](cols.ContentHashTXT), + Created: psql.Where[Q, time.Time](cols.Created), + ID: psql.Where[Q, int32](cols.ID), + Superceded: psql.WhereNull[Q, time.Time](cols.Superceded), + MessageType: psql.Where[Q, enums.CommsMessagetypeemail](cols.MessageType), + } +} + +func (o *CommsEmailTemplate) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "TemplateEmailLogs": + rels, ok := retrieved.(CommsEmailLogSlice) + if !ok { + return fmt.Errorf("commsEmailTemplate cannot load %T as %q", retrieved, name) + } + + o.R.TemplateEmailLogs = rels + + for _, rel := range rels { + if rel != nil { + rel.R.TemplateEmailTemplate = o + } + } + return nil + default: + return fmt.Errorf("commsEmailTemplate has no relationship %q", name) + } +} + +type commsEmailTemplatePreloader struct{} + +func buildCommsEmailTemplatePreloader() commsEmailTemplatePreloader { + return commsEmailTemplatePreloader{} +} + +type commsEmailTemplateThenLoader[Q orm.Loadable] struct { + TemplateEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildCommsEmailTemplateThenLoader[Q orm.Loadable]() commsEmailTemplateThenLoader[Q] { + type TemplateEmailLogsLoadInterface interface { + LoadTemplateEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return commsEmailTemplateThenLoader[Q]{ + TemplateEmailLogs: thenLoadBuilder[Q]( + "TemplateEmailLogs", + func(ctx context.Context, exec bob.Executor, retrieved TemplateEmailLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadTemplateEmailLogs(ctx, exec, mods...) + }, + ), + } +} + +// LoadTemplateEmailLogs loads the commsEmailTemplate's TemplateEmailLogs into the .R struct +func (o *CommsEmailTemplate) LoadTemplateEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.TemplateEmailLogs = nil + + related, err := o.TemplateEmailLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.TemplateEmailTemplate = o + } + + o.R.TemplateEmailLogs = related + return nil +} + +// LoadTemplateEmailLogs loads the commsEmailTemplate's TemplateEmailLogs into the .R struct +func (os CommsEmailTemplateSlice) LoadTemplateEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsEmailLogs, err := os.TemplateEmailLogs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.TemplateEmailLogs = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsEmailLogs { + + if !rel.TemplateID.IsValue() { + continue + } + if !(rel.TemplateID.IsValue() && o.ID == rel.TemplateID.MustGet()) { + continue + } + + rel.R.TemplateEmailTemplate = o + + o.R.TemplateEmailLogs = append(o.R.TemplateEmailLogs, rel) + } + } + + return nil +} + +// commsEmailTemplateC is where relationship counts are stored. +type commsEmailTemplateC struct { + TemplateEmailLogs *int64 +} + +// PreloadCount sets a count in the C struct by name +func (o *CommsEmailTemplate) PreloadCount(name string, count int64) error { + if o == nil { + return nil + } + + switch name { + case "TemplateEmailLogs": + o.C.TemplateEmailLogs = &count + } + return nil +} + +type commsEmailTemplateCountPreloader struct { + TemplateEmailLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader +} + +func buildCommsEmailTemplateCountPreloader() commsEmailTemplateCountPreloader { + return commsEmailTemplateCountPreloader{ + TemplateEmailLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*CommsEmailTemplate]("TemplateEmailLogs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = CommsEmailTemplates.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(CommsEmailLogs.Name()), + sm.Where(psql.Quote(CommsEmailLogs.Alias(), "template_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, + } +} + +type commsEmailTemplateCountThenLoader[Q orm.Loadable] struct { + TemplateEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildCommsEmailTemplateCountThenLoader[Q orm.Loadable]() commsEmailTemplateCountThenLoader[Q] { + type TemplateEmailLogsCountInterface interface { + LoadCountTemplateEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return commsEmailTemplateCountThenLoader[Q]{ + TemplateEmailLogs: countThenLoadBuilder[Q]( + "TemplateEmailLogs", + func(ctx context.Context, exec bob.Executor, retrieved TemplateEmailLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountTemplateEmailLogs(ctx, exec, mods...) + }, + ), + } +} + +// LoadCountTemplateEmailLogs loads the count of TemplateEmailLogs into the C struct +func (o *CommsEmailTemplate) LoadCountTemplateEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.TemplateEmailLogs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.TemplateEmailLogs = &count + return nil +} + +// LoadCountTemplateEmailLogs loads the count of TemplateEmailLogs for a slice +func (os CommsEmailTemplateSlice) LoadCountTemplateEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountTemplateEmailLogs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + +type commsEmailTemplateJoins[Q dialect.Joinable] struct { + typ string + TemplateEmailLogs modAs[Q, commsEmailLogColumns] +} + +func (j commsEmailTemplateJoins[Q]) aliasedAs(alias string) commsEmailTemplateJoins[Q] { + return buildCommsEmailTemplateJoins[Q](buildCommsEmailTemplateColumns(alias), j.typ) +} + +func buildCommsEmailTemplateJoins[Q dialect.Joinable](cols commsEmailTemplateColumns, typ string) commsEmailTemplateJoins[Q] { + return commsEmailTemplateJoins[Q]{ + typ: typ, + TemplateEmailLogs: modAs[Q, commsEmailLogColumns]{ + c: CommsEmailLogs.Columns, + f: func(to commsEmailLogColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsEmailLogs.Name().As(to.Alias())).On( + to.TemplateID.EQ(cols.ID), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/comms.phone.bob.go b/db/models/comms.phone.bob.go index 185a680c..ffc12632 100644 --- a/db/models/comms.phone.bob.go +++ b/db/models/comms.phone.bob.go @@ -43,9 +43,8 @@ type CommsPhonesQuery = *psql.ViewQuery[*CommsPhone, CommsPhoneSlice] // commsPhoneR is where relationships are stored. type commsPhoneR struct { - SourceEmailLogs CommsEmailLogSlice // comms.email_log.email_log_source_fkey - DestinationTextLogs CommsTextLogSlice // comms.text_log.text_log_destination_fkey - SourceTextLogs CommsTextLogSlice // comms.text_log.text_log_source_fkey + DestinationTextLogs CommsTextLogSlice // comms.text_log.text_log_destination_fkey + SourceTextLogs CommsTextLogSlice // comms.text_log.text_log_source_fkey } func buildCommsPhoneColumns(alias string) commsPhoneColumns { @@ -372,30 +371,6 @@ func (o CommsPhoneSlice) ReloadAll(ctx context.Context, exec bob.Executor) error return nil } -// SourceEmailLogs starts a query for related objects on comms.email_log -func (o *CommsPhone) SourceEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { - return CommsEmailLogs.Query(append(mods, - sm.Where(CommsEmailLogs.Columns.Source.EQ(psql.Arg(o.E164))), - )...) -} - -func (os CommsPhoneSlice) SourceEmailLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { - pkE164 := make(pgtypes.Array[string], 0, len(os)) - for _, o := range os { - if o == nil { - continue - } - pkE164 = append(pkE164, o.E164) - } - PKArgExpr := psql.Select(sm.Columns( - psql.F("unnest", psql.Cast(psql.Arg(pkE164), "text[]")), - )) - - return CommsEmailLogs.Query(append(mods, - sm.Where(psql.Group(CommsEmailLogs.Columns.Source).OP("IN", PKArgExpr)), - )...) -} - // DestinationTextLogs starts a query for related objects on comms.text_log func (o *CommsPhone) DestinationTextLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsTextLogsQuery { return CommsTextLogs.Query(append(mods, @@ -444,74 +419,6 @@ func (os CommsPhoneSlice) SourceTextLogs(mods ...bob.Mod[*dialect.SelectQuery]) )...) } -func insertCommsPhoneSourceEmailLogs0(ctx context.Context, exec bob.Executor, commsEmailLogs1 []*CommsEmailLogSetter, commsPhone0 *CommsPhone) (CommsEmailLogSlice, error) { - for i := range commsEmailLogs1 { - commsEmailLogs1[i].Source = omit.From(commsPhone0.E164) - } - - ret, err := CommsEmailLogs.Insert(bob.ToMods(commsEmailLogs1...)).All(ctx, exec) - if err != nil { - return ret, fmt.Errorf("insertCommsPhoneSourceEmailLogs0: %w", err) - } - - return ret, nil -} - -func attachCommsPhoneSourceEmailLogs0(ctx context.Context, exec bob.Executor, count int, commsEmailLogs1 CommsEmailLogSlice, commsPhone0 *CommsPhone) (CommsEmailLogSlice, error) { - setter := &CommsEmailLogSetter{ - Source: omit.From(commsPhone0.E164), - } - - err := commsEmailLogs1.UpdateAll(ctx, exec, *setter) - if err != nil { - return nil, fmt.Errorf("attachCommsPhoneSourceEmailLogs0: %w", err) - } - - return commsEmailLogs1, nil -} - -func (commsPhone0 *CommsPhone) InsertSourceEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLogSetter) error { - if len(related) == 0 { - return nil - } - - var err error - - commsEmailLogs1, err := insertCommsPhoneSourceEmailLogs0(ctx, exec, related, commsPhone0) - if err != nil { - return err - } - - commsPhone0.R.SourceEmailLogs = append(commsPhone0.R.SourceEmailLogs, commsEmailLogs1...) - - for _, rel := range commsEmailLogs1 { - rel.R.SourcePhone = commsPhone0 - } - return nil -} - -func (commsPhone0 *CommsPhone) AttachSourceEmailLogs(ctx context.Context, exec bob.Executor, related ...*CommsEmailLog) error { - if len(related) == 0 { - return nil - } - - var err error - commsEmailLogs1 := CommsEmailLogSlice(related) - - _, err = attachCommsPhoneSourceEmailLogs0(ctx, exec, len(related), commsEmailLogs1, commsPhone0) - if err != nil { - return err - } - - commsPhone0.R.SourceEmailLogs = append(commsPhone0.R.SourceEmailLogs, commsEmailLogs1...) - - for _, rel := range related { - rel.R.SourcePhone = commsPhone0 - } - - return nil -} - func insertCommsPhoneDestinationTextLogs0(ctx context.Context, exec bob.Executor, commsTextLogs1 []*CommsTextLogSetter, commsPhone0 *CommsPhone) (CommsTextLogSlice, error) { for i := range commsTextLogs1 { commsTextLogs1[i].Destination = omit.From(commsPhone0.E164) @@ -670,20 +577,6 @@ func (o *CommsPhone) Preload(name string, retrieved any) error { } switch name { - case "SourceEmailLogs": - rels, ok := retrieved.(CommsEmailLogSlice) - if !ok { - return fmt.Errorf("commsPhone cannot load %T as %q", retrieved, name) - } - - o.R.SourceEmailLogs = rels - - for _, rel := range rels { - if rel != nil { - rel.R.SourcePhone = o - } - } - return nil case "DestinationTextLogs": rels, ok := retrieved.(CommsTextLogSlice) if !ok { @@ -724,15 +617,11 @@ func buildCommsPhonePreloader() commsPhonePreloader { } type commsPhoneThenLoader[Q orm.Loadable] struct { - SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { - type SourceEmailLogsLoadInterface interface { - LoadSourceEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error - } type DestinationTextLogsLoadInterface interface { LoadDestinationTextLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -741,12 +630,6 @@ func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { } return commsPhoneThenLoader[Q]{ - SourceEmailLogs: thenLoadBuilder[Q]( - "SourceEmailLogs", - func(ctx context.Context, exec bob.Executor, retrieved SourceEmailLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadSourceEmailLogs(ctx, exec, mods...) - }, - ), DestinationTextLogs: thenLoadBuilder[Q]( "DestinationTextLogs", func(ctx context.Context, exec bob.Executor, retrieved DestinationTextLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -762,67 +645,6 @@ func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { } } -// LoadSourceEmailLogs loads the commsPhone's SourceEmailLogs into the .R struct -func (o *CommsPhone) LoadSourceEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { - if o == nil { - return nil - } - - // Reset the relationship - o.R.SourceEmailLogs = nil - - related, err := o.SourceEmailLogs(mods...).All(ctx, exec) - if err != nil { - return err - } - - for _, rel := range related { - rel.R.SourcePhone = o - } - - o.R.SourceEmailLogs = related - return nil -} - -// LoadSourceEmailLogs loads the commsPhone's SourceEmailLogs into the .R struct -func (os CommsPhoneSlice) LoadSourceEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { - if len(os) == 0 { - return nil - } - - commsEmailLogs, err := os.SourceEmailLogs(mods...).All(ctx, exec) - if err != nil { - return err - } - - for _, o := range os { - if o == nil { - continue - } - - o.R.SourceEmailLogs = nil - } - - for _, o := range os { - if o == nil { - continue - } - - for _, rel := range commsEmailLogs { - - if !(o.E164 == rel.Source) { - continue - } - - rel.R.SourcePhone = o - - o.R.SourceEmailLogs = append(o.R.SourceEmailLogs, rel) - } - } - - return nil -} - // LoadDestinationTextLogs loads the commsPhone's DestinationTextLogs into the .R struct func (o *CommsPhone) LoadDestinationTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -947,7 +769,6 @@ func (os CommsPhoneSlice) LoadSourceTextLogs(ctx context.Context, exec bob.Execu // commsPhoneC is where relationship counts are stored. type commsPhoneC struct { - SourceEmailLogs *int64 DestinationTextLogs *int64 SourceTextLogs *int64 } @@ -959,8 +780,6 @@ func (o *CommsPhone) PreloadCount(name string, count int64) error { } switch name { - case "SourceEmailLogs": - o.C.SourceEmailLogs = &count case "DestinationTextLogs": o.C.DestinationTextLogs = &count case "SourceTextLogs": @@ -970,30 +789,12 @@ func (o *CommsPhone) PreloadCount(name string, count int64) error { } type commsPhoneCountPreloader struct { - SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader } func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { return commsPhoneCountPreloader{ - SourceEmailLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { - return countPreloader[*CommsPhone]("SourceEmailLogs", func(parent string) bob.Expression { - // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) - if parent == "" { - parent = CommsPhones.Alias() - } - - subqueryMods := []bob.Mod[*dialect.SelectQuery]{ - sm.Columns(psql.Raw("count(*)")), - - sm.From(CommsEmailLogs.Name()), - sm.Where(psql.Quote(CommsEmailLogs.Alias(), "source").EQ(psql.Quote(parent, "e164"))), - } - subqueryMods = append(subqueryMods, mods...) - return psql.Group(psql.Select(subqueryMods...).Expression) - }) - }, DestinationTextLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { return countPreloader[*CommsPhone]("DestinationTextLogs", func(parent string) bob.Expression { // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) @@ -1032,15 +833,11 @@ func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { } type commsPhoneCountThenLoader[Q orm.Loadable] struct { - SourceEmailLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[Q] { - type SourceEmailLogsCountInterface interface { - LoadCountSourceEmailLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error - } type DestinationTextLogsCountInterface interface { LoadCountDestinationTextLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -1049,12 +846,6 @@ func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[ } return commsPhoneCountThenLoader[Q]{ - SourceEmailLogs: countThenLoadBuilder[Q]( - "SourceEmailLogs", - func(ctx context.Context, exec bob.Executor, retrieved SourceEmailLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { - return retrieved.LoadCountSourceEmailLogs(ctx, exec, mods...) - }, - ), DestinationTextLogs: countThenLoadBuilder[Q]( "DestinationTextLogs", func(ctx context.Context, exec bob.Executor, retrieved DestinationTextLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -1070,36 +861,6 @@ func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[ } } -// LoadCountSourceEmailLogs loads the count of SourceEmailLogs into the C struct -func (o *CommsPhone) LoadCountSourceEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { - if o == nil { - return nil - } - - count, err := o.SourceEmailLogs(mods...).Count(ctx, exec) - if err != nil { - return err - } - - o.C.SourceEmailLogs = &count - return nil -} - -// LoadCountSourceEmailLogs loads the count of SourceEmailLogs for a slice -func (os CommsPhoneSlice) LoadCountSourceEmailLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { - if len(os) == 0 { - return nil - } - - for _, o := range os { - if err := o.LoadCountSourceEmailLogs(ctx, exec, mods...); err != nil { - return err - } - } - - return nil -} - // LoadCountDestinationTextLogs loads the count of DestinationTextLogs into the C struct func (o *CommsPhone) LoadCountDestinationTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -1162,7 +923,6 @@ func (os CommsPhoneSlice) LoadCountSourceTextLogs(ctx context.Context, exec bob. type commsPhoneJoins[Q dialect.Joinable] struct { typ string - SourceEmailLogs modAs[Q, commsEmailLogColumns] DestinationTextLogs modAs[Q, commsTextLogColumns] SourceTextLogs modAs[Q, commsTextLogColumns] } @@ -1174,20 +934,6 @@ func (j commsPhoneJoins[Q]) aliasedAs(alias string) commsPhoneJoins[Q] { func buildCommsPhoneJoins[Q dialect.Joinable](cols commsPhoneColumns, typ string) commsPhoneJoins[Q] { return commsPhoneJoins[Q]{ typ: typ, - SourceEmailLogs: modAs[Q, commsEmailLogColumns]{ - c: CommsEmailLogs.Columns, - f: func(to commsEmailLogColumns) bob.Mod[Q] { - mods := make(mods.QueryMods[Q], 0, 1) - - { - mods = append(mods, dialect.Join[Q](typ, CommsEmailLogs.Name().As(to.Alias())).On( - to.Source.EQ(cols.E164), - )) - } - - return mods - }, - }, DestinationTextLogs: modAs[Q, commsTextLogColumns]{ c: CommsTextLogs.Columns, f: func(to commsTextLogColumns) bob.Mod[Q] { diff --git a/main.go b/main.go index 4b73dfac..ed8956b6 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/auth" "github.com/Gleipnir-Technology/nidus-sync/background" + "github.com/Gleipnir-Technology/nidus-sync/comms/email" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/public-report" @@ -39,6 +40,12 @@ func main() { os.Exit(2) } + err = email.LoadTemplates() + if err != nil { + log.Error().Err(err).Msg("Failed to load email templates") + os.Exit(3) + } + router_logger := log.With().Logger() r := chi.NewRouter() diff --git a/public-report/email.go b/public-report/email.go index e39d9de0..823a30fd 100644 --- a/public-report/email.go +++ b/public-report/email.go @@ -1,17 +1,18 @@ package publicreport import ( + "fmt" "net/http" - "github.com/Gleipnir-Technology/nidus-sync/comms" + //"github.com/Gleipnir-Technology/nidus-sync/comms/email" "github.com/go-chi/chi/v5" ) -func getEmailInitial(w http.ResponseWriter, r *http.Request) { - email := chi.URLParam(r, "email") - comms.RenderEmailInitial(w, email) -} -func getEmailReportSubscriptionConfirmation(w http.ResponseWriter, r *http.Request) { - report_id := chi.URLParam(r, "report_id") - comms.RenderEmailReportConfirmation(w, report_id) +func getEmailByCode(w http.ResponseWriter, r *http.Request) { + code := chi.URLParam(r, "code") + if code == "" { + http.Error(w, "You must specify a code", http.StatusBadRequest) + return + } + fmt.Fprintf(w, "Pretend email contet for %s", code) } diff --git a/public-report/quick.go b/public-report/quick.go index 0c344f41..c8d94c32 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -258,7 +258,7 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { return } if email != "" { - background.ReportSubscriptionConfirmationEmail(email) + background.ReportSubscriptionConfirmationEmail(email, report_id) } if phone_str != "" { background.ReportSubscriptionConfirmationText(*phone, report_id) diff --git a/public-report/routes.go b/public-report/routes.go index 5fefd950..2c757748 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -10,8 +10,7 @@ func Router() chi.Router { r.Get("/", getRoot) r.Get("/privacy", getPrivacy) r.Get("/robots.txt", getRobots) - r.Get("/email/initial", getEmailInitial) - r.Get("/email/report/{report_id}/subscription-confirmation", getEmailReportSubscriptionConfirmation) + r.Get("/email", getEmailByCode) r.Get("/image/{uuid}", getImageByUUID) r.Get("/nuisance", getNuisance) r.Post("/nuisance-submit", postNuisance) From 804059f87d2ccf26f7721c61407a0c4a6c9d9e1a Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 23 Jan 2026 20:37:55 +0000 Subject: [PATCH 0091/1453] Attempt to check if I have an SSH identity before committing I've lost too many commit messages at this point. --- lefthook.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lefthook.yml b/lefthook.yml index 2dfa23dc..003fb93d 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,5 +1,13 @@ pre-commit: commands: + check-ssh-identity: + run: | + # Check if any SSH identities are available + if ! ssh-add -l &>/dev/null || [ "$(ssh-add -l 2>/dev/null | grep -v 'The agent has no identities.')" = "" ]; then + echo "Error: No SSH identities found in your SSH agent." + echo "Please run 'ssh-add' to add your SSH key before committing." + exit 1 + fi gofmt: glob: "*.go" run: gofmt -w {staged_files} From 1dc340872cb41d335cf065b988c977040ce2839a Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 00:07:04 +0000 Subject: [PATCH 0092/1453] Add initial mock root for public report --- public-report/routes.go | 1 + public-report/template/mock/root.html | 135 ++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 public-report/template/mock/root.html diff --git a/public-report/routes.go b/public-report/routes.go index 2c757748..14541bbc 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -12,6 +12,7 @@ func Router() chi.Router { r.Get("/robots.txt", getRobots) r.Get("/email", getEmailByCode) r.Get("/image/{uuid}", getImageByUUID) + r.Get("/mock", getMockRoot) r.Get("/nuisance", getNuisance) r.Post("/nuisance-submit", postNuisance) r.Get("/nuisance-submit-complete", getNuisanceSubmitComplete) diff --git a/public-report/template/mock/root.html b/public-report/template/mock/root.html new file mode 100644 index 00000000..d55a39bb --- /dev/null +++ b/public-report/template/mock/root.html @@ -0,0 +1,135 @@ +{{template "base.html" .}} + +{{define "title"}}Main{{end}} +{{define "extraheader"}} + +{{end}} +{{define "content"}} + +
    + +
    +
    +
    +
    +

    Report Mosquitoes Online

    +

    + We are dedicated to protecting public health and improving quality of life by reducing + mosquito populations and the diseases they can carry. Our districts provide comprehensive + mosquito surveillance, control, and education services to our community. +

    +
    +
    +
    +
    + + +
    +
    +
    +
    +

    On the go?

    + Make a Quick Report +

    Report mosquito issues in under 60 seconds

    +
    +
    +
    +
    + + +
    +
    +

    How Can We Help You Today?

    +
    + +
    +
    +
    +
    + + + +
    +

    Follow-up or Check Status

    +

    Check on a previous request or view current mosquito activity in your area.

    + Get Status +
    +
    +
    + + +
    +
    +
    +
    + + + +
    +

    Report a Green Pool

    +

    Report stagnant water sources like abandoned pools that may breed mosquitoes.

    + Report Source +
    +
    +
    + + +
    +
    +
    +
    + + + +
    +

    Report Mosquito Nuisance

    +

    Report areas with high adult mosquito activity causing discomfort or concern.

    + Report Problem +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    Need to make a quick report?
    +

    Use our streamlined form to report mosquito issues in under 60 seconds

    +
    + +
    +
    +
    +
    +
    +
    +
    +
    + +{{end}} From 3144717b0b988a30087991f22bb1642e12b779cd Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 00:12:05 +0000 Subject: [PATCH 0093/1453] Remove 'quick report' --- public-report/template/mock/root.html | 32 --------------------------- 1 file changed, 32 deletions(-) diff --git a/public-report/template/mock/root.html b/public-report/template/mock/root.html index d55a39bb..eab49e6b 100644 --- a/public-report/template/mock/root.html +++ b/public-report/template/mock/root.html @@ -43,19 +43,6 @@
    - -
    -
    -
    -
    -

    On the go?

    - Make a Quick Report -

    Report mosquito issues in under 60 seconds

    -
    -
    -
    -
    -
    @@ -109,25 +96,6 @@
    - - -
    -
    -
    -
    -
    -
    -
    Need to make a quick report?
    -

    Use our streamlined form to report mosquito issues in under 60 seconds

    -
    - -
    -
    -
    -
    -
    From 07c5b30dc4bde3738ef11ae02a4daefb2a688cf0 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 00:12:14 +0000 Subject: [PATCH 0094/1453] Reword green pool to 'standing water' --- public-report/template/mock/root.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public-report/template/mock/root.html b/public-report/template/mock/root.html index eab49e6b..d5fcb2d0 100644 --- a/public-report/template/mock/root.html +++ b/public-report/template/mock/root.html @@ -73,8 +73,8 @@ -

    Report a Green Pool

    -

    Report stagnant water sources like abandoned pools that may breed mosquitoes.

    +

    Report Standing Water

    +

    Report any water that has been sitting for several days, where mosquitoes can live.

    Report Source From 35eaf781f3e95f1f3c2487ad5f1aa6c2afb03969 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 18:45:11 +0000 Subject: [PATCH 0095/1453] Update icons to higher-quality in mocks --- public-report/page.go | 4 +++ public-report/routes.go | 2 +- public-report/template/mock/root.html | 29 +++++++-------------- public-report/template/svg/check-report.svg | 3 +++ public-report/template/svg/mosquito.svg | 3 +++ public-report/template/svg/pond.svg | 3 +++ 6 files changed, 23 insertions(+), 21 deletions(-) create mode 100644 public-report/template/svg/check-report.svg create mode 100644 public-report/template/svg/mosquito.svg create mode 100644 public-report/template/svg/pond.svg diff --git a/public-report/page.go b/public-report/page.go index 0351e04f..0b166703 100644 --- a/public-report/page.go +++ b/public-report/page.go @@ -11,6 +11,7 @@ import ( var embeddedFiles embed.FS var components = [...]string{"footer", "photo-upload", "photo-upload-header"} +var svgs = [...]string{"check-report", "mosquito", "pond"} func buildTemplate(files ...string) *htmlpage.BuiltTemplate { subdir := "public-report" @@ -21,5 +22,8 @@ func buildTemplate(files ...string) *htmlpage.BuiltTemplate { for _, c := range components { full_files = append(full_files, fmt.Sprintf("%s/template/component/%s.html", subdir, c)) } + for _, c := range svgs { + full_files = append(full_files, fmt.Sprintf("%s/template/svg/%s.svg", subdir, c)) + } return htmlpage.NewBuiltTemplate(embeddedFiles, "public-report/", full_files...) } diff --git a/public-report/routes.go b/public-report/routes.go index 14541bbc..ec4924f3 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -12,7 +12,7 @@ func Router() chi.Router { r.Get("/robots.txt", getRobots) r.Get("/email", getEmailByCode) r.Get("/image/{uuid}", getImageByUUID) - r.Get("/mock", getMockRoot) + r.Route("/mock", addMockRoutes) r.Get("/nuisance", getNuisance) r.Post("/nuisance-submit", postNuisance) r.Get("/nuisance-submit-complete", getNuisanceSubmitComplete) diff --git a/public-report/template/mock/root.html b/public-report/template/mock/root.html index d5fcb2d0..d4c32601 100644 --- a/public-report/template/mock/root.html +++ b/public-report/template/mock/root.html @@ -48,30 +48,23 @@

    How Can We Help You Today?

    -
    - - - + {{ template "svg/mosquito" }}
    -

    Follow-up or Check Status

    -

    Check on a previous request or view current mosquito activity in your area.

    - Get Status +

    Report Mosquito Nuisance

    +

    Report areas with high adult mosquito activity causing discomfort or concern.

    + Report Problem
    - -
    - - - + {{ template "svg/pond" }}

    Report Standing Water

    Report any water that has been sitting for several days, where mosquitoes can live.

    @@ -79,19 +72,15 @@
    - -
    - - - + {{ template "svg/check-report" }}
    -

    Report Mosquito Nuisance

    -

    Report areas with high adult mosquito activity causing discomfort or concern.

    - Report Problem +

    Follow-up or Check Status

    +

    Check on a previous request or view current mosquito activity in your area.

    + Get Status
    diff --git a/public-report/template/svg/check-report.svg b/public-report/template/svg/check-report.svg new file mode 100644 index 00000000..15b82fd6 --- /dev/null +++ b/public-report/template/svg/check-report.svg @@ -0,0 +1,3 @@ +{{define "svg/check-report"}} + +{{end}} diff --git a/public-report/template/svg/mosquito.svg b/public-report/template/svg/mosquito.svg new file mode 100644 index 00000000..ccbcec39 --- /dev/null +++ b/public-report/template/svg/mosquito.svg @@ -0,0 +1,3 @@ +{{define "svg/mosquito"}} + +{{end}} diff --git a/public-report/template/svg/pond.svg b/public-report/template/svg/pond.svg new file mode 100644 index 00000000..bffa55de --- /dev/null +++ b/public-report/template/svg/pond.svg @@ -0,0 +1,3 @@ +{{define "svg/pond"}} + +{{end}} From 45868e4bde92eea589711126844e47fd8176da52 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 18:46:18 +0000 Subject: [PATCH 0096/1453] Update subtitle on RMO root page --- public-report/template/mock/root.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/public-report/template/mock/root.html b/public-report/template/mock/root.html index d4c32601..02d7ae16 100644 --- a/public-report/template/mock/root.html +++ b/public-report/template/mock/root.html @@ -33,11 +33,7 @@

    Report Mosquitoes Online

    -

    - We are dedicated to protecting public health and improving quality of life by reducing - mosquito populations and the diseases they can carry. Our districts provide comprehensive - mosquito surveillance, control, and education services to our community. -

    +

    This is the reporting page for mosquito problems in your area.

    From f549243c10fdc8067666ab750935be98591204f3 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 19:13:55 +0000 Subject: [PATCH 0097/1453] Render organization logos by 'slug' This avoids leaking org IDs in the URL, and makes it possible to have a district-specific root mock that works in both dev and prod. --- api/district.go | 28 +++--- api/routes.go | 2 +- db/dbinfo/organization.bob.go | 12 ++- db/factory/bobfactory_main.bob.go | 1 + db/factory/organization.bob.go | 62 +++++++++++++ db/migrations/00040_organization_slug.sql | 4 + db/models/organization.bob.go | 33 ++++++- public-report/mock.go | 43 +++++++++ public-report/quick.go | 2 +- .../template/mock/district-root.html | 89 +++++++++++++++++++ 10 files changed, 259 insertions(+), 17 deletions(-) create mode 100644 db/migrations/00040_organization_slug.sql create mode 100644 public-report/mock.go create mode 100644 public-report/template/mock/district-root.html diff --git a/api/district.go b/api/district.go index 360e473b..85499cbb 100644 --- a/api/district.go +++ b/api/district.go @@ -54,21 +54,29 @@ func apiGetDistrict(w http.ResponseWriter, r *http.Request) { } func apiGetDistrictLogo(w http.ResponseWriter, r *http.Request) { - id_str := chi.URLParam(r, "id") - org_id, err := strconv.ParseInt(id_str, 10, 32) + slug := chi.URLParam(r, "slug") + ctx := r.Context() + rows, err := models.Organizations.Query( + models.SelectWhere.Organizations.Slug.EQ(slug), + ).All(ctx, db.PGInstance.BobDB) if err != nil { - render.Render(w, r, errRender(fmt.Errorf("%s is not a recognized organization ID: %w", id_str, err))) + http.Error(w, "Failed to query", http.StatusInternalServerError) return } - ctx := r.Context() - org, err := models.FindOrganization(ctx, db.PGInstance.BobDB, int32(org_id)) - if err != nil { + switch len(rows) { + case 0: http.Error(w, "Organization not found", http.StatusNotFound) return - } - if org.LogoUUID.IsNull() { - http.Error(w, "Logo not found", http.StatusNotFound) + case 1: + org := rows[0] + if org.LogoUUID.IsNull() { + http.Error(w, "Logo not found", http.StatusNotFound) + return + } + userfile.ImageFileContentWriteLogo(w, org.LogoUUID.MustGet()) + return + default: + http.Error(w, "Too many organizations, this is a programmer error", http.StatusInternalServerError) return } - userfile.ImageFileContentWriteLogo(w, org.LogoUUID.MustGet()) } diff --git a/api/routes.go b/api/routes.go index 81458890..80726f40 100644 --- a/api/routes.go +++ b/api/routes.go @@ -21,7 +21,7 @@ func AddRoutes(r chi.Router) { // Unauthenticated endpoints r.Get("/district", apiGetDistrict) - r.Get("/district/{id}/logo", apiGetDistrictLogo) + r.Get("/district/{slug}/logo", apiGetDistrictLogo) r.Post("/twilio/message", twilioMessagePost) r.Post("/twilio/status", twilioStatusPost) r.Post("/twilio/text", twilioTextPost) diff --git a/db/dbinfo/organization.bob.go b/db/dbinfo/organization.bob.go index 5013dc15..23be6de2 100644 --- a/db/dbinfo/organization.bob.go +++ b/db/dbinfo/organization.bob.go @@ -87,6 +87,15 @@ var Organizations = Table[ Generated: false, AutoIncr: false, }, + Slug: column{ + Name: "slug", + DBType: "character varying", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, }, Indexes: organizationIndexes{ OrganizationPkey: index{ @@ -182,11 +191,12 @@ type organizationColumns struct { ImportDistrictGid column Website column LogoUUID column + Slug column } func (c organizationColumns) AsSlice() []column { return []column{ - c.ID, c.Name, c.ArcgisID, c.ArcgisName, c.FieldseekerURL, c.ImportDistrictGid, c.Website, c.LogoUUID, + c.ID, c.Name, c.ArcgisID, c.ArcgisName, c.FieldseekerURL, c.ImportDistrictGid, c.Website, c.LogoUUID, c.Slug, } } diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index 2f2ea083..00bfe2bc 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -2594,6 +2594,7 @@ func (f *Factory) FromExistingOrganization(m *models.Organization) *Organization o.ImportDistrictGid = func() null.Val[int32] { return m.ImportDistrictGid } o.Website = func() null.Val[string] { return m.Website } o.LogoUUID = func() null.Val[uuid.UUID] { return m.LogoUUID } + o.Slug = func() null.Val[string] { return m.Slug } ctx := context.Background() if len(m.R.Containerrelates) > 0 { diff --git a/db/factory/organization.bob.go b/db/factory/organization.bob.go index a30832a3..e92ea5a7 100644 --- a/db/factory/organization.bob.go +++ b/db/factory/organization.bob.go @@ -45,6 +45,7 @@ type OrganizationTemplate struct { ImportDistrictGid func() null.Val[int32] Website func() null.Val[string] LogoUUID func() null.Val[uuid.UUID] + Slug func() null.Val[string] r organizationR f *Factory @@ -745,6 +746,10 @@ func (o OrganizationTemplate) BuildSetter() *models.OrganizationSetter { val := o.LogoUUID() m.LogoUUID = omitnull.FromNull(val) } + if o.Slug != nil { + val := o.Slug() + m.Slug = omitnull.FromNull(val) + } return m } @@ -791,6 +796,9 @@ func (o OrganizationTemplate) Build() *models.Organization { if o.LogoUUID != nil { m.LogoUUID = o.LogoUUID() } + if o.Slug != nil { + m.Slug = o.Slug() + } o.setModelRels(m) @@ -1642,6 +1650,7 @@ func (m organizationMods) RandomizeAllColumns(f *faker.Faker) OrganizationMod { OrganizationMods.RandomImportDistrictGid(f), OrganizationMods.RandomWebsite(f), OrganizationMods.RandomLogoUUID(f), + OrganizationMods.RandomSlug(f), } } @@ -2025,6 +2034,59 @@ func (m organizationMods) RandomLogoUUIDNotNull(f *faker.Faker) OrganizationMod }) } +// Set the model columns to this value +func (m organizationMods) Slug(val null.Val[string]) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Slug = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m organizationMods) SlugFunc(f func() null.Val[string]) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Slug = f + }) +} + +// Clear any values for the column +func (m organizationMods) UnsetSlug() OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Slug = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m organizationMods) RandomSlug(f *faker.Faker) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Slug = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f, "24") + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m organizationMods) RandomSlugNotNull(f *faker.Faker) OrganizationMod { + return OrganizationModFunc(func(_ context.Context, o *OrganizationTemplate) { + o.Slug = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f, "24") + return null.From(val) + } + }) +} + func (m organizationMods) WithParentsCascading() OrganizationMod { return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { if isDone, _ := organizationWithParentsCascadingCtx.Value(ctx); isDone { diff --git a/db/migrations/00040_organization_slug.sql b/db/migrations/00040_organization_slug.sql new file mode 100644 index 00000000..3b57452c --- /dev/null +++ b/db/migrations/00040_organization_slug.sql @@ -0,0 +1,4 @@ +-- +goose Up +ALTER TABLE organization ADD COLUMN slug VARCHAR(24) UNIQUE; +-- +goose Down +ALTER TABLE organization DROP COLUMN slug; diff --git a/db/models/organization.bob.go b/db/models/organization.bob.go index af84e294..8876432c 100644 --- a/db/models/organization.bob.go +++ b/db/models/organization.bob.go @@ -34,6 +34,7 @@ type Organization struct { ImportDistrictGid null.Val[int32] `db:"import_district_gid" ` Website null.Val[string] `db:"website" ` LogoUUID null.Val[uuid.UUID] `db:"logo_uuid" ` + Slug null.Val[string] `db:"slug" ` R organizationR `db:"-" ` @@ -93,7 +94,7 @@ type organizationR struct { func buildOrganizationColumns(alias string) organizationColumns { return organizationColumns{ ColumnsExpr: expr.NewColumnsExpr( - "id", "name", "arcgis_id", "arcgis_name", "fieldseeker_url", "import_district_gid", "website", "logo_uuid", + "id", "name", "arcgis_id", "arcgis_name", "fieldseeker_url", "import_district_gid", "website", "logo_uuid", "slug", ).WithParent("organization"), tableAlias: alias, ID: psql.Quote(alias, "id"), @@ -104,6 +105,7 @@ func buildOrganizationColumns(alias string) organizationColumns { ImportDistrictGid: psql.Quote(alias, "import_district_gid"), Website: psql.Quote(alias, "website"), LogoUUID: psql.Quote(alias, "logo_uuid"), + Slug: psql.Quote(alias, "slug"), } } @@ -118,6 +120,7 @@ type organizationColumns struct { ImportDistrictGid psql.Expression Website psql.Expression LogoUUID psql.Expression + Slug psql.Expression } func (c organizationColumns) Alias() string { @@ -140,10 +143,11 @@ type OrganizationSetter struct { ImportDistrictGid omitnull.Val[int32] `db:"import_district_gid" ` Website omitnull.Val[string] `db:"website" ` LogoUUID omitnull.Val[uuid.UUID] `db:"logo_uuid" ` + Slug omitnull.Val[string] `db:"slug" ` } func (s OrganizationSetter) SetColumns() []string { - vals := make([]string, 0, 8) + vals := make([]string, 0, 9) if s.ID.IsValue() { vals = append(vals, "id") } @@ -168,6 +172,9 @@ func (s OrganizationSetter) SetColumns() []string { if !s.LogoUUID.IsUnset() { vals = append(vals, "logo_uuid") } + if !s.Slug.IsUnset() { + vals = append(vals, "slug") + } return vals } @@ -196,6 +203,9 @@ func (s OrganizationSetter) Overwrite(t *Organization) { if !s.LogoUUID.IsUnset() { t.LogoUUID = s.LogoUUID.MustGetNull() } + if !s.Slug.IsUnset() { + t.Slug = s.Slug.MustGetNull() + } } func (s *OrganizationSetter) Apply(q *dialect.InsertQuery) { @@ -204,7 +214,7 @@ func (s *OrganizationSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 8) + vals := make([]bob.Expression, 9) if s.ID.IsValue() { vals[0] = psql.Arg(s.ID.MustGet()) } else { @@ -253,6 +263,12 @@ func (s *OrganizationSetter) Apply(q *dialect.InsertQuery) { vals[7] = psql.Raw("DEFAULT") } + if !s.Slug.IsUnset() { + vals[8] = psql.Arg(s.Slug.MustGetNull()) + } else { + vals[8] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -262,7 +278,7 @@ func (s OrganizationSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s OrganizationSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 8) + exprs := make([]bob.Expression, 0, 9) if s.ID.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -320,6 +336,13 @@ func (s OrganizationSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if !s.Slug.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "slug")...), + psql.Arg(s.Slug), + }}) + } + return exprs } @@ -3847,6 +3870,7 @@ type organizationWhere[Q psql.Filterable] struct { ImportDistrictGid psql.WhereNullMod[Q, int32] Website psql.WhereNullMod[Q, string] LogoUUID psql.WhereNullMod[Q, uuid.UUID] + Slug psql.WhereNullMod[Q, string] } func (organizationWhere[Q]) AliasedAs(alias string) organizationWhere[Q] { @@ -3863,6 +3887,7 @@ func buildOrganizationWhere[Q psql.Filterable](cols organizationColumns) organiz ImportDistrictGid: psql.WhereNull[Q, int32](cols.ImportDistrictGid), Website: psql.WhereNull[Q, string](cols.Website), LogoUUID: psql.WhereNull[Q, uuid.UUID](cols.LogoUUID), + Slug: psql.WhereNull[Q, string](cols.Slug), } } diff --git a/public-report/mock.go b/public-report/mock.go new file mode 100644 index 00000000..fb4d0017 --- /dev/null +++ b/public-report/mock.go @@ -0,0 +1,43 @@ +package publicreport + +import ( + "net/http" + + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/go-chi/chi/v5" +) + +var ( + mockRootT = buildTemplate("mock/root", "base") + mockDistrictRootT = buildTemplate("mock/district-root", "base") +) + +type ContentDistrict struct { + LogoURL string + Name string +} +type ContentMock struct { + District ContentDistrict +} + +func addMockRoutes(r chi.Router) { + r.Get("/", renderMock(mockRootT)) + r.Get("/district/{slug}", renderMock(mockDistrictRootT)) +} + +func renderMock(t *htmlpage.BuiltTemplate) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + slug := chi.URLParam(r, "slug") + htmlpage.RenderOrError( + w, + t, + ContentMock{ + District: ContentDistrict{ + LogoURL: config.MakeURLNidus("/api/district/%s/logo", slug), + Name: "Delta MCD", + }, + }, + ) + } +} diff --git a/public-report/quick.go b/public-report/quick.go index c8d94c32..cfb27a0b 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -75,7 +75,7 @@ func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) { log.Debug().Int32("org_id", org.ID).Int32("d_gid", d.Gid).Msg("Getting district") if d != nil { district = &District{ - LogoURL: config.MakeURLNidus("/api/district/%s/logo", strconv.Itoa(int(org_id))), + LogoURL: config.MakeURLNidus("/api/district/%s/logo", org.Slug.GetOr("placeholder")), Name: d.Agency.GetOr("Unknown"), } } diff --git a/public-report/template/mock/district-root.html b/public-report/template/mock/district-root.html new file mode 100644 index 00000000..bfc8bdca --- /dev/null +++ b/public-report/template/mock/district-root.html @@ -0,0 +1,89 @@ +{{template "base.html" .}} + +{{define "title"}}Main{{end}} +{{define "extraheader"}} + +{{end}} +{{define "content"}} + +
    + +
    +
    +
    +
    +

    Report Mosquitoes for {{.District.Name}}

    + +

    This is the reporting page for mosquito problems in your area.

    +
    +
    +
    +
    + + +
    +
    +

    How Can We Help You Today?

    +
    +
    +
    +
    +
    + {{ template "svg/mosquito" }} +
    +

    Report Mosquito Nuisance

    +

    Report areas with high adult mosquito activity causing discomfort or concern.

    + Report Problem +
    +
    +
    +
    +
    +
    +
    + {{ template "svg/pond" }} +
    +

    Report Standing Water

    +

    Report any water that has been sitting for several days, where mosquitoes can live.

    + Report Source +
    +
    +
    +
    +
    +
    +
    + {{ template "svg/check-report" }} +
    +

    Follow-up or Check Status

    +

    Check on a previous request or view current mosquito activity in your area.

    + Get Status +
    +
    +
    +
    +
    +
    +
    + +{{end}} From 9ca470ffb652ebf3dabd545a2d1132ad43f2bea7 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 19:18:27 +0000 Subject: [PATCH 0098/1453] Center logo, add "powered by" in footer --- public-report/template/component/footer.html | 1 + public-report/template/mock/district-root.html | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public-report/template/component/footer.html b/public-report/template/component/footer.html index eddc65aa..4f119317 100644 --- a/public-report/template/component/footer.html +++ b/public-report/template/component/footer.html @@ -3,6 +3,7 @@
    +

    Powered by Report Mosquitoes Online

    © 2025 Gleipnir LLC

    diff --git a/public-report/template/mock/district-root.html b/public-report/template/mock/district-root.html index bfc8bdca..f8e890d0 100644 --- a/public-report/template/mock/district-root.html +++ b/public-report/template/mock/district-root.html @@ -12,7 +12,10 @@ box-shadow: 0 10px 20px rgba(0,0,0,0.1); } .district-logo { - max-height: 80px; + display:block; + margin-left:auto; + margin-right:auto; + max-height: 88px; width: auto; } .quick-report-mobile { @@ -33,7 +36,7 @@

    Report Mosquitoes for {{.District.Name}}

    - +

    This is the reporting page for mosquito problems in your area.

    From 53397d2609e0cf02c1bfba32fb89e1fd4c7eaf4e Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 19:19:52 +0000 Subject: [PATCH 0099/1453] Add additional subtitle for districts --- public-report/template/mock/district-root.html | 1 + 1 file changed, 1 insertion(+) diff --git a/public-report/template/mock/district-root.html b/public-report/template/mock/district-root.html index f8e890d0..14009aa0 100644 --- a/public-report/template/mock/district-root.html +++ b/public-report/template/mock/district-root.html @@ -38,6 +38,7 @@

    Report Mosquitoes for {{.District.Name}}

    This is the reporting page for mosquito problems in your area.

    +

    Reports submitted here are reviewed by {{.District.Name}}, which helps identify and address mosquito problems in your community.

    From db75826e59939bb93e3c115f8f61eeefa47b96fe Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 19:32:19 +0000 Subject: [PATCH 0100/1453] Add link to new nuisance mock --- public-report/mock.go | 22 +- .../template/mock/district-root.html | 4 +- public-report/template/mock/nuisance.html | 500 ++++++++++++++++++ 3 files changed, 521 insertions(+), 5 deletions(-) create mode 100644 public-report/template/mock/nuisance.html diff --git a/public-report/mock.go b/public-report/mock.go index fb4d0017..d9746194 100644 --- a/public-report/mock.go +++ b/public-report/mock.go @@ -9,23 +9,38 @@ import ( ) var ( - mockRootT = buildTemplate("mock/root", "base") mockDistrictRootT = buildTemplate("mock/district-root", "base") + mockNuisanceT = buildTemplate("mock/nuisance", "base") + mockRootT = buildTemplate("mock/root", "base") ) type ContentDistrict struct { - LogoURL string Name string + URLLogo string +} +type ContentURL struct { + Nuisance string } type ContentMock struct { District ContentDistrict + URL ContentURL } func addMockRoutes(r chi.Router) { r.Get("/", renderMock(mockRootT)) + r.Get("/nuisance", renderMock(mockNuisanceT)) r.Get("/district/{slug}", renderMock(mockDistrictRootT)) } +func makeContentURL() ContentURL { + return ContentURL{ + Nuisance: makeURLMock("nuisance"), + } +} + +func makeURLMock(p string) string { + return config.MakeURLReport("/mock/%s", p) +} func renderMock(t *htmlpage.BuiltTemplate) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { slug := chi.URLParam(r, "slug") @@ -34,9 +49,10 @@ func renderMock(t *htmlpage.BuiltTemplate) func(http.ResponseWriter, *http.Reque t, ContentMock{ District: ContentDistrict{ - LogoURL: config.MakeURLNidus("/api/district/%s/logo", slug), Name: "Delta MCD", + URLLogo: config.MakeURLNidus("/api/district/%s/logo", slug), }, + URL: makeContentURL(), }, ) } diff --git a/public-report/template/mock/district-root.html b/public-report/template/mock/district-root.html index 14009aa0..68a0b33e 100644 --- a/public-report/template/mock/district-root.html +++ b/public-report/template/mock/district-root.html @@ -36,7 +36,7 @@

    Report Mosquitoes for {{.District.Name}}

    - +

    This is the reporting page for mosquito problems in your area.

    Reports submitted here are reviewed by {{.District.Name}}, which helps identify and address mosquito problems in your community.

    @@ -57,7 +57,7 @@

    Report Mosquito Nuisance

    Report areas with high adult mosquito activity causing discomfort or concern.

    - Report Problem + Report Problem
    diff --git a/public-report/template/mock/nuisance.html b/public-report/template/mock/nuisance.html new file mode 100644 index 00000000..14643efb --- /dev/null +++ b/public-report/template/mock/nuisance.html @@ -0,0 +1,500 @@ +{{template "base.html" .}} + +{{define "title"}}Nuisance{{end}} +{{define "extraheader"}} + + +{{end}} +{{define "content"}} +
    + +
    +
    +

    Report Mosquito Nuisance

    +

    Help us identify mosquito activity in your area

    +
    +
    + + +
    +
    + +
    +
    + + + + +
    +
    + +

    Mosquito Activity Information

    + optional +
    +

    The time when mosquitoes are active can help us identify the species and likely breeding sources.

    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    + + +
    + + +
    +
    +
    Minor
    + Occasional mosquito +
    +
    +
    Moderate
    + Regular presence +
    +
    +
    Severe
    + Many mosquitoes +
    +
    +
    + Current selection: 3/5 +
    +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + +

    Potential Mosquito Sources

    + optional +
    +

    Have you noticed any of these common mosquito breeding sources in your area?

    + +
    +
    +
    Did you know?
    +

    Mosquitoes can breed in as little as a bottle cap of water! Eliminating standing water is the most effective way to reduce mosquito populations.

    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    Stagnant Water
    +

    Green pools, ponds, fountains, or birdbaths that aren't maintained

    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + +
    +
    Containers
    +

    Buckets, planters, toys, tires, or any items that collect rainwater

    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + +
    +
    Roof & Gutters
    +

    Clogged gutters, flat roofs, or AC units that collect water

    +
    + + +
    +
    +
    +
    +
    + + + +
    +
    + + +
    +
    +
    + + +
    +
    + +

    Inspection Request

    +
    +

    Would you like our technicians to inspect for potential mosquito sources?

    + +
    +
    +
    +
    Property Inspection
    +

    Request a technician to inspect your property for mosquito sources. We'll contact you to schedule a convenient time.

    +
    + + +
    +
    +
    + +
    +
    +
    Neighborhood Inspection
    +

    Request a general inspection of your neighborhood. We'll survey the area for potential mosquito breeding sources.

    +
    + + +
    +
    +
    +
    + + + +
    + + +
    +
    + +

    Location & Contact Information

    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    We'll use this to send you a confirmation and follow-up information.
    +
    +
    +
    + +
    +
    + +

    Additional Information

    + optional +
    + +
    +
    + + +
    +
    +
    + + +
    +
    +
    +

    Thank you for reporting this mosquito issue.

    +

    After submission, you'll receive a confirmation with a report ID and further information.

    +
    +
    + +
    +
    +
    + +
    +{{end}} From 46fcfa88ad33d8e797f4451d86524f97f53f58f6 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 19:36:21 +0000 Subject: [PATCH 0101/1453] Remove inspection request, reorder location & contact --- public-report/template/mock/nuisance.html | 138 +++++----------------- 1 file changed, 31 insertions(+), 107 deletions(-) diff --git a/public-report/template/mock/nuisance.html b/public-report/template/mock/nuisance.html index 14643efb..37fd0da1 100644 --- a/public-report/template/mock/nuisance.html +++ b/public-report/template/mock/nuisance.html @@ -164,6 +164,37 @@ document.addEventListener('DOMContentLoaded', function() {
    + +
    +
    + +

    Location & Contact Information

    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    We'll use this to send you a confirmation and follow-up information.
    +
    +
    +
    +
    @@ -359,113 +390,6 @@ document.addEventListener('DOMContentLoaded', function() {
    - -
    -
    - -

    Inspection Request

    -
    -

    Would you like our technicians to inspect for potential mosquito sources?

    - -
    -
    -
    -
    Property Inspection
    -

    Request a technician to inspect your property for mosquito sources. We'll contact you to schedule a convenient time.

    -
    - - -
    -
    -
    - -
    -
    -
    Neighborhood Inspection
    -

    Request a general inspection of your neighborhood. We'll survey the area for potential mosquito breeding sources.

    -
    - - -
    -
    -
    -
    - - - -
    - - -
    -
    - -

    Location & Contact Information

    -
    - -
    -
    - - -
    -
    - -
    -
    - - -
    -
    - - -
    -
    - - -
    We'll use this to send you a confirmation and follow-up information.
    -
    -
    -
    -
    From 03a97f30a8004fcb4c7a784ec98ef9797a510344 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 19:44:32 +0000 Subject: [PATCH 0102/1453] Change roof to street gutters --- public-report/template/mock/nuisance.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public-report/template/mock/nuisance.html b/public-report/template/mock/nuisance.html index 37fd0da1..e61d1f86 100644 --- a/public-report/template/mock/nuisance.html +++ b/public-report/template/mock/nuisance.html @@ -365,9 +365,9 @@ document.addEventListener('DOMContentLoaded', function() {
    Roof & Gutters
    -

    Clogged gutters, flat roofs, or AC units that collect water

    +

    Clogged street gutters, yard drains, or AC units that collect water

    - + From ee1ee1e90171b1f73468b644fda4158aee3f5b5c Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 19:55:09 +0000 Subject: [PATCH 0103/1453] Add map and photo upload to nuisance report page --- htmlpage/static/js/map-locator.js | 14 ++- public-report/mock.go | 8 +- public-report/template/mock/nuisance.html | 121 ++++++++++++++++++---- public-report/template/pool.html | 1 - 4 files changed, 113 insertions(+), 31 deletions(-) diff --git a/htmlpage/static/js/map-locator.js b/htmlpage/static/js/map-locator.js index 012ba60d..65b94cd1 100644 --- a/htmlpage/static/js/map-locator.js +++ b/htmlpage/static/js/map-locator.js @@ -12,7 +12,7 @@ class MapLocator extends HTMLElement { this.render(); // markers shown on the map. Should be none or 1, generally. - this._markers = null; + this._markers = []; } // Lifecycle: when element is added to the DOM @@ -34,9 +34,9 @@ class MapLocator extends HTMLElement { const lng = Number(this.getAttribute('longitude') || -119.2); const zoom = Number(this.getAttribute('zoom') || 15); - const mapElement = this.shadowRoot.querySelector("#map"); mapboxgl.accessToken = apiKey; - map = new mapboxgl.Map({ + const mapElement = this.shadowRoot.querySelector("#map"); + this._map = new mapboxgl.Map({ container: mapElement, center: { lat: lat, @@ -45,6 +45,7 @@ class MapLocator extends HTMLElement { style: 'mapbox://styles/mapbox/streets-v12', // style URL zoom: zoom, }); + /* map.addControl(new mapboxgl.GeolocateControl({ positionOptions: { enableHighAccuracy: true @@ -53,7 +54,9 @@ class MapLocator extends HTMLElement { showUserHeading: true })); map.addControl(new mapboxgl.NavigationControl()); - map.on("load", function() { + */ + this._map.on("load", () => { + console.log("map loaded"); this.dispatchEvent(new CustomEvent('load'), { bubbles: true, composed: true, // Allows event to cross shadow DOM boundary @@ -67,6 +70,7 @@ class MapLocator extends HTMLElement { // Initial render of component render() { this.shadowRoot.innerHTML = ` + + +{{end}} +{{define "content"}} +
    +
    +
    + +
    +
    +

    + + + + Report Successfully Submitted +

    +
    +
    +
    +

    Thank you for helping us control mosquito populations in your area!

    +
    + Your Report ID: + {{.ReportID|publicReportID}} +
    +

    Please save this ID for your reference.

    + {{ if not (eq .District nil) }} +

    Your report has been assigned to

    +

    {{ .District.Name }}

    + + {{ end }} +
    + +
    + + +
    +

    + + + + Check Your Report Status +

    +

    You can check the status of your report at any time using your Report ID.

    + + Check Status + +
    + +
    + + +
    +

    + + + + Get Updates +

    +

    Provide your contact information to receive updates about your report.

    + + + + +
    + +
    + + + + + + +
    +
    + +
    + +
    + + + + + + + +
    +
    + +
    + + +
    + You must consent to receive notifications. +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    +{{end}} diff --git a/public-report/template/mock/nuisance.html b/public-report/template/mock/nuisance.html index 0729e830..ed22a714 100644 --- a/public-report/template/mock/nuisance.html +++ b/public-report/template/mock/nuisance.html @@ -502,9 +502,9 @@ document.addEventListener('DOMContentLoaded', function() {

    After submission, you'll receive a confirmation with a report ID and further information.

    From 8f58859693b9a5669fbe9b291407e96dad2823a6 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 20:45:40 +0000 Subject: [PATCH 0106/1453] Make report complete page show after nuisance --- public-report/mock.go | 3 +++ public-report/template/mock/nuisance-submit-complete.html | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/public-report/mock.go b/public-report/mock.go index 02aa98aa..83d2805f 100644 --- a/public-report/mock.go +++ b/public-report/mock.go @@ -50,6 +50,9 @@ func makeURLMock(p string) string { func renderMock(t *htmlpage.BuiltTemplate) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { slug := chi.URLParam(r, "slug") + if slug == "" { + slug = "delta-mvcd" + } htmlpage.RenderOrError( w, t, diff --git a/public-report/template/mock/nuisance-submit-complete.html b/public-report/template/mock/nuisance-submit-complete.html index 333059d6..dbb71286 100644 --- a/public-report/template/mock/nuisance-submit-complete.html +++ b/public-report/template/mock/nuisance-submit-complete.html @@ -30,9 +30,9 @@

    Please save this ID for your reference.

    {{ if not (eq .District nil) }} -

    Your report has been assigned to

    +

    Your report will be handled by

    {{ .District.Name }}

    - + {{ end }} From 9e586ae6efc4f67f522206beb478ab177b871f15 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 20:45:51 +0000 Subject: [PATCH 0107/1453] Update wording for district landing page At Ben's request --- public-report/template/mock/district-root.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public-report/template/mock/district-root.html b/public-report/template/mock/district-root.html index 68a0b33e..42ca10a0 100644 --- a/public-report/template/mock/district-root.html +++ b/public-report/template/mock/district-root.html @@ -35,10 +35,10 @@
    -

    Report Mosquitoes for {{.District.Name}}

    +

    Report a Mosquito Problem

    -

    This is the reporting page for mosquito problems in your area.

    -

    Reports submitted here are reviewed by {{.District.Name}}, which helps identify and address mosquito problems in your community.

    +

    Submit a report to help reduce mosquito activity in your neighborhood.

    +

    Report Mosquitoes Online works with local mosquito control agencies to receive public reports. For this area, mosquito control services are provided by Delta Mosquito and Vector Control District.

    From 9aee938e30b6ab7ec8befb45488f99809c3ac606 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 21:00:30 +0000 Subject: [PATCH 0108/1453] Add status searching page --- public-report/mock.go | 8 +- .../template/mock/district-root.html | 2 +- public-report/template/mock/status.html | 204 ++++++++++++++++++ 3 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 public-report/template/mock/status.html diff --git a/public-report/mock.go b/public-report/mock.go index 83d2805f..d7b10ce0 100644 --- a/public-report/mock.go +++ b/public-report/mock.go @@ -13,6 +13,7 @@ var ( mockNuisanceT = buildTemplate("mock/nuisance", "base") mockNuisanceSubmitCompleteT = buildTemplate("mock/nuisance-submit-complete", "base") mockRootT = buildTemplate("mock/root", "base") + mockStatusT = buildTemplate("mock/status", "base") ) type ContentDistrict struct { @@ -22,6 +23,8 @@ type ContentDistrict struct { type ContentURL struct { Nuisance string NuisanceSubmitComplete string + Status string + Tegola string } type ContentMock struct { District ContentDistrict @@ -32,15 +35,18 @@ type ContentMock struct { func addMockRoutes(r chi.Router) { r.Get("/", renderMock(mockRootT)) + r.Get("/district/{slug}", renderMock(mockDistrictRootT)) r.Get("/nuisance", renderMock(mockNuisanceT)) r.Get("/nuisance-submit-complete", renderMock(mockNuisanceSubmitCompleteT)) - r.Get("/district/{slug}", renderMock(mockDistrictRootT)) + r.Get("/status", renderMock(mockStatusT)) } func makeContentURL() ContentURL { return ContentURL{ Nuisance: makeURLMock("nuisance"), NuisanceSubmitComplete: makeURLMock("nuisance-submit-complete"), + Status: makeURLMock("status"), + Tegola: config.MakeURLTegola("/"), } } diff --git a/public-report/template/mock/district-root.html b/public-report/template/mock/district-root.html index 42ca10a0..8cb54251 100644 --- a/public-report/template/mock/district-root.html +++ b/public-report/template/mock/district-root.html @@ -81,7 +81,7 @@

    Follow-up or Check Status

    Check on a previous request or view current mosquito activity in your area.

    - Get Status + Get Status diff --git a/public-report/template/mock/status.html b/public-report/template/mock/status.html new file mode 100644 index 00000000..2b8dd8f2 --- /dev/null +++ b/public-report/template/mock/status.html @@ -0,0 +1,204 @@ +{{template "base.html" .}} + +{{define "title"}}Status{{end}} +{{define "extraheader"}} + + + + + + + +{{end}} +{{define "content"}} +
    + + + + +
    +
    +
    Reports Map
    +
    +
    + +
    +
    + + +
    +
    +
    Reports Near You
    + 15 Reports Found +
    +
    +
    + +
    +
    + +
    +
    +{{end}} From 65c3e8ee5198d3f339b251cb41e6c63a0268379e Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 21:29:24 +0000 Subject: [PATCH 0109/1453] Add branded header for nuisance report --- public-report/mock.go | 19 +++++++++++-------- public-report/page.go | 2 +- public-report/template/component/header.html | 13 +++++++++++++ public-report/template/mock/nuisance.html | 3 +++ 4 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 public-report/template/component/header.html diff --git a/public-report/mock.go b/public-report/mock.go index d7b10ce0..2d7b3469 100644 --- a/public-report/mock.go +++ b/public-report/mock.go @@ -36,22 +36,25 @@ type ContentMock struct { func addMockRoutes(r chi.Router) { r.Get("/", renderMock(mockRootT)) r.Get("/district/{slug}", renderMock(mockDistrictRootT)) + r.Get("/district/{slug}/nuisance", renderMock(mockNuisanceT)) + r.Get("/district/{slug}/nuisance-submit-complete", renderMock(mockNuisanceSubmitCompleteT)) + r.Get("/district/{slug}/status", renderMock(mockStatusT)) r.Get("/nuisance", renderMock(mockNuisanceT)) r.Get("/nuisance-submit-complete", renderMock(mockNuisanceSubmitCompleteT)) r.Get("/status", renderMock(mockStatusT)) } -func makeContentURL() ContentURL { +func makeContentURL(slug string) ContentURL { return ContentURL{ - Nuisance: makeURLMock("nuisance"), - NuisanceSubmitComplete: makeURLMock("nuisance-submit-complete"), - Status: makeURLMock("status"), + Nuisance: makeURLMock(slug, "nuisance"), + NuisanceSubmitComplete: makeURLMock(slug, "nuisance-submit-complete"), + Status: makeURLMock(slug, "status"), Tegola: config.MakeURLTegola("/"), } } -func makeURLMock(p string) string { - return config.MakeURLReport("/mock/%s", p) +func makeURLMock(slug, p string) string { + return config.MakeURLReport("/mock/district/%s/%s", slug, p) } func renderMock(t *htmlpage.BuiltTemplate) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { @@ -64,12 +67,12 @@ func renderMock(t *htmlpage.BuiltTemplate) func(http.ResponseWriter, *http.Reque t, ContentMock{ District: ContentDistrict{ - Name: "Delta MCD", + Name: "Delta MVCD", URLLogo: config.MakeURLNidus("/api/district/%s/logo", slug), }, MapboxToken: config.MapboxToken, ReportID: "abcd-1234-5678", - URL: makeContentURL(), + URL: makeContentURL(slug), }, ) } diff --git a/public-report/page.go b/public-report/page.go index 0b166703..2785e7e6 100644 --- a/public-report/page.go +++ b/public-report/page.go @@ -10,7 +10,7 @@ import ( //go:embed template/* var embeddedFiles embed.FS -var components = [...]string{"footer", "photo-upload", "photo-upload-header"} +var components = [...]string{"footer", "header", "photo-upload", "photo-upload-header"} var svgs = [...]string{"check-report", "mosquito", "pond"} func buildTemplate(files ...string) *htmlpage.BuiltTemplate { diff --git a/public-report/template/component/header.html b/public-report/template/component/header.html new file mode 100644 index 00000000..453a2aa3 --- /dev/null +++ b/public-report/template/component/header.html @@ -0,0 +1,13 @@ +{{define "header"}} + + +{{end}} diff --git a/public-report/template/mock/nuisance.html b/public-report/template/mock/nuisance.html index ed22a714..0883a3f7 100644 --- a/public-report/template/mock/nuisance.html +++ b/public-report/template/mock/nuisance.html @@ -236,6 +236,9 @@ document.addEventListener('DOMContentLoaded', function() { {{end}} {{define "content"}} +{{if .District}} + {{template "header" .}} +{{end}}
    From c6fd6295a078e659172017afc2c10d132ac99039 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 21:30:25 +0000 Subject: [PATCH 0110/1453] Add district header to seach page satisfies a requirement. --- public-report/template/mock/status.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public-report/template/mock/status.html b/public-report/template/mock/status.html index 2b8dd8f2..eeab00c5 100644 --- a/public-report/template/mock/status.html +++ b/public-report/template/mock/status.html @@ -125,6 +125,9 @@ document.addEventListener('DOMContentLoaded', onLoad); {{end}} {{define "content"}} +{{if .District}} + {{template "header" .}} +{{end}}

    Report Standing Water

    Report any water that has been sitting for several days, where mosquitoes can live.

    - Report Source + Report Water
    diff --git a/public-report/template/mock/water.html b/public-report/template/mock/water.html new file mode 100644 index 00000000..52926830 --- /dev/null +++ b/public-report/template/mock/water.html @@ -0,0 +1,569 @@ +{{template "base.html" .}} + +{{define "title"}}Green Pool{{end}} +{{define "extraheader"}} + + + + + + +{{template "photo-upload-header"}} + + +{{end}} +{{define "content"}} +{{if .District}} + {{template "header" .}} +{{end}} + +
    +
    + +
    +
    +

    Report Standing Water

    +

    Help us locate and treat potential mosquito production sources in your area

    +
    +
    + + +
    +
    + +
    +
    + + +
    + +
    +
    + +

    Photos

    +
    +

    Photos help us identify the severity of the issue and may contain location data that can help us find the source.

    +
    + {{template "photo-upload"}} +
    +
    + + +
    +
    + +

    Location

    +
    +

    Please provide the location of the potential mosquito production source. We may be able to extract this information from your photos if they contain location data.

    + +
    + + + + + + + + + + +
    +
    + + +
    +
    +
    + +
    +
    + +

    You can also click on the map to mark the location precisely

    + + +
    + + +
    +
    + +

    Source Details

    +
    + +
    +
    + + +
    + +
    + +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +

    Access Information

    +
    +

    Please provide any details about how to access the mosquito source. This helps our technicians when they visit the site.

    + +
    +
    + + +
    +
    + +
    +
    + +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + +
    +
    + +

    Contact Information

    +
    + + +
    Property Owner Information (if known)
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    Your Contact Information (for updates)
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +

    Additional Information

    +
    +

    Please provide any other information that might help us address this mosquito source.

    + +
    +
    + + +
    +
    +
    + + +
    +
    +
    +

    Thank you for helping us keep our community safe from mosquito-borne illnesses.

    +

    After submission, you will receive a confirmation with a report ID for tracking purposes.

    +
    +
    + +
    +
    +
    +
    + + + +
    +
    + + + +{{end}} From d552a18c0b63e0f8afdfcb80b8f353a5cae6adad Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 21:43:51 +0000 Subject: [PATCH 0112/1453] Remove location details by request --- public-report/template/mock/water.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/public-report/template/mock/water.html b/public-report/template/mock/water.html index 52926830..7542f5f5 100644 --- a/public-report/template/mock/water.html +++ b/public-report/template/mock/water.html @@ -215,7 +215,6 @@ document.addEventListener('DOMContentLoaded', function() { console.log("location error", error); }) }) - mapLocator.addEventListener("markerdragend", let mapZoom = document.getElementById('map-zoom'); mapLocator.addEventListener("zoomend", function(e) { mapZoom.value = e.target.getZoom(); @@ -316,9 +315,6 @@ function displaySelectedCoordinates(lngLat) {
    -
    - -

    You can also click on the map to mark the location precisely

    From d59c6197292f1f00e084958ca2023d123dbb89bd Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 21:47:24 +0000 Subject: [PATCH 0113/1453] Move additional details to below photo as requested --- public-report/template/mock/water.html | 39 +++++++++++++------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/public-report/template/mock/water.html b/public-report/template/mock/water.html index 7542f5f5..c86023bd 100644 --- a/public-report/template/mock/water.html +++ b/public-report/template/mock/water.html @@ -275,19 +275,36 @@ function displaySelectedCoordinates(lngLat) { -
    +

    Photos

    -

    Photos help us identify the severity of the issue and may contain location data that can help us find the source.

    +

    Photos help us identify the severity of the issue and may contain location data that can help us find the production source.

    {{template "photo-upload"}}
    + +
    +
    + +

    Additional Information

    +
    +

    Please provide any other information that might help us address this mosquito production source.

    + +
    +
    + + +
    +
    +
    + +
    @@ -331,7 +348,7 @@ function displaySelectedCoordinates(lngLat) {
    - + -
    -
    -
    -
    From 135e2ef77d634a539180c61408d962247d048708 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 21:50:28 +0000 Subject: [PATCH 0114/1453] Put additional fields behind collapse button --- public-report/template/mock/water.html | 256 +++++++++++++------------ 1 file changed, 135 insertions(+), 121 deletions(-) diff --git a/public-report/template/mock/water.html b/public-report/template/mock/water.html index c86023bd..221f2b76 100644 --- a/public-report/template/mock/water.html +++ b/public-report/template/mock/water.html @@ -162,6 +162,14 @@ function setLocationInputs(location) { street.value = context.country.name; } +function toggleCollapse(something) { + el = document.getElementById(something) + if (el.classList.contains('collapse')) { + el.classList.remove('collapse'); + } else { + el.classList.add('collapse'); + } +} document.addEventListener('DOMContentLoaded', function() { // Elements const photoInput = document.getElementById('photos'); @@ -339,145 +347,151 @@ function displaySelectedCoordinates(lngLat) {
    - -
    -
    - -

    Source Details

    -
    - -
    -
    - - + +
    + + +
    +
    + +

    Source Details

    -
    - -
    - - +
    +
    + +
    -
    - - -
    -
    - - + +
    + +
    + + +
    +
    + + +
    +
    + + +
    -
    - -
    -
    - -

    Access Information

    -
    -

    Please provide any details about how to access the mosquito source. This helps our technicians when they visit the site.

    - -
    -
    - - + +
    +
    + +

    Access Information

    -
    - -
    -
    - -
    -
    -
    - - +

    Please provide any details about how to access the mosquito source. This helps our technicians when they visit the site.

    + +
    +
    + + +
    +
    + +
    +
    + +
    +
    +
    + + +
    +
    + + +
    -
    - - +
    +
    + + +
    +
    + + +
    -
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    - - +
    +
    + + +
    -
    - -
    -
    - -

    Contact Information

    -
    - - -
    Property Owner Information (if known)
    -
    -
    - - + +
    +
    + +

    Contact Information

    -
    - - + + +
    Property Owner Information (if known)
    +
    +
    + + +
    +
    + + +
    +
    + + +
    -
    - - -
    -
    - - -
    Your Contact Information (for updates)
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    - - + + +
    Your Contact Information (for updates)
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    From 0407f270ade6d67b634bea4c79c125460494cdb4 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 21:52:54 +0000 Subject: [PATCH 0115/1453] Fix page title for standing water --- public-report/template/mock/water.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public-report/template/mock/water.html b/public-report/template/mock/water.html index 221f2b76..53cdc550 100644 --- a/public-report/template/mock/water.html +++ b/public-report/template/mock/water.html @@ -1,6 +1,6 @@ {{template "base.html" .}} -{{define "title"}}Green Pool{{end}} +{{define "title"}}Standing Water{{end}} {{define "extraheader"}} From 16477a9f5aee938c362c54f97f05d7e3b9c1b92b Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 21:53:06 +0000 Subject: [PATCH 0116/1453] Remove back button --- public-report/template/mock/water.html | 9 --------- 1 file changed, 9 deletions(-) diff --git a/public-report/template/mock/water.html b/public-report/template/mock/water.html index 53cdc550..aed3ad70 100644 --- a/public-report/template/mock/water.html +++ b/public-report/template/mock/water.html @@ -512,15 +512,6 @@ function displaySelectedCoordinates(lngLat) {
    - - -
    From dd57cacd3b0c5a035537a992540be6841d72d391 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 21:55:10 +0000 Subject: [PATCH 0117/1453] Link up water report with confirmation page --- public-report/template/mock/water.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public-report/template/mock/water.html b/public-report/template/mock/water.html index aed3ad70..dd5d244d 100644 --- a/public-report/template/mock/water.html +++ b/public-report/template/mock/water.html @@ -505,9 +505,9 @@ function displaySelectedCoordinates(lngLat) {

    After submission, you will receive a confirmation with a report ID for tracking purposes.

    From 1f52dda56ded4fa83120f95eea7ce9a034c10947 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 21:58:22 +0000 Subject: [PATCH 0118/1453] Fix bug icon. --- public-report/template/mock/nuisance.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public-report/template/mock/nuisance.html b/public-report/template/mock/nuisance.html index 0883a3f7..426ef1ff 100644 --- a/public-report/template/mock/nuisance.html +++ b/public-report/template/mock/nuisance.html @@ -284,7 +284,7 @@ document.addEventListener('DOMContentLoaded', function() {
    - + {{ template "svg/mosquito" }}

    Mosquito Activity Information

    optional
    From 5e9c0d9f11201cd5625d236eb80a6f8ba8edd7fe Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 24 Jan 2026 22:00:08 +0000 Subject: [PATCH 0119/1453] Remove the address display By request --- public-report/template/mock/nuisance.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/public-report/template/mock/nuisance.html b/public-report/template/mock/nuisance.html index 426ef1ff..0cb6e57b 100644 --- a/public-report/template/mock/nuisance.html +++ b/public-report/template/mock/nuisance.html @@ -273,9 +273,6 @@ document.addEventListener('DOMContentLoaded', function() {
    -
    - -

    You can also click on the map to mark the location precisely

    From c0b6398de283b15d069831b443fc2a309198a4b9 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sun, 25 Jan 2026 18:47:22 +0000 Subject: [PATCH 0120/1453] Overhaul text messaging system to be like emails It's a better system for organization and makes it so we can have better logs about what gets sent. --- background/background.go | 3 +- background/text.go | 54 ++----- comms/text/db.go | 93 ++++++++++++ comms/text/job.go | 39 +++++ comms/text/report-subscription.go | 56 +++++++ comms/{ => text}/text.go | 19 ++- db/dberrors/comms.text_log.bob.go | 2 +- db/dberrors/organization.bob.go | 9 ++ db/dbinfo/comms.text_log.bob.go | 52 ++++--- db/dbinfo/organization.bob.go | 28 +++- db/enums/enums.bob.go | 57 ++++--- db/factory/bobfactory_main.bob.go | 4 +- db/factory/bobfactory_random.bob.go | 4 +- db/factory/comms.text_log.bob.go | 172 ++++++++++++++++------ db/migrations/00041_text_log_overhaul.sql | 34 +++++ db/models/comms.text_log.bob.go | 162 ++++++++++++-------- public-report/quick.go | 4 +- public-report/template/mock/nuisance.html | 2 +- public-report/template/mock/water.html | 2 +- 19 files changed, 577 insertions(+), 219 deletions(-) create mode 100644 comms/text/db.go create mode 100644 comms/text/job.go create mode 100644 comms/text/report-subscription.go rename comms/{ => text}/text.go (74%) create mode 100644 db/migrations/00041_text_log_overhaul.sql diff --git a/background/background.go b/background/background.go index 6afdabb4..a5c44584 100644 --- a/background/background.go +++ b/background/background.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/Gleipnir-Technology/nidus-sync/comms/email" + "github.com/Gleipnir-Technology/nidus-sync/comms/text" ) var waitGroup sync.WaitGroup @@ -14,7 +15,7 @@ func Start(ctx context.Context) { channelJobAudio = make(chan jobAudio, 100) // Buffered channel to prevent blocking channelJobEmail = make(chan email.Job, 100) // Buffered channel to prevent blocking - channelJobText = make(chan jobText, 100) // Buffered channel to prevent blocking + channelJobText = make(chan text.Job, 100) // Buffered channel to prevent blocking waitGroup.Add(1) go func() { diff --git a/background/text.go b/background/text.go index 853f7041..80de3b2b 100644 --- a/background/text.go +++ b/background/text.go @@ -2,34 +2,23 @@ package background import ( "context" - "errors" - "fmt" - "github.com/Gleipnir-Technology/nidus-sync/comms" + "github.com/Gleipnir-Technology/nidus-sync/comms/text" "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/rs/zerolog/log" ) -var channelJobText chan jobText +var channelJobText chan text.Job -func ReportSubscriptionConfirmationText(destination comms.E164, report_id string) { - enqueueJobText(jobText{ - Destination: destination, - ReportID: report_id, - Source: config.RMOPhoneNumber, - Type: enums.CommsMessagetypetextReportSubscriptionConfirmation, - }) +func ReportSubscriptionConfirmationText(destination text.E164, report_id string) { + enqueueJobText(text.NewJobReportSubscriptionConfirmation( + destination, + report_id, + config.RMOPhoneNumber, + )) } -type jobText struct { - Destination comms.E164 - ReportID string - Source comms.E164 - Type enums.CommsMessagetypetext -} - -func enqueueJobText(job jobText) { +func enqueueJobText(job text.Job) { select { case channelJobText <- job: log.Info().Msg("Enqueued text job") @@ -38,7 +27,7 @@ func enqueueJobText(job jobText) { } } -func startWorkerText(ctx context.Context, channel chan jobText) { +func startWorkerText(ctx context.Context, channel chan text.Job) { go func() { for { select { @@ -46,29 +35,8 @@ func startWorkerText(ctx context.Context, channel chan jobText) { log.Info().Msg("Email worker shutting down.") return case job := <-channel: - err := jobProcessText(job) - if err != nil { - log.Error().Err(err).Str("type", string(job.Type)).Msg("Error processing text message job") - } + text.Handle(ctx, job) } } }() } - -func jobProcessText(job jobText) error { - var message string - switch job.Type { - case enums.CommsMessagetypetextInitialContact: - message = "This is Report Mosquitoes Online. We just got your number. Text \"YES\" to get texts, or \"STOP\" to stap." - case enums.CommsMessagetypetextReportSubscriptionConfirmation: - message = "Thanks for submitting a mosquito report. Text for any questions. We'll send you updates as we get them." - default: - return errors.New("No idea what message to send") - } - err := comms.SendText(job.Source, job.Destination, message) - if err != nil { - log.Error().Err(err).Msg("Failed to send text message") - return fmt.Errorf("Failed to send message '%s' to '%s'", job.Type, job.Destination) - } - return nil -} diff --git a/comms/text/db.go b/comms/text/db.go new file mode 100644 index 00000000..5b609df9 --- /dev/null +++ b/comms/text/db.go @@ -0,0 +1,93 @@ +package text + +import ( + "context" + "crypto/sha256" + "database/sql" + "encoding/hex" + "fmt" + "sort" + "strings" + "time" + + "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/aarondl/opt/omit" + "github.com/rs/zerolog/log" + "github.com/stephenafamo/bob/types/pgtypes" +) + +func convertToPGData(data map[string]string) pgtypes.HStore { + result := pgtypes.HStore{} + for k, v := range data { + result[k] = sql.Null[string]{V: v, Valid: true} + } + return result +} + +func ensureInDB(ctx context.Context, destination string) (err error) { + _, err = models.FindCommsPhone(ctx, db.PGInstance.BobDB, destination) + if err != nil { + // doesn't exist + if err.Error() == "sql: no rows in result set" { + _, err = models.CommsPhones.Insert(&models.CommsPhoneSetter{ + E164: omit.From(destination), + IsSubscribed: omit.From(false), + }).One(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to insert new phone contact: %w", err) + } + log.Info().Str("phone", destination).Msg("Added text to the comms database") + return nil + } + return fmt.Errorf("Unexpected error searching for phone contact: %w", err) + } + return nil +} + +func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin) (err error) { + _, err = models.CommsTextLogs.Insert(&models.CommsTextLogSetter{ + //ID: + Content: omit.From(content), + Created: omit.From(time.Now()), + Destination: omit.From(destination), + Origin: omit.From(origin), + Source: omit.From(source), + }).One(ctx, db.PGInstance.BobDB) + + return err +} +func generatePublicId(t enums.CommsMessagetypeemail, m map[string]string) string { + if m == nil || len(m) == 0 { + // Return hash of empty string for empty maps + emptyHash := sha256.Sum256([]byte("")) + return hex.EncodeToString(emptyHash[:]) + } + + // Get and sort keys for deterministic ordering + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + + // Build a string with all key-value pairs + var sb strings.Builder + // Add type first + sb.WriteString(fmt.Sprintf("type:%s,", t)) + for _, k := range keys { + sb.WriteString(k) + sb.WriteString(":") // Separator between key and value + sb.WriteString(m[k]) + sb.WriteString(",") // Separator between pairs + } + + // Compute SHA-256 hash + hasher := sha256.New() + hasher.Write([]byte(sb.String())) + hashBytes := hasher.Sum(nil) + + // Convert to hex string and return + return hex.EncodeToString(hashBytes) +} diff --git a/comms/text/job.go b/comms/text/job.go new file mode 100644 index 00000000..4d2bfdf3 --- /dev/null +++ b/comms/text/job.go @@ -0,0 +1,39 @@ +package text + +import ( + "context" + + "github.com/rs/zerolog/log" +) + +type MessageType int + +const ( + ReportSubscription MessageType = iota +) + +type Job interface { + content() string + destination() string + messageType() MessageType + messageTypeName() string + source() string +} + +func Handle(ctx context.Context, job Job) { + var err error + switch job.messageType() { + case ReportSubscription: + err = sendReportSubscription(ctx, job) + } + if err != nil { + log.Error().Err(err).Str("dest", job.destination()).Str("type", string(job.messageTypeName())).Msg("Error processing email") + return + } + /* + case enums.CommsMessagetypeemailReportStatusScheduled: + case enums.CommsMessagetypeemailReportStatusComplete: + + } + */ +} diff --git a/comms/text/report-subscription.go b/comms/text/report-subscription.go new file mode 100644 index 00000000..1dd1f193 --- /dev/null +++ b/comms/text/report-subscription.go @@ -0,0 +1,56 @@ +package text + +import ( + "context" + "fmt" + + "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/nyaruka/phonenumbers" + //"github.com/rs/zerolog/log" +) + +func NewJobReportSubscriptionConfirmation( + destination E164, + report_id string, + source E164) jobReportSubscription { + return jobReportSubscription{ + dst: destination, + reportID: report_id, + src: source, + } +} + +type jobReportSubscription struct { + dst E164 + reportID string + src E164 +} + +func (j jobReportSubscription) content() string { + return fmt.Sprintf("Thanks for submitting mosquito report %s. Text for any questions. We'll send you updates as we get them.", j.reportID) +} +func (j jobReportSubscription) destination() string { + return phonenumbers.Format(&j.dst, phonenumbers.E164) +} +func (j jobReportSubscription) messageType() MessageType { + return ReportSubscription +} +func (j jobReportSubscription) messageTypeName() string { + return "report-subscription" +} +func (j jobReportSubscription) source() string { + return phonenumbers.Format(&j.src, phonenumbers.E164) +} + +func sendReportSubscription(ctx context.Context, job Job) error { + j, ok := job.(jobReportSubscription) + if !ok { + return fmt.Errorf("job is not for report subscription confirmation") + } + + err := sendText(ctx, j.src, j.dst, j.content(), enums.CommsTextoriginWebsiteAction) + if err != nil { + return fmt.Errorf("Failed to send report subscription confirmation: %w", err) + } + return nil +} diff --git a/comms/text.go b/comms/text/text.go similarity index 74% rename from comms/text.go rename to comms/text/text.go index 5dfce308..e0846d2b 100644 --- a/comms/text.go +++ b/comms/text/text.go @@ -1,10 +1,12 @@ -package comms +package text import ( + "context" "encoding/json" "fmt" "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/nyaruka/phonenumbers" "github.com/rs/zerolog/log" "github.com/twilio/twilio-go" @@ -17,16 +19,23 @@ func ParsePhoneNumber(input string) (*E164, error) { return phonenumbers.Parse(input, "US") } -func SendText(source E164, destination E164, message string) error { - log.Info().Msg("Sending text message...") - // Make sure TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN exists in your environment +func sendText(ctx context.Context, source E164, destination E164, message string, origin enums.CommsTextorigin) error { + src := phonenumbers.Format(&source, phonenumbers.E164) + dest := phonenumbers.Format(&destination, phonenumbers.E164) + err := ensureInDB(ctx, dest) + if err != nil { + return fmt.Errorf("Failed to ensure text message destination is in the DB: %w", err) + } + err = insertTextLog(ctx, message, dest, src, origin) + if err != nil { + return fmt.Errorf("Failed to insert text message in the DB: %w", err) + } client := twilio.NewRestClient() params := &twilioApi.CreateMessageParams{} params.SetMessagingServiceSid(config.TwilioMessagingServiceSID) params.SetBody(message) - dest := phonenumbers.Format(&destination, phonenumbers.E164) params.SetTo(dest) resp, err := client.Api.CreateMessage(params) diff --git a/db/dberrors/comms.text_log.bob.go b/db/dberrors/comms.text_log.bob.go index 4611eb7a..8bd6a9dc 100644 --- a/db/dberrors/comms.text_log.bob.go +++ b/db/dberrors/comms.text_log.bob.go @@ -7,7 +7,7 @@ var CommsTextLogErrors = &commsTextLogErrors{ ErrUniqueTextLogPkey: &UniqueConstraintError{ schema: "comms", table: "text_log", - columns: []string{"destination", "source", "type"}, + columns: []string{"id"}, s: "text_log_pkey", }, } diff --git a/db/dberrors/organization.bob.go b/db/dberrors/organization.bob.go index fee0219d..d3199621 100644 --- a/db/dberrors/organization.bob.go +++ b/db/dberrors/organization.bob.go @@ -18,6 +18,13 @@ var OrganizationErrors = &organizationErrors{ s: "organization_import_district_gid_key", }, + ErrUniqueOrganizationSlugKey: &UniqueConstraintError{ + schema: "", + table: "organization", + columns: []string{"slug"}, + s: "organization_slug_key", + }, + ErrUniqueOrganizationWebsiteKey: &UniqueConstraintError{ schema: "", table: "organization", @@ -31,5 +38,7 @@ type organizationErrors struct { ErrUniqueOrganizationImportDistrictGidKey *UniqueConstraintError + ErrUniqueOrganizationSlugKey *UniqueConstraintError + ErrUniqueOrganizationWebsiteKey *UniqueConstraintError } diff --git a/db/dbinfo/comms.text_log.bob.go b/db/dbinfo/comms.text_log.bob.go index 308ca842..7447d67d 100644 --- a/db/dbinfo/comms.text_log.bob.go +++ b/db/dbinfo/comms.text_log.bob.go @@ -15,6 +15,15 @@ var CommsTextLogs = Table[ Schema: "comms", Name: "text_log", Columns: commsTextLogColumns{ + Content: column{ + Name: "content", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, Created: column{ Name: "created", DBType: "timestamp without time zone", @@ -33,18 +42,27 @@ var CommsTextLogs = Table[ Generated: false, AutoIncr: false, }, - Source: column{ - Name: "source", - DBType: "text", + ID: column{ + Name: "id", + DBType: "integer", + Default: "nextval('comms.text_log_id_seq'::regclass)", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Origin: column{ + Name: "origin", + DBType: "comms.textorigin", Default: "", Comment: "", Nullable: false, Generated: false, AutoIncr: false, }, - Type: column{ - Name: "type", - DBType: "comms.messagetypetext", + Source: column{ + Name: "source", + DBType: "text", Default: "", Comment: "", Nullable: false, @@ -58,24 +76,14 @@ var CommsTextLogs = Table[ Name: "text_log_pkey", Columns: []indexColumn{ { - Name: "destination", - Desc: null.FromCond(false, true), - IsExpression: false, - }, - { - Name: "source", - Desc: null.FromCond(false, true), - IsExpression: false, - }, - { - Name: "type", + Name: "id", Desc: null.FromCond(false, true), IsExpression: false, }, }, Unique: true, Comment: "", - NullsFirst: []bool{false, false, false}, + NullsFirst: []bool{false}, NullsDistinct: false, Where: "", Include: []string{}, @@ -83,7 +91,7 @@ var CommsTextLogs = Table[ }, PrimaryKey: &constraint{ Name: "text_log_pkey", - Columns: []string{"destination", "source", "type"}, + Columns: []string{"id"}, Comment: "", }, ForeignKeys: commsTextLogForeignKeys{ @@ -111,15 +119,17 @@ var CommsTextLogs = Table[ } type commsTextLogColumns struct { + Content column Created column Destination column + ID column + Origin column Source column - Type column } func (c commsTextLogColumns) AsSlice() []column { return []column{ - c.Created, c.Destination, c.Source, c.Type, + c.Content, c.Created, c.Destination, c.ID, c.Origin, c.Source, } } diff --git a/db/dbinfo/organization.bob.go b/db/dbinfo/organization.bob.go index 23be6de2..da9cf320 100644 --- a/db/dbinfo/organization.bob.go +++ b/db/dbinfo/organization.bob.go @@ -132,6 +132,23 @@ var Organizations = Table[ Where: "", Include: []string{}, }, + OrganizationSlugKey: index{ + Type: "btree", + Name: "organization_slug_key", + Columns: []indexColumn{ + { + Name: "slug", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, OrganizationWebsiteKey: index{ Type: "btree", Name: "organization_website_key", @@ -172,6 +189,11 @@ var Organizations = Table[ Columns: []string{"import_district_gid"}, Comment: "", }, + OrganizationSlugKey: constraint{ + Name: "organization_slug_key", + Columns: []string{"slug"}, + Comment: "", + }, OrganizationWebsiteKey: constraint{ Name: "organization_website_key", Columns: []string{"website"}, @@ -203,12 +225,13 @@ func (c organizationColumns) AsSlice() []column { type organizationIndexes struct { OrganizationPkey index OrganizationImportDistrictGidKey index + OrganizationSlugKey index OrganizationWebsiteKey index } func (i organizationIndexes) AsSlice() []index { return []index{ - i.OrganizationPkey, i.OrganizationImportDistrictGidKey, i.OrganizationWebsiteKey, + i.OrganizationPkey, i.OrganizationImportDistrictGidKey, i.OrganizationSlugKey, i.OrganizationWebsiteKey, } } @@ -224,12 +247,13 @@ func (f organizationForeignKeys) AsSlice() []foreignKey { type organizationUniques struct { OrganizationImportDistrictGidKey constraint + OrganizationSlugKey constraint OrganizationWebsiteKey constraint } func (u organizationUniques) AsSlice() []constraint { return []constraint{ - u.OrganizationImportDistrictGidKey, u.OrganizationWebsiteKey, + u.OrganizationImportDistrictGidKey, u.OrganizationSlugKey, u.OrganizationWebsiteKey, } } diff --git a/db/enums/enums.bob.go b/db/enums/enums.bob.go index 373cd454..2cccb414 100644 --- a/db/enums/enums.bob.go +++ b/db/enums/enums.bob.go @@ -272,35 +272,32 @@ func (e *CommsMessagetypeemail) Scan(value any) error { return nil } -// Enum values for CommsMessagetypetext +// Enum values for CommsTextorigin const ( - CommsMessagetypetextInitialContact CommsMessagetypetext = "initial-contact" - CommsMessagetypetextReportSubscriptionConfirmation CommsMessagetypetext = "report-subscription-confirmation" - CommsMessagetypetextReportStatusScheduled CommsMessagetypetext = "report-status-scheduled" - CommsMessagetypetextReportStatusComplete CommsMessagetypetext = "report-status-complete" + CommsTextoriginDistrict CommsTextorigin = "district" + CommsTextoriginLLM CommsTextorigin = "llm" + CommsTextoriginWebsiteAction CommsTextorigin = "website-action" ) -func AllCommsMessagetypetext() []CommsMessagetypetext { - return []CommsMessagetypetext{ - CommsMessagetypetextInitialContact, - CommsMessagetypetextReportSubscriptionConfirmation, - CommsMessagetypetextReportStatusScheduled, - CommsMessagetypetextReportStatusComplete, +func AllCommsTextorigin() []CommsTextorigin { + return []CommsTextorigin{ + CommsTextoriginDistrict, + CommsTextoriginLLM, + CommsTextoriginWebsiteAction, } } -type CommsMessagetypetext string +type CommsTextorigin string -func (e CommsMessagetypetext) String() string { +func (e CommsTextorigin) String() string { return string(e) } -func (e CommsMessagetypetext) Valid() bool { +func (e CommsTextorigin) Valid() bool { switch e { - case CommsMessagetypetextInitialContact, - CommsMessagetypetextReportSubscriptionConfirmation, - CommsMessagetypetextReportStatusScheduled, - CommsMessagetypetextReportStatusComplete: + case CommsTextoriginDistrict, + CommsTextoriginLLM, + CommsTextoriginWebsiteAction: return true default: return false @@ -308,44 +305,44 @@ func (e CommsMessagetypetext) Valid() bool { } // useful when testing in other packages -func (e CommsMessagetypetext) All() []CommsMessagetypetext { - return AllCommsMessagetypetext() +func (e CommsTextorigin) All() []CommsTextorigin { + return AllCommsTextorigin() } -func (e CommsMessagetypetext) MarshalText() ([]byte, error) { +func (e CommsTextorigin) MarshalText() ([]byte, error) { return []byte(e), nil } -func (e *CommsMessagetypetext) UnmarshalText(text []byte) error { +func (e *CommsTextorigin) UnmarshalText(text []byte) error { return e.Scan(text) } -func (e CommsMessagetypetext) MarshalBinary() ([]byte, error) { +func (e CommsTextorigin) MarshalBinary() ([]byte, error) { return []byte(e), nil } -func (e *CommsMessagetypetext) UnmarshalBinary(data []byte) error { +func (e *CommsTextorigin) UnmarshalBinary(data []byte) error { return e.Scan(data) } -func (e CommsMessagetypetext) Value() (driver.Value, error) { +func (e CommsTextorigin) Value() (driver.Value, error) { return string(e), nil } -func (e *CommsMessagetypetext) Scan(value any) error { +func (e *CommsTextorigin) Scan(value any) error { switch x := value.(type) { case string: - *e = CommsMessagetypetext(x) + *e = CommsTextorigin(x) case []byte: - *e = CommsMessagetypetext(x) + *e = CommsTextorigin(x) case nil: - return fmt.Errorf("cannot nil into CommsMessagetypetext") + return fmt.Errorf("cannot nil into CommsTextorigin") default: return fmt.Errorf("cannot scan type %T: %v", value, value) } if !e.Valid() { - return fmt.Errorf("invalid CommsMessagetypetext value: %s", *e) + return fmt.Errorf("invalid CommsTextorigin value: %s", *e) } return nil diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index 00bfe2bc..ab527cba 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -324,10 +324,12 @@ func (f *Factory) NewCommsTextLogWithContext(ctx context.Context, mods ...CommsT func (f *Factory) FromExistingCommsTextLog(m *models.CommsTextLog) *CommsTextLogTemplate { o := &CommsTextLogTemplate{f: f, alreadyPersisted: true} + o.Content = func() string { return m.Content } o.Created = func() time.Time { return m.Created } o.Destination = func() string { return m.Destination } + o.ID = func() int32 { return m.ID } + o.Origin = func() enums.CommsTextorigin { return m.Origin } o.Source = func() string { return m.Source } - o.Type = func() enums.CommsMessagetypetext { return m.Type } ctx := context.Background() if m.R.DestinationPhone != nil { diff --git a/db/factory/bobfactory_random.bob.go b/db/factory/bobfactory_random.bob.go index dfa5ebc5..23a5b12f 100644 --- a/db/factory/bobfactory_random.bob.go +++ b/db/factory/bobfactory_random.bob.go @@ -101,12 +101,12 @@ func random_enums_CommsMessagetypeemail(f *faker.Faker, limits ...string) enums. return all[f.IntBetween(0, len(all)-1)] } -func random_enums_CommsMessagetypetext(f *faker.Faker, limits ...string) enums.CommsMessagetypetext { +func random_enums_CommsTextorigin(f *faker.Faker, limits ...string) enums.CommsTextorigin { if f == nil { f = &defaultFaker } - var e enums.CommsMessagetypetext + var e enums.CommsTextorigin all := e.All() return all[f.IntBetween(0, len(all)-1)] } diff --git a/db/factory/comms.text_log.bob.go b/db/factory/comms.text_log.bob.go index 91d5a2f3..6b1cd6e0 100644 --- a/db/factory/comms.text_log.bob.go +++ b/db/factory/comms.text_log.bob.go @@ -36,10 +36,12 @@ func (mods CommsTextLogModSlice) Apply(ctx context.Context, n *CommsTextLogTempl // CommsTextLogTemplate is an object representing the database table. // all columns are optional and should be set by mods type CommsTextLogTemplate struct { + Content func() string Created func() time.Time Destination func() string + ID func() int32 + Origin func() enums.CommsTextorigin Source func() string - Type func() enums.CommsMessagetypetext r commsTextLogR f *Factory @@ -89,6 +91,10 @@ func (t CommsTextLogTemplate) setModelRels(o *models.CommsTextLog) { func (o CommsTextLogTemplate) BuildSetter() *models.CommsTextLogSetter { m := &models.CommsTextLogSetter{} + if o.Content != nil { + val := o.Content() + m.Content = omit.From(val) + } if o.Created != nil { val := o.Created() m.Created = omit.From(val) @@ -97,14 +103,18 @@ func (o CommsTextLogTemplate) BuildSetter() *models.CommsTextLogSetter { val := o.Destination() m.Destination = omit.From(val) } + if o.ID != nil { + val := o.ID() + m.ID = omit.From(val) + } + if o.Origin != nil { + val := o.Origin() + m.Origin = omit.From(val) + } if o.Source != nil { val := o.Source() m.Source = omit.From(val) } - if o.Type != nil { - val := o.Type() - m.Type = omit.From(val) - } return m } @@ -127,18 +137,24 @@ func (o CommsTextLogTemplate) BuildManySetter(number int) []*models.CommsTextLog func (o CommsTextLogTemplate) Build() *models.CommsTextLog { m := &models.CommsTextLog{} + if o.Content != nil { + m.Content = o.Content() + } if o.Created != nil { m.Created = o.Created() } if o.Destination != nil { m.Destination = o.Destination() } + if o.ID != nil { + m.ID = o.ID() + } + if o.Origin != nil { + m.Origin = o.Origin() + } if o.Source != nil { m.Source = o.Source() } - if o.Type != nil { - m.Type = o.Type() - } o.setModelRels(m) @@ -159,6 +175,10 @@ func (o CommsTextLogTemplate) BuildMany(number int) models.CommsTextLogSlice { } func ensureCreatableCommsTextLog(m *models.CommsTextLogSetter) { + if !(m.Content.IsValue()) { + val := random_string(nil) + m.Content = omit.From(val) + } if !(m.Created.IsValue()) { val := random_time_Time(nil) m.Created = omit.From(val) @@ -167,14 +187,14 @@ func ensureCreatableCommsTextLog(m *models.CommsTextLogSetter) { val := random_string(nil) m.Destination = omit.From(val) } + if !(m.Origin.IsValue()) { + val := random_enums_CommsTextorigin(nil) + m.Origin = omit.From(val) + } if !(m.Source.IsValue()) { val := random_string(nil) m.Source = omit.From(val) } - if !(m.Type.IsValue()) { - val := random_enums_CommsMessagetypetext(nil) - m.Type = omit.From(val) - } } // insertOptRels creates and inserts any optional the relationships on *models.CommsTextLog @@ -312,13 +332,46 @@ type commsTextLogMods struct{} func (m commsTextLogMods) RandomizeAllColumns(f *faker.Faker) CommsTextLogMod { return CommsTextLogModSlice{ + CommsTextLogMods.RandomContent(f), CommsTextLogMods.RandomCreated(f), CommsTextLogMods.RandomDestination(f), + CommsTextLogMods.RandomID(f), + CommsTextLogMods.RandomOrigin(f), CommsTextLogMods.RandomSource(f), - CommsTextLogMods.RandomType(f), } } +// Set the model columns to this value +func (m commsTextLogMods) Content(val string) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Content = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsTextLogMods) ContentFunc(f func() string) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Content = f + }) +} + +// Clear any values for the column +func (m commsTextLogMods) UnsetContent() CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Content = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextLogMods) RandomContent(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Content = func() string { + return random_string(f) + } + }) +} + // Set the model columns to this value func (m commsTextLogMods) Created(val time.Time) CommsTextLogMod { return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { @@ -381,6 +434,68 @@ func (m commsTextLogMods) RandomDestination(f *faker.Faker) CommsTextLogMod { }) } +// Set the model columns to this value +func (m commsTextLogMods) ID(val int32) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.ID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m commsTextLogMods) IDFunc(f func() int32) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m commsTextLogMods) UnsetID() CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + 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 commsTextLogMods) RandomID(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.ID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m commsTextLogMods) Origin(val enums.CommsTextorigin) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Origin = func() enums.CommsTextorigin { return val } + }) +} + +// Set the Column from the function +func (m commsTextLogMods) OriginFunc(f func() enums.CommsTextorigin) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Origin = f + }) +} + +// Clear any values for the column +func (m commsTextLogMods) UnsetOrigin() CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Origin = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextLogMods) RandomOrigin(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.Origin = func() enums.CommsTextorigin { + return random_enums_CommsTextorigin(f) + } + }) +} + // Set the model columns to this value func (m commsTextLogMods) Source(val string) CommsTextLogMod { return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { @@ -412,37 +527,6 @@ func (m commsTextLogMods) RandomSource(f *faker.Faker) CommsTextLogMod { }) } -// Set the model columns to this value -func (m commsTextLogMods) Type(val enums.CommsMessagetypetext) CommsTextLogMod { - return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { - o.Type = func() enums.CommsMessagetypetext { return val } - }) -} - -// Set the Column from the function -func (m commsTextLogMods) TypeFunc(f func() enums.CommsMessagetypetext) CommsTextLogMod { - return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { - o.Type = f - }) -} - -// Clear any values for the column -func (m commsTextLogMods) UnsetType() CommsTextLogMod { - return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { - o.Type = nil - }) -} - -// Generates a random value for the column using the given faker -// if faker is nil, a default faker is used -func (m commsTextLogMods) RandomType(f *faker.Faker) CommsTextLogMod { - return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { - o.Type = func() enums.CommsMessagetypetext { - return random_enums_CommsMessagetypetext(f) - } - }) -} - func (m commsTextLogMods) WithParentsCascading() CommsTextLogMod { return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { if isDone, _ := commsTextLogWithParentsCascadingCtx.Value(ctx); isDone { diff --git a/db/migrations/00041_text_log_overhaul.sql b/db/migrations/00041_text_log_overhaul.sql new file mode 100644 index 00000000..4e33d846 --- /dev/null +++ b/db/migrations/00041_text_log_overhaul.sql @@ -0,0 +1,34 @@ +-- +goose Up +DROP TABLE comms.text_log; +DROP TYPE comms.MessageTypeText; +CREATE TYPE comms.TextOrigin AS ENUM ( + 'district', + 'llm', + 'website-action' +); +CREATE TABLE comms.text_log ( + content TEXT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + destination TEXT NOT NULL REFERENCES comms.phone(e164), + id SERIAL, + origin comms.TextOrigin NOT NULL, + source TEXT NOT NULL REFERENCES comms.phone(e164), + PRIMARY KEY(id) +); + +-- +goose Down +DROP TABLE comms.text_log; +DROP TYPE comms.TextOrigin; +CREATE TYPE comms.MessageTypeText AS ENUM ( + 'initial-contact', + 'report-subscription-confirmation', + 'report-status-scheduled', + 'report-status-complete' +); +CREATE TABLE comms.text_log ( + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + destination TEXT NOT NULL REFERENCES comms.phone(e164), + source TEXT NOT NULL REFERENCES comms.phone(e164), + type comms.MessageTypeText NOT NULL, + PRIMARY KEY (destination, source, type) +); diff --git a/db/models/comms.text_log.bob.go b/db/models/comms.text_log.bob.go index d717c547..fc1054d3 100644 --- a/db/models/comms.text_log.bob.go +++ b/db/models/comms.text_log.bob.go @@ -25,10 +25,12 @@ import ( // CommsTextLog is an object representing the database table. type CommsTextLog struct { - Created time.Time `db:"created" ` - Destination string `db:"destination,pk" ` - Source string `db:"source,pk" ` - Type enums.CommsMessagetypetext `db:"type,pk" ` + Content string `db:"content" ` + Created time.Time `db:"created" ` + Destination string `db:"destination" ` + ID int32 `db:"id,pk" ` + Origin enums.CommsTextorigin `db:"origin" ` + Source string `db:"source" ` R commsTextLogR `db:"-" ` } @@ -52,23 +54,27 @@ type commsTextLogR struct { func buildCommsTextLogColumns(alias string) commsTextLogColumns { return commsTextLogColumns{ ColumnsExpr: expr.NewColumnsExpr( - "created", "destination", "source", "type", + "content", "created", "destination", "id", "origin", "source", ).WithParent("comms.text_log"), tableAlias: alias, + Content: psql.Quote(alias, "content"), Created: psql.Quote(alias, "created"), Destination: psql.Quote(alias, "destination"), + ID: psql.Quote(alias, "id"), + Origin: psql.Quote(alias, "origin"), Source: psql.Quote(alias, "source"), - Type: psql.Quote(alias, "type"), } } type commsTextLogColumns struct { expr.ColumnsExpr tableAlias string + Content psql.Expression Created psql.Expression Destination psql.Expression + ID psql.Expression + Origin psql.Expression Source psql.Expression - Type psql.Expression } func (c commsTextLogColumns) Alias() string { @@ -83,42 +89,56 @@ func (commsTextLogColumns) AliasedAs(alias string) commsTextLogColumns { // All values are optional, and do not have to be set // Generated columns are not included type CommsTextLogSetter struct { - Created omit.Val[time.Time] `db:"created" ` - Destination omit.Val[string] `db:"destination,pk" ` - Source omit.Val[string] `db:"source,pk" ` - Type omit.Val[enums.CommsMessagetypetext] `db:"type,pk" ` + Content omit.Val[string] `db:"content" ` + Created omit.Val[time.Time] `db:"created" ` + Destination omit.Val[string] `db:"destination" ` + ID omit.Val[int32] `db:"id,pk" ` + Origin omit.Val[enums.CommsTextorigin] `db:"origin" ` + Source omit.Val[string] `db:"source" ` } func (s CommsTextLogSetter) SetColumns() []string { - vals := make([]string, 0, 4) + vals := make([]string, 0, 6) + if s.Content.IsValue() { + vals = append(vals, "content") + } if s.Created.IsValue() { vals = append(vals, "created") } if s.Destination.IsValue() { vals = append(vals, "destination") } + if s.ID.IsValue() { + vals = append(vals, "id") + } + if s.Origin.IsValue() { + vals = append(vals, "origin") + } if s.Source.IsValue() { vals = append(vals, "source") } - if s.Type.IsValue() { - vals = append(vals, "type") - } return vals } func (s CommsTextLogSetter) Overwrite(t *CommsTextLog) { + if s.Content.IsValue() { + t.Content = s.Content.MustGet() + } if s.Created.IsValue() { t.Created = s.Created.MustGet() } if s.Destination.IsValue() { t.Destination = s.Destination.MustGet() } + if s.ID.IsValue() { + t.ID = s.ID.MustGet() + } + if s.Origin.IsValue() { + t.Origin = s.Origin.MustGet() + } if s.Source.IsValue() { t.Source = s.Source.MustGet() } - if s.Type.IsValue() { - t.Type = s.Type.MustGet() - } } func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) { @@ -127,31 +147,43 @@ func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 4) - if s.Created.IsValue() { - vals[0] = psql.Arg(s.Created.MustGet()) + vals := make([]bob.Expression, 6) + if s.Content.IsValue() { + vals[0] = psql.Arg(s.Content.MustGet()) } else { vals[0] = psql.Raw("DEFAULT") } - if s.Destination.IsValue() { - vals[1] = psql.Arg(s.Destination.MustGet()) + if s.Created.IsValue() { + vals[1] = psql.Arg(s.Created.MustGet()) } else { vals[1] = psql.Raw("DEFAULT") } - if s.Source.IsValue() { - vals[2] = psql.Arg(s.Source.MustGet()) + if s.Destination.IsValue() { + vals[2] = psql.Arg(s.Destination.MustGet()) } else { vals[2] = psql.Raw("DEFAULT") } - if s.Type.IsValue() { - vals[3] = psql.Arg(s.Type.MustGet()) + if s.ID.IsValue() { + vals[3] = psql.Arg(s.ID.MustGet()) } else { vals[3] = psql.Raw("DEFAULT") } + if s.Origin.IsValue() { + vals[4] = psql.Arg(s.Origin.MustGet()) + } else { + vals[4] = psql.Raw("DEFAULT") + } + + if s.Source.IsValue() { + vals[5] = psql.Arg(s.Source.MustGet()) + } else { + vals[5] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -161,7 +193,14 @@ func (s CommsTextLogSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 4) + exprs := make([]bob.Expression, 0, 6) + + if s.Content.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "content")...), + psql.Arg(s.Content), + }}) + } if s.Created.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -177,6 +216,20 @@ func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if s.ID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "id")...), + psql.Arg(s.ID), + }}) + } + + if s.Origin.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "origin")...), + psql.Arg(s.Origin), + }}) + } + if s.Source.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "source")...), @@ -184,41 +237,28 @@ func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression { }}) } - if s.Type.IsValue() { - exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ - psql.Quote(append(prefix, "type")...), - psql.Arg(s.Type), - }}) - } - return exprs } // FindCommsTextLog retrieves a single record by primary key // If cols is empty Find will return all columns. -func FindCommsTextLog(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsMessagetypetext, cols ...string) (*CommsTextLog, error) { +func FindCommsTextLog(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*CommsTextLog, error) { if len(cols) == 0 { return CommsTextLogs.Query( - sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), - sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(SourcePK))), - sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(TypePK))), + sm.Where(CommsTextLogs.Columns.ID.EQ(psql.Arg(IDPK))), ).One(ctx, exec) } return CommsTextLogs.Query( - sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), - sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(SourcePK))), - sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(TypePK))), + sm.Where(CommsTextLogs.Columns.ID.EQ(psql.Arg(IDPK))), sm.Columns(CommsTextLogs.Columns.Only(cols...)), ).One(ctx, exec) } // CommsTextLogExists checks the presence of a single record by primary key -func CommsTextLogExists(ctx context.Context, exec bob.Executor, DestinationPK string, SourcePK string, TypePK enums.CommsMessagetypetext) (bool, error) { +func CommsTextLogExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { return CommsTextLogs.Query( - sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(DestinationPK))), - sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(SourcePK))), - sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(TypePK))), + sm.Where(CommsTextLogs.Columns.ID.EQ(psql.Arg(IDPK))), ).Exists(ctx, exec) } @@ -242,15 +282,11 @@ func (o *CommsTextLog) AfterQueryHook(ctx context.Context, exec bob.Executor, qu // primaryKeyVals returns the primary key values of the CommsTextLog func (o *CommsTextLog) primaryKeyVals() bob.Expression { - return psql.ArgGroup( - o.Destination, - o.Source, - o.Type, - ) + return psql.Arg(o.ID) } func (o *CommsTextLog) pkEQ() dialect.Expression { - return psql.Group(psql.Quote("comms.text_log", "destination"), psql.Quote("comms.text_log", "source"), psql.Quote("comms.text_log", "type")).EQ(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return psql.Quote("comms.text_log", "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) })) } @@ -277,9 +313,7 @@ func (o *CommsTextLog) Delete(ctx context.Context, exec bob.Executor) error { // Reload refreshes the CommsTextLog using the executor func (o *CommsTextLog) Reload(ctx context.Context, exec bob.Executor) error { o2, err := CommsTextLogs.Query( - sm.Where(CommsTextLogs.Columns.Destination.EQ(psql.Arg(o.Destination))), - sm.Where(CommsTextLogs.Columns.Source.EQ(psql.Arg(o.Source))), - sm.Where(CommsTextLogs.Columns.Type.EQ(psql.Arg(o.Type))), + sm.Where(CommsTextLogs.Columns.ID.EQ(psql.Arg(o.ID))), ).One(ctx, exec) if err != nil { return err @@ -313,7 +347,7 @@ func (o CommsTextLogSlice) pkIN() dialect.Expression { return psql.Raw("NULL") } - return psql.Group(psql.Quote("comms.text_log", "destination"), psql.Quote("comms.text_log", "source"), psql.Quote("comms.text_log", "type")).In(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return psql.Quote("comms.text_log", "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() @@ -328,13 +362,7 @@ func (o CommsTextLogSlice) pkIN() dialect.Expression { func (o CommsTextLogSlice) copyMatchingRows(from ...*CommsTextLog) { for i, old := range o { for _, new := range from { - if new.Destination != old.Destination { - continue - } - if new.Source != old.Source { - continue - } - if new.Type != old.Type { + if new.ID != old.ID { continue } new.R = old.R @@ -580,10 +608,12 @@ func (commsTextLog0 *CommsTextLog) AttachSourcePhone(ctx context.Context, exec b } type commsTextLogWhere[Q psql.Filterable] struct { + Content psql.WhereMod[Q, string] Created psql.WhereMod[Q, time.Time] Destination psql.WhereMod[Q, string] + ID psql.WhereMod[Q, int32] + Origin psql.WhereMod[Q, enums.CommsTextorigin] Source psql.WhereMod[Q, string] - Type psql.WhereMod[Q, enums.CommsMessagetypetext] } func (commsTextLogWhere[Q]) AliasedAs(alias string) commsTextLogWhere[Q] { @@ -592,10 +622,12 @@ func (commsTextLogWhere[Q]) AliasedAs(alias string) commsTextLogWhere[Q] { func buildCommsTextLogWhere[Q psql.Filterable](cols commsTextLogColumns) commsTextLogWhere[Q] { return commsTextLogWhere[Q]{ + Content: psql.Where[Q, string](cols.Content), Created: psql.Where[Q, time.Time](cols.Created), Destination: psql.Where[Q, string](cols.Destination), + ID: psql.Where[Q, int32](cols.ID), + Origin: psql.Where[Q, enums.CommsTextorigin](cols.Origin), Source: psql.Where[Q, string](cols.Source), - Type: psql.Where[Q, enums.CommsMessagetypetext](cols.Type), } } diff --git a/public-report/quick.go b/public-report/quick.go index cfb27a0b..beb95308 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -8,7 +8,7 @@ import ( "time" "github.com/Gleipnir-Technology/nidus-sync/background" - "github.com/Gleipnir-Technology/nidus-sync/comms" + "github.com/Gleipnir-Technology/nidus-sync/comms/text" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/enums" @@ -241,7 +241,7 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, fmt.Sprintf("/quick-submit-complete?report=%s", report_id), http.StatusFound) return } - phone, err := comms.ParsePhoneNumber(phone_str) + phone, err := text.ParsePhoneNumber(phone_str) result, err := psql.Update( um.Table("publicreport.quick"), um.SetCol("reporter_email").ToArg(email), diff --git a/public-report/template/mock/nuisance.html b/public-report/template/mock/nuisance.html index 0cb6e57b..85f3c6fb 100644 --- a/public-report/template/mock/nuisance.html +++ b/public-report/template/mock/nuisance.html @@ -385,7 +385,7 @@ document.addEventListener('DOMContentLoaded', function() {
    -
    diff --git a/public-report/template/mock/water.html b/public-report/template/mock/water.html index dd5d244d..d37ebf2e 100644 --- a/public-report/template/mock/water.html +++ b/public-report/template/mock/water.html @@ -347,7 +347,7 @@ function displaySelectedCoordinates(lngLat) {
    -
    From 82081b960997d85ebd6492b344b5d1d62d6888f4 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sun, 25 Jan 2026 19:36:56 +0000 Subject: [PATCH 0121/1453] Add API signin URL That was we can have much more specific failure modes for API clients --- api/routes.go | 1 + api/signin.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ auth/auth.go | 17 ++++++++++++++++- 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 api/signin.go diff --git a/api/routes.go b/api/routes.go index 80726f40..8db2879b 100644 --- a/api/routes.go +++ b/api/routes.go @@ -22,6 +22,7 @@ func AddRoutes(r chi.Router) { // Unauthenticated endpoints r.Get("/district", apiGetDistrict) r.Get("/district/{slug}/logo", apiGetDistrictLogo) + r.Post("/signin", postSignin) r.Post("/twilio/message", twilioMessagePost) r.Post("/twilio/status", twilioStatusPost) r.Post("/twilio/text", twilioTextPost) diff --git a/api/signin.go b/api/signin.go new file mode 100644 index 00000000..d182c897 --- /dev/null +++ b/api/signin.go @@ -0,0 +1,45 @@ +package api + +import ( + "errors" + "fmt" + "net/http" + + "github.com/Gleipnir-Technology/nidus-sync/auth" + "github.com/go-chi/render" + "github.com/rs/zerolog/log" +) + +func postSignin(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + render.Render(w, r, errRender(fmt.Errorf("Failed to parse POST form: %w", err))) + return + } + + username := r.FormValue("username") + password := r.FormValue("password") + + if password == "" || username == "" { + w.Header().Set("WWW-Authenticate-Error", "no-credentials") + http.Error(w, "invalid-credentials", http.StatusUnauthorized) + return + } + log.Info().Str("username", username).Msg("API Signin") + _, err := auth.SigninUser(r, username, password) + if err != nil { + if errors.Is(err, auth.InvalidCredentials{}) { + w.Header().Set("WWW-Authenticate-Error", "invalid-credentials") + http.Error(w, "invalid-credentials", http.StatusUnauthorized) + return + } + if errors.Is(err, auth.InvalidUsername{}) { + w.Header().Set("WWW-Authenticate-Error", "invalid-credentials") + http.Error(w, "invalid-credentials", http.StatusUnauthorized) + return + } + http.Error(w, "signin-server-error", http.StatusInternalServerError) + return + } + + http.Error(w, "", http.StatusAccepted) +} diff --git a/auth/auth.go b/auth/auth.go index 7548ecca..d5245414 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "strconv" + "strings" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/enums" @@ -188,6 +189,18 @@ func hashPassword(password string) (string, error) { return string(bytes), err } +func redact(s string) string { + if len(s) <= 4 { + return s + } + + first_two := s[:2] + last_two := s[len(s)-2:] + middle_length := len(s) - 4 + + return first_two + strings.Repeat("*", middle_length) + last_two +} + func validatePassword(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil @@ -198,17 +211,18 @@ func validateUser(ctx context.Context, username string, password string) (*model if err != nil { return nil, fmt.Errorf("Failed to hash password: %w", err) } - log.Info().Str("username", username).Str("password", password).Str("hash", passwordHash).Msg("Validating user") result, err := sql.UserByUsername(username).All(ctx, db.PGInstance.BobDB) if err != nil { return nil, fmt.Errorf("Failed to query for user: %w", err) } switch len(result) { case 0: + log.Info().Str("username", username).Str("password", redact(password)).Msg("Invalid username") return nil, InvalidUsername{} case 1: row := result[0] if !validatePassword(password, row.PasswordHash) { + log.Info().Str("username", username).Str("password", redact(password)).Str("hash", passwordHash).Msg("Invalid password for user") return nil, InvalidCredentials{} } user := models.User{ @@ -223,6 +237,7 @@ func validateUser(ctx context.Context, username string, password string) (*model OrganizationID: row.OrganizationID, Username: row.Username, } + log.Info().Str("username", username).Msg("Validated user") return &user, nil default: return nil, errors.New("More than one matching row, this should be impossible.") From ab105e16e8c54a96a5f089f8445614a7d4e6c802 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sun, 25 Jan 2026 21:18:39 +0000 Subject: [PATCH 0122/1453] Remove some user session logs that we don't need --- auth/auth.go | 2 -- sync/signin.go | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index d5245414..688ec2c6 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -46,7 +46,6 @@ func AddUserSession(r *http.Request, user *models.User) { id := strconv.Itoa(int(user.ID)) sessionManager.Put(r.Context(), "user_id", id) sessionManager.Put(r.Context(), "username", user.Username) - log.Info().Str("username", user.Username).Str("user_id", id).Msg("Created new user session") } func GetAuthenticatedUser(r *http.Request) (*models.User, error) { @@ -58,7 +57,6 @@ func GetAuthenticatedUser(r *http.Request) (*models.User, error) { return nil, fmt.Errorf("Failed to convert user_id to int: %w", err) } username := sessionManager.GetString(r.Context(), "username") - log.Info().Int("user_id", user_id).Str("username", username).Msg("Current session info") if user_id > 0 && username != "" { return findUser(r.Context(), user_id) } diff --git a/sync/signin.go b/sync/signin.go index b1f9b63f..6b72d011 100644 --- a/sync/signin.go +++ b/sync/signin.go @@ -39,7 +39,7 @@ func postSignin(w http.ResponseWriter, r *http.Request) { username := r.FormValue("username") password := r.FormValue("password") - log.Info().Str("username", username).Msg("Signin") + log.Info().Str("username", username).Msg("HTML Signin") _, err := auth.SigninUser(r, username, password) if err != nil { From adc99e8871e992981145ce43e9624259f66dba41 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 26 Jan 2026 16:10:30 +0000 Subject: [PATCH 0123/1453] Add ability to delay text message sending --- background/text.go | 2 +- comms/text/db.go | 33 ++ comms/text/initial.go | 31 + comms/text/report-subscription.go | 19 +- comms/text/text.go | 16 +- config/config.go | 4 +- db/dberrors/comms.text_job.bob.go | 17 + db/dbinfo/comms.text_job.bob.go | 147 +++++ db/dbinfo/comms.text_log.bob.go | 14 +- db/enums/enums.bob.go | 70 +++ db/factory/bobfactory_context.bob.go | 5 + db/factory/bobfactory_main.bob.go | 46 ++ db/factory/bobfactory_random.bob.go | 10 + db/factory/comms.phone.bob.go | 94 ++- db/factory/comms.text_job.bob.go | 499 ++++++++++++++++ db/factory/comms.text_log.bob.go | 44 ++ db/migrations/00041_text_log_overhaul.sql | 16 + db/models/bob_joins.bob.go | 2 + db/models/bob_loaders.bob.go | 4 + db/models/bob_where.bob.go | 3 + db/models/comms.phone.bob.go | 254 ++++++++ db/models/comms.text_job.bob.go | 679 ++++++++++++++++++++++ db/models/comms.text_log.bob.go | 41 +- main.go | 6 + 24 files changed, 2028 insertions(+), 28 deletions(-) create mode 100644 comms/text/initial.go create mode 100644 db/dberrors/comms.text_job.bob.go create mode 100644 db/dbinfo/comms.text_job.bob.go create mode 100644 db/factory/comms.text_job.bob.go create mode 100644 db/models/comms.text_job.bob.go diff --git a/background/text.go b/background/text.go index 80de3b2b..91308769 100644 --- a/background/text.go +++ b/background/text.go @@ -14,7 +14,7 @@ func ReportSubscriptionConfirmationText(destination text.E164, report_id string) enqueueJobText(text.NewJobReportSubscriptionConfirmation( destination, report_id, - config.RMOPhoneNumber, + config.PhoneNumberReport, )) } diff --git a/comms/text/db.go b/comms/text/db.go index 5b609df9..1795a061 100644 --- a/comms/text/db.go +++ b/comms/text/db.go @@ -10,14 +10,21 @@ import ( "strings" "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/aarondl/opt/omit" + "github.com/nyaruka/phonenumbers" "github.com/rs/zerolog/log" "github.com/stephenafamo/bob/types/pgtypes" ) +func StoreSources() error { + ctx := context.TODO() + src := phonenumbers.Format(&config.PhoneNumberReport, phonenumbers.E164) + return ensureInDB(ctx, src) +} func convertToPGData(data map[string]string) pgtypes.HStore { result := pgtypes.HStore{} for k, v := range data { @@ -26,6 +33,21 @@ func convertToPGData(data map[string]string) pgtypes.HStore { return result } +func delayMessage(ctx context.Context, source string, destination string, content string, type_ enums.CommsTextjobtype) error { + job, err := models.CommsTextJobs.Insert(&models.CommsTextJobSetter{ + Content: omit.From(content), + Created: omit.From(time.Now()), + Destination: omit.From(destination), + //ID: + Type: omit.From(type_), + }).One(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to add delayed text job: %w", err) + } + log.Info().Int32("id", job.ID).Msg("Created delayed text job") + return nil +} + func ensureInDB(ctx context.Context, destination string) (err error) { _, err = models.FindCommsPhone(ctx, db.PGInstance.BobDB, destination) if err != nil { @@ -58,6 +80,17 @@ func insertTextLog(ctx context.Context, content string, destination string, sour return err } +func isSubscribed(ctx context.Context, destination string) (bool, error) { + phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, destination) + if err != nil { + if err.Error() == "sql: no rows in result set" { + return false, nil + } + return false, fmt.Errorf("Failed to find phone number %s: %w", destination, err) + } + return phone.IsSubscribed, nil +} + func generatePublicId(t enums.CommsMessagetypeemail, m map[string]string) string { if m == nil || len(m) == 0 { // Return hash of empty string for empty maps diff --git a/comms/text/initial.go b/comms/text/initial.go new file mode 100644 index 00000000..d2bf35a5 --- /dev/null +++ b/comms/text/initial.go @@ -0,0 +1,31 @@ +package text + +import ( + "context" + "fmt" + + "github.com/Gleipnir-Technology/nidus-sync/db" + "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/Gleipnir-Technology/nidus-sync/db/models" +) + +func ensureInitialText(ctx context.Context, src string, dst string) error { + // + origin := enums.CommsTextoriginWebsiteAction + rows, err := models.CommsTextLogs.Query( + models.SelectWhere.CommsTextLogs.Destination.EQ(dst), + models.SelectWhere.CommsTextLogs.IsWelcome.EQ(true), + ).All(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to query text logs: %w", err) + } + if len(rows) > 0 { + return nil + } + content := "Welcome to Report Mosquitoes Online. We received your request and want to confirm text updates. Reply YES to continue. Reply STOP at any time to unsubscribe" + err = sendText(ctx, src, dst, content, origin) + if err != nil { + return fmt.Errorf("Failed to send initial confirmation: %w", err) + } + return nil +} diff --git a/comms/text/report-subscription.go b/comms/text/report-subscription.go index 1dd1f193..092b9ee2 100644 --- a/comms/text/report-subscription.go +++ b/comms/text/report-subscription.go @@ -48,9 +48,24 @@ func sendReportSubscription(ctx context.Context, job Job) error { return fmt.Errorf("job is not for report subscription confirmation") } - err := sendText(ctx, j.src, j.dst, j.content(), enums.CommsTextoriginWebsiteAction) + sub, err := isSubscribed(ctx, job.destination()) if err != nil { - return fmt.Errorf("Failed to send report subscription confirmation: %w", err) + return fmt.Errorf("Failed to check if subscribed: %w", err) + } + if !sub { + err = sendText(ctx, j.source(), j.destination(), j.content(), enums.CommsTextoriginWebsiteAction) + if err != nil { + return fmt.Errorf("Failed to send report subscription confirmation: %w", err) + } + } else { + err = delayMessage(ctx, j.source(), j.destination(), j.content(), enums.CommsTextjobtypeReportConfirmation) + if err != nil { + return fmt.Errorf("Failed to delay report subscription message: %w", err) + } + err := ensureInitialText(ctx, j.source(), j.destination()) + if err != nil { + return fmt.Errorf("Failed to ensure initial text has been sent: %w", err) + } } return nil } diff --git a/comms/text/text.go b/comms/text/text.go index e0846d2b..415a301d 100644 --- a/comms/text/text.go +++ b/comms/text/text.go @@ -19,14 +19,12 @@ func ParsePhoneNumber(input string) (*E164, error) { return phonenumbers.Parse(input, "US") } -func sendText(ctx context.Context, source E164, destination E164, message string, origin enums.CommsTextorigin) error { - src := phonenumbers.Format(&source, phonenumbers.E164) - dest := phonenumbers.Format(&destination, phonenumbers.E164) - err := ensureInDB(ctx, dest) +func sendText(ctx context.Context, source string, destination string, message string, origin enums.CommsTextorigin) error { + err := ensureInDB(ctx, destination) if err != nil { return fmt.Errorf("Failed to ensure text message destination is in the DB: %w", err) } - err = insertTextLog(ctx, message, dest, src, origin) + err = insertTextLog(ctx, message, destination, source, origin) if err != nil { return fmt.Errorf("Failed to insert text message in the DB: %w", err) } @@ -36,16 +34,16 @@ func sendText(ctx context.Context, source E164, destination E164, message string params.SetMessagingServiceSid(config.TwilioMessagingServiceSID) params.SetBody(message) - params.SetTo(dest) + params.SetTo(destination) resp, err := client.Api.CreateMessage(params) if err != nil { - return fmt.Errorf("Failed to create message to %s: %w", dest, err) + return fmt.Errorf("Failed to create message to %s: %w", destination, err) } else { if resp.Body != nil { - log.Info().Str("dest", dest).Str("body", *resp.Body).Msg("Text message response") + log.Info().Str("dest", destination).Str("body", *resp.Body).Msg("Text message response") } else { - log.Info().Str("dest", dest).Msg("Text message response is nil") + log.Info().Str("dest", destination).Msg("Text message response is nil") } } return nil diff --git a/config/config.go b/config/config.go index 584e5f0c..09b356f2 100644 --- a/config/config.go +++ b/config/config.go @@ -26,7 +26,7 @@ var ( ForwardEmailReportUsername string MapboxToken string PGDSN string - RMOPhoneNumber phonenumbers.PhoneNumber + PhoneNumberReport phonenumbers.PhoneNumber TwilioAuthToken string TwilioAccountSID string TwilioMessagingServiceSID string @@ -135,7 +135,7 @@ func Parse() (err error) { if err != nil { return fmt.Errorf("Failed to parse '%s' as a valid phone number: %w", rmo_phone_number, err) } - RMOPhoneNumber = *p + PhoneNumberReport = *p TwilioAccountSID = os.Getenv("TWILIO_ACCOUNT_SID") if TwilioAccountSID == "" { diff --git a/db/dberrors/comms.text_job.bob.go b/db/dberrors/comms.text_job.bob.go new file mode 100644 index 00000000..cedcca8e --- /dev/null +++ b/db/dberrors/comms.text_job.bob.go @@ -0,0 +1,17 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package dberrors + +var CommsTextJobErrors = &commsTextJobErrors{ + ErrUniqueTextJobPkey: &UniqueConstraintError{ + schema: "comms", + table: "text_job", + columns: []string{"id"}, + s: "text_job_pkey", + }, +} + +type commsTextJobErrors struct { + ErrUniqueTextJobPkey *UniqueConstraintError +} diff --git a/db/dbinfo/comms.text_job.bob.go b/db/dbinfo/comms.text_job.bob.go new file mode 100644 index 00000000..aa87ea6f --- /dev/null +++ b/db/dbinfo/comms.text_job.bob.go @@ -0,0 +1,147 @@ +// Code generated by BobGen psql v0.42.1. 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 CommsTextJobs = Table[ + commsTextJobColumns, + commsTextJobIndexes, + commsTextJobForeignKeys, + commsTextJobUniques, + commsTextJobChecks, +]{ + Schema: "comms", + Name: "text_job", + Columns: commsTextJobColumns{ + Content: column{ + Name: "content", + 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, + }, + Destination: column{ + Name: "destination", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + ID: column{ + Name: "id", + DBType: "integer", + Default: "nextval('comms.text_job_id_seq'::regclass)", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + Type: column{ + Name: "type_", + DBType: "comms.textjobtype", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, + }, + Indexes: commsTextJobIndexes{ + TextJobPkey: index{ + Type: "btree", + Name: "text_job_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: "text_job_pkey", + Columns: []string{"id"}, + Comment: "", + }, + ForeignKeys: commsTextJobForeignKeys{ + CommsTextJobTextJobDestinationFkey: foreignKey{ + constraint: constraint{ + Name: "comms.text_job.text_job_destination_fkey", + Columns: []string{"destination"}, + Comment: "", + }, + ForeignTable: "comms.phone", + ForeignColumns: []string{"e164"}, + }, + }, + + Comment: "Used to track text messages that should be sent later", +} + +type commsTextJobColumns struct { + Content column + Created column + Destination column + ID column + Type column +} + +func (c commsTextJobColumns) AsSlice() []column { + return []column{ + c.Content, c.Created, c.Destination, c.ID, c.Type, + } +} + +type commsTextJobIndexes struct { + TextJobPkey index +} + +func (i commsTextJobIndexes) AsSlice() []index { + return []index{ + i.TextJobPkey, + } +} + +type commsTextJobForeignKeys struct { + CommsTextJobTextJobDestinationFkey foreignKey +} + +func (f commsTextJobForeignKeys) AsSlice() []foreignKey { + return []foreignKey{ + f.CommsTextJobTextJobDestinationFkey, + } +} + +type commsTextJobUniques struct{} + +func (u commsTextJobUniques) AsSlice() []constraint { + return []constraint{} +} + +type commsTextJobChecks struct{} + +func (c commsTextJobChecks) AsSlice() []check { + return []check{} +} diff --git a/db/dbinfo/comms.text_log.bob.go b/db/dbinfo/comms.text_log.bob.go index 7447d67d..0e4a1329 100644 --- a/db/dbinfo/comms.text_log.bob.go +++ b/db/dbinfo/comms.text_log.bob.go @@ -51,6 +51,15 @@ var CommsTextLogs = Table[ Generated: false, AutoIncr: false, }, + IsWelcome: column{ + Name: "is_welcome", + DBType: "boolean", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, Origin: column{ Name: "origin", DBType: "comms.textorigin", @@ -115,7 +124,7 @@ var CommsTextLogs = Table[ }, }, - Comment: "", + Comment: "Used to track text messages that were sent.", } type commsTextLogColumns struct { @@ -123,13 +132,14 @@ type commsTextLogColumns struct { Created column Destination column ID column + IsWelcome column Origin column Source column } func (c commsTextLogColumns) AsSlice() []column { return []column{ - c.Content, c.Created, c.Destination, c.ID, c.Origin, c.Source, + c.Content, c.Created, c.Destination, c.ID, c.IsWelcome, c.Origin, c.Source, } } diff --git a/db/enums/enums.bob.go b/db/enums/enums.bob.go index 2cccb414..81f030ec 100644 --- a/db/enums/enums.bob.go +++ b/db/enums/enums.bob.go @@ -272,6 +272,76 @@ func (e *CommsMessagetypeemail) Scan(value any) error { return nil } +// Enum values for CommsTextjobtype +const ( + CommsTextjobtypeReportConfirmation CommsTextjobtype = "report-confirmation" +) + +func AllCommsTextjobtype() []CommsTextjobtype { + return []CommsTextjobtype{ + CommsTextjobtypeReportConfirmation, + } +} + +type CommsTextjobtype string + +func (e CommsTextjobtype) String() string { + return string(e) +} + +func (e CommsTextjobtype) Valid() bool { + switch e { + case CommsTextjobtypeReportConfirmation: + return true + default: + return false + } +} + +// useful when testing in other packages +func (e CommsTextjobtype) All() []CommsTextjobtype { + return AllCommsTextjobtype() +} + +func (e CommsTextjobtype) MarshalText() ([]byte, error) { + return []byte(e), nil +} + +func (e *CommsTextjobtype) UnmarshalText(text []byte) error { + return e.Scan(text) +} + +func (e CommsTextjobtype) MarshalBinary() ([]byte, error) { + return []byte(e), nil +} + +func (e *CommsTextjobtype) UnmarshalBinary(data []byte) error { + return e.Scan(data) +} + +func (e CommsTextjobtype) Value() (driver.Value, error) { + return string(e), nil +} + +func (e *CommsTextjobtype) Scan(value any) error { + switch x := value.(type) { + case string: + *e = CommsTextjobtype(x) + case []byte: + *e = CommsTextjobtype(x) + case nil: + return fmt.Errorf("cannot nil into CommsTextjobtype") + default: + return fmt.Errorf("cannot scan type %T: %v", value, value) + } + + if !e.Valid() { + return fmt.Errorf("invalid CommsTextjobtype value: %s", *e) + } + + return nil +} + // Enum values for CommsTextorigin const ( CommsTextoriginDistrict CommsTextorigin = "district" diff --git a/db/factory/bobfactory_context.bob.go b/db/factory/bobfactory_context.bob.go index f105c35c..4258ed4f 100644 --- a/db/factory/bobfactory_context.bob.go +++ b/db/factory/bobfactory_context.bob.go @@ -32,9 +32,14 @@ var ( // Relationship Contexts for comms.phone commsPhoneWithParentsCascadingCtx = newContextual[bool]("commsPhoneWithParentsCascading") + commsPhoneRelDestinationTextJobsCtx = newContextual[bool]("comms.phone.comms.text_job.comms.text_job.text_job_destination_fkey") commsPhoneRelDestinationTextLogsCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_destination_fkey") commsPhoneRelSourceTextLogsCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_source_fkey") + // Relationship Contexts for comms.text_job + commsTextJobWithParentsCascadingCtx = newContextual[bool]("commsTextJobWithParentsCascading") + commsTextJobRelDestinationPhoneCtx = newContextual[bool]("comms.phone.comms.text_job.comms.text_job.text_job_destination_fkey") + // Relationship Contexts for comms.text_log commsTextLogWithParentsCascadingCtx = newContextual[bool]("commsTextLogWithParentsCascading") commsTextLogRelDestinationPhoneCtx = newContextual[bool]("comms.phone.comms.text_log.comms.text_log.text_log_destination_fkey") diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index ab527cba..6e8d4391 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -25,6 +25,7 @@ type Factory struct { baseCommsEmailLogMods CommsEmailLogModSlice baseCommsEmailTemplateMods CommsEmailTemplateModSlice baseCommsPhoneMods CommsPhoneModSlice + baseCommsTextJobMods CommsTextJobModSlice baseCommsTextLogMods CommsTextLogModSlice baseFieldseekerContainerrelateMods FieldseekerContainerrelateModSlice baseFieldseekerFieldscoutinglogMods FieldseekerFieldscoutinglogModSlice @@ -295,6 +296,9 @@ func (f *Factory) FromExistingCommsPhone(m *models.CommsPhone) *CommsPhoneTempla o.IsSubscribed = func() bool { return m.IsSubscribed } ctx := context.Background() + if len(m.R.DestinationTextJobs) > 0 { + CommsPhoneMods.AddExistingDestinationTextJobs(m.R.DestinationTextJobs...).Apply(ctx, o) + } if len(m.R.DestinationTextLogs) > 0 { CommsPhoneMods.AddExistingDestinationTextLogs(m.R.DestinationTextLogs...).Apply(ctx, o) } @@ -305,6 +309,39 @@ func (f *Factory) FromExistingCommsPhone(m *models.CommsPhone) *CommsPhoneTempla return o } +func (f *Factory) NewCommsTextJob(mods ...CommsTextJobMod) *CommsTextJobTemplate { + return f.NewCommsTextJobWithContext(context.Background(), mods...) +} + +func (f *Factory) NewCommsTextJobWithContext(ctx context.Context, mods ...CommsTextJobMod) *CommsTextJobTemplate { + o := &CommsTextJobTemplate{f: f} + + if f != nil { + f.baseCommsTextJobMods.Apply(ctx, o) + } + + CommsTextJobModSlice(mods).Apply(ctx, o) + + return o +} + +func (f *Factory) FromExistingCommsTextJob(m *models.CommsTextJob) *CommsTextJobTemplate { + o := &CommsTextJobTemplate{f: f, alreadyPersisted: true} + + o.Content = func() string { return m.Content } + o.Created = func() time.Time { return m.Created } + o.Destination = func() string { return m.Destination } + o.ID = func() int32 { return m.ID } + o.Type = func() enums.CommsTextjobtype { return m.Type } + + ctx := context.Background() + if m.R.DestinationPhone != nil { + CommsTextJobMods.WithExistingDestinationPhone(m.R.DestinationPhone).Apply(ctx, o) + } + + return o +} + func (f *Factory) NewCommsTextLog(mods ...CommsTextLogMod) *CommsTextLogTemplate { return f.NewCommsTextLogWithContext(context.Background(), mods...) } @@ -328,6 +365,7 @@ func (f *Factory) FromExistingCommsTextLog(m *models.CommsTextLog) *CommsTextLog o.Created = func() time.Time { return m.Created } o.Destination = func() string { return m.Destination } o.ID = func() int32 { return m.ID } + o.IsWelcome = func() bool { return m.IsWelcome } o.Origin = func() enums.CommsTextorigin { return m.Origin } o.Source = func() string { return m.Source } @@ -3276,6 +3314,14 @@ func (f *Factory) AddBaseCommsPhoneMod(mods ...CommsPhoneMod) { f.baseCommsPhoneMods = append(f.baseCommsPhoneMods, mods...) } +func (f *Factory) ClearBaseCommsTextJobMods() { + f.baseCommsTextJobMods = nil +} + +func (f *Factory) AddBaseCommsTextJobMod(mods ...CommsTextJobMod) { + f.baseCommsTextJobMods = append(f.baseCommsTextJobMods, mods...) +} + func (f *Factory) ClearBaseCommsTextLogMods() { f.baseCommsTextLogMods = nil } diff --git a/db/factory/bobfactory_random.bob.go b/db/factory/bobfactory_random.bob.go index 23a5b12f..28ceac86 100644 --- a/db/factory/bobfactory_random.bob.go +++ b/db/factory/bobfactory_random.bob.go @@ -101,6 +101,16 @@ func random_enums_CommsMessagetypeemail(f *faker.Faker, limits ...string) enums. return all[f.IntBetween(0, len(all)-1)] } +func random_enums_CommsTextjobtype(f *faker.Faker, limits ...string) enums.CommsTextjobtype { + if f == nil { + f = &defaultFaker + } + + var e enums.CommsTextjobtype + all := e.All() + return all[f.IntBetween(0, len(all)-1)] +} + func random_enums_CommsTextorigin(f *faker.Faker, limits ...string) enums.CommsTextorigin { if f == nil { f = &defaultFaker diff --git a/db/factory/comms.phone.bob.go b/db/factory/comms.phone.bob.go index 415c6957..cefbb124 100644 --- a/db/factory/comms.phone.bob.go +++ b/db/factory/comms.phone.bob.go @@ -44,10 +44,15 @@ type CommsPhoneTemplate struct { } type commsPhoneR struct { + DestinationTextJobs []*commsPhoneRDestinationTextJobsR DestinationTextLogs []*commsPhoneRDestinationTextLogsR SourceTextLogs []*commsPhoneRSourceTextLogsR } +type commsPhoneRDestinationTextJobsR struct { + number int + o *CommsTextJobTemplate +} type commsPhoneRDestinationTextLogsR struct { number int o *CommsTextLogTemplate @@ -67,6 +72,19 @@ func (o *CommsPhoneTemplate) Apply(ctx context.Context, mods ...CommsPhoneMod) { // setModelRels creates and sets the relationships on *models.CommsPhone // according to the relationships in the template. Nothing is inserted into the db func (t CommsPhoneTemplate) setModelRels(o *models.CommsPhone) { + if t.r.DestinationTextJobs != nil { + rel := models.CommsTextJobSlice{} + for _, r := range t.r.DestinationTextJobs { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.Destination = o.E164 // h2 + rel.R.DestinationPhone = o + } + rel = append(rel, related...) + } + o.R.DestinationTextJobs = rel + } + if t.r.DestinationTextLogs != nil { rel := models.CommsTextLogSlice{} for _, r := range t.r.DestinationTextLogs { @@ -171,6 +189,26 @@ func ensureCreatableCommsPhone(m *models.CommsPhoneSetter) { func (o *CommsPhoneTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsPhone) error { var err error + isDestinationTextJobsDone, _ := commsPhoneRelDestinationTextJobsCtx.Value(ctx) + if !isDestinationTextJobsDone && o.r.DestinationTextJobs != nil { + ctx = commsPhoneRelDestinationTextJobsCtx.WithValue(ctx, true) + for _, r := range o.r.DestinationTextJobs { + if r.o.alreadyPersisted { + m.R.DestinationTextJobs = append(m.R.DestinationTextJobs, r.o.Build()) + } else { + rel0, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachDestinationTextJobs(ctx, exec, rel0...) + if err != nil { + return err + } + } + } + } + isDestinationTextLogsDone, _ := commsPhoneRelDestinationTextLogsCtx.Value(ctx) if !isDestinationTextLogsDone && o.r.DestinationTextLogs != nil { ctx = commsPhoneRelDestinationTextLogsCtx.WithValue(ctx, true) @@ -178,12 +216,12 @@ func (o *CommsPhoneTemplate) insertOptRels(ctx context.Context, exec bob.Executo if r.o.alreadyPersisted { m.R.DestinationTextLogs = append(m.R.DestinationTextLogs, r.o.Build()) } else { - rel0, err := r.o.CreateMany(ctx, exec, r.number) + rel1, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachDestinationTextLogs(ctx, exec, rel0...) + err = m.AttachDestinationTextLogs(ctx, exec, rel1...) if err != nil { return err } @@ -198,12 +236,12 @@ func (o *CommsPhoneTemplate) insertOptRels(ctx context.Context, exec bob.Executo if r.o.alreadyPersisted { m.R.SourceTextLogs = append(m.R.SourceTextLogs, r.o.Build()) } else { - rel1, err := r.o.CreateMany(ctx, exec, r.number) + rel2, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachSourceTextLogs(ctx, exec, rel1...) + err = m.AttachSourceTextLogs(ctx, exec, rel2...) if err != nil { return err } @@ -379,6 +417,54 @@ func (m commsPhoneMods) WithParentsCascading() CommsPhoneMod { }) } +func (m commsPhoneMods) WithDestinationTextJobs(number int, related *CommsTextJobTemplate) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.DestinationTextJobs = []*commsPhoneRDestinationTextJobsR{{ + number: number, + o: related, + }} + }) +} + +func (m commsPhoneMods) WithNewDestinationTextJobs(number int, mods ...CommsTextJobMod) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + related := o.f.NewCommsTextJobWithContext(ctx, mods...) + m.WithDestinationTextJobs(number, related).Apply(ctx, o) + }) +} + +func (m commsPhoneMods) AddDestinationTextJobs(number int, related *CommsTextJobTemplate) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.DestinationTextJobs = append(o.r.DestinationTextJobs, &commsPhoneRDestinationTextJobsR{ + number: number, + o: related, + }) + }) +} + +func (m commsPhoneMods) AddNewDestinationTextJobs(number int, mods ...CommsTextJobMod) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + related := o.f.NewCommsTextJobWithContext(ctx, mods...) + m.AddDestinationTextJobs(number, related).Apply(ctx, o) + }) +} + +func (m commsPhoneMods) AddExistingDestinationTextJobs(existingModels ...*models.CommsTextJob) CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + for _, em := range existingModels { + o.r.DestinationTextJobs = append(o.r.DestinationTextJobs, &commsPhoneRDestinationTextJobsR{ + o: o.f.FromExistingCommsTextJob(em), + }) + } + }) +} + +func (m commsPhoneMods) WithoutDestinationTextJobs() CommsPhoneMod { + return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { + o.r.DestinationTextJobs = nil + }) +} + func (m commsPhoneMods) WithDestinationTextLogs(number int, related *CommsTextLogTemplate) CommsPhoneMod { return CommsPhoneModFunc(func(ctx context.Context, o *CommsPhoneTemplate) { o.r.DestinationTextLogs = []*commsPhoneRDestinationTextLogsR{{ diff --git a/db/factory/comms.text_job.bob.go b/db/factory/comms.text_job.bob.go new file mode 100644 index 00000000..6d16388f --- /dev/null +++ b/db/factory/comms.text_job.bob.go @@ -0,0 +1,499 @@ +// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package factory + +import ( + "context" + "testing" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/omit" + "github.com/jaswdr/faker/v2" + "github.com/stephenafamo/bob" +) + +type CommsTextJobMod interface { + Apply(context.Context, *CommsTextJobTemplate) +} + +type CommsTextJobModFunc func(context.Context, *CommsTextJobTemplate) + +func (f CommsTextJobModFunc) Apply(ctx context.Context, n *CommsTextJobTemplate) { + f(ctx, n) +} + +type CommsTextJobModSlice []CommsTextJobMod + +func (mods CommsTextJobModSlice) Apply(ctx context.Context, n *CommsTextJobTemplate) { + for _, f := range mods { + f.Apply(ctx, n) + } +} + +// CommsTextJobTemplate is an object representing the database table. +// all columns are optional and should be set by mods +type CommsTextJobTemplate struct { + Content func() string + Created func() time.Time + Destination func() string + ID func() int32 + Type func() enums.CommsTextjobtype + + r commsTextJobR + f *Factory + + alreadyPersisted bool +} + +type commsTextJobR struct { + DestinationPhone *commsTextJobRDestinationPhoneR +} + +type commsTextJobRDestinationPhoneR struct { + o *CommsPhoneTemplate +} + +// Apply mods to the CommsTextJobTemplate +func (o *CommsTextJobTemplate) Apply(ctx context.Context, mods ...CommsTextJobMod) { + for _, mod := range mods { + mod.Apply(ctx, o) + } +} + +// setModelRels creates and sets the relationships on *models.CommsTextJob +// according to the relationships in the template. Nothing is inserted into the db +func (t CommsTextJobTemplate) setModelRels(o *models.CommsTextJob) { + if t.r.DestinationPhone != nil { + rel := t.r.DestinationPhone.o.Build() + rel.R.DestinationTextJobs = append(rel.R.DestinationTextJobs, o) + o.Destination = rel.E164 // h2 + o.R.DestinationPhone = rel + } +} + +// BuildSetter returns an *models.CommsTextJobSetter +// this does nothing with the relationship templates +func (o CommsTextJobTemplate) BuildSetter() *models.CommsTextJobSetter { + m := &models.CommsTextJobSetter{} + + if o.Content != nil { + val := o.Content() + m.Content = omit.From(val) + } + if o.Created != nil { + val := o.Created() + m.Created = omit.From(val) + } + if o.Destination != nil { + val := o.Destination() + m.Destination = omit.From(val) + } + if o.ID != nil { + val := o.ID() + m.ID = omit.From(val) + } + if o.Type != nil { + val := o.Type() + m.Type = omit.From(val) + } + + return m +} + +// BuildManySetter returns an []*models.CommsTextJobSetter +// this does nothing with the relationship templates +func (o CommsTextJobTemplate) BuildManySetter(number int) []*models.CommsTextJobSetter { + m := make([]*models.CommsTextJobSetter, number) + + for i := range m { + m[i] = o.BuildSetter() + } + + return m +} + +// Build returns an *models.CommsTextJob +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsTextJobTemplate.Create +func (o CommsTextJobTemplate) Build() *models.CommsTextJob { + m := &models.CommsTextJob{} + + if o.Content != nil { + m.Content = o.Content() + } + if o.Created != nil { + m.Created = o.Created() + } + if o.Destination != nil { + m.Destination = o.Destination() + } + if o.ID != nil { + m.ID = o.ID() + } + if o.Type != nil { + m.Type = o.Type() + } + + o.setModelRels(m) + + return m +} + +// BuildMany returns an models.CommsTextJobSlice +// Related objects are also created and placed in the .R field +// NOTE: Objects are not inserted into the database. Use CommsTextJobTemplate.CreateMany +func (o CommsTextJobTemplate) BuildMany(number int) models.CommsTextJobSlice { + m := make(models.CommsTextJobSlice, number) + + for i := range m { + m[i] = o.Build() + } + + return m +} + +func ensureCreatableCommsTextJob(m *models.CommsTextJobSetter) { + if !(m.Content.IsValue()) { + val := random_string(nil) + m.Content = omit.From(val) + } + if !(m.Created.IsValue()) { + val := random_time_Time(nil) + m.Created = omit.From(val) + } + if !(m.Destination.IsValue()) { + val := random_string(nil) + m.Destination = omit.From(val) + } + if !(m.Type.IsValue()) { + val := random_enums_CommsTextjobtype(nil) + m.Type = omit.From(val) + } +} + +// insertOptRels creates and inserts any optional the relationships on *models.CommsTextJob +// according to the relationships in the template. +// any required relationship should have already exist on the model +func (o *CommsTextJobTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsTextJob) error { + var err error + + return err +} + +// Create builds a commsTextJob and inserts it into the database +// Relations objects are also inserted and placed in the .R field +func (o *CommsTextJobTemplate) Create(ctx context.Context, exec bob.Executor) (*models.CommsTextJob, error) { + var err error + opt := o.BuildSetter() + ensureCreatableCommsTextJob(opt) + + if o.r.DestinationPhone == nil { + CommsTextJobMods.WithNewDestinationPhone().Apply(ctx, o) + } + + var rel0 *models.CommsPhone + + if o.r.DestinationPhone.o.alreadyPersisted { + rel0 = o.r.DestinationPhone.o.Build() + } else { + rel0, err = o.r.DestinationPhone.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.Destination = omit.From(rel0.E164) + + m, err := models.CommsTextJobs.Insert(opt).One(ctx, exec) + if err != nil { + return nil, err + } + + m.R.DestinationPhone = rel0 + + if err := o.insertOptRels(ctx, exec, m); err != nil { + return nil, err + } + return m, err +} + +// MustCreate builds a commsTextJob and inserts it into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o *CommsTextJobTemplate) MustCreate(ctx context.Context, exec bob.Executor) *models.CommsTextJob { + m, err := o.Create(ctx, exec) + if err != nil { + panic(err) + } + return m +} + +// CreateOrFail builds a commsTextJob 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 *CommsTextJobTemplate) CreateOrFail(ctx context.Context, tb testing.TB, exec bob.Executor) *models.CommsTextJob { + tb.Helper() + m, err := o.Create(ctx, exec) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CreateMany builds multiple commsTextJobs and inserts them into the database +// Relations objects are also inserted and placed in the .R field +func (o CommsTextJobTemplate) CreateMany(ctx context.Context, exec bob.Executor, number int) (models.CommsTextJobSlice, error) { + var err error + m := make(models.CommsTextJobSlice, number) + + for i := range m { + m[i], err = o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + return m, nil +} + +// MustCreateMany builds multiple commsTextJobs and inserts them into the database +// Relations objects are also inserted and placed in the .R field +// panics if an error occurs +func (o CommsTextJobTemplate) MustCreateMany(ctx context.Context, exec bob.Executor, number int) models.CommsTextJobSlice { + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + panic(err) + } + return m +} + +// CreateManyOrFail builds multiple commsTextJobs 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 CommsTextJobTemplate) CreateManyOrFail(ctx context.Context, tb testing.TB, exec bob.Executor, number int) models.CommsTextJobSlice { + tb.Helper() + m, err := o.CreateMany(ctx, exec, number) + if err != nil { + tb.Fatal(err) + return nil + } + return m +} + +// CommsTextJob has methods that act as mods for the CommsTextJobTemplate +var CommsTextJobMods commsTextJobMods + +type commsTextJobMods struct{} + +func (m commsTextJobMods) RandomizeAllColumns(f *faker.Faker) CommsTextJobMod { + return CommsTextJobModSlice{ + CommsTextJobMods.RandomContent(f), + CommsTextJobMods.RandomCreated(f), + CommsTextJobMods.RandomDestination(f), + CommsTextJobMods.RandomID(f), + CommsTextJobMods.RandomType(f), + } +} + +// Set the model columns to this value +func (m commsTextJobMods) Content(val string) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Content = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsTextJobMods) ContentFunc(f func() string) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Content = f + }) +} + +// Clear any values for the column +func (m commsTextJobMods) UnsetContent() CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Content = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextJobMods) RandomContent(f *faker.Faker) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Content = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsTextJobMods) Created(val time.Time) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Created = func() time.Time { return val } + }) +} + +// Set the Column from the function +func (m commsTextJobMods) CreatedFunc(f func() time.Time) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Created = f + }) +} + +// Clear any values for the column +func (m commsTextJobMods) UnsetCreated() CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Created = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextJobMods) RandomCreated(f *faker.Faker) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Created = func() time.Time { + return random_time_Time(f) + } + }) +} + +// Set the model columns to this value +func (m commsTextJobMods) Destination(val string) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Destination = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsTextJobMods) DestinationFunc(f func() string) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Destination = f + }) +} + +// Clear any values for the column +func (m commsTextJobMods) UnsetDestination() CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Destination = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextJobMods) RandomDestination(f *faker.Faker) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Destination = func() string { + return random_string(f) + } + }) +} + +// Set the model columns to this value +func (m commsTextJobMods) ID(val int32) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.ID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m commsTextJobMods) IDFunc(f func() int32) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.ID = f + }) +} + +// Clear any values for the column +func (m commsTextJobMods) UnsetID() CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + 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 commsTextJobMods) RandomID(f *faker.Faker) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.ID = func() int32 { + return random_int32(f) + } + }) +} + +// Set the model columns to this value +func (m commsTextJobMods) Type(val enums.CommsTextjobtype) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Type = func() enums.CommsTextjobtype { return val } + }) +} + +// Set the Column from the function +func (m commsTextJobMods) TypeFunc(f func() enums.CommsTextjobtype) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Type = f + }) +} + +// Clear any values for the column +func (m commsTextJobMods) UnsetType() CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Type = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextJobMods) RandomType(f *faker.Faker) CommsTextJobMod { + return CommsTextJobModFunc(func(_ context.Context, o *CommsTextJobTemplate) { + o.Type = func() enums.CommsTextjobtype { + return random_enums_CommsTextjobtype(f) + } + }) +} + +func (m commsTextJobMods) WithParentsCascading() CommsTextJobMod { + return CommsTextJobModFunc(func(ctx context.Context, o *CommsTextJobTemplate) { + if isDone, _ := commsTextJobWithParentsCascadingCtx.Value(ctx); isDone { + return + } + ctx = commsTextJobWithParentsCascadingCtx.WithValue(ctx, true) + { + + related := o.f.NewCommsPhoneWithContext(ctx, CommsPhoneMods.WithParentsCascading()) + m.WithDestinationPhone(related).Apply(ctx, o) + } + }) +} + +func (m commsTextJobMods) WithDestinationPhone(rel *CommsPhoneTemplate) CommsTextJobMod { + return CommsTextJobModFunc(func(ctx context.Context, o *CommsTextJobTemplate) { + o.r.DestinationPhone = &commsTextJobRDestinationPhoneR{ + o: rel, + } + }) +} + +func (m commsTextJobMods) WithNewDestinationPhone(mods ...CommsPhoneMod) CommsTextJobMod { + return CommsTextJobModFunc(func(ctx context.Context, o *CommsTextJobTemplate) { + related := o.f.NewCommsPhoneWithContext(ctx, mods...) + + m.WithDestinationPhone(related).Apply(ctx, o) + }) +} + +func (m commsTextJobMods) WithExistingDestinationPhone(em *models.CommsPhone) CommsTextJobMod { + return CommsTextJobModFunc(func(ctx context.Context, o *CommsTextJobTemplate) { + o.r.DestinationPhone = &commsTextJobRDestinationPhoneR{ + o: o.f.FromExistingCommsPhone(em), + } + }) +} + +func (m commsTextJobMods) WithoutDestinationPhone() CommsTextJobMod { + return CommsTextJobModFunc(func(ctx context.Context, o *CommsTextJobTemplate) { + o.r.DestinationPhone = nil + }) +} diff --git a/db/factory/comms.text_log.bob.go b/db/factory/comms.text_log.bob.go index 6b1cd6e0..d2e88519 100644 --- a/db/factory/comms.text_log.bob.go +++ b/db/factory/comms.text_log.bob.go @@ -40,6 +40,7 @@ type CommsTextLogTemplate struct { Created func() time.Time Destination func() string ID func() int32 + IsWelcome func() bool Origin func() enums.CommsTextorigin Source func() string @@ -107,6 +108,10 @@ func (o CommsTextLogTemplate) BuildSetter() *models.CommsTextLogSetter { val := o.ID() m.ID = omit.From(val) } + if o.IsWelcome != nil { + val := o.IsWelcome() + m.IsWelcome = omit.From(val) + } if o.Origin != nil { val := o.Origin() m.Origin = omit.From(val) @@ -149,6 +154,9 @@ func (o CommsTextLogTemplate) Build() *models.CommsTextLog { if o.ID != nil { m.ID = o.ID() } + if o.IsWelcome != nil { + m.IsWelcome = o.IsWelcome() + } if o.Origin != nil { m.Origin = o.Origin() } @@ -187,6 +195,10 @@ func ensureCreatableCommsTextLog(m *models.CommsTextLogSetter) { val := random_string(nil) m.Destination = omit.From(val) } + if !(m.IsWelcome.IsValue()) { + val := random_bool(nil) + m.IsWelcome = omit.From(val) + } if !(m.Origin.IsValue()) { val := random_enums_CommsTextorigin(nil) m.Origin = omit.From(val) @@ -336,6 +348,7 @@ func (m commsTextLogMods) RandomizeAllColumns(f *faker.Faker) CommsTextLogMod { CommsTextLogMods.RandomCreated(f), CommsTextLogMods.RandomDestination(f), CommsTextLogMods.RandomID(f), + CommsTextLogMods.RandomIsWelcome(f), CommsTextLogMods.RandomOrigin(f), CommsTextLogMods.RandomSource(f), } @@ -465,6 +478,37 @@ func (m commsTextLogMods) RandomID(f *faker.Faker) CommsTextLogMod { }) } +// Set the model columns to this value +func (m commsTextLogMods) IsWelcome(val bool) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.IsWelcome = func() bool { return val } + }) +} + +// Set the Column from the function +func (m commsTextLogMods) IsWelcomeFunc(f func() bool) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.IsWelcome = f + }) +} + +// Clear any values for the column +func (m commsTextLogMods) UnsetIsWelcome() CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.IsWelcome = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextLogMods) RandomIsWelcome(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.IsWelcome = func() bool { + return random_bool(f) + } + }) +} + // Set the model columns to this value func (m commsTextLogMods) Origin(val enums.CommsTextorigin) CommsTextLogMod { return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { diff --git a/db/migrations/00041_text_log_overhaul.sql b/db/migrations/00041_text_log_overhaul.sql index 4e33d846..2a954d31 100644 --- a/db/migrations/00041_text_log_overhaul.sql +++ b/db/migrations/00041_text_log_overhaul.sql @@ -6,18 +6,34 @@ CREATE TYPE comms.TextOrigin AS ENUM ( 'llm', 'website-action' ); +CREATE TYPE comms.TextJobType AS ENUM ( + 'report-confirmation' +); +CREATE TABLE comms.text_job ( + content TEXT NOT NULL, + created TIMESTAMP WITHOUT TIME ZONE NOT NULL, + destination TEXT NOT NULL REFERENCES comms.phone(e164), + id SERIAL NOT NULL, + type_ comms.TextJobType NOT NULL, + PRIMARY KEY(id) +); +COMMENT ON TABLE comms.text_job IS 'Used to track text messages that should be sent later'; CREATE TABLE comms.text_log ( content TEXT NOT NULL, created TIMESTAMP WITHOUT TIME ZONE NOT NULL, destination TEXT NOT NULL REFERENCES comms.phone(e164), id SERIAL, + is_welcome BOOLEAN NOT NULL, origin comms.TextOrigin NOT NULL, source TEXT NOT NULL REFERENCES comms.phone(e164), PRIMARY KEY(id) ); +COMMENT ON TABLE comms.text_log IS 'Used to track text messages that were sent.'; -- +goose Down DROP TABLE comms.text_log; +DROP TABLE comms.text_job; +DROP TYPE comms.TextJobType; DROP TYPE comms.TextOrigin; CREATE TYPE comms.MessageTypeText AS ENUM ( 'initial-contact', diff --git a/db/models/bob_joins.bob.go b/db/models/bob_joins.bob.go index e6ca18af..a69510f4 100644 --- a/db/models/bob_joins.bob.go +++ b/db/models/bob_joins.bob.go @@ -38,6 +38,7 @@ type joins[Q dialect.Joinable] struct { CommsEmailLogs joinSet[commsEmailLogJoins[Q]] CommsEmailTemplates joinSet[commsEmailTemplateJoins[Q]] CommsPhones joinSet[commsPhoneJoins[Q]] + CommsTextJobs joinSet[commsTextJobJoins[Q]] CommsTextLogs joinSet[commsTextLogJoins[Q]] FieldseekerContainerrelates joinSet[fieldseekerContainerrelateJoins[Q]] FieldseekerFieldscoutinglogs joinSet[fieldseekerFieldscoutinglogJoins[Q]] @@ -104,6 +105,7 @@ func getJoins[Q dialect.Joinable]() joins[Q] { CommsEmailLogs: buildJoinSet[commsEmailLogJoins[Q]](CommsEmailLogs.Columns, buildCommsEmailLogJoins), CommsEmailTemplates: buildJoinSet[commsEmailTemplateJoins[Q]](CommsEmailTemplates.Columns, buildCommsEmailTemplateJoins), CommsPhones: buildJoinSet[commsPhoneJoins[Q]](CommsPhones.Columns, buildCommsPhoneJoins), + CommsTextJobs: buildJoinSet[commsTextJobJoins[Q]](CommsTextJobs.Columns, buildCommsTextJobJoins), CommsTextLogs: buildJoinSet[commsTextLogJoins[Q]](CommsTextLogs.Columns, buildCommsTextLogJoins), FieldseekerContainerrelates: buildJoinSet[fieldseekerContainerrelateJoins[Q]](FieldseekerContainerrelates.Columns, buildFieldseekerContainerrelateJoins), FieldseekerFieldscoutinglogs: buildJoinSet[fieldseekerFieldscoutinglogJoins[Q]](FieldseekerFieldscoutinglogs.Columns, buildFieldseekerFieldscoutinglogJoins), diff --git a/db/models/bob_loaders.bob.go b/db/models/bob_loaders.bob.go index aff45d78..3a003482 100644 --- a/db/models/bob_loaders.bob.go +++ b/db/models/bob_loaders.bob.go @@ -23,6 +23,7 @@ type preloaders struct { CommsEmailLog commsEmailLogPreloader CommsEmailTemplate commsEmailTemplatePreloader CommsPhone commsPhonePreloader + CommsTextJob commsTextJobPreloader CommsTextLog commsTextLogPreloader FieldseekerContainerrelate fieldseekerContainerrelatePreloader FieldseekerFieldscoutinglog fieldseekerFieldscoutinglogPreloader @@ -81,6 +82,7 @@ func getPreloaders() preloaders { CommsEmailLog: buildCommsEmailLogPreloader(), CommsEmailTemplate: buildCommsEmailTemplatePreloader(), CommsPhone: buildCommsPhonePreloader(), + CommsTextJob: buildCommsTextJobPreloader(), CommsTextLog: buildCommsTextLogPreloader(), FieldseekerContainerrelate: buildFieldseekerContainerrelatePreloader(), FieldseekerFieldscoutinglog: buildFieldseekerFieldscoutinglogPreloader(), @@ -145,6 +147,7 @@ type thenLoaders[Q orm.Loadable] struct { CommsEmailLog commsEmailLogThenLoader[Q] CommsEmailTemplate commsEmailTemplateThenLoader[Q] CommsPhone commsPhoneThenLoader[Q] + CommsTextJob commsTextJobThenLoader[Q] CommsTextLog commsTextLogThenLoader[Q] FieldseekerContainerrelate fieldseekerContainerrelateThenLoader[Q] FieldseekerFieldscoutinglog fieldseekerFieldscoutinglogThenLoader[Q] @@ -203,6 +206,7 @@ func getThenLoaders[Q orm.Loadable]() thenLoaders[Q] { CommsEmailLog: buildCommsEmailLogThenLoader[Q](), CommsEmailTemplate: buildCommsEmailTemplateThenLoader[Q](), CommsPhone: buildCommsPhoneThenLoader[Q](), + CommsTextJob: buildCommsTextJobThenLoader[Q](), CommsTextLog: buildCommsTextLogThenLoader[Q](), FieldseekerContainerrelate: buildFieldseekerContainerrelateThenLoader[Q](), FieldseekerFieldscoutinglog: buildFieldseekerFieldscoutinglogThenLoader[Q](), diff --git a/db/models/bob_where.bob.go b/db/models/bob_where.bob.go index fad47793..d25228a0 100644 --- a/db/models/bob_where.bob.go +++ b/db/models/bob_where.bob.go @@ -23,6 +23,7 @@ func Where[Q psql.Filterable]() struct { CommsEmailLogs commsEmailLogWhere[Q] CommsEmailTemplates commsEmailTemplateWhere[Q] CommsPhones commsPhoneWhere[Q] + CommsTextJobs commsTextJobWhere[Q] CommsTextLogs commsTextLogWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] @@ -87,6 +88,7 @@ func Where[Q psql.Filterable]() struct { CommsEmailLogs commsEmailLogWhere[Q] CommsEmailTemplates commsEmailTemplateWhere[Q] CommsPhones commsPhoneWhere[Q] + CommsTextJobs commsTextJobWhere[Q] CommsTextLogs commsTextLogWhere[Q] FieldseekerContainerrelates fieldseekerContainerrelateWhere[Q] FieldseekerFieldscoutinglogs fieldseekerFieldscoutinglogWhere[Q] @@ -150,6 +152,7 @@ func Where[Q psql.Filterable]() struct { CommsEmailLogs: buildCommsEmailLogWhere[Q](CommsEmailLogs.Columns), CommsEmailTemplates: buildCommsEmailTemplateWhere[Q](CommsEmailTemplates.Columns), CommsPhones: buildCommsPhoneWhere[Q](CommsPhones.Columns), + CommsTextJobs: buildCommsTextJobWhere[Q](CommsTextJobs.Columns), CommsTextLogs: buildCommsTextLogWhere[Q](CommsTextLogs.Columns), FieldseekerContainerrelates: buildFieldseekerContainerrelateWhere[Q](FieldseekerContainerrelates.Columns), FieldseekerFieldscoutinglogs: buildFieldseekerFieldscoutinglogWhere[Q](FieldseekerFieldscoutinglogs.Columns), diff --git a/db/models/comms.phone.bob.go b/db/models/comms.phone.bob.go index ffc12632..6af2d6e7 100644 --- a/db/models/comms.phone.bob.go +++ b/db/models/comms.phone.bob.go @@ -43,6 +43,7 @@ type CommsPhonesQuery = *psql.ViewQuery[*CommsPhone, CommsPhoneSlice] // commsPhoneR is where relationships are stored. type commsPhoneR struct { + DestinationTextJobs CommsTextJobSlice // comms.text_job.text_job_destination_fkey DestinationTextLogs CommsTextLogSlice // comms.text_log.text_log_destination_fkey SourceTextLogs CommsTextLogSlice // comms.text_log.text_log_source_fkey } @@ -371,6 +372,30 @@ func (o CommsPhoneSlice) ReloadAll(ctx context.Context, exec bob.Executor) error return nil } +// DestinationTextJobs starts a query for related objects on comms.text_job +func (o *CommsPhone) DestinationTextJobs(mods ...bob.Mod[*dialect.SelectQuery]) CommsTextJobsQuery { + return CommsTextJobs.Query(append(mods, + sm.Where(CommsTextJobs.Columns.Destination.EQ(psql.Arg(o.E164))), + )...) +} + +func (os CommsPhoneSlice) DestinationTextJobs(mods ...bob.Mod[*dialect.SelectQuery]) CommsTextJobsQuery { + pkE164 := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkE164 = append(pkE164, o.E164) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkE164), "text[]")), + )) + + return CommsTextJobs.Query(append(mods, + sm.Where(psql.Group(CommsTextJobs.Columns.Destination).OP("IN", PKArgExpr)), + )...) +} + // DestinationTextLogs starts a query for related objects on comms.text_log func (o *CommsPhone) DestinationTextLogs(mods ...bob.Mod[*dialect.SelectQuery]) CommsTextLogsQuery { return CommsTextLogs.Query(append(mods, @@ -419,6 +444,74 @@ func (os CommsPhoneSlice) SourceTextLogs(mods ...bob.Mod[*dialect.SelectQuery]) )...) } +func insertCommsPhoneDestinationTextJobs0(ctx context.Context, exec bob.Executor, commsTextJobs1 []*CommsTextJobSetter, commsPhone0 *CommsPhone) (CommsTextJobSlice, error) { + for i := range commsTextJobs1 { + commsTextJobs1[i].Destination = omit.From(commsPhone0.E164) + } + + ret, err := CommsTextJobs.Insert(bob.ToMods(commsTextJobs1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertCommsPhoneDestinationTextJobs0: %w", err) + } + + return ret, nil +} + +func attachCommsPhoneDestinationTextJobs0(ctx context.Context, exec bob.Executor, count int, commsTextJobs1 CommsTextJobSlice, commsPhone0 *CommsPhone) (CommsTextJobSlice, error) { + setter := &CommsTextJobSetter{ + Destination: omit.From(commsPhone0.E164), + } + + err := commsTextJobs1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachCommsPhoneDestinationTextJobs0: %w", err) + } + + return commsTextJobs1, nil +} + +func (commsPhone0 *CommsPhone) InsertDestinationTextJobs(ctx context.Context, exec bob.Executor, related ...*CommsTextJobSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + commsTextJobs1, err := insertCommsPhoneDestinationTextJobs0(ctx, exec, related, commsPhone0) + if err != nil { + return err + } + + commsPhone0.R.DestinationTextJobs = append(commsPhone0.R.DestinationTextJobs, commsTextJobs1...) + + for _, rel := range commsTextJobs1 { + rel.R.DestinationPhone = commsPhone0 + } + return nil +} + +func (commsPhone0 *CommsPhone) AttachDestinationTextJobs(ctx context.Context, exec bob.Executor, related ...*CommsTextJob) error { + if len(related) == 0 { + return nil + } + + var err error + commsTextJobs1 := CommsTextJobSlice(related) + + _, err = attachCommsPhoneDestinationTextJobs0(ctx, exec, len(related), commsTextJobs1, commsPhone0) + if err != nil { + return err + } + + commsPhone0.R.DestinationTextJobs = append(commsPhone0.R.DestinationTextJobs, commsTextJobs1...) + + for _, rel := range related { + rel.R.DestinationPhone = commsPhone0 + } + + return nil +} + func insertCommsPhoneDestinationTextLogs0(ctx context.Context, exec bob.Executor, commsTextLogs1 []*CommsTextLogSetter, commsPhone0 *CommsPhone) (CommsTextLogSlice, error) { for i := range commsTextLogs1 { commsTextLogs1[i].Destination = omit.From(commsPhone0.E164) @@ -577,6 +670,20 @@ func (o *CommsPhone) Preload(name string, retrieved any) error { } switch name { + case "DestinationTextJobs": + rels, ok := retrieved.(CommsTextJobSlice) + if !ok { + return fmt.Errorf("commsPhone cannot load %T as %q", retrieved, name) + } + + o.R.DestinationTextJobs = rels + + for _, rel := range rels { + if rel != nil { + rel.R.DestinationPhone = o + } + } + return nil case "DestinationTextLogs": rels, ok := retrieved.(CommsTextLogSlice) if !ok { @@ -617,11 +724,15 @@ func buildCommsPhonePreloader() commsPhonePreloader { } type commsPhoneThenLoader[Q orm.Loadable] struct { + DestinationTextJobs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { + type DestinationTextJobsLoadInterface interface { + LoadDestinationTextJobs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type DestinationTextLogsLoadInterface interface { LoadDestinationTextLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -630,6 +741,12 @@ func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { } return commsPhoneThenLoader[Q]{ + DestinationTextJobs: thenLoadBuilder[Q]( + "DestinationTextJobs", + func(ctx context.Context, exec bob.Executor, retrieved DestinationTextJobsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadDestinationTextJobs(ctx, exec, mods...) + }, + ), DestinationTextLogs: thenLoadBuilder[Q]( "DestinationTextLogs", func(ctx context.Context, exec bob.Executor, retrieved DestinationTextLogsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -645,6 +762,67 @@ func buildCommsPhoneThenLoader[Q orm.Loadable]() commsPhoneThenLoader[Q] { } } +// LoadDestinationTextJobs loads the commsPhone's DestinationTextJobs into the .R struct +func (o *CommsPhone) LoadDestinationTextJobs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.DestinationTextJobs = nil + + related, err := o.DestinationTextJobs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.DestinationPhone = o + } + + o.R.DestinationTextJobs = related + return nil +} + +// LoadDestinationTextJobs loads the commsPhone's DestinationTextJobs into the .R struct +func (os CommsPhoneSlice) LoadDestinationTextJobs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsTextJobs, err := os.DestinationTextJobs(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.DestinationTextJobs = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsTextJobs { + + if !(o.E164 == rel.Destination) { + continue + } + + rel.R.DestinationPhone = o + + o.R.DestinationTextJobs = append(o.R.DestinationTextJobs, rel) + } + } + + return nil +} + // LoadDestinationTextLogs loads the commsPhone's DestinationTextLogs into the .R struct func (o *CommsPhone) LoadDestinationTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -769,6 +947,7 @@ func (os CommsPhoneSlice) LoadSourceTextLogs(ctx context.Context, exec bob.Execu // commsPhoneC is where relationship counts are stored. type commsPhoneC struct { + DestinationTextJobs *int64 DestinationTextLogs *int64 SourceTextLogs *int64 } @@ -780,6 +959,8 @@ func (o *CommsPhone) PreloadCount(name string, count int64) error { } switch name { + case "DestinationTextJobs": + o.C.DestinationTextJobs = &count case "DestinationTextLogs": o.C.DestinationTextLogs = &count case "SourceTextLogs": @@ -789,12 +970,30 @@ func (o *CommsPhone) PreloadCount(name string, count int64) error { } type commsPhoneCountPreloader struct { + DestinationTextJobs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader } func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { return commsPhoneCountPreloader{ + DestinationTextJobs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*CommsPhone]("DestinationTextJobs", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = CommsPhones.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(CommsTextJobs.Name()), + sm.Where(psql.Quote(CommsTextJobs.Alias(), "destination").EQ(psql.Quote(parent, "e164"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, DestinationTextLogs: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { return countPreloader[*CommsPhone]("DestinationTextLogs", func(parent string) bob.Expression { // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) @@ -833,11 +1032,15 @@ func buildCommsPhoneCountPreloader() commsPhoneCountPreloader { } type commsPhoneCountThenLoader[Q orm.Loadable] struct { + DestinationTextJobs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] DestinationTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] SourceTextLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[Q] { + type DestinationTextJobsCountInterface interface { + LoadCountDestinationTextJobs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type DestinationTextLogsCountInterface interface { LoadCountDestinationTextLogs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -846,6 +1049,12 @@ func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[ } return commsPhoneCountThenLoader[Q]{ + DestinationTextJobs: countThenLoadBuilder[Q]( + "DestinationTextJobs", + func(ctx context.Context, exec bob.Executor, retrieved DestinationTextJobsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountDestinationTextJobs(ctx, exec, mods...) + }, + ), DestinationTextLogs: countThenLoadBuilder[Q]( "DestinationTextLogs", func(ctx context.Context, exec bob.Executor, retrieved DestinationTextLogsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -861,6 +1070,36 @@ func buildCommsPhoneCountThenLoader[Q orm.Loadable]() commsPhoneCountThenLoader[ } } +// LoadCountDestinationTextJobs loads the count of DestinationTextJobs into the C struct +func (o *CommsPhone) LoadCountDestinationTextJobs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.DestinationTextJobs(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.DestinationTextJobs = &count + return nil +} + +// LoadCountDestinationTextJobs loads the count of DestinationTextJobs for a slice +func (os CommsPhoneSlice) LoadCountDestinationTextJobs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountDestinationTextJobs(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + // LoadCountDestinationTextLogs loads the count of DestinationTextLogs into the C struct func (o *CommsPhone) LoadCountDestinationTextLogs(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -923,6 +1162,7 @@ func (os CommsPhoneSlice) LoadCountSourceTextLogs(ctx context.Context, exec bob. type commsPhoneJoins[Q dialect.Joinable] struct { typ string + DestinationTextJobs modAs[Q, commsTextJobColumns] DestinationTextLogs modAs[Q, commsTextLogColumns] SourceTextLogs modAs[Q, commsTextLogColumns] } @@ -934,6 +1174,20 @@ func (j commsPhoneJoins[Q]) aliasedAs(alias string) commsPhoneJoins[Q] { func buildCommsPhoneJoins[Q dialect.Joinable](cols commsPhoneColumns, typ string) commsPhoneJoins[Q] { return commsPhoneJoins[Q]{ typ: typ, + DestinationTextJobs: modAs[Q, commsTextJobColumns]{ + c: CommsTextJobs.Columns, + f: func(to commsTextJobColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsTextJobs.Name().As(to.Alias())).On( + to.Destination.EQ(cols.E164), + )) + } + + return mods + }, + }, DestinationTextLogs: modAs[Q, commsTextLogColumns]{ c: CommsTextLogs.Columns, f: func(to commsTextLogColumns) bob.Mod[Q] { diff --git a/db/models/comms.text_job.bob.go b/db/models/comms.text_job.bob.go new file mode 100644 index 00000000..6a8ce5b5 --- /dev/null +++ b/db/models/comms.text_job.bob.go @@ -0,0 +1,679 @@ +// Code generated by BobGen psql v0.42.1. 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" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/aarondl/opt/omit" + "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" +) + +// CommsTextJob is an object representing the database table. +type CommsTextJob struct { + Content string `db:"content" ` + Created time.Time `db:"created" ` + Destination string `db:"destination" ` + ID int32 `db:"id,pk" ` + Type enums.CommsTextjobtype `db:"type_" ` + + R commsTextJobR `db:"-" ` +} + +// CommsTextJobSlice is an alias for a slice of pointers to CommsTextJob. +// This should almost always be used instead of []*CommsTextJob. +type CommsTextJobSlice []*CommsTextJob + +// CommsTextJobs contains methods to work with the text_job table +var CommsTextJobs = psql.NewTablex[*CommsTextJob, CommsTextJobSlice, *CommsTextJobSetter]("comms", "text_job", buildCommsTextJobColumns("comms.text_job")) + +// CommsTextJobsQuery is a query on the text_job table +type CommsTextJobsQuery = *psql.ViewQuery[*CommsTextJob, CommsTextJobSlice] + +// commsTextJobR is where relationships are stored. +type commsTextJobR struct { + DestinationPhone *CommsPhone // comms.text_job.text_job_destination_fkey +} + +func buildCommsTextJobColumns(alias string) commsTextJobColumns { + return commsTextJobColumns{ + ColumnsExpr: expr.NewColumnsExpr( + "content", "created", "destination", "id", "type_", + ).WithParent("comms.text_job"), + tableAlias: alias, + Content: psql.Quote(alias, "content"), + Created: psql.Quote(alias, "created"), + Destination: psql.Quote(alias, "destination"), + ID: psql.Quote(alias, "id"), + Type: psql.Quote(alias, "type_"), + } +} + +type commsTextJobColumns struct { + expr.ColumnsExpr + tableAlias string + Content psql.Expression + Created psql.Expression + Destination psql.Expression + ID psql.Expression + Type psql.Expression +} + +func (c commsTextJobColumns) Alias() string { + return c.tableAlias +} + +func (commsTextJobColumns) AliasedAs(alias string) commsTextJobColumns { + return buildCommsTextJobColumns(alias) +} + +// CommsTextJobSetter is used for insert/upsert/update operations +// All values are optional, and do not have to be set +// Generated columns are not included +type CommsTextJobSetter struct { + Content omit.Val[string] `db:"content" ` + Created omit.Val[time.Time] `db:"created" ` + Destination omit.Val[string] `db:"destination" ` + ID omit.Val[int32] `db:"id,pk" ` + Type omit.Val[enums.CommsTextjobtype] `db:"type_" ` +} + +func (s CommsTextJobSetter) SetColumns() []string { + vals := make([]string, 0, 5) + if s.Content.IsValue() { + vals = append(vals, "content") + } + if s.Created.IsValue() { + vals = append(vals, "created") + } + if s.Destination.IsValue() { + vals = append(vals, "destination") + } + if s.ID.IsValue() { + vals = append(vals, "id") + } + if s.Type.IsValue() { + vals = append(vals, "type_") + } + return vals +} + +func (s CommsTextJobSetter) Overwrite(t *CommsTextJob) { + if s.Content.IsValue() { + t.Content = s.Content.MustGet() + } + if s.Created.IsValue() { + t.Created = s.Created.MustGet() + } + if s.Destination.IsValue() { + t.Destination = s.Destination.MustGet() + } + if s.ID.IsValue() { + t.ID = s.ID.MustGet() + } + if s.Type.IsValue() { + t.Type = s.Type.MustGet() + } +} + +func (s *CommsTextJobSetter) Apply(q *dialect.InsertQuery) { + q.AppendHooks(func(ctx context.Context, exec bob.Executor) (context.Context, error) { + return CommsTextJobs.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.Content.IsValue() { + vals[0] = psql.Arg(s.Content.MustGet()) + } else { + vals[0] = psql.Raw("DEFAULT") + } + + if s.Created.IsValue() { + vals[1] = psql.Arg(s.Created.MustGet()) + } else { + vals[1] = psql.Raw("DEFAULT") + } + + if s.Destination.IsValue() { + vals[2] = psql.Arg(s.Destination.MustGet()) + } else { + vals[2] = psql.Raw("DEFAULT") + } + + if s.ID.IsValue() { + vals[3] = psql.Arg(s.ID.MustGet()) + } else { + vals[3] = psql.Raw("DEFAULT") + } + + if s.Type.IsValue() { + vals[4] = psql.Arg(s.Type.MustGet()) + } else { + vals[4] = psql.Raw("DEFAULT") + } + + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") + })) +} + +func (s CommsTextJobSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { + return um.Set(s.Expressions()...) +} + +func (s CommsTextJobSetter) Expressions(prefix ...string) []bob.Expression { + exprs := make([]bob.Expression, 0, 5) + + if s.Content.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "content")...), + psql.Arg(s.Content), + }}) + } + + if s.Created.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "created")...), + psql.Arg(s.Created), + }}) + } + + if s.Destination.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "destination")...), + psql.Arg(s.Destination), + }}) + } + + if s.ID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "id")...), + psql.Arg(s.ID), + }}) + } + + if s.Type.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "type_")...), + psql.Arg(s.Type), + }}) + } + + return exprs +} + +// FindCommsTextJob retrieves a single record by primary key +// If cols is empty Find will return all columns. +func FindCommsTextJob(ctx context.Context, exec bob.Executor, IDPK int32, cols ...string) (*CommsTextJob, error) { + if len(cols) == 0 { + return CommsTextJobs.Query( + sm.Where(CommsTextJobs.Columns.ID.EQ(psql.Arg(IDPK))), + ).One(ctx, exec) + } + + return CommsTextJobs.Query( + sm.Where(CommsTextJobs.Columns.ID.EQ(psql.Arg(IDPK))), + sm.Columns(CommsTextJobs.Columns.Only(cols...)), + ).One(ctx, exec) +} + +// CommsTextJobExists checks the presence of a single record by primary key +func CommsTextJobExists(ctx context.Context, exec bob.Executor, IDPK int32) (bool, error) { + return CommsTextJobs.Query( + sm.Where(CommsTextJobs.Columns.ID.EQ(psql.Arg(IDPK))), + ).Exists(ctx, exec) +} + +// AfterQueryHook is called after CommsTextJob is retrieved from the database +func (o *CommsTextJob) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsTextJobs.AfterSelectHooks.RunHooks(ctx, exec, CommsTextJobSlice{o}) + case bob.QueryTypeInsert: + ctx, err = CommsTextJobs.AfterInsertHooks.RunHooks(ctx, exec, CommsTextJobSlice{o}) + case bob.QueryTypeUpdate: + ctx, err = CommsTextJobs.AfterUpdateHooks.RunHooks(ctx, exec, CommsTextJobSlice{o}) + case bob.QueryTypeDelete: + ctx, err = CommsTextJobs.AfterDeleteHooks.RunHooks(ctx, exec, CommsTextJobSlice{o}) + } + + return err +} + +// primaryKeyVals returns the primary key values of the CommsTextJob +func (o *CommsTextJob) primaryKeyVals() bob.Expression { + return psql.Arg(o.ID) +} + +func (o *CommsTextJob) pkEQ() dialect.Expression { + return psql.Quote("comms.text_job", "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 CommsTextJob +func (o *CommsTextJob) Update(ctx context.Context, exec bob.Executor, s *CommsTextJobSetter) error { + v, err := CommsTextJobs.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 CommsTextJob record with an executor +func (o *CommsTextJob) Delete(ctx context.Context, exec bob.Executor) error { + _, err := CommsTextJobs.Delete(dm.Where(o.pkEQ())).Exec(ctx, exec) + return err +} + +// Reload refreshes the CommsTextJob using the executor +func (o *CommsTextJob) Reload(ctx context.Context, exec bob.Executor) error { + o2, err := CommsTextJobs.Query( + sm.Where(CommsTextJobs.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 CommsTextJobSlice is retrieved from the database +func (o CommsTextJobSlice) AfterQueryHook(ctx context.Context, exec bob.Executor, queryType bob.QueryType) error { + var err error + + switch queryType { + case bob.QueryTypeSelect: + ctx, err = CommsTextJobs.AfterSelectHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeInsert: + ctx, err = CommsTextJobs.AfterInsertHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeUpdate: + ctx, err = CommsTextJobs.AfterUpdateHooks.RunHooks(ctx, exec, o) + case bob.QueryTypeDelete: + ctx, err = CommsTextJobs.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err +} + +func (o CommsTextJobSlice) pkIN() dialect.Expression { + if len(o) == 0 { + return psql.Raw("NULL") + } + + return psql.Quote("comms.text_job", "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 CommsTextJobSlice) copyMatchingRows(from ...*CommsTextJob) { + 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 CommsTextJobSlice) 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 CommsTextJobs.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 *CommsTextJob: + o.copyMatchingRows(retrieved) + case []*CommsTextJob: + o.copyMatchingRows(retrieved...) + case CommsTextJobSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsTextJob or a slice of CommsTextJob + // then run the AfterUpdateHooks on the slice + _, err = CommsTextJobs.AfterUpdateHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +// DeleteMod modifies an delete query with "WHERE primary_key IN (o...)" +func (o CommsTextJobSlice) 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 CommsTextJobs.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 *CommsTextJob: + o.copyMatchingRows(retrieved) + case []*CommsTextJob: + o.copyMatchingRows(retrieved...) + case CommsTextJobSlice: + o.copyMatchingRows(retrieved...) + default: + // If the retrieved value is not a CommsTextJob or a slice of CommsTextJob + // then run the AfterDeleteHooks on the slice + _, err = CommsTextJobs.AfterDeleteHooks.RunHooks(ctx, exec, o) + } + + return err + })) + + q.AppendWhere(o.pkIN()) + }) +} + +func (o CommsTextJobSlice) UpdateAll(ctx context.Context, exec bob.Executor, vals CommsTextJobSetter) error { + if len(o) == 0 { + return nil + } + + _, err := CommsTextJobs.Update(vals.UpdateMod(), o.UpdateMod()).All(ctx, exec) + return err +} + +func (o CommsTextJobSlice) DeleteAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + _, err := CommsTextJobs.Delete(o.DeleteMod()).Exec(ctx, exec) + return err +} + +func (o CommsTextJobSlice) ReloadAll(ctx context.Context, exec bob.Executor) error { + if len(o) == 0 { + return nil + } + + o2, err := CommsTextJobs.Query(sm.Where(o.pkIN())).All(ctx, exec) + if err != nil { + return err + } + + o.copyMatchingRows(o2...) + + return nil +} + +// DestinationPhone starts a query for related objects on comms.phone +func (o *CommsTextJob) DestinationPhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + return CommsPhones.Query(append(mods, + sm.Where(CommsPhones.Columns.E164.EQ(psql.Arg(o.Destination))), + )...) +} + +func (os CommsTextJobSlice) DestinationPhone(mods ...bob.Mod[*dialect.SelectQuery]) CommsPhonesQuery { + pkDestination := make(pgtypes.Array[string], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkDestination = append(pkDestination, o.Destination) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkDestination), "text[]")), + )) + + return CommsPhones.Query(append(mods, + sm.Where(psql.Group(CommsPhones.Columns.E164).OP("IN", PKArgExpr)), + )...) +} + +func attachCommsTextJobDestinationPhone0(ctx context.Context, exec bob.Executor, count int, commsTextJob0 *CommsTextJob, commsPhone1 *CommsPhone) (*CommsTextJob, error) { + setter := &CommsTextJobSetter{ + Destination: omit.From(commsPhone1.E164), + } + + err := commsTextJob0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachCommsTextJobDestinationPhone0: %w", err) + } + + return commsTextJob0, nil +} + +func (commsTextJob0 *CommsTextJob) InsertDestinationPhone(ctx context.Context, exec bob.Executor, related *CommsPhoneSetter) error { + var err error + + commsPhone1, err := CommsPhones.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachCommsTextJobDestinationPhone0(ctx, exec, 1, commsTextJob0, commsPhone1) + if err != nil { + return err + } + + commsTextJob0.R.DestinationPhone = commsPhone1 + + commsPhone1.R.DestinationTextJobs = append(commsPhone1.R.DestinationTextJobs, commsTextJob0) + + return nil +} + +func (commsTextJob0 *CommsTextJob) AttachDestinationPhone(ctx context.Context, exec bob.Executor, commsPhone1 *CommsPhone) error { + var err error + + _, err = attachCommsTextJobDestinationPhone0(ctx, exec, 1, commsTextJob0, commsPhone1) + if err != nil { + return err + } + + commsTextJob0.R.DestinationPhone = commsPhone1 + + commsPhone1.R.DestinationTextJobs = append(commsPhone1.R.DestinationTextJobs, commsTextJob0) + + return nil +} + +type commsTextJobWhere[Q psql.Filterable] struct { + Content psql.WhereMod[Q, string] + Created psql.WhereMod[Q, time.Time] + Destination psql.WhereMod[Q, string] + ID psql.WhereMod[Q, int32] + Type psql.WhereMod[Q, enums.CommsTextjobtype] +} + +func (commsTextJobWhere[Q]) AliasedAs(alias string) commsTextJobWhere[Q] { + return buildCommsTextJobWhere[Q](buildCommsTextJobColumns(alias)) +} + +func buildCommsTextJobWhere[Q psql.Filterable](cols commsTextJobColumns) commsTextJobWhere[Q] { + return commsTextJobWhere[Q]{ + Content: psql.Where[Q, string](cols.Content), + Created: psql.Where[Q, time.Time](cols.Created), + Destination: psql.Where[Q, string](cols.Destination), + ID: psql.Where[Q, int32](cols.ID), + Type: psql.Where[Q, enums.CommsTextjobtype](cols.Type), + } +} + +func (o *CommsTextJob) Preload(name string, retrieved any) error { + if o == nil { + return nil + } + + switch name { + case "DestinationPhone": + rel, ok := retrieved.(*CommsPhone) + if !ok { + return fmt.Errorf("commsTextJob cannot load %T as %q", retrieved, name) + } + + o.R.DestinationPhone = rel + + if rel != nil { + rel.R.DestinationTextJobs = CommsTextJobSlice{o} + } + return nil + default: + return fmt.Errorf("commsTextJob has no relationship %q", name) + } +} + +type commsTextJobPreloader struct { + DestinationPhone func(...psql.PreloadOption) psql.Preloader +} + +func buildCommsTextJobPreloader() commsTextJobPreloader { + return commsTextJobPreloader{ + DestinationPhone: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*CommsPhone, CommsPhoneSlice](psql.PreloadRel{ + Name: "DestinationPhone", + Sides: []psql.PreloadSide{ + { + From: CommsTextJobs, + To: CommsPhones, + FromColumns: []string{"destination"}, + ToColumns: []string{"e164"}, + }, + }, + }, CommsPhones.Columns.Names(), opts...) + }, + } +} + +type commsTextJobThenLoader[Q orm.Loadable] struct { + DestinationPhone func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] +} + +func buildCommsTextJobThenLoader[Q orm.Loadable]() commsTextJobThenLoader[Q] { + type DestinationPhoneLoadInterface interface { + LoadDestinationPhone(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + + return commsTextJobThenLoader[Q]{ + DestinationPhone: thenLoadBuilder[Q]( + "DestinationPhone", + func(ctx context.Context, exec bob.Executor, retrieved DestinationPhoneLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadDestinationPhone(ctx, exec, mods...) + }, + ), + } +} + +// LoadDestinationPhone loads the commsTextJob's DestinationPhone into the .R struct +func (o *CommsTextJob) LoadDestinationPhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.DestinationPhone = nil + + related, err := o.DestinationPhone(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.DestinationTextJobs = CommsTextJobSlice{o} + + o.R.DestinationPhone = related + return nil +} + +// LoadDestinationPhone loads the commsTextJob's DestinationPhone into the .R struct +func (os CommsTextJobSlice) LoadDestinationPhone(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + commsPhones, err := os.DestinationPhone(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range commsPhones { + + if !(o.Destination == rel.E164) { + continue + } + + rel.R.DestinationTextJobs = append(rel.R.DestinationTextJobs, o) + + o.R.DestinationPhone = rel + break + } + } + + return nil +} + +type commsTextJobJoins[Q dialect.Joinable] struct { + typ string + DestinationPhone modAs[Q, commsPhoneColumns] +} + +func (j commsTextJobJoins[Q]) aliasedAs(alias string) commsTextJobJoins[Q] { + return buildCommsTextJobJoins[Q](buildCommsTextJobColumns(alias), j.typ) +} + +func buildCommsTextJobJoins[Q dialect.Joinable](cols commsTextJobColumns, typ string) commsTextJobJoins[Q] { + return commsTextJobJoins[Q]{ + typ: typ, + DestinationPhone: modAs[Q, commsPhoneColumns]{ + c: CommsPhones.Columns, + f: func(to commsPhoneColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, CommsPhones.Name().As(to.Alias())).On( + to.E164.EQ(cols.Destination), + )) + } + + return mods + }, + }, + } +} diff --git a/db/models/comms.text_log.bob.go b/db/models/comms.text_log.bob.go index fc1054d3..445ac37f 100644 --- a/db/models/comms.text_log.bob.go +++ b/db/models/comms.text_log.bob.go @@ -29,6 +29,7 @@ type CommsTextLog struct { Created time.Time `db:"created" ` Destination string `db:"destination" ` ID int32 `db:"id,pk" ` + IsWelcome bool `db:"is_welcome" ` Origin enums.CommsTextorigin `db:"origin" ` Source string `db:"source" ` @@ -54,13 +55,14 @@ type commsTextLogR struct { func buildCommsTextLogColumns(alias string) commsTextLogColumns { return commsTextLogColumns{ ColumnsExpr: expr.NewColumnsExpr( - "content", "created", "destination", "id", "origin", "source", + "content", "created", "destination", "id", "is_welcome", "origin", "source", ).WithParent("comms.text_log"), tableAlias: alias, Content: psql.Quote(alias, "content"), Created: psql.Quote(alias, "created"), Destination: psql.Quote(alias, "destination"), ID: psql.Quote(alias, "id"), + IsWelcome: psql.Quote(alias, "is_welcome"), Origin: psql.Quote(alias, "origin"), Source: psql.Quote(alias, "source"), } @@ -73,6 +75,7 @@ type commsTextLogColumns struct { Created psql.Expression Destination psql.Expression ID psql.Expression + IsWelcome psql.Expression Origin psql.Expression Source psql.Expression } @@ -93,12 +96,13 @@ type CommsTextLogSetter struct { Created omit.Val[time.Time] `db:"created" ` Destination omit.Val[string] `db:"destination" ` ID omit.Val[int32] `db:"id,pk" ` + IsWelcome omit.Val[bool] `db:"is_welcome" ` Origin omit.Val[enums.CommsTextorigin] `db:"origin" ` Source omit.Val[string] `db:"source" ` } func (s CommsTextLogSetter) SetColumns() []string { - vals := make([]string, 0, 6) + vals := make([]string, 0, 7) if s.Content.IsValue() { vals = append(vals, "content") } @@ -111,6 +115,9 @@ func (s CommsTextLogSetter) SetColumns() []string { if s.ID.IsValue() { vals = append(vals, "id") } + if s.IsWelcome.IsValue() { + vals = append(vals, "is_welcome") + } if s.Origin.IsValue() { vals = append(vals, "origin") } @@ -133,6 +140,9 @@ func (s CommsTextLogSetter) Overwrite(t *CommsTextLog) { if s.ID.IsValue() { t.ID = s.ID.MustGet() } + if s.IsWelcome.IsValue() { + t.IsWelcome = s.IsWelcome.MustGet() + } if s.Origin.IsValue() { t.Origin = s.Origin.MustGet() } @@ -147,7 +157,7 @@ func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) { }) 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, 7) if s.Content.IsValue() { vals[0] = psql.Arg(s.Content.MustGet()) } else { @@ -172,18 +182,24 @@ func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) { vals[3] = psql.Raw("DEFAULT") } - if s.Origin.IsValue() { - vals[4] = psql.Arg(s.Origin.MustGet()) + if s.IsWelcome.IsValue() { + vals[4] = psql.Arg(s.IsWelcome.MustGet()) } else { vals[4] = psql.Raw("DEFAULT") } - if s.Source.IsValue() { - vals[5] = psql.Arg(s.Source.MustGet()) + if s.Origin.IsValue() { + vals[5] = psql.Arg(s.Origin.MustGet()) } else { vals[5] = psql.Raw("DEFAULT") } + if s.Source.IsValue() { + vals[6] = psql.Arg(s.Source.MustGet()) + } else { + vals[6] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -193,7 +209,7 @@ func (s CommsTextLogSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 6) + exprs := make([]bob.Expression, 0, 7) if s.Content.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -223,6 +239,13 @@ func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if s.IsWelcome.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "is_welcome")...), + psql.Arg(s.IsWelcome), + }}) + } + if s.Origin.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "origin")...), @@ -612,6 +635,7 @@ type commsTextLogWhere[Q psql.Filterable] struct { Created psql.WhereMod[Q, time.Time] Destination psql.WhereMod[Q, string] ID psql.WhereMod[Q, int32] + IsWelcome psql.WhereMod[Q, bool] Origin psql.WhereMod[Q, enums.CommsTextorigin] Source psql.WhereMod[Q, string] } @@ -626,6 +650,7 @@ func buildCommsTextLogWhere[Q psql.Filterable](cols commsTextLogColumns) commsTe Created: psql.Where[Q, time.Time](cols.Created), Destination: psql.Where[Q, string](cols.Destination), ID: psql.Where[Q, int32](cols.ID), + IsWelcome: psql.Where[Q, bool](cols.IsWelcome), Origin: psql.Where[Q, enums.CommsTextorigin](cols.Origin), Source: psql.Where[Q, string](cols.Source), } diff --git a/main.go b/main.go index ed8956b6..ac76e6af 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/auth" "github.com/Gleipnir-Technology/nidus-sync/background" "github.com/Gleipnir-Technology/nidus-sync/comms/email" + "github.com/Gleipnir-Technology/nidus-sync/comms/text" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/public-report" @@ -46,6 +47,11 @@ func main() { os.Exit(3) } + err = text.StoreSources() + if err != nil { + log.Error().Err(err).Msg("Failed to store text source phone numbers") + os.Exit(4) + } router_logger := log.With().Logger() r := chi.NewRouter() From 940f3901be9fdf65f57e15c60790ad3821298a20 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 26 Jan 2026 16:11:00 +0000 Subject: [PATCH 0124/1453] Redirect root mock to additional mocks --- public-report/template/mock/root.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public-report/template/mock/root.html b/public-report/template/mock/root.html index 02d7ae16..fb2ac179 100644 --- a/public-report/template/mock/root.html +++ b/public-report/template/mock/root.html @@ -52,7 +52,7 @@

    Report Mosquito Nuisance

    Report areas with high adult mosquito activity causing discomfort or concern.

    - Report Problem + Report Problem
    @@ -64,7 +64,7 @@

    Report Standing Water

    Report any water that has been sitting for several days, where mosquitoes can live.

    - Report Source + Report Source
    @@ -76,7 +76,7 @@

    Follow-up or Check Status

    Check on a previous request or view current mosquito activity in your area.

    - Get Status + Get Status
    From c276cbac0b50e399055114801d4458157712537c Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 26 Jan 2026 18:42:30 +0000 Subject: [PATCH 0125/1453] Add command to hash password directly Useful for resetting passwords manually. --- auth/auth.go | 6 +++--- cmd/passwordgen/main.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 cmd/passwordgen/main.go diff --git a/auth/auth.go b/auth/auth.go index 688ec2c6..f2f8d0b5 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -131,7 +131,7 @@ func SignoutUser(r *http.Request, user *models.User) { } func SignupUser(ctx context.Context, username string, name string, password string) (*models.User, error) { - passwordHash, err := hashPassword(password) + passwordHash, err := HashPassword(password) if err != nil { return nil, fmt.Errorf("Cannot signup user, failed to create hashed password: %w", err) } @@ -182,7 +182,7 @@ func findUser(ctx context.Context, user_id int) (*models.User, error) { return user, err } -func hashPassword(password string) (string, error) { +func HashPassword(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) return string(bytes), err } @@ -205,7 +205,7 @@ func validatePassword(password, hash string) bool { } func validateUser(ctx context.Context, username string, password string) (*models.User, error) { - passwordHash, err := hashPassword(password) + passwordHash, err := HashPassword(password) if err != nil { return nil, fmt.Errorf("Failed to hash password: %w", err) } diff --git a/cmd/passwordgen/main.go b/cmd/passwordgen/main.go new file mode 100644 index 00000000..e0b3aad4 --- /dev/null +++ b/cmd/passwordgen/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "bufio" + "errors" + "fmt" + "log" + "os" + + "github.com/Gleipnir-Technology/nidus-sync/auth" +) + +func main() { + var password string + scanValue("Please enter your password : ", &password) + + hash, err := auth.HashPassword(password) + if err != nil { + fmt.Printf("Failed to hash password: %v\n", err) + os.Exit(1) + } + + fmt.Println("Password:", password) + fmt.Println("Hash: ", hash) + +} + +func scanValue(message string, result *string) { + fmt.Printf(message) + scanner := bufio.NewScanner(os.Stdin) + if ok := scanner.Scan(); !ok { + log.Fatal(errors.New("Failed to scan input")) + } + *result = scanner.Text() +} From 6070d50a5895dfcf00f00f22c33593f857b125b4 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 26 Jan 2026 20:29:04 +0000 Subject: [PATCH 0126/1453] Begin process of getting text responses from an LLM. --- api/twilio.go | 9 +- comms/text/db.go | 3 +- comms/text/initial.go | 8 +- comms/text/llm.go | 9 ++ comms/text/report-subscription.go | 2 +- comms/text/text.go | 4 +- config/config.go | 14 ++- db/sql/texts_by_senders.bob.go | 135 +++++++++++++++++++++++ db/sql/texts_by_senders.bob.sql | 20 ++++ db/sql/texts_by_senders.sql | 17 +++ go.mod | 26 +++-- go.sum | 38 +++++++ llm/client.go | 22 ++++ llm/log.go | 42 +++++++ llm/openai.go | 175 ++++++++++++++++++++++++++++++ main.go | 7 ++ platform/text.go | 113 +++++++++++++++++++ tools/texts_by_senders.sql | 17 +++ 18 files changed, 639 insertions(+), 22 deletions(-) create mode 100644 comms/text/llm.go create mode 100644 db/sql/texts_by_senders.bob.go create mode 100644 db/sql/texts_by_senders.bob.sql create mode 100644 db/sql/texts_by_senders.sql create mode 100644 llm/client.go create mode 100644 llm/log.go create mode 100644 llm/openai.go create mode 100644 platform/text.go create mode 100644 tools/texts_by_senders.sql diff --git a/api/twilio.go b/api/twilio.go index 7323eb69..9a252e80 100644 --- a/api/twilio.go +++ b/api/twilio.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" + "github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/rs/zerolog/log" "github.com/twilio/twilio-go/twiml" ) @@ -39,11 +40,9 @@ func twilioTextPost(w http.ResponseWriter, r *http.Request) { to_zip := r.PostFormValue("ToZip") to_country := r.PostFormValue("ToCountry") log.Info().Str("message_sid", message_sid).Str("account_sid", account_sid).Str("messaging_service_sid", messaging_service_sid).Str("from", from).Str("to_", to_).Str("body", body).Str("num_media", num_media).Str("num_segments", num_segments).Str("media_content_type0", media_content_type0).Str("media_url0", media_url0).Str("from_city", from_city).Str("from_state", from_state).Str("from_zip", from_zip).Str("from_country", from_country).Str("to_city", to_city).Str("to_state", to_state).Str("to_zip", to_zip).Str("to_country", to_country).Msg("got text") - twiml, _ := twiml.Messages([]twiml.Element{ - &twiml.MessagingMessage{ - Body: "Hey there.", - }, - }) + + twiml, _ := twiml.Messages([]twiml.Element{}) + go platform.HandleTextMessage(from, to_, body) w.Header().Set("Content-Type", "text/xml") fmt.Fprintf(w, "%s", twiml) } diff --git a/comms/text/db.go b/comms/text/db.go index 1795a061..9801e1ca 100644 --- a/comms/text/db.go +++ b/comms/text/db.go @@ -68,12 +68,13 @@ func ensureInDB(ctx context.Context, destination string) (err error) { return nil } -func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin) (err error) { +func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin, is_welcome bool) (err error) { _, err = models.CommsTextLogs.Insert(&models.CommsTextLogSetter{ //ID: Content: omit.From(content), Created: omit.From(time.Now()), Destination: omit.From(destination), + IsWelcome: omit.From(is_welcome), Origin: omit.From(origin), Source: omit.From(source), }).One(ctx, db.PGInstance.BobDB) diff --git a/comms/text/initial.go b/comms/text/initial.go index d2bf35a5..0c25a62c 100644 --- a/comms/text/initial.go +++ b/comms/text/initial.go @@ -23,9 +23,15 @@ func ensureInitialText(ctx context.Context, src string, dst string) error { return nil } content := "Welcome to Report Mosquitoes Online. We received your request and want to confirm text updates. Reply YES to continue. Reply STOP at any time to unsubscribe" - err = sendText(ctx, src, dst, content, origin) + err = sendText(ctx, src, dst, content, origin, true) if err != nil { return fmt.Errorf("Failed to send initial confirmation: %w", err) } return nil } + +func SendInitialReprompt(ctx context.Context, src string, dst string) error { + content := "I have to start with either 'YES' or 'STOP' first, Which do you want?" + err := sendText(ctx, src, dst, content, enums.CommsTextoriginLLM, false) + return err +} diff --git a/comms/text/llm.go b/comms/text/llm.go new file mode 100644 index 00000000..efd664fa --- /dev/null +++ b/comms/text/llm.go @@ -0,0 +1,9 @@ +package text + +import ( + "github.com/rs/zerolog/log" +) + +func SendTextFromLLM(content string) { + log.Info().Str("content", content).Msg("Pretend I sent a message") +} diff --git a/comms/text/report-subscription.go b/comms/text/report-subscription.go index 092b9ee2..c43f6b89 100644 --- a/comms/text/report-subscription.go +++ b/comms/text/report-subscription.go @@ -53,7 +53,7 @@ func sendReportSubscription(ctx context.Context, job Job) error { return fmt.Errorf("Failed to check if subscribed: %w", err) } if !sub { - err = sendText(ctx, j.source(), j.destination(), j.content(), enums.CommsTextoriginWebsiteAction) + err = sendText(ctx, j.source(), j.destination(), j.content(), enums.CommsTextoriginWebsiteAction, false) if err != nil { return fmt.Errorf("Failed to send report subscription confirmation: %w", err) } diff --git a/comms/text/text.go b/comms/text/text.go index 415a301d..8384405f 100644 --- a/comms/text/text.go +++ b/comms/text/text.go @@ -19,12 +19,12 @@ func ParsePhoneNumber(input string) (*E164, error) { return phonenumbers.Parse(input, "US") } -func sendText(ctx context.Context, source string, destination string, message string, origin enums.CommsTextorigin) error { +func sendText(ctx context.Context, source string, destination string, message string, origin enums.CommsTextorigin, is_welcome bool) error { err := ensureInDB(ctx, destination) if err != nil { return fmt.Errorf("Failed to ensure text message destination is in the DB: %w", err) } - err = insertTextLog(ctx, message, destination, source, origin) + err = insertTextLog(ctx, message, destination, source, origin, is_welcome) if err != nil { return fmt.Errorf("Failed to insert text message in the DB: %w", err) } diff --git a/config/config.go b/config/config.go index 09b356f2..9b7461ab 100644 --- a/config/config.go +++ b/config/config.go @@ -27,9 +27,11 @@ var ( MapboxToken string PGDSN string PhoneNumberReport phonenumbers.PhoneNumber + PhoneNumberReportStr string TwilioAuthToken string TwilioAccountSID string TwilioMessagingServiceSID string + TwilioRCSSenderRMO string ) func IsProductionEnvironment() bool { @@ -127,13 +129,13 @@ func Parse() (err error) { if PGDSN == "" { return fmt.Errorf("You must specify a non-empty POSTGRES_DSN") } - rmo_phone_number := os.Getenv("RMO_PHONE_NUMBER") - if rmo_phone_number == "" { + PhoneNumberReportStr = os.Getenv("RMO_PHONE_NUMBER") + if PhoneNumberReportStr == "" { return fmt.Errorf("You must specify a non-empty RMO_PHONE_NUMBER") } - p, err := phonenumbers.Parse(rmo_phone_number, "US") + p, err := phonenumbers.Parse(PhoneNumberReportStr, "US") if err != nil { - return fmt.Errorf("Failed to parse '%s' as a valid phone number: %w", rmo_phone_number, err) + return fmt.Errorf("Failed to parse '%s' as a valid phone number: %w", PhoneNumberReportStr, err) } PhoneNumberReport = *p @@ -149,6 +151,10 @@ func Parse() (err error) { if TwilioMessagingServiceSID == "" { return fmt.Errorf("You must specify a non-empty TWILIO_MESSAGING_SERVICE_SID") } + TwilioRCSSenderRMO = os.Getenv("TWILIO_RCS_SENDER_RMO") + if TwilioRCSSenderRMO == "" { + return fmt.Errorf("You must specify a non-empty TWILIO_RCS_SENDER_RMO") + } return nil } diff --git a/db/sql/texts_by_senders.bob.go b/db/sql/texts_by_senders.bob.go new file mode 100644 index 00000000..3a8ce5bb --- /dev/null +++ b/db/sql/texts_by_senders.bob.go @@ -0,0 +1,135 @@ +// Code generated by BobGen psql v0.42.1. 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" + "time" + + enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "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 texts_by_senders.bob.sql +var formattedQueries_texts_by_senders string + +var textsBySendersSQL = formattedQueries_texts_by_senders[152:393] + +type TextsBySendersQuery = orm.ModQuery[*dialect.SelectQuery, textsBySenders, TextsBySendersRow, []TextsBySendersRow, textsBySendersTransformer] + +func TextsBySenders(Destination string, Source string) *TextsBySendersQuery { + var expressionTypArgs textsBySenders + + expressionTypArgs.Destination = psql.Arg(Destination) + expressionTypArgs.Source = psql.Arg(Source) + + return &TextsBySendersQuery{ + Query: orm.Query[textsBySenders, TextsBySendersRow, []TextsBySendersRow, textsBySendersTransformer]{ + ExecQuery: orm.ExecQuery[textsBySenders]{ + BaseQuery: bob.BaseQuery[textsBySenders]{ + Expression: expressionTypArgs, + Dialect: dialect.Dialect, + QueryType: bob.QueryTypeSelect, + }, + }, + Scanner: func(context.Context, []string) (func(*scan.Row) (any, error), func(any) (TextsBySendersRow, error)) { + return func(row *scan.Row) (any, error) { + var t TextsBySendersRow + row.ScheduleScanByIndex(0, &t.ID) + row.ScheduleScanByIndex(1, &t.Content) + row.ScheduleScanByIndex(2, &t.Created) + row.ScheduleScanByIndex(3, &t.Source) + row.ScheduleScanByIndex(4, &t.Destination) + row.ScheduleScanByIndex(5, &t.IsWelcome) + row.ScheduleScanByIndex(6, &t.Origin) + return &t, nil + }, func(v any) (TextsBySendersRow, error) { + return *(v.(*TextsBySendersRow)), nil + } + }, + }, + Mod: bob.ModFunc[*dialect.SelectQuery](func(q *dialect.SelectQuery) { + q.AppendSelect(expressionTypArgs.subExpr(12, 97)) + q.SetTable(expressionTypArgs.subExpr(108, 122)) + q.AppendWhere(expressionTypArgs.subExpr(135, 214)) + q.CombinedOrder.AppendOrder(expressionTypArgs.subExpr(230, 241)) + }), + } +} + +type TextsBySendersRow = struct { + ID int32 `db:"id"` + Content string `db:"content"` + Created time.Time `db:"created"` + Source string `db:"source"` + Destination string `db:"destination"` + IsWelcome bool `db:"is_welcome"` + Origin enums.CommsTextorigin `db:"origin"` +} + +type textsBySendersTransformer = bob.SliceTransformer[TextsBySendersRow, []TextsBySendersRow] + +type textsBySenders struct { + Destination bob.Expression + Source bob.Expression +} + +func (o textsBySenders) args() iter.Seq[orm.ArgWithPosition] { + return func(yield func(arg orm.ArgWithPosition) bool) { + if !yield(orm.ArgWithPosition{ + Name: "destination", + Start: 144, + Stop: 146, + Expression: o.Destination, + }) { + return + } + + if !yield(orm.ArgWithPosition{ + Name: "source", + Start: 165, + Stop: 167, + Expression: o.Source, + }) { + return + } + + if !yield(orm.ArgWithPosition{ + Name: "source", + Start: 191, + Stop: 193, + Expression: o.Source, + }) { + return + } + + if !yield(orm.ArgWithPosition{ + Name: "destination", + Start: 212, + Stop: 214, + Expression: o.Destination, + }) { + return + } + } +} + +func (o textsBySenders) raw(from, to int) string { + return textsBySendersSQL[from:to] +} + +func (o textsBySenders) subExpr(from, to int) bob.Expression { + return orm.ArgsToExpression(textsBySendersSQL, from, to, o.args()) +} + +func (o textsBySenders) WriteSQL(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { + return o.subExpr(0, len(textsBySendersSQL)).WriteSQL(ctx, w, d, start) +} diff --git a/db/sql/texts_by_senders.bob.sql b/db/sql/texts_by_senders.bob.sql new file mode 100644 index 00000000..fbe09f0c --- /dev/null +++ b/db/sql/texts_by_senders.bob.sql @@ -0,0 +1,20 @@ +-- Code generated by BobGen psql v0.42.1. DO NOT EDIT. +-- This file is meant to be re-generated in place and/or deleted at any time. + +-- TextsBySenders +SELECT + id, + content, + created, + source, + destination, + is_welcome, + origin +FROM + comms.text_log +WHERE + (source = $1 AND destination = $2) + OR + (source = $3 AND destination = $4) +ORDER BY + created ASC; diff --git a/db/sql/texts_by_senders.sql b/db/sql/texts_by_senders.sql new file mode 100644 index 00000000..80fabedf --- /dev/null +++ b/db/sql/texts_by_senders.sql @@ -0,0 +1,17 @@ +-- TextsBySenders +SELECT + id, + content, + created, + source, + destination, + is_welcome, + origin +FROM + comms.text_log +WHERE + (source = $1 AND destination = $2) + OR + (source = $2 AND destination = $1) +ORDER BY + created ASC; diff --git a/go.mod b/go.mod index 00c6eb34..fdc15bd7 100644 --- a/go.mod +++ b/go.mod @@ -29,23 +29,31 @@ require ( github.com/tidwall/geojson v1.4.5 github.com/twilio/twilio-go v1.29.1 github.com/uber/h3-go/v4 v4.4.0 - golang.org/x/crypto v0.42.0 + golang.org/x/crypto v0.47.0 ) require ( github.com/ajg/form v1.5.1 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beevik/etree v1.1.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/mock v1.6.0 // indirect + github.com/invopop/jsonschema v0.13.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.2 // indirect github.com/klauspost/cpuid/v2 v2.2.11 // indirect github.com/klauspost/crc32 v1.3.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/maruel/genai v0.0.0-20251221000642-77279d1194c1 // indirect + github.com/maruel/httpjson v0.5.0 // indirect + github.com/maruel/roundtrippers v0.5.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mfridman/interpolate v0.0.2 // indirect github.com/minio/crc64nvme v1.1.0 // indirect @@ -62,12 +70,14 @@ require ( github.com/tidwall/rtree v1.3.1 // indirect github.com/tidwall/sjson v1.2.4 // indirect github.com/tinylib/msgp v1.3.0 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + go.mau.fi/util v0.9.5 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 18176087..f17eabfd 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,14 @@ github.com/alexedwards/scs/v2 v2.9.0 h1:xa05mVpwTBm1iLeTMNFfAWpKUm4fXAW7CeAViqBV github.com/alexedwards/scs/v2 v2.9.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= github.com/alitto/pond/v2 v2.5.0 h1:vPzS5GnvSDRhWQidmj2djHllOmjFExVFbDGCw1jdqDw= github.com/alitto/pond/v2 v2.5.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -79,6 +85,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= @@ -94,6 +102,8 @@ github.com/jaswdr/faker/v2 v2.8.1 h1:2AcPgHDBXYQregFUH9LgVZKfFupc4SIquYhp29sf5wQ github.com/jaswdr/faker/v2 v2.8.1/go.mod h1:jZq+qzNQr8/P+5fHd9t3txe2GNPnthrTfohtnJ7B+68= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= @@ -115,8 +125,18 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/maruel/genai v0.0.0-20251221000642-77279d1194c1 h1:zBQI2oPYBnnho8AndgaD7ib8djNeTJZckwMOUGAqrn0= +github.com/maruel/genai v0.0.0-20251221000642-77279d1194c1/go.mod h1:5umBYxgRJHAjfc++Gto7w1Hnys5WHHHjwtkfiky5MSg= +github.com/maruel/httpjson v0.5.0 h1:fUkECNt2G2rSi9rzklMVcElsiucUj8LoKhKqaUvlaYA= +github.com/maruel/httpjson v0.5.0/go.mod h1:Rbue+VwOe1TC6doGXddW8EWg2fW4Je6RhCo7iPuNpTo= +github.com/maruel/roundtrippers v0.5.0 h1:0ot2VEWg2KbrHMh67/ysw5P9HQBhMdST4QZfR7QKFBo= +github.com/maruel/roundtrippers v0.5.0/go.mod h1:By9wgqtmfQEs7hQmz7m8N2jr2m8VDPXNIRxOtK/042U= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -245,10 +265,14 @@ github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfP github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM= github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4= github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= +go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= @@ -267,8 +291,12 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -281,12 +309,16 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -303,6 +335,10 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -316,6 +352,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= diff --git a/llm/client.go b/llm/client.go new file mode 100644 index 00000000..45ff2a24 --- /dev/null +++ b/llm/client.go @@ -0,0 +1,22 @@ +package llm + +import ( + "github.com/rs/zerolog/log" +) + +type Message struct { + Content string + IsFromCustomer bool +} + +func GenerateNextMessage(history []Message, current Message) (Message, error) { + // In general our history + for i, msg := range history { + log.Info().Int("i", i).Bool("is_customer", msg.IsFromCustomer).Msg("History") + } + + return Message{ + Content: "hey there. :)", + IsFromCustomer: false, + }, nil +} diff --git a/llm/log.go b/llm/log.go new file mode 100644 index 00000000..52c48989 --- /dev/null +++ b/llm/log.go @@ -0,0 +1,42 @@ +package llm + +import ( + "log" + "strings" + "time" + + "github.com/rs/zerolog" + "go.mau.fi/util/exzerolog" +) + +type Logger = zerolog.Logger + +func createLogger() *Logger { + l := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { + //w.Out = io.Writer(buf) + w.TimeFormat = time.Stamp + })).With().Timestamp().Logger() + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + exzerolog.SetupDefaults(&l) + return &l +} + +type ZerologWriter struct { + zerologger zerolog.Logger + level zerolog.Level +} + +func (w ZerologWriter) Write(p []byte) (n int, err error) { + msg := strings.TrimSuffix(string(p), "\n") + event := w.zerologger.WithLevel(w.level) + event.Msg(msg) + return len(p), nil +} + +func LoggerShim(l zerolog.Logger) *log.Logger { + writer := &ZerologWriter{ + zerologger: l, + level: zerolog.DebugLevel, + } + return log.New(writer, "", 0) +} diff --git a/llm/openai.go b/llm/openai.go new file mode 100644 index 00000000..d936bb0d --- /dev/null +++ b/llm/openai.go @@ -0,0 +1,175 @@ +package llm + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/maruel/genai" + "github.com/maruel/genai/adapters" + "github.com/maruel/genai/providers/openaichat" + "github.com/rs/zerolog/log" +) + +type openAIClient struct { + client *openaichat.Client + conversations map[string][]genai.Message + log *Logger +} + +var client *openAIClient + +type AIRequest struct { + Displayname string + Message string + Sender string + Timestamp time.Time +} + +func CreateOpenAIClient(ctx context.Context) error { + logger := createLogger() + + opts := genai.ProviderOptions{ + Model: genai.ModelCheap, + } + c, err := openaichat.New(ctx, &opts, nil) + if err != nil { + return fmt.Errorf("Failed to create genai client: %v", err) + } + client = &openAIClient{ + client: c, + conversations: make(map[string][]genai.Message), + log: logger, + } + return nil +} + +func (c *openAIClient) continueConversation(ctx context.Context, req AIRequest) error { + msgs, ok := c.conversations["roomid"] + if !ok { + msgs = genai.Messages{ + c.startConversation(ctx, req), + } + } else { + msgs = append(msgs, genai.NewTextMessage(fmt.Sprintf("(%s) user: %s\nbot: ", req.Timestamp.String(), req.Message))) + } + + c.log.Debug().Msg("Generating response...") + opts := genai.OptionsTools{ + Tools: []genai.ToolDef{ + { + Name: "followup_timer", + Description: "This should be used to indicate that the bot should follow up with the user in the future to check on task progress.", + Callback: func(ctx2 context.Context, input *FollowupTimerInput) (string, error) { + return c.followupSchedule(ctx2, req, input) + }, + }, { + Name: "switch_task", + Description: "Any time the user indicates they change tasks this must be called to update the record of what tasks are being done.", + Callback: func(ctx2 context.Context, input *SwitchTaskInput) (string, error) { + return c.switchTask(ctx2, req, input) + }, + }, + }, + } + + res, _, err := adapters.GenSyncWithToolCallLoop(ctx, c.client, msgs, &opts) + if err != nil { + return fmt.Errorf("Failed to continue conversation: %v", err) + } + + for _, m := range res { + msgs = append(msgs, m) + // Empty responses are tool call related. + if m.String() == "" { + } else { + //c.log.Info().Str("room", req.RoomID.String()).Msg(m.String()) + var toSay string = m.String() + toSay = strings.Replace(toSay, "bot: ", "", 1) + log.Info().Str("to say", toSay).Msg("Responding") + /*c.aiResponseChannel <- AIResponse{ + Message: toSay, + RoomID: req.RoomID, + }*/ + } + } + //c.conversations[req.RoomID.String()] = msgs + + return nil +} + +type FollowupTimerInput struct { + DelayInSeconds int64 `json:"delay_in_seconds"` +} + +func (c *openAIClient) followupFire(ctx context.Context, req AIRequest, duration time.Duration) { + if err := ctx.Err(); err != nil { + //c.log.Info().Str("room", req.RoomID.String()).Msg("Context canceled") + return + } + msgs, ok := c.conversations["roomid"] + if !ok { + //c.log.Warn().Str("room", req.RoomID.String()).Str("elapsed", duration.String()).Msg("No messages for room") + return + } + msgs = append(msgs, genai.NewTextMessage(fmt.Sprintf("<%s passed>", duration.String()))) + res, err := c.client.GenSync(ctx, msgs) + if err != nil { + //c.log.Error().Str("room", req.RoomID.String()).Err(err).Msg("Failed to continue after timer") + return + } + msgs = append(msgs, res.Message) + var toSay string = res.String() + toSay = strings.Replace(toSay, "bot: ", "", 1) + log.Info().Str("to say", toSay).Msg("To say") + /*c.aiResponseChannel <- AIResponse{ + Message: toSay, + RoomID: req.RoomID, + } + c.conversations[req.RoomID.String()] = msgs + */ +} + +func (c *openAIClient) followupSchedule(ctx context.Context, req AIRequest, input *FollowupTimerInput) (string, error) { + //c.log.Info().Str("room", req.RoomID.String()).Int64("delay", input.DelayInSeconds).Msg("Followup timer scheduled.") + duration, err := time.ParseDuration(fmt.Sprintf("%ds", input.DelayInSeconds)) + if err != nil { + return "", fmt.Errorf("Failed to parse %d as a valid duration: %v", input.DelayInSeconds, err) + } + /*c.aiResponseChannel <- AIResponse{ + Message: fmt.Sprintf("⌛ followup scheduled '%s'", duration.String()), + RoomID: req.RoomID, + }*/ + time.AfterFunc(duration, func() { + c.followupFire(ctx, req, duration) + }) + return fmt.Sprintf("Followup timer set for %s in the future", duration.String()), nil +} + +type SwitchTaskInput struct { + TaskName string `json:"task_name"` +} + +func (c *openAIClient) switchTask(ctx context.Context, req AIRequest, input *SwitchTaskInput) (string, error) { + //c.log.Info().Str("room", req.RoomID.String()).Str("task", input.TaskName).Msg("Task Switched") + /*c.aiResponseChannel <- AIResponse{ + Message: fmt.Sprintf("📋 notes task '%s'", input.TaskName), + RoomID: req.RoomID, + }*/ + + return fmt.Sprintf("Recorded a switch to task %s at %s", input.TaskName, time.Now().String()), nil +} + +func (c *openAIClient) startConversation(ctx context.Context, req AIRequest) genai.Message { + return genai.NewTextMessage(fmt.Sprintf( + `This is a text chat conversation between an employee and a chatbot helping to manage timecards. + The user's name is '%[1]s'. + Messages from the user will start with '(timestamp) %[1]s:'. + Messages from the bot will start with 'bot:'. + Sometimes the user won't say anything for a long time and the chatbot needs to follow-up with them. + When time passes, there will be a prompt like '<200s passed>'. + The bot should then prompt the user to provide a bit of information about what they've been working on during that time. + The bot should be interested to know what the user's goals are at a high level and should pay attention to any difficulties or frustrations the user experiences.\n\n + (%[2]s) user: %[3]s\nbot:`, req.Displayname, req.Timestamp.String(), req.Message)) +} diff --git a/main.go b/main.go index ac76e6af..ee3b1c8c 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/comms/text" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" + "github.com/Gleipnir-Technology/nidus-sync/llm" "github.com/Gleipnir-Technology/nidus-sync/public-report" nidussync "github.com/Gleipnir-Technology/nidus-sync/sync" "github.com/go-chi/chi/v5" @@ -52,6 +53,7 @@ func main() { log.Error().Err(err).Msg("Failed to store text source phone numbers") os.Exit(4) } + router_logger := log.With().Logger() r := chi.NewRouter() @@ -75,6 +77,11 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + err = llm.CreateOpenAIClient(ctx) + if err != nil { + log.Error().Err(err).Msg("Failed to start openAI client") + os.Exit(5) + } background.Start(ctx) server := &http.Server{ Addr: config.Bind, diff --git a/platform/text.go b/platform/text.go new file mode 100644 index 00000000..776fe315 --- /dev/null +++ b/platform/text.go @@ -0,0 +1,113 @@ +package platform + +import ( + "context" + "fmt" + "strings" + + "github.com/Gleipnir-Technology/nidus-sync/comms/text" + "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/db/sql" + "github.com/Gleipnir-Technology/nidus-sync/llm" + "github.com/rs/zerolog/log" +) + +// Translate from Twilio's representation of a RCS message sender to our concept of a phone number +// From: rcs:dev_report_mosquitoes_online_dosrvwxm_agent +// To: +16235525879 +func getDst(ctx context.Context, to string) (string, error) { + + if to == config.TwilioRCSSenderRMO { + return config.PhoneNumberReportStr, nil + } + /* + phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, to) + if err != nil { + return "", fmt.Errorf("Failed to search for dest phone %s: %w", to, err) + } + return phone.E164, nil + */ + return "", fmt.Errorf("Cannot match phone number to '%s'", to) +} + +func loadPreviousMessages(ctx context.Context, dst, src string) ([]llm.Message, error) { + messages, err := sql.TextsBySenders(dst, src).All(ctx, db.PGInstance.BobDB) + results := make([]llm.Message, 0) + if err != nil { + return results, fmt.Errorf("Failed to get message history for %s and %s: %w", dst, src, err) + } + log.Info().Int("count", len(messages)).Str("src", src).Str("dst", dst).Msg("Found previous messages") + for _, m := range messages { + is_from_customer := (m.Source == src) + results = append(results, llm.Message{ + IsFromCustomer: is_from_customer, + Content: m.Content, + }) + } + return results, nil +} + +func splitPhoneSource(s string) (string, string) { + parts := strings.Split(s, ":") + switch len(parts) { + case 0: + return "this isn't", "possible" + case 1: + return "", s + case 2: + return parts[0], parts[1] + default: + log.Warn().Str("s", s).Msg("Got an incomprehensible number of parts of a phone number") + return parts[0], parts[1] + } + +} + +func isSubscribed(ctx context.Context, src string) (bool, error) { + phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, src) + if err != nil { + return false, fmt.Errorf("Failed to determine if '%s' is subscribed: %w", src, err) + } + return phone.IsSubscribed, nil +} + +func HandleTextMessage(from string, to string, body string) { + ctx := context.Background() + type_, src := splitPhoneSource(from) + dst, err := getDst(ctx, to) + if err != nil { + log.Error().Err(err).Str("to", to).Msg("Failed to get dst") + return + } + subscribed, err := isSubscribed(ctx, from) + if err != nil { + log.Error().Err(err).Msg("Failed to handle message") + return + } + if !subscribed { + err = text.SendInitialReprompt(ctx, dst, src) + if err != nil { + log.Error().Err(err).Msg("Failed to resend initial prompt.") + } + return + } + previous_messages, err := loadPreviousMessages(ctx, dst, src) + if err != nil { + log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to get previous messages") + return + } + current := llm.Message{ + Content: body, + IsFromCustomer: true, + } + log.Info().Int("len", len(previous_messages)).Msg("passing") + next_message, err := llm.GenerateNextMessage(previous_messages, current) + if err != nil { + log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to generate next message") + return + } + text.SendTextFromLLM(next_message.Content) + log.Info().Str("from", from).Str("from-type", type_).Str("to", to).Str("src", src).Str("dst", dst).Str("body", body).Str("reply", next_message.Content).Msg("Handling text message") +} diff --git a/tools/texts_by_senders.sql b/tools/texts_by_senders.sql new file mode 100644 index 00000000..a60a8ebe --- /dev/null +++ b/tools/texts_by_senders.sql @@ -0,0 +1,17 @@ +-- TextsBySenders +SELECT + id, + content, + created, + source, + destination, + is_welcome, + origin +FROM + comms.text_log +WHERE + (source = :src AND destination = :dst) + OR + (source = :dst AND destination = :src) +ORDER BY + created ASC; From c0ecfe2e1808423fdc9de544cf1eb8c5318dbbcf Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 26 Jan 2026 20:41:21 +0000 Subject: [PATCH 0127/1453] Add more log info on login failure --- api/signin.go | 1 + 1 file changed, 1 insertion(+) diff --git a/api/signin.go b/api/signin.go index d182c897..bd319adc 100644 --- a/api/signin.go +++ b/api/signin.go @@ -37,6 +37,7 @@ func postSignin(w http.ResponseWriter, r *http.Request) { http.Error(w, "invalid-credentials", http.StatusUnauthorized) return } + log.Error().Err(err).Str("username", username).Msg("Login server error") http.Error(w, "signin-server-error", http.StatusInternalServerError) return } From d2d95a1f6bdc68cf1ab745b431109476b10d08d3 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 26 Jan 2026 20:51:07 +0000 Subject: [PATCH 0128/1453] Update vendor hash for recent vendor changes --- default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default.nix b/default.nix index eedd831e..3d6ed30c 100644 --- a/default.nix +++ b/default.nix @@ -9,5 +9,5 @@ pkgs.buildGoModule rec { subPackages = []; version = "0.0.11"; # Needs to be updated after every modification of go.mod/go.sum - vendorHash = "sha256-Z1kpPmQytPAaKXQDlD8ILNyPchZornVTO+enOcS2cEQ="; + vendorHash = "sha256-HJg9c+ZUWxQgu5FBgcc0BMUWkkK6YyFVVBrFF0oUz/8="; } From 1cd4a31404f873e20c640483c58039a0162dc079 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 26 Jan 2026 20:51:26 +0000 Subject: [PATCH 0129/1453] Start saving subscribed status --- platform/text.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/platform/text.go b/platform/text.go index 776fe315..2157dae2 100644 --- a/platform/text.go +++ b/platform/text.go @@ -11,6 +11,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/sql" "github.com/Gleipnir-Technology/nidus-sync/llm" + "github.com/aarondl/opt/omit" "github.com/rs/zerolog/log" ) @@ -73,6 +74,17 @@ func isSubscribed(ctx context.Context, src string) (bool, error) { return phone.IsSubscribed, nil } +func setSubscribed(ctx context.Context, src string, is_subscribed bool) error { + phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, src) + if err != nil { + return fmt.Errorf("Failed to determine if '%s' is subscribed: %w", src, err) + } + phone.Update(ctx, db.PGInstance.BobDB, &models.CommsPhoneSetter{ + IsSubscribed: omit.From(is_subscribed), + }) + return nil +} + func HandleTextMessage(from string, to string, body string) { ctx := context.Background() type_, src := splitPhoneSource(from) @@ -81,12 +93,17 @@ func HandleTextMessage(from string, to string, body string) { log.Error().Err(err).Str("to", to).Msg("Failed to get dst") return } - subscribed, err := isSubscribed(ctx, from) + subscribed, err := isSubscribed(ctx, src) if err != nil { log.Error().Err(err).Msg("Failed to handle message") return } if !subscribed { + body_l := strings.TrimSpace(strings.ToLower(body)) + if body_l == "stop" { + setSubscribed(ctx, src, false) + return + } err = text.SendInitialReprompt(ctx, dst, src) if err != nil { log.Error().Err(err).Msg("Failed to resend initial prompt.") From e8e840ec44e47e0d4c9c539730b8c11c4ece6dab Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 26 Jan 2026 21:11:31 +0000 Subject: [PATCH 0130/1453] Make username unique, make is_subscribed nullable --- comms/text/db.go | 13 +----- comms/text/report-subscription.go | 46 ++++++++++--------- db/dberrors/user_.bob.go | 9 ++++ db/dbinfo/comms.phone.bob.go | 4 +- db/dbinfo/user_.bob.go | 37 +++++++++++++-- db/factory/bobfactory_main.bob.go | 2 +- db/factory/comms.phone.bob.go | 42 ++++++++++++----- .../00042_text_subscribe_nullable.sql | 2 + db/migrations/00043_username_unique.sql | 2 + db/models/comms.phone.bob.go | 26 ++++++----- platform/text.go | 40 +++++++++++----- 11 files changed, 149 insertions(+), 74 deletions(-) create mode 100644 db/migrations/00042_text_subscribe_nullable.sql create mode 100644 db/migrations/00043_username_unique.sql diff --git a/comms/text/db.go b/comms/text/db.go index 9801e1ca..791dac38 100644 --- a/comms/text/db.go +++ b/comms/text/db.go @@ -15,6 +15,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" "github.com/nyaruka/phonenumbers" "github.com/rs/zerolog/log" "github.com/stephenafamo/bob/types/pgtypes" @@ -55,7 +56,7 @@ func ensureInDB(ctx context.Context, destination string) (err error) { if err.Error() == "sql: no rows in result set" { _, err = models.CommsPhones.Insert(&models.CommsPhoneSetter{ E164: omit.From(destination), - IsSubscribed: omit.From(false), + IsSubscribed: omitnull.FromPtr[bool](nil), }).One(ctx, db.PGInstance.BobDB) if err != nil { return fmt.Errorf("Failed to insert new phone contact: %w", err) @@ -81,16 +82,6 @@ func insertTextLog(ctx context.Context, content string, destination string, sour return err } -func isSubscribed(ctx context.Context, destination string) (bool, error) { - phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, destination) - if err != nil { - if err.Error() == "sql: no rows in result set" { - return false, nil - } - return false, fmt.Errorf("Failed to find phone number %s: %w", destination, err) - } - return phone.IsSubscribed, nil -} func generatePublicId(t enums.CommsMessagetypeemail, m map[string]string) string { if m == nil || len(m) == 0 { diff --git a/comms/text/report-subscription.go b/comms/text/report-subscription.go index c43f6b89..12328338 100644 --- a/comms/text/report-subscription.go +++ b/comms/text/report-subscription.go @@ -4,7 +4,8 @@ import ( "context" "fmt" - "github.com/Gleipnir-Technology/nidus-sync/db/enums" + //"github.com/Gleipnir-Technology/nidus-sync/db/enums" + //"github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/nyaruka/phonenumbers" //"github.com/rs/zerolog/log" ) @@ -43,29 +44,32 @@ func (j jobReportSubscription) source() string { } func sendReportSubscription(ctx context.Context, job Job) error { - j, ok := job.(jobReportSubscription) - if !ok { - return fmt.Errorf("job is not for report subscription confirmation") - } + /* + j, ok := job.(jobReportSubscription) + if !ok { + return fmt.Errorf("job is not for report subscription confirmation") + } - sub, err := isSubscribed(ctx, job.destination()) - if err != nil { - return fmt.Errorf("Failed to check if subscribed: %w", err) - } - if !sub { - err = sendText(ctx, j.source(), j.destination(), j.content(), enums.CommsTextoriginWebsiteAction, false) + sub, err := isSubscribed(ctx, job.destination()) if err != nil { - return fmt.Errorf("Failed to send report subscription confirmation: %w", err) + return fmt.Errorf("Failed to check if subscribed: %w", err) } - } else { - err = delayMessage(ctx, j.source(), j.destination(), j.content(), enums.CommsTextjobtypeReportConfirmation) - if err != nil { - return fmt.Errorf("Failed to delay report subscription message: %w", err) + if !sub { + err = sendText(ctx, j.source(), j.destination(), j.content(), enums.CommsTextoriginWebsiteAction, false) + if err != nil { + return fmt.Errorf("Failed to send report subscription confirmation: %w", err) + } + } else { + err = delayMessage(ctx, j.source(), j.destination(), j.content(), enums.CommsTextjobtypeReportConfirmation) + if err != nil { + return fmt.Errorf("Failed to delay report subscription message: %w", err) + } + err := ensureInitialText(ctx, j.source(), j.destination()) + if err != nil { + return fmt.Errorf("Failed to ensure initial text has been sent: %w", err) + } } - err := ensureInitialText(ctx, j.source(), j.destination()) - if err != nil { - return fmt.Errorf("Failed to ensure initial text has been sent: %w", err) - } - } + return nil + */ return nil } diff --git a/db/dberrors/user_.bob.go b/db/dberrors/user_.bob.go index d000ac1f..7f08ae62 100644 --- a/db/dberrors/user_.bob.go +++ b/db/dberrors/user_.bob.go @@ -10,8 +10,17 @@ var UserErrors = &userErrors{ columns: []string{"id"}, s: "user__pkey", }, + + ErrUniqueUserUsernameUnique: &UniqueConstraintError{ + schema: "", + table: "user_", + columns: []string{"username"}, + s: "user_username_unique", + }, } type userErrors struct { ErrUniqueUser_Pkey *UniqueConstraintError + + ErrUniqueUserUsernameUnique *UniqueConstraintError } diff --git a/db/dbinfo/comms.phone.bob.go b/db/dbinfo/comms.phone.bob.go index 2919387c..be6039b2 100644 --- a/db/dbinfo/comms.phone.bob.go +++ b/db/dbinfo/comms.phone.bob.go @@ -27,9 +27,9 @@ var CommsPhones = Table[ IsSubscribed: column{ Name: "is_subscribed", DBType: "boolean", - Default: "", + Default: "NULL", Comment: "", - Nullable: false, + Nullable: true, Generated: false, AutoIncr: false, }, diff --git a/db/dbinfo/user_.bob.go b/db/dbinfo/user_.bob.go index b72cfaf4..1c1f25ef 100644 --- a/db/dbinfo/user_.bob.go +++ b/db/dbinfo/user_.bob.go @@ -142,6 +142,23 @@ var Users = Table[ Where: "", Include: []string{}, }, + UserUsernameUnique: index{ + Type: "btree", + Name: "user_username_unique", + Columns: []indexColumn{ + { + Name: "username", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, }, PrimaryKey: &constraint{ Name: "user__pkey", @@ -159,6 +176,13 @@ var Users = Table[ ForeignColumns: []string{"id"}, }, }, + Uniques: userUniques{ + UserUsernameUnique: constraint{ + Name: "user_username_unique", + Columns: []string{"username"}, + Comment: "", + }, + }, Comment: "", } @@ -185,12 +209,13 @@ func (c userColumns) AsSlice() []column { } type userIndexes struct { - UserPkey index + UserPkey index + UserUsernameUnique index } func (i userIndexes) AsSlice() []index { return []index{ - i.UserPkey, + i.UserPkey, i.UserUsernameUnique, } } @@ -204,10 +229,14 @@ func (f userForeignKeys) AsSlice() []foreignKey { } } -type userUniques struct{} +type userUniques struct { + UserUsernameUnique constraint +} func (u userUniques) AsSlice() []constraint { - return []constraint{} + return []constraint{ + u.UserUsernameUnique, + } } type userChecks struct{} diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index 6e8d4391..45b2b33f 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -293,7 +293,7 @@ func (f *Factory) FromExistingCommsPhone(m *models.CommsPhone) *CommsPhoneTempla o := &CommsPhoneTemplate{f: f, alreadyPersisted: true} o.E164 = func() string { return m.E164 } - o.IsSubscribed = func() bool { return m.IsSubscribed } + o.IsSubscribed = func() null.Val[bool] { return m.IsSubscribed } ctx := context.Background() if len(m.R.DestinationTextJobs) > 0 { diff --git a/db/factory/comms.phone.bob.go b/db/factory/comms.phone.bob.go index cefbb124..abcd554d 100644 --- a/db/factory/comms.phone.bob.go +++ b/db/factory/comms.phone.bob.go @@ -8,7 +8,9 @@ import ( "testing" models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" "github.com/stephenafamo/bob" ) @@ -35,7 +37,7 @@ func (mods CommsPhoneModSlice) Apply(ctx context.Context, n *CommsPhoneTemplate) // all columns are optional and should be set by mods type CommsPhoneTemplate struct { E164 func() string - IsSubscribed func() bool + IsSubscribed func() null.Val[bool] r commsPhoneR f *Factory @@ -123,7 +125,7 @@ func (o CommsPhoneTemplate) BuildSetter() *models.CommsPhoneSetter { } if o.IsSubscribed != nil { val := o.IsSubscribed() - m.IsSubscribed = omit.From(val) + m.IsSubscribed = omitnull.FromNull(val) } return m @@ -177,10 +179,6 @@ func ensureCreatableCommsPhone(m *models.CommsPhoneSetter) { val := random_string(nil) m.E164 = omit.From(val) } - if !(m.IsSubscribed.IsValue()) { - val := random_bool(nil) - m.IsSubscribed = omit.From(val) - } } // insertOptRels creates and inserts any optional the relationships on *models.CommsPhone @@ -378,14 +376,14 @@ func (m commsPhoneMods) RandomE164(f *faker.Faker) CommsPhoneMod { } // Set the model columns to this value -func (m commsPhoneMods) IsSubscribed(val bool) CommsPhoneMod { +func (m commsPhoneMods) IsSubscribed(val null.Val[bool]) CommsPhoneMod { return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { - o.IsSubscribed = func() bool { return val } + o.IsSubscribed = func() null.Val[bool] { return val } }) } // Set the Column from the function -func (m commsPhoneMods) IsSubscribedFunc(f func() bool) CommsPhoneMod { +func (m commsPhoneMods) IsSubscribedFunc(f func() null.Val[bool]) CommsPhoneMod { return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { o.IsSubscribed = f }) @@ -400,10 +398,32 @@ func (m commsPhoneMods) UnsetIsSubscribed() CommsPhoneMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used +// The generated value is sometimes null func (m commsPhoneMods) RandomIsSubscribed(f *faker.Faker) CommsPhoneMod { return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { - o.IsSubscribed = func() bool { - return random_bool(f) + o.IsSubscribed = func() null.Val[bool] { + if f == nil { + f = &defaultFaker + } + + val := random_bool(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m commsPhoneMods) RandomIsSubscribedNotNull(f *faker.Faker) CommsPhoneMod { + return CommsPhoneModFunc(func(_ context.Context, o *CommsPhoneTemplate) { + o.IsSubscribed = func() null.Val[bool] { + if f == nil { + f = &defaultFaker + } + + val := random_bool(f) + return null.From(val) } }) } diff --git a/db/migrations/00042_text_subscribe_nullable.sql b/db/migrations/00042_text_subscribe_nullable.sql new file mode 100644 index 00000000..778c7577 --- /dev/null +++ b/db/migrations/00042_text_subscribe_nullable.sql @@ -0,0 +1,2 @@ +-- +goose Up +ALTER TABLE comms.phone ALTER COLUMN is_subscribed DROP NOT NULL; diff --git a/db/migrations/00043_username_unique.sql b/db/migrations/00043_username_unique.sql new file mode 100644 index 00000000..48225744 --- /dev/null +++ b/db/migrations/00043_username_unique.sql @@ -0,0 +1,2 @@ +-- +goose Up +ALTER TABLE user_ ADD CONSTRAINT user_username_unique UNIQUE (username); diff --git a/db/models/comms.phone.bob.go b/db/models/comms.phone.bob.go index 6af2d6e7..56a665c0 100644 --- a/db/models/comms.phone.bob.go +++ b/db/models/comms.phone.bob.go @@ -8,7 +8,9 @@ import ( "fmt" "io" + "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" "github.com/stephenafamo/bob" "github.com/stephenafamo/bob/dialect/psql" "github.com/stephenafamo/bob/dialect/psql/dialect" @@ -23,8 +25,8 @@ import ( // CommsPhone is an object representing the database table. type CommsPhone struct { - E164 string `db:"e164,pk" ` - IsSubscribed bool `db:"is_subscribed" ` + E164 string `db:"e164,pk" ` + IsSubscribed null.Val[bool] `db:"is_subscribed" ` R commsPhoneR `db:"-" ` @@ -78,8 +80,8 @@ func (commsPhoneColumns) AliasedAs(alias string) commsPhoneColumns { // All values are optional, and do not have to be set // Generated columns are not included type CommsPhoneSetter struct { - E164 omit.Val[string] `db:"e164,pk" ` - IsSubscribed omit.Val[bool] `db:"is_subscribed" ` + E164 omit.Val[string] `db:"e164,pk" ` + IsSubscribed omitnull.Val[bool] `db:"is_subscribed" ` } func (s CommsPhoneSetter) SetColumns() []string { @@ -87,7 +89,7 @@ func (s CommsPhoneSetter) SetColumns() []string { if s.E164.IsValue() { vals = append(vals, "e164") } - if s.IsSubscribed.IsValue() { + if !s.IsSubscribed.IsUnset() { vals = append(vals, "is_subscribed") } return vals @@ -97,8 +99,8 @@ func (s CommsPhoneSetter) Overwrite(t *CommsPhone) { if s.E164.IsValue() { t.E164 = s.E164.MustGet() } - if s.IsSubscribed.IsValue() { - t.IsSubscribed = s.IsSubscribed.MustGet() + if !s.IsSubscribed.IsUnset() { + t.IsSubscribed = s.IsSubscribed.MustGetNull() } } @@ -115,8 +117,8 @@ func (s *CommsPhoneSetter) Apply(q *dialect.InsertQuery) { vals[0] = psql.Raw("DEFAULT") } - if s.IsSubscribed.IsValue() { - vals[1] = psql.Arg(s.IsSubscribed.MustGet()) + if !s.IsSubscribed.IsUnset() { + vals[1] = psql.Arg(s.IsSubscribed.MustGetNull()) } else { vals[1] = psql.Raw("DEFAULT") } @@ -139,7 +141,7 @@ func (s CommsPhoneSetter) Expressions(prefix ...string) []bob.Expression { }}) } - if s.IsSubscribed.IsValue() { + if !s.IsSubscribed.IsUnset() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "is_subscribed")...), psql.Arg(s.IsSubscribed), @@ -650,7 +652,7 @@ func (commsPhone0 *CommsPhone) AttachSourceTextLogs(ctx context.Context, exec bo type commsPhoneWhere[Q psql.Filterable] struct { E164 psql.WhereMod[Q, string] - IsSubscribed psql.WhereMod[Q, bool] + IsSubscribed psql.WhereNullMod[Q, bool] } func (commsPhoneWhere[Q]) AliasedAs(alias string) commsPhoneWhere[Q] { @@ -660,7 +662,7 @@ func (commsPhoneWhere[Q]) AliasedAs(alias string) commsPhoneWhere[Q] { func buildCommsPhoneWhere[Q psql.Filterable](cols commsPhoneColumns) commsPhoneWhere[Q] { return commsPhoneWhere[Q]{ E164: psql.Where[Q, string](cols.E164), - IsSubscribed: psql.Where[Q, bool](cols.IsSubscribed), + IsSubscribed: psql.WhereNull[Q, bool](cols.IsSubscribed), } } diff --git a/platform/text.go b/platform/text.go index 2157dae2..e857a702 100644 --- a/platform/text.go +++ b/platform/text.go @@ -11,7 +11,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/sql" "github.com/Gleipnir-Technology/nidus-sync/llm" - "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" ) @@ -66,12 +66,16 @@ func splitPhoneSource(s string) (string, string) { } -func isSubscribed(ctx context.Context, src string) (bool, error) { +func isSubscribed(ctx context.Context, src string) (*bool, error) { phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, src) if err != nil { - return false, fmt.Errorf("Failed to determine if '%s' is subscribed: %w", src, err) + return nil, fmt.Errorf("Failed to determine if '%s' is subscribed: %w", src, err) } - return phone.IsSubscribed, nil + if phone.IsSubscribed.IsNull() { + return nil, nil + } + result := phone.IsSubscribed.MustGet() + return &result, nil } func setSubscribed(ctx context.Context, src string, is_subscribed bool) error { @@ -80,11 +84,16 @@ func setSubscribed(ctx context.Context, src string, is_subscribed bool) error { return fmt.Errorf("Failed to determine if '%s' is subscribed: %w", src, err) } phone.Update(ctx, db.PGInstance.BobDB, &models.CommsPhoneSetter{ - IsSubscribed: omit.From(is_subscribed), + IsSubscribed: omitnull.From(is_subscribed), }) + log.Info().Str("src", src).Bool("is_subscribed", is_subscribed).Msg("Set number subscribed") return nil } +func handleWaitingTextJobs(ctx context.Context, src string) { + log.Info().Str("src", src).Msg("Pretend handle waiting jobs") + +} func HandleTextMessage(from string, to string, body string) { ctx := context.Background() type_, src := splitPhoneSource(from) @@ -98,18 +107,25 @@ func HandleTextMessage(from string, to string, body string) { log.Error().Err(err).Msg("Failed to handle message") return } - if !subscribed { + // We don't know if they're subscribed or not. + if subscribed == nil { body_l := strings.TrimSpace(strings.ToLower(body)) - if body_l == "stop" { + switch body_l { + case "stop": setSubscribed(ctx, src, false) - return - } - err = text.SendInitialReprompt(ctx, dst, src) - if err != nil { - log.Error().Err(err).Msg("Failed to resend initial prompt.") + case "yes": + setSubscribed(ctx, src, true) + handleWaitingTextJobs(ctx, src) + default: + err = text.SendInitialReprompt(ctx, dst, src) + if err != nil { + log.Error().Err(err).Msg("Failed to resend initial prompt.") + } } return } + if !(*subscribed) { + } previous_messages, err := loadPreviousMessages(ctx, dst, src) if err != nil { log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to get previous messages") From 407b4786373ed5083898a1fe4f543e53adf69845 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 26 Jan 2026 21:21:21 +0000 Subject: [PATCH 0131/1453] Fold more text logic into the platform Because it is better at managing the database, the comms/text package will just be for integration. --- comms/text/db.go | 63 ---------------------------- comms/text/initial.go | 37 ---------------- comms/text/text.go | 11 +---- main.go | 4 +- platform/text.go | 98 +++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 98 insertions(+), 115 deletions(-) delete mode 100644 comms/text/initial.go diff --git a/comms/text/db.go b/comms/text/db.go index 791dac38..cd6c3d7d 100644 --- a/comms/text/db.go +++ b/comms/text/db.go @@ -1,31 +1,17 @@ package text import ( - "context" "crypto/sha256" "database/sql" "encoding/hex" "fmt" "sort" "strings" - "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/aarondl/opt/omit" - "github.com/aarondl/opt/omitnull" - "github.com/nyaruka/phonenumbers" - "github.com/rs/zerolog/log" "github.com/stephenafamo/bob/types/pgtypes" ) -func StoreSources() error { - ctx := context.TODO() - src := phonenumbers.Format(&config.PhoneNumberReport, phonenumbers.E164) - return ensureInDB(ctx, src) -} func convertToPGData(data map[string]string) pgtypes.HStore { result := pgtypes.HStore{} for k, v := range data { @@ -34,55 +20,6 @@ func convertToPGData(data map[string]string) pgtypes.HStore { return result } -func delayMessage(ctx context.Context, source string, destination string, content string, type_ enums.CommsTextjobtype) error { - job, err := models.CommsTextJobs.Insert(&models.CommsTextJobSetter{ - Content: omit.From(content), - Created: omit.From(time.Now()), - Destination: omit.From(destination), - //ID: - Type: omit.From(type_), - }).One(ctx, db.PGInstance.BobDB) - if err != nil { - return fmt.Errorf("Failed to add delayed text job: %w", err) - } - log.Info().Int32("id", job.ID).Msg("Created delayed text job") - return nil -} - -func ensureInDB(ctx context.Context, destination string) (err error) { - _, err = models.FindCommsPhone(ctx, db.PGInstance.BobDB, destination) - if err != nil { - // doesn't exist - if err.Error() == "sql: no rows in result set" { - _, err = models.CommsPhones.Insert(&models.CommsPhoneSetter{ - E164: omit.From(destination), - IsSubscribed: omitnull.FromPtr[bool](nil), - }).One(ctx, db.PGInstance.BobDB) - if err != nil { - return fmt.Errorf("Failed to insert new phone contact: %w", err) - } - log.Info().Str("phone", destination).Msg("Added text to the comms database") - return nil - } - return fmt.Errorf("Unexpected error searching for phone contact: %w", err) - } - return nil -} - -func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin, is_welcome bool) (err error) { - _, err = models.CommsTextLogs.Insert(&models.CommsTextLogSetter{ - //ID: - Content: omit.From(content), - Created: omit.From(time.Now()), - Destination: omit.From(destination), - IsWelcome: omit.From(is_welcome), - Origin: omit.From(origin), - Source: omit.From(source), - }).One(ctx, db.PGInstance.BobDB) - - return err -} - func generatePublicId(t enums.CommsMessagetypeemail, m map[string]string) string { if m == nil || len(m) == 0 { // Return hash of empty string for empty maps diff --git a/comms/text/initial.go b/comms/text/initial.go deleted file mode 100644 index 0c25a62c..00000000 --- a/comms/text/initial.go +++ /dev/null @@ -1,37 +0,0 @@ -package text - -import ( - "context" - "fmt" - - "github.com/Gleipnir-Technology/nidus-sync/db" - "github.com/Gleipnir-Technology/nidus-sync/db/enums" - "github.com/Gleipnir-Technology/nidus-sync/db/models" -) - -func ensureInitialText(ctx context.Context, src string, dst string) error { - // - origin := enums.CommsTextoriginWebsiteAction - rows, err := models.CommsTextLogs.Query( - models.SelectWhere.CommsTextLogs.Destination.EQ(dst), - models.SelectWhere.CommsTextLogs.IsWelcome.EQ(true), - ).All(ctx, db.PGInstance.BobDB) - if err != nil { - return fmt.Errorf("Failed to query text logs: %w", err) - } - if len(rows) > 0 { - return nil - } - content := "Welcome to Report Mosquitoes Online. We received your request and want to confirm text updates. Reply YES to continue. Reply STOP at any time to unsubscribe" - err = sendText(ctx, src, dst, content, origin, true) - if err != nil { - return fmt.Errorf("Failed to send initial confirmation: %w", err) - } - return nil -} - -func SendInitialReprompt(ctx context.Context, src string, dst string) error { - content := "I have to start with either 'YES' or 'STOP' first, Which do you want?" - err := sendText(ctx, src, dst, content, enums.CommsTextoriginLLM, false) - return err -} diff --git a/comms/text/text.go b/comms/text/text.go index 8384405f..34d2aa7a 100644 --- a/comms/text/text.go +++ b/comms/text/text.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/nyaruka/phonenumbers" "github.com/rs/zerolog/log" "github.com/twilio/twilio-go" @@ -19,15 +18,7 @@ func ParsePhoneNumber(input string) (*E164, error) { return phonenumbers.Parse(input, "US") } -func sendText(ctx context.Context, source string, destination string, message string, origin enums.CommsTextorigin, is_welcome bool) error { - err := ensureInDB(ctx, destination) - if err != nil { - return fmt.Errorf("Failed to ensure text message destination is in the DB: %w", err) - } - err = insertTextLog(ctx, message, destination, source, origin, is_welcome) - if err != nil { - return fmt.Errorf("Failed to insert text message in the DB: %w", err) - } +func SendText(ctx context.Context, source string, destination string, message string) error { client := twilio.NewRestClient() params := &twilioApi.CreateMessageParams{} diff --git a/main.go b/main.go index ee3b1c8c..012f9372 100644 --- a/main.go +++ b/main.go @@ -13,10 +13,10 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/auth" "github.com/Gleipnir-Technology/nidus-sync/background" "github.com/Gleipnir-Technology/nidus-sync/comms/email" - "github.com/Gleipnir-Technology/nidus-sync/comms/text" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/llm" + "github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/public-report" nidussync "github.com/Gleipnir-Technology/nidus-sync/sync" "github.com/go-chi/chi/v5" @@ -48,7 +48,7 @@ func main() { os.Exit(3) } - err = text.StoreSources() + err = platform.TextStoreSources() if err != nil { log.Error().Err(err).Msg("Failed to store text source phone numbers") os.Exit(4) diff --git a/platform/text.go b/platform/text.go index e857a702..f77a6e77 100644 --- a/platform/text.go +++ b/platform/text.go @@ -4,17 +4,97 @@ import ( "context" "fmt" "strings" + "time" "github.com/Gleipnir-Technology/nidus-sync/comms/text" "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/db/sql" "github.com/Gleipnir-Technology/nidus-sync/llm" + "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" + "github.com/nyaruka/phonenumbers" "github.com/rs/zerolog/log" ) +func TextStoreSources() error { + ctx := context.TODO() + src := phonenumbers.Format(&config.PhoneNumberReport, phonenumbers.E164) + return ensureInDB(ctx, src) +} + +func delayMessage(ctx context.Context, source string, destination string, content string, type_ enums.CommsTextjobtype) error { + job, err := models.CommsTextJobs.Insert(&models.CommsTextJobSetter{ + Content: omit.From(content), + Created: omit.From(time.Now()), + Destination: omit.From(destination), + //ID: + Type: omit.From(type_), + }).One(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to add delayed text job: %w", err) + } + log.Info().Int32("id", job.ID).Msg("Created delayed text job") + return nil +} + +func ensureInitialText(ctx context.Context, src string, dst string) error { + // + origin := enums.CommsTextoriginWebsiteAction + rows, err := models.CommsTextLogs.Query( + models.SelectWhere.CommsTextLogs.Destination.EQ(dst), + models.SelectWhere.CommsTextLogs.IsWelcome.EQ(true), + ).All(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to query text logs: %w", err) + } + if len(rows) > 0 { + return nil + } + content := "Welcome to Report Mosquitoes Online. We received your request and want to confirm text updates. Reply YES to continue. Reply STOP at any time to unsubscribe" + err = sendText(ctx, src, dst, content, origin, true) + if err != nil { + return fmt.Errorf("Failed to send initial confirmation: %w", err) + } + return nil +} + +func ensureInDB(ctx context.Context, destination string) (err error) { + _, err = models.FindCommsPhone(ctx, db.PGInstance.BobDB, destination) + if err != nil { + // doesn't exist + if err.Error() == "sql: no rows in result set" { + _, err = models.CommsPhones.Insert(&models.CommsPhoneSetter{ + E164: omit.From(destination), + IsSubscribed: omitnull.FromPtr[bool](nil), + }).One(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to insert new phone contact: %w", err) + } + log.Info().Str("phone", destination).Msg("Added text to the comms database") + return nil + } + return fmt.Errorf("Unexpected error searching for phone contact: %w", err) + } + return nil +} + +func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin, is_welcome bool) (err error) { + _, err = models.CommsTextLogs.Insert(&models.CommsTextLogSetter{ + //ID: + Content: omit.From(content), + Created: omit.From(time.Now()), + Destination: omit.From(destination), + IsWelcome: omit.From(is_welcome), + Origin: omit.From(origin), + Source: omit.From(source), + }).One(ctx, db.PGInstance.BobDB) + + return err +} + // Translate from Twilio's representation of a RCS message sender to our concept of a phone number // From: rcs:dev_report_mosquitoes_online_dosrvwxm_agent // To: +16235525879 @@ -50,6 +130,19 @@ func loadPreviousMessages(ctx context.Context, dst, src string) ([]llm.Message, return results, nil } +func sendText(ctx context.Context, source string, destination string, message string, origin enums.CommsTextorigin, is_welcome bool) error { + err := ensureInDB(ctx, destination) + if err != nil { + return fmt.Errorf("Failed to ensure text message destination is in the DB: %w", err) + } + err = insertTextLog(ctx, message, destination, source, origin, is_welcome) + if err != nil { + return fmt.Errorf("Failed to insert text message in the DB: %w", err) + } + err = text.SendText(ctx, source, destination, message) + return nil +} + func splitPhoneSource(s string) (string, string) { parts := strings.Split(s, ":") switch len(parts) { @@ -117,15 +210,14 @@ func HandleTextMessage(from string, to string, body string) { setSubscribed(ctx, src, true) handleWaitingTextJobs(ctx, src) default: - err = text.SendInitialReprompt(ctx, dst, src) + content := "I have to start with either 'YES' or 'STOP' first, Which do you want?" + err := text.SendText(ctx, src, dst, content) if err != nil { log.Error().Err(err).Msg("Failed to resend initial prompt.") } } return } - if !(*subscribed) { - } previous_messages, err := loadPreviousMessages(ctx, dst, src) if err != nil { log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to get previous messages") From b8e7b9b7fd0ad1e7398663b279545b7a573db511 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 27 Jan 2026 14:29:55 +0000 Subject: [PATCH 0132/1453] Working LLM responses and Twilio status tracking The responses aren't good, but they do exist. --- api/twilio.go | 1 + comms/text/text.go | 17 +- db/dberrors/comms.text_log.bob.go | 9 + db/dbinfo/comms.text_log.bob.go | 73 +++++- db/enums/enums.bob.go | 8 +- db/factory/bobfactory_main.bob.go | 2 + db/factory/comms.text_log.bob.go | 122 +++++++++- .../00044_comms_text_origin_user.sql | 6 + db/migrations/00045_comms_text_sid.sql | 8 + db/models/comms.text_log.bob.go | 148 ++++++++---- llm/client.go | 18 +- llm/openai.go | 162 ++++---------- platform/text.go | 210 +++++++++++------- 13 files changed, 497 insertions(+), 287 deletions(-) create mode 100644 db/migrations/00044_comms_text_origin_user.sql create mode 100644 db/migrations/00045_comms_text_sid.sql diff --git a/api/twilio.go b/api/twilio.go index 9a252e80..91ee54ba 100644 --- a/api/twilio.go +++ b/api/twilio.go @@ -18,6 +18,7 @@ func twilioStatusPost(w http.ResponseWriter, r *http.Request) { message_sid := r.PostFormValue("MessageSid") message_status := r.PostFormValue("MessageStatus") log.Info().Str("sid", message_sid).Str("status", message_status).Msg("Updated message status") + platform.UpdateMessageStatus(message_sid, message_status) fmt.Fprintf(w, "") } func twilioTextPost(w http.ResponseWriter, r *http.Request) { diff --git a/comms/text/text.go b/comms/text/text.go index 34d2aa7a..c98e33fc 100644 --- a/comms/text/text.go +++ b/comms/text/text.go @@ -18,7 +18,7 @@ func ParsePhoneNumber(input string) (*E164, error) { return phonenumbers.Parse(input, "US") } -func SendText(ctx context.Context, source string, destination string, message string) error { +func SendText(ctx context.Context, source string, destination string, message string) (string, error) { client := twilio.NewRestClient() params := &twilioApi.CreateMessageParams{} @@ -29,15 +29,14 @@ func SendText(ctx context.Context, source string, destination string, message st resp, err := client.Api.CreateMessage(params) if err != nil { - return fmt.Errorf("Failed to create message to %s: %w", destination, err) - } else { - if resp.Body != nil { - log.Info().Str("dest", destination).Str("body", *resp.Body).Msg("Text message response") - } else { - log.Info().Str("dest", destination).Msg("Text message response is nil") - } + return "", fmt.Errorf("Failed to create message to %s: %w", destination, err) } - return nil + //log.Info().Str("dest", destination).Str("sid", *resp.Body).Msg("Text message response") + if resp.Sid == nil { + log.Warn().Str("src", source).Str("dst", destination).Msg("Text message sid is nil") + return "", nil + } + return *resp.Sid, nil } func sendSMS(destination, source, message string) error { diff --git a/db/dberrors/comms.text_log.bob.go b/db/dberrors/comms.text_log.bob.go index 8bd6a9dc..541e7178 100644 --- a/db/dberrors/comms.text_log.bob.go +++ b/db/dberrors/comms.text_log.bob.go @@ -10,8 +10,17 @@ var CommsTextLogErrors = &commsTextLogErrors{ columns: []string{"id"}, s: "text_log_pkey", }, + + ErrUniqueTextLogTwilioSidKey: &UniqueConstraintError{ + schema: "comms", + table: "text_log", + columns: []string{"twilio_sid"}, + s: "text_log_twilio_sid_key", + }, } type commsTextLogErrors struct { ErrUniqueTextLogPkey *UniqueConstraintError + + ErrUniqueTextLogTwilioSidKey *UniqueConstraintError } diff --git a/db/dbinfo/comms.text_log.bob.go b/db/dbinfo/comms.text_log.bob.go index 0e4a1329..d85d75d6 100644 --- a/db/dbinfo/comms.text_log.bob.go +++ b/db/dbinfo/comms.text_log.bob.go @@ -78,6 +78,24 @@ var CommsTextLogs = Table[ Generated: false, AutoIncr: false, }, + TwilioSid: column{ + Name: "twilio_sid", + DBType: "text", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + TwilioStatus: column{ + Name: "twilio_status", + DBType: "text", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, }, Indexes: commsTextLogIndexes{ TextLogPkey: index{ @@ -97,6 +115,23 @@ var CommsTextLogs = Table[ Where: "", Include: []string{}, }, + TextLogTwilioSidKey: index{ + Type: "btree", + Name: "text_log_twilio_sid_key", + Columns: []indexColumn{ + { + Name: "twilio_sid", + Desc: null.FromCond(false, true), + IsExpression: false, + }, + }, + Unique: true, + Comment: "", + NullsFirst: []bool{false}, + NullsDistinct: false, + Where: "", + Include: []string{}, + }, }, PrimaryKey: &constraint{ Name: "text_log_pkey", @@ -123,33 +158,43 @@ var CommsTextLogs = Table[ ForeignColumns: []string{"e164"}, }, }, + Uniques: commsTextLogUniques{ + TextLogTwilioSidKey: constraint{ + Name: "text_log_twilio_sid_key", + Columns: []string{"twilio_sid"}, + Comment: "", + }, + }, Comment: "Used to track text messages that were sent.", } type commsTextLogColumns struct { - Content column - Created column - Destination column - ID column - IsWelcome column - Origin column - Source column + Content column + Created column + Destination column + ID column + IsWelcome column + Origin column + Source column + TwilioSid column + TwilioStatus column } func (c commsTextLogColumns) AsSlice() []column { return []column{ - c.Content, c.Created, c.Destination, c.ID, c.IsWelcome, c.Origin, c.Source, + c.Content, c.Created, c.Destination, c.ID, c.IsWelcome, c.Origin, c.Source, c.TwilioSid, c.TwilioStatus, } } type commsTextLogIndexes struct { - TextLogPkey index + TextLogPkey index + TextLogTwilioSidKey index } func (i commsTextLogIndexes) AsSlice() []index { return []index{ - i.TextLogPkey, + i.TextLogPkey, i.TextLogTwilioSidKey, } } @@ -164,10 +209,14 @@ func (f commsTextLogForeignKeys) AsSlice() []foreignKey { } } -type commsTextLogUniques struct{} +type commsTextLogUniques struct { + TextLogTwilioSidKey constraint +} func (u commsTextLogUniques) AsSlice() []constraint { - return []constraint{} + return []constraint{ + u.TextLogTwilioSidKey, + } } type commsTextLogChecks struct{} diff --git a/db/enums/enums.bob.go b/db/enums/enums.bob.go index 81f030ec..cadb022f 100644 --- a/db/enums/enums.bob.go +++ b/db/enums/enums.bob.go @@ -347,6 +347,8 @@ const ( CommsTextoriginDistrict CommsTextorigin = "district" CommsTextoriginLLM CommsTextorigin = "llm" CommsTextoriginWebsiteAction CommsTextorigin = "website-action" + CommsTextoriginCustomer CommsTextorigin = "customer" + CommsTextoriginReiteration CommsTextorigin = "reiteration" ) func AllCommsTextorigin() []CommsTextorigin { @@ -354,6 +356,8 @@ func AllCommsTextorigin() []CommsTextorigin { CommsTextoriginDistrict, CommsTextoriginLLM, CommsTextoriginWebsiteAction, + CommsTextoriginCustomer, + CommsTextoriginReiteration, } } @@ -367,7 +371,9 @@ func (e CommsTextorigin) Valid() bool { switch e { case CommsTextoriginDistrict, CommsTextoriginLLM, - CommsTextoriginWebsiteAction: + CommsTextoriginWebsiteAction, + CommsTextoriginCustomer, + CommsTextoriginReiteration: return true default: return false diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index 45b2b33f..f8f5ca98 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -368,6 +368,8 @@ func (f *Factory) FromExistingCommsTextLog(m *models.CommsTextLog) *CommsTextLog o.IsWelcome = func() bool { return m.IsWelcome } o.Origin = func() enums.CommsTextorigin { return m.Origin } o.Source = func() string { return m.Source } + o.TwilioSid = func() null.Val[string] { return m.TwilioSid } + o.TwilioStatus = func() string { return m.TwilioStatus } ctx := context.Background() if m.R.DestinationPhone != nil { diff --git a/db/factory/comms.text_log.bob.go b/db/factory/comms.text_log.bob.go index d2e88519..5edb4b92 100644 --- a/db/factory/comms.text_log.bob.go +++ b/db/factory/comms.text_log.bob.go @@ -10,7 +10,9 @@ import ( enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" "github.com/stephenafamo/bob" ) @@ -36,13 +38,15 @@ func (mods CommsTextLogModSlice) Apply(ctx context.Context, n *CommsTextLogTempl // CommsTextLogTemplate is an object representing the database table. // all columns are optional and should be set by mods type CommsTextLogTemplate struct { - Content func() string - Created func() time.Time - Destination func() string - ID func() int32 - IsWelcome func() bool - Origin func() enums.CommsTextorigin - Source func() string + Content func() string + Created func() time.Time + Destination func() string + ID func() int32 + IsWelcome func() bool + Origin func() enums.CommsTextorigin + Source func() string + TwilioSid func() null.Val[string] + TwilioStatus func() string r commsTextLogR f *Factory @@ -120,6 +124,14 @@ func (o CommsTextLogTemplate) BuildSetter() *models.CommsTextLogSetter { val := o.Source() m.Source = omit.From(val) } + if o.TwilioSid != nil { + val := o.TwilioSid() + m.TwilioSid = omitnull.FromNull(val) + } + if o.TwilioStatus != nil { + val := o.TwilioStatus() + m.TwilioStatus = omit.From(val) + } return m } @@ -163,6 +175,12 @@ func (o CommsTextLogTemplate) Build() *models.CommsTextLog { if o.Source != nil { m.Source = o.Source() } + if o.TwilioSid != nil { + m.TwilioSid = o.TwilioSid() + } + if o.TwilioStatus != nil { + m.TwilioStatus = o.TwilioStatus() + } o.setModelRels(m) @@ -207,6 +225,10 @@ func ensureCreatableCommsTextLog(m *models.CommsTextLogSetter) { val := random_string(nil) m.Source = omit.From(val) } + if !(m.TwilioStatus.IsValue()) { + val := random_string(nil) + m.TwilioStatus = omit.From(val) + } } // insertOptRels creates and inserts any optional the relationships on *models.CommsTextLog @@ -351,6 +373,8 @@ func (m commsTextLogMods) RandomizeAllColumns(f *faker.Faker) CommsTextLogMod { CommsTextLogMods.RandomIsWelcome(f), CommsTextLogMods.RandomOrigin(f), CommsTextLogMods.RandomSource(f), + CommsTextLogMods.RandomTwilioSid(f), + CommsTextLogMods.RandomTwilioStatus(f), } } @@ -571,6 +595,90 @@ func (m commsTextLogMods) RandomSource(f *faker.Faker) CommsTextLogMod { }) } +// Set the model columns to this value +func (m commsTextLogMods) TwilioSid(val null.Val[string]) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.TwilioSid = func() null.Val[string] { return val } + }) +} + +// Set the Column from the function +func (m commsTextLogMods) TwilioSidFunc(f func() null.Val[string]) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.TwilioSid = f + }) +} + +// Clear any values for the column +func (m commsTextLogMods) UnsetTwilioSid() CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.TwilioSid = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is sometimes null +func (m commsTextLogMods) RandomTwilioSid(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.TwilioSid = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +// The generated value is never null +func (m commsTextLogMods) RandomTwilioSidNotNull(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.TwilioSid = func() null.Val[string] { + if f == nil { + f = &defaultFaker + } + + val := random_string(f) + return null.From(val) + } + }) +} + +// Set the model columns to this value +func (m commsTextLogMods) TwilioStatus(val string) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.TwilioStatus = func() string { return val } + }) +} + +// Set the Column from the function +func (m commsTextLogMods) TwilioStatusFunc(f func() string) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.TwilioStatus = f + }) +} + +// Clear any values for the column +func (m commsTextLogMods) UnsetTwilioStatus() CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.TwilioStatus = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextLogMods) RandomTwilioStatus(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.TwilioStatus = func() string { + return random_string(f) + } + }) +} + func (m commsTextLogMods) WithParentsCascading() CommsTextLogMod { return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { if isDone, _ := commsTextLogWithParentsCascadingCtx.Value(ctx); isDone { diff --git a/db/migrations/00044_comms_text_origin_user.sql b/db/migrations/00044_comms_text_origin_user.sql new file mode 100644 index 00000000..f8aa6829 --- /dev/null +++ b/db/migrations/00044_comms_text_origin_user.sql @@ -0,0 +1,6 @@ +-- +goose Up +ALTER TYPE comms.TextOrigin ADD VALUE 'customer'; +ALTER TYPE comms.TextOrigin ADD VALUE 'reiteration'; +-- +goose Down +ALTER TYPE comms.TextOrigin DROP VALUE 'reiteration'; +ALTER TYPE comms.TextOrigin DROP VALUE 'customer'; diff --git a/db/migrations/00045_comms_text_sid.sql b/db/migrations/00045_comms_text_sid.sql new file mode 100644 index 00000000..71b0e09e --- /dev/null +++ b/db/migrations/00045_comms_text_sid.sql @@ -0,0 +1,8 @@ +-- +goose Up +ALTER TABLE comms.text_log ADD COLUMN twilio_sid TEXT UNIQUE; +ALTER TABLE comms.text_log ADD COLUMN twilio_status TEXT; +UPDATE comms.text_log SET twilio_status = ''; +ALTER TABLE comms.text_log ALTER COLUMN twilio_status SET NOT NULL; +-- +goose Down +ALTER TABLE comms.text_log DROP COLUMN twilio_status; +ALTER TABLE comms.text_log DROP COLUMN twilio_sid; diff --git a/db/models/comms.text_log.bob.go b/db/models/comms.text_log.bob.go index 445ac37f..45e671d0 100644 --- a/db/models/comms.text_log.bob.go +++ b/db/models/comms.text_log.bob.go @@ -10,7 +10,9 @@ import ( "time" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" + "github.com/aarondl/opt/omitnull" "github.com/stephenafamo/bob" "github.com/stephenafamo/bob/dialect/psql" "github.com/stephenafamo/bob/dialect/psql/dialect" @@ -25,13 +27,15 @@ import ( // CommsTextLog is an object representing the database table. type CommsTextLog struct { - Content string `db:"content" ` - Created time.Time `db:"created" ` - Destination string `db:"destination" ` - ID int32 `db:"id,pk" ` - IsWelcome bool `db:"is_welcome" ` - Origin enums.CommsTextorigin `db:"origin" ` - Source string `db:"source" ` + Content string `db:"content" ` + Created time.Time `db:"created" ` + Destination string `db:"destination" ` + ID int32 `db:"id,pk" ` + IsWelcome bool `db:"is_welcome" ` + Origin enums.CommsTextorigin `db:"origin" ` + Source string `db:"source" ` + TwilioSid null.Val[string] `db:"twilio_sid" ` + TwilioStatus string `db:"twilio_status" ` R commsTextLogR `db:"-" ` } @@ -55,29 +59,33 @@ type commsTextLogR struct { func buildCommsTextLogColumns(alias string) commsTextLogColumns { return commsTextLogColumns{ ColumnsExpr: expr.NewColumnsExpr( - "content", "created", "destination", "id", "is_welcome", "origin", "source", + "content", "created", "destination", "id", "is_welcome", "origin", "source", "twilio_sid", "twilio_status", ).WithParent("comms.text_log"), - tableAlias: alias, - Content: psql.Quote(alias, "content"), - Created: psql.Quote(alias, "created"), - Destination: psql.Quote(alias, "destination"), - ID: psql.Quote(alias, "id"), - IsWelcome: psql.Quote(alias, "is_welcome"), - Origin: psql.Quote(alias, "origin"), - Source: psql.Quote(alias, "source"), + tableAlias: alias, + Content: psql.Quote(alias, "content"), + Created: psql.Quote(alias, "created"), + Destination: psql.Quote(alias, "destination"), + ID: psql.Quote(alias, "id"), + IsWelcome: psql.Quote(alias, "is_welcome"), + Origin: psql.Quote(alias, "origin"), + Source: psql.Quote(alias, "source"), + TwilioSid: psql.Quote(alias, "twilio_sid"), + TwilioStatus: psql.Quote(alias, "twilio_status"), } } type commsTextLogColumns struct { expr.ColumnsExpr - tableAlias string - Content psql.Expression - Created psql.Expression - Destination psql.Expression - ID psql.Expression - IsWelcome psql.Expression - Origin psql.Expression - Source psql.Expression + tableAlias string + Content psql.Expression + Created psql.Expression + Destination psql.Expression + ID psql.Expression + IsWelcome psql.Expression + Origin psql.Expression + Source psql.Expression + TwilioSid psql.Expression + TwilioStatus psql.Expression } func (c commsTextLogColumns) Alias() string { @@ -92,17 +100,19 @@ func (commsTextLogColumns) AliasedAs(alias string) commsTextLogColumns { // All values are optional, and do not have to be set // Generated columns are not included type CommsTextLogSetter struct { - Content omit.Val[string] `db:"content" ` - Created omit.Val[time.Time] `db:"created" ` - Destination omit.Val[string] `db:"destination" ` - ID omit.Val[int32] `db:"id,pk" ` - IsWelcome omit.Val[bool] `db:"is_welcome" ` - Origin omit.Val[enums.CommsTextorigin] `db:"origin" ` - Source omit.Val[string] `db:"source" ` + Content omit.Val[string] `db:"content" ` + Created omit.Val[time.Time] `db:"created" ` + Destination omit.Val[string] `db:"destination" ` + ID omit.Val[int32] `db:"id,pk" ` + IsWelcome omit.Val[bool] `db:"is_welcome" ` + Origin omit.Val[enums.CommsTextorigin] `db:"origin" ` + Source omit.Val[string] `db:"source" ` + TwilioSid omitnull.Val[string] `db:"twilio_sid" ` + TwilioStatus omit.Val[string] `db:"twilio_status" ` } func (s CommsTextLogSetter) SetColumns() []string { - vals := make([]string, 0, 7) + vals := make([]string, 0, 9) if s.Content.IsValue() { vals = append(vals, "content") } @@ -124,6 +134,12 @@ func (s CommsTextLogSetter) SetColumns() []string { if s.Source.IsValue() { vals = append(vals, "source") } + if !s.TwilioSid.IsUnset() { + vals = append(vals, "twilio_sid") + } + if s.TwilioStatus.IsValue() { + vals = append(vals, "twilio_status") + } return vals } @@ -149,6 +165,12 @@ func (s CommsTextLogSetter) Overwrite(t *CommsTextLog) { if s.Source.IsValue() { t.Source = s.Source.MustGet() } + if !s.TwilioSid.IsUnset() { + t.TwilioSid = s.TwilioSid.MustGetNull() + } + if s.TwilioStatus.IsValue() { + t.TwilioStatus = s.TwilioStatus.MustGet() + } } func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) { @@ -157,7 +179,7 @@ func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 7) + vals := make([]bob.Expression, 9) if s.Content.IsValue() { vals[0] = psql.Arg(s.Content.MustGet()) } else { @@ -200,6 +222,18 @@ func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) { vals[6] = psql.Raw("DEFAULT") } + if !s.TwilioSid.IsUnset() { + vals[7] = psql.Arg(s.TwilioSid.MustGetNull()) + } else { + vals[7] = psql.Raw("DEFAULT") + } + + if s.TwilioStatus.IsValue() { + vals[8] = psql.Arg(s.TwilioStatus.MustGet()) + } else { + vals[8] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -209,7 +243,7 @@ func (s CommsTextLogSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 7) + exprs := make([]bob.Expression, 0, 9) if s.Content.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -260,6 +294,20 @@ func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if !s.TwilioSid.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "twilio_sid")...), + psql.Arg(s.TwilioSid), + }}) + } + + if s.TwilioStatus.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "twilio_status")...), + psql.Arg(s.TwilioStatus), + }}) + } + return exprs } @@ -631,13 +679,15 @@ func (commsTextLog0 *CommsTextLog) AttachSourcePhone(ctx context.Context, exec b } type commsTextLogWhere[Q psql.Filterable] struct { - Content psql.WhereMod[Q, string] - Created psql.WhereMod[Q, time.Time] - Destination psql.WhereMod[Q, string] - ID psql.WhereMod[Q, int32] - IsWelcome psql.WhereMod[Q, bool] - Origin psql.WhereMod[Q, enums.CommsTextorigin] - Source psql.WhereMod[Q, string] + Content psql.WhereMod[Q, string] + Created psql.WhereMod[Q, time.Time] + Destination psql.WhereMod[Q, string] + ID psql.WhereMod[Q, int32] + IsWelcome psql.WhereMod[Q, bool] + Origin psql.WhereMod[Q, enums.CommsTextorigin] + Source psql.WhereMod[Q, string] + TwilioSid psql.WhereNullMod[Q, string] + TwilioStatus psql.WhereMod[Q, string] } func (commsTextLogWhere[Q]) AliasedAs(alias string) commsTextLogWhere[Q] { @@ -646,13 +696,15 @@ func (commsTextLogWhere[Q]) AliasedAs(alias string) commsTextLogWhere[Q] { func buildCommsTextLogWhere[Q psql.Filterable](cols commsTextLogColumns) commsTextLogWhere[Q] { return commsTextLogWhere[Q]{ - Content: psql.Where[Q, string](cols.Content), - Created: psql.Where[Q, time.Time](cols.Created), - Destination: psql.Where[Q, string](cols.Destination), - ID: psql.Where[Q, int32](cols.ID), - IsWelcome: psql.Where[Q, bool](cols.IsWelcome), - Origin: psql.Where[Q, enums.CommsTextorigin](cols.Origin), - Source: psql.Where[Q, string](cols.Source), + Content: psql.Where[Q, string](cols.Content), + Created: psql.Where[Q, time.Time](cols.Created), + Destination: psql.Where[Q, string](cols.Destination), + ID: psql.Where[Q, int32](cols.ID), + IsWelcome: psql.Where[Q, bool](cols.IsWelcome), + Origin: psql.Where[Q, enums.CommsTextorigin](cols.Origin), + Source: psql.Where[Q, string](cols.Source), + TwilioSid: psql.WhereNull[Q, string](cols.TwilioSid), + TwilioStatus: psql.Where[Q, string](cols.TwilioStatus), } } diff --git a/llm/client.go b/llm/client.go index 45ff2a24..af53d5f2 100644 --- a/llm/client.go +++ b/llm/client.go @@ -1,7 +1,9 @@ package llm import ( - "github.com/rs/zerolog/log" + "context" + "fmt" + //"github.com/rs/zerolog/log" ) type Message struct { @@ -9,14 +11,10 @@ type Message struct { IsFromCustomer bool } -func GenerateNextMessage(history []Message, current Message) (Message, error) { - // In general our history - for i, msg := range history { - log.Info().Int("i", i).Bool("is_customer", msg.IsFromCustomer).Msg("History") +func GenerateNextMessage(ctx context.Context, history []Message, customer_phone string) (Message, error) { + next, err := client.continueConversation(ctx, history, customer_phone) + if err != nil { + return Message{}, fmt.Errorf("Failed to generate next message: %w", err) } - - return Message{ - Content: "hey there. :)", - IsFromCustomer: false, - }, nil + return next, nil } diff --git a/llm/openai.go b/llm/openai.go index d936bb0d..b971d1a3 100644 --- a/llm/openai.go +++ b/llm/openai.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "strings" - "time" "github.com/maruel/genai" "github.com/maruel/genai/adapters" @@ -12,21 +11,6 @@ import ( "github.com/rs/zerolog/log" ) -type openAIClient struct { - client *openaichat.Client - conversations map[string][]genai.Message - log *Logger -} - -var client *openAIClient - -type AIRequest struct { - Displayname string - Message string - Sender string - Timestamp time.Time -} - func CreateOpenAIClient(ctx context.Context) error { logger := createLogger() @@ -45,131 +29,73 @@ func CreateOpenAIClient(ctx context.Context) error { return nil } -func (c *openAIClient) continueConversation(ctx context.Context, req AIRequest) error { - msgs, ok := c.conversations["roomid"] - if !ok { - msgs = genai.Messages{ - c.startConversation(ctx, req), - } - } else { - msgs = append(msgs, genai.NewTextMessage(fmt.Sprintf("(%s) user: %s\nbot: ", req.Timestamp.String(), req.Message))) - } +type openAIClient struct { + client *openaichat.Client + conversations map[string][]genai.Message + log *Logger +} - c.log.Debug().Msg("Generating response...") +type QueryReportStatusInput struct { + ReportID string `json:"report_id"` +} + +var client *openAIClient + +func (c *openAIClient) continueConversation(ctx context.Context, history []Message, customer_phone string) (Message, error) { opts := genai.OptionsTools{ Tools: []genai.ToolDef{ { - Name: "followup_timer", - Description: "This should be used to indicate that the bot should follow up with the user in the future to check on task progress.", - Callback: func(ctx2 context.Context, input *FollowupTimerInput) (string, error) { - return c.followupSchedule(ctx2, req, input) - }, - }, { - Name: "switch_task", - Description: "Any time the user indicates they change tasks this must be called to update the record of what tasks are being done.", - Callback: func(ctx2 context.Context, input *SwitchTaskInput) (string, error) { - return c.switchTask(ctx2, req, input) + Name: "query_report_status", + Description: "This is used to answer any questions about the current state of the mosquito nuisance report.", + Callback: func(ctx2 context.Context, input *QueryReportStatusInput) (string, error) { + return c.queryReportStatus(ctx2, customer_phone) }, }, }, } - res, _, err := adapters.GenSyncWithToolCallLoop(ctx, c.client, msgs, &opts) + msg := c.convertHistory(history) + res, _, err := adapters.GenSyncWithToolCallLoop(ctx, c.client, genai.Messages{msg}, &opts) if err != nil { - return fmt.Errorf("Failed to continue conversation: %v", err) + return Message{}, fmt.Errorf("Failed to continue conversation: %v", err) } for _, m := range res { - msgs = append(msgs, m) // Empty responses are tool call related. if m.String() == "" { + log.Debug().Msg("Tool called") } else { - //c.log.Info().Str("room", req.RoomID.String()).Msg(m.String()) var toSay string = m.String() - toSay = strings.Replace(toSay, "bot: ", "", 1) - log.Info().Str("to say", toSay).Msg("Responding") - /*c.aiResponseChannel <- AIResponse{ - Message: toSay, - RoomID: req.RoomID, - }*/ + toSay = strings.Replace(toSay, "report-mosquitoes-online: ", "", 1) + return Message{ + Content: toSay, + IsFromCustomer: false, + }, nil } } - //c.conversations[req.RoomID.String()] = msgs - return nil + return Message{}, nil } -type FollowupTimerInput struct { - DelayInSeconds int64 `json:"delay_in_seconds"` -} - -func (c *openAIClient) followupFire(ctx context.Context, req AIRequest, duration time.Duration) { - if err := ctx.Err(); err != nil { - //c.log.Info().Str("room", req.RoomID.String()).Msg("Context canceled") - return +func (c *openAIClient) convertHistory(history []Message) genai.Message { + var sb strings.Builder + sb.WriteString( + `This is a text chat conversation between a customer that's a member of the public and a mosquito abatement district. + The customer has reported a mosquito nuisance or mosquito breeding through the website report.mosquitoes.online. + Messages from the customer are prefixed with 'customer:' and reponses from the service agent servicing the request are prefixed with 'agent:'. + The agent wants to provide clear, confident, and succint information about the state of the customer's request. The agent also provides general information about how members of the public can help with controlling mosquitoes. For complex or highly specific requests, the agent will need to defer to the mosquito abatement district. This will take some time because contacting the district may take a few hours to get a response. When the agent needs to contact the district, the agent should tell the customer they are reaching out to the district and to expect a delay. + Transcript starts:`, + ) + for _, h := range history { + if h.IsFromCustomer { + sb.WriteString(fmt.Sprintf("\n\ncustomer (%s): %s\n", h.Content)) + } else { + sb.WriteString(fmt.Sprintf("\n\nagent (%s): %s\n", h.Content)) + } } - msgs, ok := c.conversations["roomid"] - if !ok { - //c.log.Warn().Str("room", req.RoomID.String()).Str("elapsed", duration.String()).Msg("No messages for room") - return - } - msgs = append(msgs, genai.NewTextMessage(fmt.Sprintf("<%s passed>", duration.String()))) - res, err := c.client.GenSync(ctx, msgs) - if err != nil { - //c.log.Error().Str("room", req.RoomID.String()).Err(err).Msg("Failed to continue after timer") - return - } - msgs = append(msgs, res.Message) - var toSay string = res.String() - toSay = strings.Replace(toSay, "bot: ", "", 1) - log.Info().Str("to say", toSay).Msg("To say") - /*c.aiResponseChannel <- AIResponse{ - Message: toSay, - RoomID: req.RoomID, - } - c.conversations[req.RoomID.String()] = msgs - */ + return genai.NewTextMessage(sb.String()) } -func (c *openAIClient) followupSchedule(ctx context.Context, req AIRequest, input *FollowupTimerInput) (string, error) { - //c.log.Info().Str("room", req.RoomID.String()).Int64("delay", input.DelayInSeconds).Msg("Followup timer scheduled.") - duration, err := time.ParseDuration(fmt.Sprintf("%ds", input.DelayInSeconds)) - if err != nil { - return "", fmt.Errorf("Failed to parse %d as a valid duration: %v", input.DelayInSeconds, err) - } - /*c.aiResponseChannel <- AIResponse{ - Message: fmt.Sprintf("⌛ followup scheduled '%s'", duration.String()), - RoomID: req.RoomID, - }*/ - time.AfterFunc(duration, func() { - c.followupFire(ctx, req, duration) - }) - return fmt.Sprintf("Followup timer set for %s in the future", duration.String()), nil -} - -type SwitchTaskInput struct { - TaskName string `json:"task_name"` -} - -func (c *openAIClient) switchTask(ctx context.Context, req AIRequest, input *SwitchTaskInput) (string, error) { - //c.log.Info().Str("room", req.RoomID.String()).Str("task", input.TaskName).Msg("Task Switched") - /*c.aiResponseChannel <- AIResponse{ - Message: fmt.Sprintf("📋 notes task '%s'", input.TaskName), - RoomID: req.RoomID, - }*/ - - return fmt.Sprintf("Recorded a switch to task %s at %s", input.TaskName, time.Now().String()), nil -} - -func (c *openAIClient) startConversation(ctx context.Context, req AIRequest) genai.Message { - return genai.NewTextMessage(fmt.Sprintf( - `This is a text chat conversation between an employee and a chatbot helping to manage timecards. - The user's name is '%[1]s'. - Messages from the user will start with '(timestamp) %[1]s:'. - Messages from the bot will start with 'bot:'. - Sometimes the user won't say anything for a long time and the chatbot needs to follow-up with them. - When time passes, there will be a prompt like '<200s passed>'. - The bot should then prompt the user to provide a bit of information about what they've been working on during that time. - The bot should be interested to know what the user's goals are at a high level and should pay attention to any difficulties or frustrations the user experiences.\n\n - (%[2]s) user: %[3]s\nbot:`, req.Displayname, req.Timestamp.String(), req.Message)) +func (c *openAIClient) queryReportStatus(ctx context.Context, customer_phone string) (string, error) { + return "Report is scheduled for work in 3 days at 2:00pm by the district", nil } diff --git a/platform/text.go b/platform/text.go index f77a6e77..cebc16e4 100644 --- a/platform/text.go +++ b/platform/text.go @@ -19,12 +19,97 @@ import ( "github.com/rs/zerolog/log" ) +func HandleTextMessage(from string, to string, body string) { + ctx := context.Background() + type_, src := splitPhoneSource(from) + dst, err := getDst(ctx, to) + if err != nil { + log.Error().Err(err).Str("to", to).Msg("Failed to get dst") + return + } + + _, err = insertTextLog(ctx, body, dst, src, enums.CommsTextoriginCustomer, false) + if err != nil { + log.Error().Err(err).Str("dst", dst).Msg("Failed to add text message log") + return + } + subscribed, err := isSubscribed(ctx, src) + if err != nil { + log.Error().Err(err).Msg("Failed to handle message") + return + } + // We don't know if they're subscribed or not. + if subscribed == nil { + body_l := strings.TrimSpace(strings.ToLower(body)) + switch body_l { + case "stop": + setSubscribed(ctx, src, false) + case "yes": + setSubscribed(ctx, src, true) + handleWaitingTextJobs(ctx, src) + default: + content := "I have to start with either 'YES' or 'STOP' first, Which do you want?" + /*err := insertTextLog(ctx, body, src, dst, enums.CommsTextoriginReiteration, false) + if err != nil { + log.Error().Err(err).Msg("Failed to add reiteration to the text log") + return + }*/ + err = sendText(ctx, src, dst, content, enums.CommsTextoriginReiteration, false) + if err != nil { + log.Error().Err(err).Msg("Failed to resend initial prompt.") + } + } + return + } + previous_messages, err := loadPreviousMessages(ctx, dst, src) + if err != nil { + log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to get previous messages") + return + } + log.Info().Int("len", len(previous_messages)).Msg("passing") + next_message, err := llm.GenerateNextMessage(ctx, previous_messages, src) + if err != nil { + log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to generate next message") + return + } + /* + err = insertTextLog(ctx, next_message.Content, src, dst, enums.CommsTextoriginLLM, false) + if err != nil { + log.Error().Err(err).Str("dst", dst).Msg("Failed to insert new text message to the text log") + return + } + */ + err = sendText(ctx, dst, src, next_message.Content, enums.CommsTextoriginLLM, false) + if err != nil { + log.Error().Err(err).Str("src", src).Str("dst", dst).Str("content", next_message.Content).Msg("Failed to send response text") + return + } + log.Info().Str("from", from).Str("from-type", type_).Str("to", to).Str("src", src).Str("dst", dst).Str("body", body).Str("reply", next_message.Content).Msg("Handled text message") +} + func TextStoreSources() error { ctx := context.TODO() src := phonenumbers.Format(&config.PhoneNumberReport, phonenumbers.E164) return ensureInDB(ctx, src) } +func UpdateMessageStatus(twilio_sid string, status string) { + ctx := context.TODO() + l, err := models.CommsTextLogs.Query( + models.SelectWhere.CommsTextLogs.TwilioSid.EQ(twilio_sid), + ).One(ctx, db.PGInstance.BobDB) + if err != nil { + log.Error().Err(err).Str("twilio_sid", twilio_sid).Str("status", status).Msg("Failed to update message status query failed") + return + } + err = l.Update(ctx, db.PGInstance.BobDB, &models.CommsTextLogSetter{ + TwilioStatus: omit.From(status), + }) + if err != nil { + log.Error().Err(err).Str("twilio_sid", twilio_sid).Str("status", status).Msg("Failed to update message status update failed") + return + } +} func delayMessage(ctx context.Context, source string, destination string, content string, type_ enums.CommsTextjobtype) error { job, err := models.CommsTextJobs.Insert(&models.CommsTextJobSetter{ Content: omit.From(content), @@ -81,20 +166,6 @@ func ensureInDB(ctx context.Context, destination string) (err error) { return nil } -func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin, is_welcome bool) (err error) { - _, err = models.CommsTextLogs.Insert(&models.CommsTextLogSetter{ - //ID: - Content: omit.From(content), - Created: omit.From(time.Now()), - Destination: omit.From(destination), - IsWelcome: omit.From(is_welcome), - Origin: omit.From(origin), - Source: omit.From(source), - }).One(ctx, db.PGInstance.BobDB) - - return err -} - // Translate from Twilio's representation of a RCS message sender to our concept of a phone number // From: rcs:dev_report_mosquitoes_online_dosrvwxm_agent // To: +16235525879 @@ -113,6 +184,39 @@ func getDst(ctx context.Context, to string) (string, error) { return "", fmt.Errorf("Cannot match phone number to '%s'", to) } +func handleWaitingTextJobs(ctx context.Context, src string) { + log.Info().Str("src", src).Msg("Pretend handle waiting jobs") + +} + +func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin, is_welcome bool) (log *models.CommsTextLog, err error) { + log, err = models.CommsTextLogs.Insert(&models.CommsTextLogSetter{ + //ID: + Content: omit.From(content), + Created: omit.From(time.Now()), + Destination: omit.From(destination), + IsWelcome: omit.From(is_welcome), + Origin: omit.From(origin), + Source: omit.From(source), + TwilioSid: omitnull.FromPtr[string](nil), + TwilioStatus: omit.From(""), + }).One(ctx, db.PGInstance.BobDB) + + return log, err +} + +func isSubscribed(ctx context.Context, src string) (*bool, error) { + phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, src) + if err != nil { + return nil, fmt.Errorf("Failed to determine if '%s' is subscribed: %w", src, err) + } + if phone.IsSubscribed.IsNull() { + return nil, nil + } + result := phone.IsSubscribed.MustGet() + return &result, nil +} + func loadPreviousMessages(ctx context.Context, dst, src string) ([]llm.Message, error) { messages, err := sql.TextsBySenders(dst, src).All(ctx, db.PGInstance.BobDB) results := make([]llm.Message, 0) @@ -135,11 +239,19 @@ func sendText(ctx context.Context, source string, destination string, message st if err != nil { return fmt.Errorf("Failed to ensure text message destination is in the DB: %w", err) } - err = insertTextLog(ctx, message, destination, source, origin, is_welcome) + log, err := insertTextLog(ctx, message, destination, source, origin, is_welcome) if err != nil { return fmt.Errorf("Failed to insert text message in the DB: %w", err) } - err = text.SendText(ctx, source, destination, message) + sid, err := text.SendText(ctx, source, destination, message) + if err != nil { + return fmt.Errorf("Failed to send text message: %w", err) + } + err = log.Update(ctx, db.PGInstance.BobDB, &models.CommsTextLogSetter{ + TwilioSid: omitnull.From(sid), + TwilioStatus: omit.From("created"), + }) + return nil } @@ -159,18 +271,6 @@ func splitPhoneSource(s string) (string, string) { } -func isSubscribed(ctx context.Context, src string) (*bool, error) { - phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, src) - if err != nil { - return nil, fmt.Errorf("Failed to determine if '%s' is subscribed: %w", src, err) - } - if phone.IsSubscribed.IsNull() { - return nil, nil - } - result := phone.IsSubscribed.MustGet() - return &result, nil -} - func setSubscribed(ctx context.Context, src string, is_subscribed bool) error { phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, src) if err != nil { @@ -182,57 +282,3 @@ func setSubscribed(ctx context.Context, src string, is_subscribed bool) error { log.Info().Str("src", src).Bool("is_subscribed", is_subscribed).Msg("Set number subscribed") return nil } - -func handleWaitingTextJobs(ctx context.Context, src string) { - log.Info().Str("src", src).Msg("Pretend handle waiting jobs") - -} -func HandleTextMessage(from string, to string, body string) { - ctx := context.Background() - type_, src := splitPhoneSource(from) - dst, err := getDst(ctx, to) - if err != nil { - log.Error().Err(err).Str("to", to).Msg("Failed to get dst") - return - } - subscribed, err := isSubscribed(ctx, src) - if err != nil { - log.Error().Err(err).Msg("Failed to handle message") - return - } - // We don't know if they're subscribed or not. - if subscribed == nil { - body_l := strings.TrimSpace(strings.ToLower(body)) - switch body_l { - case "stop": - setSubscribed(ctx, src, false) - case "yes": - setSubscribed(ctx, src, true) - handleWaitingTextJobs(ctx, src) - default: - content := "I have to start with either 'YES' or 'STOP' first, Which do you want?" - err := text.SendText(ctx, src, dst, content) - if err != nil { - log.Error().Err(err).Msg("Failed to resend initial prompt.") - } - } - return - } - previous_messages, err := loadPreviousMessages(ctx, dst, src) - if err != nil { - log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to get previous messages") - return - } - current := llm.Message{ - Content: body, - IsFromCustomer: true, - } - log.Info().Int("len", len(previous_messages)).Msg("passing") - next_message, err := llm.GenerateNextMessage(previous_messages, current) - if err != nil { - log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to generate next message") - return - } - text.SendTextFromLLM(next_message.Content) - log.Info().Str("from", from).Str("from-type", type_).Str("to", to).Str("src", src).Str("dst", dst).Str("body", body).Str("reply", next_message.Content).Msg("Handling text message") -} From a68b8781e785de412418eaec0434ac2538b1c5e3 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 27 Jan 2026 18:44:02 +0000 Subject: [PATCH 0133/1453] Add ability to make LLM agent forget the conversation history This is extremely useful for testing. In order to do this I needed to actually deploy the migration to a bob fork so I could start to add support for behaviors I really want. Specifically the ability to search for ids in a slice. --- auth/auth.go | 4 +- background/arcgis.go | 8 +- background/summary.go | 10 +- comms/email/db.go | 2 +- comms/email/template.go | 6 +- comms/text/db.go | 2 +- db/connection.go | 2 +- db/dberrors/arcgis.user_.bob.go | 2 +- db/dberrors/arcgis.user_privilege.bob.go | 2 +- db/dberrors/bob_errors.bob.go | 2 +- db/dberrors/comms.email_contact.bob.go | 2 +- db/dberrors/comms.email_log.bob.go | 2 +- db/dberrors/comms.email_template.bob.go | 2 +- db/dberrors/comms.phone.bob.go | 2 +- db/dberrors/comms.text_job.bob.go | 2 +- db/dberrors/comms.text_log.bob.go | 2 +- .../fieldseeker.containerrelate.bob.go | 2 +- .../fieldseeker.fieldscoutinglog.bob.go | 2 +- db/dberrors/fieldseeker.habitatrelate.bob.go | 2 +- .../fieldseeker.inspectionsample.bob.go | 2 +- .../fieldseeker.inspectionsampledetail.bob.go | 2 +- db/dberrors/fieldseeker.linelocation.bob.go | 2 +- .../fieldseeker.locationtracking.bob.go | 2 +- .../fieldseeker.mosquitoinspection.bob.go | 2 +- db/dberrors/fieldseeker.pointlocation.bob.go | 2 +- .../fieldseeker.polygonlocation.bob.go | 2 +- db/dberrors/fieldseeker.pool.bob.go | 2 +- db/dberrors/fieldseeker.pooldetail.bob.go | 2 +- .../fieldseeker.proposedtreatmentarea.bob.go | 2 +- .../fieldseeker.qamosquitoinspection.bob.go | 2 +- db/dberrors/fieldseeker.rodentlocation.bob.go | 2 +- .../fieldseeker.samplecollection.bob.go | 2 +- db/dberrors/fieldseeker.samplelocation.bob.go | 2 +- db/dberrors/fieldseeker.servicerequest.bob.go | 2 +- .../fieldseeker.speciesabundance.bob.go | 2 +- db/dberrors/fieldseeker.stormdrain.bob.go | 2 +- db/dberrors/fieldseeker.timecard.bob.go | 2 +- db/dberrors/fieldseeker.trapdata.bob.go | 2 +- db/dberrors/fieldseeker.traplocation.bob.go | 2 +- db/dberrors/fieldseeker.treatment.bob.go | 2 +- db/dberrors/fieldseeker.treatmentarea.bob.go | 2 +- db/dberrors/fieldseeker.zones.bob.go | 2 +- db/dberrors/fieldseeker.zones2.bob.go | 2 +- db/dberrors/fieldseeker_sync.bob.go | 2 +- db/dberrors/goose_db_version.bob.go | 2 +- db/dberrors/h3_aggregation.bob.go | 2 +- db/dberrors/import.district.bob.go | 2 +- db/dberrors/note_audio.bob.go | 2 +- db/dberrors/note_audio_breadcrumb.bob.go | 2 +- db/dberrors/note_audio_data.bob.go | 2 +- db/dberrors/note_image.bob.go | 2 +- db/dberrors/note_image_breadcrumb.bob.go | 2 +- db/dberrors/note_image_data.bob.go | 2 +- db/dberrors/notification.bob.go | 2 +- db/dberrors/oauth_token.bob.go | 2 +- db/dberrors/organization.bob.go | 2 +- db/dberrors/publicreport.image.bob.go | 2 +- db/dberrors/publicreport.image_exif.bob.go | 2 +- db/dberrors/publicreport.nuisance.bob.go | 2 +- db/dberrors/publicreport.pool.bob.go | 2 +- db/dberrors/publicreport.pool_image.bob.go | 2 +- db/dberrors/publicreport.quick.bob.go | 2 +- db/dberrors/publicreport.quick_image.bob.go | 2 +- db/dberrors/sessions.bob.go | 2 +- db/dberrors/spatial_ref_sys.bob.go | 2 +- db/dberrors/user_.bob.go | 2 +- db/dbinfo/arcgis.user_.bob.go | 2 +- db/dbinfo/arcgis.user_privilege.bob.go | 2 +- db/dbinfo/bob_types.bob.go | 2 +- db/dbinfo/comms.email_contact.bob.go | 2 +- db/dbinfo/comms.email_log.bob.go | 2 +- db/dbinfo/comms.email_template.bob.go | 2 +- db/dbinfo/comms.phone.bob.go | 2 +- db/dbinfo/comms.text_job.bob.go | 2 +- db/dbinfo/comms.text_log.bob.go | 32 ++-- db/dbinfo/fieldseeker.containerrelate.bob.go | 2 +- db/dbinfo/fieldseeker.fieldscoutinglog.bob.go | 2 +- db/dbinfo/fieldseeker.habitatrelate.bob.go | 2 +- db/dbinfo/fieldseeker.inspectionsample.bob.go | 2 +- .../fieldseeker.inspectionsampledetail.bob.go | 2 +- db/dbinfo/fieldseeker.linelocation.bob.go | 2 +- db/dbinfo/fieldseeker.locationtracking.bob.go | 2 +- .../fieldseeker.mosquitoinspection.bob.go | 2 +- db/dbinfo/fieldseeker.pointlocation.bob.go | 2 +- db/dbinfo/fieldseeker.polygonlocation.bob.go | 2 +- db/dbinfo/fieldseeker.pool.bob.go | 2 +- db/dbinfo/fieldseeker.pooldetail.bob.go | 2 +- .../fieldseeker.proposedtreatmentarea.bob.go | 2 +- .../fieldseeker.qamosquitoinspection.bob.go | 2 +- db/dbinfo/fieldseeker.rodentlocation.bob.go | 2 +- db/dbinfo/fieldseeker.samplecollection.bob.go | 2 +- db/dbinfo/fieldseeker.samplelocation.bob.go | 2 +- db/dbinfo/fieldseeker.servicerequest.bob.go | 2 +- db/dbinfo/fieldseeker.speciesabundance.bob.go | 2 +- db/dbinfo/fieldseeker.stormdrain.bob.go | 2 +- db/dbinfo/fieldseeker.timecard.bob.go | 2 +- db/dbinfo/fieldseeker.trapdata.bob.go | 2 +- db/dbinfo/fieldseeker.traplocation.bob.go | 2 +- db/dbinfo/fieldseeker.treatment.bob.go | 2 +- db/dbinfo/fieldseeker.treatmentarea.bob.go | 2 +- db/dbinfo/fieldseeker.zones.bob.go | 2 +- db/dbinfo/fieldseeker.zones2.bob.go | 2 +- db/dbinfo/fieldseeker_sync.bob.go | 2 +- db/dbinfo/geography_columns.bob.go | 2 +- db/dbinfo/geometry_columns.bob.go | 2 +- db/dbinfo/goose_db_version.bob.go | 2 +- db/dbinfo/h3_aggregation.bob.go | 2 +- db/dbinfo/import.district.bob.go | 2 +- db/dbinfo/note_audio.bob.go | 2 +- db/dbinfo/note_audio_breadcrumb.bob.go | 2 +- db/dbinfo/note_audio_data.bob.go | 2 +- db/dbinfo/note_image.bob.go | 2 +- db/dbinfo/note_image_breadcrumb.bob.go | 2 +- db/dbinfo/note_image_data.bob.go | 2 +- db/dbinfo/notification.bob.go | 2 +- db/dbinfo/oauth_token.bob.go | 2 +- db/dbinfo/organization.bob.go | 2 +- db/dbinfo/publicreport.image.bob.go | 2 +- db/dbinfo/publicreport.image_exif.bob.go | 2 +- db/dbinfo/publicreport.nuisance.bob.go | 2 +- db/dbinfo/publicreport.pool.bob.go | 2 +- db/dbinfo/publicreport.pool_image.bob.go | 2 +- db/dbinfo/publicreport.quick.bob.go | 2 +- db/dbinfo/publicreport.quick_image.bob.go | 2 +- db/dbinfo/publicreport.report_location.bob.go | 2 +- db/dbinfo/raster_columns.bob.go | 2 +- db/dbinfo/raster_overviews.bob.go | 2 +- db/dbinfo/sessions.bob.go | 2 +- db/dbinfo/spatial_ref_sys.bob.go | 2 +- db/dbinfo/user_.bob.go | 2 +- db/enums/enums.bob.go | 17 +- db/factory/arcgis.user_.bob.go | 4 +- db/factory/arcgis.user_privilege.bob.go | 4 +- db/factory/bobfactory_context.bob.go | 2 +- db/factory/bobfactory_main.bob.go | 7 +- db/factory/bobfactory_random.bob.go | 6 +- db/factory/comms.email_contact.bob.go | 4 +- db/factory/comms.email_log.bob.go | 6 +- db/factory/comms.email_template.bob.go | 4 +- db/factory/comms.phone.bob.go | 4 +- db/factory/comms.text_job.bob.go | 4 +- db/factory/comms.text_log.bob.go | 66 +++++-- db/factory/fieldseeker.containerrelate.bob.go | 6 +- .../fieldseeker.fieldscoutinglog.bob.go | 6 +- db/factory/fieldseeker.habitatrelate.bob.go | 6 +- .../fieldseeker.inspectionsample.bob.go | 6 +- .../fieldseeker.inspectionsampledetail.bob.go | 6 +- db/factory/fieldseeker.linelocation.bob.go | 6 +- .../fieldseeker.locationtracking.bob.go | 6 +- .../fieldseeker.mosquitoinspection.bob.go | 6 +- db/factory/fieldseeker.pointlocation.bob.go | 6 +- db/factory/fieldseeker.polygonlocation.bob.go | 6 +- db/factory/fieldseeker.pool.bob.go | 6 +- db/factory/fieldseeker.pooldetail.bob.go | 6 +- .../fieldseeker.proposedtreatmentarea.bob.go | 6 +- .../fieldseeker.qamosquitoinspection.bob.go | 6 +- db/factory/fieldseeker.rodentlocation.bob.go | 6 +- .../fieldseeker.samplecollection.bob.go | 6 +- db/factory/fieldseeker.samplelocation.bob.go | 6 +- db/factory/fieldseeker.servicerequest.bob.go | 6 +- .../fieldseeker.speciesabundance.bob.go | 6 +- db/factory/fieldseeker.stormdrain.bob.go | 6 +- db/factory/fieldseeker.timecard.bob.go | 6 +- db/factory/fieldseeker.trapdata.bob.go | 6 +- db/factory/fieldseeker.traplocation.bob.go | 6 +- db/factory/fieldseeker.treatment.bob.go | 6 +- db/factory/fieldseeker.treatmentarea.bob.go | 6 +- db/factory/fieldseeker.zones.bob.go | 6 +- db/factory/fieldseeker.zones2.bob.go | 6 +- db/factory/fieldseeker_sync.bob.go | 4 +- db/factory/geography_columns.bob.go | 2 +- db/factory/geometry_columns.bob.go | 2 +- db/factory/goose_db_version.bob.go | 4 +- db/factory/h3_aggregation.bob.go | 4 +- db/factory/import.district.bob.go | 4 +- db/factory/note_audio.bob.go | 4 +- db/factory/note_audio_breadcrumb.bob.go | 4 +- db/factory/note_audio_data.bob.go | 4 +- db/factory/note_image.bob.go | 4 +- db/factory/note_image_breadcrumb.bob.go | 4 +- db/factory/note_image_data.bob.go | 4 +- db/factory/notification.bob.go | 4 +- db/factory/oauth_token.bob.go | 4 +- db/factory/organization.bob.go | 4 +- db/factory/publicreport.image.bob.go | 4 +- db/factory/publicreport.image_exif.bob.go | 4 +- db/factory/publicreport.nuisance.bob.go | 4 +- db/factory/publicreport.pool.bob.go | 4 +- db/factory/publicreport.pool_image.bob.go | 4 +- db/factory/publicreport.quick.bob.go | 4 +- db/factory/publicreport.quick_image.bob.go | 4 +- .../publicreport.report_location.bob.go | 2 +- db/factory/raster_columns.bob.go | 2 +- db/factory/raster_overviews.bob.go | 2 +- db/factory/sessions.bob.go | 4 +- db/factory/spatial_ref_sys.bob.go | 4 +- db/factory/user_.bob.go | 4 +- db/fieldseeker.go | 4 +- .../00044_comms_text_origin_user.sql | 3 - db/migrations/00045_comms_text_sid.sql | 5 + db/models/arcgis.user_.bob.go | 22 +-- db/models/arcgis.user_privilege.bob.go | 22 +-- db/models/bob_counts.bob.go | 10 +- db/models/bob_joins.bob.go | 8 +- db/models/bob_loaders.bob.go | 8 +- db/models/bob_where.bob.go | 8 +- db/models/comms.email_contact.bob.go | 22 +-- db/models/comms.email_log.bob.go | 22 +-- db/models/comms.email_template.bob.go | 22 +-- db/models/comms.phone.bob.go | 22 +-- db/models/comms.text_job.bob.go | 22 +-- db/models/comms.text_log.bob.go | 167 ++++++++++-------- db/models/fieldseeker.containerrelate.bob.go | 24 +-- db/models/fieldseeker.fieldscoutinglog.bob.go | 24 +-- db/models/fieldseeker.habitatrelate.bob.go | 24 +-- db/models/fieldseeker.inspectionsample.bob.go | 24 +-- .../fieldseeker.inspectionsampledetail.bob.go | 24 +-- db/models/fieldseeker.linelocation.bob.go | 24 +-- db/models/fieldseeker.locationtracking.bob.go | 24 +-- .../fieldseeker.mosquitoinspection.bob.go | 24 +-- db/models/fieldseeker.pointlocation.bob.go | 24 +-- db/models/fieldseeker.polygonlocation.bob.go | 24 +-- db/models/fieldseeker.pool.bob.go | 24 +-- db/models/fieldseeker.pooldetail.bob.go | 24 +-- .../fieldseeker.proposedtreatmentarea.bob.go | 24 +-- .../fieldseeker.qamosquitoinspection.bob.go | 24 +-- db/models/fieldseeker.rodentlocation.bob.go | 24 +-- db/models/fieldseeker.samplecollection.bob.go | 24 +-- db/models/fieldseeker.samplelocation.bob.go | 24 +-- db/models/fieldseeker.servicerequest.bob.go | 24 +-- db/models/fieldseeker.speciesabundance.bob.go | 24 +-- db/models/fieldseeker.stormdrain.bob.go | 24 +-- db/models/fieldseeker.timecard.bob.go | 24 +-- db/models/fieldseeker.trapdata.bob.go | 24 +-- db/models/fieldseeker.traplocation.bob.go | 24 +-- db/models/fieldseeker.treatment.bob.go | 24 +-- db/models/fieldseeker.treatmentarea.bob.go | 24 +-- db/models/fieldseeker.zones.bob.go | 24 +-- db/models/fieldseeker.zones2.bob.go | 24 +-- db/models/fieldseeker_sync.bob.go | 22 +-- db/models/geography_columns.bob.go | 8 +- db/models/geometry_columns.bob.go | 8 +- db/models/goose_db_version.bob.go | 16 +- db/models/h3_aggregation.bob.go | 22 +-- db/models/import.district.bob.go | 22 +-- db/models/note_audio.bob.go | 22 +-- db/models/note_audio_breadcrumb.bob.go | 22 +-- db/models/note_audio_data.bob.go | 22 +-- db/models/note_image.bob.go | 22 +-- db/models/note_image_breadcrumb.bob.go | 22 +-- db/models/note_image_data.bob.go | 22 +-- db/models/notification.bob.go | 22 +-- db/models/oauth_token.bob.go | 22 +-- db/models/organization.bob.go | 22 +-- db/models/publicreport.image.bob.go | 22 +-- db/models/publicreport.image_exif.bob.go | 22 +-- db/models/publicreport.nuisance.bob.go | 22 +-- db/models/publicreport.pool.bob.go | 22 +-- db/models/publicreport.pool_image.bob.go | 22 +-- db/models/publicreport.quick.bob.go | 22 +-- db/models/publicreport.quick_image.bob.go | 22 +-- db/models/publicreport.report_location.bob.go | 8 +- db/models/raster_columns.bob.go | 8 +- db/models/raster_overviews.bob.go | 8 +- db/models/sessions.bob.go | 16 +- db/models/spatial_ref_sys.bob.go | 16 +- db/models/user_.bob.go | 22 +-- db/prepared.go | 6 +- db/sql/org_by_oauth_id.bob.go | 10 +- db/sql/org_by_oauth_id.bob.sql | 2 +- ...creport_image_with_json_by_quick_id.bob.go | 10 +- ...report_image_with_json_by_quick_id.bob.sql | 2 +- db/sql/publicreport_publicid_table.bob.go | 10 +- db/sql/publicreport_publicid_table.bob.sql | 2 +- db/sql/texts_by_senders.bob.go | 56 +++--- db/sql/texts_by_senders.bob.sql | 3 +- db/sql/texts_by_senders.sql | 1 + db/sql/trapcount_by_location_id.bob.go | 12 +- db/sql/trapcount_by_location_id.bob.sql | 2 +- db/sql/trapdata_by_location_id_recent.bob.go | 12 +- db/sql/trapdata_by_location_id_recent.bob.sql | 2 +- db/sql/traplocation_by_source_id.bob.go | 10 +- db/sql/traplocation_by_source_id.bob.sql | 2 +- db/sql/update_oauth_org.bob.go | 10 +- db/sql/update_oauth_org.bob.sql | 2 +- db/sql/user_by_username.bob.go | 10 +- db/sql/user_by_username.bob.sql | 2 +- go.mod | 3 +- go.sum | 2 + llm/client.go | 56 +++++- llm/log.go | 11 +- llm/openai.go | 56 ++---- main.go | 3 +- platform/district.go | 4 +- platform/text.go | 102 +++++++++-- public-report/image-upload.go | 6 +- public-report/pool.go | 6 +- public-report/quick.go | 6 +- public-report/status.go | 6 +- query.go | 4 +- sync/dash.go | 2 +- sync/utils.go | 6 +- 302 files changed, 1428 insertions(+), 1256 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index f2f8d0b5..df88736b 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -8,6 +8,8 @@ import ( "strconv" "strings" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" @@ -16,8 +18,6 @@ import ( "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/sm" "golang.org/x/crypto/bcrypt" ) diff --git a/background/arcgis.go b/background/arcgis.go index 0e7e166a..c2a1f474 100644 --- a/background/arcgis.go +++ b/background/arcgis.go @@ -22,6 +22,10 @@ import ( "github.com/Gleipnir-Technology/arcgis-go" "github.com/Gleipnir-Technology/arcgis-go/fieldseeker" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" @@ -33,10 +37,6 @@ import ( "github.com/alitto/pond/v2" "github.com/jackc/pgx/v5" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/dm" - "github.com/stephenafamo/bob/dialect/psql/sm" - "github.com/stephenafamo/bob/dialect/psql/um" ) var syncStatusByOrg map[int32]bool diff --git a/background/summary.go b/background/summary.go index dc6b701f..874c56c8 100644 --- a/background/summary.go +++ b/background/summary.go @@ -4,16 +4,16 @@ import ( "context" "fmt" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/im" "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/rs/zerolog/log" - "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/im" "github.com/uber/h3-go/v4" ) diff --git a/comms/email/db.go b/comms/email/db.go index 39d4dc59..d12e9316 100644 --- a/comms/email/db.go +++ b/comms/email/db.go @@ -10,13 +10,13 @@ import ( "strings" "time" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "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/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob/types/pgtypes" ) func convertToPGData(data map[string]string) pgtypes.HStore { diff --git a/comms/email/template.go b/comms/email/template.go index 424619f6..adb6446f 100644 --- a/comms/email/template.go +++ b/comms/email/template.go @@ -16,15 +16,15 @@ import ( templatetxt "text/template" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/um" ) //go:embed template/* diff --git a/comms/text/db.go b/comms/text/db.go index cd6c3d7d..47a58c0e 100644 --- a/comms/text/db.go +++ b/comms/text/db.go @@ -8,8 +8,8 @@ import ( "sort" "strings" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/Gleipnir-Technology/nidus-sync/db/enums" - "github.com/stephenafamo/bob/types/pgtypes" ) func convertToPGData(data map[string]string) pgtypes.HStore { diff --git a/db/connection.go b/db/connection.go index cc6924e4..7636dd4a 100644 --- a/db/connection.go +++ b/db/connection.go @@ -11,12 +11,12 @@ import ( //"github.com/georgysavva/scany/v2/pgxscan" //"github.com/jackc/pgx/v5" + "github.com/Gleipnir-Technology/bob" "github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/stdlib" _ "github.com/jackc/pgx/v5/stdlib" "github.com/pressly/goose/v3" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob" ) //go:embed migrations/*.sql diff --git a/db/dberrors/arcgis.user_.bob.go b/db/dberrors/arcgis.user_.bob.go index d17b1fef..ecdd2bf4 100644 --- a/db/dberrors/arcgis.user_.bob.go +++ b/db/dberrors/arcgis.user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/arcgis.user_privilege.bob.go b/db/dberrors/arcgis.user_privilege.bob.go index 7ff6253f..1539a638 100644 --- a/db/dberrors/arcgis.user_privilege.bob.go +++ b/db/dberrors/arcgis.user_privilege.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/bob_errors.bob.go b/db/dberrors/bob_errors.bob.go index ec4056c3..c236b601 100644 --- a/db/dberrors/bob_errors.bob.go +++ b/db/dberrors/bob_errors.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/comms.email_contact.bob.go b/db/dberrors/comms.email_contact.bob.go index 08568a2c..fab88251 100644 --- a/db/dberrors/comms.email_contact.bob.go +++ b/db/dberrors/comms.email_contact.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/comms.email_log.bob.go b/db/dberrors/comms.email_log.bob.go index 29f8370c..ed3d2ceb 100644 --- a/db/dberrors/comms.email_log.bob.go +++ b/db/dberrors/comms.email_log.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/comms.email_template.bob.go b/db/dberrors/comms.email_template.bob.go index d0fe76ed..d1cf47f5 100644 --- a/db/dberrors/comms.email_template.bob.go +++ b/db/dberrors/comms.email_template.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/comms.phone.bob.go b/db/dberrors/comms.phone.bob.go index ee7af99c..bf0cb617 100644 --- a/db/dberrors/comms.phone.bob.go +++ b/db/dberrors/comms.phone.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/comms.text_job.bob.go b/db/dberrors/comms.text_job.bob.go index cedcca8e..361daa57 100644 --- a/db/dberrors/comms.text_job.bob.go +++ b/db/dberrors/comms.text_job.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/comms.text_log.bob.go b/db/dberrors/comms.text_log.bob.go index 541e7178..11377aff 100644 --- a/db/dberrors/comms.text_log.bob.go +++ b/db/dberrors/comms.text_log.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.containerrelate.bob.go b/db/dberrors/fieldseeker.containerrelate.bob.go index 40d01312..e5a7e4f9 100644 --- a/db/dberrors/fieldseeker.containerrelate.bob.go +++ b/db/dberrors/fieldseeker.containerrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.fieldscoutinglog.bob.go b/db/dberrors/fieldseeker.fieldscoutinglog.bob.go index 3237f5db..43b8f9aa 100644 --- a/db/dberrors/fieldseeker.fieldscoutinglog.bob.go +++ b/db/dberrors/fieldseeker.fieldscoutinglog.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.habitatrelate.bob.go b/db/dberrors/fieldseeker.habitatrelate.bob.go index d00f431c..20046415 100644 --- a/db/dberrors/fieldseeker.habitatrelate.bob.go +++ b/db/dberrors/fieldseeker.habitatrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.inspectionsample.bob.go b/db/dberrors/fieldseeker.inspectionsample.bob.go index ce6725a5..2b247029 100644 --- a/db/dberrors/fieldseeker.inspectionsample.bob.go +++ b/db/dberrors/fieldseeker.inspectionsample.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.inspectionsampledetail.bob.go b/db/dberrors/fieldseeker.inspectionsampledetail.bob.go index 69d7b6e6..1bbb1035 100644 --- a/db/dberrors/fieldseeker.inspectionsampledetail.bob.go +++ b/db/dberrors/fieldseeker.inspectionsampledetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.linelocation.bob.go b/db/dberrors/fieldseeker.linelocation.bob.go index 5a135a53..742fbfa2 100644 --- a/db/dberrors/fieldseeker.linelocation.bob.go +++ b/db/dberrors/fieldseeker.linelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.locationtracking.bob.go b/db/dberrors/fieldseeker.locationtracking.bob.go index e68d1172..ccaa77c6 100644 --- a/db/dberrors/fieldseeker.locationtracking.bob.go +++ b/db/dberrors/fieldseeker.locationtracking.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.mosquitoinspection.bob.go b/db/dberrors/fieldseeker.mosquitoinspection.bob.go index f9b119a7..f456a2e0 100644 --- a/db/dberrors/fieldseeker.mosquitoinspection.bob.go +++ b/db/dberrors/fieldseeker.mosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.pointlocation.bob.go b/db/dberrors/fieldseeker.pointlocation.bob.go index 9078a437..831398ca 100644 --- a/db/dberrors/fieldseeker.pointlocation.bob.go +++ b/db/dberrors/fieldseeker.pointlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.polygonlocation.bob.go b/db/dberrors/fieldseeker.polygonlocation.bob.go index 250e5de2..ade05089 100644 --- a/db/dberrors/fieldseeker.polygonlocation.bob.go +++ b/db/dberrors/fieldseeker.polygonlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.pool.bob.go b/db/dberrors/fieldseeker.pool.bob.go index 7bb30e90..fd1eb1d9 100644 --- a/db/dberrors/fieldseeker.pool.bob.go +++ b/db/dberrors/fieldseeker.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.pooldetail.bob.go b/db/dberrors/fieldseeker.pooldetail.bob.go index 1423b462..fb713106 100644 --- a/db/dberrors/fieldseeker.pooldetail.bob.go +++ b/db/dberrors/fieldseeker.pooldetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.proposedtreatmentarea.bob.go b/db/dberrors/fieldseeker.proposedtreatmentarea.bob.go index 991a4d46..2dfb4338 100644 --- a/db/dberrors/fieldseeker.proposedtreatmentarea.bob.go +++ b/db/dberrors/fieldseeker.proposedtreatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.qamosquitoinspection.bob.go b/db/dberrors/fieldseeker.qamosquitoinspection.bob.go index 5ae42a56..9176d95e 100644 --- a/db/dberrors/fieldseeker.qamosquitoinspection.bob.go +++ b/db/dberrors/fieldseeker.qamosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.rodentlocation.bob.go b/db/dberrors/fieldseeker.rodentlocation.bob.go index d865425f..7513df7a 100644 --- a/db/dberrors/fieldseeker.rodentlocation.bob.go +++ b/db/dberrors/fieldseeker.rodentlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.samplecollection.bob.go b/db/dberrors/fieldseeker.samplecollection.bob.go index 6f0de912..874a33d2 100644 --- a/db/dberrors/fieldseeker.samplecollection.bob.go +++ b/db/dberrors/fieldseeker.samplecollection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.samplelocation.bob.go b/db/dberrors/fieldseeker.samplelocation.bob.go index 35a96bc1..1c1d72d9 100644 --- a/db/dberrors/fieldseeker.samplelocation.bob.go +++ b/db/dberrors/fieldseeker.samplelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.servicerequest.bob.go b/db/dberrors/fieldseeker.servicerequest.bob.go index 5e8d1020..ceaa44d6 100644 --- a/db/dberrors/fieldseeker.servicerequest.bob.go +++ b/db/dberrors/fieldseeker.servicerequest.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.speciesabundance.bob.go b/db/dberrors/fieldseeker.speciesabundance.bob.go index 922bcc2a..e9bc8e6d 100644 --- a/db/dberrors/fieldseeker.speciesabundance.bob.go +++ b/db/dberrors/fieldseeker.speciesabundance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.stormdrain.bob.go b/db/dberrors/fieldseeker.stormdrain.bob.go index 689fe28d..1fc22909 100644 --- a/db/dberrors/fieldseeker.stormdrain.bob.go +++ b/db/dberrors/fieldseeker.stormdrain.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.timecard.bob.go b/db/dberrors/fieldseeker.timecard.bob.go index bf09f766..abc16c3d 100644 --- a/db/dberrors/fieldseeker.timecard.bob.go +++ b/db/dberrors/fieldseeker.timecard.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.trapdata.bob.go b/db/dberrors/fieldseeker.trapdata.bob.go index 7ffe6603..312ec967 100644 --- a/db/dberrors/fieldseeker.trapdata.bob.go +++ b/db/dberrors/fieldseeker.trapdata.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.traplocation.bob.go b/db/dberrors/fieldseeker.traplocation.bob.go index de57fa80..45ac70f1 100644 --- a/db/dberrors/fieldseeker.traplocation.bob.go +++ b/db/dberrors/fieldseeker.traplocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.treatment.bob.go b/db/dberrors/fieldseeker.treatment.bob.go index 40e0bddf..768ec5c0 100644 --- a/db/dberrors/fieldseeker.treatment.bob.go +++ b/db/dberrors/fieldseeker.treatment.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.treatmentarea.bob.go b/db/dberrors/fieldseeker.treatmentarea.bob.go index c7ff263b..3bf5ff44 100644 --- a/db/dberrors/fieldseeker.treatmentarea.bob.go +++ b/db/dberrors/fieldseeker.treatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.zones.bob.go b/db/dberrors/fieldseeker.zones.bob.go index bd2fd6e6..6ba01fe8 100644 --- a/db/dberrors/fieldseeker.zones.bob.go +++ b/db/dberrors/fieldseeker.zones.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker.zones2.bob.go b/db/dberrors/fieldseeker.zones2.bob.go index 644248d7..eb77f353 100644 --- a/db/dberrors/fieldseeker.zones2.bob.go +++ b/db/dberrors/fieldseeker.zones2.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/fieldseeker_sync.bob.go b/db/dberrors/fieldseeker_sync.bob.go index ac899d66..5f412382 100644 --- a/db/dberrors/fieldseeker_sync.bob.go +++ b/db/dberrors/fieldseeker_sync.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/goose_db_version.bob.go b/db/dberrors/goose_db_version.bob.go index af370d75..05367ee2 100644 --- a/db/dberrors/goose_db_version.bob.go +++ b/db/dberrors/goose_db_version.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/h3_aggregation.bob.go b/db/dberrors/h3_aggregation.bob.go index 80c555b1..e3b34027 100644 --- a/db/dberrors/h3_aggregation.bob.go +++ b/db/dberrors/h3_aggregation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/import.district.bob.go b/db/dberrors/import.district.bob.go index a69509a5..61d0df39 100644 --- a/db/dberrors/import.district.bob.go +++ b/db/dberrors/import.district.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_audio.bob.go b/db/dberrors/note_audio.bob.go index 51621255..da314876 100644 --- a/db/dberrors/note_audio.bob.go +++ b/db/dberrors/note_audio.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_audio_breadcrumb.bob.go b/db/dberrors/note_audio_breadcrumb.bob.go index c0db7c25..4b642bf5 100644 --- a/db/dberrors/note_audio_breadcrumb.bob.go +++ b/db/dberrors/note_audio_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_audio_data.bob.go b/db/dberrors/note_audio_data.bob.go index bd4b07c1..a04b251a 100644 --- a/db/dberrors/note_audio_data.bob.go +++ b/db/dberrors/note_audio_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_image.bob.go b/db/dberrors/note_image.bob.go index 8090f936..bc6f8f40 100644 --- a/db/dberrors/note_image.bob.go +++ b/db/dberrors/note_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_image_breadcrumb.bob.go b/db/dberrors/note_image_breadcrumb.bob.go index 7e62d2ad..bc5fef56 100644 --- a/db/dberrors/note_image_breadcrumb.bob.go +++ b/db/dberrors/note_image_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/note_image_data.bob.go b/db/dberrors/note_image_data.bob.go index 91c1a89a..62cfa026 100644 --- a/db/dberrors/note_image_data.bob.go +++ b/db/dberrors/note_image_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/notification.bob.go b/db/dberrors/notification.bob.go index d4f2841a..e32540ef 100644 --- a/db/dberrors/notification.bob.go +++ b/db/dberrors/notification.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/oauth_token.bob.go b/db/dberrors/oauth_token.bob.go index 050b1a47..b1b15e6b 100644 --- a/db/dberrors/oauth_token.bob.go +++ b/db/dberrors/oauth_token.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/organization.bob.go b/db/dberrors/organization.bob.go index d3199621..d01bcc41 100644 --- a/db/dberrors/organization.bob.go +++ b/db/dberrors/organization.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.image.bob.go b/db/dberrors/publicreport.image.bob.go index 4986333c..4bc0270a 100644 --- a/db/dberrors/publicreport.image.bob.go +++ b/db/dberrors/publicreport.image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.image_exif.bob.go b/db/dberrors/publicreport.image_exif.bob.go index 28ab3ada..41de3c31 100644 --- a/db/dberrors/publicreport.image_exif.bob.go +++ b/db/dberrors/publicreport.image_exif.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.nuisance.bob.go b/db/dberrors/publicreport.nuisance.bob.go index c1fcebb0..6c0fd841 100644 --- a/db/dberrors/publicreport.nuisance.bob.go +++ b/db/dberrors/publicreport.nuisance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.pool.bob.go b/db/dberrors/publicreport.pool.bob.go index aefb420d..c774c014 100644 --- a/db/dberrors/publicreport.pool.bob.go +++ b/db/dberrors/publicreport.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.pool_image.bob.go b/db/dberrors/publicreport.pool_image.bob.go index 5317ef9f..8f5c66e2 100644 --- a/db/dberrors/publicreport.pool_image.bob.go +++ b/db/dberrors/publicreport.pool_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.quick.bob.go b/db/dberrors/publicreport.quick.bob.go index d51ed705..e00cd069 100644 --- a/db/dberrors/publicreport.quick.bob.go +++ b/db/dberrors/publicreport.quick.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/publicreport.quick_image.bob.go b/db/dberrors/publicreport.quick_image.bob.go index d0bb4097..d66ae0a3 100644 --- a/db/dberrors/publicreport.quick_image.bob.go +++ b/db/dberrors/publicreport.quick_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/sessions.bob.go b/db/dberrors/sessions.bob.go index e271ce05..c27ab6b7 100644 --- a/db/dberrors/sessions.bob.go +++ b/db/dberrors/sessions.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/spatial_ref_sys.bob.go b/db/dberrors/spatial_ref_sys.bob.go index 195ac12d..b3a777fd 100644 --- a/db/dberrors/spatial_ref_sys.bob.go +++ b/db/dberrors/spatial_ref_sys.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dberrors/user_.bob.go b/db/dberrors/user_.bob.go index 7f08ae62..f1d6fbb1 100644 --- a/db/dberrors/user_.bob.go +++ b/db/dberrors/user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dberrors diff --git a/db/dbinfo/arcgis.user_.bob.go b/db/dbinfo/arcgis.user_.bob.go index 772f92db..0e70ba47 100644 --- a/db/dbinfo/arcgis.user_.bob.go +++ b/db/dbinfo/arcgis.user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/arcgis.user_privilege.bob.go b/db/dbinfo/arcgis.user_privilege.bob.go index 88e2785f..56de66b9 100644 --- a/db/dbinfo/arcgis.user_privilege.bob.go +++ b/db/dbinfo/arcgis.user_privilege.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/bob_types.bob.go b/db/dbinfo/bob_types.bob.go index 2ece4dab..0337bf04 100644 --- a/db/dbinfo/bob_types.bob.go +++ b/db/dbinfo/bob_types.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/comms.email_contact.bob.go b/db/dbinfo/comms.email_contact.bob.go index 6dff7493..fdb1bde5 100644 --- a/db/dbinfo/comms.email_contact.bob.go +++ b/db/dbinfo/comms.email_contact.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/comms.email_log.bob.go b/db/dbinfo/comms.email_log.bob.go index 09a41454..09417e10 100644 --- a/db/dbinfo/comms.email_log.bob.go +++ b/db/dbinfo/comms.email_log.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/comms.email_template.bob.go b/db/dbinfo/comms.email_template.bob.go index b299e2e7..a5615595 100644 --- a/db/dbinfo/comms.email_template.bob.go +++ b/db/dbinfo/comms.email_template.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/comms.phone.bob.go b/db/dbinfo/comms.phone.bob.go index be6039b2..d769ef1d 100644 --- a/db/dbinfo/comms.phone.bob.go +++ b/db/dbinfo/comms.phone.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/comms.text_job.bob.go b/db/dbinfo/comms.text_job.bob.go index aa87ea6f..cd2dfabd 100644 --- a/db/dbinfo/comms.text_job.bob.go +++ b/db/dbinfo/comms.text_job.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/comms.text_log.bob.go b/db/dbinfo/comms.text_log.bob.go index d85d75d6..f01f10bb 100644 --- a/db/dbinfo/comms.text_log.bob.go +++ b/db/dbinfo/comms.text_log.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo @@ -96,6 +96,15 @@ var CommsTextLogs = Table[ Generated: false, AutoIncr: false, }, + IsVisibleToLLM: column{ + Name: "is_visible_to_llm", + DBType: "boolean", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, }, Indexes: commsTextLogIndexes{ TextLogPkey: index{ @@ -170,20 +179,21 @@ var CommsTextLogs = Table[ } type commsTextLogColumns struct { - Content column - Created column - Destination column - ID column - IsWelcome column - Origin column - Source column - TwilioSid column - TwilioStatus column + Content column + Created column + Destination column + ID column + IsWelcome column + Origin column + Source column + TwilioSid column + TwilioStatus column + IsVisibleToLLM column } func (c commsTextLogColumns) AsSlice() []column { return []column{ - c.Content, c.Created, c.Destination, c.ID, c.IsWelcome, c.Origin, c.Source, c.TwilioSid, c.TwilioStatus, + c.Content, c.Created, c.Destination, c.ID, c.IsWelcome, c.Origin, c.Source, c.TwilioSid, c.TwilioStatus, c.IsVisibleToLLM, } } diff --git a/db/dbinfo/fieldseeker.containerrelate.bob.go b/db/dbinfo/fieldseeker.containerrelate.bob.go index 9b293435..c41dbb5a 100644 --- a/db/dbinfo/fieldseeker.containerrelate.bob.go +++ b/db/dbinfo/fieldseeker.containerrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.fieldscoutinglog.bob.go b/db/dbinfo/fieldseeker.fieldscoutinglog.bob.go index 7a8b2e88..21b7ae92 100644 --- a/db/dbinfo/fieldseeker.fieldscoutinglog.bob.go +++ b/db/dbinfo/fieldseeker.fieldscoutinglog.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.habitatrelate.bob.go b/db/dbinfo/fieldseeker.habitatrelate.bob.go index 5b7eef2f..a2229e90 100644 --- a/db/dbinfo/fieldseeker.habitatrelate.bob.go +++ b/db/dbinfo/fieldseeker.habitatrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.inspectionsample.bob.go b/db/dbinfo/fieldseeker.inspectionsample.bob.go index 7222c516..e7e34fca 100644 --- a/db/dbinfo/fieldseeker.inspectionsample.bob.go +++ b/db/dbinfo/fieldseeker.inspectionsample.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.inspectionsampledetail.bob.go b/db/dbinfo/fieldseeker.inspectionsampledetail.bob.go index 9bfee61f..8b32c98b 100644 --- a/db/dbinfo/fieldseeker.inspectionsampledetail.bob.go +++ b/db/dbinfo/fieldseeker.inspectionsampledetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.linelocation.bob.go b/db/dbinfo/fieldseeker.linelocation.bob.go index 426609fa..81cd0549 100644 --- a/db/dbinfo/fieldseeker.linelocation.bob.go +++ b/db/dbinfo/fieldseeker.linelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.locationtracking.bob.go b/db/dbinfo/fieldseeker.locationtracking.bob.go index fa3d2922..091413c6 100644 --- a/db/dbinfo/fieldseeker.locationtracking.bob.go +++ b/db/dbinfo/fieldseeker.locationtracking.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.mosquitoinspection.bob.go b/db/dbinfo/fieldseeker.mosquitoinspection.bob.go index 333ae452..aadf5c34 100644 --- a/db/dbinfo/fieldseeker.mosquitoinspection.bob.go +++ b/db/dbinfo/fieldseeker.mosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.pointlocation.bob.go b/db/dbinfo/fieldseeker.pointlocation.bob.go index 5f65e44a..6504254d 100644 --- a/db/dbinfo/fieldseeker.pointlocation.bob.go +++ b/db/dbinfo/fieldseeker.pointlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.polygonlocation.bob.go b/db/dbinfo/fieldseeker.polygonlocation.bob.go index e200e6c7..471fb5ca 100644 --- a/db/dbinfo/fieldseeker.polygonlocation.bob.go +++ b/db/dbinfo/fieldseeker.polygonlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.pool.bob.go b/db/dbinfo/fieldseeker.pool.bob.go index 9f359745..d92b0a47 100644 --- a/db/dbinfo/fieldseeker.pool.bob.go +++ b/db/dbinfo/fieldseeker.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.pooldetail.bob.go b/db/dbinfo/fieldseeker.pooldetail.bob.go index b3a9e700..930442a3 100644 --- a/db/dbinfo/fieldseeker.pooldetail.bob.go +++ b/db/dbinfo/fieldseeker.pooldetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.proposedtreatmentarea.bob.go b/db/dbinfo/fieldseeker.proposedtreatmentarea.bob.go index f85288c6..518e08c4 100644 --- a/db/dbinfo/fieldseeker.proposedtreatmentarea.bob.go +++ b/db/dbinfo/fieldseeker.proposedtreatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.qamosquitoinspection.bob.go b/db/dbinfo/fieldseeker.qamosquitoinspection.bob.go index 76da7dd7..b34a6a93 100644 --- a/db/dbinfo/fieldseeker.qamosquitoinspection.bob.go +++ b/db/dbinfo/fieldseeker.qamosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.rodentlocation.bob.go b/db/dbinfo/fieldseeker.rodentlocation.bob.go index 8713d101..7e7e16f5 100644 --- a/db/dbinfo/fieldseeker.rodentlocation.bob.go +++ b/db/dbinfo/fieldseeker.rodentlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.samplecollection.bob.go b/db/dbinfo/fieldseeker.samplecollection.bob.go index a205203b..c91d1b83 100644 --- a/db/dbinfo/fieldseeker.samplecollection.bob.go +++ b/db/dbinfo/fieldseeker.samplecollection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.samplelocation.bob.go b/db/dbinfo/fieldseeker.samplelocation.bob.go index 1f63b1c4..1d2aad35 100644 --- a/db/dbinfo/fieldseeker.samplelocation.bob.go +++ b/db/dbinfo/fieldseeker.samplelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.servicerequest.bob.go b/db/dbinfo/fieldseeker.servicerequest.bob.go index 30de6afe..908cfc92 100644 --- a/db/dbinfo/fieldseeker.servicerequest.bob.go +++ b/db/dbinfo/fieldseeker.servicerequest.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.speciesabundance.bob.go b/db/dbinfo/fieldseeker.speciesabundance.bob.go index 7431468f..a3e2c508 100644 --- a/db/dbinfo/fieldseeker.speciesabundance.bob.go +++ b/db/dbinfo/fieldseeker.speciesabundance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.stormdrain.bob.go b/db/dbinfo/fieldseeker.stormdrain.bob.go index 412832a3..f3f68c92 100644 --- a/db/dbinfo/fieldseeker.stormdrain.bob.go +++ b/db/dbinfo/fieldseeker.stormdrain.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.timecard.bob.go b/db/dbinfo/fieldseeker.timecard.bob.go index 65b6a792..fca3c1fb 100644 --- a/db/dbinfo/fieldseeker.timecard.bob.go +++ b/db/dbinfo/fieldseeker.timecard.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.trapdata.bob.go b/db/dbinfo/fieldseeker.trapdata.bob.go index 2c7c523f..4588e275 100644 --- a/db/dbinfo/fieldseeker.trapdata.bob.go +++ b/db/dbinfo/fieldseeker.trapdata.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.traplocation.bob.go b/db/dbinfo/fieldseeker.traplocation.bob.go index 27aa9951..a9bf2bf7 100644 --- a/db/dbinfo/fieldseeker.traplocation.bob.go +++ b/db/dbinfo/fieldseeker.traplocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.treatment.bob.go b/db/dbinfo/fieldseeker.treatment.bob.go index b6a0075e..0e4aec16 100644 --- a/db/dbinfo/fieldseeker.treatment.bob.go +++ b/db/dbinfo/fieldseeker.treatment.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.treatmentarea.bob.go b/db/dbinfo/fieldseeker.treatmentarea.bob.go index d9df5b58..7141b8b2 100644 --- a/db/dbinfo/fieldseeker.treatmentarea.bob.go +++ b/db/dbinfo/fieldseeker.treatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.zones.bob.go b/db/dbinfo/fieldseeker.zones.bob.go index 1cd771cd..603514ae 100644 --- a/db/dbinfo/fieldseeker.zones.bob.go +++ b/db/dbinfo/fieldseeker.zones.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker.zones2.bob.go b/db/dbinfo/fieldseeker.zones2.bob.go index 7a55e0e8..eea38196 100644 --- a/db/dbinfo/fieldseeker.zones2.bob.go +++ b/db/dbinfo/fieldseeker.zones2.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/fieldseeker_sync.bob.go b/db/dbinfo/fieldseeker_sync.bob.go index f0d18f63..e5265867 100644 --- a/db/dbinfo/fieldseeker_sync.bob.go +++ b/db/dbinfo/fieldseeker_sync.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/geography_columns.bob.go b/db/dbinfo/geography_columns.bob.go index fede3218..5e450b0d 100644 --- a/db/dbinfo/geography_columns.bob.go +++ b/db/dbinfo/geography_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/geometry_columns.bob.go b/db/dbinfo/geometry_columns.bob.go index 2292ee9a..b28a0ded 100644 --- a/db/dbinfo/geometry_columns.bob.go +++ b/db/dbinfo/geometry_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/goose_db_version.bob.go b/db/dbinfo/goose_db_version.bob.go index 9bda0126..3b4889fe 100644 --- a/db/dbinfo/goose_db_version.bob.go +++ b/db/dbinfo/goose_db_version.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/h3_aggregation.bob.go b/db/dbinfo/h3_aggregation.bob.go index 65bd6414..e2c5ae90 100644 --- a/db/dbinfo/h3_aggregation.bob.go +++ b/db/dbinfo/h3_aggregation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/import.district.bob.go b/db/dbinfo/import.district.bob.go index e51c4fcb..cefc73ed 100644 --- a/db/dbinfo/import.district.bob.go +++ b/db/dbinfo/import.district.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_audio.bob.go b/db/dbinfo/note_audio.bob.go index 4725221d..3046131f 100644 --- a/db/dbinfo/note_audio.bob.go +++ b/db/dbinfo/note_audio.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_audio_breadcrumb.bob.go b/db/dbinfo/note_audio_breadcrumb.bob.go index 0f6e5248..b71e6b31 100644 --- a/db/dbinfo/note_audio_breadcrumb.bob.go +++ b/db/dbinfo/note_audio_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_audio_data.bob.go b/db/dbinfo/note_audio_data.bob.go index 8e3dae3f..cf7b22d2 100644 --- a/db/dbinfo/note_audio_data.bob.go +++ b/db/dbinfo/note_audio_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_image.bob.go b/db/dbinfo/note_image.bob.go index 89a0dccd..2a48e71a 100644 --- a/db/dbinfo/note_image.bob.go +++ b/db/dbinfo/note_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_image_breadcrumb.bob.go b/db/dbinfo/note_image_breadcrumb.bob.go index 7824d997..8492f301 100644 --- a/db/dbinfo/note_image_breadcrumb.bob.go +++ b/db/dbinfo/note_image_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/note_image_data.bob.go b/db/dbinfo/note_image_data.bob.go index dade46b3..53bbeb23 100644 --- a/db/dbinfo/note_image_data.bob.go +++ b/db/dbinfo/note_image_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/notification.bob.go b/db/dbinfo/notification.bob.go index b405ed17..39c98e12 100644 --- a/db/dbinfo/notification.bob.go +++ b/db/dbinfo/notification.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/oauth_token.bob.go b/db/dbinfo/oauth_token.bob.go index e7ae9dcf..3101f04e 100644 --- a/db/dbinfo/oauth_token.bob.go +++ b/db/dbinfo/oauth_token.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/organization.bob.go b/db/dbinfo/organization.bob.go index da9cf320..8248f9fc 100644 --- a/db/dbinfo/organization.bob.go +++ b/db/dbinfo/organization.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.image.bob.go b/db/dbinfo/publicreport.image.bob.go index 231da24f..987af228 100644 --- a/db/dbinfo/publicreport.image.bob.go +++ b/db/dbinfo/publicreport.image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.image_exif.bob.go b/db/dbinfo/publicreport.image_exif.bob.go index 97aa27b7..ab70ef12 100644 --- a/db/dbinfo/publicreport.image_exif.bob.go +++ b/db/dbinfo/publicreport.image_exif.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.nuisance.bob.go b/db/dbinfo/publicreport.nuisance.bob.go index 20debe36..e8d1cad6 100644 --- a/db/dbinfo/publicreport.nuisance.bob.go +++ b/db/dbinfo/publicreport.nuisance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.pool.bob.go b/db/dbinfo/publicreport.pool.bob.go index a749fdba..dc985a82 100644 --- a/db/dbinfo/publicreport.pool.bob.go +++ b/db/dbinfo/publicreport.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.pool_image.bob.go b/db/dbinfo/publicreport.pool_image.bob.go index cb44e9b9..3d39ed61 100644 --- a/db/dbinfo/publicreport.pool_image.bob.go +++ b/db/dbinfo/publicreport.pool_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.quick.bob.go b/db/dbinfo/publicreport.quick.bob.go index a986b5b8..74d3d8ec 100644 --- a/db/dbinfo/publicreport.quick.bob.go +++ b/db/dbinfo/publicreport.quick.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.quick_image.bob.go b/db/dbinfo/publicreport.quick_image.bob.go index d311615b..1615d4ee 100644 --- a/db/dbinfo/publicreport.quick_image.bob.go +++ b/db/dbinfo/publicreport.quick_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/publicreport.report_location.bob.go b/db/dbinfo/publicreport.report_location.bob.go index 92fa50fe..d458deff 100644 --- a/db/dbinfo/publicreport.report_location.bob.go +++ b/db/dbinfo/publicreport.report_location.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/raster_columns.bob.go b/db/dbinfo/raster_columns.bob.go index a7d56223..e81669dd 100644 --- a/db/dbinfo/raster_columns.bob.go +++ b/db/dbinfo/raster_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/raster_overviews.bob.go b/db/dbinfo/raster_overviews.bob.go index 7199817b..1c1a6e9e 100644 --- a/db/dbinfo/raster_overviews.bob.go +++ b/db/dbinfo/raster_overviews.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/sessions.bob.go b/db/dbinfo/sessions.bob.go index 4f66b5ce..c18de5c4 100644 --- a/db/dbinfo/sessions.bob.go +++ b/db/dbinfo/sessions.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/spatial_ref_sys.bob.go b/db/dbinfo/spatial_ref_sys.bob.go index 78b5a363..a28829c4 100644 --- a/db/dbinfo/spatial_ref_sys.bob.go +++ b/db/dbinfo/spatial_ref_sys.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/dbinfo/user_.bob.go b/db/dbinfo/user_.bob.go index 1c1f25ef..44fdd868 100644 --- a/db/dbinfo/user_.bob.go +++ b/db/dbinfo/user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package dbinfo diff --git a/db/enums/enums.bob.go b/db/enums/enums.bob.go index cadb022f..66d39176 100644 --- a/db/enums/enums.bob.go +++ b/db/enums/enums.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package enums @@ -344,11 +344,12 @@ func (e *CommsTextjobtype) Scan(value any) error { // Enum values for CommsTextorigin const ( - CommsTextoriginDistrict CommsTextorigin = "district" - CommsTextoriginLLM CommsTextorigin = "llm" - CommsTextoriginWebsiteAction CommsTextorigin = "website-action" - CommsTextoriginCustomer CommsTextorigin = "customer" - CommsTextoriginReiteration CommsTextorigin = "reiteration" + CommsTextoriginDistrict CommsTextorigin = "district" + CommsTextoriginLLM CommsTextorigin = "llm" + CommsTextoriginWebsiteAction CommsTextorigin = "website-action" + CommsTextoriginCustomer CommsTextorigin = "customer" + CommsTextoriginReiteration CommsTextorigin = "reiteration" + CommsTextoriginCommandResponse CommsTextorigin = "command-response" ) func AllCommsTextorigin() []CommsTextorigin { @@ -358,6 +359,7 @@ func AllCommsTextorigin() []CommsTextorigin { CommsTextoriginWebsiteAction, CommsTextoriginCustomer, CommsTextoriginReiteration, + CommsTextoriginCommandResponse, } } @@ -373,7 +375,8 @@ func (e CommsTextorigin) Valid() bool { CommsTextoriginLLM, CommsTextoriginWebsiteAction, CommsTextoriginCustomer, - CommsTextoriginReiteration: + CommsTextoriginReiteration, + CommsTextoriginCommandResponse: return true default: return false diff --git a/db/factory/arcgis.user_.bob.go b/db/factory/arcgis.user_.bob.go index 43969366..51851826 100644 --- a/db/factory/arcgis.user_.bob.go +++ b/db/factory/arcgis.user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,10 +8,10 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type ArcgisUserMod interface { diff --git a/db/factory/arcgis.user_privilege.bob.go b/db/factory/arcgis.user_privilege.bob.go index 37ed5e39..0fe57443 100644 --- a/db/factory/arcgis.user_privilege.bob.go +++ b/db/factory/arcgis.user_privilege.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -7,10 +7,10 @@ import ( "context" "testing" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type ArcgisUserPrivilegeMod interface { diff --git a/db/factory/bobfactory_context.bob.go b/db/factory/bobfactory_context.bob.go index 4258ed4f..0643830a 100644 --- a/db/factory/bobfactory_context.bob.go +++ b/db/factory/bobfactory_context.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index f8f5ca98..cd749e18 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,14 +8,14 @@ import ( "encoding/json" "time" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/google/uuid" "github.com/lib/pq" "github.com/shopspring/decimal" - "github.com/stephenafamo/bob/types" - "github.com/stephenafamo/bob/types/pgtypes" ) type Factory struct { @@ -370,6 +370,7 @@ func (f *Factory) FromExistingCommsTextLog(m *models.CommsTextLog) *CommsTextLog o.Source = func() string { return m.Source } o.TwilioSid = func() null.Val[string] { return m.TwilioSid } o.TwilioStatus = func() string { return m.TwilioStatus } + o.IsVisibleToLLM = func() bool { return m.IsVisibleToLLM } ctx := context.Background() if m.R.DestinationPhone != nil { diff --git a/db/factory/bobfactory_random.bob.go b/db/factory/bobfactory_random.bob.go index 28ceac86..fe4997bf 100644 --- a/db/factory/bobfactory_random.bob.go +++ b/db/factory/bobfactory_random.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -13,13 +13,13 @@ import ( "strings" "time" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/google/uuid" "github.com/jaswdr/faker/v2" "github.com/lib/pq" "github.com/shopspring/decimal" - "github.com/stephenafamo/bob/types" - "github.com/stephenafamo/bob/types/pgtypes" ) var defaultFaker = faker.New() diff --git a/db/factory/comms.email_contact.bob.go b/db/factory/comms.email_contact.bob.go index 11a7fe1d..a0ca1038 100644 --- a/db/factory/comms.email_contact.bob.go +++ b/db/factory/comms.email_contact.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -7,10 +7,10 @@ import ( "context" "testing" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type CommsEmailContactMod interface { diff --git a/db/factory/comms.email_log.bob.go b/db/factory/comms.email_log.bob.go index 13d3e040..04e52de6 100644 --- a/db/factory/comms.email_log.bob.go +++ b/db/factory/comms.email_log.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,14 +8,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types/pgtypes" ) type CommsEmailLogMod interface { diff --git a/db/factory/comms.email_template.bob.go b/db/factory/comms.email_template.bob.go index dbfdd1f7..a40c2f68 100644 --- a/db/factory/comms.email_template.bob.go +++ b/db/factory/comms.email_template.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,13 +8,13 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type CommsEmailTemplateMod interface { diff --git a/db/factory/comms.phone.bob.go b/db/factory/comms.phone.bob.go index abcd554d..5c5d9aec 100644 --- a/db/factory/comms.phone.bob.go +++ b/db/factory/comms.phone.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -7,12 +7,12 @@ import ( "context" "testing" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type CommsPhoneMod interface { diff --git a/db/factory/comms.text_job.bob.go b/db/factory/comms.text_job.bob.go index 6d16388f..1c62de8e 100644 --- a/db/factory/comms.text_job.bob.go +++ b/db/factory/comms.text_job.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,11 +8,11 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type CommsTextJobMod interface { diff --git a/db/factory/comms.text_log.bob.go b/db/factory/comms.text_log.bob.go index 5edb4b92..b25a4a43 100644 --- a/db/factory/comms.text_log.bob.go +++ b/db/factory/comms.text_log.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,13 +8,13 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type CommsTextLogMod interface { @@ -38,15 +38,16 @@ func (mods CommsTextLogModSlice) Apply(ctx context.Context, n *CommsTextLogTempl // CommsTextLogTemplate is an object representing the database table. // all columns are optional and should be set by mods type CommsTextLogTemplate struct { - Content func() string - Created func() time.Time - Destination func() string - ID func() int32 - IsWelcome func() bool - Origin func() enums.CommsTextorigin - Source func() string - TwilioSid func() null.Val[string] - TwilioStatus func() string + Content func() string + Created func() time.Time + Destination func() string + ID func() int32 + IsWelcome func() bool + Origin func() enums.CommsTextorigin + Source func() string + TwilioSid func() null.Val[string] + TwilioStatus func() string + IsVisibleToLLM func() bool r commsTextLogR f *Factory @@ -132,6 +133,10 @@ func (o CommsTextLogTemplate) BuildSetter() *models.CommsTextLogSetter { val := o.TwilioStatus() m.TwilioStatus = omit.From(val) } + if o.IsVisibleToLLM != nil { + val := o.IsVisibleToLLM() + m.IsVisibleToLLM = omit.From(val) + } return m } @@ -181,6 +186,9 @@ func (o CommsTextLogTemplate) Build() *models.CommsTextLog { if o.TwilioStatus != nil { m.TwilioStatus = o.TwilioStatus() } + if o.IsVisibleToLLM != nil { + m.IsVisibleToLLM = o.IsVisibleToLLM() + } o.setModelRels(m) @@ -229,6 +237,10 @@ func ensureCreatableCommsTextLog(m *models.CommsTextLogSetter) { val := random_string(nil) m.TwilioStatus = omit.From(val) } + if !(m.IsVisibleToLLM.IsValue()) { + val := random_bool(nil) + m.IsVisibleToLLM = omit.From(val) + } } // insertOptRels creates and inserts any optional the relationships on *models.CommsTextLog @@ -375,6 +387,7 @@ func (m commsTextLogMods) RandomizeAllColumns(f *faker.Faker) CommsTextLogMod { CommsTextLogMods.RandomSource(f), CommsTextLogMods.RandomTwilioSid(f), CommsTextLogMods.RandomTwilioStatus(f), + CommsTextLogMods.RandomIsVisibleToLLM(f), } } @@ -679,6 +692,37 @@ func (m commsTextLogMods) RandomTwilioStatus(f *faker.Faker) CommsTextLogMod { }) } +// Set the model columns to this value +func (m commsTextLogMods) IsVisibleToLLM(val bool) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.IsVisibleToLLM = func() bool { return val } + }) +} + +// Set the Column from the function +func (m commsTextLogMods) IsVisibleToLLMFunc(f func() bool) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.IsVisibleToLLM = f + }) +} + +// Clear any values for the column +func (m commsTextLogMods) UnsetIsVisibleToLLM() CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.IsVisibleToLLM = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m commsTextLogMods) RandomIsVisibleToLLM(f *faker.Faker) CommsTextLogMod { + return CommsTextLogModFunc(func(_ context.Context, o *CommsTextLogTemplate) { + o.IsVisibleToLLM = func() bool { + return random_bool(f) + } + }) +} + func (m commsTextLogMods) WithParentsCascading() CommsTextLogMod { return CommsTextLogModFunc(func(ctx context.Context, o *CommsTextLogTemplate) { if isDone, _ := commsTextLogWithParentsCascadingCtx.Value(ctx); isDone { diff --git a/db/factory/fieldseeker.containerrelate.bob.go b/db/factory/fieldseeker.containerrelate.bob.go index 31dbc2be..4cead88c 100644 --- a/db/factory/fieldseeker.containerrelate.bob.go +++ b/db/factory/fieldseeker.containerrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerContainerrelateMod interface { diff --git a/db/factory/fieldseeker.fieldscoutinglog.bob.go b/db/factory/fieldseeker.fieldscoutinglog.bob.go index ac7f0351..ebc104ac 100644 --- a/db/factory/fieldseeker.fieldscoutinglog.bob.go +++ b/db/factory/fieldseeker.fieldscoutinglog.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerFieldscoutinglogMod interface { diff --git a/db/factory/fieldseeker.habitatrelate.bob.go b/db/factory/fieldseeker.habitatrelate.bob.go index 863ee087..7081f663 100644 --- a/db/factory/fieldseeker.habitatrelate.bob.go +++ b/db/factory/fieldseeker.habitatrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerHabitatrelateMod interface { diff --git a/db/factory/fieldseeker.inspectionsample.bob.go b/db/factory/fieldseeker.inspectionsample.bob.go index 75993f79..a735a71d 100644 --- a/db/factory/fieldseeker.inspectionsample.bob.go +++ b/db/factory/fieldseeker.inspectionsample.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerInspectionsampleMod interface { diff --git a/db/factory/fieldseeker.inspectionsampledetail.bob.go b/db/factory/fieldseeker.inspectionsampledetail.bob.go index 53e60c10..827b0942 100644 --- a/db/factory/fieldseeker.inspectionsampledetail.bob.go +++ b/db/factory/fieldseeker.inspectionsampledetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerInspectionsampledetailMod interface { diff --git a/db/factory/fieldseeker.linelocation.bob.go b/db/factory/fieldseeker.linelocation.bob.go index 4cd24b90..cf5b71c9 100644 --- a/db/factory/fieldseeker.linelocation.bob.go +++ b/db/factory/fieldseeker.linelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerLinelocationMod interface { diff --git a/db/factory/fieldseeker.locationtracking.bob.go b/db/factory/fieldseeker.locationtracking.bob.go index a258b8a1..5cafc232 100644 --- a/db/factory/fieldseeker.locationtracking.bob.go +++ b/db/factory/fieldseeker.locationtracking.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerLocationtrackingMod interface { diff --git a/db/factory/fieldseeker.mosquitoinspection.bob.go b/db/factory/fieldseeker.mosquitoinspection.bob.go index fa9c480a..3420f5d7 100644 --- a/db/factory/fieldseeker.mosquitoinspection.bob.go +++ b/db/factory/fieldseeker.mosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerMosquitoinspectionMod interface { diff --git a/db/factory/fieldseeker.pointlocation.bob.go b/db/factory/fieldseeker.pointlocation.bob.go index 01cf29b0..5eeb395f 100644 --- a/db/factory/fieldseeker.pointlocation.bob.go +++ b/db/factory/fieldseeker.pointlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerPointlocationMod interface { diff --git a/db/factory/fieldseeker.polygonlocation.bob.go b/db/factory/fieldseeker.polygonlocation.bob.go index 7def30d7..874b05ac 100644 --- a/db/factory/fieldseeker.polygonlocation.bob.go +++ b/db/factory/fieldseeker.polygonlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerPolygonlocationMod interface { diff --git a/db/factory/fieldseeker.pool.bob.go b/db/factory/fieldseeker.pool.bob.go index 322b8bcd..549bafcc 100644 --- a/db/factory/fieldseeker.pool.bob.go +++ b/db/factory/fieldseeker.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerPoolMod interface { diff --git a/db/factory/fieldseeker.pooldetail.bob.go b/db/factory/fieldseeker.pooldetail.bob.go index c4978b51..624fe189 100644 --- a/db/factory/fieldseeker.pooldetail.bob.go +++ b/db/factory/fieldseeker.pooldetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerPooldetailMod interface { diff --git a/db/factory/fieldseeker.proposedtreatmentarea.bob.go b/db/factory/fieldseeker.proposedtreatmentarea.bob.go index a9dd58c6..05ce4a6c 100644 --- a/db/factory/fieldseeker.proposedtreatmentarea.bob.go +++ b/db/factory/fieldseeker.proposedtreatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerProposedtreatmentareaMod interface { diff --git a/db/factory/fieldseeker.qamosquitoinspection.bob.go b/db/factory/fieldseeker.qamosquitoinspection.bob.go index cabb9b45..b99924d0 100644 --- a/db/factory/fieldseeker.qamosquitoinspection.bob.go +++ b/db/factory/fieldseeker.qamosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerQamosquitoinspectionMod interface { diff --git a/db/factory/fieldseeker.rodentlocation.bob.go b/db/factory/fieldseeker.rodentlocation.bob.go index 4911723e..b348a13e 100644 --- a/db/factory/fieldseeker.rodentlocation.bob.go +++ b/db/factory/fieldseeker.rodentlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerRodentlocationMod interface { diff --git a/db/factory/fieldseeker.samplecollection.bob.go b/db/factory/fieldseeker.samplecollection.bob.go index 29e18a7d..41890846 100644 --- a/db/factory/fieldseeker.samplecollection.bob.go +++ b/db/factory/fieldseeker.samplecollection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerSamplecollectionMod interface { diff --git a/db/factory/fieldseeker.samplelocation.bob.go b/db/factory/fieldseeker.samplelocation.bob.go index bc41d319..69a1a719 100644 --- a/db/factory/fieldseeker.samplelocation.bob.go +++ b/db/factory/fieldseeker.samplelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerSamplelocationMod interface { diff --git a/db/factory/fieldseeker.servicerequest.bob.go b/db/factory/fieldseeker.servicerequest.bob.go index 4a69b438..2375df74 100644 --- a/db/factory/fieldseeker.servicerequest.bob.go +++ b/db/factory/fieldseeker.servicerequest.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerServicerequestMod interface { diff --git a/db/factory/fieldseeker.speciesabundance.bob.go b/db/factory/fieldseeker.speciesabundance.bob.go index efafd1fe..958d702d 100644 --- a/db/factory/fieldseeker.speciesabundance.bob.go +++ b/db/factory/fieldseeker.speciesabundance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerSpeciesabundanceMod interface { diff --git a/db/factory/fieldseeker.stormdrain.bob.go b/db/factory/fieldseeker.stormdrain.bob.go index bb3fdca3..c3d5ac53 100644 --- a/db/factory/fieldseeker.stormdrain.bob.go +++ b/db/factory/fieldseeker.stormdrain.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerStormdrainMod interface { diff --git a/db/factory/fieldseeker.timecard.bob.go b/db/factory/fieldseeker.timecard.bob.go index fa18147b..f962dc43 100644 --- a/db/factory/fieldseeker.timecard.bob.go +++ b/db/factory/fieldseeker.timecard.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerTimecardMod interface { diff --git a/db/factory/fieldseeker.trapdata.bob.go b/db/factory/fieldseeker.trapdata.bob.go index 649b359f..12669f0c 100644 --- a/db/factory/fieldseeker.trapdata.bob.go +++ b/db/factory/fieldseeker.trapdata.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerTrapdatumMod interface { diff --git a/db/factory/fieldseeker.traplocation.bob.go b/db/factory/fieldseeker.traplocation.bob.go index 178079d8..6466750d 100644 --- a/db/factory/fieldseeker.traplocation.bob.go +++ b/db/factory/fieldseeker.traplocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerTraplocationMod interface { diff --git a/db/factory/fieldseeker.treatment.bob.go b/db/factory/fieldseeker.treatment.bob.go index dadf53bd..13949e67 100644 --- a/db/factory/fieldseeker.treatment.bob.go +++ b/db/factory/fieldseeker.treatment.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerTreatmentMod interface { diff --git a/db/factory/fieldseeker.treatmentarea.bob.go b/db/factory/fieldseeker.treatmentarea.bob.go index 2290a138..f8ec7fcb 100644 --- a/db/factory/fieldseeker.treatmentarea.bob.go +++ b/db/factory/fieldseeker.treatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerTreatmentareaMod interface { diff --git a/db/factory/fieldseeker.zones.bob.go b/db/factory/fieldseeker.zones.bob.go index 1e6fab87..522c7721 100644 --- a/db/factory/fieldseeker.zones.bob.go +++ b/db/factory/fieldseeker.zones.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerZoneMod interface { diff --git a/db/factory/fieldseeker.zones2.bob.go b/db/factory/fieldseeker.zones2.bob.go index 9cf78520..04b70f03 100644 --- a/db/factory/fieldseeker.zones2.bob.go +++ b/db/factory/fieldseeker.zones2.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -9,14 +9,14 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/types" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/types" ) type FieldseekerZones2Mod interface { diff --git a/db/factory/fieldseeker_sync.bob.go b/db/factory/fieldseeker_sync.bob.go index 2358f34a..10736c41 100644 --- a/db/factory/fieldseeker_sync.bob.go +++ b/db/factory/fieldseeker_sync.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,10 +8,10 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type FieldseekerSyncMod interface { diff --git a/db/factory/geography_columns.bob.go b/db/factory/geography_columns.bob.go index 16709713..17469e4b 100644 --- a/db/factory/geography_columns.bob.go +++ b/db/factory/geography_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/geometry_columns.bob.go b/db/factory/geometry_columns.bob.go index 63c97d2a..20c97753 100644 --- a/db/factory/geometry_columns.bob.go +++ b/db/factory/geometry_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/goose_db_version.bob.go b/db/factory/goose_db_version.bob.go index 581502cb..01377c00 100644 --- a/db/factory/goose_db_version.bob.go +++ b/db/factory/goose_db_version.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,10 +8,10 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type GooseDBVersionMod interface { diff --git a/db/factory/h3_aggregation.bob.go b/db/factory/h3_aggregation.bob.go index 5977ac04..dc25bfa2 100644 --- a/db/factory/h3_aggregation.bob.go +++ b/db/factory/h3_aggregation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -7,13 +7,13 @@ import ( "context" "testing" + "github.com/Gleipnir-Technology/bob" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type H3AggregationMod interface { diff --git a/db/factory/import.district.bob.go b/db/factory/import.district.bob.go index b2ebd11c..3cadd923 100644 --- a/db/factory/import.district.bob.go +++ b/db/factory/import.district.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -7,13 +7,13 @@ import ( "context" "testing" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" "github.com/shopspring/decimal" - "github.com/stephenafamo/bob" ) type ImportDistrictMod interface { diff --git a/db/factory/note_audio.bob.go b/db/factory/note_audio.bob.go index 557894ba..5876c708 100644 --- a/db/factory/note_audio.bob.go +++ b/db/factory/note_audio.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,13 +8,13 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type NoteAudioMod interface { diff --git a/db/factory/note_audio_breadcrumb.bob.go b/db/factory/note_audio_breadcrumb.bob.go index b4a8c8a2..b339b800 100644 --- a/db/factory/note_audio_breadcrumb.bob.go +++ b/db/factory/note_audio_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,11 +8,11 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" 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 NoteAudioBreadcrumbMod interface { diff --git a/db/factory/note_audio_data.bob.go b/db/factory/note_audio_data.bob.go index 3030278b..70983493 100644 --- a/db/factory/note_audio_data.bob.go +++ b/db/factory/note_audio_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,12 +8,12 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" 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 NoteAudioDatumMod interface { diff --git a/db/factory/note_image.bob.go b/db/factory/note_image.bob.go index 53cc85cf..db3cecb7 100644 --- a/db/factory/note_image.bob.go +++ b/db/factory/note_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,13 +8,13 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type NoteImageMod interface { diff --git a/db/factory/note_image_breadcrumb.bob.go b/db/factory/note_image_breadcrumb.bob.go index 31cb44f6..b51fe9f9 100644 --- a/db/factory/note_image_breadcrumb.bob.go +++ b/db/factory/note_image_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,11 +8,11 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" 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 NoteImageBreadcrumbMod interface { diff --git a/db/factory/note_image_data.bob.go b/db/factory/note_image_data.bob.go index dc8350ce..caf0b1e9 100644 --- a/db/factory/note_image_data.bob.go +++ b/db/factory/note_image_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,12 +8,12 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" 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 NoteImageDatumMod interface { diff --git a/db/factory/notification.bob.go b/db/factory/notification.bob.go index 9b34b0c7..874649ec 100644 --- a/db/factory/notification.bob.go +++ b/db/factory/notification.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,13 +8,13 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type NotificationMod interface { diff --git a/db/factory/oauth_token.bob.go b/db/factory/oauth_token.bob.go index 12ef5b67..8bb6fe31 100644 --- a/db/factory/oauth_token.bob.go +++ b/db/factory/oauth_token.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,12 +8,12 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type OauthTokenMod interface { diff --git a/db/factory/organization.bob.go b/db/factory/organization.bob.go index e92ea5a7..e5396b91 100644 --- a/db/factory/organization.bob.go +++ b/db/factory/organization.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -7,13 +7,13 @@ import ( "context" "testing" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type OrganizationMod interface { diff --git a/db/factory/publicreport.image.bob.go b/db/factory/publicreport.image.bob.go index cf3044aa..7772d7a9 100644 --- a/db/factory/publicreport.image.bob.go +++ b/db/factory/publicreport.image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,13 +8,13 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/google/uuid" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type PublicreportImageMod interface { diff --git a/db/factory/publicreport.image_exif.bob.go b/db/factory/publicreport.image_exif.bob.go index 742829a4..1726c82a 100644 --- a/db/factory/publicreport.image_exif.bob.go +++ b/db/factory/publicreport.image_exif.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -7,10 +7,10 @@ import ( "context" "testing" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type PublicreportImageExifMod interface { diff --git a/db/factory/publicreport.nuisance.bob.go b/db/factory/publicreport.nuisance.bob.go index faccd615..aa9b49de 100644 --- a/db/factory/publicreport.nuisance.bob.go +++ b/db/factory/publicreport.nuisance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,13 +8,13 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type PublicreportNuisanceMod interface { diff --git a/db/factory/publicreport.pool.bob.go b/db/factory/publicreport.pool.bob.go index 103060a5..34bac1f9 100644 --- a/db/factory/publicreport.pool.bob.go +++ b/db/factory/publicreport.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,13 +8,13 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type PublicreportPoolMod interface { diff --git a/db/factory/publicreport.pool_image.bob.go b/db/factory/publicreport.pool_image.bob.go index e7af585c..ce98ed9a 100644 --- a/db/factory/publicreport.pool_image.bob.go +++ b/db/factory/publicreport.pool_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -7,10 +7,10 @@ import ( "context" "testing" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type PublicreportPoolImageMod interface { diff --git a/db/factory/publicreport.quick.bob.go b/db/factory/publicreport.quick.bob.go index 30ff2b16..97cec339 100644 --- a/db/factory/publicreport.quick.bob.go +++ b/db/factory/publicreport.quick.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,13 +8,13 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type PublicreportQuickMod interface { diff --git a/db/factory/publicreport.quick_image.bob.go b/db/factory/publicreport.quick_image.bob.go index 951dc333..63873cbf 100644 --- a/db/factory/publicreport.quick_image.bob.go +++ b/db/factory/publicreport.quick_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -7,10 +7,10 @@ import ( "context" "testing" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type PublicreportQuickImageMod interface { diff --git a/db/factory/publicreport.report_location.bob.go b/db/factory/publicreport.report_location.bob.go index 2b1dac21..44b0057a 100644 --- a/db/factory/publicreport.report_location.bob.go +++ b/db/factory/publicreport.report_location.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/raster_columns.bob.go b/db/factory/raster_columns.bob.go index bd01e09e..1e8a8255 100644 --- a/db/factory/raster_columns.bob.go +++ b/db/factory/raster_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/raster_overviews.bob.go b/db/factory/raster_overviews.bob.go index 3d7f41da..7c4bb9bf 100644 --- a/db/factory/raster_overviews.bob.go +++ b/db/factory/raster_overviews.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory diff --git a/db/factory/sessions.bob.go b/db/factory/sessions.bob.go index d0148f28..562aed8a 100644 --- a/db/factory/sessions.bob.go +++ b/db/factory/sessions.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,10 +8,10 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type SessionMod interface { diff --git a/db/factory/spatial_ref_sys.bob.go b/db/factory/spatial_ref_sys.bob.go index 8eeff8fa..f59a43da 100644 --- a/db/factory/spatial_ref_sys.bob.go +++ b/db/factory/spatial_ref_sys.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -7,12 +7,12 @@ import ( "context" "testing" + "github.com/Gleipnir-Technology/bob" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type SpatialRefSyMod interface { diff --git a/db/factory/user_.bob.go b/db/factory/user_.bob.go index 0bfd18f8..5112ee5b 100644 --- a/db/factory/user_.bob.go +++ b/db/factory/user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package factory @@ -8,13 +8,13 @@ import ( "testing" "time" + "github.com/Gleipnir-Technology/bob" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" models "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/jaswdr/faker/v2" - "github.com/stephenafamo/bob" ) type UserMod interface { diff --git a/db/fieldseeker.go b/db/fieldseeker.go index 24d80942..ecc4f4e3 100644 --- a/db/fieldseeker.go +++ b/db/fieldseeker.go @@ -5,14 +5,14 @@ import ( "fmt" fslayer "github.com/Gleipnir-Technology/arcgis-go/fieldseeker/layer" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/gofrs/uuid/v5" googleuuid "github.com/google/uuid" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" "github.com/stephenafamo/scan" ) diff --git a/db/migrations/00044_comms_text_origin_user.sql b/db/migrations/00044_comms_text_origin_user.sql index f8aa6829..2e75d3fa 100644 --- a/db/migrations/00044_comms_text_origin_user.sql +++ b/db/migrations/00044_comms_text_origin_user.sql @@ -1,6 +1,3 @@ -- +goose Up ALTER TYPE comms.TextOrigin ADD VALUE 'customer'; ALTER TYPE comms.TextOrigin ADD VALUE 'reiteration'; --- +goose Down -ALTER TYPE comms.TextOrigin DROP VALUE 'reiteration'; -ALTER TYPE comms.TextOrigin DROP VALUE 'customer'; diff --git a/db/migrations/00045_comms_text_sid.sql b/db/migrations/00045_comms_text_sid.sql index 71b0e09e..bbda4771 100644 --- a/db/migrations/00045_comms_text_sid.sql +++ b/db/migrations/00045_comms_text_sid.sql @@ -3,6 +3,11 @@ ALTER TABLE comms.text_log ADD COLUMN twilio_sid TEXT UNIQUE; ALTER TABLE comms.text_log ADD COLUMN twilio_status TEXT; UPDATE comms.text_log SET twilio_status = ''; ALTER TABLE comms.text_log ALTER COLUMN twilio_status SET NOT NULL; +ALTER TABLE comms.text_log ADD COLUMN is_visible_to_llm BOOLEAN; +UPDATE comms.text_log SET is_visible_to_llm = FALSE; +ALTER TABLE comms.text_log ALTER COLUMN is_visible_to_llm SET NOT NULL; +ALTER TYPE comms.TextOrigin ADD VALUE 'command-response'; -- +goose Down +ALTER TABLE comms.text_log DROP COLUMN is_visible_to_llm; ALTER TABLE comms.text_log DROP COLUMN twilio_status; ALTER TABLE comms.text_log DROP COLUMN twilio_sid; diff --git a/db/models/arcgis.user_.bob.go b/db/models/arcgis.user_.bob.go index 6613b87f..3796abf0 100644 --- a/db/models/arcgis.user_.bob.go +++ b/db/models/arcgis.user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,17 +9,17 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/omit" - "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" ) // ArcgisUser is an object representing the database table. diff --git a/db/models/arcgis.user_privilege.bob.go b/db/models/arcgis.user_privilege.bob.go index 0783119b..00b0a456 100644 --- a/db/models/arcgis.user_privilege.bob.go +++ b/db/models/arcgis.user_privilege.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -8,17 +8,17 @@ import ( "fmt" "io" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/omit" - "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" ) // ArcgisUserPrivilege is an object representing the database table. diff --git a/db/models/bob_counts.bob.go b/db/models/bob_counts.bob.go index 41648f3a..219db03a 100644 --- a/db/models/bob_counts.bob.go +++ b/db/models/bob_counts.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -7,10 +7,10 @@ import ( "context" "io" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/dialect" - "github.com/stephenafamo/bob/orm" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/orm" "github.com/stephenafamo/scan" ) diff --git a/db/models/bob_joins.bob.go b/db/models/bob_joins.bob.go index a69510f4..e6c2439d 100644 --- a/db/models/bob_joins.bob.go +++ b/db/models/bob_joins.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -6,9 +6,9 @@ package models import ( "hash/maphash" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/clause" - "github.com/stephenafamo/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/clause" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" ) var ( diff --git a/db/models/bob_loaders.bob.go b/db/models/bob_loaders.bob.go index 3a003482..708a4e90 100644 --- a/db/models/bob_loaders.bob.go +++ b/db/models/bob_loaders.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,9 +9,9 @@ import ( "errors" "fmt" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql/dialect" - "github.com/stephenafamo/bob/orm" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/orm" ) var Preload = getPreloaders() diff --git a/db/models/bob_where.bob.go b/db/models/bob_where.bob.go index d25228a0..2eaec9d9 100644 --- a/db/models/bob_where.bob.go +++ b/db/models/bob_where.bob.go @@ -1,12 +1,12 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models import ( - "github.com/stephenafamo/bob/clause" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/clause" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" ) var ( diff --git a/db/models/comms.email_contact.bob.go b/db/models/comms.email_contact.bob.go index ca468f6b..ff97fc8d 100644 --- a/db/models/comms.email_contact.bob.go +++ b/db/models/comms.email_contact.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -8,17 +8,17 @@ import ( "fmt" "io" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/omit" - "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" ) // CommsEmailContact is an object representing the database table. diff --git a/db/models/comms.email_log.bob.go b/db/models/comms.email_log.bob.go index ef59ff78..d22a9269 100644 --- a/db/models/comms.email_log.bob.go +++ b/db/models/comms.email_log.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,20 +9,20 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" ) // CommsEmailLog is an object representing the database table. diff --git a/db/models/comms.email_template.bob.go b/db/models/comms.email_template.bob.go index ca95017d..0f62168d 100644 --- a/db/models/comms.email_template.bob.go +++ b/db/models/comms.email_template.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,20 +9,20 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" ) // CommsEmailTemplate is an object representing the database table. diff --git a/db/models/comms.phone.bob.go b/db/models/comms.phone.bob.go index 56a665c0..70bc8a63 100644 --- a/db/models/comms.phone.bob.go +++ b/db/models/comms.phone.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -8,19 +8,19 @@ import ( "fmt" "io" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" ) // CommsPhone is an object representing the database table. diff --git a/db/models/comms.text_job.bob.go b/db/models/comms.text_job.bob.go index 6a8ce5b5..aabb5a6a 100644 --- a/db/models/comms.text_job.bob.go +++ b/db/models/comms.text_job.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,18 +9,18 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/omit" - "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" ) // CommsTextJob is an object representing the database table. diff --git a/db/models/comms.text_log.bob.go b/db/models/comms.text_log.bob.go index 45e671d0..33c31b19 100644 --- a/db/models/comms.text_log.bob.go +++ b/db/models/comms.text_log.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,33 +9,34 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" ) // CommsTextLog is an object representing the database table. type CommsTextLog struct { - Content string `db:"content" ` - Created time.Time `db:"created" ` - Destination string `db:"destination" ` - ID int32 `db:"id,pk" ` - IsWelcome bool `db:"is_welcome" ` - Origin enums.CommsTextorigin `db:"origin" ` - Source string `db:"source" ` - TwilioSid null.Val[string] `db:"twilio_sid" ` - TwilioStatus string `db:"twilio_status" ` + Content string `db:"content" ` + Created time.Time `db:"created" ` + Destination string `db:"destination" ` + ID int32 `db:"id,pk" ` + IsWelcome bool `db:"is_welcome" ` + Origin enums.CommsTextorigin `db:"origin" ` + Source string `db:"source" ` + TwilioSid null.Val[string] `db:"twilio_sid" ` + TwilioStatus string `db:"twilio_status" ` + IsVisibleToLLM bool `db:"is_visible_to_llm" ` R commsTextLogR `db:"-" ` } @@ -59,33 +60,35 @@ type commsTextLogR struct { func buildCommsTextLogColumns(alias string) commsTextLogColumns { return commsTextLogColumns{ ColumnsExpr: expr.NewColumnsExpr( - "content", "created", "destination", "id", "is_welcome", "origin", "source", "twilio_sid", "twilio_status", + "content", "created", "destination", "id", "is_welcome", "origin", "source", "twilio_sid", "twilio_status", "is_visible_to_llm", ).WithParent("comms.text_log"), - tableAlias: alias, - Content: psql.Quote(alias, "content"), - Created: psql.Quote(alias, "created"), - Destination: psql.Quote(alias, "destination"), - ID: psql.Quote(alias, "id"), - IsWelcome: psql.Quote(alias, "is_welcome"), - Origin: psql.Quote(alias, "origin"), - Source: psql.Quote(alias, "source"), - TwilioSid: psql.Quote(alias, "twilio_sid"), - TwilioStatus: psql.Quote(alias, "twilio_status"), + tableAlias: alias, + Content: psql.Quote(alias, "content"), + Created: psql.Quote(alias, "created"), + Destination: psql.Quote(alias, "destination"), + ID: psql.Quote(alias, "id"), + IsWelcome: psql.Quote(alias, "is_welcome"), + Origin: psql.Quote(alias, "origin"), + Source: psql.Quote(alias, "source"), + TwilioSid: psql.Quote(alias, "twilio_sid"), + TwilioStatus: psql.Quote(alias, "twilio_status"), + IsVisibleToLLM: psql.Quote(alias, "is_visible_to_llm"), } } type commsTextLogColumns struct { expr.ColumnsExpr - tableAlias string - Content psql.Expression - Created psql.Expression - Destination psql.Expression - ID psql.Expression - IsWelcome psql.Expression - Origin psql.Expression - Source psql.Expression - TwilioSid psql.Expression - TwilioStatus psql.Expression + tableAlias string + Content psql.Expression + Created psql.Expression + Destination psql.Expression + ID psql.Expression + IsWelcome psql.Expression + Origin psql.Expression + Source psql.Expression + TwilioSid psql.Expression + TwilioStatus psql.Expression + IsVisibleToLLM psql.Expression } func (c commsTextLogColumns) Alias() string { @@ -100,19 +103,20 @@ func (commsTextLogColumns) AliasedAs(alias string) commsTextLogColumns { // All values are optional, and do not have to be set // Generated columns are not included type CommsTextLogSetter struct { - Content omit.Val[string] `db:"content" ` - Created omit.Val[time.Time] `db:"created" ` - Destination omit.Val[string] `db:"destination" ` - ID omit.Val[int32] `db:"id,pk" ` - IsWelcome omit.Val[bool] `db:"is_welcome" ` - Origin omit.Val[enums.CommsTextorigin] `db:"origin" ` - Source omit.Val[string] `db:"source" ` - TwilioSid omitnull.Val[string] `db:"twilio_sid" ` - TwilioStatus omit.Val[string] `db:"twilio_status" ` + Content omit.Val[string] `db:"content" ` + Created omit.Val[time.Time] `db:"created" ` + Destination omit.Val[string] `db:"destination" ` + ID omit.Val[int32] `db:"id,pk" ` + IsWelcome omit.Val[bool] `db:"is_welcome" ` + Origin omit.Val[enums.CommsTextorigin] `db:"origin" ` + Source omit.Val[string] `db:"source" ` + TwilioSid omitnull.Val[string] `db:"twilio_sid" ` + TwilioStatus omit.Val[string] `db:"twilio_status" ` + IsVisibleToLLM omit.Val[bool] `db:"is_visible_to_llm" ` } func (s CommsTextLogSetter) SetColumns() []string { - vals := make([]string, 0, 9) + vals := make([]string, 0, 10) if s.Content.IsValue() { vals = append(vals, "content") } @@ -140,6 +144,9 @@ func (s CommsTextLogSetter) SetColumns() []string { if s.TwilioStatus.IsValue() { vals = append(vals, "twilio_status") } + if s.IsVisibleToLLM.IsValue() { + vals = append(vals, "is_visible_to_llm") + } return vals } @@ -171,6 +178,9 @@ func (s CommsTextLogSetter) Overwrite(t *CommsTextLog) { if s.TwilioStatus.IsValue() { t.TwilioStatus = s.TwilioStatus.MustGet() } + if s.IsVisibleToLLM.IsValue() { + t.IsVisibleToLLM = s.IsVisibleToLLM.MustGet() + } } func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) { @@ -179,7 +189,7 @@ func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 9) + vals := make([]bob.Expression, 10) if s.Content.IsValue() { vals[0] = psql.Arg(s.Content.MustGet()) } else { @@ -234,6 +244,12 @@ func (s *CommsTextLogSetter) Apply(q *dialect.InsertQuery) { vals[8] = psql.Raw("DEFAULT") } + if s.IsVisibleToLLM.IsValue() { + vals[9] = psql.Arg(s.IsVisibleToLLM.MustGet()) + } else { + vals[9] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -243,7 +259,7 @@ func (s CommsTextLogSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 9) + exprs := make([]bob.Expression, 0, 10) if s.Content.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -308,6 +324,13 @@ func (s CommsTextLogSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if s.IsVisibleToLLM.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "is_visible_to_llm")...), + psql.Arg(s.IsVisibleToLLM), + }}) + } + return exprs } @@ -679,15 +702,16 @@ func (commsTextLog0 *CommsTextLog) AttachSourcePhone(ctx context.Context, exec b } type commsTextLogWhere[Q psql.Filterable] struct { - Content psql.WhereMod[Q, string] - Created psql.WhereMod[Q, time.Time] - Destination psql.WhereMod[Q, string] - ID psql.WhereMod[Q, int32] - IsWelcome psql.WhereMod[Q, bool] - Origin psql.WhereMod[Q, enums.CommsTextorigin] - Source psql.WhereMod[Q, string] - TwilioSid psql.WhereNullMod[Q, string] - TwilioStatus psql.WhereMod[Q, string] + Content psql.WhereMod[Q, string] + Created psql.WhereMod[Q, time.Time] + Destination psql.WhereMod[Q, string] + ID psql.WhereMod[Q, int32] + IsWelcome psql.WhereMod[Q, bool] + Origin psql.WhereMod[Q, enums.CommsTextorigin] + Source psql.WhereMod[Q, string] + TwilioSid psql.WhereNullMod[Q, string] + TwilioStatus psql.WhereMod[Q, string] + IsVisibleToLLM psql.WhereMod[Q, bool] } func (commsTextLogWhere[Q]) AliasedAs(alias string) commsTextLogWhere[Q] { @@ -696,15 +720,16 @@ func (commsTextLogWhere[Q]) AliasedAs(alias string) commsTextLogWhere[Q] { func buildCommsTextLogWhere[Q psql.Filterable](cols commsTextLogColumns) commsTextLogWhere[Q] { return commsTextLogWhere[Q]{ - Content: psql.Where[Q, string](cols.Content), - Created: psql.Where[Q, time.Time](cols.Created), - Destination: psql.Where[Q, string](cols.Destination), - ID: psql.Where[Q, int32](cols.ID), - IsWelcome: psql.Where[Q, bool](cols.IsWelcome), - Origin: psql.Where[Q, enums.CommsTextorigin](cols.Origin), - Source: psql.Where[Q, string](cols.Source), - TwilioSid: psql.WhereNull[Q, string](cols.TwilioSid), - TwilioStatus: psql.Where[Q, string](cols.TwilioStatus), + Content: psql.Where[Q, string](cols.Content), + Created: psql.Where[Q, time.Time](cols.Created), + Destination: psql.Where[Q, string](cols.Destination), + ID: psql.Where[Q, int32](cols.ID), + IsWelcome: psql.Where[Q, bool](cols.IsWelcome), + Origin: psql.Where[Q, enums.CommsTextorigin](cols.Origin), + Source: psql.Where[Q, string](cols.Source), + TwilioSid: psql.WhereNull[Q, string](cols.TwilioSid), + TwilioStatus: psql.Where[Q, string](cols.TwilioStatus), + IsVisibleToLLM: psql.Where[Q, bool](cols.IsVisibleToLLM), } } diff --git a/db/models/fieldseeker.containerrelate.bob.go b/db/models/fieldseeker.containerrelate.bob.go index 8b439dce..3d08edbd 100644 --- a/db/models/fieldseeker.containerrelate.bob.go +++ b/db/models/fieldseeker.containerrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerContainerrelate is an object representing the database table. diff --git a/db/models/fieldseeker.fieldscoutinglog.bob.go b/db/models/fieldseeker.fieldscoutinglog.bob.go index a0caa392..ff221e64 100644 --- a/db/models/fieldseeker.fieldscoutinglog.bob.go +++ b/db/models/fieldseeker.fieldscoutinglog.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerFieldscoutinglog is an object representing the database table. diff --git a/db/models/fieldseeker.habitatrelate.bob.go b/db/models/fieldseeker.habitatrelate.bob.go index cefb73a8..37743713 100644 --- a/db/models/fieldseeker.habitatrelate.bob.go +++ b/db/models/fieldseeker.habitatrelate.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerHabitatrelate is an object representing the database table. diff --git a/db/models/fieldseeker.inspectionsample.bob.go b/db/models/fieldseeker.inspectionsample.bob.go index 73aaa6da..b70b3452 100644 --- a/db/models/fieldseeker.inspectionsample.bob.go +++ b/db/models/fieldseeker.inspectionsample.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerInspectionsample is an object representing the database table. diff --git a/db/models/fieldseeker.inspectionsampledetail.bob.go b/db/models/fieldseeker.inspectionsampledetail.bob.go index d9d4d406..fde2deb9 100644 --- a/db/models/fieldseeker.inspectionsampledetail.bob.go +++ b/db/models/fieldseeker.inspectionsampledetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerInspectionsampledetail is an object representing the database table. diff --git a/db/models/fieldseeker.linelocation.bob.go b/db/models/fieldseeker.linelocation.bob.go index 9efc4ee5..dfa31d27 100644 --- a/db/models/fieldseeker.linelocation.bob.go +++ b/db/models/fieldseeker.linelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerLinelocation is an object representing the database table. diff --git a/db/models/fieldseeker.locationtracking.bob.go b/db/models/fieldseeker.locationtracking.bob.go index facfcf7d..452a6c61 100644 --- a/db/models/fieldseeker.locationtracking.bob.go +++ b/db/models/fieldseeker.locationtracking.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerLocationtracking is an object representing the database table. diff --git a/db/models/fieldseeker.mosquitoinspection.bob.go b/db/models/fieldseeker.mosquitoinspection.bob.go index db094235..89e5d974 100644 --- a/db/models/fieldseeker.mosquitoinspection.bob.go +++ b/db/models/fieldseeker.mosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerMosquitoinspection is an object representing the database table. diff --git a/db/models/fieldseeker.pointlocation.bob.go b/db/models/fieldseeker.pointlocation.bob.go index e212b558..0b4b92e8 100644 --- a/db/models/fieldseeker.pointlocation.bob.go +++ b/db/models/fieldseeker.pointlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerPointlocation is an object representing the database table. diff --git a/db/models/fieldseeker.polygonlocation.bob.go b/db/models/fieldseeker.polygonlocation.bob.go index 5003caa2..69b142ce 100644 --- a/db/models/fieldseeker.polygonlocation.bob.go +++ b/db/models/fieldseeker.polygonlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerPolygonlocation is an object representing the database table. diff --git a/db/models/fieldseeker.pool.bob.go b/db/models/fieldseeker.pool.bob.go index 898e7bcb..ea2129b0 100644 --- a/db/models/fieldseeker.pool.bob.go +++ b/db/models/fieldseeker.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerPool is an object representing the database table. diff --git a/db/models/fieldseeker.pooldetail.bob.go b/db/models/fieldseeker.pooldetail.bob.go index a9cbd612..38b65824 100644 --- a/db/models/fieldseeker.pooldetail.bob.go +++ b/db/models/fieldseeker.pooldetail.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerPooldetail is an object representing the database table. diff --git a/db/models/fieldseeker.proposedtreatmentarea.bob.go b/db/models/fieldseeker.proposedtreatmentarea.bob.go index 8f89fe65..413a4ebd 100644 --- a/db/models/fieldseeker.proposedtreatmentarea.bob.go +++ b/db/models/fieldseeker.proposedtreatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerProposedtreatmentarea is an object representing the database table. diff --git a/db/models/fieldseeker.qamosquitoinspection.bob.go b/db/models/fieldseeker.qamosquitoinspection.bob.go index 76fcae96..2f73acba 100644 --- a/db/models/fieldseeker.qamosquitoinspection.bob.go +++ b/db/models/fieldseeker.qamosquitoinspection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerQamosquitoinspection is an object representing the database table. diff --git a/db/models/fieldseeker.rodentlocation.bob.go b/db/models/fieldseeker.rodentlocation.bob.go index 28117cfb..de21bf63 100644 --- a/db/models/fieldseeker.rodentlocation.bob.go +++ b/db/models/fieldseeker.rodentlocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerRodentlocation is an object representing the database table. diff --git a/db/models/fieldseeker.samplecollection.bob.go b/db/models/fieldseeker.samplecollection.bob.go index f3648eda..d4bbbb9f 100644 --- a/db/models/fieldseeker.samplecollection.bob.go +++ b/db/models/fieldseeker.samplecollection.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerSamplecollection is an object representing the database table. diff --git a/db/models/fieldseeker.samplelocation.bob.go b/db/models/fieldseeker.samplelocation.bob.go index cde38c45..ffa55a24 100644 --- a/db/models/fieldseeker.samplelocation.bob.go +++ b/db/models/fieldseeker.samplelocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerSamplelocation is an object representing the database table. diff --git a/db/models/fieldseeker.servicerequest.bob.go b/db/models/fieldseeker.servicerequest.bob.go index f3b89f50..7f736675 100644 --- a/db/models/fieldseeker.servicerequest.bob.go +++ b/db/models/fieldseeker.servicerequest.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerServicerequest is an object representing the database table. diff --git a/db/models/fieldseeker.speciesabundance.bob.go b/db/models/fieldseeker.speciesabundance.bob.go index f8b46124..894630a0 100644 --- a/db/models/fieldseeker.speciesabundance.bob.go +++ b/db/models/fieldseeker.speciesabundance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerSpeciesabundance is an object representing the database table. diff --git a/db/models/fieldseeker.stormdrain.bob.go b/db/models/fieldseeker.stormdrain.bob.go index c603b20c..c67451a9 100644 --- a/db/models/fieldseeker.stormdrain.bob.go +++ b/db/models/fieldseeker.stormdrain.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerStormdrain is an object representing the database table. diff --git a/db/models/fieldseeker.timecard.bob.go b/db/models/fieldseeker.timecard.bob.go index 8b7b1805..5dfed24a 100644 --- a/db/models/fieldseeker.timecard.bob.go +++ b/db/models/fieldseeker.timecard.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerTimecard is an object representing the database table. diff --git a/db/models/fieldseeker.trapdata.bob.go b/db/models/fieldseeker.trapdata.bob.go index f6fe3f01..8075cd5b 100644 --- a/db/models/fieldseeker.trapdata.bob.go +++ b/db/models/fieldseeker.trapdata.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerTrapdatum is an object representing the database table. diff --git a/db/models/fieldseeker.traplocation.bob.go b/db/models/fieldseeker.traplocation.bob.go index ba711df6..e6c0a5b9 100644 --- a/db/models/fieldseeker.traplocation.bob.go +++ b/db/models/fieldseeker.traplocation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerTraplocation is an object representing the database table. diff --git a/db/models/fieldseeker.treatment.bob.go b/db/models/fieldseeker.treatment.bob.go index d219c571..81f67bbd 100644 --- a/db/models/fieldseeker.treatment.bob.go +++ b/db/models/fieldseeker.treatment.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerTreatment is an object representing the database table. diff --git a/db/models/fieldseeker.treatmentarea.bob.go b/db/models/fieldseeker.treatmentarea.bob.go index 6e1ffcb8..46c42d5a 100644 --- a/db/models/fieldseeker.treatmentarea.bob.go +++ b/db/models/fieldseeker.treatmentarea.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerTreatmentarea is an object representing the database table. diff --git a/db/models/fieldseeker.zones.bob.go b/db/models/fieldseeker.zones.bob.go index e5e9f7b4..8d2cabad 100644 --- a/db/models/fieldseeker.zones.bob.go +++ b/db/models/fieldseeker.zones.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerZone is an object representing the database table. diff --git a/db/models/fieldseeker.zones2.bob.go b/db/models/fieldseeker.zones2.bob.go index 02e3a743..18b69253 100644 --- a/db/models/fieldseeker.zones2.bob.go +++ b/db/models/fieldseeker.zones2.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,21 +10,21 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" - "github.com/stephenafamo/bob/types/pgtypes" ) // FieldseekerZones2 is an object representing the database table. diff --git a/db/models/fieldseeker_sync.bob.go b/db/models/fieldseeker_sync.bob.go index fcf5e738..f1cf9e73 100644 --- a/db/models/fieldseeker_sync.bob.go +++ b/db/models/fieldseeker_sync.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,17 +9,17 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/omit" - "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" ) // FieldseekerSync is an object representing the database table. diff --git a/db/models/geography_columns.bob.go b/db/models/geography_columns.bob.go index c1452873..59a4edd7 100644 --- a/db/models/geography_columns.bob.go +++ b/db/models/geography_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -6,10 +6,10 @@ package models import ( "context" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/expr" "github.com/aarondl/opt/null" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/expr" ) // GeographyColumn is an object representing the database table. diff --git a/db/models/geometry_columns.bob.go b/db/models/geometry_columns.bob.go index 5d875b78..1d9f9f6a 100644 --- a/db/models/geometry_columns.bob.go +++ b/db/models/geometry_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -6,10 +6,10 @@ package models import ( "context" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/expr" "github.com/aarondl/opt/null" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/expr" ) // GeometryColumn is an object representing the database table. diff --git a/db/models/goose_db_version.bob.go b/db/models/goose_db_version.bob.go index 19519e27..b68acace 100644 --- a/db/models/goose_db_version.bob.go +++ b/db/models/goose_db_version.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -8,14 +8,14 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" "github.com/aarondl/opt/omit" - "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" ) // GooseDBVersion is an object representing the database table. diff --git a/db/models/h3_aggregation.bob.go b/db/models/h3_aggregation.bob.go index 7e09d5c5..1de3e34a 100644 --- a/db/models/h3_aggregation.bob.go +++ b/db/models/h3_aggregation.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -8,20 +8,20 @@ import ( "fmt" "io" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" ) // H3Aggregation is an object representing the database table. diff --git a/db/models/import.district.bob.go b/db/models/import.district.bob.go index 6f922256..6523d151 100644 --- a/db/models/import.district.bob.go +++ b/db/models/import.district.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -8,20 +8,20 @@ import ( "fmt" "io" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/shopspring/decimal" - "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" ) // ImportDistrict is an object representing the database table. diff --git a/db/models/note_audio.bob.go b/db/models/note_audio.bob.go index 204cb229..66a9e2a6 100644 --- a/db/models/note_audio.bob.go +++ b/db/models/note_audio.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,20 +9,20 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" ) // NoteAudio is an object representing the database table. diff --git a/db/models/note_audio_breadcrumb.bob.go b/db/models/note_audio_breadcrumb.bob.go index 67165c53..c25e40b8 100644 --- a/db/models/note_audio_breadcrumb.bob.go +++ b/db/models/note_audio_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,18 +9,18 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "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" ) // NoteAudioBreadcrumb is an object representing the database table. diff --git a/db/models/note_audio_data.bob.go b/db/models/note_audio_data.bob.go index 89dd6055..d1304e38 100644 --- a/db/models/note_audio_data.bob.go +++ b/db/models/note_audio_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,19 +9,19 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "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" ) // NoteAudioDatum is an object representing the database table. diff --git a/db/models/note_image.bob.go b/db/models/note_image.bob.go index 34e4edd2..2185c6e9 100644 --- a/db/models/note_image.bob.go +++ b/db/models/note_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,20 +9,20 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" ) // NoteImage is an object representing the database table. diff --git a/db/models/note_image_breadcrumb.bob.go b/db/models/note_image_breadcrumb.bob.go index be232187..2cfc2f79 100644 --- a/db/models/note_image_breadcrumb.bob.go +++ b/db/models/note_image_breadcrumb.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,18 +9,18 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "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" ) // NoteImageBreadcrumb is an object representing the database table. diff --git a/db/models/note_image_data.bob.go b/db/models/note_image_data.bob.go index 2eb3f729..8bac0d0a 100644 --- a/db/models/note_image_data.bob.go +++ b/db/models/note_image_data.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,19 +9,19 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "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" ) // NoteImageDatum is an object representing the database table. diff --git a/db/models/notification.bob.go b/db/models/notification.bob.go index ee150774..b2166395 100644 --- a/db/models/notification.bob.go +++ b/db/models/notification.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,20 +9,20 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" ) // Notification is an object representing the database table. diff --git a/db/models/oauth_token.bob.go b/db/models/oauth_token.bob.go index 9b8c7156..8d8efe52 100644 --- a/db/models/oauth_token.bob.go +++ b/db/models/oauth_token.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,19 +9,19 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" ) // OauthToken is an object representing the database table. diff --git a/db/models/organization.bob.go b/db/models/organization.bob.go index 8876432c..40ce86eb 100644 --- a/db/models/organization.bob.go +++ b/db/models/organization.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -8,20 +8,20 @@ import ( "fmt" "io" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" ) // Organization is an object representing the database table. diff --git a/db/models/publicreport.image.bob.go b/db/models/publicreport.image.bob.go index 0dc68ab3..c0601c2b 100644 --- a/db/models/publicreport.image.bob.go +++ b/db/models/publicreport.image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,20 +10,20 @@ import ( "strconv" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "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" "github.com/stephenafamo/scan" ) diff --git a/db/models/publicreport.image_exif.bob.go b/db/models/publicreport.image_exif.bob.go index bc4ac461..d7050087 100644 --- a/db/models/publicreport.image_exif.bob.go +++ b/db/models/publicreport.image_exif.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -8,17 +8,17 @@ import ( "fmt" "io" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/omit" - "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" ) // PublicreportImageExif is an object representing the database table. diff --git a/db/models/publicreport.nuisance.bob.go b/db/models/publicreport.nuisance.bob.go index 0797594d..ad16bd4f 100644 --- a/db/models/publicreport.nuisance.bob.go +++ b/db/models/publicreport.nuisance.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,20 +9,20 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" ) // PublicreportNuisance is an object representing the database table. diff --git a/db/models/publicreport.pool.bob.go b/db/models/publicreport.pool.bob.go index 3b5f7c08..a43fbcf9 100644 --- a/db/models/publicreport.pool.bob.go +++ b/db/models/publicreport.pool.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,20 +10,20 @@ import ( "strconv" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" "github.com/stephenafamo/scan" ) diff --git a/db/models/publicreport.pool_image.bob.go b/db/models/publicreport.pool_image.bob.go index 3547232d..289ae655 100644 --- a/db/models/publicreport.pool_image.bob.go +++ b/db/models/publicreport.pool_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -8,17 +8,17 @@ import ( "fmt" "io" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/omit" - "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" ) // PublicreportPoolImage is an object representing the database table. diff --git a/db/models/publicreport.quick.bob.go b/db/models/publicreport.quick.bob.go index 0486aebb..3e4ae67d 100644 --- a/db/models/publicreport.quick.bob.go +++ b/db/models/publicreport.quick.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -10,20 +10,20 @@ import ( "strconv" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" "github.com/stephenafamo/scan" ) diff --git a/db/models/publicreport.quick_image.bob.go b/db/models/publicreport.quick_image.bob.go index 9d78ab8c..5bf0c644 100644 --- a/db/models/publicreport.quick_image.bob.go +++ b/db/models/publicreport.quick_image.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -8,17 +8,17 @@ import ( "fmt" "io" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/aarondl/opt/omit" - "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" ) // PublicreportQuickImage is an object representing the database table. diff --git a/db/models/publicreport.report_location.bob.go b/db/models/publicreport.report_location.bob.go index 1bbb46e9..04348b85 100644 --- a/db/models/publicreport.report_location.bob.go +++ b/db/models/publicreport.report_location.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -7,11 +7,11 @@ import ( "context" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/expr" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/null" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/expr" ) // PublicreportReportLocation is an object representing the database table. diff --git a/db/models/raster_columns.bob.go b/db/models/raster_columns.bob.go index a97fc1cb..790d214b 100644 --- a/db/models/raster_columns.bob.go +++ b/db/models/raster_columns.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -6,11 +6,11 @@ package models import ( "context" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/expr" "github.com/aarondl/opt/null" "github.com/lib/pq" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/expr" ) // RasterColumn is an object representing the database table. diff --git a/db/models/raster_overviews.bob.go b/db/models/raster_overviews.bob.go index bc5115b3..6dbf105c 100644 --- a/db/models/raster_overviews.bob.go +++ b/db/models/raster_overviews.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -6,10 +6,10 @@ package models import ( "context" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/expr" "github.com/aarondl/opt/null" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/expr" ) // RasterOverview is an object representing the database table. diff --git a/db/models/sessions.bob.go b/db/models/sessions.bob.go index 4899065f..0ebf852f 100644 --- a/db/models/sessions.bob.go +++ b/db/models/sessions.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -8,14 +8,14 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" "github.com/aarondl/opt/omit" - "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" ) // Session is an object representing the database table. diff --git a/db/models/spatial_ref_sys.bob.go b/db/models/spatial_ref_sys.bob.go index 651d4ad9..f4fce111 100644 --- a/db/models/spatial_ref_sys.bob.go +++ b/db/models/spatial_ref_sys.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -7,16 +7,16 @@ import ( "context" "io" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" ) // SpatialRefSy is an object representing the database table. diff --git a/db/models/user_.bob.go b/db/models/user_.bob.go index 2b8e80c2..9b74ebf1 100644 --- a/db/models/user_.bob.go +++ b/db/models/user_.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package models @@ -9,20 +9,20 @@ import ( "io" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/dialect/psql/dm" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/mods" + "github.com/Gleipnir-Technology/bob/orm" + "github.com/Gleipnir-Technology/bob/types/pgtypes" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/null" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" - "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" ) // User is an object representing the database table. diff --git a/db/prepared.go b/db/prepared.go index 29d97c1b..f66277ba 100644 --- a/db/prepared.go +++ b/db/prepared.go @@ -9,13 +9,11 @@ import ( "strings" "time" - //"github.com/stephenafamo/bob" - //"github.com/stephenafamo/bob/dialect/psql" fslayer "github.com/Gleipnir-Technology/arcgis-go/fieldseeker/layer" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" "github.com/google/uuid" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" "github.com/stephenafamo/scan" ) diff --git a/db/sql/org_by_oauth_id.bob.go b/db/sql/org_by_oauth_id.bob.go index f9b35ccd..e780eaed 100644 --- a/db/sql/org_by_oauth_id.bob.go +++ b/db/sql/org_by_oauth_id.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -9,11 +9,11 @@ import ( "io" "iter" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/orm" "github.com/aarondl/opt/null" - "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" ) diff --git a/db/sql/org_by_oauth_id.bob.sql b/db/sql/org_by_oauth_id.bob.sql index f0945529..800e0e6e 100644 --- a/db/sql/org_by_oauth_id.bob.sql +++ b/db/sql/org_by_oauth_id.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.42.1. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.5. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- OrgByOauthId diff --git a/db/sql/publicreport_image_with_json_by_quick_id.bob.go b/db/sql/publicreport_image_with_json_by_quick_id.bob.go index 19c257f8..747a6609 100644 --- a/db/sql/publicreport_image_with_json_by_quick_id.bob.go +++ b/db/sql/publicreport_image_with_json_by_quick_id.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -10,12 +10,12 @@ import ( "iter" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/orm" "github.com/aarondl/opt/null" "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/orm" "github.com/stephenafamo/scan" ) diff --git a/db/sql/publicreport_image_with_json_by_quick_id.bob.sql b/db/sql/publicreport_image_with_json_by_quick_id.bob.sql index d0e37c01..21bf1e29 100644 --- a/db/sql/publicreport_image_with_json_by_quick_id.bob.sql +++ b/db/sql/publicreport_image_with_json_by_quick_id.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.42.1. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.5. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- PublicreportImageWithJSONByQuickID diff --git a/db/sql/publicreport_publicid_table.bob.go b/db/sql/publicreport_publicid_table.bob.go index f52b9b42..e8ea698b 100644 --- a/db/sql/publicreport_publicid_table.bob.go +++ b/db/sql/publicreport_publicid_table.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -9,11 +9,11 @@ import ( "io" "iter" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/orm" "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" ) diff --git a/db/sql/publicreport_publicid_table.bob.sql b/db/sql/publicreport_publicid_table.bob.sql index f9e1c4a6..4be6c4b0 100644 --- a/db/sql/publicreport_publicid_table.bob.sql +++ b/db/sql/publicreport_publicid_table.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.42.1. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.5. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- PublicreportIDTable diff --git a/db/sql/texts_by_senders.bob.go b/db/sql/texts_by_senders.bob.go index 3a8ce5bb..26a9cd1f 100644 --- a/db/sql/texts_by_senders.bob.go +++ b/db/sql/texts_by_senders.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -10,18 +10,18 @@ import ( "iter" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/orm" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" - "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 texts_by_senders.bob.sql var formattedQueries_texts_by_senders string -var textsBySendersSQL = formattedQueries_texts_by_senders[152:393] +var textsBySendersSQL = formattedQueries_texts_by_senders[152:416] type TextsBySendersQuery = orm.ModQuery[*dialect.SelectQuery, textsBySenders, TextsBySendersRow, []TextsBySendersRow, textsBySendersTransformer] @@ -48,8 +48,9 @@ func TextsBySenders(Destination string, Source string) *TextsBySendersQuery { row.ScheduleScanByIndex(2, &t.Created) row.ScheduleScanByIndex(3, &t.Source) row.ScheduleScanByIndex(4, &t.Destination) - row.ScheduleScanByIndex(5, &t.IsWelcome) - row.ScheduleScanByIndex(6, &t.Origin) + row.ScheduleScanByIndex(5, &t.IsVisibleToLLM) + row.ScheduleScanByIndex(6, &t.IsWelcome) + row.ScheduleScanByIndex(7, &t.Origin) return &t, nil }, func(v any) (TextsBySendersRow, error) { return *(v.(*TextsBySendersRow)), nil @@ -57,22 +58,23 @@ func TextsBySenders(Destination string, Source string) *TextsBySendersQuery { }, }, Mod: bob.ModFunc[*dialect.SelectQuery](func(q *dialect.SelectQuery) { - q.AppendSelect(expressionTypArgs.subExpr(12, 97)) - q.SetTable(expressionTypArgs.subExpr(108, 122)) - q.AppendWhere(expressionTypArgs.subExpr(135, 214)) - q.CombinedOrder.AppendOrder(expressionTypArgs.subExpr(230, 241)) + q.AppendSelect(expressionTypArgs.subExpr(12, 120)) + q.SetTable(expressionTypArgs.subExpr(131, 145)) + q.AppendWhere(expressionTypArgs.subExpr(158, 237)) + q.CombinedOrder.AppendOrder(expressionTypArgs.subExpr(253, 264)) }), } } type TextsBySendersRow = struct { - ID int32 `db:"id"` - Content string `db:"content"` - Created time.Time `db:"created"` - Source string `db:"source"` - Destination string `db:"destination"` - IsWelcome bool `db:"is_welcome"` - Origin enums.CommsTextorigin `db:"origin"` + ID int32 `db:"id"` + Content string `db:"content"` + Created time.Time `db:"created"` + Source string `db:"source"` + Destination string `db:"destination"` + IsVisibleToLLM bool `db:"is_visible_to_llm"` + IsWelcome bool `db:"is_welcome"` + Origin enums.CommsTextorigin `db:"origin"` } type textsBySendersTransformer = bob.SliceTransformer[TextsBySendersRow, []TextsBySendersRow] @@ -86,8 +88,8 @@ func (o textsBySenders) args() iter.Seq[orm.ArgWithPosition] { return func(yield func(arg orm.ArgWithPosition) bool) { if !yield(orm.ArgWithPosition{ Name: "destination", - Start: 144, - Stop: 146, + Start: 167, + Stop: 169, Expression: o.Destination, }) { return @@ -95,8 +97,8 @@ func (o textsBySenders) args() iter.Seq[orm.ArgWithPosition] { if !yield(orm.ArgWithPosition{ Name: "source", - Start: 165, - Stop: 167, + Start: 188, + Stop: 190, Expression: o.Source, }) { return @@ -104,8 +106,8 @@ func (o textsBySenders) args() iter.Seq[orm.ArgWithPosition] { if !yield(orm.ArgWithPosition{ Name: "source", - Start: 191, - Stop: 193, + Start: 214, + Stop: 216, Expression: o.Source, }) { return @@ -113,8 +115,8 @@ func (o textsBySenders) args() iter.Seq[orm.ArgWithPosition] { if !yield(orm.ArgWithPosition{ Name: "destination", - Start: 212, - Stop: 214, + Start: 235, + Stop: 237, Expression: o.Destination, }) { return diff --git a/db/sql/texts_by_senders.bob.sql b/db/sql/texts_by_senders.bob.sql index fbe09f0c..ec0a7f30 100644 --- a/db/sql/texts_by_senders.bob.sql +++ b/db/sql/texts_by_senders.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.42.1. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.5. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- TextsBySenders @@ -8,6 +8,7 @@ SELECT created, source, destination, + is_visible_to_llm, is_welcome, origin FROM diff --git a/db/sql/texts_by_senders.sql b/db/sql/texts_by_senders.sql index 80fabedf..945e832e 100644 --- a/db/sql/texts_by_senders.sql +++ b/db/sql/texts_by_senders.sql @@ -5,6 +5,7 @@ SELECT created, source, destination, + is_visible_to_llm, is_welcome, origin FROM diff --git a/db/sql/trapcount_by_location_id.bob.go b/db/sql/trapcount_by_location_id.bob.go index 73475582..42afb22b 100644 --- a/db/sql/trapcount_by_location_id.bob.go +++ b/db/sql/trapcount_by_location_id.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -10,13 +10,13 @@ import ( "iter" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/orm" "github.com/aarondl/opt/null" "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/expr" - "github.com/stephenafamo/bob/orm" "github.com/stephenafamo/scan" ) diff --git a/db/sql/trapcount_by_location_id.bob.sql b/db/sql/trapcount_by_location_id.bob.sql index 6e76e700..cf65e9eb 100644 --- a/db/sql/trapcount_by_location_id.bob.sql +++ b/db/sql/trapcount_by_location_id.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.42.1. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.5. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- TrapCountByLocationID diff --git a/db/sql/trapdata_by_location_id_recent.bob.go b/db/sql/trapdata_by_location_id_recent.bob.go index 704f722c..b2e7c827 100644 --- a/db/sql/trapdata_by_location_id_recent.bob.go +++ b/db/sql/trapdata_by_location_id_recent.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -10,12 +10,12 @@ import ( "iter" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/expr" + "github.com/Gleipnir-Technology/bob/orm" "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/expr" - "github.com/stephenafamo/bob/orm" "github.com/stephenafamo/scan" ) diff --git a/db/sql/trapdata_by_location_id_recent.bob.sql b/db/sql/trapdata_by_location_id_recent.bob.sql index 917e2ee6..726b461e 100644 --- a/db/sql/trapdata_by_location_id_recent.bob.sql +++ b/db/sql/trapdata_by_location_id_recent.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.42.1. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.5. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- TrapDataByLocationIDRecent diff --git a/db/sql/traplocation_by_source_id.bob.go b/db/sql/traplocation_by_source_id.bob.go index d0b437e3..668cda9c 100644 --- a/db/sql/traplocation_by_source_id.bob.go +++ b/db/sql/traplocation_by_source_id.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -9,11 +9,11 @@ import ( "io" "iter" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/orm" "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/orm" "github.com/stephenafamo/scan" ) diff --git a/db/sql/traplocation_by_source_id.bob.sql b/db/sql/traplocation_by_source_id.bob.sql index b4610521..98ed3556 100644 --- a/db/sql/traplocation_by_source_id.bob.sql +++ b/db/sql/traplocation_by_source_id.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.42.1. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.5. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- TrapLocationBySourceID diff --git a/db/sql/update_oauth_org.bob.go b/db/sql/update_oauth_org.bob.go index 7bb34302..85591579 100644 --- a/db/sql/update_oauth_org.bob.go +++ b/db/sql/update_oauth_org.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -9,10 +9,10 @@ import ( "io" "iter" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/dialect" - "github.com/stephenafamo/bob/orm" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/orm" ) //go:embed update_oauth_org.bob.sql diff --git a/db/sql/update_oauth_org.bob.sql b/db/sql/update_oauth_org.bob.sql index 64bdb6cb..37337db6 100644 --- a/db/sql/update_oauth_org.bob.sql +++ b/db/sql/update_oauth_org.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.42.1. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.5. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- UpdateOauthTokenOrg diff --git a/db/sql/user_by_username.bob.go b/db/sql/user_by_username.bob.go index 4ee9ec2f..3ae2301d 100644 --- a/db/sql/user_by_username.bob.go +++ b/db/sql/user_by_username.bob.go @@ -1,4 +1,4 @@ -// Code generated by BobGen psql v0.42.1. DO NOT EDIT. +// Code generated by BobGen psql v0.42.5. DO NOT EDIT. // This file is meant to be re-generated in place and/or deleted at any time. package sql @@ -10,12 +10,12 @@ import ( "iter" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/dialect" + "github.com/Gleipnir-Technology/bob/orm" enums "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/aarondl/opt/null" - "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" ) diff --git a/db/sql/user_by_username.bob.sql b/db/sql/user_by_username.bob.sql index 9526a44e..414622c9 100644 --- a/db/sql/user_by_username.bob.sql +++ b/db/sql/user_by_username.bob.sql @@ -1,4 +1,4 @@ --- Code generated by BobGen psql v0.42.1. DO NOT EDIT. +-- Code generated by BobGen psql v0.42.5. DO NOT EDIT. -- This file is meant to be re-generated in place and/or deleted at any time. -- UserByUsername diff --git a/go.mod b/go.mod index fdc15bd7..2acd32bb 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.24.9 require ( github.com/Gleipnir-Technology/arcgis-go v0.0.6 + github.com/Gleipnir-Technology/bob v0.42.5 github.com/Gleipnir-Technology/go-geojson2h3/v2 v2.0.0 github.com/aarondl/opt v0.0.0-20250607033636-982744e1bd65 github.com/alexedwards/scs/pgxstore v0.0.0-20251002162104-209de6e426de @@ -24,7 +25,6 @@ require ( github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd github.com/shopspring/decimal v1.4.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - github.com/stephenafamo/bob v0.42.0 github.com/stephenafamo/scan v0.7.0 github.com/tidwall/geojson v1.4.5 github.com/twilio/twilio-go v1.29.1 @@ -81,3 +81,4 @@ require ( google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) +// replace github.com/stephenafamo/bob v0.42.0 => ../bob diff --git a/go.sum b/go.sum index f17eabfd..6a807f06 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Gleipnir-Technology/arcgis-go v0.0.6 h1:h21+ijW5NNAgRoBn2ktJwfmbgFX6f/CdlUojB4xQCWo= github.com/Gleipnir-Technology/arcgis-go v0.0.6/go.mod h1:Stx2sn5Lvuyhy4SaTQpbLNCAfenboDINi/UU5gQvz4k= +github.com/Gleipnir-Technology/bob v0.42.5 h1:fm4vH48E7scLwMFSJ4fX3+q2wSo+6Iphh+yVIrMgatE= +github.com/Gleipnir-Technology/bob v0.42.5/go.mod h1:cjUNiSRIMBsk94NQQrpYoraCe0WxIc04C8A+PcJ5z8Q= github.com/Gleipnir-Technology/go-geojson2h3/v2 v2.0.0 h1:6OMVxoiX9r7dEkIyYYKtSu7I2UDq64dww4JxJTo3p78= github.com/Gleipnir-Technology/go-geojson2h3/v2 v2.0.0/go.mod h1:W77HoRoEXUWAc24AbDHIaSH2U4vJNWgNRpEuySXzqDs= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= diff --git a/llm/client.go b/llm/client.go index af53d5f2..419b9b0c 100644 --- a/llm/client.go +++ b/llm/client.go @@ -3,6 +3,9 @@ package llm import ( "context" "fmt" + "strings" + + "github.com/maruel/genai" //"github.com/rs/zerolog/log" ) @@ -11,10 +14,59 @@ type Message struct { IsFromCustomer bool } -func GenerateNextMessage(ctx context.Context, history []Message, customer_phone string) (Message, error) { - next, err := client.continueConversation(ctx, history, customer_phone) +func GenerateNextMessage(ctx context.Context, history []Message, _handle_report_status func() (string, error), _handle_contact_district func(string), _handle_contact_supervisor func(string)) (Message, error) { + msg := convertHistory(history) + tools := genai.OptionsTools{ + Tools: []genai.ToolDef{ + { + Name: "contact_district", + Description: "Reach out to the district to get answers for a customer about their operations or schedule.", + Callback: func(ctx2 context.Context, input *ContactDistrictInput) (string, error) { + _handle_contact_district(input.Reason) + return "district has been contacted.", nil + }, + }, { + Name: "contact_supervisor", + Description: "Flag a conversation from a customer as abusive, concerning, or off-topic.", + Callback: func(ctx2 context.Context, input *ContactSupervisorInput) (string, error) { + _handle_contact_supervisor(input.Reason) + return "supervisor has been notified", nil + }, + }, { + Name: "query_report_status", + Description: "This is used to answer any questions about the current state of the mosquito nuisance report.", + Callback: func(ctx2 context.Context, input *QueryReportStatusInput) (string, error) { + return _handle_report_status() + }, + }, + }, + } + next, err := client.continueConversation(ctx, tools, msg) if err != nil { return Message{}, fmt.Errorf("Failed to generate next message: %w", err) } + return next, nil } +func convertHistory(history []Message) genai.Message { + var sb strings.Builder + sb.WriteString( + `This is a text chat conversation between a customer that's a member of the public and a mosquito abatement district. + The customer has reported a mosquito nuisance or mosquito breeding through the website report.mosquitoes.online. + Messages from the customer are prefixed with 'customer:' and reponses from the service agent servicing the request are prefixed with 'agent:'. + The agent provides clear, confident, and succint information about the state of the customer's request. + The agent answers just the questions that are asked, and prefers very short answers because the conversation is happening over SMS. + The agent rarely asks questions, preferring to just answer direct queries. + For complex or highly specific requests, the agent will need to defer to the mosquito abatement district. This will take some time because contacting the district may take a few hours to get a response. When the agent needs to contact the district, the agent should tell the customer they are reaching out to the district and to expect a delay. + When conversations start to veer away from the agent's job they should contact a supervisor. + Transcript starts:`, + ) + for _, h := range history { + if h.IsFromCustomer { + sb.WriteString(fmt.Sprintf("\n\ncustomer (%s): %s\n", h.Content)) + } else { + sb.WriteString(fmt.Sprintf("\n\nagent (%s): %s\n", h.Content)) + } + } + return genai.NewTextMessage(sb.String()) +} diff --git a/llm/log.go b/llm/log.go index 52c48989..9e305727 100644 --- a/llm/log.go +++ b/llm/log.go @@ -3,7 +3,6 @@ package llm import ( "log" "strings" - "time" "github.com/rs/zerolog" "go.mau.fi/util/exzerolog" @@ -11,14 +10,8 @@ import ( type Logger = zerolog.Logger -func createLogger() *Logger { - l := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { - //w.Out = io.Writer(buf) - w.TimeFormat = time.Stamp - })).With().Timestamp().Logger() - zerolog.TimeFieldFormat = zerolog.TimeFormatUnix - exzerolog.SetupDefaults(&l) - return &l +func linkLogger(logger *zerolog.Logger) { + exzerolog.SetupDefaults(logger) } type ZerologWriter struct { diff --git a/llm/openai.go b/llm/openai.go index b971d1a3..1712100d 100644 --- a/llm/openai.go +++ b/llm/openai.go @@ -8,11 +8,11 @@ import ( "github.com/maruel/genai" "github.com/maruel/genai/adapters" "github.com/maruel/genai/providers/openaichat" - "github.com/rs/zerolog/log" + "github.com/rs/zerolog" ) -func CreateOpenAIClient(ctx context.Context) error { - logger := createLogger() +func CreateOpenAIClient(ctx context.Context, logger *zerolog.Logger) error { + linkLogger(logger) opts := genai.ProviderOptions{ Model: genai.ModelCheap, @@ -35,27 +35,22 @@ type openAIClient struct { log *Logger } +type ContactSupervisorInput struct { + Reason string `json:"reason"` +} + +type ContactDistrictInput struct { + Reason string `json:"reason"` +} + type QueryReportStatusInput struct { ReportID string `json:"report_id"` } var client *openAIClient -func (c *openAIClient) continueConversation(ctx context.Context, history []Message, customer_phone string) (Message, error) { - opts := genai.OptionsTools{ - Tools: []genai.ToolDef{ - { - Name: "query_report_status", - Description: "This is used to answer any questions about the current state of the mosquito nuisance report.", - Callback: func(ctx2 context.Context, input *QueryReportStatusInput) (string, error) { - return c.queryReportStatus(ctx2, customer_phone) - }, - }, - }, - } - - msg := c.convertHistory(history) - res, _, err := adapters.GenSyncWithToolCallLoop(ctx, c.client, genai.Messages{msg}, &opts) +func (c *openAIClient) continueConversation(ctx context.Context, tools genai.OptionsTools, msg genai.Message) (Message, error) { + res, _, err := adapters.GenSyncWithToolCallLoop(ctx, c.client, genai.Messages{msg}, &tools) if err != nil { return Message{}, fmt.Errorf("Failed to continue conversation: %v", err) } @@ -63,7 +58,7 @@ func (c *openAIClient) continueConversation(ctx context.Context, history []Messa for _, m := range res { // Empty responses are tool call related. if m.String() == "" { - log.Debug().Msg("Tool called") + //log.Debug().Msg("Tool called") } else { var toSay string = m.String() toSay = strings.Replace(toSay, "report-mosquitoes-online: ", "", 1) @@ -76,26 +71,3 @@ func (c *openAIClient) continueConversation(ctx context.Context, history []Messa return Message{}, nil } - -func (c *openAIClient) convertHistory(history []Message) genai.Message { - var sb strings.Builder - sb.WriteString( - `This is a text chat conversation between a customer that's a member of the public and a mosquito abatement district. - The customer has reported a mosquito nuisance or mosquito breeding through the website report.mosquitoes.online. - Messages from the customer are prefixed with 'customer:' and reponses from the service agent servicing the request are prefixed with 'agent:'. - The agent wants to provide clear, confident, and succint information about the state of the customer's request. The agent also provides general information about how members of the public can help with controlling mosquitoes. For complex or highly specific requests, the agent will need to defer to the mosquito abatement district. This will take some time because contacting the district may take a few hours to get a response. When the agent needs to contact the district, the agent should tell the customer they are reaching out to the district and to expect a delay. - Transcript starts:`, - ) - for _, h := range history { - if h.IsFromCustomer { - sb.WriteString(fmt.Sprintf("\n\ncustomer (%s): %s\n", h.Content)) - } else { - sb.WriteString(fmt.Sprintf("\n\nagent (%s): %s\n", h.Content)) - } - } - return genai.NewTextMessage(sb.String()) -} - -func (c *openAIClient) queryReportStatus(ctx context.Context, customer_phone string) (string, error) { - return "Report is scheduled for work in 3 days at 2:00pm by the district", nil -} diff --git a/main.go b/main.go index 012f9372..426ea7c3 100644 --- a/main.go +++ b/main.go @@ -77,7 +77,8 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - err = llm.CreateOpenAIClient(ctx) + openai_logger := log.With().Logger() + err = llm.CreateOpenAIClient(ctx, &openai_logger) if err != nil { log.Error().Err(err).Msg("Failed to start openAI client") os.Exit(5) diff --git a/platform/district.go b/platform/district.go index 076848d5..ec3bffab 100644 --- a/platform/district.go +++ b/platform/district.go @@ -5,12 +5,12 @@ import ( "errors" "fmt" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/sm" ) func DistrictForLocation(ctx context.Context, lng float64, lat float64) (*models.ImportDistrict, *models.Organization, error) { diff --git a/platform/text.go b/platform/text.go index cebc16e4..8d76da0f 100644 --- a/platform/text.go +++ b/platform/text.go @@ -6,6 +6,8 @@ import ( "strings" "time" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" "github.com/Gleipnir-Technology/nidus-sync/comms/text" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" @@ -38,9 +40,9 @@ func HandleTextMessage(from string, to string, body string) { log.Error().Err(err).Msg("Failed to handle message") return } + body_l := strings.TrimSpace(strings.ToLower(body)) // We don't know if they're subscribed or not. if subscribed == nil { - body_l := strings.TrimSpace(strings.ToLower(body)) switch body_l { case "stop": setSubscribed(ctx, src, false) @@ -54,20 +56,25 @@ func HandleTextMessage(from string, to string, body string) { log.Error().Err(err).Msg("Failed to add reiteration to the text log") return }*/ - err = sendText(ctx, src, dst, content, enums.CommsTextoriginReiteration, false) + err = sendText(ctx, dst, src, content, enums.CommsTextoriginReiteration, false) if err != nil { log.Error().Err(err).Msg("Failed to resend initial prompt.") } } return } - previous_messages, err := loadPreviousMessages(ctx, dst, src) + // If we get the super-special "reset conversation" then wipe the LLM's memory + if body_l == "reset conversation" { + handleResetConversation(ctx, src, dst) + return + } + previous_messages, err := loadPreviousMessagesForLLM(ctx, dst, src) if err != nil { log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to get previous messages") return } log.Info().Int("len", len(previous_messages)).Msg("passing") - next_message, err := llm.GenerateNextMessage(ctx, previous_messages, src) + next_message, err := generateNextMessage(ctx, previous_messages, src) if err != nil { log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to generate next message") return @@ -79,7 +86,7 @@ func HandleTextMessage(from string, to string, body string) { return } */ - err = sendText(ctx, dst, src, next_message.Content, enums.CommsTextoriginLLM, false) + err = sendText(ctx, src, dst, next_message.Content, enums.CommsTextoriginLLM, false) if err != nil { log.Error().Err(err).Str("src", src).Str("dst", dst).Str("content", next_message.Content).Msg("Failed to send response text") return @@ -166,6 +173,19 @@ func ensureInDB(ctx context.Context, destination string) (err error) { return nil } +func generateNextMessage(ctx context.Context, history []llm.Message, customer_phone string) (llm.Message, error) { + _handle_report_status := func() (string, error) { + return "Report: ABCD-1234-5678, Status: scheduled, Appointment: Wednesday 3:30pm", nil + } + _handle_contact_district := func(reason string) { + log.Warn().Str("reason", reason).Msg("Contacting district") + } + _handle_contact_supervisor := func(reason string) { + log.Warn().Str("reason", reason).Msg("Contacting supervisor") + } + return llm.GenerateNextMessage(ctx, history, _handle_report_status, _handle_contact_district, _handle_contact_supervisor) +} + // Translate from Twilio's representation of a RCS message sender to our concept of a phone number // From: rcs:dev_report_mosquitoes_online_dosrvwxm_agent // To: +16235525879 @@ -188,18 +208,38 @@ func handleWaitingTextJobs(ctx context.Context, src string) { log.Info().Str("src", src).Msg("Pretend handle waiting jobs") } +func handleResetConversation(ctx context.Context, src string, dst string) { + err := wipeLLMMemory(ctx, src, dst) + if err != nil { + log.Error().Err(err).Str("src", src).Str("dst", dst).Msg("Failed to wipe memory") + content := "Failed to wip memory" + err = sendText(ctx, dst, src, content, enums.CommsTextoriginCommandResponse, false) + if err != nil { + log.Error().Err(err).Str("src", src).Str("dst", dst).Msg("Failed to indicated memory wipe failure.") + } + return + } + content := "LLM memory wiped" + err = sendText(ctx, dst, src, content, enums.CommsTextoriginCommandResponse, false) + if err != nil { + log.Error().Err(err).Str("src", src).Str("dst", dst).Msg("Failed to indicated memory wiped.") + return + } + log.Info().Err(err).Str("src", src).Str("dst", dst).Msg("Wiped LLM memory") +} func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin, is_welcome bool) (log *models.CommsTextLog, err error) { log, err = models.CommsTextLogs.Insert(&models.CommsTextLogSetter{ //ID: - Content: omit.From(content), - Created: omit.From(time.Now()), - Destination: omit.From(destination), - IsWelcome: omit.From(is_welcome), - Origin: omit.From(origin), - Source: omit.From(source), - TwilioSid: omitnull.FromPtr[string](nil), - TwilioStatus: omit.From(""), + Content: omit.From(content), + Created: omit.From(time.Now()), + Destination: omit.From(destination), + IsVisibleToLLM: omit.From(true), + IsWelcome: omit.From(is_welcome), + Origin: omit.From(origin), + Source: omit.From(source), + TwilioSid: omitnull.FromPtr[string](nil), + TwilioStatus: omit.From(""), }).One(ctx, db.PGInstance.BobDB) return log, err @@ -217,7 +257,7 @@ func isSubscribed(ctx context.Context, src string) (*bool, error) { return &result, nil } -func loadPreviousMessages(ctx context.Context, dst, src string) ([]llm.Message, error) { +func loadPreviousMessagesForLLM(ctx context.Context, dst, src string) ([]llm.Message, error) { messages, err := sql.TextsBySenders(dst, src).All(ctx, db.PGInstance.BobDB) results := make([]llm.Message, 0) if err != nil { @@ -225,11 +265,13 @@ func loadPreviousMessages(ctx context.Context, dst, src string) ([]llm.Message, } log.Info().Int("count", len(messages)).Str("src", src).Str("dst", dst).Msg("Found previous messages") for _, m := range messages { - is_from_customer := (m.Source == src) - results = append(results, llm.Message{ - IsFromCustomer: is_from_customer, - Content: m.Content, - }) + if m.IsVisibleToLLM { + is_from_customer := (m.Source == src) + results = append(results, llm.Message{ + IsFromCustomer: is_from_customer, + Content: m.Content, + }) + } } return results, nil } @@ -282,3 +324,25 @@ func setSubscribed(ctx context.Context, src string, is_subscribed bool) error { log.Info().Str("src", src).Bool("is_subscribed", is_subscribed).Msg("Set number subscribed") return nil } + +func wipeLLMMemory(ctx context.Context, src string, dst string) error { + rows, err := sql.TextsBySenders(dst, src).All(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to query for texts: %w", err) + } + ids := make([]int32, 0) + for _, r := range rows { + ids = append(ids, r.ID) + } + _, err = models.CommsTextLogs.Update( + um.Where( + models.CommsTextLogs.Columns.ID.EQ(psql.Any(ids)), + ), + um.SetCol("is_visible_to_llm").ToArg(false), + ).Exec(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to update texts: %w", err) + } + + return nil +} diff --git a/public-report/image-upload.go b/public-report/image-upload.go index b566f06e..a6ccb71d 100644 --- a/public-report/image-upload.go +++ b/public-report/image-upload.go @@ -13,6 +13,9 @@ import ( "net/http" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/userfile" "github.com/aarondl/opt/omit" @@ -20,9 +23,6 @@ import ( "github.com/google/uuid" "github.com/rs/zerolog/log" "github.com/rwcarlsen/goexif/exif" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/um" //exif "github.com/rwcarlsen/goexif/exif" //"github.com/dsoprea/go-exif-extra/format" ) diff --git a/public-report/pool.go b/public-report/pool.go index 6872c8a7..087f5301 100644 --- a/public-report/pool.go +++ b/public-report/pool.go @@ -6,6 +6,9 @@ import ( "strconv" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/enums" @@ -13,9 +16,6 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/aarondl/opt/omit" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/um" ) type ContextPool struct { diff --git a/public-report/quick.go b/public-report/quick.go index beb95308..4feea1a5 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -7,6 +7,9 @@ import ( "strconv" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/um" "github.com/Gleipnir-Technology/nidus-sync/background" "github.com/Gleipnir-Technology/nidus-sync/comms/text" "github.com/Gleipnir-Technology/nidus-sync/config" @@ -19,9 +22,6 @@ import ( "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/um" ) type ContentQuick struct{} diff --git a/public-report/status.go b/public-report/status.go index 02b908be..7b0f39ae 100644 --- a/public-report/status.go +++ b/public-report/status.go @@ -7,6 +7,9 @@ import ( "strings" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" @@ -14,9 +17,6 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/go-chi/chi/v5" "github.com/rs/zerolog/log" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/sm" "github.com/stephenafamo/scan" /* "github.com/Gleipnir-Technology/nidus-sync/db" diff --git a/query.go b/query.go index a13d70d8..0b50022e 100644 --- a/query.go +++ b/query.go @@ -5,8 +5,8 @@ import ( "context" "fmt" "io" - //"github.com/stephenafamo/bob" - //"github.com/stephenafamo/bob/dialect/psql" + //"github.com/Gleipnir-Technology/bob" + //"github.com/Gleipnir-Technology/bob/dialect/psql" ) type QueryWriter interface { diff --git a/sync/dash.go b/sync/dash.go index c0233739..54ab9abd 100644 --- a/sync/dash.go +++ b/sync/dash.go @@ -7,6 +7,7 @@ import ( "net/http" "time" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" "github.com/Gleipnir-Technology/nidus-sync/auth" "github.com/Gleipnir-Technology/nidus-sync/background" "github.com/Gleipnir-Technology/nidus-sync/config" @@ -16,7 +17,6 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/go-chi/chi/v5" "github.com/google/uuid" - "github.com/stephenafamo/bob/dialect/psql/sm" "github.com/uber/h3-go/v4" ) diff --git a/sync/utils.go b/sync/utils.go index 94f2dcd7..4af735d6 100644 --- a/sync/utils.go +++ b/sync/utils.go @@ -6,14 +6,14 @@ import ( "strings" "time" + "github.com/Gleipnir-Technology/bob" + "github.com/Gleipnir-Technology/bob/dialect/psql" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/sql" "github.com/Gleipnir-Technology/nidus-sync/notification" "github.com/google/uuid" - "github.com/stephenafamo/bob" - "github.com/stephenafamo/bob/dialect/psql" - "github.com/stephenafamo/bob/dialect/psql/sm" "github.com/uber/h3-go/v4" ) From 9914274d42f00b6144807f0cb5e08c3b907c077a Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 27 Jan 2026 19:56:26 +0000 Subject: [PATCH 0134/1453] Wire in agent to the reporter texting system Also rework the so the platform absorbs all the business logic that was going in the wrong place. --- api/twilio.go | 6 +- auth/auth.go | 2 +- background/background.go | 2 +- background/email.go | 1 + background/text.go | 5 +- comms/text/text.go | 9 +-- llm/log.go | 4 +- main.go | 4 +- {comms => platform}/text/db.go | 0 {comms => platform}/text/job.go | 0 {comms => platform}/text/llm.go | 0 .../text/report-subscription.go | 48 +++++------ platform/{ => text}/text.go | 79 +++++++++++-------- public-report/quick.go | 2 +- 14 files changed, 86 insertions(+), 76 deletions(-) rename {comms => platform}/text/db.go (100%) rename {comms => platform}/text/job.go (100%) rename {comms => platform}/text/llm.go (100%) rename {comms => platform}/text/report-subscription.go (53%) rename platform/{ => text}/text.go (87%) diff --git a/api/twilio.go b/api/twilio.go index 91ee54ba..ef1d205c 100644 --- a/api/twilio.go +++ b/api/twilio.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - "github.com/Gleipnir-Technology/nidus-sync/platform" + "github.com/Gleipnir-Technology/nidus-sync/platform/text" "github.com/rs/zerolog/log" "github.com/twilio/twilio-go/twiml" ) @@ -18,7 +18,7 @@ func twilioStatusPost(w http.ResponseWriter, r *http.Request) { message_sid := r.PostFormValue("MessageSid") message_status := r.PostFormValue("MessageStatus") log.Info().Str("sid", message_sid).Str("status", message_status).Msg("Updated message status") - platform.UpdateMessageStatus(message_sid, message_status) + text.UpdateMessageStatus(message_sid, message_status) fmt.Fprintf(w, "") } func twilioTextPost(w http.ResponseWriter, r *http.Request) { @@ -43,7 +43,7 @@ func twilioTextPost(w http.ResponseWriter, r *http.Request) { log.Info().Str("message_sid", message_sid).Str("account_sid", account_sid).Str("messaging_service_sid", messaging_service_sid).Str("from", from).Str("to_", to_).Str("body", body).Str("num_media", num_media).Str("num_segments", num_segments).Str("media_content_type0", media_content_type0).Str("media_url0", media_url0).Str("from_city", from_city).Str("from_state", from_state).Str("from_zip", from_zip).Str("from_country", from_country).Str("to_city", to_city).Str("to_state", to_state).Str("to_zip", to_zip).Str("to_country", to_country).Msg("got text") twiml, _ := twiml.Messages([]twiml.Element{}) - go platform.HandleTextMessage(from, to_, body) + go text.HandleTextMessage(from, to_, body) w.Header().Set("Content-Type", "text/xml") fmt.Fprintf(w, "%s", twiml) } diff --git a/auth/auth.go b/auth/auth.go index df88736b..6599d328 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -178,7 +178,7 @@ func findUser(ctx context.Context, user_id int) (*models.User, error) { return nil, err } } - log.Info().Int32("user_id", user.ID).Int32("org_id", user.OrganizationID).Msg("Found user") + //log.Info().Int32("user_id", user.ID).Int32("org_id", user.OrganizationID).Msg("Found user") return user, err } diff --git a/background/background.go b/background/background.go index a5c44584..92705b81 100644 --- a/background/background.go +++ b/background/background.go @@ -5,7 +5,7 @@ import ( "sync" "github.com/Gleipnir-Technology/nidus-sync/comms/email" - "github.com/Gleipnir-Technology/nidus-sync/comms/text" + "github.com/Gleipnir-Technology/nidus-sync/platform/text" ) var waitGroup sync.WaitGroup diff --git a/background/email.go b/background/email.go index ea05ac9f..ccdd464c 100644 --- a/background/email.go +++ b/background/email.go @@ -27,6 +27,7 @@ func enqueueJobEmail(job email.Job) { func startWorkerEmail(ctx context.Context, channel chan email.Job) { go func() { + log.Info().Msg("Email worker started") for { select { case <-ctx.Done(): diff --git a/background/text.go b/background/text.go index 91308769..8e9d920a 100644 --- a/background/text.go +++ b/background/text.go @@ -3,8 +3,8 @@ package background import ( "context" - "github.com/Gleipnir-Technology/nidus-sync/comms/text" "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/platform/text" "github.com/rs/zerolog/log" ) @@ -29,10 +29,11 @@ func enqueueJobText(job text.Job) { func startWorkerText(ctx context.Context, channel chan text.Job) { go func() { + log.Info().Msg("Text worker started") for { select { case <-ctx.Done(): - log.Info().Msg("Email worker shutting down.") + log.Info().Msg("Text worker shutting down.") return case job := <-channel: text.Handle(ctx, job) diff --git a/comms/text/text.go b/comms/text/text.go index c98e33fc..018cfdd8 100644 --- a/comms/text/text.go +++ b/comms/text/text.go @@ -6,18 +6,11 @@ import ( "fmt" "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/nyaruka/phonenumbers" "github.com/rs/zerolog/log" "github.com/twilio/twilio-go" twilioApi "github.com/twilio/twilio-go/rest/api/v2010" ) -type E164 = phonenumbers.PhoneNumber - -func ParsePhoneNumber(input string) (*E164, error) { - return phonenumbers.Parse(input, "US") -} - func SendText(ctx context.Context, source string, destination string, message string) (string, error) { client := twilio.NewRestClient() @@ -31,11 +24,11 @@ func SendText(ctx context.Context, source string, destination string, message st if err != nil { return "", fmt.Errorf("Failed to create message to %s: %w", destination, err) } - //log.Info().Str("dest", destination).Str("sid", *resp.Body).Msg("Text message response") if resp.Sid == nil { log.Warn().Str("src", source).Str("dst", destination).Msg("Text message sid is nil") return "", nil } + log.Info().Str("src", source).Str("dst", destination).Str("message", message).Str("sid", *resp.Sid).Msg("Created text message") return *resp.Sid, nil } diff --git a/llm/log.go b/llm/log.go index 9e305727..984bd88d 100644 --- a/llm/log.go +++ b/llm/log.go @@ -5,13 +5,13 @@ import ( "strings" "github.com/rs/zerolog" - "go.mau.fi/util/exzerolog" + //"go.mau.fi/util/exzerolog" ) type Logger = zerolog.Logger func linkLogger(logger *zerolog.Logger) { - exzerolog.SetupDefaults(logger) + //exzerolog.SetupDefaults(logger) } type ZerologWriter struct { diff --git a/main.go b/main.go index 426ea7c3..3c695f90 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/llm" - "github.com/Gleipnir-Technology/nidus-sync/platform" + "github.com/Gleipnir-Technology/nidus-sync/platform/text" "github.com/Gleipnir-Technology/nidus-sync/public-report" nidussync "github.com/Gleipnir-Technology/nidus-sync/sync" "github.com/go-chi/chi/v5" @@ -48,7 +48,7 @@ func main() { os.Exit(3) } - err = platform.TextStoreSources() + err = text.StoreSources() if err != nil { log.Error().Err(err).Msg("Failed to store text source phone numbers") os.Exit(4) diff --git a/comms/text/db.go b/platform/text/db.go similarity index 100% rename from comms/text/db.go rename to platform/text/db.go diff --git a/comms/text/job.go b/platform/text/job.go similarity index 100% rename from comms/text/job.go rename to platform/text/job.go diff --git a/comms/text/llm.go b/platform/text/llm.go similarity index 100% rename from comms/text/llm.go rename to platform/text/llm.go diff --git a/comms/text/report-subscription.go b/platform/text/report-subscription.go similarity index 53% rename from comms/text/report-subscription.go rename to platform/text/report-subscription.go index 12328338..3fe39e84 100644 --- a/comms/text/report-subscription.go +++ b/platform/text/report-subscription.go @@ -4,8 +4,7 @@ import ( "context" "fmt" - //"github.com/Gleipnir-Technology/nidus-sync/db/enums" - //"github.com/Gleipnir-Technology/nidus-sync/platform" + "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/nyaruka/phonenumbers" //"github.com/rs/zerolog/log" ) @@ -44,32 +43,33 @@ func (j jobReportSubscription) source() string { } func sendReportSubscription(ctx context.Context, job Job) error { - /* - j, ok := job.(jobReportSubscription) - if !ok { - return fmt.Errorf("job is not for report subscription confirmation") - } + j, ok := job.(jobReportSubscription) + if !ok { + return fmt.Errorf("job is not for report subscription confirmation") + } - sub, err := isSubscribed(ctx, job.destination()) + sub, err := isSubscribed(ctx, job.destination()) + if err != nil { + return fmt.Errorf("Failed to check if subscribed: %w", err) + } + if sub == nil { + err = delayMessage(ctx, j.source(), j.destination(), j.content(), enums.CommsTextjobtypeReportConfirmation) if err != nil { - return fmt.Errorf("Failed to check if subscribed: %w", err) + return fmt.Errorf("Failed to delay report subscription message: %w", err) } - if !sub { - err = sendText(ctx, j.source(), j.destination(), j.content(), enums.CommsTextoriginWebsiteAction, false) - if err != nil { - return fmt.Errorf("Failed to send report subscription confirmation: %w", err) - } - } else { - err = delayMessage(ctx, j.source(), j.destination(), j.content(), enums.CommsTextjobtypeReportConfirmation) - if err != nil { - return fmt.Errorf("Failed to delay report subscription message: %w", err) - } - err := ensureInitialText(ctx, j.source(), j.destination()) - if err != nil { - return fmt.Errorf("Failed to ensure initial text has been sent: %w", err) - } + err := ensureInitialText(ctx, j.source(), j.destination()) + if err != nil { + return fmt.Errorf("Failed to ensure initial text has been sent: %w", err) } return nil - */ + } + if *sub { + err = sendText(ctx, j.source(), j.destination(), j.content(), enums.CommsTextoriginWebsiteAction, false, true) + if err != nil { + return fmt.Errorf("Failed to send report subscription confirmation: %w", err) + } + } else { + resendInitialText(ctx, j.source(), j.destination()) + } return nil } diff --git a/platform/text.go b/platform/text/text.go similarity index 87% rename from platform/text.go rename to platform/text/text.go index 8d76da0f..84536653 100644 --- a/platform/text.go +++ b/platform/text/text.go @@ -1,4 +1,4 @@ -package platform +package text import ( "context" @@ -21,6 +21,8 @@ import ( "github.com/rs/zerolog/log" ) +type E164 = phonenumbers.PhoneNumber + func HandleTextMessage(from string, to string, body string) { ctx := context.Background() type_, src := splitPhoneSource(from) @@ -30,7 +32,7 @@ func HandleTextMessage(from string, to string, body string) { return } - _, err = insertTextLog(ctx, body, dst, src, enums.CommsTextoriginCustomer, false) + _, err = insertTextLog(ctx, body, dst, src, enums.CommsTextoriginCustomer, false, true) if err != nil { log.Error().Err(err).Str("dst", dst).Msg("Failed to add text message log") return @@ -51,12 +53,7 @@ func HandleTextMessage(from string, to string, body string) { handleWaitingTextJobs(ctx, src) default: content := "I have to start with either 'YES' or 'STOP' first, Which do you want?" - /*err := insertTextLog(ctx, body, src, dst, enums.CommsTextoriginReiteration, false) - if err != nil { - log.Error().Err(err).Msg("Failed to add reiteration to the text log") - return - }*/ - err = sendText(ctx, dst, src, content, enums.CommsTextoriginReiteration, false) + err = sendText(ctx, dst, src, content, enums.CommsTextoriginReiteration, false, false) if err != nil { log.Error().Err(err).Msg("Failed to resend initial prompt.") } @@ -79,14 +76,7 @@ func HandleTextMessage(from string, to string, body string) { log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to generate next message") return } - /* - err = insertTextLog(ctx, next_message.Content, src, dst, enums.CommsTextoriginLLM, false) - if err != nil { - log.Error().Err(err).Str("dst", dst).Msg("Failed to insert new text message to the text log") - return - } - */ - err = sendText(ctx, src, dst, next_message.Content, enums.CommsTextoriginLLM, false) + err = sendText(ctx, dst, src, next_message.Content, enums.CommsTextoriginLLM, false, true) if err != nil { log.Error().Err(err).Str("src", src).Str("dst", dst).Str("content", next_message.Content).Msg("Failed to send response text") return @@ -94,7 +84,11 @@ func HandleTextMessage(from string, to string, body string) { log.Info().Str("from", from).Str("from-type", type_).Str("to", to).Str("src", src).Str("dst", dst).Str("body", body).Str("reply", next_message.Content).Msg("Handled text message") } -func TextStoreSources() error { +func ParsePhoneNumber(input string) (*E164, error) { + return phonenumbers.Parse(input, "US") +} + +func StoreSources() error { ctx := context.TODO() src := phonenumbers.Format(&config.PhoneNumberReport, phonenumbers.E164) return ensureInDB(ctx, src) @@ -132,9 +126,32 @@ func delayMessage(ctx context.Context, source string, destination string, conten return nil } +func resendInitialText(ctx context.Context, src string, dst string) error { + phone, err := models.FindCommsPhone(ctx, db.PGInstance.BobDB, dst) + if err != nil { + return fmt.Errorf("Failed to find phone %s: %w", dst, err) + } + err = phone.Update(ctx, db.PGInstance.BobDB, &models.CommsPhoneSetter{ + IsSubscribed: omitnull.FromPtr[bool](nil), + }) + if err != nil { + return fmt.Errorf("Failed to clear subscription on phone %s: %w", dst, err) + } + return nil +} + +func sendInitialText(ctx context.Context, src string, dst string) error { + content := "Welcome to Report Mosquitoes Online. We received your request and want to confirm text updates. Reply YES to continue. Reply STOP at any time to unsubscribe" + origin := enums.CommsTextoriginWebsiteAction + err := sendText(ctx, src, dst, content, origin, true, true) + if err != nil { + return fmt.Errorf("Failed to send initial confirmation: %w", err) + } + return nil +} + func ensureInitialText(ctx context.Context, src string, dst string) error { // - origin := enums.CommsTextoriginWebsiteAction rows, err := models.CommsTextLogs.Query( models.SelectWhere.CommsTextLogs.Destination.EQ(dst), models.SelectWhere.CommsTextLogs.IsWelcome.EQ(true), @@ -145,12 +162,7 @@ func ensureInitialText(ctx context.Context, src string, dst string) error { if len(rows) > 0 { return nil } - content := "Welcome to Report Mosquitoes Online. We received your request and want to confirm text updates. Reply YES to continue. Reply STOP at any time to unsubscribe" - err = sendText(ctx, src, dst, content, origin, true) - if err != nil { - return fmt.Errorf("Failed to send initial confirmation: %w", err) - } - return nil + return sendInitialText(ctx, src, dst) } func ensureInDB(ctx context.Context, destination string) (err error) { @@ -213,14 +225,14 @@ func handleResetConversation(ctx context.Context, src string, dst string) { if err != nil { log.Error().Err(err).Str("src", src).Str("dst", dst).Msg("Failed to wipe memory") content := "Failed to wip memory" - err = sendText(ctx, dst, src, content, enums.CommsTextoriginCommandResponse, false) + err = sendText(ctx, dst, src, content, enums.CommsTextoriginCommandResponse, false, false) if err != nil { log.Error().Err(err).Str("src", src).Str("dst", dst).Msg("Failed to indicated memory wipe failure.") } return } content := "LLM memory wiped" - err = sendText(ctx, dst, src, content, enums.CommsTextoriginCommandResponse, false) + err = sendText(ctx, dst, src, content, enums.CommsTextoriginCommandResponse, false, false) if err != nil { log.Error().Err(err).Str("src", src).Str("dst", dst).Msg("Failed to indicated memory wiped.") return @@ -228,13 +240,13 @@ func handleResetConversation(ctx context.Context, src string, dst string) { log.Info().Err(err).Str("src", src).Str("dst", dst).Msg("Wiped LLM memory") } -func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin, is_welcome bool) (log *models.CommsTextLog, err error) { +func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin, is_welcome bool, is_visible_to_llm bool) (log *models.CommsTextLog, err error) { log, err = models.CommsTextLogs.Insert(&models.CommsTextLogSetter{ //ID: Content: omit.From(content), Created: omit.From(time.Now()), Destination: omit.From(destination), - IsVisibleToLLM: omit.From(true), + IsVisibleToLLM: omit.From(is_visible_to_llm), IsWelcome: omit.From(is_welcome), Origin: omit.From(origin), Source: omit.From(source), @@ -263,7 +275,6 @@ func loadPreviousMessagesForLLM(ctx context.Context, dst, src string) ([]llm.Mes if err != nil { return results, fmt.Errorf("Failed to get message history for %s and %s: %w", dst, src, err) } - log.Info().Int("count", len(messages)).Str("src", src).Str("dst", dst).Msg("Found previous messages") for _, m := range messages { if m.IsVisibleToLLM { is_from_customer := (m.Source == src) @@ -276,12 +287,12 @@ func loadPreviousMessagesForLLM(ctx context.Context, dst, src string) ([]llm.Mes return results, nil } -func sendText(ctx context.Context, source string, destination string, message string, origin enums.CommsTextorigin, is_welcome bool) error { +func sendText(ctx context.Context, source string, destination string, message string, origin enums.CommsTextorigin, is_welcome bool, is_visible_to_llm bool) error { err := ensureInDB(ctx, destination) if err != nil { return fmt.Errorf("Failed to ensure text message destination is in the DB: %w", err) } - log, err := insertTextLog(ctx, message, destination, source, origin, is_welcome) + l, err := insertTextLog(ctx, message, destination, source, origin, is_welcome, is_visible_to_llm) if err != nil { return fmt.Errorf("Failed to insert text message in the DB: %w", err) } @@ -289,10 +300,14 @@ func sendText(ctx context.Context, source string, destination string, message st if err != nil { return fmt.Errorf("Failed to send text message: %w", err) } - err = log.Update(ctx, db.PGInstance.BobDB, &models.CommsTextLogSetter{ + err = l.Update(ctx, db.PGInstance.BobDB, &models.CommsTextLogSetter{ TwilioSid: omitnull.From(sid), TwilioStatus: omit.From("created"), }) + if err != nil { + return fmt.Errorf("Failed to update text Twilio status: %w", err) + } + log.Info().Int32("id", l.ID).Bool("is_visible_to_llm", is_visible_to_llm).Str("message", message).Msg("inserted text log") return nil } diff --git a/public-report/quick.go b/public-report/quick.go index 4feea1a5..708b5953 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -11,7 +11,6 @@ import ( "github.com/Gleipnir-Technology/bob/dialect/psql" "github.com/Gleipnir-Technology/bob/dialect/psql/um" "github.com/Gleipnir-Technology/nidus-sync/background" - "github.com/Gleipnir-Technology/nidus-sync/comms/text" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/enums" @@ -19,6 +18,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/h3utils" "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/Gleipnir-Technology/nidus-sync/platform" + "github.com/Gleipnir-Technology/nidus-sync/platform/text" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" From a42c5824af5ec0ab7a3b31b4488ac4aabead530d Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 27 Jan 2026 23:25:51 +0000 Subject: [PATCH 0135/1453] Add district to LLM context, be more aggressive about trimming agent: --- llm/client.go | 14 +++++++++++--- platform/text/text.go | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/llm/client.go b/llm/client.go index 419b9b0c..832b5be3 100644 --- a/llm/client.go +++ b/llm/client.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/maruel/genai" - //"github.com/rs/zerolog/log" + "github.com/rs/zerolog/log" ) type Message struct { @@ -45,6 +45,14 @@ func GenerateNextMessage(ctx context.Context, history []Message, _handle_report_ if err != nil { return Message{}, fmt.Errorf("Failed to generate next message: %w", err) } + trimmed, found := strings.CutPrefix(next.Content, "agent:") + if !found { + trimmed, found = strings.CutPrefix(next.Content, "Agent:") + if !found { + log.Warn().Str("content", next.Content).Msg("No 'agent:' prefix on next message") + } + } + next.Content = trimmed return next, nil } @@ -63,9 +71,9 @@ func convertHistory(history []Message) genai.Message { ) for _, h := range history { if h.IsFromCustomer { - sb.WriteString(fmt.Sprintf("\n\ncustomer (%s): %s\n", h.Content)) + sb.WriteString(fmt.Sprintf("\n\ncustomer: %s\n", h.Content)) } else { - sb.WriteString(fmt.Sprintf("\n\nagent (%s): %s\n", h.Content)) + sb.WriteString(fmt.Sprintf("\n\nagent: %s\n", h.Content)) } } return genai.NewTextMessage(sb.String()) diff --git a/platform/text/text.go b/platform/text/text.go index 84536653..bcca0bc1 100644 --- a/platform/text/text.go +++ b/platform/text/text.go @@ -187,7 +187,7 @@ func ensureInDB(ctx context.Context, destination string) (err error) { func generateNextMessage(ctx context.Context, history []llm.Message, customer_phone string) (llm.Message, error) { _handle_report_status := func() (string, error) { - return "Report: ABCD-1234-5678, Status: scheduled, Appointment: Wednesday 3:30pm", nil + return "Report: ABCD-1234-5678, District: Delta MVCD, Status: scheduled, Appointment: Wednesday 3:30pm", nil } _handle_contact_district := func(reason string) { log.Warn().Str("reason", reason).Msg("Contacting district") From 182175254ed9b6c96202193713956c6f732393ea Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 28 Jan 2026 14:57:50 +0000 Subject: [PATCH 0136/1453] Clean up old SMS callback endpoints --- llm/client.go | 2 +- sync/routes.go | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/llm/client.go b/llm/client.go index 832b5be3..1bad4e63 100644 --- a/llm/client.go +++ b/llm/client.go @@ -67,7 +67,7 @@ func convertHistory(history []Message) genai.Message { The agent rarely asks questions, preferring to just answer direct queries. For complex or highly specific requests, the agent will need to defer to the mosquito abatement district. This will take some time because contacting the district may take a few hours to get a response. When the agent needs to contact the district, the agent should tell the customer they are reaching out to the district and to expect a delay. When conversations start to veer away from the agent's job they should contact a supervisor. - Transcript starts:`, + Transcript:\n`, ) for _, h := range history { if h.IsFromCustomer { diff --git a/sync/routes.go b/sync/routes.go index f980dcdb..645ecf70 100644 --- a/sync/routes.go +++ b/sync/routes.go @@ -54,19 +54,14 @@ func Router() chi.Router { r.Get("/qr-code/report/{code}", getQRCodeReport) r.Get("/signin", getSignin) r.Post("/signin", postSignin) - r.Method("GET", "/signout", auth.NewEnsureAuth(getSignout)) r.Get("/signup", getSignup) r.Post("/signup", postSignup) - r.Get("/sms", getSMS) - r.Post("/sms", postSMS) - r.Get("/sms.php", getSMS) - r.Get("/sms/{org}", getSMS) - r.Post("/sms/{org}", postSMS) // Authenticated endpoints r.Route("/api", api.AddRoutes) r.Method("GET", "/cell/{cell}", auth.NewEnsureAuth(getCellDetails)) r.Method("GET", "/settings", auth.NewEnsureAuth(getSettings)) + r.Method("GET", "/signout", auth.NewEnsureAuth(getSignout)) r.Method("GET", "/source/{globalid}", auth.NewEnsureAuth(getSource)) r.Method("GET", "/trap/{globalid}", auth.NewEnsureAuth(getTrap)) From bdb3c80ad7639b2e7c357cbcda572599ec0a1085 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 28 Jan 2026 14:58:13 +0000 Subject: [PATCH 0137/1453] Add Basic text message review mock --- sync/routes.go | 1 + sync/template/text-messages.html | 143 +++++++++++++++++++++++++++++++ sync/text.go | 28 ++++++ 3 files changed, 172 insertions(+) create mode 100644 sync/template/text-messages.html create mode 100644 sync/text.go diff --git a/sync/routes.go b/sync/routes.go index 645ecf70..feba55ce 100644 --- a/sync/routes.go +++ b/sync/routes.go @@ -64,6 +64,7 @@ func Router() chi.Router { r.Method("GET", "/signout", auth.NewEnsureAuth(getSignout)) r.Method("GET", "/source/{globalid}", auth.NewEnsureAuth(getSource)) r.Method("GET", "/trap/{globalid}", auth.NewEnsureAuth(getTrap)) + r.Method("GET", "/text/{destination}", auth.NewEnsureAuth(getTextMessages)) htmlpage.AddStaticRoute(r, "/static") return r diff --git a/sync/template/text-messages.html b/sync/template/text-messages.html new file mode 100644 index 00000000..c38c243e --- /dev/null +++ b/sync/template/text-messages.html @@ -0,0 +1,143 @@ +{{template "authenticated.html" .}} + +{{define "title"}}Dash{{end}} +{{define "extraheader"}} + +{{end}} +{{define "content"}} +
    + +
    +
    +
    +
    +
    + User avatar +
    +
    +
    Chat with Sarah Johnson
    + Last active 5 minutes ago +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + +
    + Today, 2:30 PM +
    + + +
    + Receiver avatar +
    +
    + Hi there! How's the project coming along? +
    +
    2:31 PM
    +
    +
    + + +
    + Sender avatar +
    +
    + Hey! It's going pretty well. I'm working on the UI mockups right now. +
    +
    2:33 PM
    +
    +
    + + +
    + Receiver avatar +
    +
    + That's great to hear! When do you think you'll be able to share them with the team? +
    +
    2:35 PM
    +
    +
    + + +
    + Sender avatar +
    +
    + I'm hoping to have something ready by tomorrow afternoon. I'm just working out some details with the responsive design. +
    +
    2:36 PM
    +
    +
    + + +
    + Sender avatar +
    +
    + Do you have any specific feedback on the initial concept I shared last week? +
    +
    2:37 PM
    +
    +
    + + +
    + Receiver avatar +
    +
    + Yes! The team loved it. The color scheme was particularly well received. We just had some minor suggestions about the navigation that I can share during our next call. +
    +
    2:40 PM
    +
    +
    + + +
    + Sender avatar +
    +
    + That sounds great! Looking forward to the feedback. +
    +
    2:41 PM
    +
    +
    +
    +
    +
    +
    +
    +{{end}} diff --git a/sync/text.go b/sync/text.go new file mode 100644 index 00000000..2d91cbea --- /dev/null +++ b/sync/text.go @@ -0,0 +1,28 @@ +package sync + +import ( + "net/http" + + "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/Gleipnir-Technology/nidus-sync/htmlpage" +) + +type ContentTextMessages struct { + User User +} + +var ( + textMessagesT = buildTemplate("text-messages", "authenticated") +) + +func getTextMessages(w http.ResponseWriter, r *http.Request, u *models.User) { + userContent, err := contentForUser(r.Context(), u) + if err != nil { + respondError(w, "Failed to get user", err, http.StatusInternalServerError) + return + } + content := ContentTextMessages{ + User: userContent, + } + htmlpage.RenderOrError(w, textMessagesT, content) +} From 082fdeebdd35d969ca7cc73a68c3a4dfeca7812a Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 28 Jan 2026 17:15:42 +0000 Subject: [PATCH 0138/1453] Add basic layout test This is testing a new way to do the main site layout that I think will be a better fit for where I want the UI to go with a collapsable interface. --- sync/dash.go | 25 ++++-- sync/page.go | 2 +- sync/routes.go | 1 + sync/template/authenticated.html | 114 +++++++++++++++++++++++++- sync/template/components/icons.html | 60 ++++++++++++++ sync/template/components/sidebar.html | 52 ++++++++++++ sync/template/layout-test.html | 50 +++++++++++ 7 files changed, 294 insertions(+), 10 deletions(-) create mode 100644 sync/template/components/icons.html create mode 100644 sync/template/components/sidebar.html create mode 100644 sync/template/layout-test.html diff --git a/sync/dash.go b/sync/dash.go index 54ab9abd..e0b58016 100644 --- a/sync/dash.go +++ b/sync/dash.go @@ -22,12 +22,13 @@ import ( // Authenticated pages var ( - cellT = buildTemplate("cell", "authenticated") - dashboardT = buildTemplate("dashboard", "authenticated") - districtT = buildTemplate("district", "base") - settingsT = buildTemplate("settings", "authenticated") - sourceT = buildTemplate("source", "authenticated") - trapT = buildTemplate("trap", "authenticated") + cellT = buildTemplate("cell", "authenticated") + dashboardT = buildTemplate("dashboard", "authenticated") + districtT = buildTemplate("district", "base") + layoutTestT = buildTemplate("layout-test", "authenticated") + settingsT = buildTemplate("settings", "authenticated") + sourceT = buildTemplate("source", "authenticated") + trapT = buildTemplate("trap", "authenticated") ) type Config struct { @@ -71,6 +72,9 @@ type ContextDashboard struct { User User } +type ContentLayoutTest struct { + User User +} type ContextDistrict struct { MapboxToken string } @@ -96,6 +100,15 @@ func getDistrict(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError(w, districtT, &context) } +func getLayoutTest(w http.ResponseWriter, r *http.Request, u *models.User) { + userContent, err := contentForUser(r.Context(), u) + if err != nil { + respondError(w, "Failed to get user", err, http.StatusInternalServerError) + return + } + htmlpage.RenderOrError(w, layoutTestT, &ContentLayoutTest{User: userContent}) +} + func getRoot(w http.ResponseWriter, r *http.Request) { user, err := auth.GetAuthenticatedUser(r) if err != nil { diff --git a/sync/page.go b/sync/page.go index 1738e5b7..291c5e05 100644 --- a/sync/page.go +++ b/sync/page.go @@ -12,7 +12,7 @@ import ( //go:embed template/* var embeddedFiles embed.FS -var components = [...]string{"header", "map"} +var components = [...]string{"header", "icons", "map", "sidebar"} func buildTemplate(files ...string) *htmlpage.BuiltTemplate { subdir := "sync" diff --git a/sync/routes.go b/sync/routes.go index feba55ce..abb2be6b 100644 --- a/sync/routes.go +++ b/sync/routes.go @@ -60,6 +60,7 @@ func Router() chi.Router { // Authenticated endpoints r.Route("/api", api.AddRoutes) r.Method("GET", "/cell/{cell}", auth.NewEnsureAuth(getCellDetails)) + r.Method("GET", "/layout-test", auth.NewEnsureAuth(getLayoutTest)) r.Method("GET", "/settings", auth.NewEnsureAuth(getSettings)) r.Method("GET", "/signout", auth.NewEnsureAuth(getSignout)) r.Method("GET", "/source/{globalid}", auth.NewEnsureAuth(getSource)) diff --git a/sync/template/authenticated.html b/sync/template/authenticated.html index 0ffb64e6..4fc97cc4 100644 --- a/sync/template/authenticated.html +++ b/sync/template/authenticated.html @@ -8,15 +8,123 @@ + + + {{block "extraheader" .}} {{end}} -{{if .User}} - {{template "header" .User}} -{{end}} +{{template "icons"}} +
    + {{if .User}} + {{template "sidebar" .User}} + {{end}} +
    + {{template "content" .}} + diff --git a/sync/template/components/icons.html b/sync/template/components/icons.html new file mode 100644 index 00000000..424e1ccc --- /dev/null +++ b/sync/template/components/icons.html @@ -0,0 +1,60 @@ +{{define "icons"}} + + + Bootstrap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{{end}} diff --git a/sync/template/components/sidebar.html b/sync/template/components/sidebar.html new file mode 100644 index 00000000..2786092a --- /dev/null +++ b/sync/template/components/sidebar.html @@ -0,0 +1,52 @@ +{{define "sidebar"}} + +{{end}} diff --git a/sync/template/layout-test.html b/sync/template/layout-test.html new file mode 100644 index 00000000..9abc1750 --- /dev/null +++ b/sync/template/layout-test.html @@ -0,0 +1,50 @@ +{{template "authenticated.html" . }} +{{define "title"}}Layout Test{{end}} +{{define "extraheader"}} +{{end}} +{{define "content"}} + +
    + + +
    +
    +
    +
    +
    +
    Welcome to the Dashboard
    +

    This is an example of a Bootstrap layout with a collapsible sidebar.

    +

    The sidebar can be toggled using the button in the navigation bar. When the sidebar collapses, only the icons remain visible.

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    Card 1
    +

    Some example content for the first card.

    +
    +
    +
    +
    +
    +
    +
    Card 2
    +

    Some example content for the second card.

    +
    +
    +
    +
    +
    +
    +{{end}} From 5d4a7a4155c10a8eec6d23bfadfd340e5a5d9e97 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 28 Jan 2026 22:25:02 +0000 Subject: [PATCH 0139/1453] Add customized CSS theme for bootstrap --- .gitignore | 2 + .gitmodules | 3 + README.md | 10 +++ bootstrap | 1 + flake.nix | 2 + htmlpage/static/css/placeholder | 0 scss/custom.scss | 51 ++++++++++++ scss/sidebar.scss | 90 +++++++++++++++++++++ sync/template/authenticated.html | 100 +---------------------- sync/template/components/sidebar.html | 100 +++++++++++------------ sync/template/layout-test.html | 111 ++++++++++++++++---------- 11 files changed, 279 insertions(+), 191 deletions(-) create mode 160000 bootstrap create mode 100644 htmlpage/static/css/placeholder create mode 100644 scss/custom.scss create mode 100644 scss/sidebar.scss diff --git a/.gitignore b/.gitignore index 32ca81cf..a4db107f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ nidus-sync +htmlpage/static/css/bootstrap.css +scss/.sass-cache/ tmp/ diff --git a/.gitmodules b/.gitmodules index dc0c25cc..d33f3b73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "go-geojson2h3"] path = go-geojson2h3 url = git@github.com:Gleipnir-Technology/go-geojson2h3.git +[submodule "bootstrap"] + path = bootstrap + url = https://github.com/twbs/bootstrap.git diff --git a/README.md b/README.md index 936e7461..27749fc2 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,16 @@ nix develop go build . ``` +## Building Custom Theme + +We're using a customized Bootstrap theme for this site. You'll need to build the SCSS into CSS: + +``` +nix develop +cd scss +scss custom.scss > ../htmlpage/static/css/bootstrap.css +``` + ## Running You'll need a number of environment variables for configuring things; diff --git a/bootstrap b/bootstrap new file mode 160000 index 00000000..25aa8cc0 --- /dev/null +++ b/bootstrap @@ -0,0 +1 @@ +Subproject commit 25aa8cc0b32f0d1a54be575347e6d84b70b1acd7 diff --git a/flake.nix b/flake.nix index 12fd6c72..fba2b1fa 100644 --- a/flake.nix +++ b/flake.nix @@ -20,10 +20,12 @@ devShells.default = pkgs.mkShell { buildInputs = [ pkgs.air + pkgs.autoprefixer pkgs.go pkgs.goose pkgs.gotools pkgs.lefthook + pkgs.sass ]; }; } diff --git a/htmlpage/static/css/placeholder b/htmlpage/static/css/placeholder new file mode 100644 index 00000000..e69de29b diff --git a/scss/custom.scss b/scss/custom.scss new file mode 100644 index 00000000..733d7c9b --- /dev/null +++ b/scss/custom.scss @@ -0,0 +1,51 @@ +// Custom.scss +// Option B: Include parts of Bootstrap + +// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc) +@import "../bootstrap/scss/functions"; + +// 2. Include any default variable overrides here +$primary: #F76436; +$secondary: #3C552D; +$success: #8BAE67; +$danger: #FFC01B; +$info: #D7B26D; + + +// 3. Include remainder of required Bootstrap stylesheets +@import "../bootstrap/scss/variables"; + +// 4. Include any default map overrides here + +// 5. Include remainder of required parts +@import "../bootstrap/scss/maps"; +@import "../bootstrap/scss/mixins"; +@import "../bootstrap/scss/root"; + +// 6. Optionally include any other parts as needed +@import "../bootstrap/scss/utilities"; +@import "../bootstrap/scss/reboot"; +@import "../bootstrap/scss/type"; +@import "../bootstrap/scss/images"; +@import "../bootstrap/scss/containers"; +@import "../bootstrap/scss/grid"; +@import "../bootstrap/scss/helpers"; + +// 7. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss` +@import "../bootstrap/scss/utilities/api"; + +// 8. Add additional custom code here +$custom-colors: ( + "color1": $primary, + "color2": $danger, + "color3": #8C552D, + "color4": $success, + "color5": $info, +); +$off-white: #F8F9FA; +$off-black: #495057; + +@import "./sidebar.scss"; + +// Merge the maps +$theme-colors: map-merge($theme-colors, $custom-colors); diff --git a/scss/sidebar.scss b/scss/sidebar.scss new file mode 100644 index 00000000..858c04f7 --- /dev/null +++ b/scss/sidebar.scss @@ -0,0 +1,90 @@ +#sidebar { + background-color: $off-white; + min-height: 100vh; + transition: all 0.3s; + width: 250px; + position: fixed; + z-index: 1000; + padding: 20px; +} + +#sidebar.collapsed { + width: 70px; + padding: 20px 10px; +} + +#content { + transition: all 0.3s; + margin-left: 250px; + width: calc(100% - 250px); +} + +#content.expanded { + margin-left: 70px; + width: calc(100% - 70px); +} + +.sidebar-header { + padding-bottom: 20px; + border-bottom: 1px solid #dee2e6; + margin-bottom: 20px; + overflow: hidden; + white-space: nowrap; +} + +.sidebar-menu { + list-style: none; + padding: 0; +} + +.sidebar-menu li { + padding: 10px 0; +} + +.sidebar-menu li a { + text-decoration: none; + color: $off-black; + display: flex; + align-items: center; + overflow: hidden; + white-space: nowrap; +} + +.sidebar-menu li a:hover { + color: $primary; +} + +.sidebar-menu .menu-icon { + font-size: 1.2rem; + min-width: 30px; + display: flex; + justify-content: center; +} + +.sidebar-menu .menu-text { + transition: opacity 0.3s; +} + +#sidebar.collapsed .menu-text { + opacity: 0; + visibility: hidden; + width: 0; +} + +#sidebar.collapsed .sidebar-header h4 { + opacity: 0; + visibility: hidden; +} + +#sidebar.collapsed .sidebar-menu .menu-icon { + min-width: 100%; + font-size: 1.5rem; +} + +#sidebarToggle i { + transition: transform 0.3s; +} + +#sidebar.collapsed + #content #sidebarToggle i { + transform: rotate(180deg); +} diff --git a/sync/template/authenticated.html b/sync/template/authenticated.html index 4fc97cc4..ba12bf54 100644 --- a/sync/template/authenticated.html +++ b/sync/template/authenticated.html @@ -5,109 +5,13 @@ {{template "title" .}} - Nidus Sync - + - + - {{block "extraheader" .}} {{end}} diff --git a/sync/template/components/sidebar.html b/sync/template/components/sidebar.html index 2786092a..be4f03b4 100644 --- a/sync/template/components/sidebar.html +++ b/sync/template/components/sidebar.html @@ -1,52 +1,52 @@ {{define "sidebar"}} - + {{end}} diff --git a/sync/template/layout-test.html b/sync/template/layout-test.html index 9abc1750..7a2a135d 100644 --- a/sync/template/layout-test.html +++ b/sync/template/layout-test.html @@ -4,47 +4,72 @@ {{end}} {{define "content"}} -
    - - -
    -
    -
    -
    -
    -
    Welcome to the Dashboard
    -

    This is an example of a Bootstrap layout with a collapsible sidebar.

    -

    The sidebar can be toggled using the button in the navigation bar. When the sidebar collapses, only the icons remain visible.

    -
    -
    -
    -
    - -
    -
    -
    -
    -
    Card 1
    -

    Some example content for the first card.

    -
    -
    -
    -
    -
    -
    -
    Card 2
    -

    Some example content for the second card.

    -
    -
    -
    -
    -
    -
    +
    + + +
    +
    +
    +
    +
    +
    Welcome to the Dashboard
    +

    This is an example of a Bootstrap layout with a collapsible sidebar.

    +

    The sidebar can be toggled using the button in the navigation bar. When the sidebar collapses, only the icons remain visible.

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    Card 1
    +

    Some example content for the first card.

    +
    +
    +
    +
    +
    +
    +
    Card 2
    +

    Some example content for the second card.

    +
    +
    +
    +
    +
    +
    +
    +
    Primary
    +
    Primary-100
    +
    Primary-200
    +
    Primary-300
    +
    +
    +
    Secondary
    +
    +
    +
    Success
    +
    +
    +
    +
    +
    Danger
    +
    +
    +
    Warning
    +
    +
    +
    Info
    +
    +
    +
    {{end}} From 20eda6a1d83ecfa0c00d82d669a5e448a8deb44b Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 28 Jan 2026 22:33:32 +0000 Subject: [PATCH 0140/1453] Make logo change with sidebar collapse --- scss/sidebar.scss | 24 +++++++++++++++++++++++- sync/template/components/sidebar.html | 4 +++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/scss/sidebar.scss b/scss/sidebar.scss index 858c04f7..ec0b015f 100644 --- a/scss/sidebar.scss +++ b/scss/sidebar.scss @@ -1,3 +1,16 @@ +.logo-container { + display: flex; + justify-content: center; + align-items: center; + transition: all 0.3s ease; +} + +.logo { + max-width: 100%; + height: auto; + transition: all 0.3s ease; +} + #sidebar { background-color: $off-white; min-height: 100vh; @@ -12,7 +25,14 @@ width: 70px; padding: 20px 10px; } +/* Logo style when sidebar is collapsed */ +#sidebar.collapsed .logo-container { + width: 100%; +} +#sidebar.collapsed .logo-img { + max-width: 40px; /* smaller size for collapsed state */ +} #content { transition: all 0.3s; margin-left: 250px; @@ -26,10 +46,12 @@ .sidebar-header { padding-bottom: 20px; - border-bottom: 1px solid #dee2e6; + border-bottom: 1px solid $off-black; margin-bottom: 20px; overflow: hidden; white-space: nowrap; + display: flex; + justify-content: center; /* Center for the logo */ } .sidebar-menu { diff --git a/sync/template/components/sidebar.html b/sync/template/components/sidebar.html index be4f03b4..11314973 100644 --- a/sync/template/components/sidebar.html +++ b/sync/template/components/sidebar.html @@ -1,7 +1,9 @@ {{define "sidebar"}} From 38d492e9da015b0fdbde2000287b626a8504ed7d Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 15:08:11 +0000 Subject: [PATCH 0170/1453] Rework custom bootstrap theme to include all of bootstrap I already had most of it anyway. This also fixes our buttons to have the correct contrast. --- scss/custom.scss | 86 ++++++++++++---------------------- sync/template/layout-test.html | 35 +++++++++----- 2 files changed, 53 insertions(+), 68 deletions(-) diff --git a/scss/custom.scss b/scss/custom.scss index a38ce76d..4bdaa478 100644 --- a/scss/custom.scss +++ b/scss/custom.scss @@ -1,65 +1,39 @@ -// Custom.scss -// Option B: Include parts of Bootstrap - -// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc) -@import "./bootstrap/scss/functions"; - -// 2. Include any default variable overrides here +// 1. Include specific theme variables $primary: #F76436; $secondary: #3C552D; $success: #8BAE67; -$danger: #FFC01B; +$warning: #FFC01B; +$danger: #6b2737; $info: #D7B26D; - -// 3. Include remainder of required Bootstrap stylesheets -@import "./bootstrap/scss/variables"; - -// 4. Include any default map overrides here - -// 5. Include remainder of required parts -@import "./bootstrap/scss/mixins/border-radius"; -@import "./bootstrap/scss/mixins/box-shadow"; -@import "./bootstrap/scss/mixins/breakpoints"; -@import "./bootstrap/scss/mixins/buttons"; -@import "./bootstrap/scss/mixins/color-mode"; -@import "./bootstrap/scss/mixins/forms"; -@import "./bootstrap/scss/mixins/gradients"; -@import "./bootstrap/scss/mixins/transition"; -@import "./bootstrap/scss/vendor/rfs"; -@import "./bootstrap/scss/alert"; -@import "./bootstrap/scss/buttons"; -@import "./bootstrap/scss/card"; -@import "./bootstrap/scss/forms"; -@import "./bootstrap/scss/maps"; -@import "./bootstrap/scss/mixins"; -@import "./bootstrap/scss/root"; -@import "./bootstrap/scss/transitions"; - -// 6. Optionally include any other parts as needed -@import "./bootstrap/scss/utilities"; -@import "./bootstrap/scss/reboot"; -@import "./bootstrap/scss/type"; -@import "./bootstrap/scss/images"; -@import "./bootstrap/scss/containers"; -@import "./bootstrap/scss/grid"; -@import "./bootstrap/scss/helpers"; - -// 7. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss` -@import "./bootstrap/scss/utilities/api"; - -// 8. Add additional custom code here -$custom-colors: ( - "color1": $primary, - "color2": $danger, - "color3": #8C552D, - "color4": $success, - "color5": $info, -); $off-white: #F8F9FA; $off-black: #495057; -@import "./sidebar.scss"; +// 2. Configure color contrast +$color-contrast-dark: #000; +$color-contrast-light: #fff; +$min-contrast-ratio: 2.0; -// Merge the maps -$theme-colors: map-merge($theme-colors, $custom-colors); +$custom-colors: ( + "color1": $primary, + "color2": $secondary, + "color3": $success, + "color4": $danger, + "color5": $warning, + "color6": $info, +); +$theme-colors: map-merge( + ( + "primary": $primary, + "secondary": $secondary, + "success": $success, + "danger": $danger, + "warning": $warning, + "info": $info + ), + $custom-colors +); + +@import "./bootstrap/scss/bootstrap"; + +@import "./sidebar.scss"; diff --git a/sync/template/layout-test.html b/sync/template/layout-test.html index b82f295a..f5894c89 100644 --- a/sync/template/layout-test.html +++ b/sync/template/layout-test.html @@ -1,24 +1,35 @@ {{template "authenticated.html" . }} {{define "title"}}Layout Test{{end}} {{define "extraheader"}} + {{end}} {{define "content"}}
    -
    -
    -
    -
    -
    Welcome to the Dashboard
    -

    This is an example of a Bootstrap layout with a collapsible sidebar.

    -

    The sidebar can be toggled using the button in the navigation bar. When the sidebar collapses, only the icons remain visible.

    -
    -
    -
    -
    -
    +
    + + + + + + + + + + +
    +
    From db9d4e05b718bf3ef7ece4c55b427832eec72d23 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 15:13:38 +0000 Subject: [PATCH 0171/1453] Add missing dark/light variables Fixes things like "bg-dark", "bg-light" --- scss/custom.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scss/custom.scss b/scss/custom.scss index 4bdaa478..b69de586 100644 --- a/scss/custom.scss +++ b/scss/custom.scss @@ -5,6 +5,8 @@ $success: #8BAE67; $warning: #FFC01B; $danger: #6b2737; $info: #D7B26D; +$dark: #3b1002; +$light: #fde1d8; $off-white: #F8F9FA; $off-black: #495057; @@ -29,7 +31,9 @@ $theme-colors: map-merge( "success": $success, "danger": $danger, "warning": $warning, - "info": $info + "info": $info, + "dark": $dark, + "light": $light ), $custom-colors ); From 1dea676ef9d2bcd3c5e87a077d62c7b4f83e67d7 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 15:22:24 +0000 Subject: [PATCH 0172/1453] Add banner to main RMO mock. --- htmlpage/static/img/rmo/banner.jpg | Bin 0 -> 133315 bytes rmo/template/mock/root.html | 22 +++++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 htmlpage/static/img/rmo/banner.jpg diff --git a/htmlpage/static/img/rmo/banner.jpg b/htmlpage/static/img/rmo/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a01f736726e65cb500464d60ef4b18160ad3f223 GIT binary patch literal 133315 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 z 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 {{end}} {{define "content"}} @@ -29,13 +42,8 @@
    -
    -
    -
    -

    Report Mosquitoes Online

    -

    This is the reporting page for mosquito problems in your area.

    -
    -
    +
    From 9d5cc8575732d68bb997a2af5a8fc4b79241605a Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 15:27:04 +0000 Subject: [PATCH 0173/1453] Remove old RMO banner --- htmlpage/static/img/rmo-banner-1440.png | Bin 33292 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 htmlpage/static/img/rmo-banner-1440.png diff --git a/htmlpage/static/img/rmo-banner-1440.png b/htmlpage/static/img/rmo-banner-1440.png deleted file mode 100644 index 03b28559e2c681f3a36c9f2d6de563e4e2960aec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33292 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

    o>J@8T<#W9|*L{Be=yrbkS(%q- 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 XH4ofb9E~P^09Bx#u6{1-oD!M<_$;px From 8dc23d26215b28d61a2c708cf93034ec03a603ec Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 15:27:18 +0000 Subject: [PATCH 0174/1453] Hide the additional information button after use --- rmo/template/mock/nuisance.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rmo/template/mock/nuisance.html b/rmo/template/mock/nuisance.html index 8820cd92..aa2f88b6 100644 --- a/rmo/template/mock/nuisance.html +++ b/rmo/template/mock/nuisance.html @@ -35,6 +35,7 @@ function toggleCollapse(something) { } else { el.classList.add('collapse'); } + document.getElementById("toggle-additional").classList.add("visually-hidden"); } // Check for source identification document.addEventListener('DOMContentLoaded', function() { @@ -351,7 +352,7 @@ select.tall {

    -
    From 2baad02a0c7c8f47b8fc5dca93a51a72d4a54986 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 15:32:50 +0000 Subject: [PATCH 0175/1453] Make "submit another report" link work, remove "back home" button --- rmo/template/mock/nuisance-submit-complete.html | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/rmo/template/mock/nuisance-submit-complete.html b/rmo/template/mock/nuisance-submit-complete.html index 7af06e85..4249a70a 100644 --- a/rmo/template/mock/nuisance-submit-complete.html +++ b/rmo/template/mock/nuisance-submit-complete.html @@ -116,13 +116,7 @@ -
    - {{ if not (eq .District nil) }} -

    Your report will be handled by

    -

    {{ .District.Name }}

    - - {{ end }} +
    +
    + {{ if not (eq .District nil) }} +

    Your report will be handled by

    +

    {{ .District.Name }}

    + + {{ end }} +
    From c820ec91c6a2f2226a3b1bb4fa54b2d2136671d8 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 15:45:56 +0000 Subject: [PATCH 0177/1453] Add district name to periodic updates subscription --- rmo/template/mock/nuisance-submit-complete.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rmo/template/mock/nuisance-submit-complete.html b/rmo/template/mock/nuisance-submit-complete.html index 87b366f0..f5d11bb1 100644 --- a/rmo/template/mock/nuisance-submit-complete.html +++ b/rmo/template/mock/nuisance-submit-complete.html @@ -80,7 +80,7 @@
    From 62ec73c6734b452b55186ca10d46e3ca88f08def Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 15:48:47 +0000 Subject: [PATCH 0178/1453] Add primary color to header bar --- rmo/template/component/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rmo/template/component/header.html b/rmo/template/component/header.html index 453a2aa3..6679d739 100644 --- a/rmo/template/component/header.html +++ b/rmo/template/component/header.html @@ -1,5 +1,5 @@ {{define "header"}} -

    Please provide the location of the potential mosquito production source. We may be able to extract this information from your photos if they contain location data.

    +
    +
    +

    You can select the location by address or by moving the marker on the map.

    +
    +
    @@ -347,8 +337,8 @@ function displaySelectedCoordinates(lngLat) {
    -
    From e7230eb3c22a5d220c34cdb15068fb05e3f7f885 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 15:56:46 +0000 Subject: [PATCH 0180/1453] Remove redundant contact information section It's handled in the next page. --- rmo/template/mock/water.html | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/rmo/template/mock/water.html b/rmo/template/mock/water.html index 110708aa..a919edf4 100644 --- a/rmo/template/mock/water.html +++ b/rmo/template/mock/water.html @@ -459,31 +459,6 @@ function displaySelectedCoordinates(lngLat) {
    - - -
    Your Contact Information (for updates)
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    - - -
    -
    -
    From 78ee0483d3ab38957e55937fdc3f3b3f9092c85b Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 16:10:17 +0000 Subject: [PATCH 0181/1453] Add "this is my property" checkbox --- rmo/template/mock/water.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rmo/template/mock/water.html b/rmo/template/mock/water.html index a919edf4..ff6199a1 100644 --- a/rmo/template/mock/water.html +++ b/rmo/template/mock/water.html @@ -454,10 +454,16 @@ function displaySelectedCoordinates(lngLat) {
    -
    +
    +
    +
    + + +
    +
    From a41fdac13be2b325727e66bc45e73856b91579fd Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 16:14:52 +0000 Subject: [PATCH 0182/1453] Add checkbox granting backyard access --- rmo/template/mock/water.html | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/rmo/template/mock/water.html b/rmo/template/mock/water.html index ff6199a1..0d7cd460 100644 --- a/rmo/template/mock/water.html +++ b/rmo/template/mock/water.html @@ -440,29 +440,33 @@ function displaySelectedCoordinates(lngLat) {
    -

    Contact Information

    +

    Property Owner Information (if known)

    - -
    Property Owner Information (if known)
    -
    +
    -
    +
    -
    +
    +
    +
    - +
    +
    + + +
    From 5970e9c5a4e24fed6e6ddb3a399091e65e52ad55 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 16:22:35 +0000 Subject: [PATCH 0183/1453] Add anonymity checkbox --- rmo/template/mock/water.html | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/rmo/template/mock/water.html b/rmo/template/mock/water.html index 0d7cd460..fb8ab1a5 100644 --- a/rmo/template/mock/water.html +++ b/rmo/template/mock/water.html @@ -166,6 +166,14 @@ function toggleCollapse(something) { document.getElementById("toggle-additional").classList.add("visually-hidden"); } document.addEventListener('DOMContentLoaded', function() { + // Initialize tooltips + var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')) + tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl, { + trigger: 'hover focus' // Works on hover (desktop) and tap (mobile) + }); + }); + // Elements const photoInput = document.getElementById('photos'); @@ -464,8 +472,17 @@ function displaySelectedCoordinates(lngLat) {
    - - + + +
    +
    + +
    From bc27a195345fe5576828cd85aa4aac4c2afcd3d1 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 16:32:13 +0000 Subject: [PATCH 0184/1453] Switch to dart-sass Faster, and hopefully less error-prone --- default.nix | 2 +- flake.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/default.nix b/default.nix index 7068db64..700a599f 100644 --- a/default.nix +++ b/default.nix @@ -11,7 +11,7 @@ pkgs.buildGoModule rec { # Needs to be updated after every modification of go.mod/go.sum vendorHash = "sha256-aaJnH258H1LkXvb22rR3Clg7fKzA/HSmBZUkh1E8jKI="; - nativeBuildInputs = [ pkgs.sass ]; + nativeBuildInputs = [ pkgs.dart-sass ]; preBuild = '' diff --git a/flake.nix b/flake.nix index 69f7af77..ed2819ba 100644 --- a/flake.nix +++ b/flake.nix @@ -21,11 +21,11 @@ buildInputs = [ pkgs.air pkgs.autoprefixer + pkgs.dart-sass pkgs.go pkgs.goose pkgs.gotools pkgs.lefthook - pkgs.sass pkgs.watchexec ]; }; From 87893363e5e23bb165f93344628c0864ce6cf5fb Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 16:34:58 +0000 Subject: [PATCH 0185/1453] Remove redundant request logging --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 91c8de6a..6211f76d 100644 --- a/main.go +++ b/main.go @@ -96,7 +96,7 @@ func main() { r.Use(LoggerMiddleware(&router_logger)) r.Use(middleware.RequestID) r.Use(middleware.RealIP) - r.Use(middleware.Logger) + //r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Use(sentryMiddleware.Handle) r.Use(auth.NewSessionManager().LoadAndSave) From 3bf40572e2f4343a7c91afe66c254f3bd803836c Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 18:00:56 +0000 Subject: [PATCH 0186/1453] Rename rmo/page.go to rmo/template.go Because it contains stuff for dealing with templates. --- rmo/{page.go => template.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename rmo/{page.go => template.go} (100%) diff --git a/rmo/page.go b/rmo/template.go similarity index 100% rename from rmo/page.go rename to rmo/template.go From 2bd848fa97cde83e35a3f39ba81b01120a614ac3 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 18:01:39 +0000 Subject: [PATCH 0187/1453] Rename rmo/endpoint.go to rmo/root.go Because it contains the root page logic. --- rmo/{endpoint.go => root.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename rmo/{endpoint.go => root.go} (100%) diff --git a/rmo/endpoint.go b/rmo/root.go similarity index 100% rename from rmo/endpoint.go rename to rmo/root.go From 9b1d75d47f16f7c0eb6c6ca667e4dd25cf3575e5 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 18:21:27 +0000 Subject: [PATCH 0188/1453] Rename htmlpage to html Because it's going to get more tools. --- .gitignore | 4 +-- README.md | 4 +-- default.nix | 2 +- {htmlpage => html}/fileserver.go | 27 +++++++++++------- {htmlpage => html}/h3.go | 2 +- {htmlpage => html}/html.go | 2 +- {htmlpage => html}/response.go | 2 +- {htmlpage => html}/static.go | 6 ++-- {htmlpage => html}/static/css/placeholder | 0 {htmlpage => html}/static/favicon-rmo.ico | Bin {htmlpage => html}/static/favicon-sync.ico | Bin .../static/img/nidus-logo-256-transparent.png | Bin .../static/img/nidus-logo-no-lettering-64.png | Bin .../static/img/rmo-logo-224.png | Bin {htmlpage => html}/static/img/rmo/banner.jpg | Bin .../static/js/address-display.js | 0 .../static/js/address-suggestion.js | 0 {htmlpage => html}/static/js/geocode.js | 0 {htmlpage => html}/static/js/location.js | 0 {htmlpage => html}/static/js/map-aggregate.js | 0 {htmlpage => html}/static/js/map-locator.js | 0 .../static/js/map-multipoint.js | 0 .../static/js/map-with-markers.js | 0 {htmlpage => html}/static/js/report-table.js | 0 .../static/vendor/css/bootstrap.min.css | 0 .../static/vendor/js/bootstrap.bundle.min.js | 0 .../static/vendor/js/bootstrap.min.js | 0 rmo/mock.go | 7 ++--- rmo/nuisance.go | 6 ++-- rmo/pool.go | 6 ++-- rmo/quick.go | 8 +++--- rmo/root.go | 6 ++-- rmo/routes.go | 4 +-- rmo/search.go | 4 +-- rmo/status.go | 14 ++++----- rmo/template.go | 6 ++-- sync/dash.go | 16 +++++------ sync/mock.go | 6 ++-- sync/oauth.go | 4 +-- sync/page.go | 6 ++-- sync/privacy.go | 4 +-- sync/routes.go | 4 +-- sync/signin.go | 6 ++-- sync/text.go | 4 +-- 44 files changed, 83 insertions(+), 77 deletions(-) rename {htmlpage => html}/fileserver.go (88%) rename {htmlpage => html}/h3.go (97%) rename {htmlpage => html}/html.go (99%) rename {htmlpage => html}/response.go (98%) rename {htmlpage => html}/static.go (75%) rename {htmlpage => html}/static/css/placeholder (100%) rename {htmlpage => html}/static/favicon-rmo.ico (100%) rename {htmlpage => html}/static/favicon-sync.ico (100%) rename {htmlpage => html}/static/img/nidus-logo-256-transparent.png (100%) rename {htmlpage => html}/static/img/nidus-logo-no-lettering-64.png (100%) rename {htmlpage => html}/static/img/rmo-logo-224.png (100%) rename {htmlpage => html}/static/img/rmo/banner.jpg (100%) rename {htmlpage => html}/static/js/address-display.js (100%) rename {htmlpage => html}/static/js/address-suggestion.js (100%) rename {htmlpage => html}/static/js/geocode.js (100%) rename {htmlpage => html}/static/js/location.js (100%) rename {htmlpage => html}/static/js/map-aggregate.js (100%) rename {htmlpage => html}/static/js/map-locator.js (100%) rename {htmlpage => html}/static/js/map-multipoint.js (100%) rename {htmlpage => html}/static/js/map-with-markers.js (100%) rename {htmlpage => html}/static/js/report-table.js (100%) rename {htmlpage => html}/static/vendor/css/bootstrap.min.css (100%) rename {htmlpage => html}/static/vendor/js/bootstrap.bundle.min.js (100%) rename {htmlpage => html}/static/vendor/js/bootstrap.min.js (100%) diff --git a/.gitignore b/.gitignore index 2c345b04..36b3665b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ nidus-sync -htmlpage/static/css/bootstrap.css -htmlpage/static/css/bootstrap.css.map +html/static/css/bootstrap.css +html/static/css/bootstrap.css.map .sass-cache/ tmp/ diff --git a/README.md b/README.md index 8cfe8ff5..521e07f1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ We're using a customized Bootstrap theme for this site. You'll need to build the ``` nix develop cd scss -scss custom.scss > ../htmlpage/static/css/bootstrap.css +scss custom.scss > ../html/static/css/bootstrap.css ``` ## Running @@ -90,5 +90,5 @@ This uses [goose](https://github.com/pressly/goose). You can use the goose comma For iterating on styles ``` -watchexec -e *.scss sass --style=compressed scss/custom.scss:htmlpage/static/css/bootstrap.css +watchexec -e *.scss sass --style=compressed scss/custom.scss:html/static/css/bootstrap.css ``` diff --git a/default.nix b/default.nix index 700a599f..e38aec3f 100644 --- a/default.nix +++ b/default.nix @@ -16,7 +16,7 @@ pkgs.buildGoModule rec { preBuild = '' SASS_SRC_DIR="./scss" - CSS_OUTPUT_DIR="./htmlpage/static/css/" + CSS_OUTPUT_DIR="./html/static/css/" mkdir -p "$CSS_OUTPUT_DIR" diff --git a/htmlpage/fileserver.go b/html/fileserver.go similarity index 88% rename from htmlpage/fileserver.go rename to html/fileserver.go index 5dc69871..245b8d1c 100644 --- a/htmlpage/fileserver.go +++ b/html/fileserver.go @@ -1,4 +1,4 @@ -package htmlpage +package html import ( "embed" @@ -12,6 +12,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/go-chi/chi/v5" + "github.com/rs/zerolog/log" ) // FileServer conveniently sets up a http.FileServer handler to serve @@ -36,9 +37,21 @@ func FileServer(r chi.Router, path string, root http.FileSystem, embeddedFS embe var err error var fileToServe http.File + found := false - if config.IsProductionEnvironment() { - // For production use the embedded filesystem + // For dev, try the current filesystem + if !config.IsProductionEnvironment() { + // Try to open from local filesystem for development + fileToServe, err = root.Open(requestedPath) + if err != nil { + log.Warn().Str("path", requestedPath).Msg("Failed to read static file for dev") + found = false + } else { + found = true + } + } + // For production use the embedded filesystem + if !found { embeddedFilePath := filepath.Join(embeddedPath, requestedPath) embeddedFile, err := embeddedFS.Open(embeddedFilePath) @@ -49,14 +62,6 @@ func FileServer(r chi.Router, path string, root http.FileSystem, embeddedFS embe // Wrap the embedded file to implement http.File interface fileToServe = &embeddedFileWrapper{embeddedFile} - - } else { - // Try to open from local filesystem for development - fileToServe, err = root.Open(requestedPath) - if err != nil { - RespondError(w, "Failed to open file", err, http.StatusNotFound) - return - } } // Create a custom ResponseWriter that allows us to modify headers diff --git a/htmlpage/h3.go b/html/h3.go similarity index 97% rename from htmlpage/h3.go rename to html/h3.go index 6de2b817..644db8c4 100644 --- a/htmlpage/h3.go +++ b/html/h3.go @@ -1,4 +1,4 @@ -package htmlpage +package html import ( "fmt" diff --git a/htmlpage/html.go b/html/html.go similarity index 99% rename from htmlpage/html.go rename to html/html.go index a3882f55..f16312d8 100644 --- a/htmlpage/html.go +++ b/html/html.go @@ -1,4 +1,4 @@ -package htmlpage +package html import ( "bytes" diff --git a/htmlpage/response.go b/html/response.go similarity index 98% rename from htmlpage/response.go rename to html/response.go index 74b5cc2d..33e1d386 100644 --- a/htmlpage/response.go +++ b/html/response.go @@ -1,4 +1,4 @@ -package htmlpage +package html import ( "net/http" diff --git a/htmlpage/static.go b/html/static.go similarity index 75% rename from htmlpage/static.go rename to html/static.go index 0a544c00..c6fbb429 100644 --- a/htmlpage/static.go +++ b/html/static.go @@ -1,10 +1,12 @@ -package htmlpage +package html import ( "embed" + "io/fs" "net/http" "github.com/go-chi/chi/v5" + "github.com/rs/zerolog/log" ) //go:embed static/* @@ -14,7 +16,7 @@ var localFS http.Dir func AddStaticRoute(r chi.Router, path string) { if localFS == "" { - localFS = http.Dir("./htmlpage/static") + localFS = http.Dir("./html/static") } FileServer(r, "/static", localFS, EmbeddedStaticFS, "static") } diff --git a/htmlpage/static/css/placeholder b/html/static/css/placeholder similarity index 100% rename from htmlpage/static/css/placeholder rename to html/static/css/placeholder diff --git a/htmlpage/static/favicon-rmo.ico b/html/static/favicon-rmo.ico similarity index 100% rename from htmlpage/static/favicon-rmo.ico rename to html/static/favicon-rmo.ico diff --git a/htmlpage/static/favicon-sync.ico b/html/static/favicon-sync.ico similarity index 100% rename from htmlpage/static/favicon-sync.ico rename to html/static/favicon-sync.ico diff --git a/htmlpage/static/img/nidus-logo-256-transparent.png b/html/static/img/nidus-logo-256-transparent.png similarity index 100% rename from htmlpage/static/img/nidus-logo-256-transparent.png rename to html/static/img/nidus-logo-256-transparent.png diff --git a/htmlpage/static/img/nidus-logo-no-lettering-64.png b/html/static/img/nidus-logo-no-lettering-64.png similarity index 100% rename from htmlpage/static/img/nidus-logo-no-lettering-64.png rename to html/static/img/nidus-logo-no-lettering-64.png diff --git a/htmlpage/static/img/rmo-logo-224.png b/html/static/img/rmo-logo-224.png similarity index 100% rename from htmlpage/static/img/rmo-logo-224.png rename to html/static/img/rmo-logo-224.png diff --git a/htmlpage/static/img/rmo/banner.jpg b/html/static/img/rmo/banner.jpg similarity index 100% rename from htmlpage/static/img/rmo/banner.jpg rename to html/static/img/rmo/banner.jpg diff --git a/htmlpage/static/js/address-display.js b/html/static/js/address-display.js similarity index 100% rename from htmlpage/static/js/address-display.js rename to html/static/js/address-display.js diff --git a/htmlpage/static/js/address-suggestion.js b/html/static/js/address-suggestion.js similarity index 100% rename from htmlpage/static/js/address-suggestion.js rename to html/static/js/address-suggestion.js diff --git a/htmlpage/static/js/geocode.js b/html/static/js/geocode.js similarity index 100% rename from htmlpage/static/js/geocode.js rename to html/static/js/geocode.js diff --git a/htmlpage/static/js/location.js b/html/static/js/location.js similarity index 100% rename from htmlpage/static/js/location.js rename to html/static/js/location.js diff --git a/htmlpage/static/js/map-aggregate.js b/html/static/js/map-aggregate.js similarity index 100% rename from htmlpage/static/js/map-aggregate.js rename to html/static/js/map-aggregate.js diff --git a/htmlpage/static/js/map-locator.js b/html/static/js/map-locator.js similarity index 100% rename from htmlpage/static/js/map-locator.js rename to html/static/js/map-locator.js diff --git a/htmlpage/static/js/map-multipoint.js b/html/static/js/map-multipoint.js similarity index 100% rename from htmlpage/static/js/map-multipoint.js rename to html/static/js/map-multipoint.js diff --git a/htmlpage/static/js/map-with-markers.js b/html/static/js/map-with-markers.js similarity index 100% rename from htmlpage/static/js/map-with-markers.js rename to html/static/js/map-with-markers.js diff --git a/htmlpage/static/js/report-table.js b/html/static/js/report-table.js similarity index 100% rename from htmlpage/static/js/report-table.js rename to html/static/js/report-table.js diff --git a/htmlpage/static/vendor/css/bootstrap.min.css b/html/static/vendor/css/bootstrap.min.css similarity index 100% rename from htmlpage/static/vendor/css/bootstrap.min.css rename to html/static/vendor/css/bootstrap.min.css diff --git a/htmlpage/static/vendor/js/bootstrap.bundle.min.js b/html/static/vendor/js/bootstrap.bundle.min.js similarity index 100% rename from htmlpage/static/vendor/js/bootstrap.bundle.min.js rename to html/static/vendor/js/bootstrap.bundle.min.js diff --git a/htmlpage/static/vendor/js/bootstrap.min.js b/html/static/vendor/js/bootstrap.min.js similarity index 100% rename from htmlpage/static/vendor/js/bootstrap.min.js rename to html/static/vendor/js/bootstrap.min.js diff --git a/rmo/mock.go b/rmo/mock.go index 3255eab9..a49554bd 100644 --- a/rmo/mock.go +++ b/rmo/mock.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/go-chi/chi/v5" ) @@ -57,17 +57,16 @@ func makeContentURL(slug string) ContentURL { Water: makeURLMock(slug, "water"), } } - func makeURLMock(slug, p string) string { return config.MakeURLReport("/mock/district/%s/%s", slug, p) } -func renderMock(t *htmlpage.BuiltTemplate) func(http.ResponseWriter, *http.Request) { +func renderMock(t *html.BuiltTemplate) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { slug := chi.URLParam(r, "slug") if slug == "" { slug = "delta-mvcd" } - htmlpage.RenderOrError( + html.RenderOrError( w, t, ContentMock{ diff --git a/rmo/nuisance.go b/rmo/nuisance.go index ace0ee08..5580daa4 100644 --- a/rmo/nuisance.go +++ b/rmo/nuisance.go @@ -9,7 +9,7 @@ import ( "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/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" @@ -26,7 +26,7 @@ var ( ) func getNuisance(w http.ResponseWriter, r *http.Request) { - htmlpage.RenderOrError( + html.RenderOrError( w, Nuisance, ContextNuisance{}, @@ -34,7 +34,7 @@ func getNuisance(w http.ResponseWriter, r *http.Request) { } func getNuisanceSubmitComplete(w http.ResponseWriter, r *http.Request) { report := r.URL.Query().Get("report") - htmlpage.RenderOrError( + html.RenderOrError( w, NuisanceSubmitComplete, ContextNuisanceSubmitComplete{ diff --git a/rmo/pool.go b/rmo/pool.go index 5b6adb08..6abb7efe 100644 --- a/rmo/pool.go +++ b/rmo/pool.go @@ -13,7 +13,7 @@ import ( "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/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/aarondl/opt/omit" "github.com/rs/zerolog/log" ) @@ -31,7 +31,7 @@ var ( ) func getPool(w http.ResponseWriter, r *http.Request) { - htmlpage.RenderOrError( + html.RenderOrError( w, Pool, ContextPool{ @@ -41,7 +41,7 @@ func getPool(w http.ResponseWriter, r *http.Request) { } func getPoolSubmitComplete(w http.ResponseWriter, r *http.Request) { report := r.URL.Query().Get("report") - htmlpage.RenderOrError( + html.RenderOrError( w, PoolSubmitComplete, ContextPoolSubmitComplete{ diff --git a/rmo/quick.go b/rmo/quick.go index 87606e71..d001d358 100644 --- a/rmo/quick.go +++ b/rmo/quick.go @@ -16,7 +16,7 @@ import ( "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/html" "github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/platform/text" "github.com/aarondl/opt/omit" @@ -44,7 +44,7 @@ var ( ) func getQuick(w http.ResponseWriter, r *http.Request) { - htmlpage.RenderOrError( + html.RenderOrError( w, quickT, ContentQuick{}, @@ -80,7 +80,7 @@ func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) { } } } - htmlpage.RenderOrError( + html.RenderOrError( w, quickSubmitCompleteT, ContentQuickSubmitComplete{ @@ -91,7 +91,7 @@ func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) { } func getRegisterNotificationsComplete(w http.ResponseWriter, r *http.Request) { report := r.URL.Query().Get("report") - htmlpage.RenderOrError( + html.RenderOrError( w, registerNotificationsCompleteT, ContentRegisterNotificationsComplete{ diff --git a/rmo/root.go b/rmo/root.go index a2cf985e..18b5284f 100644 --- a/rmo/root.go +++ b/rmo/root.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/rs/zerolog/log" ) @@ -24,7 +24,7 @@ var ( ) func getPrivacy(w http.ResponseWriter, r *http.Request) { - htmlpage.RenderOrError( + html.RenderOrError( w, PrivacyT, ContentPrivacy{ @@ -36,7 +36,7 @@ func getPrivacy(w http.ResponseWriter, r *http.Request) { ) } func getRoot(w http.ResponseWriter, r *http.Request) { - htmlpage.RenderOrError( + html.RenderOrError( w, RootT, ContentRoot{}, diff --git a/rmo/routes.go b/rmo/routes.go index 105f2422..415fb836 100644 --- a/rmo/routes.go +++ b/rmo/routes.go @@ -1,7 +1,7 @@ package rmo import ( - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/go-chi/chi/v5" ) @@ -28,6 +28,6 @@ func Router() chi.Router { r.Get("/status", getStatus) r.Get("/status/{report_id}", getStatusByID) r.Get("/terms-of-service", getTerms) - htmlpage.AddStaticRoute(r, "/static") + html.AddStaticRoute(r, "/static") return r } diff --git a/rmo/search.go b/rmo/search.go index d5f0d3ac..d37dbd3a 100644 --- a/rmo/search.go +++ b/rmo/search.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" ) type ContentSearch struct { @@ -17,7 +17,7 @@ var ( ) func getSearch(w http.ResponseWriter, r *http.Request) { - htmlpage.RenderOrError( + html.RenderOrError( w, Search, ContentSearch{ diff --git a/rmo/status.go b/rmo/status.go index 98b134ea..402937c1 100644 --- a/rmo/status.go +++ b/rmo/status.go @@ -14,7 +14,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/sql" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/go-chi/chi/v5" "github.com/rs/zerolog/log" "github.com/stephenafamo/scan" @@ -84,7 +84,7 @@ func formatReportID(s string) string { func getStatus(w http.ResponseWriter, r *http.Request) { report_id_str := r.URL.Query().Get("report") if report_id_str == "" { - htmlpage.RenderOrError( + html.RenderOrError( w, Status, ContentStatus{ @@ -103,7 +103,7 @@ func getStatus(w http.ResponseWriter, r *http.Request) { } 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( + html.RenderOrError( w, Status, ContentStatus{ @@ -117,7 +117,7 @@ func getStatus(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, fmt.Sprintf("/status/%s", report_id), http.StatusFound) return } - htmlpage.RenderOrError( + html.RenderOrError( w, Status, ContentStatus{ @@ -226,7 +226,7 @@ func getStatusByID(w http.ResponseWriter, r *http.Request) { content, err = contentFromQuick(ctx, report_id) } content.MapboxToken = config.MapboxToken - htmlpage.RenderOrError( + html.RenderOrError( w, StatusByID, content, @@ -235,7 +235,7 @@ func getStatusByID(w http.ResponseWriter, r *http.Request) { /* func getQuick(w http.ResponseWriter, r *http.Request) { - htmlpage.RenderOrError( + html.RenderOrError( w, Quick, ContentQuick{}, @@ -244,7 +244,7 @@ func getStatusByID(w http.ResponseWriter, r *http.Request) { func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) { report := r.URL.Query().Get("report") - htmlpage.RenderOrError( + html.RenderOrError( w, QuickSubmitComplete, ContentQuickSubmitComplete{ diff --git a/rmo/template.go b/rmo/template.go index c7f6ebc4..f7db337d 100644 --- a/rmo/template.go +++ b/rmo/template.go @@ -4,7 +4,7 @@ import ( "embed" "fmt" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" ) //go:embed template/* @@ -13,7 +13,7 @@ var embeddedFiles embed.FS var components = [...]string{"footer", "header", "photo-upload", "photo-upload-header"} var svgs = [...]string{"check-report", "mosquito", "pond"} -func buildTemplate(files ...string) *htmlpage.BuiltTemplate { +func buildTemplate(files ...string) *html.BuiltTemplate { subdir := "rmo" full_files := make([]string, 0) for _, f := range files { @@ -25,5 +25,5 @@ func buildTemplate(files ...string) *htmlpage.BuiltTemplate { for _, c := range svgs { full_files = append(full_files, fmt.Sprintf("%s/template/svg/%s.svg", subdir, c)) } - return htmlpage.NewBuiltTemplate(embeddedFiles, "rmo/", full_files...) + return html.NewBuiltTemplate(embeddedFiles, "rmo/", full_files...) } diff --git a/sync/dash.go b/sync/dash.go index e0b58016..22ef7987 100644 --- a/sync/dash.go +++ b/sync/dash.go @@ -14,7 +14,7 @@ import ( "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/Gleipnir-Technology/nidus-sync/html" "github.com/go-chi/chi/v5" "github.com/google/uuid" "github.com/uber/h3-go/v4" @@ -97,7 +97,7 @@ func getDistrict(w http.ResponseWriter, r *http.Request) { context := ContextDistrict{ MapboxToken: config.MapboxToken, } - htmlpage.RenderOrError(w, districtT, &context) + html.RenderOrError(w, districtT, &context) } func getLayoutTest(w http.ResponseWriter, r *http.Request, u *models.User) { @@ -106,7 +106,7 @@ func getLayoutTest(w http.ResponseWriter, r *http.Request, u *models.User) { respondError(w, "Failed to get user", err, http.StatusInternalServerError) return } - htmlpage.RenderOrError(w, layoutTestT, &ContentLayoutTest{User: userContent}) + html.RenderOrError(w, layoutTestT, &ContentLayoutTest{User: userContent}) } func getRoot(w http.ResponseWriter, r *http.Request) { @@ -241,7 +241,7 @@ func cell(ctx context.Context, w http.ResponseWriter, user *models.User, c int64 Treatments: treatments, User: userContent, } - htmlpage.RenderOrError(w, cellT, &data) + html.RenderOrError(w, cellT, &data) } func dashboard(ctx context.Context, w http.ResponseWriter, user *models.User) { @@ -310,7 +310,7 @@ func dashboard(ctx context.Context, w http.ResponseWriter, user *models.User) { RecentRequests: requests, User: userContent, } - htmlpage.RenderOrError(w, dashboardT, data) + html.RenderOrError(w, dashboardT, data) } func settings(w http.ResponseWriter, r *http.Request, user *models.User) { @@ -322,7 +322,7 @@ func settings(w http.ResponseWriter, r *http.Request, user *models.User) { data := ContentAuthenticatedPlaceholder{ User: userContent, } - htmlpage.RenderOrError(w, settingsT, data) + html.RenderOrError(w, settingsT, data) } func source(w http.ResponseWriter, r *http.Request, user *models.User, id uuid.UUID) { @@ -383,7 +383,7 @@ func source(w http.ResponseWriter, r *http.Request, user *models.User, id uuid.U User: userContent, } - htmlpage.RenderOrError(w, sourceT, data) + html.RenderOrError(w, sourceT, data) } func trap(w http.ResponseWriter, r *http.Request, user *models.User, id uuid.UUID) { @@ -423,5 +423,5 @@ func trap(w http.ResponseWriter, r *http.Request, user *models.User, id uuid.UUI User: userContent, } - htmlpage.RenderOrError(w, trapT, data) + html.RenderOrError(w, trapT, data) } diff --git a/sync/mock.go b/sync/mock.go index 923c3840..e61dd847 100644 --- a/sync/mock.go +++ b/sync/mock.go @@ -6,7 +6,7 @@ import ( "strconv" "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/go-chi/chi/v5" "github.com/rs/zerolog/log" "github.com/skip2/go-qrcode" @@ -125,13 +125,13 @@ func mock(t string, w http.ResponseWriter, code string) { SettingUserAdd: "/mock/setting/user/add", }, } - template, ok := htmlpage.TemplatesByFilename[t+".html"] + template, ok := html.TemplatesByFilename[t+".html"] if !ok { log.Error().Str("template", t).Msg("Failed to find template") respondError(w, "Failed to render template", nil, http.StatusInternalServerError) return } - htmlpage.RenderOrError(w, &template, data) + html.RenderOrError(w, &template, data) } func renderMock(templateName string) http.HandlerFunc { diff --git a/sync/oauth.go b/sync/oauth.go index 25129363..7dc9d443 100644 --- a/sync/oauth.go +++ b/sync/oauth.go @@ -9,7 +9,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/background" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db/models" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/rs/zerolog/log" ) @@ -89,5 +89,5 @@ func oauthPrompt(w http.ResponseWriter, r *http.Request, user *models.User) { data := ContextOauthPrompt{ User: userContent, } - htmlpage.RenderOrError(w, oauthPromptT, data) + html.RenderOrError(w, oauthPromptT, data) } diff --git a/sync/page.go b/sync/page.go index 291c5e05..50d812ee 100644 --- a/sync/page.go +++ b/sync/page.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/rs/zerolog/log" ) @@ -14,7 +14,7 @@ var embeddedFiles embed.FS var components = [...]string{"header", "icons", "map", "sidebar"} -func buildTemplate(files ...string) *htmlpage.BuiltTemplate { +func buildTemplate(files ...string) *html.BuiltTemplate { subdir := "sync" full_files := make([]string, 0) for _, f := range files { @@ -23,7 +23,7 @@ func buildTemplate(files ...string) *htmlpage.BuiltTemplate { for _, c := range components { full_files = append(full_files, fmt.Sprintf("%s/template/components/%s.html", subdir, c)) } - return htmlpage.NewBuiltTemplate(embeddedFiles, "sync/", full_files...) + return html.NewBuiltTemplate(embeddedFiles, "sync/", full_files...) } // Respond with an error that is visible to the user diff --git a/sync/privacy.go b/sync/privacy.go index 9631a1aa..a1e98279 100644 --- a/sync/privacy.go +++ b/sync/privacy.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" ) type ContentPrivacy struct { @@ -19,7 +19,7 @@ var ( ) func getPrivacy(w http.ResponseWriter, r *http.Request) { - htmlpage.RenderOrError( + html.RenderOrError( w, PrivacyT, ContentPrivacy{ diff --git a/sync/routes.go b/sync/routes.go index abb2be6b..86c14bb9 100644 --- a/sync/routes.go +++ b/sync/routes.go @@ -3,7 +3,7 @@ package sync import ( "github.com/Gleipnir-Technology/nidus-sync/api" "github.com/Gleipnir-Technology/nidus-sync/auth" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/go-chi/chi/v5" ) @@ -67,6 +67,6 @@ func Router() chi.Router { r.Method("GET", "/trap/{globalid}", auth.NewEnsureAuth(getTrap)) r.Method("GET", "/text/{destination}", auth.NewEnsureAuth(getTextMessages)) - htmlpage.AddStaticRoute(r, "/static") + html.AddStaticRoute(r, "/static") return r } diff --git a/sync/signin.go b/sync/signin.go index 6b72d011..40c91861 100644 --- a/sync/signin.go +++ b/sync/signin.go @@ -7,7 +7,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/auth" "github.com/Gleipnir-Technology/nidus-sync/db/models" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/rs/zerolog/log" ) @@ -92,10 +92,10 @@ func signin(w http.ResponseWriter, errorCode string) { data := ContentSignin{ InvalidCredentials: errorCode == "invalid-credentials", } - htmlpage.RenderOrError(w, signinT, data) + html.RenderOrError(w, signinT, data) } func signup(w http.ResponseWriter, path string) { data := ContentSignup{} - htmlpage.RenderOrError(w, signupT, data) + html.RenderOrError(w, signupT, data) } diff --git a/sync/text.go b/sync/text.go index 2d91cbea..473b51e7 100644 --- a/sync/text.go +++ b/sync/text.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/Gleipnir-Technology/nidus-sync/db/models" - "github.com/Gleipnir-Technology/nidus-sync/htmlpage" + "github.com/Gleipnir-Technology/nidus-sync/html" ) type ContentTextMessages struct { @@ -24,5 +24,5 @@ func getTextMessages(w http.ResponseWriter, r *http.Request, u *models.User) { content := ContentTextMessages{ User: userContent, } - htmlpage.RenderOrError(w, textMessagesT, content) + html.RenderOrError(w, textMessagesT, content) } From bb9dd1754f6fef8ed9f7558d53cd035e5ee83968 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 18:22:16 +0000 Subject: [PATCH 0189/1453] Start setting up structure for generating URLs This is to eventually avoid adding URLs through hard-coded strings to our templates. --- rmo/mock.go | 11 ++--------- rmo/root.go | 48 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/rmo/mock.go b/rmo/mock.go index a49554bd..7d6a6599 100644 --- a/rmo/mock.go +++ b/rmo/mock.go @@ -22,13 +22,6 @@ type ContentDistrict struct { URLLogo string URLWebsite string } -type ContentURL struct { - Nuisance string - NuisanceSubmitComplete string - Status string - Tegola string - Water string -} type ContentMock struct { District ContentDistrict MapboxToken string @@ -48,7 +41,7 @@ func addMockRoutes(r chi.Router) { r.Get("/status", renderMock(mockStatusT)) } -func makeContentURL(slug string) ContentURL { +func makeContentURLMock(slug string) ContentURL { return ContentURL{ Nuisance: makeURLMock(slug, "nuisance"), NuisanceSubmitComplete: makeURLMock(slug, "nuisance-submit-complete"), @@ -77,7 +70,7 @@ func renderMock(t *html.BuiltTemplate) func(http.ResponseWriter, *http.Request) }, MapboxToken: config.MapboxToken, ReportID: "abcd-1234-5678", - URL: makeContentURL(slug), + URL: makeContentURLMock(slug), }, ) } diff --git a/rmo/root.go b/rmo/root.go index 18b5284f..b9edf4ab 100644 --- a/rmo/root.go +++ b/rmo/root.go @@ -15,7 +15,16 @@ type ContentPrivacy struct { Site string URLReport string } -type ContentRoot struct{} +type ContentRoot struct { + URL ContentURL +} +type ContentURL struct { + Nuisance string + NuisanceSubmitComplete string + Status string + Tegola string + Water string +} var ( PrivacyT = buildTemplate("privacy", "base") @@ -23,6 +32,14 @@ var ( TermsT = buildTemplate("terms", "base") ) +func boolFromForm(r *http.Request, k string) bool { + s := r.PostFormValue(k) + if s == "on" { + return true + } + return false +} + func getPrivacy(w http.ResponseWriter, r *http.Request) { html.RenderOrError( w, @@ -48,27 +65,26 @@ func getRobots(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Allow: /\n") } func getTerms(w http.ResponseWriter, r *http.Request) { - htmlpage.RenderOrError( + html.RenderOrError( w, TermsT, ContentRoot{}, ) } -// Respond with an error that is visible to the user -func respondError(w http.ResponseWriter, m string, e error, s int) { - log.Warn().Int("status", s).Err(e).Str("user message", m).Msg("Responding with an error") - http.Error(w, m, s) -} - -func boolFromForm(r *http.Request, k string) bool { - s := r.PostFormValue(k) - if s == "on" { - return true +func makeContentURL(slug string) ContentURL { + return ContentURL{ + Nuisance: makeURL("nuisance"), + NuisanceSubmitComplete: makeURL("nuisance-submit-complete"), + Status: makeURL("status"), + Tegola: config.MakeURLTegola("/"), + Water: makeURL("water"), } - return false } +func makeURL(p string) string { + return config.MakeURLReport("/%s", p) +} func postFormValueOrNone(r *http.Request, k string) string { v := r.PostFormValue(k) if v == "" { @@ -76,3 +92,9 @@ func postFormValueOrNone(r *http.Request, k string) string { } return v } + +// Respond with an error that is visible to the user +func respondError(w http.ResponseWriter, m string, e error, s int) { + log.Warn().Int("status", s).Err(e).Str("user message", m).Msg("Responding with an error") + http.Error(w, m, s) +} From 38b1cdbbad3c9de5dd9e067ea35ad238d3e7e60a Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 18:58:50 +0000 Subject: [PATCH 0190/1453] Auto transform SVGs into template portions This means I don't have to modify the files correctly by hand --- html/html.go | 55 +++++++++++++++++++++++++++---- rmo/template.go | 5 +-- rmo/template/root.html | 12 ++----- rmo/template/svg/check-report.svg | 2 -- rmo/template/svg/mosquito.svg | 2 -- rmo/template/svg/pond.svg | 2 -- sync/page.go | 2 +- 7 files changed, 55 insertions(+), 25 deletions(-) diff --git a/html/html.go b/html/html.go index f16312d8..c3b99fa6 100644 --- a/html/html.go +++ b/html/html.go @@ -26,6 +26,7 @@ var TemplatesByFilename = make(map[string]BuiltTemplate, 0) type BuiltTemplate struct { files []string subdir string + svgs []string // Nil if we are going to read templates off disk every time we render // because we are in development mode. template *template.Template @@ -34,7 +35,7 @@ type BuiltTemplate struct { func (bt *BuiltTemplate) executeTemplate(w io.Writer, data any) error { if bt.template == nil { name := path.Base(bt.files[0]) - templ, err := parseFromDisk(bt.files) + templ, err := parseFromDisk(bt.svgs, bt.files) if err != nil { return fmt.Errorf("Failed to parse template file: %w", err) } @@ -50,7 +51,7 @@ func (bt *BuiltTemplate) executeTemplate(w io.Writer, data any) error { } } -func NewBuiltTemplate(embeddedFiles embed.FS, subdir string, files ...string) *BuiltTemplate { +func NewBuiltTemplate(embeddedFiles embed.FS, subdir string, svgs []string, files ...string) *BuiltTemplate { files_on_disk := true for _, f := range files { _, err := os.Stat(f) @@ -67,13 +68,15 @@ func NewBuiltTemplate(embeddedFiles embed.FS, subdir string, files ...string) *B result = BuiltTemplate{ files: files, subdir: subdir, + svgs: svgs, template: nil, } } else { result = BuiltTemplate{ files: files, subdir: subdir, - template: parseEmbedded(embeddedFiles, subdir, files), + svgs: svgs, + template: parseEmbedded(embeddedFiles, subdir, svgs, files), } } TemplatesByFilename[path.Base(files[0])] = result @@ -125,7 +128,7 @@ func makeFuncMap() template.FuncMap { } return funcMap } -func parseEmbedded(embeddedFiles embed.FS, subdir string, files []string) *template.Template { +func parseEmbedded(embeddedFiles embed.FS, subdir string, svgs []string, files []string) *template.Template { funcMap := makeFuncMap() // Remap the file names to embedded paths embeddedFilePaths := make([]string, 0) @@ -134,11 +137,31 @@ func parseEmbedded(embeddedFiles embed.FS, subdir string, files []string) *templ } name := path.Base(embeddedFilePaths[0]) log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template") - return template.Must( - template.New(name).Funcs(funcMap).ParseFS(embeddedFiles, embeddedFilePaths...)) + t, err := template.New(name).Funcs(funcMap).ParseFS(embeddedFiles, embeddedFilePaths...) + if err != nil { + panic(fmt.Sprintf("Failed to parse embedded template %s: %v", name, err)) + } + for _, svg := range svgs { + svg_path := strings.TrimPrefix(svg, subdir) + content, err := embeddedFiles.ReadFile(svg_path) + if err != nil { + panic(fmt.Sprintf("Failed to read svg '%s' from embedded filesystem: %v", svg, err)) + } + svg_name := path.Base(svg) + svg_template := fmt.Sprintf("{{define \"%s\"}}%s{{end}}", svg_name, string(content)) + svg_t, err := template.New(svg_name).Parse(svg_template) + if err != nil { + panic(fmt.Sprintf("Failed to parse svg '%s' from embedded filesystem: %v", svg, err)) + } + _, err = t.AddParseTree(svg_t.Name(), svg_t.Tree) + if err != nil { + panic(fmt.Sprintf("Failed to add svg '%s' to embedded template: %v", svg, err)) + } + } + return t } -func parseFromDisk(files []string) (*template.Template, error) { +func parseFromDisk(svgs []string, files []string) (*template.Template, error) { funcMap := makeFuncMap() name := path.Base(files[0]) //log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk") @@ -146,6 +169,24 @@ func parseFromDisk(files []string) (*template.Template, error) { if err != nil { return nil, fmt.Errorf("Failed to parse %s: %w", files, err) } + for _, svg := range svgs { + content, err := os.ReadFile(svg) + if err != nil { + return nil, fmt.Errorf("Failed to read svg '%s' from filesystem: %w", svg, err) + } + svg_name := path.Base(svg) + svg_template := fmt.Sprintf("{{define \"%s\"}}%s{{end}}", svg_name, string(content)) + svg_t, err := template.New(svg_name).Parse(svg_template) + if err != nil { + log.Debug().Str("svg", svg).Str("svg_name", svg_name).Str("template", svg_template).Msg("failed to parse") + return nil, fmt.Errorf("Failed to parse svg '%s' from filesystem: %w", svg, err) + } + _, err = templ.AddParseTree(svg_t.Name(), svg_t.Tree) + if err != nil { + return nil, fmt.Errorf("Failed to add svg '%s' to template: %w", svg, err) + } + log.Debug().Str("name", svg_t.Name()).Str("svg_name", svg_name).Msg("Added svg template") + } return templ, nil } diff --git a/rmo/template.go b/rmo/template.go index f7db337d..d86ecd1d 100644 --- a/rmo/template.go +++ b/rmo/template.go @@ -22,8 +22,9 @@ func buildTemplate(files ...string) *html.BuiltTemplate { for _, c := range components { full_files = append(full_files, fmt.Sprintf("%s/template/component/%s.html", subdir, c)) } + full_svgs := make([]string, 0) for _, c := range svgs { - full_files = append(full_files, fmt.Sprintf("%s/template/svg/%s.svg", subdir, c)) + full_svgs = append(full_svgs, fmt.Sprintf("%s/template/svg/%s.svg", subdir, c)) } - return html.NewBuiltTemplate(embeddedFiles, "rmo/", full_files...) + return html.NewBuiltTemplate(embeddedFiles, "rmo/", full_svgs, full_files...) } diff --git a/rmo/template/root.html b/rmo/template/root.html index d55a39bb..43d1da62 100644 --- a/rmo/template/root.html +++ b/rmo/template/root.html @@ -66,9 +66,7 @@
    - - - + {{ template "mosquito.svg" }}

    Follow-up or Check Status

    Check on a previous request or view current mosquito activity in your area.

    @@ -82,9 +80,7 @@
    - - - + {{ template "pond.svg" }}

    Report a Green Pool

    Report stagnant water sources like abandoned pools that may breed mosquitoes.

    @@ -98,9 +94,7 @@
    - - - + {{ template "check-report.svg" }}

    Report Mosquito Nuisance

    Report areas with high adult mosquito activity causing discomfort or concern.

    diff --git a/rmo/template/svg/check-report.svg b/rmo/template/svg/check-report.svg index 15b82fd6..bc645483 100644 --- a/rmo/template/svg/check-report.svg +++ b/rmo/template/svg/check-report.svg @@ -1,3 +1 @@ -{{define "svg/check-report"}} -{{end}} diff --git a/rmo/template/svg/mosquito.svg b/rmo/template/svg/mosquito.svg index ccbcec39..a19b8b23 100644 --- a/rmo/template/svg/mosquito.svg +++ b/rmo/template/svg/mosquito.svg @@ -1,3 +1 @@ -{{define "svg/mosquito"}} -{{end}} diff --git a/rmo/template/svg/pond.svg b/rmo/template/svg/pond.svg index bffa55de..d44728ee 100644 --- a/rmo/template/svg/pond.svg +++ b/rmo/template/svg/pond.svg @@ -1,3 +1 @@ -{{define "svg/pond"}} -{{end}} diff --git a/sync/page.go b/sync/page.go index 50d812ee..8cd7b1c0 100644 --- a/sync/page.go +++ b/sync/page.go @@ -23,7 +23,7 @@ func buildTemplate(files ...string) *html.BuiltTemplate { for _, c := range components { full_files = append(full_files, fmt.Sprintf("%s/template/components/%s.html", subdir, c)) } - return html.NewBuiltTemplate(embeddedFiles, "sync/", full_files...) + return html.NewBuiltTemplate(embeddedFiles, "sync/", []string{}, full_files...) } // Respond with an error that is visible to the user From 40028744bad0beb2a33d3a514bd8ea7d1be259a3 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 20:11:17 +0000 Subject: [PATCH 0191/1453] Fix svg pipeline to not need an explicit list --- html/html.go | 83 +++++++++++++++++++------------------ html/static.go | 2 - rmo/template.go | 7 +--- rmo/template/mock/root.html | 6 +-- sync/page.go | 2 +- 5 files changed, 47 insertions(+), 53 deletions(-) diff --git a/html/html.go b/html/html.go index c3b99fa6..47116935 100644 --- a/html/html.go +++ b/html/html.go @@ -7,6 +7,7 @@ import ( "fmt" "html/template" "io" + "io/fs" "math" "net/http" "os" @@ -26,7 +27,6 @@ var TemplatesByFilename = make(map[string]BuiltTemplate, 0) type BuiltTemplate struct { files []string subdir string - svgs []string // Nil if we are going to read templates off disk every time we render // because we are in development mode. template *template.Template @@ -35,7 +35,7 @@ type BuiltTemplate struct { func (bt *BuiltTemplate) executeTemplate(w io.Writer, data any) error { if bt.template == nil { name := path.Base(bt.files[0]) - templ, err := parseFromDisk(bt.svgs, bt.files) + templ, err := parseFromDisk(bt.subdir, bt.files) if err != nil { return fmt.Errorf("Failed to parse template file: %w", err) } @@ -51,7 +51,7 @@ func (bt *BuiltTemplate) executeTemplate(w io.Writer, data any) error { } } -func NewBuiltTemplate(embeddedFiles embed.FS, subdir string, svgs []string, files ...string) *BuiltTemplate { +func NewBuiltTemplate(embeddedFiles embed.FS, subdir string, files ...string) *BuiltTemplate { files_on_disk := true for _, f := range files { _, err := os.Stat(f) @@ -68,15 +68,13 @@ func NewBuiltTemplate(embeddedFiles embed.FS, subdir string, svgs []string, file result = BuiltTemplate{ files: files, subdir: subdir, - svgs: svgs, template: nil, } } else { result = BuiltTemplate{ files: files, subdir: subdir, - svgs: svgs, - template: parseEmbedded(embeddedFiles, subdir, svgs, files), + template: parseEmbedded(embeddedFiles, subdir, files), } } TemplatesByFilename[path.Base(files[0])] = result @@ -128,7 +126,32 @@ func makeFuncMap() template.FuncMap { } return funcMap } -func parseEmbedded(embeddedFiles embed.FS, subdir string, svgs []string, files []string) *template.Template { +func addSVGTemplates(fsys fs.FS, templ *template.Template) error { + svgs, err := fs.ReadDir(fsys, ".") + if err != nil { + log.Warn().Msg("Failed to read svg directory") + return nil + } + for _, svg := range svgs { + content, err := fs.ReadFile(fsys, svg.Name()) + if err != nil { + return fmt.Errorf("Failed to read svg '%s' from embedded filesystem: %w", svg, err) + } + svg_name := svg.Name() + svg_template := fmt.Sprintf("{{define \"%s\"}}%s{{end}}", svg_name, string(content)) + svg_t, err := template.New(svg_name).Parse(svg_template) + if err != nil { + return fmt.Errorf("Failed to parse svg '%s' from embedded filesystem: %v", svg, err) + } + _, err = templ.AddParseTree(svg_t.Name(), svg_t.Tree) + if err != nil { + return fmt.Errorf("Failed to add svg '%s' to embedded template: %v", svg, err) + } + log.Debug().Str("name", svg_name).Msg("add svg template") + } + return nil +} +func parseEmbedded(embeddedFiles embed.FS, subdir string, files []string) *template.Template { funcMap := makeFuncMap() // Remap the file names to embedded paths embeddedFilePaths := make([]string, 0) @@ -141,27 +164,18 @@ func parseEmbedded(embeddedFiles embed.FS, subdir string, svgs []string, files [ if err != nil { panic(fmt.Sprintf("Failed to parse embedded template %s: %v", name, err)) } - for _, svg := range svgs { - svg_path := strings.TrimPrefix(svg, subdir) - content, err := embeddedFiles.ReadFile(svg_path) - if err != nil { - panic(fmt.Sprintf("Failed to read svg '%s' from embedded filesystem: %v", svg, err)) - } - svg_name := path.Base(svg) - svg_template := fmt.Sprintf("{{define \"%s\"}}%s{{end}}", svg_name, string(content)) - svg_t, err := template.New(svg_name).Parse(svg_template) - if err != nil { - panic(fmt.Sprintf("Failed to parse svg '%s' from embedded filesystem: %v", svg, err)) - } - _, err = t.AddParseTree(svg_t.Name(), svg_t.Tree) - if err != nil { - panic(fmt.Sprintf("Failed to add svg '%s' to embedded template: %v", svg, err)) - } + svg_fs, err := fs.Sub(embeddedFiles, "template/svg") + if err != nil { + panic(fmt.Sprintf("Failed to read static/svg: %v", err)) + } + err = addSVGTemplates(svg_fs, t) + if err != nil { + panic(fmt.Sprintf("Failed to add SVG templates: %v", err)) } return t } -func parseFromDisk(svgs []string, files []string) (*template.Template, error) { +func parseFromDisk(subdir string, files []string) (*template.Template, error) { funcMap := makeFuncMap() name := path.Base(files[0]) //log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk") @@ -169,23 +183,10 @@ func parseFromDisk(svgs []string, files []string) (*template.Template, error) { if err != nil { return nil, fmt.Errorf("Failed to parse %s: %w", files, err) } - for _, svg := range svgs { - content, err := os.ReadFile(svg) - if err != nil { - return nil, fmt.Errorf("Failed to read svg '%s' from filesystem: %w", svg, err) - } - svg_name := path.Base(svg) - svg_template := fmt.Sprintf("{{define \"%s\"}}%s{{end}}", svg_name, string(content)) - svg_t, err := template.New(svg_name).Parse(svg_template) - if err != nil { - log.Debug().Str("svg", svg).Str("svg_name", svg_name).Str("template", svg_template).Msg("failed to parse") - return nil, fmt.Errorf("Failed to parse svg '%s' from filesystem: %w", svg, err) - } - _, err = templ.AddParseTree(svg_t.Name(), svg_t.Tree) - if err != nil { - return nil, fmt.Errorf("Failed to add svg '%s' to template: %w", svg, err) - } - log.Debug().Str("name", svg_t.Name()).Str("svg_name", svg_name).Msg("Added svg template") + fsys := os.DirFS(subdir + "/template/svg") + err = addSVGTemplates(fsys, templ) + if err != nil { + return nil, fmt.Errorf("Failed to add SVGs from disk: %w", err) } return templ, nil } diff --git a/html/static.go b/html/static.go index c6fbb429..4f4818a0 100644 --- a/html/static.go +++ b/html/static.go @@ -2,11 +2,9 @@ package html import ( "embed" - "io/fs" "net/http" "github.com/go-chi/chi/v5" - "github.com/rs/zerolog/log" ) //go:embed static/* diff --git a/rmo/template.go b/rmo/template.go index d86ecd1d..3238b81b 100644 --- a/rmo/template.go +++ b/rmo/template.go @@ -11,7 +11,6 @@ import ( var embeddedFiles embed.FS var components = [...]string{"footer", "header", "photo-upload", "photo-upload-header"} -var svgs = [...]string{"check-report", "mosquito", "pond"} func buildTemplate(files ...string) *html.BuiltTemplate { subdir := "rmo" @@ -22,9 +21,5 @@ func buildTemplate(files ...string) *html.BuiltTemplate { for _, c := range components { full_files = append(full_files, fmt.Sprintf("%s/template/component/%s.html", subdir, c)) } - full_svgs := make([]string, 0) - for _, c := range svgs { - full_svgs = append(full_svgs, fmt.Sprintf("%s/template/svg/%s.svg", subdir, c)) - } - return html.NewBuiltTemplate(embeddedFiles, "rmo/", full_svgs, full_files...) + return html.NewBuiltTemplate(embeddedFiles, "rmo/", full_files...) } diff --git a/rmo/template/mock/root.html b/rmo/template/mock/root.html index ff6c712c..232fe125 100644 --- a/rmo/template/mock/root.html +++ b/rmo/template/mock/root.html @@ -56,7 +56,7 @@
    - {{ template "svg/mosquito" }} + {{ template "mosquito.svg" }}

    Report Mosquito Nuisance

    Report areas with high adult mosquito activity causing discomfort or concern.

    @@ -68,7 +68,7 @@
    - {{ template "svg/pond" }} + {{ template "pond.svg" }}

    Report Standing Water

    Report any water that has been sitting for several days, where mosquitoes can live.

    @@ -80,7 +80,7 @@
    - {{ template "svg/check-report" }} + {{ template "check-report.svg" }}

    Follow-up or Check Status

    Check on a previous request or view current mosquito activity in your area.

    diff --git a/sync/page.go b/sync/page.go index 8cd7b1c0..50d812ee 100644 --- a/sync/page.go +++ b/sync/page.go @@ -23,7 +23,7 @@ func buildTemplate(files ...string) *html.BuiltTemplate { for _, c := range components { full_files = append(full_files, fmt.Sprintf("%s/template/components/%s.html", subdir, c)) } - return html.NewBuiltTemplate(embeddedFiles, "sync/", []string{}, full_files...) + return html.NewBuiltTemplate(embeddedFiles, "sync/", full_files...) } // Respond with an error that is visible to the user From 7fb02f478833f322926926e9ddde927ee501f877 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 20:14:10 +0000 Subject: [PATCH 0192/1453] Update root RMO to use banner and new colored icon --- rmo/template/mock/root.html | 34 ----------- rmo/template/root.html | 91 +++++------------------------ rmo/template/svg/mosquito-color.svg | 1 + scss/custom.scss | 1 + scss/rmo/root.scss | 32 ++++++++++ 5 files changed, 47 insertions(+), 112 deletions(-) create mode 100644 rmo/template/svg/mosquito-color.svg create mode 100644 scss/rmo/root.scss diff --git a/rmo/template/mock/root.html b/rmo/template/mock/root.html index 232fe125..5f0f8df9 100644 --- a/rmo/template/mock/root.html +++ b/rmo/template/mock/root.html @@ -2,40 +2,6 @@ {{define "title"}}Main{{end}} {{define "extraheader"}} - {{end}} {{define "content"}} diff --git a/rmo/template/root.html b/rmo/template/root.html index 43d1da62..80aed492 100644 --- a/rmo/template/root.html +++ b/rmo/template/root.html @@ -2,57 +2,14 @@ {{define "title"}}Main{{end}} {{define "extraheader"}} - {{end}} {{define "content"}}
    -
    -
    -
    -

    Report Mosquitoes Online

    -

    - We are dedicated to protecting public health and improving quality of life by reducing - mosquito populations and the diseases they can carry. Our districts provide comprehensive - mosquito surveillance, control, and education services to our community. -

    -
    -
    -
    -
    - - -
    -
    -
    -
    -

    On the go?

    - Make a Quick Report -

    Report mosquito issues in under 60 seconds

    -
    -
    +
    @@ -61,67 +18,45 @@

    How Can We Help You Today?

    - - -
    {{ template "pond.svg" }}
    -

    Report a Green Pool

    -

    Report stagnant water sources like abandoned pools that may breed mosquitoes.

    - Report Source +

    Report Standing Water

    +

    Report any water that has been sitting for several days, where mosquitoes can live.

    + Report Source
    - -
    {{ template "check-report.svg" }}
    -

    Report Mosquito Nuisance

    -

    Report areas with high adult mosquito activity causing discomfort or concern.

    - Report Problem +

    Follow-up or Check Status

    +

    Check on a previous request or view current mosquito activity in your area.

    + Get Status
    +
    - -
    -
    -
    -
    -
    -
    -
    Need to make a quick report?
    -

    Use our streamlined form to report mosquito issues in under 60 seconds

    -
    - -
    -
    -
    -
    -
    diff --git a/rmo/template/svg/mosquito-color.svg b/rmo/template/svg/mosquito-color.svg new file mode 100644 index 00000000..00b251d7 --- /dev/null +++ b/rmo/template/svg/mosquito-color.svg @@ -0,0 +1 @@ + diff --git a/scss/custom.scss b/scss/custom.scss index b69de586..4edb4fde 100644 --- a/scss/custom.scss +++ b/scss/custom.scss @@ -41,3 +41,4 @@ $theme-colors: map-merge( @import "./bootstrap/scss/bootstrap"; @import "./sidebar.scss"; +@import "./rmo/root.scss"; diff --git a/scss/rmo/root.scss b/scss/rmo/root.scss new file mode 100644 index 00000000..5606b41a --- /dev/null +++ b/scss/rmo/root.scss @@ -0,0 +1,32 @@ +.service-card { + transition: transform 0.3s; + height: 100%; +} +.service-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 20px rgba(0,0,0,0.1); +} +.district-logo { + max-height: 80px; + width: auto; +} +.quick-report-mobile { + background-color: #ff9800; +} +.quick-report-desktop { + background-color: #ffefd5; + border-left: 4px solid #ff9800; +} + +.banner-container { + position: relative; + width: 100%; + background-color: #F76436; + overflow: hidden; +} + +.banner-image { + display: block; + /* width as needed */ +} + From 48c49fc73e4aadf423c603b7cc52128d5f5a4bf0 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 20:17:29 +0000 Subject: [PATCH 0193/1453] Use colored svgs for pond and status on RMO --- rmo/template/root.html | 4 ++-- rmo/template/svg/check-report-color.svg | 1 + rmo/template/svg/pond-color.svg | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 rmo/template/svg/check-report-color.svg create mode 100644 rmo/template/svg/pond-color.svg diff --git a/rmo/template/root.html b/rmo/template/root.html index 80aed492..23d1de93 100644 --- a/rmo/template/root.html +++ b/rmo/template/root.html @@ -34,7 +34,7 @@
    - {{ template "pond.svg" }} + {{ template "pond-color.svg" }}

    Report Standing Water

    Report any water that has been sitting for several days, where mosquitoes can live.

    @@ -46,7 +46,7 @@
    - {{ template "check-report.svg" }} + {{ template "check-report-color.svg" }}

    Follow-up or Check Status

    Check on a previous request or view current mosquito activity in your area.

    diff --git a/rmo/template/svg/check-report-color.svg b/rmo/template/svg/check-report-color.svg new file mode 100644 index 00000000..4d98b2e0 --- /dev/null +++ b/rmo/template/svg/check-report-color.svg @@ -0,0 +1 @@ + diff --git a/rmo/template/svg/pond-color.svg b/rmo/template/svg/pond-color.svg new file mode 100644 index 00000000..22383a84 --- /dev/null +++ b/rmo/template/svg/pond-color.svg @@ -0,0 +1 @@ + From bab8af4572f015e6aa86f968a92958596af015b1 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 30 Jan 2026 20:41:02 +0000 Subject: [PATCH 0194/1453] Get basic bones of the nuisance page copied from the mock --- rmo/mock.go | 5 - rmo/nuisance.go | 17 +- rmo/root.go | 15 +- rmo/routes.go | 8 +- rmo/template.go | 2 +- .../{header.html => header-district.html} | 6 +- rmo/template/component/header-rmo.html | 12 + rmo/template/mock/district-root.html | 6 +- rmo/template/mock/nuisance.html | 95 +-- rmo/template/nuisance.html | 563 +++++++----------- scss/custom.scss | 1 + scss/rmo/nuisance.scss | 93 +++ 12 files changed, 371 insertions(+), 452 deletions(-) rename rmo/template/component/{header.html => header-district.html} (62%) create mode 100644 rmo/template/component/header-rmo.html create mode 100644 scss/rmo/nuisance.scss diff --git a/rmo/mock.go b/rmo/mock.go index 7d6a6599..3e6874ce 100644 --- a/rmo/mock.go +++ b/rmo/mock.go @@ -17,11 +17,6 @@ var ( mockWaterT = buildTemplate("mock/water", "base") ) -type ContentDistrict struct { - Name string - URLLogo string - URLWebsite string -} type ContentMock struct { District ContentDistrict MapboxToken string diff --git a/rmo/nuisance.go b/rmo/nuisance.go index 5580daa4..3a66d23c 100644 --- a/rmo/nuisance.go +++ b/rmo/nuisance.go @@ -6,6 +6,7 @@ import ( "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" @@ -15,8 +16,12 @@ import ( "github.com/rs/zerolog/log" ) -type ContextNuisance struct{} -type ContextNuisanceSubmitComplete struct { +type ContentNuisance struct { + District *ContentDistrict + MapboxToken string + URL ContentURL +} +type ContentNuisanceSubmitComplete struct { ReportID string } @@ -29,7 +34,11 @@ func getNuisance(w http.ResponseWriter, r *http.Request) { html.RenderOrError( w, Nuisance, - ContextNuisance{}, + ContentNuisance{ + District: nil, + MapboxToken: config.MapboxToken, + URL: makeContentURL(), + }, ) } func getNuisanceSubmitComplete(w http.ResponseWriter, r *http.Request) { @@ -37,7 +46,7 @@ func getNuisanceSubmitComplete(w http.ResponseWriter, r *http.Request) { html.RenderOrError( w, NuisanceSubmitComplete, - ContextNuisanceSubmitComplete{ + ContentNuisanceSubmitComplete{ ReportID: report, }, ) diff --git a/rmo/root.go b/rmo/root.go index b9edf4ab..a878e6ce 100644 --- a/rmo/root.go +++ b/rmo/root.go @@ -9,6 +9,11 @@ import ( "github.com/rs/zerolog/log" ) +type ContentDistrict struct { + Name string + URLLogo string + URLWebsite string +} type ContentPrivacy struct { Address string Company string @@ -56,7 +61,9 @@ func getRoot(w http.ResponseWriter, r *http.Request) { html.RenderOrError( w, RootT, - ContentRoot{}, + ContentRoot{ + URL: makeContentURL(), + }, ) } @@ -68,11 +75,13 @@ func getTerms(w http.ResponseWriter, r *http.Request) { html.RenderOrError( w, TermsT, - ContentRoot{}, + ContentRoot{ + URL: makeContentURL(), + }, ) } -func makeContentURL(slug string) ContentURL { +func makeContentURL() ContentURL { return ContentURL{ Nuisance: makeURL("nuisance"), NuisanceSubmitComplete: makeURL("nuisance-submit-complete"), diff --git a/rmo/routes.go b/rmo/routes.go index 415fb836..41662d7e 100644 --- a/rmo/routes.go +++ b/rmo/routes.go @@ -8,12 +8,18 @@ import ( func Router() chi.Router { r := chi.NewRouter() r.Get("/", getRoot) + r.Get("/nuisance", getNuisance) + //r.Get("/district/{slug}", renderMock(mockDistrictRootT)) + //r.Get("/district/{slug}/nuisance", renderMock(mockNuisanceT)) + //r.Get("/district/{slug}/nuisance-submit-complete", renderMock(mockNuisanceSubmitCompleteT)) + //r.Get("/district/{slug}/status", renderMock(mockStatusT)) + //r.Get("/district/{slug}/water", renderMock(mockWaterT)) + r.Get("/privacy", getPrivacy) r.Get("/robots.txt", getRobots) r.Get("/email", getEmailByCode) r.Get("/image/{uuid}", getImageByUUID) r.Route("/mock", addMockRoutes) - r.Get("/nuisance", getNuisance) r.Post("/nuisance-submit", postNuisance) r.Get("/nuisance-submit-complete", getNuisanceSubmitComplete) r.Get("/pool", getPool) diff --git a/rmo/template.go b/rmo/template.go index 3238b81b..470c2596 100644 --- a/rmo/template.go +++ b/rmo/template.go @@ -10,7 +10,7 @@ import ( //go:embed template/* var embeddedFiles embed.FS -var components = [...]string{"footer", "header", "photo-upload", "photo-upload-header"} +var components = [...]string{"footer", "header-district", "header-rmo", "photo-upload", "photo-upload-header"} func buildTemplate(files ...string) *html.BuiltTemplate { subdir := "rmo" diff --git a/rmo/template/component/header.html b/rmo/template/component/header-district.html similarity index 62% rename from rmo/template/component/header.html rename to rmo/template/component/header-district.html index 6679d739..b80301b8 100644 --- a/rmo/template/component/header.html +++ b/rmo/template/component/header-district.html @@ -1,11 +1,11 @@ -{{define "header"}} +{{define "header-district"}} diff --git a/rmo/template/component/header-rmo.html b/rmo/template/component/header-rmo.html new file mode 100644 index 00000000..912b6bba --- /dev/null +++ b/rmo/template/component/header-rmo.html @@ -0,0 +1,12 @@ +{{define "header-rmo"}} + + +{{end}} diff --git a/rmo/template/mock/district-root.html b/rmo/template/mock/district-root.html index f3069987..9a32b37c 100644 --- a/rmo/template/mock/district-root.html +++ b/rmo/template/mock/district-root.html @@ -53,7 +53,7 @@
    - {{ template "svg/mosquito" }} + {{ template "mosquito-color.svg" }}

    Report Mosquito Nuisance

    Report areas with high adult mosquito activity causing discomfort or concern.

    @@ -65,7 +65,7 @@
    - {{ template "svg/pond" }} + {{ template "pond-color.svg" }}

    Report Standing Water

    Report any water that has been sitting for several days, where mosquitoes can live.

    @@ -77,7 +77,7 @@
    - {{ template "svg/check-report" }} + {{ template "check-report-color.svg" }}

    Follow-up or Check Status

    Check on a previous request or view current mosquito activity in your area.

    diff --git a/rmo/template/mock/nuisance.html b/rmo/template/mock/nuisance.html index aa2f88b6..83d02910 100644 --- a/rmo/template/mock/nuisance.html +++ b/rmo/template/mock/nuisance.html @@ -138,99 +138,6 @@ document.addEventListener('DOMContentLoaded', function() { }); {{end}} {{define "content"}} @@ -275,7 +182,7 @@ select.tall {
    - {{ template "svg/mosquito" }} + {{ template "mosquito-color.svg" }}

    Mosquito Activity Information

    The time when mosquitoes are active can help us identify the species and likely breeding sources.

    diff --git a/rmo/template/nuisance.html b/rmo/template/nuisance.html index ccc5ee93..1f1a9e31 100644 --- a/rmo/template/nuisance.html +++ b/rmo/template/nuisance.html @@ -2,7 +2,14 @@ {{define "title"}}Nuisance{{end}} {{define "extraheader"}} + + + + + + {{end}} {{define "content"}} +{{if (eq .District nil)}} + {{template "header-rmo" .}} +{{else}} + {{template "header-district" .District}} +{{end}}
    -

    Report Mosquito Nuisance

    @@ -152,24 +154,38 @@ document.addEventListener('DOMContentLoaded', function() {
    - -
    -
    - -
    -
    - + +
    +
    + +

    Nuisance Location Information

    +
    +
    +
    +

    You can select the location by address or by moving the marker on the map.

    +
    +
    + +
    +
    + + +
    +
    +
    +

    You can also click on the map to mark the location precisely

    + + +
    - + {{ template "mosquito-color.svg" }}

    Mosquito Activity Information

    - optional

    The time when mosquitoes are active can help us identify the species and likely breeding sources.

    @@ -227,260 +243,131 @@ document.addEventListener('DOMContentLoaded', function() {
    - - -
    - - -
    -
    -
    Minor
    - Occasional mosquito -
    -
    -
    Moderate
    - Regular presence -
    -
    -
    Severe
    - Many mosquitoes -
    -
    -
    - Current selection: 3/5 -
    -
    - - - -
    - -
    -
    - -

    Potential Mosquito Sources

    - optional -
    -

    Have you noticed any of these common mosquito breeding sources in your area?

    - -
    -
    -
    Did you know?
    -

    Mosquitoes can breed in as little as a bottle cap of water! Eliminating standing water is the most effective way to reduce mosquito populations.

    + +
    + +
    +
    + +

    Potential Mosquito Sources

    -
    +

    Have you noticed any of these common mosquito breeding sources in your area?

    -
    - -
    -
    -
    -
    - -
    -
    Stagnant Water
    -

    Green pools, ponds, fountains, or birdbaths that aren't maintained

    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - -
    -
    Containers
    -

    Buckets, planters, toys, tires, or any items that collect rainwater

    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - -
    -
    Roof & Gutters
    -

    Clogged gutters, flat roofs, or AC units that collect water

    -
    - - -
    -
    -
    -
    -
    - - - -
    -
    - - -
    -
    -
    - - -
    -
    - -

    Inspection Request

    -
    -

    Would you like our technicians to inspect for potential mosquito sources?

    - -
    -
    -
    -
    Property Inspection
    -

    Request a technician to inspect your property for mosquito sources. We'll contact you to schedule a convenient time.

    -
    - - -
    -
    -
    - -
    -
    -
    Neighborhood Inspection
    -

    Request a general inspection of your neighborhood. We'll survey the area for potential mosquito breeding sources.

    -
    - - -
    -
    -
    -
    - - -
    From b77d9aa80a2c11278e62581c3df080e401c433ed Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 2 Feb 2026 07:14:03 +0000 Subject: [PATCH 0233/1453] Correctly set location data after reverse geocode This fixes the location when a marker drag finishes --- rmo/template/nuisance.html | 1 + 1 file changed, 1 insertion(+) diff --git a/rmo/template/nuisance.html b/rmo/template/nuisance.html index f77bac98..47839138 100644 --- a/rmo/template/nuisance.html +++ b/rmo/template/nuisance.html @@ -19,6 +19,7 @@ async function handleMarkerDrag(lngLat) { if (response !== undefined && response.features.length > 0) { const addressInput = document.querySelector("address-input"); addressInput.SetValue(response.features[0]); + setLocationInputs(response.features[0]); } } function selectInspectionType(type) { From 7ee2f72b8ea44ac46daa512343e028af2386d4fb Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 2 Feb 2026 14:23:22 +0000 Subject: [PATCH 0234/1453] Add district list to report mosquitoes online Makes it easier at a conference to find the district we're talking to --- rmo/district.go | 36 +++++++++++++++++++++++++++++++++ rmo/routes.go | 1 + rmo/template/district-list.html | 28 +++++++++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 rmo/template/district-list.html diff --git a/rmo/district.go b/rmo/district.go index 3816e3b6..e33ddf9d 100644 --- a/rmo/district.go +++ b/rmo/district.go @@ -3,17 +3,28 @@ package rmo import ( "net/http" + "github.com/Gleipnir-Technology/bob/dialect/psql/sm" "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/html" "github.com/go-chi/chi/v5" ) type ContentDistrict struct { Name string URLLogo string + URLRMO string URLWebsite string } +type ContentDistrictList struct { + Districts []ContentDistrict + URL ContentURL +} + +var ( + DistrictListT = buildTemplate("district-list", "base") +) func districtBySlug(r *http.Request) (*models.Organization, error) { slug := chi.URLParam(r, "slug") @@ -22,6 +33,30 @@ func districtBySlug(r *http.Request) (*models.Organization, error) { ).One(r.Context(), db.PGInstance.BobDB) return district, err } +func getDistrictList(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + rows, err := models.Organizations.Query( + models.SelectWhere.Organizations.ImportDistrictGid.IsNotNull(), + sm.OrderBy("name"), + ).All(ctx, db.PGInstance.BobDB) + if err != nil { + respondError(w, "failed to query for districts", err, http.StatusInternalServerError) + return + } + districts := make([]ContentDistrict, 0) + for _, row := range rows { + districts = append(districts, *newContentDistrict(row)) + } + html.RenderOrError( + w, + DistrictListT, + ContentDistrictList{ + Districts: districts, + URL: makeContentURL(nil), + }, + ) + +} func newContentDistrict(d *models.Organization) *ContentDistrict { if d == nil { return nil @@ -29,6 +64,7 @@ func newContentDistrict(d *models.Organization) *ContentDistrict { return &ContentDistrict{ Name: d.Name, URLLogo: config.MakeURLNidus("/api/district/%s/logo", d.Slug.GetOr("unset")), + URLRMO: config.MakeURLReport("/district/%s", d.Slug.GetOr("unset")), URLWebsite: d.Website.GetOr(""), } } diff --git a/rmo/routes.go b/rmo/routes.go index d672d263..cace372c 100644 --- a/rmo/routes.go +++ b/rmo/routes.go @@ -14,6 +14,7 @@ func Router() chi.Router { r.Get("/water", getWater) r.Post("/water", postWater) + r.Get("/district", getDistrictList) r.Get("/district/{slug}", getRootDistrict) r.Get("/district/{slug}/nuisance", getNuisanceDistrict) //r.Get("/district/{slug}/nuisance-submit-complete", renderMock(mockNuisanceSubmitCompleteT)) diff --git a/rmo/template/district-list.html b/rmo/template/district-list.html new file mode 100644 index 00000000..d53eab39 --- /dev/null +++ b/rmo/template/district-list.html @@ -0,0 +1,28 @@ +{{template "base.html" .}} + +{{define "title"}}Districts{{end}} +{{define "extraheader"}} +{{end}} +{{define "content"}} + +
    +

    District List

    + + + + + + + + {{ range .Districts }} + + + + + + {{ end }} + +
    LogoNameURL
    {{.Name}}{{.URLRMO}}
    +
    + +{{end}} From 00a75a556e904dd6ede07357dee6a3a63bf6f61a Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 2 Feb 2026 17:00:48 +0000 Subject: [PATCH 0235/1453] Fix email sending for report notification confirmation The links in the email don't work, but it's a first step --- background/email.go | 3 +- comms/email/job.go | 9 +- .../email/report_notification_confirmation.go | 85 +++++++++++++++++++ .../email/report_subscription_confirmation.go | 80 ----------------- comms/email/template.go | 9 +- ... => report-notification-confirmation.html} | 2 +- ...t => report-notification-confirmation.txt} | 0 db/enums/enums.bob.go | 5 +- ...1_messagetypeemail_report_notification.sql | 3 + 9 files changed, 105 insertions(+), 91 deletions(-) create mode 100644 comms/email/report_notification_confirmation.go delete mode 100644 comms/email/report_subscription_confirmation.go rename comms/email/template/{report-subscription-confirmation.html => report-notification-confirmation.html} (95%) rename comms/email/template/{report-subscription-confirmation.txt => report-notification-confirmation.txt} (100%) create mode 100644 db/migrations/00051_messagetypeemail_report_notification.sql diff --git a/background/email.go b/background/email.go index ccdd464c..352adabb 100644 --- a/background/email.go +++ b/background/email.go @@ -10,7 +10,7 @@ import ( var channelJobEmail chan email.Job func ReportSubscriptionConfirmationEmail(destination, report_id string) { - enqueueJobEmail(email.NewJobReportSubscriptionConfirmation( + enqueueJobEmail(email.NewJobReportNotificationConfirmation( destination, report_id, )) @@ -36,6 +36,7 @@ func startWorkerEmail(ctx context.Context, channel chan email.Job) { case job := <-channel: err := email.Handle(ctx, job) if err != nil { + log.Error().Err(err).Msg("Failed to handle email message") } } } diff --git a/comms/email/job.go b/comms/email/job.go index b2f3fec7..73f9f313 100644 --- a/comms/email/job.go +++ b/comms/email/job.go @@ -24,8 +24,11 @@ type jobEmailBase struct { func Handle(ctx context.Context, job Job) error { var err error + log.Debug().Str("dest", job.destination()).Str("type", string(job.messageType())).Msg("Handling email job") switch job.messageType() { case enums.CommsMessagetypeemailReportSubscriptionConfirmation: + return errors.New("ReportSubscription has been deprecated.") + case enums.CommsMessagetypeemailReportNotificationConfirmation: err = sendEmailReportConfirmation(ctx, job) default: return errors.New("not implemented") @@ -35,10 +38,4 @@ func Handle(ctx context.Context, job Job) error { return fmt.Errorf("Failed to handle email: %w", err) } return nil - /* - case enums.CommsMessagetypeemailReportStatusScheduled: - case enums.CommsMessagetypeemailReportStatusComplete: - - } - */ } diff --git a/comms/email/report_notification_confirmation.go b/comms/email/report_notification_confirmation.go new file mode 100644 index 00000000..efefd502 --- /dev/null +++ b/comms/email/report_notification_confirmation.go @@ -0,0 +1,85 @@ +package email + +import ( + "context" + "fmt" + + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/db/enums" + "github.com/rs/zerolog/log" +) + +func NewJobReportNotificationConfirmation(destination, report_id string) Job { + return jobEmailReportNotificationConfirmation{ + dest: destination, + reportID: report_id, + } +} + +type jobEmailReportNotificationConfirmation struct { + dest string + reportID string +} + +func (job jobEmailReportNotificationConfirmation) destination() string { + return job.dest +} +func (job jobEmailReportNotificationConfirmation) messageType() enums.CommsMessagetypeemail { + return enums.CommsMessagetypeemailReportNotificationConfirmation +} +func (job jobEmailReportNotificationConfirmation) renderHTML() (string, error) { + _ = newContentEmailNotificationConfirmation(job) + return "", nil +} +func (job jobEmailReportNotificationConfirmation) renderTXT() (string, error) { + return "fake txt", nil +} +func (job jobEmailReportNotificationConfirmation) subject() string { + return "" +} + +func sendEmailReportConfirmation(ctx context.Context, job Job) error { + j, ok := job.(jobEmailReportNotificationConfirmation) + if !ok { + return fmt.Errorf("job is not for report subscription confirmation") + } + err := maybeSendInitialEmail(ctx, j.destination()) + if err != nil { + return fmt.Errorf("Failed to handle initial email: %w", err) + } + data := make(map[string]string, 0) + public_id := generatePublicId(enums.CommsMessagetypeemailInitialContact, data) + data["report_id"] = j.reportID + report_id_str := publicReportID(j.reportID) + data["ReportIDStr"] = report_id_str + data["URLLogo"] = config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png") + data["URLReportStatus"] = config.MakeURLReport("/foo") + data["URLReportUnsubscribe"] = config.MakeURLReport("/email/unsubscribe") + data["URLViewInBrowser"] = config.MakeURLReport("/email?id=%s", public_id) + text, html, err := renderEmailTemplates(templateReportNotificationConfirmationID, data) + if err != nil { + return fmt.Errorf("Failed to render email report notification template: %w", err) + } + subject := fmt.Sprintf("Mosquito Report Submission - %s", report_id_str) + err = insertEmailLog(ctx, data, j.destination(), public_id, config.ForwardEmailReportAddress, subject, templateReportNotificationConfirmationID) + if err != nil { + return fmt.Errorf("Failed to store email log: %w", err) + } + resp, err := sendEmail(ctx, emailRequest{ + From: config.ForwardEmailReportAddress, + HTML: html, + Subject: subject, + Text: text, + To: j.destination(), + }, enums.CommsMessagetypeemailReportNotificationConfirmation) + if err != nil { + return fmt.Errorf("Failed to send email report confirmation to %s for report %s: %w", j.dest, j.reportID, err) + } + log.Info().Str("id", resp.ID).Str("dest", j.dest).Str("report_id", j.reportID).Msg("Sent report confirmation email") + return nil +} + +func newContentEmailNotificationConfirmation(job jobEmailReportNotificationConfirmation) (result contentEmailReportConfirmation) { + result.URLReportStatus = config.MakeURLReport("/status/%s", job.reportID) + return result +} diff --git a/comms/email/report_subscription_confirmation.go b/comms/email/report_subscription_confirmation.go deleted file mode 100644 index c689859d..00000000 --- a/comms/email/report_subscription_confirmation.go +++ /dev/null @@ -1,80 +0,0 @@ -package email - -import ( - "context" - "fmt" - - "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/Gleipnir-Technology/nidus-sync/db/enums" - //"github.com/rs/zerolog/log" -) - -func NewJobReportSubscriptionConfirmation(destination, report_id string) Job { - return jobEmailReportSubscriptionConfirmation{ - dest: destination, - reportID: report_id, - } -} - -type jobEmailReportSubscriptionConfirmation struct { - dest string - reportID string -} - -func (job jobEmailReportSubscriptionConfirmation) destination() string { - return job.dest -} -func (job jobEmailReportSubscriptionConfirmation) messageType() enums.CommsMessagetypeemail { - return enums.CommsMessagetypeemailReportSubscriptionConfirmation -} -func (job jobEmailReportSubscriptionConfirmation) renderHTML() (string, error) { - _ = newContentEmailSubscriptionConfirmation(job) - return "", nil -} -func (job jobEmailReportSubscriptionConfirmation) renderTXT() (string, error) { - return "fake txt", nil -} -func (job jobEmailReportSubscriptionConfirmation) subject() string { - return "" -} - -func sendEmailReportConfirmation(ctx context.Context, job Job) error { - j, ok := job.(jobEmailReportSubscriptionConfirmation) - if !ok { - return fmt.Errorf("job is not for report subscription confirmation") - } - err := maybeSendInitialEmail(ctx, j.destination()) - if err != nil { - return fmt.Errorf("Failed to handle initial email: %w", err) - } - return nil - /* - report_id_str := publicReportID(report_id) - content := newContentEmailSubscriptionConfirmation(report_id) - text, html, err := renderEmailTemplates(reportConfirmationT, content) - if err != nil { - return fmt.Errorf("Failed to render template %s: %w", reportConfirmationT.name, err) - } - resp, err := sendEmail(ctx, emailRequest{ - From: config.ForwardEmailReportAddress, - HTML: html, - Subject: fmt.Sprintf("Mosquito Report Submission - %s", report_id_str), - Text: text, - To: to, - }, enums.CommsMessagetypeemailReportSubscriptionConfirmation) - if err != nil { - return fmt.Errorf("Failed to send email report confirmation to %s for report %s: %w", to, report_id, err) - } - log.Info().Str("id", resp.ID).Str("to", to).Str("report_id", report_id).Msg("Sent report confirmation email") - return nil - */ -} - -func newContentEmailSubscriptionConfirmation(job jobEmailReportSubscriptionConfirmation) (result contentEmailReportConfirmation) { - /*newContentBase( - &result.Base, - config.MakeURLReport("/email/report/%s/subscription-confirmation", job.reportID), - )*/ - result.URLReportStatus = config.MakeURLReport("/status/%s", job.reportID) - return result -} diff --git a/comms/email/template.go b/comms/email/template.go index adb6446f..a59bbfe4 100644 --- a/comms/email/template.go +++ b/comms/email/template.go @@ -31,8 +31,9 @@ import ( var embeddedFiles embed.FS var ( - templateByID map[int32]*builtTemplate - templateInitialID int32 + templateByID map[int32]*builtTemplate + templateInitialID int32 + templateReportNotificationConfirmationID int32 ) type templatePair struct { @@ -81,6 +82,10 @@ func LoadTemplates() error { if err != nil { return fmt.Errorf("Failed to load template ID: %s", err) } + templateReportNotificationConfirmationID, err = loadTemplateID(ctx, tx, enums.CommsMessagetypeemailReportNotificationConfirmation) + if err != nil { + return fmt.Errorf("Failed to load report-notification-confirmation template ID: %s", err) + } tx.Commit(ctx) return nil } diff --git a/comms/email/template/report-subscription-confirmation.html b/comms/email/template/report-notification-confirmation.html similarity index 95% rename from comms/email/template/report-subscription-confirmation.html rename to comms/email/template/report-notification-confirmation.html index 88ef306a..0eef1e71 100644 --- a/comms/email/template/report-subscription-confirmation.html +++ b/comms/email/template/report-notification-confirmation.html @@ -76,7 +76,7 @@

    Thank You for Your Report

    -

    We've received your mosquito report. Thanks! We appreciate you taking the time to submit it.

    +

    We've received your mosquito report {{.ReportIDStr}}. Thanks! We appreciate you taking the time to submit it.

    You can check the current status of your report at any time by clicking the button below:

    diff --git a/comms/email/template/report-subscription-confirmation.txt b/comms/email/template/report-notification-confirmation.txt similarity index 100% rename from comms/email/template/report-subscription-confirmation.txt rename to comms/email/template/report-notification-confirmation.txt diff --git a/db/enums/enums.bob.go b/db/enums/enums.bob.go index 14d12cac..77800db7 100644 --- a/db/enums/enums.bob.go +++ b/db/enums/enums.bob.go @@ -199,6 +199,7 @@ const ( CommsMessagetypeemailReportSubscriptionConfirmation CommsMessagetypeemail = "report-subscription-confirmation" CommsMessagetypeemailReportStatusScheduled CommsMessagetypeemail = "report-status-scheduled" CommsMessagetypeemailReportStatusComplete CommsMessagetypeemail = "report-status-complete" + CommsMessagetypeemailReportNotificationConfirmation CommsMessagetypeemail = "report-notification-confirmation" ) func AllCommsMessagetypeemail() []CommsMessagetypeemail { @@ -207,6 +208,7 @@ func AllCommsMessagetypeemail() []CommsMessagetypeemail { CommsMessagetypeemailReportSubscriptionConfirmation, CommsMessagetypeemailReportStatusScheduled, CommsMessagetypeemailReportStatusComplete, + CommsMessagetypeemailReportNotificationConfirmation, } } @@ -221,7 +223,8 @@ func (e CommsMessagetypeemail) Valid() bool { case CommsMessagetypeemailInitialContact, CommsMessagetypeemailReportSubscriptionConfirmation, CommsMessagetypeemailReportStatusScheduled, - CommsMessagetypeemailReportStatusComplete: + CommsMessagetypeemailReportStatusComplete, + CommsMessagetypeemailReportNotificationConfirmation: return true default: return false diff --git a/db/migrations/00051_messagetypeemail_report_notification.sql b/db/migrations/00051_messagetypeemail_report_notification.sql new file mode 100644 index 00000000..fd85441b --- /dev/null +++ b/db/migrations/00051_messagetypeemail_report_notification.sql @@ -0,0 +1,3 @@ +-- +goose Up +ALTER TYPE comms.MessageTypeEmail ADD VALUE 'report-notification-confirmation' AFTER 'report-status-complete'; +UPDATE comms.email_log SET source = 'report-notification-confirmation' WHERE source = 'report-subscription-confirmation'; From 9d7ca815085745dac4b7dc257c3687d6a46fd475 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 2 Feb 2026 19:34:37 +0000 Subject: [PATCH 0236/1453] Make 'view in browser' on emails work correctly --- comms/email/db.go | 16 +++- comms/email/initial.go | 2 +- .../email/report_notification_confirmation.go | 2 +- comms/email/template.go | 15 ++++ db/dbinfo/comms.email_log.bob.go | 4 +- db/factory/bobfactory_main.bob.go | 2 +- db/factory/comms.email_log.bob.go | 79 +++++++------------ db/factory/comms.email_template.bob.go | 2 +- .../00052_comms_email_log_notnulls.sql | 2 + db/models/comms.email_log.bob.go | 29 +++---- db/models/comms.email_template.bob.go | 9 +-- rmo/email.go | 27 +++++-- rmo/routes.go | 2 +- 13 files changed, 106 insertions(+), 85 deletions(-) create mode 100644 db/migrations/00052_comms_email_log_notnulls.sql diff --git a/comms/email/db.go b/comms/email/db.go index d12e9316..c571ade2 100644 --- a/comms/email/db.go +++ b/comms/email/db.go @@ -27,6 +27,20 @@ func convertToPGData(data map[string]string) pgtypes.HStore { return result } +func convertFromPGData(d pgtypes.HStore) map[string]string { + result := make(map[string]string, 0) + for k, v := range d { + var s string + err := v.Scan(&s) + if err != nil { + log.Warn().Str("key", k).Msg("Failed to convert from HSTORE") + continue + } + result[k] = s + } + return result +} + func ensureInDB(ctx context.Context, destination string) (err error) { _, err = models.FindCommsEmailContact(ctx, db.PGInstance.BobDB, destination) if err != nil { @@ -61,7 +75,7 @@ func insertEmailLog(ctx context.Context, data map[string]string, destination str SentAt: omitnull.FromPtr[time.Time](nil), Source: omit.From(source), Subject: omit.From(subject), - TemplateID: omitnull.From(templateInitialID), + TemplateID: omit.From(templateInitialID), TemplateData: omit.From(data_for_insert), Type: omit.From(enums.CommsMessagetypeemailInitialContact), }).One(ctx, db.PGInstance.BobDB) diff --git a/comms/email/initial.go b/comms/email/initial.go index fd42fa1d..dbf92192 100644 --- a/comms/email/initial.go +++ b/comms/email/initial.go @@ -46,7 +46,7 @@ func sendEmailInitialContact(ctx context.Context, destination string) error { data["url_subscribe"] = config.MakeURLReport("/email/subscribe?email=%s", destination) data["url_unsubscribe"] = config.MakeURLReport("/email/unsubscribe") public_id := generatePublicId(enums.CommsMessagetypeemailInitialContact, data) - data["url_browser"] = config.MakeURLReport("/email?id=%s", public_id) + data["url_browser"] = config.MakeURLReport("/email/%s", public_id) text, html, err := renderEmailTemplates(templateInitialID, data) if err != nil { diff --git a/comms/email/report_notification_confirmation.go b/comms/email/report_notification_confirmation.go index efefd502..756a37bc 100644 --- a/comms/email/report_notification_confirmation.go +++ b/comms/email/report_notification_confirmation.go @@ -55,7 +55,7 @@ func sendEmailReportConfirmation(ctx context.Context, job Job) error { data["URLLogo"] = config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png") data["URLReportStatus"] = config.MakeURLReport("/foo") data["URLReportUnsubscribe"] = config.MakeURLReport("/email/unsubscribe") - data["URLViewInBrowser"] = config.MakeURLReport("/email?id=%s", public_id) + data["URLViewInBrowser"] = config.MakeURLReport("/email/%s", public_id) text, html, err := renderEmailTemplates(templateReportNotificationConfirmationID, data) if err != nil { return fmt.Errorf("Failed to render email report notification template: %w", err) diff --git a/comms/email/template.go b/comms/email/template.go index a59bbfe4..c3ad1a2c 100644 --- a/comms/email/template.go +++ b/comms/email/template.go @@ -19,6 +19,7 @@ import ( "github.com/Gleipnir-Technology/bob" "github.com/Gleipnir-Technology/bob/dialect/psql" "github.com/Gleipnir-Technology/bob/dialect/psql/um" + "github.com/Gleipnir-Technology/bob/types/pgtypes" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" @@ -90,6 +91,20 @@ func LoadTemplates() error { return nil } +func RenderHTML(template_id int32, s pgtypes.HStore) (html []byte, err error) { + data := convertFromPGData(s) + t, ok := templateByID[template_id] + if !ok { + return []byte{}, fmt.Errorf("Failed to lookup template %d", template_id) + } + buf_html := &bytes.Buffer{} + err = t.executeTemplateHTML(buf_html, data) + if err != nil { + return []byte{}, fmt.Errorf("Failed to render HTML template: %w", err) + } + return buf_html.Bytes(), nil +} + func loadTemplateID(ctx context.Context, tx bob.Tx, t enums.CommsMessagetypeemail) (int32, error) { templates, err := models.CommsEmailTemplates.Query( models.SelectWhere.CommsEmailTemplates.MessageType.EQ(t), diff --git a/db/dbinfo/comms.email_log.bob.go b/db/dbinfo/comms.email_log.bob.go index 09417e10..0cb69830 100644 --- a/db/dbinfo/comms.email_log.bob.go +++ b/db/dbinfo/comms.email_log.bob.go @@ -90,9 +90,9 @@ var CommsEmailLogs = Table[ TemplateID: column{ Name: "template_id", DBType: "integer", - Default: "NULL", + Default: "", Comment: "", - Nullable: true, + Nullable: false, Generated: false, AutoIncr: false, }, diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index fe532561..1d9ca8e2 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -228,7 +228,7 @@ func (f *Factory) FromExistingCommsEmailLog(m *models.CommsEmailLog) *CommsEmail o.SentAt = func() null.Val[time.Time] { return m.SentAt } o.Source = func() string { return m.Source } o.Subject = func() string { return m.Subject } - o.TemplateID = func() null.Val[int32] { return m.TemplateID } + o.TemplateID = func() int32 { return m.TemplateID } o.TemplateData = func() pgtypes.HStore { return m.TemplateData } o.Type = func() enums.CommsMessagetypeemail { return m.Type } diff --git a/db/factory/comms.email_log.bob.go b/db/factory/comms.email_log.bob.go index 04e52de6..e5d70db7 100644 --- a/db/factory/comms.email_log.bob.go +++ b/db/factory/comms.email_log.bob.go @@ -47,7 +47,7 @@ type CommsEmailLogTemplate struct { SentAt func() null.Val[time.Time] Source func() string Subject func() string - TemplateID func() null.Val[int32] + TemplateID func() int32 TemplateData func() pgtypes.HStore Type func() enums.CommsMessagetypeemail @@ -89,7 +89,7 @@ func (t CommsEmailLogTemplate) setModelRels(o *models.CommsEmailLog) { if t.r.TemplateEmailTemplate != nil { rel := t.r.TemplateEmailTemplate.o.Build() rel.R.TemplateEmailLogs = append(rel.R.TemplateEmailLogs, o) - o.TemplateID = null.From(rel.ID) // h2 + o.TemplateID = rel.ID // h2 o.R.TemplateEmailTemplate = rel } } @@ -133,7 +133,7 @@ func (o CommsEmailLogTemplate) BuildSetter() *models.CommsEmailLogSetter { } if o.TemplateID != nil { val := o.TemplateID() - m.TemplateID = omitnull.FromNull(val) + m.TemplateID = omit.From(val) } if o.TemplateData != nil { val := o.TemplateData() @@ -242,6 +242,10 @@ func ensureCreatableCommsEmailLog(m *models.CommsEmailLogSetter) { val := random_string(nil, "255") m.Subject = omit.From(val) } + if !(m.TemplateID.IsValue()) { + val := random_int32(nil) + m.TemplateID = omit.From(val) + } if !(m.TemplateData.IsValue()) { val := random_pgtypes_HStore(nil) m.TemplateData = omit.From(val) @@ -258,25 +262,6 @@ func ensureCreatableCommsEmailLog(m *models.CommsEmailLogSetter) { func (o *CommsEmailLogTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsEmailLog) error { var err error - isTemplateEmailTemplateDone, _ := commsEmailLogRelTemplateEmailTemplateCtx.Value(ctx) - if !isTemplateEmailTemplateDone && o.r.TemplateEmailTemplate != nil { - ctx = commsEmailLogRelTemplateEmailTemplateCtx.WithValue(ctx, true) - if o.r.TemplateEmailTemplate.o.alreadyPersisted { - m.R.TemplateEmailTemplate = o.r.TemplateEmailTemplate.o.Build() - } else { - var rel1 *models.CommsEmailTemplate - rel1, err = o.r.TemplateEmailTemplate.o.Create(ctx, exec) - if err != nil { - return err - } - err = m.AttachTemplateEmailTemplate(ctx, exec, rel1) - if err != nil { - return err - } - } - - } - return err } @@ -304,12 +289,30 @@ func (o *CommsEmailLogTemplate) Create(ctx context.Context, exec bob.Executor) ( opt.Destination = omit.From(rel0.Address) + if o.r.TemplateEmailTemplate == nil { + CommsEmailLogMods.WithNewTemplateEmailTemplate().Apply(ctx, o) + } + + var rel1 *models.CommsEmailTemplate + + if o.r.TemplateEmailTemplate.o.alreadyPersisted { + rel1 = o.r.TemplateEmailTemplate.o.Build() + } else { + rel1, err = o.r.TemplateEmailTemplate.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.TemplateID = omit.From(rel1.ID) + m, err := models.CommsEmailLogs.Insert(opt).One(ctx, exec) if err != nil { return nil, err } m.R.DestinationEmailContact = rel0 + m.R.TemplateEmailTemplate = rel1 if err := o.insertOptRels(ctx, exec, m); err != nil { return nil, err @@ -673,14 +676,14 @@ func (m commsEmailLogMods) RandomSubject(f *faker.Faker) CommsEmailLogMod { } // Set the model columns to this value -func (m commsEmailLogMods) TemplateID(val null.Val[int32]) CommsEmailLogMod { +func (m commsEmailLogMods) TemplateID(val int32) CommsEmailLogMod { return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { - o.TemplateID = func() null.Val[int32] { return val } + o.TemplateID = func() int32 { return val } }) } // Set the Column from the function -func (m commsEmailLogMods) TemplateIDFunc(f func() null.Val[int32]) CommsEmailLogMod { +func (m commsEmailLogMods) TemplateIDFunc(f func() int32) CommsEmailLogMod { return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { o.TemplateID = f }) @@ -695,32 +698,10 @@ func (m commsEmailLogMods) UnsetTemplateID() CommsEmailLogMod { // Generates a random value for the column using the given faker // if faker is nil, a default faker is used -// The generated value is sometimes null func (m commsEmailLogMods) RandomTemplateID(f *faker.Faker) CommsEmailLogMod { return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { - o.TemplateID = func() null.Val[int32] { - if f == nil { - f = &defaultFaker - } - - val := random_int32(f) - return null.From(val) - } - }) -} - -// Generates a random value for the column using the given faker -// if faker is nil, a default faker is used -// The generated value is never null -func (m commsEmailLogMods) RandomTemplateIDNotNull(f *faker.Faker) CommsEmailLogMod { - return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) { - o.TemplateID = func() null.Val[int32] { - if f == nil { - f = &defaultFaker - } - - val := random_int32(f) - return null.From(val) + o.TemplateID = func() int32 { + return random_int32(f) } }) } diff --git a/db/factory/comms.email_template.bob.go b/db/factory/comms.email_template.bob.go index a40c2f68..82774dda 100644 --- a/db/factory/comms.email_template.bob.go +++ b/db/factory/comms.email_template.bob.go @@ -77,7 +77,7 @@ func (t CommsEmailTemplateTemplate) setModelRels(o *models.CommsEmailTemplate) { for _, r := range t.r.TemplateEmailLogs { related := r.o.BuildMany(r.number) for _, rel := range related { - rel.TemplateID = null.From(o.ID) // h2 + rel.TemplateID = o.ID // h2 rel.R.TemplateEmailTemplate = o } rel = append(rel, related...) diff --git a/db/migrations/00052_comms_email_log_notnulls.sql b/db/migrations/00052_comms_email_log_notnulls.sql new file mode 100644 index 00000000..4e49e957 --- /dev/null +++ b/db/migrations/00052_comms_email_log_notnulls.sql @@ -0,0 +1,2 @@ +-- +goose Up +ALTER TABLE comms.email_log ALTER COLUMN template_id SET NOT NULL; diff --git a/db/models/comms.email_log.bob.go b/db/models/comms.email_log.bob.go index d22a9269..6c39c8cd 100644 --- a/db/models/comms.email_log.bob.go +++ b/db/models/comms.email_log.bob.go @@ -35,7 +35,7 @@ type CommsEmailLog struct { SentAt null.Val[time.Time] `db:"sent_at" ` Source string `db:"source" ` Subject string `db:"subject" ` - TemplateID null.Val[int32] `db:"template_id" ` + TemplateID int32 `db:"template_id" ` TemplateData pgtypes.HStore `db:"template_data" ` Type enums.CommsMessagetypeemail `db:"type" ` @@ -114,7 +114,7 @@ type CommsEmailLogSetter struct { SentAt omitnull.Val[time.Time] `db:"sent_at" ` Source omit.Val[string] `db:"source" ` Subject omit.Val[string] `db:"subject" ` - TemplateID omitnull.Val[int32] `db:"template_id" ` + TemplateID omit.Val[int32] `db:"template_id" ` TemplateData omit.Val[pgtypes.HStore] `db:"template_data" ` Type omit.Val[enums.CommsMessagetypeemail] `db:"type" ` } @@ -145,7 +145,7 @@ func (s CommsEmailLogSetter) SetColumns() []string { if s.Subject.IsValue() { vals = append(vals, "subject") } - if !s.TemplateID.IsUnset() { + if s.TemplateID.IsValue() { vals = append(vals, "template_id") } if s.TemplateData.IsValue() { @@ -182,8 +182,8 @@ func (s CommsEmailLogSetter) Overwrite(t *CommsEmailLog) { if s.Subject.IsValue() { t.Subject = s.Subject.MustGet() } - if !s.TemplateID.IsUnset() { - t.TemplateID = s.TemplateID.MustGetNull() + if s.TemplateID.IsValue() { + t.TemplateID = s.TemplateID.MustGet() } if s.TemplateData.IsValue() { t.TemplateData = s.TemplateData.MustGet() @@ -248,8 +248,8 @@ func (s *CommsEmailLogSetter) Apply(q *dialect.InsertQuery) { vals[7] = psql.Raw("DEFAULT") } - if !s.TemplateID.IsUnset() { - vals[8] = psql.Arg(s.TemplateID.MustGetNull()) + if s.TemplateID.IsValue() { + vals[8] = psql.Arg(s.TemplateID.MustGet()) } else { vals[8] = psql.Raw("DEFAULT") } @@ -333,7 +333,7 @@ func (s CommsEmailLogSetter) Expressions(prefix ...string) []bob.Expression { }}) } - if !s.TemplateID.IsUnset() { + if s.TemplateID.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "template_id")...), psql.Arg(s.TemplateID), @@ -612,7 +612,7 @@ func (o *CommsEmailLog) TemplateEmailTemplate(mods ...bob.Mod[*dialect.SelectQue } func (os CommsEmailLogSlice) TemplateEmailTemplate(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailTemplatesQuery { - pkTemplateID := make(pgtypes.Array[null.Val[int32]], 0, len(os)) + pkTemplateID := make(pgtypes.Array[int32], 0, len(os)) for _, o := range os { if o == nil { continue @@ -678,7 +678,7 @@ func (commsEmailLog0 *CommsEmailLog) AttachDestinationEmailContact(ctx context.C func attachCommsEmailLogTemplateEmailTemplate0(ctx context.Context, exec bob.Executor, count int, commsEmailLog0 *CommsEmailLog, commsEmailTemplate1 *CommsEmailTemplate) (*CommsEmailLog, error) { setter := &CommsEmailLogSetter{ - TemplateID: omitnull.From(commsEmailTemplate1.ID), + TemplateID: omit.From(commsEmailTemplate1.ID), } err := commsEmailLog0.Update(ctx, exec, setter) @@ -733,7 +733,7 @@ type commsEmailLogWhere[Q psql.Filterable] struct { SentAt psql.WhereNullMod[Q, time.Time] Source psql.WhereMod[Q, string] Subject psql.WhereMod[Q, string] - TemplateID psql.WhereNullMod[Q, int32] + TemplateID psql.WhereMod[Q, int32] TemplateData psql.WhereMod[Q, pgtypes.HStore] Type psql.WhereMod[Q, enums.CommsMessagetypeemail] } @@ -752,7 +752,7 @@ func buildCommsEmailLogWhere[Q psql.Filterable](cols commsEmailLogColumns) comms SentAt: psql.WhereNull[Q, time.Time](cols.SentAt), Source: psql.Where[Q, string](cols.Source), Subject: psql.Where[Q, string](cols.Subject), - TemplateID: psql.WhereNull[Q, int32](cols.TemplateID), + TemplateID: psql.Where[Q, int32](cols.TemplateID), TemplateData: psql.Where[Q, pgtypes.HStore](cols.TemplateData), Type: psql.Where[Q, enums.CommsMessagetypeemail](cols.Type), } @@ -947,11 +947,8 @@ func (os CommsEmailLogSlice) LoadTemplateEmailTemplate(ctx context.Context, exec } for _, rel := range commsEmailTemplates { - if !o.TemplateID.IsValue() { - continue - } - if !(o.TemplateID.IsValue() && o.TemplateID.MustGet() == rel.ID) { + if !(o.TemplateID == rel.ID) { continue } diff --git a/db/models/comms.email_template.bob.go b/db/models/comms.email_template.bob.go index 0f62168d..83471826 100644 --- a/db/models/comms.email_template.bob.go +++ b/db/models/comms.email_template.bob.go @@ -538,7 +538,7 @@ func (os CommsEmailTemplateSlice) TemplateEmailLogs(mods ...bob.Mod[*dialect.Sel func insertCommsEmailTemplateTemplateEmailLogs0(ctx context.Context, exec bob.Executor, commsEmailLogs1 []*CommsEmailLogSetter, commsEmailTemplate0 *CommsEmailTemplate) (CommsEmailLogSlice, error) { for i := range commsEmailLogs1 { - commsEmailLogs1[i].TemplateID = omitnull.From(commsEmailTemplate0.ID) + commsEmailLogs1[i].TemplateID = omit.From(commsEmailTemplate0.ID) } ret, err := CommsEmailLogs.Insert(bob.ToMods(commsEmailLogs1...)).All(ctx, exec) @@ -551,7 +551,7 @@ func insertCommsEmailTemplateTemplateEmailLogs0(ctx context.Context, exec bob.Ex func attachCommsEmailTemplateTemplateEmailLogs0(ctx context.Context, exec bob.Executor, count int, commsEmailLogs1 CommsEmailLogSlice, commsEmailTemplate0 *CommsEmailTemplate) (CommsEmailLogSlice, error) { setter := &CommsEmailLogSetter{ - TemplateID: omitnull.From(commsEmailTemplate0.ID), + TemplateID: omit.From(commsEmailTemplate0.ID), } err := commsEmailLogs1.UpdateAll(ctx, exec, *setter) @@ -730,10 +730,7 @@ func (os CommsEmailTemplateSlice) LoadTemplateEmailLogs(ctx context.Context, exe for _, rel := range commsEmailLogs { - if !rel.TemplateID.IsValue() { - continue - } - if !(rel.TemplateID.IsValue() && o.ID == rel.TemplateID.MustGet()) { + if !(o.ID == rel.TemplateID) { continue } diff --git a/rmo/email.go b/rmo/email.go index ebab6503..4a0e8106 100644 --- a/rmo/email.go +++ b/rmo/email.go @@ -1,18 +1,33 @@ package rmo import ( - "fmt" "net/http" - //"github.com/Gleipnir-Technology/nidus-sync/comms/email" + "github.com/Gleipnir-Technology/nidus-sync/comms/email" + "github.com/Gleipnir-Technology/nidus-sync/db" + "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/go-chi/chi/v5" ) func getEmailByCode(w http.ResponseWriter, r *http.Request) { - code := chi.URLParam(r, "code") - if code == "" { - http.Error(w, "You must specify a code", http.StatusBadRequest) + id := chi.URLParam(r, "code") + //id := r.FormValue("id") + if id == "" { + http.Error(w, "You must specify an id", http.StatusBadRequest) return } - fmt.Fprintf(w, "Pretend email contet for %s", code) + ctx := r.Context() + email_log, err := models.CommsEmailLogs.Query( + models.SelectWhere.CommsEmailLogs.PublicID.EQ(id), + ).One(ctx, db.PGInstance.BobDB) + if err != nil { + respondError(w, "Failed to query email_log: %w", err, http.StatusInternalServerError) + return + } + html, err := email.RenderHTML(email_log.TemplateID, email_log.TemplateData) + if err != nil { + respondError(w, "Failed to render email_log: %w", err, http.StatusInternalServerError) + return + } + w.Write(html) } diff --git a/rmo/routes.go b/rmo/routes.go index cace372c..f3028dad 100644 --- a/rmo/routes.go +++ b/rmo/routes.go @@ -24,7 +24,7 @@ func Router() chi.Router { r.Get("/privacy", getPrivacy) r.Get("/robots.txt", getRobots) - r.Get("/email", getEmailByCode) + r.Get("/email/{code}", getEmailByCode) r.Get("/image/{uuid}", getImageByUUID) r.Route("/mock", addMockRoutes) r.Get("/pool-submit-complete", getPoolSubmitComplete) From 4f56915f1545fd5b560b576768d2b54c962b5a1d Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 2 Feb 2026 19:54:32 +0000 Subject: [PATCH 0237/1453] Show a page for subscribing to emails. --- comms/email/initial.go | 17 ++++++++------ .../email/report_notification_confirmation.go | 2 +- comms/email/template/initial-contact.html | 10 +++++---- rmo/email.go | 19 ++++++++++++++++ rmo/routes.go | 3 ++- rmo/template/email-subscribe.html | 22 +++++++++++++++++++ 6 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 rmo/template/email-subscribe.html diff --git a/comms/email/initial.go b/comms/email/initial.go index dbf92192..eb60a625 100644 --- a/comms/email/initial.go +++ b/comms/email/initial.go @@ -36,17 +36,20 @@ func maybeSendInitialEmail(ctx context.Context, destination string) error { return sendEmailInitialContact(ctx, destination) } +func urlEmailInBrowser(public_id string) string { + return config.MakeURLReport("/email/render/%s", public_id) +} func sendEmailInitialContact(ctx context.Context, destination string) error { //data := pgtypes.HStore{} data := make(map[string]string, 0) - source := config.ForwardEmailReportAddress - data["destination"] = destination - data["source"] = source - data["url_logo"] = config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png") - data["url_subscribe"] = config.MakeURLReport("/email/subscribe?email=%s", destination) - data["url_unsubscribe"] = config.MakeURLReport("/email/unsubscribe") public_id := generatePublicId(enums.CommsMessagetypeemailInitialContact, data) - data["url_browser"] = config.MakeURLReport("/email/%s", public_id) + source := config.ForwardEmailReportAddress + data["Destination"] = destination + data["Source"] = source + data["URLBrowser"] = urlEmailInBrowser(public_id) + data["URLLogo"] = config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png") + data["URLSubscribe"] = config.MakeURLReport("/email/subscribe?email=%s", destination) + data["URLUnsubscribe"] = config.MakeURLReport("/email/unsubscribe") text, html, err := renderEmailTemplates(templateInitialID, data) if err != nil { diff --git a/comms/email/report_notification_confirmation.go b/comms/email/report_notification_confirmation.go index 756a37bc..7aa23501 100644 --- a/comms/email/report_notification_confirmation.go +++ b/comms/email/report_notification_confirmation.go @@ -55,7 +55,7 @@ func sendEmailReportConfirmation(ctx context.Context, job Job) error { data["URLLogo"] = config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png") data["URLReportStatus"] = config.MakeURLReport("/foo") data["URLReportUnsubscribe"] = config.MakeURLReport("/email/unsubscribe") - data["URLViewInBrowser"] = config.MakeURLReport("/email/%s", public_id) + data["URLViewInBrowser"] = urlEmailInBrowser(public_id) text, html, err := renderEmailTemplates(templateReportNotificationConfirmationID, data) if err != nil { return fmt.Errorf("Failed to render email report notification template: %w", err) diff --git a/comms/email/template/initial-contact.html b/comms/email/template/initial-contact.html index 341e3ae4..f30b6960 100644 --- a/comms/email/template/initial-contact.html +++ b/comms/email/template/initial-contact.html @@ -64,32 +64,34 @@
    + {{if .IsBrowser}}
    Email not displaying correctly? View it in your browser
    + {{end}}
    - +

    Welcome

    -

    We're sending you this email because it's the first time we've gotten this email address ({{.destination}}).

    +

    We're sending you this email because it's the first time we've gotten this email address ({{.Destination}}).

    If you'd rather not receive emails from us you can reply with "Unsubscribe" in the subject or body of the email. You can also use the "Unsubscribe" feature of your mail client, if it supports list unsubscribes.

    If instead you'd like to confirm that you're willing to receive emails at this address, you can do so by clicking below:

    diff --git a/rmo/email.go b/rmo/email.go index 4a0e8106..df3db1cd 100644 --- a/rmo/email.go +++ b/rmo/email.go @@ -6,9 +6,18 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/comms/email" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/Gleipnir-Technology/nidus-sync/html" "github.com/go-chi/chi/v5" ) +type ContentEmailSubscribe struct { + Email string +} + +var ( + EmailSubscribeT = buildTemplate("email-subscribe", "base") +) + func getEmailByCode(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "code") //id := r.FormValue("id") @@ -31,3 +40,13 @@ func getEmailByCode(w http.ResponseWriter, r *http.Request) { } w.Write(html) } +func getEmailSubscribe(w http.ResponseWriter, r *http.Request) { + email := r.FormValue("email") + html.RenderOrError( + w, + EmailSubscribeT, + ContentEmailSubscribe{ + Email: email, + }, + ) +} diff --git a/rmo/routes.go b/rmo/routes.go index f3028dad..ad664929 100644 --- a/rmo/routes.go +++ b/rmo/routes.go @@ -24,7 +24,8 @@ func Router() chi.Router { r.Get("/privacy", getPrivacy) r.Get("/robots.txt", getRobots) - r.Get("/email/{code}", getEmailByCode) + r.Get("/email/render/{code}", getEmailByCode) + r.Get("/email/subscribe", getEmailSubscribe) r.Get("/image/{uuid}", getImageByUUID) r.Route("/mock", addMockRoutes) r.Get("/pool-submit-complete", getPoolSubmitComplete) diff --git a/rmo/template/email-subscribe.html b/rmo/template/email-subscribe.html new file mode 100644 index 00000000..fa7469a7 --- /dev/null +++ b/rmo/template/email-subscribe.html @@ -0,0 +1,22 @@ +{{template "base.html" .}} + +{{define "title"}}Main{{end}} +{{define "extraheader"}} +{{end}} +{{define "content"}} + +
    +
    + +
    +
    +
    +

    Thanks!

    +

    You've allowed emails from Report Mosquitoes Online to {{.Email}}.

    +
    +
    +
    + +{{end}} From a52f5da87a314c1832249a01c4638099fabcf600 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 2 Feb 2026 20:01:28 +0000 Subject: [PATCH 0238/1453] Mark email addresses confirmed when they click the big button. --- rmo/email.go | 14 ++++++++++++++ rmo/template/email-subscribe.html | 2 ++ 2 files changed, 16 insertions(+) diff --git a/rmo/email.go b/rmo/email.go index df3db1cd..0a9422f2 100644 --- a/rmo/email.go +++ b/rmo/email.go @@ -7,6 +7,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/html" + "github.com/aarondl/opt/omit" "github.com/go-chi/chi/v5" ) @@ -42,6 +43,19 @@ func getEmailByCode(w http.ResponseWriter, r *http.Request) { } func getEmailSubscribe(w http.ResponseWriter, r *http.Request) { email := r.FormValue("email") + if email == "" { + respondError(w, "Not sure what to do with an empty email", nil, http.StatusBadRequest) + return + } + ctx := r.Context() + email_contact, err := models.FindCommsEmailContact(ctx, db.PGInstance.BobDB, email) + if err != nil { + respondError(w, "Email not in the database", err, http.StatusNotFound) + return + } + err = email_contact.Update(ctx, db.PGInstance.BobDB, &models.CommsEmailContactSetter{ + Confirmed: omit.From(true), + }) html.RenderOrError( w, EmailSubscribeT, diff --git a/rmo/template/email-subscribe.html b/rmo/template/email-subscribe.html index fa7469a7..d88a5b08 100644 --- a/rmo/template/email-subscribe.html +++ b/rmo/template/email-subscribe.html @@ -15,6 +15,8 @@

    Thanks!

    You've allowed emails from Report Mosquitoes Online to {{.Email}}.

    +

    Go ahead and close this page/tab/window whenever you're ready...

    +

    ...or maybe check out the site

    From b2737b49684918436e0cad54fdd6c86030f479cf Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 2 Feb 2026 21:34:36 +0000 Subject: [PATCH 0239/1453] Add routes for confirming email address --- comms/email/initial.go | 5 +- .../email/report_notification_confirmation.go | 3 +- .../report-notification-confirmation.html | 1 + rmo/email.go | 71 +++++++++++++++++-- rmo/routes.go | 6 +- rmo/template/email-confirm-complete.html | 24 +++++++ rmo/template/email-confirm.html | 29 ++++++++ rmo/template/email-subscribe.html | 12 ++-- 8 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 rmo/template/email-confirm-complete.html create mode 100644 rmo/template/email-confirm.html diff --git a/comms/email/initial.go b/comms/email/initial.go index eb60a625..bfc96d29 100644 --- a/comms/email/initial.go +++ b/comms/email/initial.go @@ -39,6 +39,9 @@ func maybeSendInitialEmail(ctx context.Context, destination string) error { func urlEmailInBrowser(public_id string) string { return config.MakeURLReport("/email/render/%s", public_id) } +func urlUnsubscribe(address string) string { + return config.MakeURLReport("/email/unsubscribe?email=%s") +} func sendEmailInitialContact(ctx context.Context, destination string) error { //data := pgtypes.HStore{} data := make(map[string]string, 0) @@ -49,7 +52,7 @@ func sendEmailInitialContact(ctx context.Context, destination string) error { data["URLBrowser"] = urlEmailInBrowser(public_id) data["URLLogo"] = config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png") data["URLSubscribe"] = config.MakeURLReport("/email/subscribe?email=%s", destination) - data["URLUnsubscribe"] = config.MakeURLReport("/email/unsubscribe") + data["URLUnsubscribe"] = urlUnsubscribe(destination) text, html, err := renderEmailTemplates(templateInitialID, data) if err != nil { diff --git a/comms/email/report_notification_confirmation.go b/comms/email/report_notification_confirmation.go index 7aa23501..5c9871e0 100644 --- a/comms/email/report_notification_confirmation.go +++ b/comms/email/report_notification_confirmation.go @@ -54,7 +54,8 @@ func sendEmailReportConfirmation(ctx context.Context, job Job) error { data["ReportIDStr"] = report_id_str data["URLLogo"] = config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png") data["URLReportStatus"] = config.MakeURLReport("/foo") - data["URLReportUnsubscribe"] = config.MakeURLReport("/email/unsubscribe") + data["URLReportUnsubscribe"] = config.MakeURLReport("/email/unsubscribe/report/%s", j.reportID) + data["URLUnsubscribe"] = urlUnsubscribe(j.destination()) data["URLViewInBrowser"] = urlEmailInBrowser(public_id) text, html, err := renderEmailTemplates(templateReportNotificationConfirmationID, data) if err != nil { diff --git a/comms/email/template/report-notification-confirmation.html b/comms/email/template/report-notification-confirmation.html index 0eef1e71..2ae0db35 100644 --- a/comms/email/template/report-notification-confirmation.html +++ b/comms/email/template/report-notification-confirmation.html @@ -87,6 +87,7 @@

    We'll send you additional updates as work is scheduled and completed.

    If you have any questions or need further assistance, please don't hesitate to contact us by replying to this email.

    +

    You can unsubscribe from notifications about this report by clicking here

    +
    {{end}} diff --git a/scss/custom.scss b/scss/custom.scss index 3d4714c5..b9ded3a1 100644 --- a/scss/custom.scss +++ b/scss/custom.scss @@ -43,3 +43,4 @@ $theme-colors: map-merge( @import "./sidebar.scss"; @import "./rmo/nuisance.scss"; @import "./rmo/root.scss"; +@import "./rmo/status.scss"; diff --git a/scss/status.scss b/scss/status.scss new file mode 100644 index 00000000..cdf5161f --- /dev/null +++ b/scss/status.scss @@ -0,0 +1,30 @@ +.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; +} +.search-box { + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + border-radius: 8px; +} +@media (max-width: 768px) { + .map-container { + height: 300px; + } +} + From a9e333b73a810504b9f23eb08bb17c97f50bceed Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 3 Feb 2026 22:11:54 +0000 Subject: [PATCH 0249/1453] Add new custom element for handling report ID or location Looks purty. --- .../static/js/address-or-report-suggestion.js | 209 ++++++++++++++++++ rmo/template/status.html | 43 +--- 2 files changed, 213 insertions(+), 39 deletions(-) create mode 100644 html/static/js/address-or-report-suggestion.js diff --git a/html/static/js/address-or-report-suggestion.js b/html/static/js/address-or-report-suggestion.js new file mode 100644 index 00000000..5b6ea627 --- /dev/null +++ b/html/static/js/address-or-report-suggestion.js @@ -0,0 +1,209 @@ +class AddressOrReportInput extends HTMLElement { + // make element form-associated + static formAssociated = true; + + constructor() { + super(); + + this.attachShadow({mode: "open" }); + this.internals = this.attachInternals(); + this.render(); + + // Element references + this._input = this.shadowRoot.querySelector("input"); + this._suggestions = this.shadowRoot.querySelector(".suggestions-container"); + + // Bind methods + this._handleInput = this._handleInput.bind(this); + + // Debounce timer + this._debounceTimer = null; + + // The suggestion data + this._suggestionData = null; + } + + // Lifecycle: when element is added to the DOM + connectedCallback() { + this._input.addEventListener("input", this._handleInput); + } + + // Lifecycle: when element is removed from the DOM + disconnectedCallback() { + this._input.removeEventListener('input', this._handleInput); + } + + // Lifecycle: watch these attributes for changes + static get observedAttributes() { + return ['placeholder', 'api-key']; + } + + // Lifecycle: respond to attribute changes + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'placeholder' && this._input) { + this._input.placeholder = newValue; + } + + if (name === 'api-key') { + this._apiKey = newValue; + } + } + + // Properties API + get value() { + return this._input ? this._input.value : ''; + } + + set value(val) { + if (this._input) { + this._input.value = val; + const entries = new FormData(); + entries.append("address", val); + this.internals.setFormValue(entries); + } + } + + // Private methods + _handleInput(event) { + const searchText = event.target.value.trim(); + + // Clear previous timer + clearTimeout(this._debounceTimer); + + // Clear suggestions if input is less than 3 characters + if (searchText.length < 3) { + this._suggestions.innerHTML = ''; + return; + } + + // Debounce API calls (wait 300ms after typing stops) + this._debounceTimer = setTimeout(() => { + this._fetchAddressSuggestions(searchText) + .then(response => { + this._renderSuggestions(response.features); + }); + }, 300); + + } + + async _fetchAddressSuggestions(text) { + try { + const url = `https://api.mapbox.com/search/geocode/v6/forward?q=${encodeURIComponent(text)}&access_token=${this._apiKey}`; + + const response = await fetch(url); + const data = await response.json(); + return data; + } catch (error) { + console.error('Error fetching geocoding suggestions:', error); + } + } + + _renderSuggestions(suggestions) { + console.log("Rendering suggestions", suggestions); + this._suggestions.innerHTML = suggestions.map((item, index) => { + if (item.properties.place_formatted != "") { + return ` +
    +
    ${item.properties.name || item.properties.full_address}
    +
    ${item.properties.place_formatted}
    +
    ` + } else { + return ` +
    +
    ${item.properties.name || item.properties.full_address}
    +
    ${item.properties.place_formatted}
    +
    ` + } + }).join(''); + + // Add click listeners to suggestions + this.shadowRoot.querySelectorAll('.suggestion-item').forEach(el => { + el.addEventListener('click', e => { + const index = parseInt(el.dataset.index); + const suggestion = suggestions[index]; + this.SetValue(suggestion); + // Dispatch custom event + this.dispatchEvent(new CustomEvent('address-selected', { + bubbles: true, + composed: true, // Allows event to cross shadow DOM boundary + detail: { + location: suggestion + } + })); + }); + }); + } + + // Initial render of component + render() { + const placeholder = this.getAttribute('placeholder') || 'Enter address'; + + this.shadowRoot.innerHTML = ` + + +
    + + +
    +
    + `; + } + + // Public methods + clear() { + if (this._input) { + this._input.value = ''; + this._suggestions.innerHTML = ''; + } + } + + SetValue(suggestion) { + this.value = suggestion.properties.full_address; + this._suggestions.innerHTML = ''; + } +} + +customElements.define('address-or-report-input', AddressOrReportInput); diff --git a/rmo/template/status.html b/rmo/template/status.html index 5795681d..a8f3e800 100644 --- a/rmo/template/status.html +++ b/rmo/template/status.html @@ -3,6 +3,7 @@ {{define "title"}}Status{{end}} {{define "extraheader"}} + @@ -10,40 +11,7 @@ {{end}} @@ -54,12 +22,9 @@ document.addEventListener('DOMContentLoaded', function() {
    - -
    - - -
    +
    From 26223ccc0aba10fbd5947e6ae8d1d12f38b957d6 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 3 Feb 2026 23:11:19 +0000 Subject: [PATCH 0250/1453] Fix suggestion container offset --- html/static/js/address-or-report-suggestion.js | 1 + 1 file changed, 1 insertion(+) diff --git a/html/static/js/address-or-report-suggestion.js b/html/static/js/address-or-report-suggestion.js index 5b6ea627..7e8b995b 100644 --- a/html/static/js/address-or-report-suggestion.js +++ b/html/static/js/address-or-report-suggestion.js @@ -173,6 +173,7 @@ class AddressOrReportInput extends HTMLElement { overflow-y: auto; z-index: 1000; box-shadow: 0 4px 8px rgba(0,0,0,0.1); + top: 48px; } .suggestion-item { cursor: pointer; From fa0ac035ac33b16408a4c7ea624df53050221f6e Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 3 Feb 2026 23:12:40 +0000 Subject: [PATCH 0251/1453] Add scss debug request endpoint To help with debugging my scss. --- rmo/routes.go | 1 + rmo/scss.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 rmo/scss.go diff --git a/rmo/routes.go b/rmo/routes.go index 7cc8141b..ddb231ea 100644 --- a/rmo/routes.go +++ b/rmo/routes.go @@ -39,6 +39,7 @@ func Router() chi.Router { r.Post("/register-notifications", postRegisterNotifications) r.Get("/register-notifications-complete", getRegisterNotificationsComplete) r.Get("/search", getSearch) + r.Get("/scss/*", getScssDebug) r.Get("/status", getStatus) r.Get("/status/{report_id}", getStatusByID) r.Get("/terms-of-service", getTerms) diff --git a/rmo/scss.go b/rmo/scss.go new file mode 100644 index 00000000..e58c89c5 --- /dev/null +++ b/rmo/scss.go @@ -0,0 +1,39 @@ +package rmo + +import ( + "fmt" + "io" + "net/http" + "os" + + "github.com/go-chi/chi/v5" + "github.com/rs/zerolog/log" +) + +func getScssDebug(w http.ResponseWriter, r *http.Request) { + path := chi.URLParam(r, "*") + full_path := "scss/" + path + //log.Debug().Str("path", path).Str("full_path", full_path).Msg("working on SCSS debug") + file, err := os.Open(full_path) + if err != nil { + respondError(w, "failed to open file", err, http.StatusInternalServerError) + return + } + defer file.Close() + fileInfo, err := file.Stat() + if err != nil { + respondError(w, "failed to stat file", err, http.StatusInternalServerError) + return + } + // Set appropriate headers + w.Header().Set("Content-Type", "text/scss") + w.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Size())) + // Copy file contents to response writer + _, err = io.Copy(w, file) + if err != nil { + // Note: At this point, we've already started writing the response, + // so we can't change the status code anymore. The best we can do + // is log the error and abandon the connection. + log.Warn().Str("path", path).Msg("Failed to write scss file to output") + } +} From 765e437d6c028da11be26b9b8295429dda1810d0 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 3 Feb 2026 23:12:59 +0000 Subject: [PATCH 0252/1453] Add header to status search page --- rmo/status.go | 1 + rmo/template/status.html | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/rmo/status.go b/rmo/status.go index 3eceb5b9..c5e875ac 100644 --- a/rmo/status.go +++ b/rmo/status.go @@ -31,6 +31,7 @@ type Contact struct { Phone string } type ContentStatus struct { + District *ContentDistrict Error string MapboxToken string ReportID string diff --git a/rmo/template/status.html b/rmo/template/status.html index a8f3e800..88fc4e0d 100644 --- a/rmo/template/status.html +++ b/rmo/template/status.html @@ -16,6 +16,11 @@ document.addEventListener('DOMContentLoaded', function() { {{end}} {{define "content"}} +{{if (eq .District nil)}} + {{template "header-rmo" .}} +{{else}} + {{template "header-district" .District}} +{{end}}
    - - -
    -
    -
    - - - - - Need Help? -
    -

    If you need to update your contact information or have questions about your report, please contact our Mosquito Control Unit at (123) 456-7890 or mosquito@example.gov and reference your Report ID.

    -
    -
    From 63358e684887d74d1317b0e04491e0b2adc485cd Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 4 Feb 2026 16:26:30 +0000 Subject: [PATCH 0257/1453] Render nearby reports on the search map. --- rmo/template/status.html | 85 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/rmo/template/status.html b/rmo/template/status.html index 88fc4e0d..7772604a 100644 --- a/rmo/template/status.html +++ b/rmo/template/status.html @@ -11,8 +11,89 @@ {{end}} {{define "content"}} From 820f8237d13607474f25438dd310b1c39d720479 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 4 Feb 2026 17:12:46 +0000 Subject: [PATCH 0258/1453] Show pools and nuisances in separate layers on status search --- html/static/js/report-table.js | 6 +-- rmo/template/status.html | 67 ++++++++++++++++++++++------------ scss/rmo/status.scss | 36 ++++++++++++++++++ 3 files changed, 83 insertions(+), 26 deletions(-) diff --git a/html/static/js/report-table.js b/html/static/js/report-table.js index 78c8af22..a66e5f12 100644 --- a/html/static/js/report-table.js +++ b/html/static/js/report-table.js @@ -70,11 +70,11 @@ class ReportTable extends HTMLElement { */ getTypeClass(type) { switch(type) { - case 'Nuisance': + case 'nuisance': return 'bg-danger'; - case 'Quick': + case 'quick': return 'bg-primary'; - case 'Green Pool': + case 'pool': return 'bg-success'; default: return 'bg-secondary'; diff --git a/rmo/template/status.html b/rmo/template/status.html index 7772604a..72aac1a3 100644 --- a/rmo/template/status.html +++ b/rmo/template/status.html @@ -8,22 +8,31 @@ - {{end}} @@ -152,14 +172,16 @@ document.addEventListener('DOMContentLoaded', onLoad);