package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" "github.com/swaggo/http-swagger/v2" _ "git.kundeng.us/phoenix/textsender-api/docs" "git.kundeng.us/phoenix/textsender-api/internal/app" "git.kundeng.us/phoenix/textsender-api/internal/config" database "git.kundeng.us/phoenix/textsender-api/internal/db" "git.kundeng.us/phoenix/textsender-api/internal/handler" "git.kundeng.us/phoenix/textsender-api/internal/handler/endpoint" mdlware "git.kundeng.us/phoenix/textsender-api/internal/middleware" "git.kundeng.us/phoenix/textsender-api/internal/services" "git.kundeng.us/phoenix/textsender-api/internal/store" ) // @title textsender-api // @version 1.0 // @description Core API to send text messages // @host localhost:8080 // @BasePath /api/v1 // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization // @description JWT Bearer Token func main() { cfg, tCfg, err := config.Load() if err != nil { log.Fatal("Error:", err) } else if cfg == nil { log.Fatal("Error initializing config") } else if cfg.JWTSecret == "" { log.Fatal("Error: JWTSecret not initialized") } apiApp := app.App{TwilioConfig: tCfg} db, err := database.NewDatabase(cfg.GetDBConnString()) if err != nil { log.Fatal("Failed to connect to database:", err) } defer db.Close() ctx := context.Background() if cfg.ResetDB { log.Println("Resetting database") if err := db.ResetDatabase(ctx); err != nil { log.Fatal("Failed to reset database:", err) } log.Println("Database reset completed. Exiting.") return } else { if exists, err := database.TableExists(ctx, db.Pool, "contacts"); err == nil && !exists { log.Println("Resetting database") if err = db.ResetDatabase(ctx); err != nil { log.Fatal("Error:", err) } } else { log.Fatal("Error:", err) } } jwtService := services.NewJWTService(cfg.JWTSecret) contactStore := store.NewContactStore(db.Pool) messageStore := store.NewMessageStore(db.Pool) schStore := store.NewScheduledMessageStore(db.Pool) schMsgEventStore := store.NewScheduledMessageEventStore(db.Pool) merStore := store.NewMessageEventResponseStore(db.Pool) contactHandler := handler.NewContactHandler(&apiApp, contactStore) messageHandler := handler.NewMessageHandler(&apiApp, messageStore) scheduledMessageHandler := handler.NewScheduledMessageHandler(&apiApp, schStore) scheduledMessageEventHandler := handler.NewScheduledMessageEventHandler(&apiApp, schMsgEventStore, schStore) scheduledMessageStatusHandler := handler.NewScheduledMessageStatusHandler(&apiApp, schMsgEventStore, schStore) eventHandler := handler.NewEventResponseHandler(&apiApp, merStore) instantMessageHandler := handler.NewSendInstantMessageHandler(&apiApp, merStore, contactStore, messageStore) router := chi.NewRouter() // Configure CORS router.Use(cors.Handler(cors.Options{ AllowedOrigins: cfg.AllowedOrigins, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}, AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, ExposedHeaders: []string{"Link", "X-Total-Count"}, AllowCredentials: true, MaxAge: 300, // 5 minutes })) router.Use(middleware.Logger) router.Use(middleware.Recoverer) router.Use(middleware.Timeout(60 * time.Second)) router.Use(mdlware.JSONContentType) router.Method("POST", endpoint.ADD_CONTACT_ENDPOINT, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(contactHandler.AddContact))) router.Method("GET", endpoint.GET_CONTACT, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(contactHandler.GetContact))) router.Method("POST", endpoint.ADD_MESSAGE, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(messageHandler.AddMessage))) router.Method("GET", endpoint.GET_MESSAGE, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(messageHandler.GetMessage))) router.Method("POST", endpoint.ScheduleMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.AddScheduledMessage))) router.Method("POST", endpoint.AddEventToScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageEventHandler.AddScheduledMessageEvent))) router.Method("GET", endpoint.GetScheduledMessageEventEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageEventHandler.GetScheduledMessageEvent))) router.Method("DELETE", endpoint.DeleteScheduledMessageEventEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageEventHandler.DeleteScheduledMessageEvent))) router.Method("PATCH", endpoint.UpdateScheduledMessageStatusEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageStatusHandler.UpdateStatus))) router.Method("GET", endpoint.GetScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.GetScheduledMessage))) router.Method("GET", endpoint.FetchNextScheduledMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(scheduledMessageHandler.FetchNextMessage))) router.Method("POST", endpoint.RecordEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.RecordResponse))) router.Method("GET", endpoint.FetchMessageEventResponse, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(eventHandler.Fetch))) router.Method("POST", endpoint.SendInstantMessageEndpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(instantMessageHandler.Send))) router.Method("PATCH", endpoint.Update_Names_Endpoint, mdlware.AuthMiddleware(jwtService)(http.HandlerFunc(contactHandler.UpdateName))) router.Method("GET", "/swagger/*", httpSwagger.Handler( httpSwagger.URL(fmt.Sprintf("http://localhost:%s/swagger/doc.json", config.PORT)), )) // Start server server := &http.Server{ Addr: ":" + cfg.ServerPort, Handler: router, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } // Graceful shutdown go func() { log.Printf("Server starting on port %s", cfg.ServerPort) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Server failed to start: %v", err) } }() // Wait for interrupt signal quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := server.Shutdown(ctx); err != nil { log.Fatalf("Server forced to shutdown: %v", err) } log.Println("Server exited") }