feat(admin): Implement comprehensive admin management functionality

- Add new AdminHandler with methods for dashboard, schedules, users, and bookings
- Implement GetDashboard method to retrieve admin dashboard statistics
- Add CreateSchedule method with validation and error handling
- Implement GetUsers method with pagination support
- Add GetBookings method with pagination and filtering capabilities
- Implement GetFinancialReports method with date range filtering
- Add UpdateSchedule method to modify existing schedule slots
- Enhance error handling and response formatting for admin-related operations
- Integrate admin service methods for comprehensive administrative tasks
This commit is contained in:
ats-tech25 2025-11-05 16:58:34 +00:00
parent 1cb21847d9
commit c265e8f866
7 changed files with 740 additions and 17 deletions

View File

@ -1,38 +1,263 @@
package handlers
import (
"net/http"
"strconv"
"time"
"attune-heart-therapy/internal/services"
"github.com/gin-gonic/gin"
)
type AdminHandler struct {
// Will be implemented in later tasks
adminService services.AdminService
}
func NewAdminHandler() *AdminHandler {
return &AdminHandler{}
func NewAdminHandler(adminService services.AdminService) *AdminHandler {
return &AdminHandler{
adminService: adminService,
}
}
// GetDashboard handles GET /api/admin/dashboard for dashboard statistics
func (h *AdminHandler) GetDashboard(c *gin.Context) {
// Will be implemented in task 11
c.JSON(501, gin.H{"message": "Not implemented yet"})
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) {
// Will be implemented in task 11
c.JSON(501, gin.H{"message": "Not implemented yet"})
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) {
// Will be implemented in task 11
c.JSON(501, gin.H{"message": "Not implemented yet"})
// 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) {
// Will be implemented in task 11
c.JSON(501, gin.H{"message": "Not implemented yet"})
// 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
}
func (h *AdminHandler) GetFinancialReports(c *gin.Context) {
// Will be implemented in task 11
c.JSON(501, gin.H{"message": "Not implemented yet"})
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
}
c.JSON(http.StatusOK, gin.H{
"bookings": bookings,
"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,
})
}

View File

@ -148,3 +148,89 @@ func (r *bookingRepository) GetByPaymentID(paymentID string) (*models.Booking, e
return &booking, nil
}
// GetAllBookings retrieves all bookings with pagination
func (r *bookingRepository) GetAllBookings(limit, offset int) ([]models.Booking, int64, error) {
var bookings []models.Booking
var total int64
// Get total count
if err := r.db.Model(&models.Booking{}).Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("failed to get bookings count: %w", err)
}
// Get bookings with pagination and user preloaded
if err := r.db.Preload("User").Limit(limit).Offset(offset).
Order("created_at DESC").Find(&bookings).Error; err != nil {
return nil, 0, fmt.Errorf("failed to get bookings: %w", err)
}
// Clear password hashes for security
for i := range bookings {
bookings[i].User.PasswordHash = ""
}
return bookings, total, nil
}
// GetBookingStats retrieves booking statistics for admin dashboard
func (r *bookingRepository) GetBookingStats() (*BookingStats, error) {
var stats BookingStats
// Get total bookings
if err := r.db.Model(&models.Booking{}).Count(&stats.TotalBookings).Error; err != nil {
return nil, fmt.Errorf("failed to get total bookings count: %w", err)
}
// Get upcoming bookings
if err := r.db.Model(&models.Booking{}).
Where("status = ? AND scheduled_at > ?", models.BookingStatusScheduled, time.Now()).
Count(&stats.UpcomingBookings).Error; err != nil {
return nil, fmt.Errorf("failed to get upcoming bookings count: %w", err)
}
// Get completed bookings
if err := r.db.Model(&models.Booking{}).
Where("status = ?", models.BookingStatusCompleted).
Count(&stats.CompletedBookings).Error; err != nil {
return nil, fmt.Errorf("failed to get completed bookings count: %w", err)
}
// Get cancelled bookings
if err := r.db.Model(&models.Booking{}).
Where("status = ?", models.BookingStatusCancelled).
Count(&stats.CancelledBookings).Error; err != nil {
return nil, fmt.Errorf("failed to get cancelled bookings count: %w", err)
}
return &stats, nil
}
// GetFinancialStats retrieves financial statistics for admin reports
func (r *bookingRepository) GetFinancialStats(startDate, endDate time.Time) (*FinancialStats, error) {
var stats FinancialStats
// Get total revenue and booking count for the date range
var result struct {
TotalRevenue float64 `json:"total_revenue"`
TotalBookings int64 `json:"total_bookings"`
}
if err := r.db.Model(&models.Booking{}).
Select("COALESCE(SUM(amount), 0) as total_revenue, COUNT(*) as total_bookings").
Where("payment_status = ? AND created_at >= ? AND created_at <= ?",
models.PaymentStatusSucceeded, startDate, endDate).
Scan(&result).Error; err != nil {
return nil, fmt.Errorf("failed to get financial stats: %w", err)
}
stats.TotalRevenue = result.TotalRevenue
stats.TotalBookings = result.TotalBookings
// Calculate average booking value
if stats.TotalBookings > 0 {
stats.AverageBooking = stats.TotalRevenue / float64(stats.TotalBookings)
}
return &stats, nil
}

