diff --git a/html/template/sync/layout/base.html b/html/template/sync/layout/base.html index ec96913a..e40b9ae7 100644 --- a/html/template/sync/layout/base.html +++ b/html/template/sync/layout/base.html @@ -7,7 +7,6 @@ - {{ block "extraheader" . }}{{ end }} {{ if not .Config.IsProductionEnvironment }} @@ -17,6 +16,6 @@ {{ template "content" . }} - + diff --git a/html/template/sync/mailer-1.html b/html/template/sync/mailer-1.html new file mode 100644 index 00000000..32c3a02b --- /dev/null +++ b/html/template/sync/mailer-1.html @@ -0,0 +1,176 @@ +{{ template "sync/layout/base.html" . }} + +{{ define "title" }}Mailer{{ end }} +{{ define "extraheader" }} + +{{ end }} +{{ define "content" }} + +
+ + +
+ + + +
Date:March 31, 2026
+
+ +
+

Dear Valued Member of Humanity,

+ +

+ It has come to our attention that you are currently alive. + Congratulations! However, we must inform you that this status is under + constant threat by flying vampires no bigger than your pinky fingernail. + We are, of course, referring to mosquitoes—nature's tiny + terrorists. +

+ +

+ Mosquitoes are responsible for spreading diseases like malaria, dengue + fever, West Nile virus, Zika, and a whole host of other ailments that + sound like rejected Harry Potter spells. "Dengue Fever-osa!" just doesn't + have the same ring to it, but it's certainly more dangerous than turning + your teacup into a toad. +

+ +

+ The image you see to the right is an actual aerial photograph taken from a + plane of a location you probably don't care about. It's there so we can + see how good the print quality is. Hopefully it's good, but we won't know + until you bring it to us. Please + take this letter to Benjamin Sperry. +

+ +

+ Why should you care? Because these miniature menaces are + plotting against you RIGHT NOW. While you're reading this letter, + mosquitoes are gathering in small committees near standing water, planning + their next attack. They don't sleep. They don't rest. They simply exist to + ruin your backyard barbecues and transmit diseases. +

+ +

+ The good news? Delta MVCD is like the Avengers, but for bugs. We're out + there draining standing water, applying treatments, and generally making + life difficult for mosquitoes everywhere. We're the heroes you didn't know + you needed, fighting the villain too small to see in the dark. +

+ +

What can YOU do?

+ + +

+ Remember: An ounce of prevention is worth a pound of calamine lotion and + several days of uncontrollable itching. Together, we can make our + community less hospitable to these winged hypodermic needles and more + enjoyable for actual humans. +

+ +

Stay vigilant. Stay bite-free. Stay alive.

+ +
+

Sincerely,

+

Eli Ribble

+

Founder of Gleipnir

