mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
591 lines
18 KiB
Go
591 lines
18 KiB
Go
package controllers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"fotbal-club/internal/models"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type TicketController struct {
|
|
DB *gorm.DB
|
|
}
|
|
|
|
func NewTicketController(db *gorm.DB) *TicketController {
|
|
return &TicketController{DB: db}
|
|
}
|
|
|
|
// Local type definitions for API responses
|
|
type AvailableTicketTypeResponse struct {
|
|
TicketType models.TicketType `json:"ticket_type"`
|
|
PriceCents int64 `json:"price_cents"`
|
|
MaxQuantity *int `json:"max_quantity"`
|
|
AvailableQuantity int `json:"available_quantity"`
|
|
TotalCapacity int `json:"total_capacity"`
|
|
SaleStatus string `json:"sale_status"`
|
|
}
|
|
|
|
type CampaignTicketTypeRequest struct {
|
|
TicketTypeID uint `json:"ticket_type_id" binding:"required"`
|
|
PriceCents *int64 `json:"price_cents"`
|
|
MaxQuantity *int `json:"max_quantity"`
|
|
Capacity int `json:"capacity" binding:"required,min=0"`
|
|
}
|
|
|
|
// GET /api/v1/tickets/campaigns - List all active ticket campaigns
|
|
func (tc *TicketController) GetCampaigns(c *gin.Context) {
|
|
var campaigns []models.TicketCampaign
|
|
|
|
query := tc.DB.Preload("CampaignTicketTypes.TicketType").
|
|
Preload("TicketTypes").
|
|
Where("active = ? AND deleted_at IS NULL", true)
|
|
|
|
// Filter by match if specified
|
|
if matchID := c.Query("match_id"); matchID != "" {
|
|
query = query.Where("external_match_id = ?", matchID)
|
|
}
|
|
|
|
// Filter by date range
|
|
if from := c.Query("from"); from != "" {
|
|
if fromDate, err := time.Parse("2006-01-02", from); err == nil {
|
|
query = query.Where("match_date_time >= ?", fromDate)
|
|
}
|
|
}
|
|
if to := c.Query("to"); to != "" {
|
|
if toDate, err := time.Parse("2006-01-02", to); err == nil {
|
|
query = query.Where("match_date_time <= ?", toDate)
|
|
}
|
|
}
|
|
|
|
if err := query.Order("match_date_time ASC").Find(&campaigns).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch campaigns"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, campaigns)
|
|
}
|
|
|
|
// GET /api/v1/tickets/campaigns/:id - Get specific campaign with availability
|
|
func (tc *TicketController) GetCampaign(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
var campaign models.TicketCampaign
|
|
if err := tc.DB.Preload("CampaignTicketTypes.TicketType").
|
|
Preload("TicketTypes").
|
|
Where("id = ? AND active = ? AND deleted_at IS NULL", id, true).
|
|
First(&campaign).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Campaign not found"})
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch campaign"})
|
|
}
|
|
return
|
|
}
|
|
|
|
// Load availability for each ticket type
|
|
var availabilities []models.TicketAvailability
|
|
tc.DB.Where("campaign_id = ?", campaign.ID).Find(&availabilities)
|
|
|
|
availabilityMap := make(map[uint]models.TicketAvailability)
|
|
for _, avail := range availabilities {
|
|
availabilityMap[avail.TicketTypeID] = avail
|
|
}
|
|
|
|
// Build response with availability
|
|
type CampaignResponse struct {
|
|
models.TicketCampaign
|
|
AvailableTickets []AvailableTicketTypeResponse `json:"available_tickets"`
|
|
}
|
|
|
|
response := CampaignResponse{TicketCampaign: campaign}
|
|
|
|
for _, ctt := range campaign.CampaignTicketTypes {
|
|
avail := availabilityMap[ctt.TicketTypeID]
|
|
price := ctt.PriceCents
|
|
if price == nil {
|
|
price = &ctt.TicketType.PriceCents
|
|
}
|
|
|
|
maxQty := ctt.MaxQuantity
|
|
if maxQty == nil {
|
|
maxQty = &ctt.TicketType.MaxTicketsPerOrder
|
|
}
|
|
|
|
availableQty := avail.TotalCapacity - avail.SoldQuantity - avail.ReservedQuantity
|
|
saleStatus := "available"
|
|
if time.Now().Before(campaign.SaleStartTime) {
|
|
saleStatus = "upcoming"
|
|
} else if time.Now().After(campaign.SaleEndTime) {
|
|
saleStatus = "ended"
|
|
} else if availableQty <= 0 {
|
|
saleStatus = "sold_out"
|
|
}
|
|
|
|
response.AvailableTickets = append(response.AvailableTickets, AvailableTicketTypeResponse{
|
|
TicketType: ctt.TicketType,
|
|
PriceCents: *price,
|
|
MaxQuantity: maxQty,
|
|
AvailableQuantity: availableQty,
|
|
TotalCapacity: avail.TotalCapacity,
|
|
SaleStatus: saleStatus,
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GET /api/v1/tickets/available - Get available tickets for public
|
|
func (tc *TicketController) GetAvailableTickets(c *gin.Context) {
|
|
var tickets []models.AvailableTicketView
|
|
|
|
query := tc.DB.Where("sale_status = ?", "available")
|
|
|
|
// Filter by match if specified
|
|
if matchID := c.Query("match_id"); matchID != "" {
|
|
query = query.Where("external_match_id = ?", matchID)
|
|
}
|
|
|
|
// Filter by competition if specified
|
|
if competition := c.Query("competition"); competition != "" {
|
|
query = query.Where("competition_code = ?", competition)
|
|
}
|
|
|
|
if err := query.Order("match_date_time ASC, campaign_id ASC, display_order ASC").Find(&tickets).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch available tickets"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, tickets)
|
|
}
|
|
|
|
// POST /api/v1/tickets/reserve - Reserve tickets (before payment)
|
|
func (tc *TicketController) ReserveTickets(c *gin.Context) {
|
|
type ReserveRequest struct {
|
|
CampaignID uint `json:"campaign_id" binding:"required"`
|
|
TicketTypeID uint `json:"ticket_type_id" binding:"required"`
|
|
Quantity int `json:"quantity" binding:"required,min=1"`
|
|
HolderName string `json:"holder_name" binding:"required"`
|
|
HolderEmail string `json:"holder_email" binding:"required,email"`
|
|
HolderPhone string `json:"holder_phone"`
|
|
}
|
|
|
|
var req ReserveRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Validate campaign and ticket type
|
|
var campaign models.TicketCampaign
|
|
if err := tc.DB.Where("id = ? AND active = ? AND deleted_at IS NULL", req.CampaignID, true).First(&campaign).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Campaign not found"})
|
|
return
|
|
}
|
|
|
|
// Check sale time window
|
|
now := time.Now()
|
|
if now.Before(campaign.SaleStartTime) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Sale has not started yet"})
|
|
return
|
|
}
|
|
if now.After(campaign.SaleEndTime) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Sale has ended"})
|
|
return
|
|
}
|
|
|
|
// Get campaign ticket type with overrides
|
|
var ctt models.CampaignTicketType
|
|
if err := tc.DB.Where("campaign_id = ? AND ticket_type_id = ?", req.CampaignID, req.TicketTypeID).
|
|
Preload("TicketType").Preload("Campaign").First(&ctt).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Ticket type not found in this campaign"})
|
|
return
|
|
}
|
|
|
|
// Check quantity limits
|
|
maxQty := ctt.MaxQuantity
|
|
if maxQty == nil {
|
|
maxQty = &ctt.TicketType.MaxTicketsPerOrder
|
|
}
|
|
if req.Quantity > *maxQty {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Maximum %d tickets per order", *maxQty)})
|
|
return
|
|
}
|
|
|
|
// Check availability
|
|
var availability models.TicketAvailability
|
|
if err := tc.DB.Where("campaign_id = ? AND ticket_type_id = ?", req.CampaignID, req.TicketTypeID).First(&availability).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Availability not found"})
|
|
return
|
|
}
|
|
|
|
availableQty := availability.TotalCapacity - availability.SoldQuantity - availability.ReservedQuantity
|
|
if req.Quantity > availableQty {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Not enough tickets available"})
|
|
return
|
|
}
|
|
|
|
// Get price
|
|
price := ctt.PriceCents
|
|
if price == nil {
|
|
price = &ctt.TicketType.PriceCents
|
|
}
|
|
|
|
// Create reservation
|
|
ticket := models.Ticket{
|
|
CampaignID: req.CampaignID,
|
|
TicketTypeID: req.TicketTypeID,
|
|
HolderName: req.HolderName,
|
|
HolderEmail: req.HolderEmail,
|
|
HolderPhone: req.HolderPhone,
|
|
Quantity: req.Quantity,
|
|
UnitPriceCents: *price,
|
|
TotalPriceCents: int64(req.Quantity) * *price,
|
|
Currency: ctt.TicketType.Currency,
|
|
Status: "reserved",
|
|
}
|
|
|
|
// Use transaction for atomic operations
|
|
tx := tc.DB.Begin()
|
|
|
|
// Create ticket
|
|
if err := tx.Create(&ticket).Error; err != nil {
|
|
tx.Rollback()
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create reservation"})
|
|
return
|
|
}
|
|
|
|
// Update availability
|
|
if err := tx.Model(&availability).Update("reserved_quantity", gorm.Expr("reserved_quantity + ?", req.Quantity)).Error; err != nil {
|
|
tx.Rollback()
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update availability"})
|
|
return
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
// Send confirmation email (async)
|
|
go func() {
|
|
// TODO: Implement ticket reservation email template
|
|
// For now, just log the action
|
|
fmt.Printf("Ticket reservation email sent to %s for ticket ID %d\n", req.HolderEmail, ticket.ID)
|
|
}()
|
|
|
|
c.JSON(http.StatusCreated, ticket)
|
|
}
|
|
|
|
// POST /api/v1/tickets/:id/confirm - Confirm ticket reservation (after payment)
|
|
func (tc *TicketController) ConfirmTicket(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
var ticket models.Ticket
|
|
if err := tc.DB.Where("id = ? AND status = ?", id, "reserved").First(&ticket).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Ticket reservation not found"})
|
|
return
|
|
}
|
|
|
|
// Update ticket status
|
|
if err := tc.DB.Model(&ticket).Updates(map[string]interface{}{
|
|
"status": "paid",
|
|
}).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to confirm ticket"})
|
|
return
|
|
}
|
|
|
|
// Update availability
|
|
if err := tc.DB.Model(&models.TicketAvailability{}).
|
|
Where("campaign_id = ? AND ticket_type_id = ?", ticket.CampaignID, ticket.TicketTypeID).
|
|
Updates(map[string]interface{}{
|
|
"sold_quantity": gorm.Expr("sold_quantity + ?", ticket.Quantity),
|
|
"reserved_quantity": gorm.Expr("reserved_quantity - ?", ticket.Quantity),
|
|
}).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update availability"})
|
|
return
|
|
}
|
|
|
|
// Send ticket email (async)
|
|
go func() {
|
|
// TODO: Implement ticket confirmation email with barcode/QR
|
|
// For now, just log the action
|
|
fmt.Printf("Ticket confirmation email sent to %s for ticket ID %d\n", ticket.HolderEmail, ticket.ID)
|
|
}()
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Ticket confirmed", "ticket": ticket})
|
|
}
|
|
|
|
// POST /api/v1/tickets/:id/validate - Validate ticket (for entry)
|
|
func (tc *TicketController) ValidateTicket(c *gin.Context) {
|
|
type ValidateRequest struct {
|
|
Barcode string `json:"barcode" binding:"required"`
|
|
UsedBy string `json:"used_by"`
|
|
}
|
|
|
|
var req ValidateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var ticket models.Ticket
|
|
if err := tc.DB.Where("barcode = ? AND status = ?", req.Barcode, "paid").First(&ticket).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Ticket not found or already used"})
|
|
return
|
|
}
|
|
|
|
// Check if already used
|
|
if ticket.UsedAt != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ticket already used", "used_at": ticket.UsedAt})
|
|
return
|
|
}
|
|
|
|
// Mark as used
|
|
now := time.Now()
|
|
if err := tc.DB.Model(&ticket).Updates(map[string]interface{}{
|
|
"used_at": now,
|
|
"used_by": req.UsedBy,
|
|
"status": "used",
|
|
}).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to validate ticket"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Ticket validated successfully",
|
|
"ticket": ticket,
|
|
"used_at": now,
|
|
})
|
|
}
|
|
|
|
// GET /api/v1/admin/tickets/campaigns - Admin: List all campaigns
|
|
func (tc *TicketController) AdminGetCampaigns(c *gin.Context) {
|
|
var campaigns []models.TicketCampaign
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
|
|
offset := (page - 1) * limit
|
|
|
|
if err := tc.DB.Preload("CampaignTicketTypes.TicketType").
|
|
Order("created_at DESC").
|
|
Limit(limit).Offset(offset).
|
|
Find(&campaigns).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch campaigns"})
|
|
return
|
|
}
|
|
|
|
var total int64
|
|
tc.DB.Model(&models.TicketCampaign{}).Count(&total)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"campaigns": campaigns,
|
|
"total": total,
|
|
"page": page,
|
|
"limit": limit,
|
|
})
|
|
}
|
|
|
|
// POST /api/v1/admin/tickets/campaigns - Admin: Create campaign
|
|
func (tc *TicketController) AdminCreateCampaign(c *gin.Context) {
|
|
type CreateCampaignRequest struct {
|
|
Title string `json:"title" binding:"required"`
|
|
Description string `json:"description"`
|
|
ExternalMatchID *string `json:"external_match_id"`
|
|
CompetitionCode *string `json:"competition_code"`
|
|
MatchDateTime *time.Time `json:"match_date_time"`
|
|
HomeTeam *string `json:"home_team"`
|
|
AwayTeam *string `json:"away_team"`
|
|
Venue *string `json:"venue"`
|
|
SaleStartTime time.Time `json:"sale_start_time" binding:"required"`
|
|
SaleEndTime time.Time `json:"sale_end_time" binding:"required"`
|
|
MaxTotalTickets *int `json:"max_total_tickets"`
|
|
TicketTypes []CampaignTicketTypeRequest `json:"ticket_types" binding:"required"`
|
|
}
|
|
|
|
type CampaignTicketTypeRequest struct {
|
|
TicketTypeID uint `json:"ticket_type_id" binding:"required"`
|
|
PriceCents *int64 `json:"price_cents"`
|
|
MaxQuantity *int `json:"max_quantity"`
|
|
Capacity int `json:"capacity" binding:"required,min=0"`
|
|
}
|
|
|
|
var req CreateCampaignRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Validate time window
|
|
if req.SaleEndTime.Before(req.SaleStartTime) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Sale end time must be after start time"})
|
|
return
|
|
}
|
|
|
|
// Create campaign
|
|
campaign := models.TicketCampaign{
|
|
Title: req.Title,
|
|
Description: req.Description,
|
|
ExternalMatchID: req.ExternalMatchID,
|
|
CompetitionCode: req.CompetitionCode,
|
|
MatchDateTime: req.MatchDateTime,
|
|
HomeTeam: req.HomeTeam,
|
|
AwayTeam: req.AwayTeam,
|
|
Venue: req.Venue,
|
|
SaleStartTime: req.SaleStartTime,
|
|
SaleEndTime: req.SaleEndTime,
|
|
MaxTotalTickets: req.MaxTotalTickets,
|
|
Active: true,
|
|
}
|
|
|
|
tx := tc.DB.Begin()
|
|
|
|
if err := tx.Create(&campaign).Error; err != nil {
|
|
tx.Rollback()
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create campaign"})
|
|
return
|
|
}
|
|
|
|
// Create campaign ticket types and availability
|
|
for _, ttReq := range req.TicketTypes {
|
|
// Verify ticket type exists
|
|
var ticketType models.TicketType
|
|
if err := tx.First(&ticketType, ttReq.TicketTypeID).Error; err != nil {
|
|
tx.Rollback()
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Ticket type %d not found", ttReq.TicketTypeID)})
|
|
return
|
|
}
|
|
|
|
// Create campaign ticket type
|
|
ctt := models.CampaignTicketType{
|
|
CampaignID: campaign.ID,
|
|
TicketTypeID: ttReq.TicketTypeID,
|
|
PriceCents: ttReq.PriceCents,
|
|
MaxQuantity: ttReq.MaxQuantity,
|
|
}
|
|
if err := tx.Create(&ctt).Error; err != nil {
|
|
tx.Rollback()
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create campaign ticket type"})
|
|
return
|
|
}
|
|
|
|
// Create availability
|
|
availability := models.TicketAvailability{
|
|
CampaignID: campaign.ID,
|
|
TicketTypeID: ttReq.TicketTypeID,
|
|
TotalCapacity: ttReq.Capacity,
|
|
SoldQuantity: 0,
|
|
ReservedQuantity: 0,
|
|
}
|
|
if err := tx.Create(&availability).Error; err != nil {
|
|
tx.Rollback()
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create availability"})
|
|
return
|
|
}
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
// Load full campaign for response
|
|
tc.DB.Preload("CampaignTicketTypes.TicketType").Preload("TicketTypes").First(&campaign, campaign.ID)
|
|
|
|
c.JSON(http.StatusCreated, campaign)
|
|
}
|
|
|
|
// GET /api/v1/admin/tickets/types - Admin: List ticket types
|
|
func (tc *TicketController) AdminGetTicketTypes(c *gin.Context) {
|
|
var types []models.TicketType
|
|
|
|
if err := tc.DB.Where("deleted_at IS NULL").Order("display_order ASC").Find(&types).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch ticket types"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, types)
|
|
}
|
|
|
|
// POST /api/v1/admin/tickets/types - Admin: Create ticket type
|
|
func (tc *TicketController) AdminCreateTicketType(c *gin.Context) {
|
|
var ticketType models.TicketType
|
|
|
|
if err := c.ShouldBindJSON(&ticketType); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := tc.DB.Create(&ticketType).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create ticket type"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, ticketType)
|
|
}
|
|
|
|
// GET /api/v1/tickets/my-tickets - Get current user's tickets
|
|
func (tc *TicketController) GetMyTickets(c *gin.Context) {
|
|
userIDVal, _ := c.Get("userID")
|
|
userID, ok := userIDVal.(uint)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
|
|
return
|
|
}
|
|
|
|
var tickets []models.Ticket
|
|
if err := tc.DB.Where("holder_email IN (SELECT email FROM users WHERE id = ?)", userID).
|
|
Preload("Campaign").
|
|
Preload("TicketType").
|
|
Order("created_at DESC").
|
|
Find(&tickets).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch tickets"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, tickets)
|
|
}
|
|
|
|
// Additional admin methods for routes
|
|
func (tc *TicketController) AdminUpdateCampaign(c *gin.Context) {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
|
}
|
|
|
|
func (tc *TicketController) AdminDeleteCampaign(c *gin.Context) {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
|
}
|
|
|
|
func (tc *TicketController) AdminGetTicketType(c *gin.Context) {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
|
}
|
|
|
|
func (tc *TicketController) AdminUpdateTicketType(c *gin.Context) {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
|
}
|
|
|
|
func (tc *TicketController) AdminDeleteTicketType(c *gin.Context) {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
|
}
|
|
|
|
func (tc *TicketController) AdminGetTickets(c *gin.Context) {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
|
}
|
|
|
|
func (tc *TicketController) AdminGetTicket(c *gin.Context) {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
|
}
|
|
|
|
func (tc *TicketController) AdminUpdateTicketStatus(c *gin.Context) {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
|
}
|
|
|
|
func (tc *TicketController) AdminValidateTicket(c *gin.Context) {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
|
}
|
|
|
|
func (tc *TicketController) AdminGetSalesOverview(c *gin.Context) {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
|
}
|
|
|
|
func (tc *TicketController) AdminExportSales(c *gin.Context) {
|
|
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
|
}
|