View File

@ -6,6 +6,21 @@ import (
"attune-heart-therapy/internal/models"
)
// BookingStats represents booking statistics for admin dashboard
type BookingStats struct {
TotalBookings int64 `json:"total_bookings"`
UpcomingBookings int64 `json:"upcoming_bookings"`
CompletedBookings int64 `json:"completed_bookings"`
CancelledBookings int64 `json:"cancelled_bookings"`
}
// FinancialStats represents financial statistics for admin reports
type FinancialStats struct {
TotalRevenue float64 `json:"total_revenue"`
TotalBookings int64 `json:"total_bookings"`
AverageBooking float64 `json:"average_booking"`
}
// UserRepository handles user data persistence
type UserRepository interface {
Create(user *models.User) error
@ -13,6 +28,7 @@ type UserRepository interface {
GetByEmail(email string) (*models.User, error)
Update(user *models.User) error
GetActiveUsersCount() (int64, error)
GetAllUsers(limit, offset int) ([]models.User, int64, error)
}
// BookingRepository handles booking data persistence
@ -24,6 +40,9 @@ type BookingRepository interface {
Update(booking *models.Booking) error
Delete(id uint) error
GetUpcomingBookings() ([]models.Booking, error)
GetAllBookings(limit, offset int) ([]models.Booking, int64, error)
GetBookingStats() (*BookingStats, error)
GetFinancialStats(startDate, endDate time.Time) (*FinancialStats, error)
}
// ScheduleRepository handles schedule data persistence

View File

@ -110,3 +110,26 @@ func (r *userRepository) GetActiveUsersCount() (int64, error) {
return count, nil
}
// GetAllUsers retrieves all users with pagination
func (r *userRepository) GetAllUsers(limit, offset int) ([]models.User, int64, error) {
var users []models.User
var total int64
// Get total count
if err := r.db.Model(&models.User{}).Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("failed to get users count: %w", err)
}
// Get users with pagination
if err := r.db.Limit(limit).Offset(offset).Order("created_at DESC").Find(&users).Error; err != nil {
return nil, 0, fmt.Errorf("failed to get users: %w", err)
}
// Clear password hashes for security
for i := range users {
users[i].PasswordHash = ""
}
return users, total, nil
}

View File

@ -18,6 +18,7 @@ type Server struct {
router *gin.Engine
paymentHandler *handlers.PaymentHandler
bookingHandler *handlers.BookingHandler
adminHandler *handlers.AdminHandler
}
func New(cfg *config.Config) *Server {
@ -126,12 +127,16 @@ func (s *Server) setupRoutes() {
payments.POST("/webhook", s.paymentHandler.HandleWebhook)
}
// Admin routes (will be implemented in later tasks)
// Admin routes - require admin authentication
admin := v1.Group("/admin")
// Note: Admin authentication middleware will be added in task 13
{
admin.GET("/dashboard", func(c *gin.Context) {
c.JSON(501, gin.H{"message": "Not implemented yet"})
})
admin.GET("/dashboard", s.adminHandler.GetDashboard)
admin.POST("/schedules", s.adminHandler.CreateSchedule)
admin.PUT("/schedules/:id", s.adminHandler.UpdateSchedule)
admin.GET("/users", s.adminHandler.GetUsers)
admin.GET("/bookings", s.adminHandler.GetBookings)
admin.GET("/reports/financial", s.adminHandler.GetFinancialReports)
}
}
}
@ -166,9 +171,13 @@ func (s *Server) initializeServices() {
notificationService,
)
// Initialize admin service
adminService := services.NewAdminService(repos.User, repos.Booking, repos.Schedule)
// Initialize handlers
s.paymentHandler = handlers.NewPaymentHandler(paymentService)
s.bookingHandler = handlers.NewBookingHandler(bookingService)
s.adminHandler = handlers.NewAdminHandler(adminService)
}
// healthCheck handles the health check endpoint

