package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" "git.piskot.si/SeminarM2/lambdaiot-core/internal/config" "git.piskot.si/SeminarM2/lambdaiot-core/internal/handler" "git.piskot.si/SeminarM2/lambdaiot-core/internal/middleware" mqttclient "git.piskot.si/SeminarM2/lambdaiot-core/internal/mqtt" ) func main() { // load configuration (look for ./config.toml by default) cfg, err := config.Load("") if err != nil { log.Fatalf("failed to load config: %v", err) } // determine address addr := cfg.Server.Address if addr == "" { addr = ":8080" } if cfg.Server.Port != 0 { addr = fmt.Sprintf(":%d", cfg.Server.Port) } // connect to MQTT broker (best-effort) var mq *mqttclient.Client if cfg.MQTT.Broker != "" { mqc, err := mqttclient.Connect(cfg.MQTT) if err != nil { log.Printf("warning: mqtt connect failed: %v", err) } else { mq = mqc mqttclient.SetDefault(mqc) // subscribe to the configured topic and log incoming messages if cfg.MQTT.Topic != "" { if err := mq.Subscribe(cfg.MQTT.Topic, func(t string, p []byte) { log.Printf("mqtt recv on %s: %s", t, string(p)) }); err != nil { log.Printf("warning: mqtt subscribe failed: %v", err) } } // publish a startup message (non-blocking) go func() { msg := fmt.Sprintf("lambdaiot-core started on %s", addr) if err := mq.Publish(cfg.MQTT.Topic, []byte(msg)); err != nil { log.Printf("mqtt publish failed: %v", err) } }() } } // mqttping endpoint will be added after router initialization // Gin setup r := gin.New() r.Use(gin.Recovery()) r.Use(middleware.GinLogger()) // Public routes r.GET("/health", handler.Health) r.GET("/hello", handler.Hello) r.POST("/login", handler.Login) // mqttping endpoint: publish current timestamp to MQTT topic r.POST("/mqttping", func(c *gin.Context) { ts := time.Now().Format(time.RFC3339) if mq == nil { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "mqtt not connected"}) return } if err := mq.Publish(cfg.MQTT.Topic, []byte(ts)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"timestamp": ts}) }) // Protected routes auth := r.Group("/") auth.Use(middleware.AuthMiddleware(cfg.Server.JWTSecret)) auth.GET("/protected", handler.Protected) srv := &http.Server{ Addr: addr, Handler: r, } go func() { log.Printf("starting server on %s", addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %v", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt, syscall.SIGTERM) <-quit ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatalf("server forced to shutdown: %v", err) } if mq != nil { mq.Close() } log.Println("server exiting") } func getJWTSecret() string { if s := os.Getenv("JWT_SECRET"); s != "" { return s } return "secret" }