Add support for additional report domain

This will allow us to use a different URL for doing public outreach,
which is nice for everybody.
This commit is contained in:
Eli Ribble 2026-01-07 15:08:29 +00:00
parent 62bf045edd
commit bf4c5d5c71
7 changed files with 108 additions and 63 deletions

View file

@ -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.

View file

@ -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 != "" {

2
go.mod
View file

@ -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

7
go.sum
View file

@ -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=

View file

@ -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")

136
main.go
View file

@ -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)
}

18
report/endpoint.go Normal file
View file

@ -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.")
}