View File

@ -0,0 +1,304 @@
package services
import (
"fmt"
"log"
"time"
"attune-heart-therapy/internal/models"
"attune-heart-therapy/internal/repositories"
)
// adminService implements the AdminService interface
type adminService struct {
userRepo repositories.UserRepository
bookingRepo repositories.BookingRepository
scheduleRepo repositories.ScheduleRepository
}
// NewAdminService creates a new instance of AdminService
func NewAdminService(
userRepo repositories.UserRepository,
bookingRepo repositories.BookingRepository,
scheduleRepo repositories.ScheduleRepository,
) AdminService {
return &adminService{
userRepo: userRepo,
bookingRepo: bookingRepo,
scheduleRepo: scheduleRepo,
}
}
// GetDashboardStats retrieves dashboard statistics for admin overview
func (s *adminService) GetDashboardStats() (*DashboardStats, error) {
var stats DashboardStats
// Get user statistics
totalUsers, err := s.userRepo.GetActiveUsersCount()
if err != nil {
log.Printf("Failed to get total users count: %v", err)
return nil, fmt.Errorf("failed to get user statistics: %w", err)
}
stats.TotalUsers = totalUsers
stats.ActiveUsers = totalUsers // For now, all users are considered active
// Get booking statistics
bookingStats, err := s.bookingRepo.GetBookingStats()
if err != nil {
log.Printf("Failed to get booking statistics: %v", err)
return nil, fmt.Errorf("failed to get booking statistics: %w", err)
}
stats.TotalBookings = bookingStats.TotalBookings
stats.UpcomingBookings = bookingStats.UpcomingBookings
stats.CompletedBookings = bookingStats.CompletedBookings
stats.CancelledBookings = bookingStats.CancelledBookings
// Get financial statistics (all time)
allTimeStart := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
allTimeEnd := time.Now()
financialStats, err := s.bookingRepo.GetFinancialStats(allTimeStart, allTimeEnd)
if err != nil {
log.Printf("Failed to get financial statistics: %v", err)
return nil, fmt.Errorf("failed to get financial statistics: %w", err)
}
stats.TotalRevenue = financialStats.TotalRevenue
// Get monthly revenue (current month)
now := time.Now()
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
monthEnd := monthStart.AddDate(0, 1, 0)
monthlyStats, err := s.bookingRepo.GetFinancialStats(monthStart, monthEnd)
if err != nil {
log.Printf("Failed to get monthly financial statistics: %v", err)
return nil, fmt.Errorf("failed to get monthly financial statistics: %w", err)
}
stats.MonthlyRevenue = monthlyStats.TotalRevenue
return &stats, nil
}
// GetFinancialReports generates financial reports for a given date range
func (s *adminService) GetFinancialReports(startDate, endDate time.Time) (*FinancialReport, error) {
// Validate date range
if endDate.Before(startDate) {
return nil, fmt.Errorf("end date cannot be before start date")
}
// Get overall financial statistics
financialStats, err := s.bookingRepo.GetFinancialStats(startDate, endDate)
if err != nil {
log.Printf("Failed to get financial statistics for date range %v to %v: %v", startDate, endDate, err)
return nil, fmt.Errorf("failed to get financial statistics: %w", err)
}
report := &FinancialReport{
StartDate: startDate,
EndDate: endDate,
TotalRevenue: financialStats.TotalRevenue,
TotalBookings: financialStats.TotalBookings,
AverageBooking: financialStats.AverageBooking,
}
// Generate daily breakdown
dailyBreakdown, err := s.generateDailyBreakdown(startDate, endDate)
if err != nil {
log.Printf("Failed to generate daily breakdown: %v", err)
return nil, fmt.Errorf("failed to generate daily breakdown: %w", err)
}
report.DailyBreakdown = dailyBreakdown
// Generate monthly breakdown
monthlyBreakdown, err := s.generateMonthlyBreakdown(startDate, endDate)
if err != nil {
log.Printf("Failed to generate monthly breakdown: %v", err)
return nil, fmt.Errorf("failed to generate monthly breakdown: %w", err)
}
report.MonthlyBreakdown = monthlyBreakdown
return report, nil
}
// CreateSchedule creates a new schedule slot
func (s *adminService) CreateSchedule(req CreateScheduleRequest) (*models.Schedule, error) {
// Validate the request
if req.EndTime.Before(req.StartTime) || req.EndTime.Equal(req.StartTime) {
return nil, fmt.Errorf("end time must be after start time")
}
if req.StartTime.Before(time.Now()) {
return nil, fmt.Errorf("cannot create schedule slots in the past")
}
if req.MaxBookings < 1 {
return nil, fmt.Errorf("max bookings must be at least 1")
}
// Create the schedule
schedule := &models.Schedule{
StartTime: req.StartTime,
EndTime: req.EndTime,
MaxBookings: req.MaxBookings,
IsAvailable: true,
BookedCount: 0,
}
if err := s.scheduleRepo.Create(schedule); err != nil {
log.Printf("Failed to create schedule: %v", err)
return nil, fmt.Errorf("failed to create schedule: %w", err)
}
log.Printf("Successfully created schedule slot from %v to %v with max bookings %d",
schedule.StartTime, schedule.EndTime, schedule.MaxBookings)
return schedule, nil
}
// UpdateSchedule updates an existing schedule slot
func (s *adminService) UpdateSchedule(scheduleID uint, req UpdateScheduleRequest) (*models.Schedule, error) {
// Get the existing schedule
schedule, err := s.scheduleRepo.GetByID(scheduleID)
if err != nil {
log.Printf("Failed to get schedule %d: %v", scheduleID, err)
return nil, fmt.Errorf("schedule not found: %w", err)
}
// Update fields if provided
if req.StartTime != nil {
schedule.StartTime = *req.StartTime
}
if req.EndTime != nil {
schedule.EndTime = *req.EndTime
}
if req.MaxBookings != nil {
if *req.MaxBookings < 1 {
return nil, fmt.Errorf("max bookings must be at least 1")
}
if *req.MaxBookings < schedule.BookedCount {
return nil, fmt.Errorf("max bookings cannot be less than current booked count (%d)", schedule.BookedCount)
}
schedule.MaxBookings = *req.MaxBookings
}
if req.IsAvailable != nil {
schedule.IsAvailable = *req.IsAvailable
}
// Validate time range if updated
if !schedule.EndTime.After(schedule.StartTime) {
return nil, fmt.Errorf("end time must be after start time")
}
// Update the schedule
if err := s.scheduleRepo.Update(schedule); err != nil {
log.Printf("Failed to update schedule %d: %v", scheduleID, err)
return nil, fmt.Errorf("failed to update schedule: %w", err)
}
log.Printf("Successfully updated schedule %d", scheduleID)
return schedule, nil
}
// GetAllUsers retrieves all users with pagination
func (s *adminService) GetAllUsers(limit, offset int) ([]models.User, int64, error) {
if limit <= 0 {
limit = 50 // Default limit
}
if limit > 100 {
limit = 100 // Maximum limit
}
if offset < 0 {
offset = 0
}
users, total, err := s.userRepo.GetAllUsers(limit, offset)
if err != nil {
log.Printf("Failed to get users with limit %d, offset %d: %v", limit, offset, err)
return nil, 0, fmt.Errorf("failed to get users: %w", err)
}
return users, total, nil
}
// GetAllBookings retrieves all bookings with pagination
func (s *adminService) GetAllBookings(limit, offset int) ([]models.Booking, int64, error) {
if limit <= 0 {
limit = 50 // Default limit
}
if limit > 100 {
limit = 100 // Maximum limit
}
if offset < 0 {
offset = 0
}
bookings, total, err := s.bookingRepo.GetAllBookings(limit, offset)
if err != nil {
log.Printf("Failed to get bookings with limit %d, offset %d: %v", limit, offset, err)
return nil, 0, fmt.Errorf("failed to get bookings: %w", err)
}
return bookings, total, nil
}
// generateDailyBreakdown generates daily financial breakdown for the given date range
func (s *adminService) generateDailyBreakdown(startDate, endDate time.Time) ([]DailyFinancialSummary, error) {
var breakdown []DailyFinancialSummary
// Iterate through each day in the range
for d := startDate; !d.After(endDate); d = d.AddDate(0, 0, 1) {
dayStart := time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location())
dayEnd := dayStart.AddDate(0, 0, 1)
stats, err := s.bookingRepo.GetFinancialStats(dayStart, dayEnd)
if err != nil {
return nil, fmt.Errorf("failed to get daily stats for %v: %w", d, err)
}
breakdown = append(breakdown, DailyFinancialSummary{
Date: dayStart,
Revenue: stats.TotalRevenue,
Bookings: stats.TotalBookings,
})
}
return breakdown, nil
}
// generateMonthlyBreakdown generates monthly financial breakdown for the given date range
func (s *adminService) generateMonthlyBreakdown(startDate, endDate time.Time) ([]MonthlyFinancialSummary, error) {
var breakdown []MonthlyFinancialSummary
// Start from the beginning of the start month
current := time.Date(startDate.Year(), startDate.Month(), 1, 0, 0, 0, 0, startDate.Location())
endMonth := time.Date(endDate.Year(), endDate.Month(), 1, 0, 0, 0, 0, endDate.Location())
for !current.After(endMonth) {
monthStart := current
monthEnd := monthStart.AddDate(0, 1, 0)
// Adjust for the actual date range
if monthStart.Before(startDate) {
monthStart = startDate
}
if monthEnd.After(endDate) {
monthEnd = endDate
}
stats, err := s.bookingRepo.GetFinancialStats(monthStart, monthEnd)
if err != nil {
return nil, fmt.Errorf("failed to get monthly stats for %v: %w", current, err)
}
breakdown = append(breakdown, MonthlyFinancialSummary{
Month: current.Format("2006-01"),
Revenue: stats.TotalRevenue,
Bookings: stats.TotalBookings,
})
current = current.AddDate(0, 1, 0)
}
return breakdown, nil
}

