diff --git a/api/compliance.go b/api/compliance.go new file mode 100644 index 00000000..23116aa6 --- /dev/null +++ b/api/compliance.go @@ -0,0 +1,58 @@ +package api + +import ( + "bytes" + "fmt" + "io" + "net/http" + + "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/platform/imagetile" + "github.com/go-chi/chi/v5" + "github.com/rs/zerolog/log" +) + +func getComplianceRequestImagePool(w http.ResponseWriter, r *http.Request) { + code := chi.URLParam(r, "public_id") + if code == "" { + http.Error(w, "empty public_id", http.StatusBadRequest) + return + } + + ctx := r.Context() + comp, err := models.ComplianceReportRequests.Query( + models.Preload.ComplianceReportRequest.Site(), + models.SelectWhere.ComplianceReportRequests.PublicID.EQ(code), + ).One(ctx, db.PGInstance.BobDB) + if err != nil { + http.Error(w, "no comp", http.StatusInternalServerError) + return + } + + site := comp.R.Site + org, err := models.FindOrganization(ctx, db.PGInstance.BobDB, site.OrganizationID) + if err != nil { + http.Error(w, "no org", http.StatusInternalServerError) + return + } + envelope, err := platform.ParcelEnvelope(ctx, site.ParcelID) + if err != nil { + log.Error().Err(err).Msg("parcel envelop failure") + http.Error(w, "parcel env", http.StatusInternalServerError) + return + } + log.Info().Int("len", len(*envelope)).Msg("got envelope") + level := uint(12) + ring := (*envelope)[0] + p := ring[0] + img, err := imagetile.ImageAtPoint(ctx, org, level, p[0], p[1]) + w.Header().Set("Content-Type", "image/png") + w.Header().Set("Content-Length", fmt.Sprintf("%d", len(img))) + _, err = io.Copy(w, bytes.NewBuffer(img)) + if err != nil { + http.Error(w, "failed copy", http.StatusInternalServerError) + return + } +} diff --git a/html/template/sync/mailer.html b/html/template/sync/mailer.html new file mode 100644 index 00000000..2f7d368e --- /dev/null +++ b/html/template/sync/mailer.html @@ -0,0 +1,401 @@ +{{ template "sync/layout/base.html" . }} + +{{ define "title" }}Mailer{{ end }} +{{ define "extraheader" }} + +{{ end }} +{{ define "content" }} + + +
+
+
+
+ Aerial review indicates
+ possible standing water at
+ this address.

+ Upload a photo to confirm
+ conditions and close this
+ case.

+ Not your property? Please
+ let us know. +
+ Pool photo +
+
id {{ .DocumentID }}
+
+ +
+

Dear Resident,

+

+ The {{ .Organization.Name }} is contacting you because recent aerial + imagery indicates that your swimming pool may be holding standing water. + When water sits without circulation or treatment, mosquitoes can develop + quickly and affect the surrounding neighborhood. +

+
+ Action requested + Please scan the QR code on this letter and upload a current photo of + your pool. The image should clearly show the deep end and overall water + condition. We request a photo whether the water appears clear, cloudy, + or green so we can accurately assess the situation and close the matter + if no issue exists. +
+

+ If your pool has started to look more like a pond than a pool, that is + more common than most people realize. Standing water creates ideal + conditions for mosquito production. If treatment is needed, we can + coordinate access while longer-term maintenance or repairs are + addressed. +

+ Our objective is to prevent mosquito production and protect your + neighborhood. A quick photo response through the QR code is the fastest + way to resolve this. +

+ If you are unable to use the QR code, please visit {{ .ReportURL }} or + contact our office for assistance. +

+
+ Sincerely,
+ {{ .Organization.Name }}
+ {{ .Organization.OfficeAddressStreet.GetOr "" }} - + {{ .Organization.OfficeAddressCity.GetOr "" }}, + {{ .Organization.OfficeAddressState.GetOr "" }} + {{ .Organization.OfficeAddressPostalCode.GetOr "" }}
+ Phone: + {{ .Organization.OfficePhone.GetOr "" }} +
+ District logo +
+ +
+
Scan. Snap. Done.
+ QR code +
+ Takes less than 60 seconds. +
    +
  1. Scan the QR code
  2. +
  3. Upload one pool
    photo
  4. +
  5. Case closed if clear
  6. +
