Files
honeyDueAPI/internal/admin/handlers/auth_handler.go
Trey t 2817deee3c Add Next.js admin panel and implement background worker jobs
- Add full Next.js admin panel with:
  - User, residence, task, contractor, document management
  - Notifications and notification preferences management
  - Subscriptions and auth token management
  - Dashboard with stats
  - Lookup tables management (categories, priorities, statuses, etc.)
  - Admin user management

- Implement background worker job handlers:
  - HandleTaskReminder: sends push notifications for tasks due within 24h
  - HandleOverdueReminder: sends push notifications for overdue tasks
  - HandleDailyDigest: sends daily summary of pending tasks
  - HandleSendEmail: processes email sending jobs
  - HandleSendPush: processes push notification jobs

- Make worker job schedules configurable via environment variables:
  - TASK_REMINDER_HOUR, TASK_REMINDER_MINUTE (default: 20:00 UTC)
  - OVERDUE_REMINDER_HOUR (default: 09:00 UTC)
  - DAILY_DIGEST_HOUR (default: 11:00 UTC)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 23:35:00 -06:00

141 lines
4.0 KiB
Go

package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/treytartt/mycrib-api/internal/config"
"github.com/treytartt/mycrib-api/internal/middleware"
"github.com/treytartt/mycrib-api/internal/models"
"github.com/treytartt/mycrib-api/internal/repositories"
)
// AdminAuthHandler handles admin authentication endpoints
type AdminAuthHandler struct {
adminRepo *repositories.AdminRepository
cfg *config.Config
}
// NewAdminAuthHandler creates a new admin auth handler
func NewAdminAuthHandler(adminRepo *repositories.AdminRepository, cfg *config.Config) *AdminAuthHandler {
return &AdminAuthHandler{
adminRepo: adminRepo,
cfg: cfg,
}
}
// LoginRequest represents the admin login request
type LoginRequest struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
}
// LoginResponse represents the admin login response
type LoginResponse struct {
Token string `json:"token"`
Admin AdminUserResponse `json:"admin"`
}
// AdminUserResponse represents an admin user in API responses
type AdminUserResponse struct {
ID uint `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Role models.AdminRole `json:"role"`
IsActive bool `json:"is_active"`
LastLogin *string `json:"last_login,omitempty"`
CreatedAt string `json:"created_at"`
}
// NewAdminUserResponse creates an AdminUserResponse from an AdminUser model
func NewAdminUserResponse(admin *models.AdminUser) AdminUserResponse {
resp := AdminUserResponse{
ID: admin.ID,
Email: admin.Email,
FirstName: admin.FirstName,
LastName: admin.LastName,
Role: admin.Role,
IsActive: admin.IsActive,
CreatedAt: admin.CreatedAt.Format("2006-01-02T15:04:05Z"),
}
if admin.LastLogin != nil {
lastLogin := admin.LastLogin.Format("2006-01-02T15:04:05Z")
resp.LastLogin = &lastLogin
}
return resp
}
// Login handles POST /api/admin/auth/login
func (h *AdminAuthHandler) Login(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()})
return
}
// Find admin by email
admin, err := h.adminRepo.FindByEmail(req.Email)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
return
}
// Check password
if !admin.CheckPassword(req.Password) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
return
}
// Check if admin is active
if !admin.IsActive {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Account is disabled"})
return
}
// Generate JWT token
token, err := middleware.GenerateAdminToken(admin, h.cfg)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
return
}
// Update last login
_ = h.adminRepo.UpdateLastLogin(admin.ID)
// Refresh admin data after updating last login
admin, _ = h.adminRepo.FindByID(admin.ID)
c.JSON(http.StatusOK, LoginResponse{
Token: token,
Admin: NewAdminUserResponse(admin),
})
}
// Logout handles POST /api/admin/auth/logout
// Note: JWT tokens are stateless, so logout is handled client-side by removing the token
func (h *AdminAuthHandler) Logout(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Logged out successfully"})
}
// Me handles GET /api/admin/auth/me
func (h *AdminAuthHandler) Me(c *gin.Context) {
admin := c.MustGet(middleware.AdminUserKey).(*models.AdminUser)
c.JSON(http.StatusOK, NewAdminUserResponse(admin))
}
// RefreshToken handles POST /api/admin/auth/refresh
func (h *AdminAuthHandler) RefreshToken(c *gin.Context) {
admin := c.MustGet(middleware.AdminUserKey).(*models.AdminUser)
// Generate new token
token, err := middleware.GenerateAdminToken(admin, h.cfg)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
return
}
c.JSON(http.StatusOK, gin.H{"token": token})
}