package main import ( "database/sql" "fmt" "log" "net/http" _ "github.com/lib/pq" "github.com/gorilla/mux" "aggios-app/backend/internal/api/handlers" "aggios-app/backend/internal/api/middleware" "aggios-app/backend/internal/config" "aggios-app/backend/internal/repository" "aggios-app/backend/internal/service" ) func initDB(cfg *config.Config) (*sql.DB, error) { connStr := fmt.Sprintf( "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable client_encoding=UTF8", cfg.Database.Host, cfg.Database.Port, cfg.Database.User, cfg.Database.Password, cfg.Database.Name, ) db, err := sql.Open("postgres", connStr) if err != nil { return nil, fmt.Errorf("erro ao abrir conexΓ£o: %v", err) } if err = db.Ping(); err != nil { return nil, fmt.Errorf("erro ao conectar ao banco: %v", err) } log.Println("βœ… Conectado ao PostgreSQL") return db, nil } func main() { // Load configuration cfg := config.Load() // Initialize database db, err := initDB(cfg) if err != nil { log.Fatalf("❌ Erro ao inicializar banco: %v", err) } defer db.Close() // Initialize repositories userRepo := repository.NewUserRepository(db) tenantRepo := repository.NewTenantRepository(db) companyRepo := repository.NewCompanyRepository(db) signupTemplateRepo := repository.NewSignupTemplateRepository(db) agencyTemplateRepo := repository.NewAgencyTemplateRepository(db) planRepo := repository.NewPlanRepository(db) subscriptionRepo := repository.NewSubscriptionRepository(db) crmRepo := repository.NewCRMRepository(db) solutionRepo := repository.NewSolutionRepository(db) erpRepo := repository.NewERPRepository(db) docRepo := repository.NewDocumentRepository(db) // Initialize services authService := service.NewAuthService(userRepo, tenantRepo, crmRepo, cfg) agencyService := service.NewAgencyService(userRepo, tenantRepo, cfg, db) tenantService := service.NewTenantService(tenantRepo, db) companyService := service.NewCompanyService(companyRepo) planService := service.NewPlanService(planRepo, subscriptionRepo) // Initialize handlers healthHandler := handlers.NewHealthHandler() authHandler := handlers.NewAuthHandler(authService) agencyProfileHandler := handlers.NewAgencyHandler(tenantRepo, cfg) agencyHandler := handlers.NewAgencyRegistrationHandler(agencyService, cfg) collaboratorHandler := handlers.NewCollaboratorHandler(userRepo, agencyService) tenantHandler := handlers.NewTenantHandler(tenantService) companyHandler := handlers.NewCompanyHandler(companyService) planHandler := handlers.NewPlanHandler(planService) crmHandler := handlers.NewCRMHandler(crmRepo) solutionHandler := handlers.NewSolutionHandler(solutionRepo) signupTemplateHandler := handlers.NewSignupTemplateHandler(signupTemplateRepo, userRepo, tenantRepo, agencyService) agencyTemplateHandler := handlers.NewAgencyTemplateHandler(agencyTemplateRepo, agencyService, userRepo, tenantRepo) filesHandler := handlers.NewFilesHandler(cfg) customerPortalHandler := handlers.NewCustomerPortalHandler(crmRepo, authService, cfg) erpHandler := handlers.NewERPHandler(erpRepo) docHandler := handlers.NewDocumentHandler(docRepo) // Initialize upload handler uploadHandler, err := handlers.NewUploadHandler(cfg) if err != nil { log.Fatalf("❌ Erro ao inicializar upload handler: %v", err) } // Initialize backup handler backupHandler := handlers.NewBackupHandler() // Create middleware chain tenantDetector := middleware.TenantDetector(tenantRepo) corsMiddleware := middleware.CORS(cfg) securityMiddleware := middleware.SecurityHeaders rateLimitMiddleware := middleware.RateLimit(cfg) authMiddleware := middleware.Auth(cfg) // Setup routes router := mux.NewRouter() // Serve static files (uploads) fs := http.FileServer(http.Dir("./uploads")) router.PathPrefix("/uploads/").Handler(http.StripPrefix("/uploads", fs)) // ==================== PUBLIC ROUTES ==================== // Health check router.HandleFunc("/health", healthHandler.Check) router.HandleFunc("/api/health", healthHandler.Check) // Auth router.HandleFunc("/api/auth/login", authHandler.UnifiedLogin) // Nova rota unificada router.HandleFunc("/api/auth/login/legacy", authHandler.Login) // Antiga rota (deprecada) router.HandleFunc("/api/auth/register", agencyHandler.PublicRegister).Methods("POST") // Public agency template registration (for creating new agencies) router.HandleFunc("/api/agency-templates", agencyTemplateHandler.GetTemplateBySlug).Methods("GET") router.HandleFunc("/api/agency-signup/register", agencyTemplateHandler.PublicRegisterAgency).Methods("POST") // Public client signup via templates router.HandleFunc("/api/signup-templates/slug/{slug}", signupTemplateHandler.GetTemplateBySlug).Methods("GET") router.HandleFunc("/api/signup/register", signupTemplateHandler.PublicRegister).Methods("POST") // Public plans (for signup flow) router.HandleFunc("/api/plans", planHandler.ListActivePlans).Methods("GET") router.HandleFunc("/api/plans/{id}", planHandler.GetActivePlan).Methods("GET") // File upload (public for signup, will also work with auth) router.HandleFunc("/api/upload", uploadHandler.Upload).Methods("POST") // Tenant check (public) router.HandleFunc("/api/tenant/check", tenantHandler.CheckExists).Methods("GET") router.HandleFunc("/api/tenant/config", tenantHandler.GetPublicConfig).Methods("GET") router.HandleFunc("/api/tenants/{id}/profile", tenantHandler.GetProfile).Methods("GET") // Tenant branding (protected - used by both agency and customer portal) router.Handle("/api/tenant/branding", middleware.RequireAnyAuthenticated(cfg)(http.HandlerFunc(tenantHandler.GetBranding))).Methods("GET") // Public customer registration (for agency portal signup) router.HandleFunc("/api/public/customers/register", crmHandler.PublicRegisterCustomer).Methods("POST") // Hash generator (dev only - remove in production) router.HandleFunc("/api/hash", handlers.GenerateHash).Methods("POST") // ==================== PROTECTED ROUTES ==================== // Auth (protected) router.Handle("/api/auth/change-password", authMiddleware(http.HandlerFunc(authHandler.ChangePassword))).Methods("POST") // SUPERADMIN: Agency management router.HandleFunc("/api/admin/agencies/register", agencyHandler.RegisterAgency).Methods("POST") router.HandleFunc("/api/admin/agencies", tenantHandler.ListAll).Methods("GET") router.HandleFunc("/api/admin/agencies/{id}", agencyHandler.HandleAgency).Methods("GET", "PATCH", "DELETE") // SUPERADMIN: Backup & Restore router.Handle("/api/superadmin/backups", authMiddleware(http.HandlerFunc(backupHandler.ListBackups))).Methods("GET") router.Handle("/api/superadmin/backup/create", authMiddleware(http.HandlerFunc(backupHandler.CreateBackup))).Methods("POST") router.Handle("/api/superadmin/backup/restore", authMiddleware(http.HandlerFunc(backupHandler.RestoreBackup))).Methods("POST") router.Handle("/api/superadmin/backup/download/{filename}", authMiddleware(http.HandlerFunc(backupHandler.DownloadBackup))).Methods("GET") // SUPERADMIN: Agency template management router.Handle("/api/admin/agency-templates", authMiddleware(http.HandlerFunc(agencyTemplateHandler.ListTemplates))).Methods("GET") router.Handle("/api/admin/agency-templates", authMiddleware(http.HandlerFunc(agencyTemplateHandler.CreateTemplate))).Methods("POST") // SUPERADMIN: Client signup template management router.Handle("/api/admin/signup-templates", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { signupTemplateHandler.ListTemplates(w, r) } else if r.Method == http.MethodPost { signupTemplateHandler.CreateTemplate(w, r) } }))).Methods("GET", "POST") router.Handle("/api/admin/signup-templates/{id}", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: signupTemplateHandler.GetTemplateByID(w, r) case http.MethodPut, http.MethodPatch: signupTemplateHandler.UpdateTemplate(w, r) case http.MethodDelete: signupTemplateHandler.DeleteTemplate(w, r) } }))).Methods("GET", "PUT", "PATCH", "DELETE") // SUPERADMIN: Plans management planHandler.RegisterRoutes(router) // SUPERADMIN: Solutions management router.Handle("/api/admin/solutions", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: solutionHandler.GetAllSolutions(w, r) case http.MethodPost: solutionHandler.CreateSolution(w, r) } }))).Methods("GET", "POST") router.Handle("/api/admin/solutions/{id}", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: solutionHandler.GetSolution(w, r) case http.MethodPut, http.MethodPatch: solutionHandler.UpdateSolution(w, r) case http.MethodDelete: solutionHandler.DeleteSolution(w, r) } }))).Methods("GET", "PUT", "PATCH", "DELETE") // SUPERADMIN: Plan <-> Solutions router.Handle("/api/admin/plans/{plan_id}/solutions", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: solutionHandler.GetPlanSolutions(w, r) case http.MethodPut: solutionHandler.SetPlanSolutions(w, r) } }))).Methods("GET", "PUT") // ADMIN_AGENCIA: Client registration router.Handle("/api/agencies/clients/register", authMiddleware(http.HandlerFunc(agencyHandler.RegisterClient))).Methods("POST") // Agency profile routes (protected) router.Handle("/api/agency/profile", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: agencyProfileHandler.GetProfile(w, r) case http.MethodPut, http.MethodPatch: agencyProfileHandler.UpdateProfile(w, r) } }))).Methods("GET", "PUT", "PATCH") // Agency logo upload (protected) router.Handle("/api/agency/logo", authMiddleware(http.HandlerFunc(agencyProfileHandler.UploadLogo))).Methods("POST") // File serving route (public - serves files from MinIO through API) router.PathPrefix("/api/files/{bucket}/").HandlerFunc(filesHandler.ServeFile).Methods("GET") // Company routes (protected) router.Handle("/api/companies", authMiddleware(http.HandlerFunc(companyHandler.List))).Methods("GET") router.Handle("/api/companies/create", authMiddleware(http.HandlerFunc(companyHandler.Create))).Methods("POST") // ==================== CRM ROUTES (TENANT) ==================== // Tenant solutions (which solutions the tenant has access to) router.Handle("/api/tenant/solutions", authMiddleware(http.HandlerFunc(solutionHandler.GetTenantSolutions))).Methods("GET") // Dashboard router.Handle("/api/crm/dashboard", authMiddleware(http.HandlerFunc(crmHandler.GetDashboard))).Methods("GET") // Customers router.Handle("/api/crm/customers", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: crmHandler.GetCustomers(w, r) case http.MethodPost: crmHandler.CreateCustomer(w, r) } }))).Methods("GET", "POST") router.Handle("/api/crm/customers/{id}", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: crmHandler.GetCustomer(w, r) case http.MethodPut, http.MethodPatch: crmHandler.UpdateCustomer(w, r) case http.MethodDelete: crmHandler.DeleteCustomer(w, r) } }))).Methods("GET", "PUT", "PATCH", "DELETE") // Lists router.Handle("/api/crm/lists", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: crmHandler.GetLists(w, r) case http.MethodPost: crmHandler.CreateList(w, r) } }))).Methods("GET", "POST") router.Handle("/api/crm/lists/{id}", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: crmHandler.GetList(w, r) case http.MethodPut, http.MethodPatch: crmHandler.UpdateList(w, r) case http.MethodDelete: crmHandler.DeleteList(w, r) } }))).Methods("GET", "PUT", "PATCH", "DELETE") router.Handle("/api/crm/lists/{id}/leads", authMiddleware(http.HandlerFunc(crmHandler.GetLeadsByList))).Methods("GET") // Customer <-> List relationship router.Handle("/api/crm/customers/{customer_id}/lists/{list_id}", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPost: crmHandler.AddCustomerToList(w, r) case http.MethodDelete: crmHandler.RemoveCustomerFromList(w, r) } }))).Methods("POST", "DELETE") // Leads router.Handle("/api/crm/leads", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: crmHandler.GetLeads(w, r) case http.MethodPost: crmHandler.CreateLead(w, r) } }))).Methods("GET", "POST") router.Handle("/api/crm/leads/export", authMiddleware(http.HandlerFunc(crmHandler.ExportLeads))).Methods("GET") router.Handle("/api/crm/leads/import", authMiddleware(http.HandlerFunc(crmHandler.ImportLeads))).Methods("POST") router.Handle("/api/crm/leads/{leadId}/stage", authMiddleware(http.HandlerFunc(crmHandler.UpdateLeadStage))).Methods("PUT") router.Handle("/api/crm/leads/{id}", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: crmHandler.GetLead(w, r) case http.MethodPut, http.MethodPatch: crmHandler.UpdateLead(w, r) case http.MethodDelete: crmHandler.DeleteLead(w, r) } }))).Methods("GET", "PUT", "PATCH", "DELETE") // Funnels & Stages router.Handle("/api/crm/funnels", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: crmHandler.ListFunnels(w, r) case http.MethodPost: crmHandler.CreateFunnel(w, r) } }))).Methods("GET", "POST") router.Handle("/api/crm/funnels/{id}", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: crmHandler.GetFunnel(w, r) case http.MethodPut: crmHandler.UpdateFunnel(w, r) case http.MethodDelete: crmHandler.DeleteFunnel(w, r) } }))).Methods("GET", "PUT", "DELETE") router.Handle("/api/crm/funnels/{funnelId}/stages", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: crmHandler.ListStages(w, r) case http.MethodPost: crmHandler.CreateStage(w, r) } }))).Methods("GET", "POST") router.Handle("/api/crm/stages/{id}", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPut: crmHandler.UpdateStage(w, r) case http.MethodDelete: crmHandler.DeleteStage(w, r) } }))).Methods("PUT", "DELETE") // Lead ingest (integrations) router.Handle("/api/crm/leads/ingest", authMiddleware(http.HandlerFunc(crmHandler.IngestLead))).Methods("POST") // Share tokens (generate) router.Handle("/api/crm/customers/share-token", authMiddleware(http.HandlerFunc(crmHandler.GenerateShareToken))).Methods("POST") // Share data (public endpoint - no auth required) router.HandleFunc("/api/crm/share/{token}", crmHandler.GetSharedData).Methods("GET") // ==================== CUSTOMER PORTAL ==================== // Customer portal login (public endpoint) router.HandleFunc("/api/portal/login", customerPortalHandler.Login).Methods("POST") // Customer portal dashboard (requires customer auth) router.Handle("/api/portal/dashboard", middleware.RequireCustomer(cfg)(http.HandlerFunc(customerPortalHandler.GetPortalDashboard))).Methods("GET") // Customer portal leads (requires customer auth) router.Handle("/api/portal/leads", middleware.RequireCustomer(cfg)(http.HandlerFunc(customerPortalHandler.GetPortalLeads))).Methods("GET") // Customer portal lists (requires customer auth) router.Handle("/api/portal/lists", middleware.RequireCustomer(cfg)(http.HandlerFunc(customerPortalHandler.GetPortalLists))).Methods("GET") // Customer portal profile (requires customer auth) router.Handle("/api/portal/profile", middleware.RequireCustomer(cfg)(http.HandlerFunc(customerPortalHandler.GetPortalProfile))).Methods("GET") // Customer portal change password (requires customer auth) router.Handle("/api/portal/change-password", middleware.RequireCustomer(cfg)(http.HandlerFunc(customerPortalHandler.ChangePassword))).Methods("POST") // Customer portal logo upload (requires customer auth) router.Handle("/api/portal/logo", middleware.RequireCustomer(cfg)(http.HandlerFunc(customerPortalHandler.UploadLogo))).Methods("POST") // ==================== AGENCY COLLABORATORS ==================== // List collaborators (requires agency auth, owner only) router.Handle("/api/agency/collaborators", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(collaboratorHandler.ListCollaborators))).Methods("GET") // Invite collaborator (requires agency auth, owner only) router.Handle("/api/agency/collaborators/invite", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(collaboratorHandler.InviteCollaborator))).Methods("POST") // Remove collaborator (requires agency auth, owner only) router.Handle("/api/agency/collaborators/{id}", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(collaboratorHandler.RemoveCollaborator))).Methods("DELETE") // Generate customer portal access (agency staff) router.Handle("/api/crm/customers/{id}/portal-access", authMiddleware(http.HandlerFunc(crmHandler.GenerateCustomerPortalAccess))).Methods("POST") // Lead <-> List relationship router.Handle("/api/crm/leads/{lead_id}/lists/{list_id}", authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPost: crmHandler.AddLeadToList(w, r) case http.MethodDelete: crmHandler.RemoveLeadFromList(w, r) } }))).Methods("POST", "DELETE") // ==================== ERP ROUTES (TENANT) ==================== // Finance router.Handle("/api/erp/finance/categories", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: erpHandler.GetFinancialCategories(w, r) case http.MethodPost: erpHandler.CreateFinancialCategory(w, r) } }))).Methods("GET", "POST") router.Handle("/api/erp/finance/accounts", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: erpHandler.GetBankAccounts(w, r) case http.MethodPost: erpHandler.CreateBankAccount(w, r) } }))).Methods("GET", "POST") router.Handle("/api/erp/finance/accounts/{id}", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPut: erpHandler.UpdateBankAccount(w, r) case http.MethodDelete: erpHandler.DeleteBankAccount(w, r) } }))).Methods("PUT", "DELETE") router.Handle("/api/erp/finance/transactions", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: erpHandler.GetTransactions(w, r) case http.MethodPost: erpHandler.CreateTransaction(w, r) } }))).Methods("GET", "POST") router.Handle("/api/erp/finance/transactions/{id}", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPut: erpHandler.UpdateTransaction(w, r) case http.MethodDelete: erpHandler.DeleteTransaction(w, r) } }))).Methods("PUT", "DELETE") // Products router.Handle("/api/erp/products", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: erpHandler.GetProducts(w, r) case http.MethodPost: erpHandler.CreateProduct(w, r) } }))).Methods("GET", "POST") router.Handle("/api/erp/products/{id}", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPut: erpHandler.UpdateProduct(w, r) case http.MethodDelete: erpHandler.DeleteProduct(w, r) } }))).Methods("PUT", "DELETE") // Orders router.Handle("/api/erp/orders", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: erpHandler.GetOrders(w, r) case http.MethodPost: erpHandler.CreateOrder(w, r) } }))).Methods("GET", "POST") router.Handle("/api/erp/orders/{id}", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodDelete: erpHandler.DeleteOrder(w, r) } }))).Methods("DELETE") // Entities router.Handle("/api/erp/entities", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: erpHandler.GetEntities(w, r) case http.MethodPost: erpHandler.CreateEntity(w, r) } }))).Methods("GET", "POST") router.Handle("/api/erp/entities/{id}", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPut, http.MethodPatch: erpHandler.UpdateEntity(w, r) case http.MethodDelete: erpHandler.DeleteEntity(w, r) } }))).Methods("PUT", "PATCH", "DELETE") // Documents router.Handle("/api/documents", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: docHandler.List(w, r) case http.MethodPost: docHandler.Create(w, r) } }))).Methods("GET", "POST") router.Handle("/api/documents/{id}", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: docHandler.Get(w, r) case http.MethodPut: docHandler.Update(w, r) case http.MethodDelete: docHandler.Delete(w, r) } }))).Methods("GET", "PUT", "DELETE") router.Handle("/api/documents/{id}/subpages", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(docHandler.GetSubpages))).Methods("GET") router.Handle("/api/documents/{id}/activities", middleware.RequireAgencyUser(cfg)(http.HandlerFunc(docHandler.GetActivities))).Methods("GET") // Apply global middlewares: tenant -> cors -> security -> rateLimit -> router handler := tenantDetector(corsMiddleware(securityMiddleware(rateLimitMiddleware(router)))) // Start server addr := fmt.Sprintf(":%s", cfg.Server.Port) log.Printf("πŸš€ Server starting on %s", addr) log.Printf("πŸ“ Health check: http://localhost:%s/health", cfg.Server.Port) log.Printf("πŸ”— API: http://localhost:%s/api/health", cfg.Server.Port) log.Printf("🏒 Register Agency (SUPERADMIN): http://localhost:%s/api/admin/agencies/register", cfg.Server.Port) log.Printf("πŸ” Login: http://localhost:%s/api/auth/login", cfg.Server.Port) if err := http.ListenAndServe(addr, handler); err != nil { log.Fatalf("❌ Server error: %v", err) } }