refactor(models): Enhance notification and schedule model structures
- Add comprehensive validation and error handling for Notification model - Introduce strongly typed enums for notification types and statuses - Implement GORM hooks for pre-create and pre-update validation - Add helper methods for notification retry and sending logic - Improve Schedule model with validation, availability checks, and duration methods - Add constraints and validation for schedule time slots - Enhance model relationships with foreign key references - Implement additional utility methods for schedule management Improves data integrity, adds robust validation, and provides more comprehensive model behaviors for notifications and schedules.
This commit is contained in:
parent
488be7b8ef
commit
c9d20afda8
@ -1,19 +1,117 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NotificationType represents the different types of notifications
|
||||
type NotificationType string
|
||||
|
||||
const (
|
||||
NotificationTypeWelcome NotificationType = "welcome"
|
||||
NotificationTypePaymentSuccess NotificationType = "payment_success"
|
||||
NotificationTypePaymentFailed NotificationType = "payment_failed"
|
||||
NotificationTypeMeetingInfo NotificationType = "meeting_info"
|
||||
NotificationTypeReminder NotificationType = "reminder"
|
||||
NotificationTypeCancellation NotificationType = "cancellation"
|
||||
NotificationTypeReschedule NotificationType = "reschedule"
|
||||
)
|
||||
|
||||
// NotificationStatus represents the status of a notification
|
||||
type NotificationStatus string
|
||||
|
||||
const (
|
||||
NotificationStatusPending NotificationStatus = "pending"
|
||||
NotificationStatusSent NotificationStatus = "sent"
|
||||
NotificationStatusFailed NotificationStatus = "failed"
|
||||
NotificationStatusSkipped NotificationStatus = "skipped"
|
||||
)
|
||||
|
||||
// Notification represents email notifications
|
||||
type Notification struct {
|
||||
gorm.Model
|
||||
UserID uint `json:"user_id"`
|
||||
Type string `json:"type"` // welcome, payment_success, payment_failed, meeting_info, reminder
|
||||
Subject string `json:"subject"`
|
||||
Body string `json:"body"`
|
||||
SentAt *time.Time `json:"sent_at"`
|
||||
Status string `json:"status" gorm:"default:'pending'"` // pending, sent, failed
|
||||
ScheduledAt *time.Time `json:"scheduled_at"`
|
||||
UserID uint `json:"user_id" gorm:"index" validate:"required"`
|
||||
User User `json:"user" gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
BookingID *uint `json:"booking_id" gorm:"index"`
|
||||
Booking *Booking `json:"booking" gorm:"foreignKey:BookingID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
|
||||
Type NotificationType `json:"type" gorm:"not null;size:50;index" validate:"required,oneof=welcome payment_success payment_failed meeting_info reminder cancellation reschedule"`
|
||||
Subject string `json:"subject" gorm:"not null;size:255" validate:"required,max=255"`
|
||||
Body string `json:"body" gorm:"type:text" validate:"required"`
|
||||
SentAt *time.Time `json:"sent_at" gorm:"index"`
|
||||
Status NotificationStatus `json:"status" gorm:"default:'pending';size:20;index" validate:"required,oneof=pending sent failed skipped"`
|
||||
ScheduledAt *time.Time `json:"scheduled_at" gorm:"index"`
|
||||
RetryCount int `json:"retry_count" gorm:"default:0;check:retry_count >= 0"`
|
||||
ErrorMsg string `json:"error_msg" gorm:"size:500"`
|
||||
}
|
||||
|
||||
// BeforeCreate is a GORM hook that runs before creating a notification record
|
||||
func (n *Notification) BeforeCreate(tx *gorm.DB) error {
|
||||
// Set default status if not provided
|
||||
if n.Status == "" {
|
||||
n.Status = NotificationStatusPending
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if n.Subject == "" {
|
||||
return errors.New("notification subject is required")
|
||||
}
|
||||
|
||||
if n.Body == "" {
|
||||
return errors.New("notification body is required")
|
||||
}
|
||||
|
||||
// If scheduled at is not set, schedule for immediate sending
|
||||
if n.ScheduledAt == nil {
|
||||
now := time.Now()
|
||||
n.ScheduledAt = &now
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate is a GORM hook that runs before updating a notification record
|
||||
func (n *Notification) BeforeUpdate(tx *gorm.DB) error {
|
||||
// If status is being set to sent, set SentAt timestamp
|
||||
if n.Status == NotificationStatusSent && n.SentAt == nil {
|
||||
now := time.Now()
|
||||
n.SentAt = &now
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsReadyToSend checks if the notification is ready to be sent
|
||||
func (n *Notification) IsReadyToSend() bool {
|
||||
if n.Status != NotificationStatusPending {
|
||||
return false
|
||||
}
|
||||
|
||||
if n.ScheduledAt == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return n.ScheduledAt.Before(time.Now()) || n.ScheduledAt.Equal(time.Now())
|
||||
}
|
||||
|
||||
// CanRetry checks if the notification can be retried (max 3 retries)
|
||||
func (n *Notification) CanRetry() bool {
|
||||
return n.Status == NotificationStatusFailed && n.RetryCount < 3
|
||||
}
|
||||
|
||||
// MarkAsSent marks the notification as successfully sent
|
||||
func (n *Notification) MarkAsSent() {
|
||||
n.Status = NotificationStatusSent
|
||||
now := time.Now()
|
||||
n.SentAt = &now
|
||||
n.ErrorMsg = ""
|
||||
}
|
||||
|
||||
// MarkAsFailed marks the notification as failed with an error message
|
||||
func (n *Notification) MarkAsFailed(errorMsg string) {
|
||||
n.Status = NotificationStatusFailed
|
||||
n.RetryCount++
|
||||
n.ErrorMsg = errorMsg
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@ -9,9 +10,68 @@ import (
|
||||
// Schedule represents available time slots
|
||||
type Schedule struct {
|
||||
gorm.Model
|
||||
StartTime time.Time `json:"start_time" gorm:"not null"`
|
||||
EndTime time.Time `json:"end_time" gorm:"not null"`
|
||||
StartTime time.Time `json:"start_time" gorm:"not null;index" validate:"required"`
|
||||
EndTime time.Time `json:"end_time" gorm:"not null;index" validate:"required"`
|
||||
IsAvailable bool `json:"is_available" gorm:"default:true"`
|
||||
MaxBookings int `json:"max_bookings" gorm:"default:1"`
|
||||
BookedCount int `json:"booked_count" gorm:"default:0"`
|
||||
MaxBookings int `json:"max_bookings" gorm:"default:1;check:max_bookings > 0" validate:"min=1,max=100"`
|
||||
BookedCount int `json:"booked_count" gorm:"default:0;check:booked_count >= 0" validate:"min=0"`
|
||||
}
|
||||
|
||||
// BeforeCreate is a GORM hook that runs before creating a schedule record
|
||||
func (s *Schedule) BeforeCreate(tx *gorm.DB) error {
|
||||
// Validate that end time is after start time
|
||||
if !s.EndTime.After(s.StartTime) {
|
||||
return errors.New("end time must be after start time")
|
||||
}
|
||||
|
||||
// Validate minimum duration (15 minutes)
|
||||
if s.EndTime.Sub(s.StartTime) < 15*time.Minute {
|
||||
return errors.New("schedule slot must be at least 15 minutes long")
|
||||
}
|
||||
|
||||
// Validate that start time is in the future
|
||||
if s.StartTime.Before(time.Now()) {
|
||||
return errors.New("schedule slot cannot be created in the past")
|
||||
}
|
||||
|
||||
// Set default values
|
||||
if s.MaxBookings == 0 {
|
||||
s.MaxBookings = 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate is a GORM hook that runs before updating a schedule record
|
||||
func (s *Schedule) BeforeUpdate(tx *gorm.DB) error {
|
||||
// Validate that end time is after start time
|
||||
if !s.EndTime.After(s.StartTime) {
|
||||
return errors.New("end time must be after start time")
|
||||
}
|
||||
|
||||
// Validate that booked count doesn't exceed max bookings
|
||||
if s.BookedCount > s.MaxBookings {
|
||||
return errors.New("booked count cannot exceed max bookings")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsAvailableForBooking checks if the schedule slot has availability
|
||||
func (s *Schedule) IsAvailableForBooking() bool {
|
||||
return s.IsAvailable && s.BookedCount < s.MaxBookings && s.StartTime.After(time.Now())
|
||||
}
|
||||
|
||||
// GetRemainingSlots returns the number of remaining booking slots
|
||||
func (s *Schedule) GetRemainingSlots() int {
|
||||
remaining := s.MaxBookings - s.BookedCount
|
||||
if remaining < 0 {
|
||||
return 0
|
||||
}
|
||||
return remaining
|
||||
}
|
||||
|
||||
// Duration returns the duration of the schedule slot
|
||||
func (s *Schedule) Duration() time.Duration {
|
||||
return s.EndTime.Sub(s.StartTime)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user