From bf4c5d5c717296e8adb1e695840e6b3c2c344915 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 7 Jan 2026 15:08:29 +0000 Subject: [PATCH] Add support for additional report domain This will allow us to use a different URL for doing public outreach, which is nice for everybody. --- arcgis.go | 2 +- endpoint.go | 4 +- go.mod | 2 +- go.sum | 7 +-- html.go | 2 +- main.go | 136 +++++++++++++++++++++++++++------------------ report/endpoint.go | 18 ++++++ 7 files changed, 108 insertions(+), 63 deletions(-) create mode 100644 report/endpoint.go diff --git a/arcgis.go b/arcgis.go index 029db6df..52ddf1ae 100644 --- a/arcgis.go +++ b/arcgis.go @@ -266,7 +266,7 @@ func hasFieldseekerConnection(ctx context.Context, user *models.User) (bool, err return len(result) > 0, nil } func redirectURL() string { - return BaseURL + "/arcgis/oauth/callback" + return urlSync("/arcgis/oauth/callback") } // This is a goroutine that is in charge of getting Fieldseeker data and keeping it fresh. diff --git a/endpoint.go b/endpoint.go index 29f709fc..cf3c3116 100644 --- a/endpoint.go +++ b/endpoint.go @@ -39,7 +39,7 @@ func getArcgisOauthCallback(w http.ResponseWriter, r *http.Request) { respondError(w, "Failed to handle access code", err, http.StatusInternalServerError) return } - http.Redirect(w, r, BaseURL+"/", http.StatusFound) + http.Redirect(w, r, urlSync("/"), http.StatusFound) } func getCellDetails(w http.ResponseWriter, r *http.Request, user *models.User) { @@ -76,7 +76,7 @@ func getQRCodeReport(w http.ResponseWriter, r *http.Request) { if code == "" { respondError(w, "There should always be a code", nil, http.StatusBadRequest) } - content := BaseURL + "/report/" + code + content := urlSync("/report/" + code) // Get optional size parameter (default to 256) size := 256 if sizeStr := r.URL.Query().Get("size"); sizeStr != "" { diff --git a/go.mod b/go.mod index 709c9e64..aa0285db 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/alexedwards/scs/v2 v2.9.0 github.com/alitto/pond/v2 v2.5.0 github.com/go-chi/chi/v5 v5.2.3 + 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 @@ -20,7 +21,6 @@ require ( github.com/minio/minio-go/v7 v7.0.97 github.com/pressly/goose/v3 v3.26.0 github.com/rs/zerolog v1.34.0 - 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 diff --git a/go.sum b/go.sum index adf9caa4..10270663 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,11 @@ github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0o github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 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= github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/hostrouter v0.3.0 h1:75it1eO3FvkG8te1CvU6Kvr3WzAZNEBbo8xIrxUKLOQ= +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-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= @@ -169,8 +172,6 @@ github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= -github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= -github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= @@ -222,8 +223,6 @@ 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/uber/h3-go/v4 v4.3.0 h1:5y5je8gu6+1pGzGo8soiudmgE3WJzfJRWdy0yhc3+HY= -github.com/uber/h3-go/v4 v4.3.0/go.mod h1:EyZ/EWguHlheIBcshTAMmQPYcaGKVvJ4qlzEHzC0BkU= 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= diff --git a/html.go b/html.go index 44fe5836..355271a6 100644 --- a/html.go +++ b/html.go @@ -48,7 +48,7 @@ var ( dispatch = newBuiltTemplate("dispatch", "base") dispatchResults = newBuiltTemplate("dispatch-results", "base") mockRoot = newBuiltTemplate("mock-root", "base") - report = newBuiltTemplate("report", "base") + reportPage = newBuiltTemplate("report", "base") reportConfirmation = newBuiltTemplate("report-confirmation", "base") reportContribute = newBuiltTemplate("report-contribute", "base") reportDetail = newBuiltTemplate("report-detail", "base") diff --git a/main.go b/main.go index ca40bf26..e394c3f9 100644 --- a/main.go +++ b/main.go @@ -15,14 +15,16 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/auth" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/queue" + "github.com/Gleipnir-Technology/nidus-sync/report" "github.com/Gleipnir-Technology/nidus-sync/userfile" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/hostrouter" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) -var BaseURL, ClientID, ClientSecret, Environment, FieldseekerSchemaDirectory, MapboxToken string +var ClientID, ClientSecret, Environment, FieldseekerSchemaDirectory, MapboxToken, URLReport, URLSync string func main() { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix @@ -38,9 +40,14 @@ func main() { log.Error().Msg("You must specify a non-empty ARCGIS_CLIENT_SECRET") os.Exit(1) } - BaseURL = os.Getenv("BASE_URL") - if BaseURL == "" { - log.Error().Msg("You must specify a non-empty BASE_URL") + URLReport = os.Getenv("URL_REPORT") + if URLReport == "" { + log.Error().Msg("You must specify a non-empty URL_REPORT") + os.Exit(1) + } + URLSync = os.Getenv("URL_SYNC") + if URLSync == "" { + log.Error().Msg("You must specify a non-empty URL_SYNC") os.Exit(1) } bind := os.Getenv("BIND") @@ -86,9 +93,74 @@ func main() { router_logger := log.With().Logger() r := chi.NewRouter() + r.Use(LoggerMiddleware(&router_logger)) + r.Use(middleware.RealIP) r.Use(auth.NewSessionManager().LoadAndSave) + hr := hostrouter.New() + + // Set up routing by hostname + sr := syncRouter() + hr.Map("", sr) // default + hr.Map("*", sr) // default + hr.Map(URLReport, report.Router()) // report.mosquitoes.online + hr.Map(URLSync, sr) + r.Mount("/", hr) + + log.Info().Str("report url", URLReport).Str("sync url", URLSync).Msg("Serving at URLs") + // Start up background processes + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + NewOAuthTokenChannel = make(chan struct{}, 10) + + var waitGroup sync.WaitGroup + + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + refreshFieldseekerData(ctx, NewOAuthTokenChannel) + }() + + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + queue.StartAudioWorker(ctx) + }() + + server := &http.Server{ + Addr: bind, + Handler: r, + } + go func() { + log.Info().Str("address", bind).Msg("Serving HTTP requests") + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Error().Str("err", err.Error()).Msg("HTTP Server Error") + } + }() + + // Wait for the interrupt signal to gracefully shut down + signalCh := make(chan os.Signal, 1) + signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM) + <-signalCh + + log.Info().Msg("Received shutdown signal, shutting down...") + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer shutdownCancel() + + if err := server.Shutdown(shutdownCtx); err != nil { + log.Error().Str("err", err.Error()).Msg("HTTP server shutdown error") + } + + cancel() + + waitGroup.Wait() + + log.Info().Msg("Shutdown complete") +} +func syncRouter() chi.Router { + r := chi.NewRouter() // Root is a special endpoint that is neither authenticated nor unauthenticated r.Get("/", getRoot) @@ -145,59 +217,11 @@ func main() { 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)) + //r.Method("GET", "/vector-tiles/{org_id}/{tileset_id}/{zoom}/{x}/{y}.{format}", auth.NewEnsureAuth(getVectorTiles)) localFS := http.Dir("./static") FileServer(r, "/static", localFS, embeddedStaticFS, "static") - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - NewOAuthTokenChannel = make(chan struct{}, 10) - - var waitGroup sync.WaitGroup - - waitGroup.Add(1) - go func() { - defer waitGroup.Done() - refreshFieldseekerData(ctx, NewOAuthTokenChannel) - }() - - waitGroup.Add(1) - go func() { - defer waitGroup.Done() - queue.StartAudioWorker(ctx) - }() - - server := &http.Server{ - Addr: bind, - Handler: r, - } - go func() { - log.Info().Str("address", bind).Msg("Serving HTTP requests") - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Error().Str("err", err.Error()).Msg("HTTP Server Error") - } - }() - - // Wait for the interrupt signal to gracefully shut down - signalCh := make(chan os.Signal, 1) - signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM) - <-signalCh - - log.Info().Msg("Received shutdown signal, shutting down...") - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second) - defer shutdownCancel() - - if err := server.Shutdown(shutdownCtx); err != nil { - log.Error().Str("err", err.Error()).Msg("HTTP server shutdown error") - } - - cancel() - - waitGroup.Wait() - - log.Info().Msg("Shutdown complete") + return r } func IsProductionEnvironment() bool { @@ -255,3 +279,7 @@ func LoggerMiddleware(logger *zerolog.Logger) func(next http.Handler) http.Handl return http.HandlerFunc(fn) } } + +func urlSync(path string) string { + return fmt.Sprintf("https://%s%s", URLSync, path) +} diff --git a/report/endpoint.go b/report/endpoint.go new file mode 100644 index 00000000..8f97bca2 --- /dev/null +++ b/report/endpoint.go @@ -0,0 +1,18 @@ +package report + +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" +) + +func Router() chi.Router { + r := chi.NewRouter() + r.Get("/", getRoot) + return r +} + +func getRoot(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Herro.") +}