Add mode 1 mailer for testing.
This commit is contained in:
parent
7b3c1f2b54
commit
4f96f35d9a
8 changed files with 258 additions and 14 deletions
|
|
@ -7,7 +7,6 @@
|
|||
<!-- Bootstrap CSS -->
|
||||
<link href="/static/vendor/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<!-- Bootstrap Icons -->
|
||||
<link href="/static/css/bootstrap.css" rel="stylesheet" />
|
||||
<link rel="icon" href="/static/ico/favicon-sync.ico" type="image/x-icon" />
|
||||
{{ block "extraheader" . }}{{ end }}
|
||||
{{ if not .Config.IsProductionEnvironment }}
|
||||
|
|
@ -17,6 +16,6 @@
|
|||
<body>
|
||||
{{ template "content" . }}
|
||||
<div id="flogo"></div>
|
||||
<script src="/static/vendor/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/static/vendor/bootstrap-5.3.8/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
176
html/template/sync/mailer-1.html
Normal file
176
html/template/sync/mailer-1.html
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
{{ template "sync/layout/base.html" . }}
|
||||
|
||||
{{ define "title" }}Mailer{{ end }}
|
||||
{{ define "extraheader" }}
|
||||
<style>
|
||||
@media print {
|
||||
@page {
|
||||
margin: 0.25in;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0.25in;
|
||||
font-family: "Times New Roman", serif;
|
||||
}
|
||||
|
||||
.address-space {
|
||||
position: absolute;
|
||||
top: 0.2in;
|
||||
left: 0.5in;
|
||||
width: 3.25in;
|
||||
height: 2.6in;
|
||||
/* Uncomment below to visualize the address space during development */
|
||||
/*border: 1px dashed #ccc;*/
|
||||
}
|
||||
|
||||
.header-section {
|
||||
margin-top: 2.8in; /* Clear the address space */
|
||||
margin-bottom: 0.5in;
|
||||
}
|
||||
|
||||
.pool-image {
|
||||
float: right;
|
||||
width: 2.5in;
|
||||
height: 2in;
|
||||
margin-left: 0.25in;
|
||||
margin-bottom: 0.25in;
|
||||
/*border: 2px solid #0dcaf0;
|
||||
background-color: #e7f6ff;*/
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #0dcaf0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.letter-content {
|
||||
text-align: justify;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.logo-space {
|
||||
text-align: center;
|
||||
margin-top: 1in;
|
||||
margin-bottom: 0.5in;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 1.5in;
|
||||
height: 1.5in;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #198754;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.signature-line {
|
||||
margin-top: 0.75in;
|
||||
margin-left: 3in;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #0d6efd;
|
||||
margin-bottom: 0.5in;
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-bottom: 0.3in;
|
||||
}
|
||||
</style>
|
||||
{{ end }}
|
||||
{{ define "content" }}
|
||||
<!-- Address Space (will be populated automatically) -->
|
||||
<div class="address-space"></div>
|
||||
|
||||
<!-- Main Letter Content -->
|
||||
<div class="header-section">
|
||||
<!-- Pool Image Placeholder -->
|
||||
<img class="pool-image" src="/mailer/pool/random" />
|
||||
|
||||
<div class="date"><strong>Date:</strong>March 31, 2026</div>
|
||||
</div>
|
||||
|
||||
<div class="letter-content">
|
||||
<p>Dear Valued Member of Humanity,</p>
|
||||
|
||||
<p>
|
||||
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 <strong>mosquitoes</strong>—nature's tiny
|
||||
terrorists.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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
|
||||
<b>take this letter to Benjamin Sperry.</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Why should you care?</strong> 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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p><strong>What can YOU do?</strong></p>
|
||||
<ul>
|
||||
<li>
|
||||
Eliminate standing water around your property (mosquitoes can breed in a
|
||||
bottle cap—yes, really)
|
||||
</li>
|
||||
<li>
|
||||
Wear EPA-approved insect repellent (become unfashionable to mosquitoes)
|
||||
</li>
|
||||
<li>
|
||||
Install or repair window screens (the medieval castle defense strategy,
|
||||
but modernized)
|
||||
</li>
|
||||
<li>
|
||||
Support your local mosquito control district (that's us—we accept praise
|
||||
and appreciation)
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>Stay vigilant. Stay bite-free. Stay alive.</p>
|
||||
|
||||
<div class="signature-line">
|
||||
<p><strong>Sincerely,</strong></p>
|
||||
<p>Eli Ribble</p>
|
||||
<p>Founder of Gleipnir</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Company Logo Space -->
|
||||
<div class="logo-space">
|
||||
<img class="logo" src="/static/img/nidus-logo-256-transparent.png" />
|
||||
</div>
|
||||
{{ end }}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue