nidus-sync/main.go
Eli Ribble 9774452821 Switch main report page to better example
This is still pulling from our generic district mock, but it's still
better than what we had. This also includes adding static content
hosting for the bootstrap content on the public domain.
2026-01-07 20:47:55 +00:00

163 lines
4.3 KiB
Go

package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"runtime/debug"
"sync"
"syscall"
"time"
"github.com/Gleipnir-Technology/nidus-sync/auth"
"github.com/Gleipnir-Technology/nidus-sync/background"
"github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/queue"
"github.com/Gleipnir-Technology/nidus-sync/report"
nidussync "github.com/Gleipnir-Technology/nidus-sync/sync"
"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"
)
func main() {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
err := config.Parse()
if err != nil {
log.Error().Err(err).Msg("Failed to parse config")
os.Exit(1)
}
log.Info().Msg("Starting...")
err = db.InitializeDatabase(context.TODO(), config.PGDSN)
if err != nil {
log.Error().Err(err).Msg("Failed to connect to database")
os.Exit(2)
}
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 := nidussync.Router()
hr.Map("", sr) // default
hr.Map("*", sr) // default
hr.Map(config.URLReport, report.Router()) // report.mosquitoes.online
hr.Map(config.URLSync, sr)
r.Mount("/", hr)
log.Info().Str("report url", config.URLReport).Str("sync url", config.URLSync).Msg("Serving at URLs")
// Start up background processes
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
background.NewOAuthTokenChannel = make(chan struct{}, 10)
var waitGroup sync.WaitGroup
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
background.RefreshFieldseekerData(ctx, background.NewOAuthTokenChannel)
}()
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
queue.StartAudioWorker(ctx)
}()
server := &http.Server{
Addr: config.Bind,
Handler: r,
}
go func() {
log.Info().Str("address", config.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 LoggerMiddleware(logger *zerolog.Logger) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
log := logger.With().Logger()
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
t1 := time.Now()
defer func() {
t2 := time.Now()
// Recover and record stack traces in case of a panic
if rec := recover(); rec != nil {
log.Error().
Str("type", "error").
Timestamp().
Interface("recover_info", rec).
Bytes("debug_stack", debug.Stack()).
Msg("log system error")
fmt.Println("Stack:", string(debug.Stack()))
http.Error(ww, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
remote_addr := r.RemoteAddr
forwarded_for := r.Header.Get("X-Forwarded-For")
if forwarded_for != "" {
remote_addr = forwarded_for
}
// log end request
log.Info().
//Str("type", "access").
Timestamp().
Fields(map[string]interface{}{
"remote_ip": remote_addr,
"url": r.URL.Path,
//"proto": r.Proto,
"method": r.Method,
//"user_agent": r.Header.Get("User-Agent"),
"status": ww.Status(),
"latency_ms": float64(t2.Sub(t1).Nanoseconds()) / 1000000.0,
"bytes_in": r.Header.Get("Content-Length"),
"bytes_out": ww.BytesWritten(),
}).
Msg("incoming_request")
}()
next.ServeHTTP(ww, r)
}
return http.HandlerFunc(fn)
}
}