View File

@ -49,6 +49,16 @@ type JitsiService interface {
DeleteMeeting(roomID string) error
}
// AdminService handles admin dashboard operations
type AdminService interface {
GetDashboardStats() (*DashboardStats, error)
GetFinancialReports(startDate, endDate time.Time) (*FinancialReport, error)
CreateSchedule(req CreateScheduleRequest) (*models.Schedule, error)
UpdateSchedule(scheduleID uint, req UpdateScheduleRequest) (*models.Schedule, error)
GetAllUsers(limit, offset int) ([]models.User, int64, error)
GetAllBookings(limit, offset int) ([]models.Booking, int64, error)
}
// JitsiMeeting represents a Jitsi meeting
type JitsiMeeting struct {
RoomID string `json:"room_id"`
@ -89,3 +99,50 @@ type CreatePaymentIntentRequest struct {
type ConfirmPaymentRequest struct {
PaymentIntentID string `json:"payment_intent_id" binding:"required"`
}
// Admin DTOs
type DashboardStats struct {
TotalUsers int64 `json:"total_users"`
ActiveUsers int64 `json:"active_users"`
TotalBookings int64 `json:"total_bookings"`
UpcomingBookings int64 `json:"upcoming_bookings"`
CompletedBookings int64 `json:"completed_bookings"`
CancelledBookings int64 `json:"cancelled_bookings"`
TotalRevenue float64 `json:"total_revenue"`
MonthlyRevenue float64 `json:"monthly_revenue"`
}
type FinancialReport struct {
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
TotalRevenue float64 `json:"total_revenue"`
TotalBookings int64 `json:"total_bookings"`
AverageBooking float64 `json:"average_booking"`
DailyBreakdown []DailyFinancialSummary `json:"daily_breakdown"`
MonthlyBreakdown []MonthlyFinancialSummary `json:"monthly_breakdown"`
}
type DailyFinancialSummary struct {
Date time.Time `json:"date"`
Revenue float64 `json:"revenue"`
Bookings int64 `json:"bookings"`
}
type MonthlyFinancialSummary struct {
Month string `json:"month"`
Revenue float64 `json:"revenue"`
Bookings int64 `json:"bookings"`
}
type CreateScheduleRequest struct {
StartTime time.Time `json:"start_time" binding:"required"`
EndTime time.Time `json:"end_time" binding:"required"`
MaxBookings int `json:"max_bookings" binding:"required,min=1"`
}
type UpdateScheduleRequest struct {
StartTime *time.Time `json:"start_time"`
EndTime *time.Time `json:"end_time"`
MaxBookings *int `json:"max_bookings"`
IsAvailable *bool `json:"is_available"`
}