251 lines
7.3 KiB
Go
251 lines
7.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"log"
|
|
"fmt"
|
|
"sno/internal/models"
|
|
"sno/internal/service"
|
|
"strconv"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
// AdminHandler handles HTTP requests for admin operations.
|
|
type AdminHandler struct {
|
|
adminService service.AdminService
|
|
qrService service.QRService
|
|
}
|
|
|
|
// NewAdminHandler creates a new instance of an admin handler.
|
|
func NewAdminHandler(adminSvc service.AdminService, qrSvc service.QRService) *AdminHandler {
|
|
return &AdminHandler{
|
|
adminService: adminSvc,
|
|
qrService: qrSvc,
|
|
}
|
|
}
|
|
|
|
// GrantStars handles the request to manually grant stars to a user.
|
|
// @Summary Grant stars to user
|
|
// @Description Manually grants stars to a user (admin only)
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body models.GrantStarsRequest true "Grant stars request"
|
|
// @Success 200 {object} object{success=bool,message=string}
|
|
// @Failure 400 {object} object{success=bool,message=string}
|
|
// @Failure 500 {object} object{success=bool,message=string}
|
|
// @Router /api/admin/users/grant-stars [post]
|
|
// @Security ApiKeyAuth
|
|
func (h *AdminHandler) GrantStars(c *fiber.Ctx) error {
|
|
var req models.GrantStarsRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(Response{
|
|
Success: false,
|
|
Message: "Cannot parse JSON",
|
|
})
|
|
}
|
|
|
|
if err := h.adminService.GrantStars(c.Context(), req.UserID, req.Amount); err != nil {
|
|
log.Printf("ERROR: Failed to grant stars: %v", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(Response{
|
|
Success: false,
|
|
Message: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(Response{
|
|
Success: true,
|
|
Message: "Stars granted successfully",
|
|
})
|
|
}
|
|
|
|
// GenerateQRCodes handles the request to generate unique QR codes.
|
|
// @Summary Generate QR codes
|
|
// @Description Generates unique QR codes for rewards, quizzes, or shop items (admin/operator only)
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body models.GenerateQRCodesRequest true "QR codes generation request"
|
|
// @Success 201 {object} object{success=bool,message=string,data=models.GenerateQRCodesResponse}
|
|
// @Failure 400 {object} object{success=bool,message=string}
|
|
// @Failure 500 {object} object{success=bool,message=string}
|
|
// @Router /api/admin/qrcodes [post]
|
|
// @Security ApiKeyAuth
|
|
func (h *AdminHandler) GenerateQRCodes(c *fiber.Ctx) error {
|
|
var req models.GenerateQRCodesRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(Response{
|
|
Success: false,
|
|
Message: "Cannot parse JSON",
|
|
})
|
|
}
|
|
|
|
// Validate request
|
|
if req.Type == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(Response{
|
|
Success: false,
|
|
Message: "Type cannot be empty",
|
|
})
|
|
}
|
|
|
|
if req.Value == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(Response{
|
|
Success: false,
|
|
Message: "Value cannot be empty",
|
|
})
|
|
}
|
|
|
|
if req.Count <= 0 || req.Count > 100 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(Response{
|
|
Success: false,
|
|
Message: "Count must be between 1 and 100",
|
|
})
|
|
}
|
|
|
|
// Validate type
|
|
validTypes := map[string]bool{
|
|
"reward": true,
|
|
"quiz": true,
|
|
"shop": true,
|
|
}
|
|
if !validTypes[req.Type] {
|
|
return c.Status(fiber.StatusBadRequest).JSON(Response{
|
|
Success: false,
|
|
Message: "Invalid type. Must be 'reward', 'quiz', or 'shop'",
|
|
})
|
|
}
|
|
|
|
tokens := make([]string, 0, req.Count)
|
|
for i := 0; i < req.Count; i++ {
|
|
token, err := h.qrService.GenerateUniqueToken(c.Context(), req.Type, req.Value)
|
|
if err != nil {
|
|
log.Printf("ERROR: Failed to generate QR token: %v", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(Response{
|
|
Success: false,
|
|
Message: "Failed to generate QR codes",
|
|
})
|
|
}
|
|
tokens = append(tokens, token)
|
|
}
|
|
|
|
return c.Status(fiber.StatusCreated).JSON(Response{
|
|
Success: true,
|
|
Message: fmt.Sprintf("%d unique QR codes generated successfully", len(tokens)),
|
|
Data: models.GenerateQRCodesResponse{
|
|
Tokens: tokens,
|
|
},
|
|
})
|
|
}
|
|
|
|
// CreateOperator handles the request to create a new operator
|
|
// @Summary Create operator
|
|
// @Description Creates a new operator account (admin only)
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body models.CreateOperatorRequest true "Create operator request"
|
|
// @Success 201 {object} object{success=bool,message=string}
|
|
// @Failure 400 {object} object{success=bool,message=string}
|
|
// @Failure 500 {object} object{success=bool,message=string}
|
|
// @Router /api/admin/operators [post]
|
|
// @Security ApiKeyAuth
|
|
func (h *AdminHandler) CreateOperator(c *fiber.Ctx) error {
|
|
var req models.CreateOperatorRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(Response{
|
|
Success: false,
|
|
Message: "Cannot parse JSON",
|
|
})
|
|
}
|
|
|
|
// Validate request
|
|
if req.TelegramID == 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(Response{
|
|
Success: false,
|
|
Message: "Telegram ID cannot be empty",
|
|
})
|
|
}
|
|
|
|
if req.Name == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(Response{
|
|
Success: false,
|
|
Message: "Name cannot be empty",
|
|
})
|
|
}
|
|
|
|
if err := h.adminService.CreateOperator(c.Context(), req.TelegramID, req.Name); err != nil {
|
|
log.Printf("ERROR: Failed to create operator: %v", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(Response{
|
|
Success: false,
|
|
Message: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusCreated).JSON(Response{
|
|
Success: true,
|
|
Message: "Operator created successfully",
|
|
})
|
|
}
|
|
|
|
// DeleteOperator handles the request to delete an operator
|
|
// @Summary Delete operator
|
|
// @Description Deletes an operator account (admin only)
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path int true "Operator Telegram ID"
|
|
// @Success 200 {object} object{success=bool,message=string}
|
|
// @Failure 400 {object} object{success=bool,message=string}
|
|
// @Failure 500 {object} object{success=bool,message=string}
|
|
// @Router /api/admin/operators/{id} [delete]
|
|
// @Security ApiKeyAuth
|
|
func (h *AdminHandler) DeleteOperator(c *fiber.Ctx) error {
|
|
telegramID, err := strconv.ParseInt(c.Params("id"), 10, 64)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(Response{
|
|
Success: false,
|
|
Message: "Invalid Telegram ID",
|
|
})
|
|
}
|
|
|
|
if err := h.adminService.DeleteOperator(c.Context(), telegramID); err != nil {
|
|
log.Printf("ERROR: Failed to delete operator: %v", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(Response{
|
|
Success: false,
|
|
Message: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(Response{
|
|
Success: true,
|
|
Message: "Operator deleted successfully",
|
|
})
|
|
}
|
|
|
|
// GetAnalytics handles the request to get analytics data
|
|
// @Summary Get analytics data
|
|
// @Description Returns analytics data including user statistics, rewards, and quiz performance (admin/operator only)
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} object{success=bool,message=string,data=object}
|
|
// @Failure 500 {object} object{success=bool,message=string}
|
|
// @Router /api/admin/analytics [get]
|
|
// @Security ApiKeyAuth
|
|
func (h *AdminHandler) GetAnalytics(c *fiber.Ctx) error {
|
|
analytics, err := h.adminService.GetAnalytics(c.Context())
|
|
if err != nil {
|
|
log.Printf("ERROR: Failed to get analytics: %v", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(Response{
|
|
Success: false,
|
|
Message: "Failed to retrieve analytics",
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(Response{
|
|
Success: true,
|
|
Message: "Analytics retrieved successfully",
|
|
Data: analytics,
|
|
})
|
|
}
|