Files
honeyDueAPI/internal/admin/handlers/auth_token_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

156 lines
4.3 KiB
Go

package handlers
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"github.com/treytartt/mycrib-api/internal/admin/dto"
"github.com/treytartt/mycrib-api/internal/models"
)
// AdminAuthTokenHandler handles admin auth token management endpoints
type AdminAuthTokenHandler struct {
db *gorm.DB
}
// NewAdminAuthTokenHandler creates a new admin auth token handler
func NewAdminAuthTokenHandler(db *gorm.DB) *AdminAuthTokenHandler {
return &AdminAuthTokenHandler{db: db}
}
// AuthTokenResponse represents an auth token in API responses
type AuthTokenResponse struct {
Key string `json:"key"`
UserID uint `json:"user_id"`
Username string `json:"username"`
Email string `json:"email"`
Created string `json:"created"`
}
// List handles GET /api/admin/auth-tokens
func (h *AdminAuthTokenHandler) List(c *gin.Context) {
var filters dto.PaginationParams
if err := c.ShouldBindQuery(&filters); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var tokens []models.AuthToken
var total int64
query := h.db.Model(&models.AuthToken{}).Preload("User")
// Apply search (search by user info)
if filters.Search != "" {
search := "%" + filters.Search + "%"
query = query.Joins("JOIN auth_user ON auth_user.id = user_authtoken.user_id").
Where(
"auth_user.username ILIKE ? OR auth_user.email ILIKE ? OR user_authtoken.key ILIKE ?",
search, search, search,
)
}
// Get total count
query.Count(&total)
// Apply sorting
sortBy := "created"
if filters.SortBy != "" {
sortBy = filters.SortBy
}
query = query.Order(sortBy + " " + filters.GetSortDir())
// Apply pagination
query = query.Offset(filters.GetOffset()).Limit(filters.GetPerPage())
if err := query.Find(&tokens).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch auth tokens"})
return
}
// Build response
responses := make([]AuthTokenResponse, len(tokens))
for i, token := range tokens {
responses[i] = AuthTokenResponse{
Key: token.Key,
UserID: token.UserID,
Username: token.User.Username,
Email: token.User.Email,
Created: token.Created.Format("2006-01-02T15:04:05Z"),
}
}
c.JSON(http.StatusOK, dto.NewPaginatedResponse(responses, total, filters.GetPage(), filters.GetPerPage()))
}
// Get handles GET /api/admin/auth-tokens/:id (id is actually user_id)
func (h *AdminAuthTokenHandler) Get(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
var token models.AuthToken
if err := h.db.Preload("User").Where("user_id = ?", id).First(&token).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "Auth token not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch auth token"})
return
}
response := AuthTokenResponse{
Key: token.Key,
UserID: token.UserID,
Username: token.User.Username,
Email: token.User.Email,
Created: token.Created.Format("2006-01-02T15:04:05Z"),
}
c.JSON(http.StatusOK, response)
}
// Delete handles DELETE /api/admin/auth-tokens/:id (revoke token)
func (h *AdminAuthTokenHandler) Delete(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
result := h.db.Where("user_id = ?", id).Delete(&models.AuthToken{})
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to revoke token"})
return
}
if result.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "Auth token not found"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Auth token revoked successfully"})
}
// BulkDelete handles DELETE /api/admin/auth-tokens/bulk
func (h *AdminAuthTokenHandler) BulkDelete(c *gin.Context) {
var req dto.BulkDeleteRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
result := h.db.Where("user_id IN ?", req.IDs).Delete(&models.AuthToken{})
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to revoke tokens"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Auth tokens revoked successfully", "count": result.RowsAffected})
}