+
+
+ + +
+ +
+{{ end }} diff --git a/html/template/sync/mailer.html b/html/template/sync/mailer-3.html similarity index 100% rename from html/template/sync/mailer.html rename to html/template/sync/mailer-3.html diff --git a/platform/pdf/pdf.go b/platform/pdf/pdf.go index 3d1b753f..43f4b794 100644 --- a/platform/pdf/pdf.go +++ b/platform/pdf/pdf.go @@ -10,14 +10,14 @@ import ( "github.com/rs/zerolog/log" ) -func GeneratePDF(ctx context.Context, code string) ([]byte, error) { +func GeneratePDF(ctx context.Context, path string) ([]byte, error) { // create context chrome_ctx, cancel := chromedp.NewContext(context.Background()) defer cancel() // capture pdf var buf []byte - url := fmt.Sprintf("http://%s/mailer/%s/preview", config.Bind, code) + url := fmt.Sprintf("http://%s%s", config.Bind, path) log.Info().Str("url", url).Msg("Getting with headless chrome") if err := chromedp.Run(chrome_ctx, printToPDF(url, &buf)); err != nil { return nil, fmt.Errorf("print to pdf: %w", err) diff --git a/platform/tile.go b/platform/tile.go index 1ababa79..bffc0f5e 100644 --- a/platform/tile.go +++ b/platform/tile.go @@ -6,6 +6,7 @@ import ( "embed" "fmt" "io" + "math/rand" "net/http" "os" "path/filepath" @@ -14,7 +15,8 @@ import ( "github.com/Gleipnir-Technology/arcgis-go/fieldseeker" "github.com/aarondl/opt/omit" //"github.com/Gleipnir-Technology/bob" - //"github.com/Gleipnir-Technology/bob/dialect/psql" + "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" @@ -97,6 +99,30 @@ func ImageAtPoint(ctx context.Context, org Organization, level uint, lat, lng fl IsPlaceholder: false, }, nil } + +// Writes a random tile from the cache. This is a very odd thing to do, it's for testing +func WriteTileRandom(ctx context.Context, w http.ResponseWriter) error { + tile_rows, err := models.TileCachedImages.Query( + sm.Where(psql.Quote("is_empty").EQ(psql.Arg(false))), + sm.Limit(100), + ).All(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("get tiles: %w", err) + } + tile_row := tile_rows[rand.Intn(len(tile_rows))] + tile_path := tilePath(tile_row.ArcgisID, uint(tile_row.Z), uint(tile_row.Y), uint(tile_row.X)) + var tile *TileRaster + if tile_row.IsEmpty { + tile = TileRasterPlaceholder() + } else { + tile, err = loadTileFromDisk(tile_path) + if err != nil { + return fmt.Errorf("load tile from disk: %w", err) + } + } + log.Debug().Int32("z", tile_row.Z).Int32("y", tile_row.Y).Int32("x", tile_row.X).Bool("is empty", tile_row.IsEmpty).Msg("random tile") + return writeTile(w, tile) +} func loadTileFromDisk(tile_path string) (*TileRaster, error) { file, err := os.Open(tile_path) if err != nil { diff --git a/platform/user.go b/platform/user.go index 68807e61..b9a52553 100644 --- a/platform/user.go +++ b/platform/user.go @@ -91,6 +91,7 @@ func CreateUser(ctx context.Context, username string, name string, password_hash log.Info().Int32("id", o.ID).Msg("Created organization") u_setter := models.UserSetter{ DisplayName: omit.From(name), + IsActive: omit.From(true), OrganizationID: omit.From(o.ID), PasswordHash: omit.From(password_hash), PasswordHashType: omit.From(enums.HashtypeBcrypt14), diff --git a/sync/mailer.go b/sync/mailer.go index ef65a3c8..6786d3c1 100644 --- a/sync/mailer.go +++ b/sync/mailer.go @@ -10,9 +10,11 @@ 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/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/platform/pdf" "github.com/go-chi/chi/v5" "github.com/google/uuid" + "github.com/rs/zerolog/log" ) type contentMailer struct { @@ -25,7 +27,22 @@ type contentMailer struct { ReportURL string } -func getMailer(w http.ResponseWriter, r *http.Request) { +func getMailer1(w http.ResponseWriter, r *http.Request) { + path := "/mailer/mode-1/preview" + content, err := pdf.GeneratePDF(r.Context(), path) + if err != nil { + respondError(w, "generate pdf failure", err, http.StatusInternalServerError) + return + } + err = writePDF(w, content, "mailer-mode-1.pdf") + if err != nil { + respondError(w, "copy error", err, http.StatusInternalServerError) + return + } +} +func getMailer2(w http.ResponseWriter, r *http.Request) { +} +func getMailer3(w http.ResponseWriter, r *http.Request) { code := chi.URLParam(r, "code") if code == "" { http.Error(w, "empty code", http.StatusBadRequest) @@ -37,16 +54,27 @@ func getMailer(w http.ResponseWriter, r *http.Request) { respondError(w, "generate pdf failure", err, http.StatusInternalServerError) return } - w.Header().Set("Content-Type", "application/pdf") - disposition := fmt.Sprintf("attachment; filename=\"compliance-mailer-%s.pdf\"", code) - w.Header().Set("Content-Disposition", disposition) - _, err = io.Copy(w, bytes.NewReader(content)) + filename := fmt.Sprintf("compliance-mailer-%s.pdf", code) + err = writePDF(w, content, filename) if err != nil { respondError(w, "copy error", err, http.StatusInternalServerError) return } } -func getMailerPreview(w http.ResponseWriter, r *http.Request) { +func writePDF(w http.ResponseWriter, content []byte, filename string) error { + w.Header().Set("Content-Type", "application/pdf") + disposition := fmt.Sprintf("attachment; filename=\"%s\"", filename) + w.Header().Set("Content-Disposition", disposition) + _, err := io.Copy(w, bytes.NewReader(content)) + return err +} +func getMailer1Preview(w http.ResponseWriter, r *http.Request) { + html.RenderOrError(w, "sync/mailer-1.html", contentMailer{}) +} +func getMailer2Preview(w http.ResponseWriter, r *http.Request) { + html.RenderOrError(w, "sync/mailer-2.html", contentMailer{}) +} +func getMailer3Preview(w http.ResponseWriter, r *http.Request) { code := chi.URLParam(r, "code") if code == "" { http.Error(w, "empty code", http.StatusBadRequest) @@ -70,7 +98,7 @@ func getMailerPreview(w http.ResponseWriter, r *http.Request) { return } doc_id := uuid.New() - html.RenderOrError(w, "sync/mailer.html", contentMailer{ + html.RenderOrError(w, "sync/mailer-3.html", contentMailer{ Config: html.NewContentConfig(), DocumentID: doc_id.String(), LogoURL: config.MakeURLNidus("/api/district/%s/logo", org.Slug.GetOr("unset")), @@ -80,3 +108,12 @@ func getMailerPreview(w http.ResponseWriter, r *http.Request) { ReportURL: config.MakeURLReport("/mailer/%s", code), }) } +func getMailerPoolRandom(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + err := platform.WriteTileRandom(ctx, w) + if err != nil { + log.Error().Err(err).Msg("failed to do random tile") + http.Error(w, "failed to do tile", http.StatusInternalServerError) + return + } +} diff --git a/sync/routes.go b/sync/routes.go index 360d3bf7..11bacbad 100644 --- a/sync/routes.go +++ b/sync/routes.go @@ -12,8 +12,13 @@ func Router() chi.Router { // Unauthenticated endpoints r.Get("/arcgis/oauth/begin", getArcgisOauthBegin) r.Get("/arcgis/oauth/callback", getArcgisOauthCallback) - r.Get("/mailer/{code}", getMailer) - r.Get("/mailer/{code}/preview", getMailerPreview) + r.Get("/mailer/pool/random", getMailerPoolRandom) + r.Get("/mailer/mode-1", getMailer1) + r.Get("/mailer/mode-2/{code}", getMailer2) + r.Get("/mailer/mode-3/{code}", getMailer3) + r.Get("/mailer/mode-1/preview", getMailer1Preview) + r.Get("/mailer/mode-2/preview", getMailer2Preview) + r.Get("/mailer/mode-3/{code}/preview", getMailer3Preview) r.Get("/district", getDistrict) // Mock endpoints