package main import ( "fmt" "log" "net/http" "strconv" "go-crm/config" "go-crm/internal/db" "go-crm/internal/handlers" "go-crm/internal/templates" wa "go-crm/internal/whatsapp" "go-crm/pkg/repo" "go-crm/pkg/usecase" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" ) func main() { fmt.Println("main: starting db init") database, err := db.Init(config.DatabasePath()) fmt.Println("main: db init returned") if err != nil { log.Fatalf("Failed to initialize database: %v", err) } defer database.Close() fmt.Println("main: initializing WhatsApp adapter") waConnector, err := wa.NewWhatsmeowAdapter(config.WhatsAppStorePath(), config.HTTPServerEndpoint(), config.InternalSecret()) fmt.Println("main: WhatsApp adapter init returned") if err != nil { log.Printf("Warning: failed to initialize WhatsApp connector: %v", err) waConnector = nil } else { waConnector.SetClientDB(database) } // App is the single dependency-injection container. // All handler methods receive DB and WAConnector through it, // eliminating package-level global state. // Build our clean-arch layers for leads leadRepo := repo.NewSQLiteLeadRepository(database) leadSvc := usecase.NewLeadService(leadRepo) app := handlers.NewApp(database, leadSvc, waConnector) // Also populate legacy globals so existing tests continue to work. handlers.SetupHandlers(database, waConnector) r := chi.NewRouter() r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Use(middleware.RequestID) r.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}, AllowedHeaders: []string{"Accept", "Content-Type", "X-Internal-Secret", "Origin"}, ExposedHeaders: []string{"Content-Length", "Content-Type"}, AllowCredentials: false, MaxAge: 86400, })) templates.Init() r.Get("/", app.Dashboard) r.Route("/auth", func(r chi.Router) { r.Get("/signup", app.SignupPage) r.Post("/signup", app.Signup) r.Get("/login", app.LoginPage) r.Post("/login", app.Login) r.Post("/logout", app.Logout) r.Get("/account", app.AccountPage) r.Post("/account", app.UpdateAccount) }) r.Route("/clients", func(r chi.Router) { r.Get("/", app.ListClients) r.Post("/", app.CreateClient) r.Get("/{id}", app.ViewClient) r.Put("/{id}", app.UpdateClient) r.Delete("/{id}", app.DeleteClient) }) r.Route("/customers", func(r chi.Router) { r.Get("/", app.ListCustomers) r.Post("/", app.CreateCustomer) r.Get("/{id}", app.ViewCustomer) r.Put("/{id}", app.UpdateCustomer) r.Delete("/{id}", app.DeleteCustomer) }) r.Get("/debug/cors-test", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Write([]byte(`{"status":"ok","origin":"` + r.Header.Get("Origin") + `"}`)) }) r.Get("/debug/whatsapp", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") if waConnector == nil { w.Write([]byte(`{"status":"error","error":"WhatsApp connector not initialized"}`)) return } w.Write([]byte(`{"status":"ok","message":"WhatsApp connector initialized"}`)) }) r.Get("/debug/net-test", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") resp, err := http.Get("https://www.google.com") if err != nil { w.Write([]byte(`{"status":"error","error":` + err.Error() + `}`)) return } defer resp.Body.Close() w.Write([]byte(`{"status":"ok","code":` + strconv.Itoa(resp.StatusCode) + `}`)) }) r.Route("/leads", func(r chi.Router) { r.Get("/", app.ListLeads) r.Get("/connect", app.LeadsConnectPage) r.Get("/qr", app.LeadsQR) r.Get("/verify/{client_id}", app.VerifyLead) r.Put("/{id}", app.UpdateLead) r.Delete("/{id}", app.DeleteLead) // Lead pipeline r.Post("/ingest", app.IngestLead) r.Get("/review", app.LeadReviewQueue) r.Put("/{id}/review", app.ConfirmLeadReview) r.Get("/all", app.LeadAllList) r.Delete("/{id}", app.DeleteLeadNew) // Keyword management r.Get("/keywords", app.LeadKeywordsPage) r.Post("/keywords", app.AddServiceKeyword) r.Delete("/keywords/{id}", app.DeleteServiceKeywordHandler) // Status management r.Post("/statuses", app.AddLeadStatusHandler) r.Delete("/statuses/{id}", app.DeleteLeadStatusHandler) }) r.Get("/report", app.MonthlyReport) r.Route("/services", func(r chi.Router) { r.Get("/", app.ListServices) r.Post("/", app.CreateService) r.Get("/{id}", app.ViewService) r.Put("/{id}", app.UpdateService) r.Delete("/{id}", app.DeleteService) }) r.Route("/scheduling", func(r chi.Router) { r.Get("/", app.ListSchedules) r.Post("/", app.CreateSchedule) r.Get("/{id}", app.ViewSchedule) r.Put("/{id}", app.UpdateSchedule) r.Delete("/{id}", app.DeleteSchedule) }) r.Route("/payments", func(r chi.Router) { r.Get("/", app.ListPayments) r.Post("/", app.CreatePayment) r.Get("/{id}", app.ViewPayment) r.Put("/{id}", app.UpdatePayment) r.Delete("/{id}", app.DeletePayment) }) r.Route("/questions", func(r chi.Router) { r.Get("/", app.ListQuestions) r.Post("/", app.CreateQuestion) r.Get("/{id}", app.ViewQuestion) r.Put("/{id}", app.UpdateQuestion) r.Delete("/{id}", app.DeleteQuestion) }) r.Route("/answers", func(r chi.Router) { r.Get("/", app.ListAnswers) r.Post("/", app.CreateAnswer) r.Get("/{id}", app.ViewAnswer) r.Put("/{id}", app.UpdateAnswer) r.Delete("/{id}", app.DeleteAnswer) }) fmt.Println("main: ready to serve") fmt.Println("CRM server running on http://localhost:8080") log.Fatal(http.ListenAndServe(":8080", r)) }