nidus-sync/main.go

161 lines
4.4 KiB
Go
Raw Normal View History

package main
import (
2025-11-04 23:11:32 +00:00
"context"
"log/slog"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/alexedwards/scs/pgxstore"
"github.com/alexedwards/scs/v2"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
var sessionManager *scs.SessionManager
var BaseURL, ClientID, ClientSecret, Environment, MapboxToken string
func main() {
ClientID = os.Getenv("ARCGIS_CLIENT_ID")
if ClientID == "" {
slog.Error("You must specify a non-empty ARCGIS_CLIENT_ID")
os.Exit(1)
}
ClientSecret = os.Getenv("ARCGIS_CLIENT_SECRET")
if ClientSecret == "" {
slog.Error("You must specify a non-empty ARCGIS_CLIENT_SECRET")
os.Exit(1)
}
BaseURL = os.Getenv("BASE_URL")
if BaseURL == "" {
slog.Error("You must specify a non-empty BASE_URL")
os.Exit(1)
}
bind := os.Getenv("BIND")
if bind == "" {
bind = ":9001"
}
Environment = os.Getenv("ENVIRONMENT")
if Environment == "" {
slog.Error("You must specify a non-empty ENVIRONMENT")
os.Exit(1)
}
if !(Environment == "PRODUCTION" || Environment == "DEVELOPMENT") {
slog.Error("ENVIRONMENT should be either DEVELOPMENT or PRODUCTION", slog.String("ENVIRONMENT", Environment))
os.Exit(2)
}
MapboxToken = os.Getenv("MAPBOX_TOKEN")
if MapboxToken == "" {
slog.Error("You must specify a non-empty MAPBOX_TOKEN")
os.Exit(1)
}
2025-11-04 23:11:32 +00:00
pg_dsn := os.Getenv("POSTGRES_DSN")
if pg_dsn == "" {
slog.Error("You must specify a non-empty POSTGRES_DSN")
2025-11-04 23:11:32 +00:00
os.Exit(1)
}
slog.Info("Starting...")
2025-11-04 23:11:32 +00:00
err := initializeDatabase(context.TODO(), pg_dsn)
if err != nil {
slog.Error("Failed to connect to database", slog.String("err", err.Error()))
2025-11-04 23:11:32 +00:00
os.Exit(2)
}
sessionManager = scs.New()
sessionManager.Store = pgxstore.New(PGInstance.PGXPool)
sessionManager.Lifetime = 24 * time.Hour
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(sessionManager.LoadAndSave)
// Root is a special endpoint that is neither authenticated nor unauthenticated
r.Get("/", getRoot)
// Unauthenticated endpoints
r.Get("/arcgis/oauth/begin", getArcgisOauthBegin)
r.Get("/arcgis/oauth/callback", getArcgisOauthCallback)
r.Get("/favicon.ico", getFavicon)
r.Get("/oauth/refresh", getOAuthRefresh)
r.Get("/phone-call", getPhoneCall)
r.Get("/qr-code/report/{code}", getQRCodeReport)
2025-11-05 21:05:10 +00:00
r.Get("/report", getReport)
2025-11-05 21:37:11 +00:00
r.Get("/report/{code}", getReportDetail)
2025-11-05 22:03:33 +00:00
r.Get("/report/{code}/confirm", getReportConfirmation)
r.Get("/report/{code}/contribute", getReportContribute)
r.Get("/report/{code}/evidence", getReportEvidence)
2025-11-05 21:57:59 +00:00
r.Get("/report/{code}/schedule", getReportSchedule)
2025-11-05 23:41:21 +00:00
r.Get("/report/{code}/update", getReportUpdate)
r.Get("/service-request", getServiceRequest)
r.Get("/service-request/{code}", getServiceRequestDetail)
r.Get("/service-request-location", getServiceRequestLocation)
r.Get("/service-request-mosquito", getServiceRequestMosquito)
r.Get("/service-request-pool", getServiceRequestPool)
2025-11-10 15:27:22 +00:00
r.Get("/service-request-quick", getServiceRequestQuick)
r.Get("/service-request-quick-confirmation", getServiceRequestQuickConfirmation)
r.Get("/service-request-updates", getServiceRequestUpdates)
r.Post("/signin", postSignin)
2025-11-04 00:02:51 +00:00
r.Get("/signup", getSignup)
r.Post("/signup", postSignup)
// Authenticated endpoints
r.Method("GET", "/settings", NewEnsureAuth(getSettings))
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)
}()
server := &http.Server{
Addr: bind,
Handler: r,
}
go func() {
slog.Info("Serving HTTP requests", slog.String("address", bind))
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
slog.Error("HTTP Server Error", slog.String("err", err.Error()))
}
}()
// Wait for the interrupt signal to gracefully shut down
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
<-signalCh
slog.Info("Received shutdown signal, shutting down...")
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer shutdownCancel()
if err := server.Shutdown(shutdownCtx); err != nil {
slog.Error("HTTP server shutdown error", slog.String("err", err.Error()))
}
cancel()
waitGroup.Wait()
slog.Info("Shutdown complete")
}
func IsProductionEnvironment() bool {
return Environment == "PRODUCTION"
}