2025-11-03 12:22:06 +00:00
|
|
|
package main
|
2025-11-03 12:38:47 +00:00
|
|
|
|
2025-11-03 12:22:06 +00:00
|
|
|
import (
|
2025-11-04 23:11:32 +00:00
|
|
|
"context"
|
2026-02-09 21:39:47 +00:00
|
|
|
"flag"
|
2026-01-06 16:21:59 +00:00
|
|
|
"fmt"
|
2025-11-03 12:38:47 +00:00
|
|
|
"net/http"
|
|
|
|
|
"os"
|
2025-11-07 02:29:34 +00:00
|
|
|
"os/signal"
|
2025-11-24 19:45:37 +00:00
|
|
|
"runtime/debug"
|
2025-11-07 02:29:34 +00:00
|
|
|
"syscall"
|
2025-11-03 12:38:47 +00:00
|
|
|
"time"
|
|
|
|
|
|
2026-03-13 17:33:39 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/api"
|
2025-12-16 16:37:53 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
2026-01-07 16:07:51 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/config"
|
2025-11-24 18:08:24 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/db"
|
2026-02-07 05:51:21 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/html"
|
2026-01-26 20:29:04 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/llm"
|
2026-03-12 23:49:16 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
2026-01-29 23:55:41 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/rmo"
|
2026-01-07 20:47:55 +00:00
|
|
|
nidussync "github.com/Gleipnir-Technology/nidus-sync/sync"
|
2026-01-29 15:48:15 +00:00
|
|
|
"github.com/getsentry/sentry-go"
|
|
|
|
|
sentryhttp "github.com/getsentry/sentry-go/http"
|
|
|
|
|
"github.com/getsentry/sentry-go/zerolog"
|
2025-11-03 12:38:47 +00:00
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
|
"github.com/go-chi/chi/v5/middleware"
|
2026-01-07 15:08:29 +00:00
|
|
|
"github.com/go-chi/hostrouter"
|
2025-11-13 20:11:00 +00:00
|
|
|
"github.com/rs/zerolog"
|
|
|
|
|
"github.com/rs/zerolog/log"
|
2025-11-03 12:22:06 +00:00
|
|
|
)
|
2025-11-03 12:38:47 +00:00
|
|
|
|
2025-11-03 12:22:06 +00:00
|
|
|
func main() {
|
2026-01-07 16:07:51 +00:00
|
|
|
err := config.Parse()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Error().Err(err).Msg("Failed to parse config")
|
2025-12-16 16:37:53 +00:00
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
2026-01-29 15:48:15 +00:00
|
|
|
|
2026-02-09 21:39:47 +00:00
|
|
|
var prod = flag.Bool("prod", false, "Force into production mode")
|
|
|
|
|
flag.Parse()
|
|
|
|
|
if prod != nil && *prod {
|
|
|
|
|
log.Warn().Msg("Forcing production mode for testing templates")
|
|
|
|
|
config.Environment = "PRODUCTION"
|
|
|
|
|
}
|
2026-03-05 19:17:16 +00:00
|
|
|
log.Info().Str("environment", config.Environment).Bool("is-prod", config.IsProductionEnvironment()).Msg("Starting")
|
2026-01-29 15:48:15 +00:00
|
|
|
err = sentry.Init(sentry.ClientOptions{
|
2026-03-09 19:09:07 +00:00
|
|
|
Debug: false, //!config.IsProductionEnvironment(),
|
2026-01-29 15:48:15 +00:00
|
|
|
Dsn: config.SentryDSN,
|
|
|
|
|
EnableTracing: true,
|
|
|
|
|
SendDefaultPII: true,
|
|
|
|
|
TracesSampleRate: 1.0,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Error().Err(err).Msg("Failed to start sentry connection")
|
|
|
|
|
os.Exit(2)
|
|
|
|
|
}
|
|
|
|
|
defer sentry.Flush(2 * time.Second)
|
|
|
|
|
|
|
|
|
|
sentryWriter, err := sentryzerolog.New(sentryzerolog.Config{
|
|
|
|
|
ClientOptions: sentry.ClientOptions{
|
|
|
|
|
Dsn: config.SentryDSN,
|
|
|
|
|
},
|
|
|
|
|
Options: sentryzerolog.Options{
|
|
|
|
|
Levels: []zerolog.Level{zerolog.ErrorLevel, zerolog.FatalLevel, zerolog.PanicLevel},
|
|
|
|
|
WithBreadcrumbs: true,
|
|
|
|
|
FlushTimeout: 3 * time.Second,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal().Err(err).Msg("Failed to create sentry writer")
|
|
|
|
|
os.Exit(2)
|
|
|
|
|
}
|
|
|
|
|
defer sentryWriter.Close()
|
|
|
|
|
|
|
|
|
|
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
2026-02-24 15:34:53 +00:00
|
|
|
log.Logger = log.Output(zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stderr}, sentryWriter))
|
|
|
|
|
if os.Getenv("VERBOSE") != "" {
|
|
|
|
|
log.Logger = log.Logger.Level(zerolog.DebugLevel)
|
|
|
|
|
} else {
|
|
|
|
|
log.Logger = log.Logger.Level(zerolog.InfoLevel)
|
|
|
|
|
}
|
2026-01-29 15:48:15 +00:00
|
|
|
|
2026-01-07 16:07:51 +00:00
|
|
|
err = db.InitializeDatabase(context.TODO(), config.PGDSN)
|
2025-11-04 23:11:32 +00:00
|
|
|
if err != nil {
|
2026-01-07 16:07:51 +00:00
|
|
|
log.Error().Err(err).Msg("Failed to connect to database")
|
2026-01-29 15:48:15 +00:00
|
|
|
os.Exit(3)
|
2025-11-04 23:11:32 +00:00
|
|
|
}
|
2025-11-03 12:38:47 +00:00
|
|
|
|
2026-02-07 05:51:21 +00:00
|
|
|
err = html.LoadTemplates()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Error().Err(err).Msg("Failed to load html templates")
|
|
|
|
|
os.Exit(4)
|
|
|
|
|
}
|
2026-03-16 19:52:29 +00:00
|
|
|
// Start up background processes
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
defer cancel()
|
2026-01-26 20:29:04 +00:00
|
|
|
|
2026-03-16 19:52:29 +00:00
|
|
|
err = platform.StartAll(ctx)
|
2026-02-09 21:51:57 +00:00
|
|
|
if err != nil {
|
2026-03-16 19:52:29 +00:00
|
|
|
log.Error().Err(err).Msg("Failed at platform.StartAll")
|
|
|
|
|
os.Exit(5)
|
2026-02-09 21:51:57 +00:00
|
|
|
}
|
2025-11-24 19:45:37 +00:00
|
|
|
router_logger := log.With().Logger()
|
2026-01-29 15:48:15 +00:00
|
|
|
sentryMiddleware := sentryhttp.New(sentryhttp.Options{
|
|
|
|
|
Repanic: true,
|
|
|
|
|
})
|
2025-11-03 12:38:47 +00:00
|
|
|
r := chi.NewRouter()
|
2026-01-07 15:08:29 +00:00
|
|
|
|
2025-11-24 19:45:37 +00:00
|
|
|
r.Use(LoggerMiddleware(&router_logger))
|
2026-01-29 15:48:15 +00:00
|
|
|
r.Use(middleware.RequestID)
|
2026-01-07 15:08:29 +00:00
|
|
|
r.Use(middleware.RealIP)
|
2026-01-30 16:34:58 +00:00
|
|
|
//r.Use(middleware.Logger)
|
2026-01-29 15:48:15 +00:00
|
|
|
r.Use(middleware.Recoverer)
|
|
|
|
|
r.Use(sentryMiddleware.Handle)
|
2025-12-16 16:37:53 +00:00
|
|
|
r.Use(auth.NewSessionManager().LoadAndSave)
|
2025-11-03 12:38:47 +00:00
|
|
|
|
2026-01-07 15:08:29 +00:00
|
|
|
hr := hostrouter.New()
|
|
|
|
|
|
|
|
|
|
// Set up routing by hostname
|
2026-01-07 20:47:55 +00:00
|
|
|
sr := nidussync.Router()
|
2026-01-29 23:55:41 +00:00
|
|
|
hr.Map("", sr) // default
|
|
|
|
|
hr.Map("*", sr) // default
|
|
|
|
|
hr.Map(config.DomainRMO, rmo.Router()) // report.mosquitoes.online
|
2026-01-22 03:27:32 +00:00
|
|
|
hr.Map(config.DomainNidus, sr)
|
2026-01-07 15:08:29 +00:00
|
|
|
r.Mount("/", hr)
|
|
|
|
|
|
2026-02-13 21:15:09 +00:00
|
|
|
log.Debug().Str("report url", config.DomainRMO).Str("sync url", config.DomainNidus).Msg("Serving at URLs")
|
2026-01-07 20:47:55 +00:00
|
|
|
|
2026-01-27 18:44:02 +00:00
|
|
|
openai_logger := log.With().Logger()
|
|
|
|
|
err = llm.CreateOpenAIClient(ctx, &openai_logger)
|
2026-01-26 20:29:04 +00:00
|
|
|
if err != nil {
|
|
|
|
|
log.Error().Err(err).Msg("Failed to start openAI client")
|
2026-02-09 21:51:57 +00:00
|
|
|
os.Exit(8)
|
2026-01-26 20:29:04 +00:00
|
|
|
}
|
2026-01-07 15:08:29 +00:00
|
|
|
server := &http.Server{
|
2026-01-07 16:07:51 +00:00
|
|
|
Addr: config.Bind,
|
2026-01-07 15:08:29 +00:00
|
|
|
Handler: r,
|
|
|
|
|
}
|
|
|
|
|
go func() {
|
2026-01-07 16:07:51 +00:00
|
|
|
log.Info().Str("address", config.Bind).Msg("Serving HTTP requests")
|
2026-01-07 15:08:29 +00:00
|
|
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
|
|
|
log.Error().Str("err", err.Error()).Msg("HTTP Server Error")
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2026-03-13 17:33:39 +00:00
|
|
|
chan_envelope := make(chan platform.Envelope, 10)
|
|
|
|
|
platform.SetEventChannel(chan_envelope)
|
|
|
|
|
api.SetEventChannel(chan_envelope)
|
|
|
|
|
|
2026-01-07 15:08:29 +00:00
|
|
|
// 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 {
|
2026-03-19 21:20:20 +00:00
|
|
|
log.Error().Err(err).Msg("Error on HTTP server shutdown")
|
2026-01-07 15:08:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cancel()
|
2026-03-13 17:33:39 +00:00
|
|
|
close(chan_envelope)
|
2026-03-16 19:52:29 +00:00
|
|
|
platform.WaitForExit()
|
2026-01-07 15:08:29 +00:00
|
|
|
|
|
|
|
|
log.Info().Msg("Shutdown complete")
|
|
|
|
|
}
|
2025-11-24 19:45:37 +00:00
|
|
|
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")
|
2026-01-06 16:21:59 +00:00
|
|
|
fmt.Println("Stack:", string(debug.Stack()))
|
2025-11-24 19:45:37 +00:00
|
|
|
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().
|
2025-12-05 23:11:57 +00:00
|
|
|
//Str("type", "access").
|
2025-11-24 19:45:37 +00:00
|
|
|
Timestamp().
|
|
|
|
|
Fields(map[string]interface{}{
|
2025-12-05 23:11:57 +00:00
|
|
|
"remote_ip": remote_addr,
|
|
|
|
|
"url": r.URL.Path,
|
|
|
|
|
//"proto": r.Proto,
|
|
|
|
|
"method": r.Method,
|
|
|
|
|
//"user_agent": r.Header.Get("User-Agent"),
|
2025-11-24 19:45:37 +00:00
|
|
|
"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)
|
|
|
|
|
}
|
|
|
|
|
}
|