- 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>
156 lines
4.3 KiB
Go
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})
|
|
}
|