backend-service/internal/handlers/admin.go
ats-tech25 04f2d02afc docs(api comprehensive API documentation for attune Heart Therapy
Add detailed API:
- Complete API documentation for In Format Usage flow diagrams for authentication and booking processes
- Comprehensive endpoint descriptions with request/response examples
- Detailed authentication and booking flow explanations
- Structured documentation for health checks, authentication, and booking endpoints
-: - Includes JWT authentication details
usage
- Provides clear API usage patterns for users and clients and administrators
system interactions
- Enhances project documentation with provides clear, structured API reference
- Improves developer and user understanding of system capabilities
2025-11-07 19:22:46 +00:00

340 lines
8.5 KiB
Go

package handlers
import (
"fmt"
"net/http"
"strconv"
"time"
"attune-heart-therapy/internal/middleware"
"attune-heart-therapy/internal/models"
"attune-heart-therapy/internal/repositories"
"attune-heart-therapy/internal/services"
"github.com/gin-gonic/gin"
)
type AdminHandler struct {
adminService services.AdminService
userRepo repositories.UserRepository
jitsiService services.JitsiService
}
func NewAdminHandler(adminService services.AdminService, userRepo repositories.UserRepository, jitsiService services.JitsiService) *AdminHandler {
return &AdminHandler{
adminService: adminService,
userRepo: userRepo,
jitsiService: jitsiService,
}
}
// AdminBookingResponse represents a booking with admin's personalized meeting link
type AdminBookingResponse struct {
models.Booking
AdminMeetingURL string `json:"admin_meeting_url,omitempty"`
}
// addAdminLink adds an admin's personalized meeting link to a booking
func (h *AdminHandler) addAdminLink(booking *models.Booking, admin *models.User) AdminBookingResponse {
response := AdminBookingResponse{
Booking: *booking,
}
// Only add personalized link if Jitsi URL exists
if booking.JitsiRoomURL != "" && booking.JitsiRoomID != "" {
displayName := fmt.Sprintf("%s %s", admin.FirstName, admin.LastName)
if displayName == " " || displayName == "" {
displayName = admin.Email
}
// Generate JWT token for admin with moderator privileges
token, err := h.jitsiService.GenerateJitsiToken(
booking.JitsiRoomID,
displayName,
admin.Email,
true, // Always true for admin
)
var adminURL string
if err == nil && token != "" {
// Use JWT token if available
adminURL = fmt.Sprintf("%s?jwt=%s", booking.JitsiRoomURL, token)
} else {
// Fallback to URL parameters
adminURL = fmt.Sprintf("%s#userInfo.displayName=\"%s\"&config.startWithAudioMuted=false&config.startWithVideoMuted=false",
booking.JitsiRoomURL, displayName)
}
response.AdminMeetingURL = adminURL
}
return response
}
// GetDashboard handles GET /api/admin/dashboard for dashboard statistics
func (h *AdminHandler) GetDashboard(c *gin.Context) {
stats, err := h.adminService.GetDashboardStats()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to get dashboard statistics",
"details": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"stats": stats,
})
}
// CreateSchedule handles POST /api/admin/schedules for schedule creation
func (h *AdminHandler) CreateSchedule(c *gin.Context) {
var req services.CreateScheduleRequest
// Bind JSON request to struct
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid request format",
"details": err.Error(),
})
return
}
// Create the schedule
schedule, err := h.adminService.CreateSchedule(req)
if err != nil {
// Handle specific error cases
if err.Error() == "end time must be after start time" ||
err.Error() == "cannot create schedule slots in the past" ||
err.Error() == "max bookings must be at least 1" {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
if err.Error() == "schedule slot overlaps with existing available slot" {
c.JSON(http.StatusConflict, gin.H{
"error": "Schedule slot overlaps with existing available slot",
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to create schedule",
"details": err.Error(),
})
return
}
c.JSON(http.StatusCreated, gin.H{
"message": "Schedule created successfully",
"schedule": schedule,
})
}
// GetUsers handles GET /api/admin/users for retrieving all users
func (h *AdminHandler) GetUsers(c *gin.Context) {
// Parse pagination parameters
limitStr := c.DefaultQuery("limit", "50")
offsetStr := c.DefaultQuery("offset", "0")
limit, err := strconv.Atoi(limitStr)
if err != nil || limit <= 0 {
limit = 50
}
offset, err := strconv.Atoi(offsetStr)
if err != nil || offset < 0 {
offset = 0
}
// Get users
users, total, err := h.adminService.GetAllUsers(limit, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to get users",
"details": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"users": users,
"total": total,
"limit": limit,
"offset": offset,
})
}
// GetBookings handles GET /api/admin/bookings for retrieving all bookings
func (h *AdminHandler) GetBookings(c *gin.Context) {
// Get admin user ID from JWT token
adminID, exists := middleware.GetUserIDFromContext(c)
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "User not authenticated",
})
return
}
// Get admin user details
admin, err := h.userRepo.GetByID(adminID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to get admin information",
"details": err.Error(),
})
return
}
// Parse pagination parameters
limitStr := c.DefaultQuery("limit", "50")
offsetStr := c.DefaultQuery("offset", "0")
limit, err := strconv.Atoi(limitStr)
if err != nil || limit <= 0 {
limit = 50
}
offset, err := strconv.Atoi(offsetStr)
if err != nil || offset < 0 {
offset = 0
}
// Get bookings
bookings, total, err := h.adminService.GetAllBookings(limit, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to get bookings",
"details": err.Error(),
})
return
}
// Add admin's personalized meeting links to each booking
bookingResponses := make([]AdminBookingResponse, len(bookings))
for i, booking := range bookings {
bookingResponses[i] = h.addAdminLink(&booking, admin)
}
c.JSON(http.StatusOK, gin.H{
"bookings": bookingResponses,
"total": total,
"limit": limit,
"offset": offset,
})
}
// GetFinancialReports handles GET /api/admin/reports/financial for financial reports
func (h *AdminHandler) GetFinancialReports(c *gin.Context) {
// Parse date parameters
startDateStr := c.Query("start_date")
endDateStr := c.Query("end_date")
if startDateStr == "" || endDateStr == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Both start_date and end_date parameters are required (format: YYYY-MM-DD)",
})
return
}
// Parse dates
startDate, err := time.Parse("2006-01-02", startDateStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid start_date format. Expected: YYYY-MM-DD",
"details": err.Error(),
})
return
}
endDate, err := time.Parse("2006-01-02", endDateStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid end_date format. Expected: YYYY-MM-DD",
"details": err.Error(),
})
return
}
// Adjust end date to include the entire day
endDate = endDate.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
// Generate financial report
report, err := h.adminService.GetFinancialReports(startDate, endDate)
if err != nil {
if err.Error() == "end date cannot be before start date" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "End date cannot be before start date",
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to generate financial report",
"details": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"report": report,
})
}
// UpdateSchedule handles PUT /api/admin/schedules/:id for schedule updates
func (h *AdminHandler) UpdateSchedule(c *gin.Context) {
// Get schedule ID from URL parameter
scheduleIDStr := c.Param("id")
scheduleID, err := strconv.ParseUint(scheduleIDStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid schedule ID",
})
return
}
var req services.UpdateScheduleRequest
// Bind JSON request to struct
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid request format",
"details": err.Error(),
})
return
}
// Update the schedule
schedule, err := h.adminService.UpdateSchedule(uint(scheduleID), req)
if err != nil {
// Handle specific error cases
if err.Error() == "schedule not found" {
c.JSON(http.StatusNotFound, gin.H{
"error": "Schedule not found",
})
return
}
if err.Error() == "end time must be after start time" ||
err.Error() == "max bookings must be at least 1" {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to update schedule",
"details": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Schedule updated successfully",
"schedule": schedule,
})
}