+ If treatment is needed, we
coordinate next steps. +
+
Clear photo tips
+ +
Clear photos allow faster
review.
+
+
+ + +
+
+
+
+ La revisión aérea indica
+ posible agua estancada en
+ esta dirección.

+ Suba una foto para confirmar
+ las condiciones y cerrar este
+ caso.

+ ¿No es su propiedad? Por favor
+ infórmenos. +
+ Foto de la piscina +
+
id {{ .DocumentID }}
+
+ +
+

Estimado Residente,

+

+ El Distrito de Control de Mosquitos y Vectores del Delta se comunica con + usted porque imágenes aéreas recientes indican que su piscina puede + estar reteniendo agua estancada. Cuando el agua permanece sin + circulación o tratamiento, los mosquitos pueden desarrollarse + rápidamente y afectar al vecindario. +

+
+ Acción requerida + Por favor escanee el código QR en esta carta y suba una foto actual de + su piscina. La imagen debe mostrar claramente la parte profunda y la + condición general del agua. Solicitamos una foto ya sea que el agua esté + clara, turbia o verde para poder evaluar la situación y cerrar el caso + si no existe ningún problema. +
+

+ Si su piscina ha comenzado a parecer más un estanque que una piscina, es + más común de lo que muchos creen. El agua estancada crea condiciones + ideales para la producción de mosquitos. Si se necesita tratamiento, + podemos coordinar el acceso mientras se realizan reparaciones o + mantenimiento a largo plazo. +

+ Nuestro objetivo es prevenir la producción de mosquitos y proteger su + vecindario. Una respuesta rápida con una foto a través del código QR es + la manera más rápida de resolver esto. +

+ Si no puede usar el código QR, visite {{ .ReportURL }} o comuníquese con + nuestra oficina para recibir ayuda. +

+
+ Atentamente,
+ {{ .Organization.Name }}
+ {{ .Organization.OfficeAddressStreet.GetOr "" }} - + {{ .Organization.OfficeAddressCity.GetOr "" }}, + {{ .Organization.OfficeAddressState.GetOr "" }} + {{ .Organization.OfficeAddressPostalCode.GetOr "" }}
+ Teléfono: + {{ .Organization.OfficePhone.GetOr "" }} +
+ Logotipo del distrito +
+ +
+
Escanee. Tome foto. Listo.
+ Código QR +
+ Toma menos de 60 segundos. +
    +
  1. Escanee el código QR
  2. +
  3. Suba una foto de la
    piscina
  4. +
  5. Caso cerrado si está claro
  6. +
