Files
honeyDueAPI/internal/admin/routes.go
Trey t 4976eafc6c Rebrand from Casera/MyCrib to honeyDue
Total rebrand across all Go API source files:
- Go module path: casera-api -> honeydue-api
- All imports updated (130+ files)
- Docker: containers, images, networks renamed
- Email templates: support email, noreply, icon URL
- Domains: casera.app/mycrib.treytartt.com -> honeyDue.treytartt.com
- Bundle IDs: com.tt.casera -> com.tt.honeyDue
- IAP product IDs updated
- Landing page, admin panel, config defaults
- Seeds, CI workflows, Makefile, docs
- Database table names preserved (no migration needed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 06:33:38 -06:00

483 lines
18 KiB
Go

package admin
import (
"net/http"
"net/http/httputil"
"net/url"
"os"
"github.com/labstack/echo/v4"
"gorm.io/gorm"
"github.com/treytartt/honeydue-api/internal/admin/handlers"
"github.com/treytartt/honeydue-api/internal/config"
"github.com/treytartt/honeydue-api/internal/middleware"
"github.com/treytartt/honeydue-api/internal/monitoring"
"github.com/treytartt/honeydue-api/internal/push"
"github.com/treytartt/honeydue-api/internal/repositories"
"github.com/treytartt/honeydue-api/internal/services"
)
// Dependencies holds optional services for admin routes
type Dependencies struct {
EmailService *services.EmailService
PushClient *push.Client
OnboardingService *services.OnboardingEmailService
MonitoringHandler *monitoring.Handler
}
// SetupRoutes configures all admin routes
func SetupRoutes(router *echo.Echo, db *gorm.DB, cfg *config.Config, deps *Dependencies) {
// Create repositories
adminRepo := repositories.NewAdminRepository(db)
// Create handlers
authHandler := handlers.NewAdminAuthHandler(adminRepo, cfg)
// Admin API group
admin := router.Group("/api/admin")
{
// Public auth routes (no auth required)
auth := admin.Group("/auth")
{
auth.POST("/login", authHandler.Login)
}
// Protected routes (require admin JWT)
protected := admin.Group("")
protected.Use(middleware.AdminAuthMiddleware(cfg, adminRepo))
{
// Auth routes that require authentication
protectedAuth := protected.Group("/auth")
{
protectedAuth.POST("/logout", authHandler.Logout)
protectedAuth.GET("/me", authHandler.Me)
protectedAuth.POST("/refresh", authHandler.RefreshToken)
}
// User management
userHandler := handlers.NewAdminUserHandler(db)
users := protected.Group("/users")
{
users.GET("", userHandler.List)
users.POST("", userHandler.Create)
users.GET("/:id", userHandler.Get)
users.PUT("/:id", userHandler.Update)
users.DELETE("/:id", userHandler.Delete)
users.DELETE("/bulk", userHandler.BulkDelete)
}
// Residence management
residenceHandler := handlers.NewAdminResidenceHandler(db)
residences := protected.Group("/residences")
{
residences.GET("", residenceHandler.List)
residences.POST("", residenceHandler.Create)
residences.DELETE("/bulk", residenceHandler.BulkDelete)
residences.GET("/:id", residenceHandler.Get)
residences.PUT("/:id", residenceHandler.Update)
residences.DELETE("/:id", residenceHandler.Delete)
}
// Task management
taskHandler := handlers.NewAdminTaskHandler(db)
tasks := protected.Group("/tasks")
{
tasks.GET("", taskHandler.List)
tasks.POST("", taskHandler.Create)
tasks.DELETE("/bulk", taskHandler.BulkDelete)
tasks.GET("/:id", taskHandler.Get)
tasks.PUT("/:id", taskHandler.Update)
tasks.DELETE("/:id", taskHandler.Delete)
}
// Contractor management
contractorHandler := handlers.NewAdminContractorHandler(db)
contractors := protected.Group("/contractors")
{
contractors.GET("", contractorHandler.List)
contractors.POST("", contractorHandler.Create)
contractors.DELETE("/bulk", contractorHandler.BulkDelete)
contractors.GET("/:id", contractorHandler.Get)
contractors.PUT("/:id", contractorHandler.Update)
contractors.DELETE("/:id", contractorHandler.Delete)
}
// Document management
documentHandler := handlers.NewAdminDocumentHandler(db)
documents := protected.Group("/documents")
{
documents.GET("", documentHandler.List)
documents.POST("", documentHandler.Create)
documents.DELETE("/bulk", documentHandler.BulkDelete)
documents.GET("/:id", documentHandler.Get)
documents.PUT("/:id", documentHandler.Update)
documents.DELETE("/:id", documentHandler.Delete)
}
// Notification management
var emailService *services.EmailService
var pushClient *push.Client
if deps != nil {
emailService = deps.EmailService
pushClient = deps.PushClient
}
notificationHandler := handlers.NewAdminNotificationHandler(db, emailService, pushClient)
notifications := protected.Group("/notifications")
{
notifications.GET("", notificationHandler.List)
notifications.GET("/stats", notificationHandler.GetStats)
notifications.POST("/send-test", notificationHandler.SendTestNotification)
notifications.GET("/:id", notificationHandler.Get)
notifications.PUT("/:id", notificationHandler.Update)
notifications.DELETE("/:id", notificationHandler.Delete)
}
// Email test endpoint
emails := protected.Group("/emails")
{
emails.POST("/send-test", notificationHandler.SendTestEmail)
emails.POST("/send-post-verification", notificationHandler.SendPostVerificationEmail)
}
// Subscription management
subscriptionHandler := handlers.NewAdminSubscriptionHandler(db)
subscriptions := protected.Group("/subscriptions")
{
subscriptions.GET("", subscriptionHandler.List)
subscriptions.GET("/stats", subscriptionHandler.GetStats)
subscriptions.GET("/user/:user_id", subscriptionHandler.GetByUser)
subscriptions.GET("/:id", subscriptionHandler.Get)
subscriptions.PUT("/:id", subscriptionHandler.Update)
}
// Dashboard stats
dashboardHandler := handlers.NewAdminDashboardHandler(db)
protected.GET("/dashboard/stats", dashboardHandler.GetStats)
// Auth token management
authTokenHandler := handlers.NewAdminAuthTokenHandler(db)
authTokens := protected.Group("/auth-tokens")
{
authTokens.GET("", authTokenHandler.List)
authTokens.DELETE("/bulk", authTokenHandler.BulkDelete)
authTokens.GET("/:id", authTokenHandler.Get)
authTokens.DELETE("/:id", authTokenHandler.Delete)
}
// Task completion management
completionHandler := handlers.NewAdminCompletionHandler(db)
completions := protected.Group("/completions")
{
completions.GET("", completionHandler.List)
completions.DELETE("/bulk", completionHandler.BulkDelete)
completions.GET("/:id", completionHandler.Get)
completions.PUT("/:id", completionHandler.Update)
completions.DELETE("/:id", completionHandler.Delete)
}
// Confirmation code management
confirmationCodeHandler := handlers.NewAdminConfirmationCodeHandler(db)
confirmationCodes := protected.Group("/confirmation-codes")
{
confirmationCodes.GET("", confirmationCodeHandler.List)
confirmationCodes.DELETE("/bulk", confirmationCodeHandler.BulkDelete)
confirmationCodes.GET("/:id", confirmationCodeHandler.Get)
confirmationCodes.DELETE("/:id", confirmationCodeHandler.Delete)
}
// Share code management
shareCodeHandler := handlers.NewAdminShareCodeHandler(db)
shareCodes := protected.Group("/share-codes")
{
shareCodes.GET("", shareCodeHandler.List)
shareCodes.DELETE("/bulk", shareCodeHandler.BulkDelete)
shareCodes.GET("/:id", shareCodeHandler.Get)
shareCodes.PUT("/:id", shareCodeHandler.Update)
shareCodes.DELETE("/:id", shareCodeHandler.Delete)
}
// Password reset code management
passwordResetCodeHandler := handlers.NewAdminPasswordResetCodeHandler(db)
passwordResetCodes := protected.Group("/password-reset-codes")
{
passwordResetCodes.GET("", passwordResetCodeHandler.List)
passwordResetCodes.DELETE("/bulk", passwordResetCodeHandler.BulkDelete)
passwordResetCodes.GET("/:id", passwordResetCodeHandler.Get)
passwordResetCodes.DELETE("/:id", passwordResetCodeHandler.Delete)
}
// Push notification devices management
deviceHandler := handlers.NewAdminDeviceHandler(db)
devices := protected.Group("/devices")
{
devices.GET("/stats", deviceHandler.GetStats)
devices.GET("/apns", deviceHandler.ListAPNS)
devices.DELETE("/apns/bulk", deviceHandler.BulkDeleteAPNS)
devices.PUT("/apns/:id", deviceHandler.UpdateAPNS)
devices.DELETE("/apns/:id", deviceHandler.DeleteAPNS)
devices.GET("/gcm", deviceHandler.ListGCM)
devices.DELETE("/gcm/bulk", deviceHandler.BulkDeleteGCM)
devices.PUT("/gcm/:id", deviceHandler.UpdateGCM)
devices.DELETE("/gcm/:id", deviceHandler.DeleteGCM)
}
// Feature benefits management
featureBenefitHandler := handlers.NewAdminFeatureBenefitHandler(db)
featureBenefits := protected.Group("/feature-benefits")
{
featureBenefits.GET("", featureBenefitHandler.List)
featureBenefits.POST("", featureBenefitHandler.Create)
featureBenefits.GET("/:id", featureBenefitHandler.Get)
featureBenefits.PUT("/:id", featureBenefitHandler.Update)
featureBenefits.DELETE("/:id", featureBenefitHandler.Delete)
}
// Promotions management
promotionHandler := handlers.NewAdminPromotionHandler(db)
promotions := protected.Group("/promotions")
{
promotions.GET("", promotionHandler.List)
promotions.POST("", promotionHandler.Create)
promotions.GET("/:id", promotionHandler.Get)
promotions.PUT("/:id", promotionHandler.Update)
promotions.DELETE("/:id", promotionHandler.Delete)
}
// Lookup tables management
lookupHandler := handlers.NewAdminLookupHandler(db)
// Task Categories
categories := protected.Group("/lookups/categories")
{
categories.GET("", lookupHandler.ListCategories)
categories.POST("", lookupHandler.CreateCategory)
categories.PUT("/:id", lookupHandler.UpdateCategory)
categories.DELETE("/:id", lookupHandler.DeleteCategory)
}
// Task Priorities
priorities := protected.Group("/lookups/priorities")
{
priorities.GET("", lookupHandler.ListPriorities)
priorities.POST("", lookupHandler.CreatePriority)
priorities.PUT("/:id", lookupHandler.UpdatePriority)
priorities.DELETE("/:id", lookupHandler.DeletePriority)
}
// Task Frequencies
frequencies := protected.Group("/lookups/frequencies")
{
frequencies.GET("", lookupHandler.ListFrequencies)
frequencies.POST("", lookupHandler.CreateFrequency)
frequencies.PUT("/:id", lookupHandler.UpdateFrequency)
frequencies.DELETE("/:id", lookupHandler.DeleteFrequency)
}
// Notification Schedules (read-only, shows schedule for each frequency)
protected.GET("/lookups/notification-schedules", lookupHandler.GetNotificationSchedules)
// Residence Types
residenceTypes := protected.Group("/lookups/residence-types")
{
residenceTypes.GET("", lookupHandler.ListResidenceTypes)
residenceTypes.POST("", lookupHandler.CreateResidenceType)
residenceTypes.PUT("/:id", lookupHandler.UpdateResidenceType)
residenceTypes.DELETE("/:id", lookupHandler.DeleteResidenceType)
}
// Contractor Specialties
specialties := protected.Group("/lookups/specialties")
{
specialties.GET("", lookupHandler.ListSpecialties)
specialties.POST("", lookupHandler.CreateSpecialty)
specialties.PUT("/:id", lookupHandler.UpdateSpecialty)
specialties.DELETE("/:id", lookupHandler.DeleteSpecialty)
}
// Task Templates management
taskTemplateHandler := handlers.NewAdminTaskTemplateHandler(db)
taskTemplates := protected.Group("/task-templates")
{
taskTemplates.GET("", taskTemplateHandler.ListTemplates)
taskTemplates.POST("", taskTemplateHandler.CreateTemplate)
taskTemplates.POST("/bulk", taskTemplateHandler.BulkCreate)
taskTemplates.GET("/:id", taskTemplateHandler.GetTemplate)
taskTemplates.PUT("/:id", taskTemplateHandler.UpdateTemplate)
taskTemplates.DELETE("/:id", taskTemplateHandler.DeleteTemplate)
taskTemplates.POST("/:id/toggle-active", taskTemplateHandler.ToggleActive)
}
// Admin user management
adminUserHandler := handlers.NewAdminUserManagementHandler(db)
adminUsers := protected.Group("/admin-users")
{
adminUsers.GET("", adminUserHandler.List)
adminUsers.POST("", adminUserHandler.Create)
adminUsers.GET("/:id", adminUserHandler.Get)
adminUsers.PUT("/:id", adminUserHandler.Update)
adminUsers.DELETE("/:id", adminUserHandler.Delete)
}
// Notification preferences management
notifPrefsHandler := handlers.NewAdminNotificationPrefsHandler(db)
notifPrefs := protected.Group("/notification-prefs")
{
notifPrefs.GET("", notifPrefsHandler.List)
notifPrefs.GET("/:id", notifPrefsHandler.Get)
notifPrefs.PUT("/:id", notifPrefsHandler.Update)
notifPrefs.DELETE("/:id", notifPrefsHandler.Delete)
notifPrefs.GET("/user/:user_id", notifPrefsHandler.GetByUser)
}
// User profile management
userProfileHandler := handlers.NewAdminUserProfileHandler(db)
userProfiles := protected.Group("/user-profiles")
{
userProfiles.GET("", userProfileHandler.List)
userProfiles.DELETE("/bulk", userProfileHandler.BulkDelete)
userProfiles.GET("/:id", userProfileHandler.Get)
userProfiles.PUT("/:id", userProfileHandler.Update)
userProfiles.DELETE("/:id", userProfileHandler.Delete)
userProfiles.GET("/user/:user_id", userProfileHandler.GetByUser)
}
// Apple social auth management
appleSocialAuthHandler := handlers.NewAdminAppleSocialAuthHandler(db)
appleSocialAuth := protected.Group("/apple-social-auth")
{
appleSocialAuth.GET("", appleSocialAuthHandler.List)
appleSocialAuth.DELETE("/bulk", appleSocialAuthHandler.BulkDelete)
appleSocialAuth.GET("/:id", appleSocialAuthHandler.Get)
appleSocialAuth.PUT("/:id", appleSocialAuthHandler.Update)
appleSocialAuth.DELETE("/:id", appleSocialAuthHandler.Delete)
appleSocialAuth.GET("/user/:user_id", appleSocialAuthHandler.GetByUser)
}
// Task completion images management
completionImageHandler := handlers.NewAdminCompletionImageHandler(db)
completionImages := protected.Group("/completion-images")
{
completionImages.GET("", completionImageHandler.List)
completionImages.POST("", completionImageHandler.Create)
completionImages.DELETE("/bulk", completionImageHandler.BulkDelete)
completionImages.GET("/:id", completionImageHandler.Get)
completionImages.PUT("/:id", completionImageHandler.Update)
completionImages.DELETE("/:id", completionImageHandler.Delete)
}
// Document images management
documentImageHandler := handlers.NewAdminDocumentImageHandler(db)
documentImages := protected.Group("/document-images")
{
documentImages.GET("", documentImageHandler.List)
documentImages.POST("", documentImageHandler.Create)
documentImages.DELETE("/bulk", documentImageHandler.BulkDelete)
documentImages.GET("/:id", documentImageHandler.Get)
documentImages.PUT("/:id", documentImageHandler.Update)
documentImages.DELETE("/:id", documentImageHandler.Delete)
}
// System settings management
settingsHandler := handlers.NewAdminSettingsHandler(db)
settings := protected.Group("/settings")
{
settings.GET("", settingsHandler.GetSettings)
settings.PUT("", settingsHandler.UpdateSettings)
settings.POST("/seed-lookups", settingsHandler.SeedLookups)
settings.POST("/seed-test-data", settingsHandler.SeedTestData)
settings.POST("/seed-task-templates", settingsHandler.SeedTaskTemplates)
settings.POST("/clear-all-data", settingsHandler.ClearAllData)
settings.POST("/clear-stuck-jobs", settingsHandler.ClearStuckJobs)
}
// Limitations management (tier limits, upgrade triggers)
limitationsHandler := handlers.NewAdminLimitationsHandler(db)
limitations := protected.Group("/limitations")
{
// Settings (enable_limitations toggle)
limitations.GET("/settings", limitationsHandler.GetSettings)
limitations.PUT("/settings", limitationsHandler.UpdateSettings)
// Tier Limits
limitations.GET("/tier-limits", limitationsHandler.ListTierLimits)
limitations.GET("/tier-limits/:tier", limitationsHandler.GetTierLimits)
limitations.PUT("/tier-limits/:tier", limitationsHandler.UpdateTierLimits)
// Upgrade Triggers
limitations.GET("/upgrade-triggers/keys", limitationsHandler.GetAvailableTriggerKeys)
limitations.GET("/upgrade-triggers", limitationsHandler.ListUpgradeTriggers)
limitations.POST("/upgrade-triggers", limitationsHandler.CreateUpgradeTrigger)
limitations.GET("/upgrade-triggers/:id", limitationsHandler.GetUpgradeTrigger)
limitations.PUT("/upgrade-triggers/:id", limitationsHandler.UpdateUpgradeTrigger)
limitations.DELETE("/upgrade-triggers/:id", limitationsHandler.DeleteUpgradeTrigger)
}
// Onboarding emails management
var onboardingService *services.OnboardingEmailService
if deps != nil {
onboardingService = deps.OnboardingService
}
onboardingHandler := handlers.NewAdminOnboardingHandler(db, onboardingService)
onboardingEmails := protected.Group("/onboarding-emails")
{
onboardingEmails.GET("", onboardingHandler.List)
onboardingEmails.GET("/stats", onboardingHandler.GetStats)
onboardingEmails.POST("/send", onboardingHandler.Send)
onboardingEmails.DELETE("/bulk", onboardingHandler.BulkDelete)
onboardingEmails.GET("/user/:user_id", onboardingHandler.GetByUser)
onboardingEmails.GET("/:id", onboardingHandler.Get)
onboardingEmails.DELETE("/:id", onboardingHandler.Delete)
}
// System monitoring (logs, stats, websocket)
if deps != nil && deps.MonitoringHandler != nil {
monitoringGroup := protected.Group("/monitoring")
{
monitoringGroup.GET("/logs", deps.MonitoringHandler.GetLogs)
monitoringGroup.GET("/stats", deps.MonitoringHandler.GetStats)
monitoringGroup.DELETE("/logs", deps.MonitoringHandler.ClearLogs)
monitoringGroup.GET("/ws", deps.MonitoringHandler.WebSocket)
}
}
}
}
// Proxy admin panel requests to Next.js server
setupAdminProxy(router)
}
// setupAdminProxy configures reverse proxy to the Next.js admin panel
func setupAdminProxy(router *echo.Echo) {
// Get admin panel URL from env, default to localhost:3001
// Note: In production (Dokku), Next.js runs on internal port 3001
adminURL := os.Getenv("ADMIN_PANEL_URL")
if adminURL == "" {
adminURL = "http://127.0.0.1:3001"
}
target, err := url.Parse(adminURL)
if err != nil {
return
}
proxy := httputil.NewSingleHostReverseProxy(target)
// Handle all /admin/* requests
router.Any("/admin/*", func(c echo.Context) error {
proxy.ServeHTTP(c.Response(), c.Request())
return nil
})
// Also handle /admin without trailing path
router.Any("/admin", func(c echo.Context) error {
return c.Redirect(http.StatusMovedPermanently, "/admin/")
})
// Proxy Next.js static assets
router.Any("/_next/*", func(c echo.Context) error {
proxy.ServeHTTP(c.Response(), c.Request())
return nil
})
}