package router import ( "net/http" "time" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "gorm.io/gorm" "github.com/treytartt/mycrib-api/internal/config" "github.com/treytartt/mycrib-api/internal/handlers" "github.com/treytartt/mycrib-api/internal/middleware" "github.com/treytartt/mycrib-api/internal/push" "github.com/treytartt/mycrib-api/internal/repositories" "github.com/treytartt/mycrib-api/internal/services" "github.com/treytartt/mycrib-api/pkg/utils" ) const Version = "2.0.0" // Dependencies holds all dependencies needed by the router type Dependencies struct { DB *gorm.DB Cache *services.CacheService Config *config.Config EmailService *services.EmailService PushClient interface{} // *push.GorushClient - optional } // SetupRouter creates and configures the Gin router func SetupRouter(deps *Dependencies) *gin.Engine { cfg := deps.Config // Set Gin mode based on debug setting if cfg.Server.Debug { gin.SetMode(gin.DebugMode) } else { gin.SetMode(gin.ReleaseMode) } r := gin.New() // Global middleware r.Use(utils.GinRecovery()) r.Use(utils.GinLogger()) r.Use(corsMiddleware(cfg)) // Health check endpoint (no auth required) r.GET("/api/health/", healthCheck) // Initialize repositories userRepo := repositories.NewUserRepository(deps.DB) residenceRepo := repositories.NewResidenceRepository(deps.DB) taskRepo := repositories.NewTaskRepository(deps.DB) contractorRepo := repositories.NewContractorRepository(deps.DB) documentRepo := repositories.NewDocumentRepository(deps.DB) notificationRepo := repositories.NewNotificationRepository(deps.DB) subscriptionRepo := repositories.NewSubscriptionRepository(deps.DB) // Initialize push client (optional) var gorushClient *push.GorushClient if deps.PushClient != nil { if gc, ok := deps.PushClient.(*push.GorushClient); ok { gorushClient = gc } } // Initialize services authService := services.NewAuthService(userRepo, cfg) residenceService := services.NewResidenceService(residenceRepo, userRepo, cfg) taskService := services.NewTaskService(taskRepo, residenceRepo) contractorService := services.NewContractorService(contractorRepo, residenceRepo) documentService := services.NewDocumentService(documentRepo, residenceRepo) notificationService := services.NewNotificationService(notificationRepo, gorushClient) subscriptionService := services.NewSubscriptionService(subscriptionRepo, residenceRepo, taskRepo, contractorRepo, documentRepo) // Initialize middleware authMiddleware := middleware.NewAuthMiddleware(deps.DB, deps.Cache) // Initialize handlers authHandler := handlers.NewAuthHandler(authService, deps.EmailService, deps.Cache) residenceHandler := handlers.NewResidenceHandler(residenceService) taskHandler := handlers.NewTaskHandler(taskService) contractorHandler := handlers.NewContractorHandler(contractorService) documentHandler := handlers.NewDocumentHandler(documentService) notificationHandler := handlers.NewNotificationHandler(notificationService) subscriptionHandler := handlers.NewSubscriptionHandler(subscriptionService) // API group api := r.Group("/api") { // Public auth routes (no auth required) setupPublicAuthRoutes(api, authHandler) // Public data routes (no auth required) setupPublicDataRoutes(api, residenceHandler, taskHandler, contractorHandler) // Protected routes (auth required) protected := api.Group("") protected.Use(authMiddleware.TokenAuth()) { setupProtectedAuthRoutes(protected, authHandler) setupResidenceRoutes(protected, residenceHandler) setupTaskRoutes(protected, taskHandler) setupContractorRoutes(protected, contractorHandler) setupDocumentRoutes(protected, documentHandler) setupNotificationRoutes(protected, notificationHandler) setupSubscriptionRoutes(protected, subscriptionHandler) setupUserRoutes(protected) } } return r } // corsMiddleware configures CORS func corsMiddleware(cfg *config.Config) gin.HandlerFunc { corsConfig := cors.Config{ AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, MaxAge: 12 * time.Hour, } // In debug mode, allow all origins; otherwise use configured hosts if cfg.Server.Debug { corsConfig.AllowAllOrigins = true } else { corsConfig.AllowOrigins = cfg.Server.AllowedHosts } return cors.New(corsConfig) } // healthCheck returns API health status func healthCheck(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "healthy", "version": Version, "framework": "Gin", "timestamp": time.Now().UTC().Format(time.RFC3339), }) } // setupPublicAuthRoutes configures public authentication routes func setupPublicAuthRoutes(api *gin.RouterGroup, authHandler *handlers.AuthHandler) { auth := api.Group("/auth") { auth.POST("/login/", authHandler.Login) auth.POST("/register/", authHandler.Register) auth.POST("/forgot-password/", authHandler.ForgotPassword) auth.POST("/verify-reset-code/", authHandler.VerifyResetCode) auth.POST("/reset-password/", authHandler.ResetPassword) } } // setupProtectedAuthRoutes configures protected authentication routes func setupProtectedAuthRoutes(api *gin.RouterGroup, authHandler *handlers.AuthHandler) { auth := api.Group("/auth") { auth.POST("/logout/", authHandler.Logout) auth.GET("/me/", authHandler.CurrentUser) auth.PUT("/profile/", authHandler.UpdateProfile) auth.PATCH("/profile/", authHandler.UpdateProfile) auth.POST("/verify-email/", authHandler.VerifyEmail) auth.POST("/resend-verification/", authHandler.ResendVerification) } } // setupPublicDataRoutes configures public data routes (lookups, static data) func setupPublicDataRoutes(api *gin.RouterGroup, residenceHandler *handlers.ResidenceHandler, taskHandler *handlers.TaskHandler, contractorHandler *handlers.ContractorHandler) { // Static data routes (public, cached) staticData := api.Group("/static_data") { staticData.GET("/", placeholderHandler("get-static-data")) staticData.POST("/refresh/", placeholderHandler("refresh-static-data")) } // Lookup routes (public) api.GET("/residences/types/", residenceHandler.GetResidenceTypes) api.GET("/tasks/categories/", taskHandler.GetCategories) api.GET("/tasks/priorities/", taskHandler.GetPriorities) api.GET("/tasks/frequencies/", taskHandler.GetFrequencies) api.GET("/tasks/statuses/", taskHandler.GetStatuses) api.GET("/contractors/specialties/", contractorHandler.GetSpecialties) } // setupResidenceRoutes configures residence routes func setupResidenceRoutes(api *gin.RouterGroup, residenceHandler *handlers.ResidenceHandler) { residences := api.Group("/residences") { residences.GET("/", residenceHandler.ListResidences) residences.POST("/", residenceHandler.CreateResidence) residences.GET("/my-residences/", residenceHandler.GetMyResidences) residences.POST("/join-with-code/", residenceHandler.JoinWithCode) residences.GET("/:id/", residenceHandler.GetResidence) residences.PUT("/:id/", residenceHandler.UpdateResidence) residences.PATCH("/:id/", residenceHandler.UpdateResidence) residences.DELETE("/:id/", residenceHandler.DeleteResidence) residences.POST("/:id/generate-share-code/", residenceHandler.GenerateShareCode) residences.POST("/:id/generate-tasks-report/", placeholderHandler("generate-tasks-report")) residences.GET("/:id/users/", residenceHandler.GetResidenceUsers) residences.DELETE("/:id/users/:user_id/", residenceHandler.RemoveResidenceUser) } } // setupTaskRoutes configures task routes func setupTaskRoutes(api *gin.RouterGroup, taskHandler *handlers.TaskHandler) { tasks := api.Group("/tasks") { tasks.GET("/", taskHandler.ListTasks) tasks.POST("/", taskHandler.CreateTask) tasks.GET("/by-residence/:residence_id/", taskHandler.GetTasksByResidence) tasks.GET("/:id/", taskHandler.GetTask) tasks.PUT("/:id/", taskHandler.UpdateTask) tasks.PATCH("/:id/", taskHandler.UpdateTask) tasks.DELETE("/:id/", taskHandler.DeleteTask) tasks.POST("/:id/mark-in-progress/", taskHandler.MarkInProgress) tasks.POST("/:id/cancel/", taskHandler.CancelTask) tasks.POST("/:id/uncancel/", taskHandler.UncancelTask) tasks.POST("/:id/archive/", taskHandler.ArchiveTask) tasks.POST("/:id/unarchive/", taskHandler.UnarchiveTask) } // Task Completions completions := api.Group("/task-completions") { completions.GET("/", taskHandler.ListCompletions) completions.POST("/", taskHandler.CreateCompletion) completions.GET("/:id/", taskHandler.GetCompletion) completions.DELETE("/:id/", taskHandler.DeleteCompletion) } } // setupContractorRoutes configures contractor routes func setupContractorRoutes(api *gin.RouterGroup, contractorHandler *handlers.ContractorHandler) { contractors := api.Group("/contractors") { contractors.GET("/", contractorHandler.ListContractors) contractors.POST("/", contractorHandler.CreateContractor) contractors.GET("/:id/", contractorHandler.GetContractor) contractors.PUT("/:id/", contractorHandler.UpdateContractor) contractors.PATCH("/:id/", contractorHandler.UpdateContractor) contractors.DELETE("/:id/", contractorHandler.DeleteContractor) contractors.POST("/:id/toggle-favorite/", contractorHandler.ToggleFavorite) contractors.GET("/:id/tasks/", contractorHandler.GetContractorTasks) } } // setupDocumentRoutes configures document routes func setupDocumentRoutes(api *gin.RouterGroup, documentHandler *handlers.DocumentHandler) { documents := api.Group("/documents") { documents.GET("/", documentHandler.ListDocuments) documents.POST("/", documentHandler.CreateDocument) documents.GET("/warranties/", documentHandler.ListWarranties) documents.GET("/:id/", documentHandler.GetDocument) documents.PUT("/:id/", documentHandler.UpdateDocument) documents.PATCH("/:id/", documentHandler.UpdateDocument) documents.DELETE("/:id/", documentHandler.DeleteDocument) documents.POST("/:id/activate/", documentHandler.ActivateDocument) documents.POST("/:id/deactivate/", documentHandler.DeactivateDocument) } } // setupNotificationRoutes configures notification routes func setupNotificationRoutes(api *gin.RouterGroup, notificationHandler *handlers.NotificationHandler) { notifications := api.Group("/notifications") { notifications.GET("/", notificationHandler.ListNotifications) notifications.GET("/unread-count/", notificationHandler.GetUnreadCount) notifications.POST("/mark-all-read/", notificationHandler.MarkAllAsRead) notifications.POST("/:id/read/", notificationHandler.MarkAsRead) notifications.POST("/devices/", notificationHandler.RegisterDevice) notifications.GET("/devices/", notificationHandler.ListDevices) notifications.DELETE("/devices/:id/", notificationHandler.DeleteDevice) notifications.GET("/preferences/", notificationHandler.GetPreferences) notifications.PUT("/preferences/", notificationHandler.UpdatePreferences) notifications.PATCH("/preferences/", notificationHandler.UpdatePreferences) } } // setupSubscriptionRoutes configures subscription routes func setupSubscriptionRoutes(api *gin.RouterGroup, subscriptionHandler *handlers.SubscriptionHandler) { subscription := api.Group("/subscription") { subscription.GET("/", subscriptionHandler.GetSubscription) subscription.GET("/status/", subscriptionHandler.GetSubscriptionStatus) subscription.GET("/upgrade-trigger/:key/", subscriptionHandler.GetUpgradeTrigger) subscription.GET("/features/", subscriptionHandler.GetFeatureBenefits) subscription.GET("/promotions/", subscriptionHandler.GetPromotions) subscription.POST("/purchase/", subscriptionHandler.ProcessPurchase) subscription.POST("/cancel/", subscriptionHandler.CancelSubscription) subscription.POST("/restore/", subscriptionHandler.RestoreSubscription) } } // setupUserRoutes configures user routes func setupUserRoutes(api *gin.RouterGroup) { users := api.Group("/users") { users.GET("/", placeholderHandler("list-users")) users.GET("/:id/", placeholderHandler("get-user")) users.GET("/profiles/", placeholderHandler("list-profiles")) } } // placeholderHandler returns a handler that indicates an endpoint is not yet implemented func placeholderHandler(name string) gin.HandlerFunc { return func(c *gin.Context) { c.JSON(http.StatusNotImplemented, gin.H{ "error": "Endpoint not yet implemented", "endpoint": name, "message": "This endpoint is planned for future phases", }) } }