Files
honeyDueAPI/internal/router/router.go
Trey t 1f12f3f62a Initial commit: MyCrib API in Go
Complete rewrite of Django REST API to Go with:
- Gin web framework for HTTP routing
- GORM for database operations
- GoAdmin for admin panel
- Gorush integration for push notifications
- Redis for caching and job queues

Features implemented:
- User authentication (login, register, logout, password reset)
- Residence management (CRUD, sharing, share codes)
- Task management (CRUD, kanban board, completions)
- Contractor management (CRUD, specialties)
- Document management (CRUD, warranties)
- Notifications (preferences, push notifications)
- Subscription management (tiers, limits)

Infrastructure:
- Docker Compose for local development
- Database migrations and seed data
- Admin panel for data management

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 20:07:16 -06:00

326 lines
12 KiB
Go

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",
})
}
}