- 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>
141 lines
4.0 KiB
Go
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})
|
|
}
|