+ Si se necesita tratamiento,
coordinamos los siguientes pasos. +
+
Consejos para la foto
+ +
+ Fotos claras permiten una
revisión más rápida. +
+
+
+{{ end }} diff --git a/rmo/mailer.go b/rmo/mailer.go new file mode 100644 index 00000000..9e9a09e1 --- /dev/null +++ b/rmo/mailer.go @@ -0,0 +1,12 @@ +package rmo + +import ( + "net/http" + //"github.com/Gleipnir-Technology/nidus-sync/config" +) + +type contentMailer struct{} + +func getMailer(w http.ResponseWriter, r *http.Request) { + +} diff --git a/sync/mailer.go b/sync/mailer.go new file mode 100644 index 00000000..dd57bc0c --- /dev/null +++ b/sync/mailer.go @@ -0,0 +1,60 @@ +package sync + +import ( + "net/http" + //"strconv" + + "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/google/uuid" + "github.com/go-chi/chi/v5" +) + +type contentMailer struct { + Config contentConfig + DocumentID string + LogoURL string + Organization *models.Organization + PoolImageURL string + QRCodeURL string + ReportURL string +} + +// func getMailer(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentMailer], *errorWithStatus) { +func getMailer(w http.ResponseWriter, r *http.Request) { +} +func getMailerPreview(w http.ResponseWriter, r *http.Request) { + code := chi.URLParam(r, "code") + if code == "" { + http.Error(w, "empty code", http.StatusBadRequest) + return + } + + ctx := r.Context() + comp, err := models.ComplianceReportRequests.Query( + models.Preload.ComplianceReportRequest.Site(), + models.SelectWhere.ComplianceReportRequests.PublicID.EQ(code), + ).One(ctx, db.PGInstance.BobDB) + + if err != nil { + http.Error(w, "no comp", http.StatusInternalServerError) + return + } + site := comp.R.Site + org, err := models.FindOrganization(ctx, db.PGInstance.BobDB, site.OrganizationID) + if err != nil { + http.Error(w, "no comp", http.StatusInternalServerError) + return + } + html.RenderOrError(w, "sync/mailer.html", contentMailer{ + Config: newContentConfig(), + DocumentID: "00000000-0000-0000-0000-000000000000", + LogoURL: config.MakeURLNidus("/api/district/%s/logo", org.Slug.GetOr("unset")), + Organization: org, + PoolImageURL: config.MakeURLNidus("/api/compliance-request/image/pool/%s", code), + QRCodeURL: config.MakeURLNidus("/qr-code/mailer/%s", code), + ReportURL: config.MakeURLReport("/mailer/%s", code), + }) +} diff --git a/sync/parcel.go b/sync/parcel.go new file mode 100644 index 00000000..d3329cca --- /dev/null +++ b/sync/parcel.go @@ -0,0 +1,14 @@ +package sync + +import ( + "context" + "net/http" + + "github.com/Gleipnir-Technology/nidus-sync/db/models" +) + +type contentParcel struct{} + +func getParcel(ctx context.Context, r *http.Request, org *models.Organization, user *models.User) (*response[contentParcel], *errorWithStatus) { + return newResponse("sync/parcel.html", contentParcel{}), nil +} diff --git a/sync/qr.go b/sync/qr.go new file mode 100644 index 00000000..5d2807f8 --- /dev/null +++ b/sync/qr.go @@ -0,0 +1,82 @@ +package sync + +import ( + "github.com/skip2/go-qrcode" + "net/http" + "strconv" + + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/go-chi/chi/v5" +) + +func getQRCodeMailer(w http.ResponseWriter, r *http.Request) { + code := chi.URLParam(r, "code") + if code == "" { + respondError(w, "There should always be a id", nil, http.StatusBadRequest) + } + content := config.MakeURLReport("/mailer/%s", code) + writeQRCode(w, r, content) +} + +func getQRCodeReport(w http.ResponseWriter, r *http.Request) { + code := chi.URLParam(r, "code") + if code == "" { + respondError(w, "There should always be a code", nil, http.StatusBadRequest) + } + content := config.MakeURLNidus("/report/%s", code) + writeQRCode(w, r, content) +} +func writeQRCode(w http.ResponseWriter, r *http.Request, content string) { + // Get optional size parameter (default to 256) + size := 256 + if sizeStr := r.URL.Query().Get("size"); sizeStr != "" { + var err error + size, err = strconv.Atoi(sizeStr) + if err != nil { + http.Error(w, "Invalid 'size' parameter, must be an integer", http.StatusBadRequest) + return + } + } + + // Get optional error correction level (default to Medium) + level := qrcode.Medium + if levelStr := r.URL.Query().Get("level"); levelStr != "" { + switch levelStr { + case "L", "l": + level = qrcode.Low + case "M", "m": + level = qrcode.Medium + case "Q", "q": + level = qrcode.High + case "H", "h": + level = qrcode.Highest + default: + respondError(w, "Invalid 'level' parameter, must be L, M, Q, or H", nil, http.StatusBadRequest) + return + } + } + + // Generate the QR code + var qr *qrcode.QRCode + var err error + qr, err = qrcode.New(content, level) + if err != nil { + respondError(w, "Error generating QR code", err, http.StatusInternalServerError) + return + } + + // Set the appropriate content type + w.Header().Set("Content-Type", "image/png") + + // Generate PNG and write directly to the response writer + png, err := qr.PNG(size) + if err != nil { + respondError(w, "Error encoding QR code to PNG", err, http.StatusInternalServerError) + return + } + + _, err = w.Write(png) + if err != nil { + respondError(w, "Error writing response", err, http.StatusInternalServerError) + } +}