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>
483 lines
18 KiB
Go
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
|
|
})
|
|
}
|