- Add confirmation codes management page - Add devices management page - Add feature benefits management page - Add password reset codes management page - Add promotions management page - Add share codes management page - Add corresponding backend handlers and routes - Update sidebar navigation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
385 lines
14 KiB
Go
385 lines
14 KiB
Go
package admin
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"os"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/treytartt/casera-api/internal/admin/handlers"
|
|
"github.com/treytartt/casera-api/internal/config"
|
|
"github.com/treytartt/casera-api/internal/middleware"
|
|
"github.com/treytartt/casera-api/internal/push"
|
|
"github.com/treytartt/casera-api/internal/repositories"
|
|
"github.com/treytartt/casera-api/internal/services"
|
|
)
|
|
|
|
// Dependencies holds optional services for admin routes
|
|
type Dependencies struct {
|
|
EmailService *services.EmailService
|
|
PushClient *push.GorushClient
|
|
}
|
|
|
|
// SetupRoutes configures all admin routes
|
|
func SetupRoutes(router *gin.Engine, 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.GorushClient
|
|
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)
|
|
}
|
|
|
|
// Subscription management
|
|
subscriptionHandler := handlers.NewAdminSubscriptionHandler(db)
|
|
subscriptions := protected.Group("/subscriptions")
|
|
{
|
|
subscriptions.GET("", subscriptionHandler.List)
|
|
subscriptions.GET("/stats", subscriptionHandler.GetStats)
|
|
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 Statuses
|
|
statuses := protected.Group("/lookups/statuses")
|
|
{
|
|
statuses.GET("", lookupHandler.ListStatuses)
|
|
statuses.POST("", lookupHandler.CreateStatus)
|
|
statuses.PUT("/:id", lookupHandler.UpdateStatus)
|
|
statuses.DELETE("/:id", lookupHandler.DeleteStatus)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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("/clear-all-data", settingsHandler.ClearAllData)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Proxy admin panel requests to Next.js server
|
|
setupAdminProxy(router)
|
|
}
|
|
|
|
// setupAdminProxy configures reverse proxy to the Next.js admin panel
|
|
func setupAdminProxy(router *gin.Engine) {
|
|
// Get admin panel URL from env, default to localhost:3000
|
|
adminURL := os.Getenv("ADMIN_PANEL_URL")
|
|
if adminURL == "" {
|
|
adminURL = "http://127.0.0.1:3000"
|
|
}
|
|
|
|
target, err := url.Parse(adminURL)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
|
|
|
// Handle all /admin/* requests
|
|
router.Any("/admin/*filepath", func(c *gin.Context) {
|
|
proxy.ServeHTTP(c.Writer, c.Request)
|
|
})
|
|
|
|
// Also handle /admin without trailing path
|
|
router.Any("/admin", func(c *gin.Context) {
|
|
c.Redirect(http.StatusMovedPermanently, "/admin/")
|
|
})
|
|
}
|