diff --git a/handlers/reservation/repository/reservation_repository.go b/handlers/reservation/repository/reservation_repository.go index a283454..9273bef 100644 --- a/handlers/reservation/repository/reservation_repository.go +++ b/handlers/reservation/repository/reservation_repository.go @@ -738,6 +738,21 @@ func (r *ReservationRepository) GetUserReservations(userEmail string, filter str return response, rows.Err() } +func (r *ReservationRepository) GetItemName(itemID int) (string, error) { + query := "SELECT name FROM reservation_element WHERE id_reservation_element = $1" + row := r.DB.QueryRow(query, itemID) + var name string + if err := row.Scan(&name); err != nil { + if errors.Is(err, sql.ErrNoRows) { + utils.LogMessage(utils.LevelWarn, fmt.Sprintf("Item with ID %d does not exist", itemID)) + return "", fmt.Errorf("item with ID %d not found", itemID) + } + utils.LogMessage(utils.LevelError, fmt.Sprintf("Failed to get item name: %v", err)) + return "", err + } + return name, nil +} + func (r *ReservationRepository) SearchItemsAndCategories(search string) ([]models.ReservationCategory, []models.ReservationItem, error) { like := fmt.Sprintf("%%%s%%", strings.ToLower(search)) diff --git a/handlers/reservation/reservation_handler.go b/handlers/reservation/reservation_handler.go index ebe788f..ad6024f 100644 --- a/handlers/reservation/reservation_handler.go +++ b/handlers/reservation/reservation_handler.go @@ -9,16 +9,19 @@ import ( "github.com/gofiber/fiber/v2" "github.com/plugimt/transat-backend/handlers/reservation/repository" "github.com/plugimt/transat-backend/models" + "github.com/plugimt/transat-backend/services" "github.com/plugimt/transat-backend/utils" ) type ReservationHandler struct { ReservationRepository *repository.ReservationRepository + DiscordService *services.DiscordService } -func NewReservationHandler(db *sql.DB) *ReservationHandler { +func NewReservationHandler(db *sql.DB, discordService *services.DiscordService) *ReservationHandler { return &ReservationHandler{ ReservationRepository: repository.NewReservationRepository(db), + DiscordService: discordService, } } @@ -440,6 +443,24 @@ func (h *ReservationHandler) UpdateReservationItem(c *fiber.Ctx) error { "error": "Failed to create reservation", }) } + + if h.DiscordService != nil && res.User != nil { + itemName, err := h.ReservationRepository.GetItemName(itemID) + if err != nil { + utils.LogMessage(utils.LevelWarn, fmt.Sprintf("Failed to get item name for Discord notification: %v", err)) + itemName = "Unknown Item" + } + + startDate := reservationItemTime.StartDate.Format("2006-01-02 15:04:05") + var endDate string + if reservationItemTime.EndDate != nil { + endDate = reservationItemTime.EndDate.Format("2006-01-02 15:04:05") + } + + if err := h.DiscordService.SendReservationCreated(itemName, startDate, endDate, ItemPerSlot, *res.User); err != nil { + utils.LogMessage(utils.LevelWarn, fmt.Sprintf("Failed to send Discord notification for reservation creation: %v", err)) + } + } } else if reservationRequest.EndDate != nil { res, err = h.ReservationRepository.EndReservation(reservationItemTime, itemID, ItemPerSlot, c.Locals("email").(string)) if err != nil { @@ -456,6 +477,21 @@ func (h *ReservationHandler) UpdateReservationItem(c *fiber.Ctx) error { "error": "Failed to end reservation", }) } + + if h.DiscordService != nil && res.User != nil { + itemName, err := h.ReservationRepository.GetItemName(itemID) + if err != nil { + utils.LogMessage(utils.LevelWarn, fmt.Sprintf("Failed to get item name for Discord notification: %v", err)) + itemName = "Unknown Item" + } + + startDate := reservationItemTime.StartDate.Format("2006-01-02 15:04:05") + endDate := reservationItemTime.EndDate.Format("2006-01-02 15:04:05") + + if err := h.DiscordService.SendReservationCancelled(itemName, startDate, endDate, ItemPerSlot, *res.User); err != nil { + utils.LogMessage(utils.LevelWarn, fmt.Sprintf("Failed to send Discord notification for reservation cancellation: %v", err)) + } + } } utils.LogMessage(utils.LevelInfo, "Reservation updated successfully") @@ -670,6 +706,42 @@ func (h *ReservationHandler) DeleteReservationItem(c *fiber.Ctx) error { }) } + if h.DiscordService != nil && deleted { + itemName, err := h.ReservationRepository.GetItemName(itemID) + if err != nil { + utils.LogMessage(utils.LevelWarn, fmt.Sprintf("Failed to get item name for Discord notification: %v", err)) + itemName = "Unknown Item" + } + + userEmail := c.Locals("email").(string) + userInfo := models.ReservationUser{ + Email: userEmail, + } + + // Try to get user details from database + query := "SELECT first_name, last_name, COALESCE(profile_picture, '') FROM newf WHERE email = $1" + row := h.ReservationRepository.DB.QueryRow(query, userEmail) + if err := row.Scan(&userInfo.FirstName, &userInfo.LastName, &userInfo.ProfilePicture); err != nil { + utils.LogMessage(utils.LevelWarn, fmt.Sprintf("Failed to get user details for Discord notification: %v", err)) + userInfo.FirstName = "Unknown" + userInfo.LastName = "User" + } + + startDate := reservationItemTime.StartDate.Format("2006-01-02 15:04:05") + var endDate string + if ItemPerSlot { + var slotDuration time.Duration = time.Hour // default to 1 hour if not set + if reservationItemTime.SlotDuration > 0 { + slotDuration = reservationItemTime.SlotDuration + } + endDate = reservationItemTime.StartDate.Add(slotDuration).Format("2006-01-02 15:04:05") + } + + if err := h.DiscordService.SendReservationCancelled(itemName, startDate, endDate, ItemPerSlot, userInfo); err != nil { + utils.LogMessage(utils.LevelWarn, fmt.Sprintf("Failed to send Discord notification for reservation deletion: %v", err)) + } + } + utils.LogMessage(utils.LevelInfo, "Reservation deleted successfully") utils.LogFooter() return c.JSON(fiber.Map{ diff --git a/main.go b/main.go index 151f8cc..e5a5d3f 100644 --- a/main.go +++ b/main.go @@ -82,6 +82,8 @@ func main() { // Discord webhook notifications discordService := services.NewDiscordService(os.Getenv("DISCORD_WEBHOOK_URL")) + // Discord reservation-specific webhook (falls back to default if unset) + reservationDiscordService := services.NewDiscordService(os.Getenv("DISCORD_RESERVATION_ALERT_WEBHOOK")) restHandler := restaurantHandler.NewRestaurantHandler(db, translationService, notificationService) @@ -168,7 +170,7 @@ func main() { routes.SetupWashingMachineRoutes(app) routes.SetupWeatherRoutes(app, weatherHandler) routes.SetupEventRoutes(app, eventHandler) - routes.SetupReservationRoutes(app, db) + routes.SetupReservationRoutes(app, db, reservationDiscordService) routes.SetupBassineRoutes(app, db) routes.SetupAdminRoutes(app, db) diff --git a/routes/reservation.go b/routes/reservation.go index 85aca33..314f617 100644 --- a/routes/reservation.go +++ b/routes/reservation.go @@ -6,11 +6,12 @@ import ( "github.com/gofiber/fiber/v2" "github.com/plugimt/transat-backend/handlers/reservation" // Import the reservation handlers "github.com/plugimt/transat-backend/middlewares" + "github.com/plugimt/transat-backend/services" ) -func SetupReservationRoutes(router fiber.Router, db *sql.DB) { +func SetupReservationRoutes(router fiber.Router, db *sql.DB, discordService *services.DiscordService) { // Initialize Reservation Handler - reservationHandler := reservation.NewReservationHandler(db) + reservationHandler := reservation.NewReservationHandler(db, discordService) reservationGroup := router.Group("/reservation", middlewares.JWTMiddleware) diff --git a/services/discord.go b/services/discord.go index 7cb6967..2b0a41f 100644 --- a/services/discord.go +++ b/services/discord.go @@ -34,11 +34,21 @@ type discordEmbedField struct { } type discordEmbed struct { - Title string `json:"title"` - Description string `json:"description,omitempty"` - Color int `json:"color,omitempty"` - Fields []discordEmbedField `json:"fields,omitempty"` - Timestamp string `json:"timestamp,omitempty"` + Title string `json:"title"` + Description string `json:"description,omitempty"` + Color int `json:"color,omitempty"` + Fields []discordEmbedField `json:"fields,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + Thumbnail *discordEmbedThumbnail `json:"thumbnail,omitempty"` + Image *discordEmbedImage `json:"image,omitempty"` +} + +type discordEmbedThumbnail struct { + URL string `json:"url"` +} + +type discordEmbedImage struct { + URL string `json:"url"` } type discordPayload struct { @@ -111,3 +121,67 @@ func (ds *DiscordService) SendUserVerified(user models.Newf, numberOfAccounts in } return ds.sendEmbed(embed) } + +func (ds *DiscordService) SendReservationCreated(itemName, startDate, endDate string, isSlot bool, user models.ReservationUser) error { + var slotType string + if isSlot { + slotType = "Slot-based" + } else { + slotType = "Open-ended" + } + + fields := []discordEmbedField{ + {Name: "Item", Value: safe(itemName, "N/A"), Inline: false}, + {Name: "Type", Value: slotType, Inline: true}, + {Name: "Start Date", Value: safe(startDate, "N/A"), Inline: true}, + {Name: "User", Value: fmt.Sprintf("%s %s", safe(user.FirstName, ""), safe(user.LastName, "")), Inline: true}, + {Name: "Email", Value: safe(user.Email, "N/A"), Inline: true}, + } + + if endDate != "" { + fields = append(fields, discordEmbedField{Name: "End Date", Value: endDate, Inline: true}) + } + + embed := discordEmbed{ + Title: "New Reservation Created", + Color: 0x4CAF50, // green + Timestamp: time.Now().UTC().Format(time.RFC3339), + Fields: fields, + } + if user.ProfilePicture != "" { + embed.Thumbnail = &discordEmbedThumbnail{URL: user.ProfilePicture} + } + return ds.sendEmbed(embed) +} + +func (ds *DiscordService) SendReservationCancelled(itemName, startDate, endDate string, isSlot bool, user models.ReservationUser) error { + var slotType string + if isSlot { + slotType = "Slot-based" + } else { + slotType = "Open-ended" + } + + fields := []discordEmbedField{ + {Name: "Item", Value: safe(itemName, "N/A"), Inline: false}, + {Name: "Type", Value: slotType, Inline: true}, + {Name: "Start Date", Value: safe(startDate, "N/A"), Inline: true}, + {Name: "User", Value: fmt.Sprintf("%s %s", safe(user.FirstName, ""), safe(user.LastName, "")), Inline: true}, + {Name: "Email", Value: safe(user.Email, "N/A"), Inline: true}, + } + + if endDate != "" { + fields = append(fields, discordEmbedField{Name: "End Date", Value: endDate, Inline: true}) + } + + embed := discordEmbed{ + Title: "Reservation Cancelled", + Color: 0xF44336, // red + Timestamp: time.Now().UTC().Format(time.RFC3339), + Fields: fields, + } + if user.ProfilePicture != "" { + embed.Thumbnail = &discordEmbedThumbnail{URL: user.ProfilePicture} + } + return ds.sendEmbed(embed) +}