package repository import ( "context" "fmt" "time" "apskel-pos-be/internal/entities" "gorm.io/gorm" ) type OtpRepository interface { CreateOtpSession(ctx context.Context, otpSession *entities.OtpSession) error GetOtpSessionByToken(ctx context.Context, token string) (*entities.OtpSession, error) GetOtpSessionByPhoneAndPurpose(ctx context.Context, phoneNumber string, purpose string) (*entities.OtpSession, error) GetLastOtpSessionByPhoneAndPurpose(ctx context.Context, phoneNumber string, purpose string) (*entities.OtpSession, error) GetOtpSessionByRegistrationToken(ctx context.Context, registrationToken string) (*entities.OtpSession, error) UpdateOtpSession(ctx context.Context, otpSession *entities.OtpSession) error DeleteOtpSession(ctx context.Context, token string) error DeleteExpiredOtpSessions(ctx context.Context) error InvalidateOtpSessionsByPhone(ctx context.Context, phoneNumber string, purpose string) error } type otpRepository struct { db *gorm.DB } func NewOtpRepository(db *gorm.DB) OtpRepository { return &otpRepository{ db: db, } } func (r *otpRepository) CreateOtpSession(ctx context.Context, otpSession *entities.OtpSession) error { if err := r.db.WithContext(ctx).Create(otpSession).Error; err != nil { return fmt.Errorf("failed to create OTP session: %w", err) } return nil } func (r *otpRepository) GetOtpSessionByToken(ctx context.Context, token string) (*entities.OtpSession, error) { var otpSession entities.OtpSession if err := r.db.WithContext(ctx).Where("token = ?", token).First(&otpSession).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, nil // OTP session not found, not an error } return nil, fmt.Errorf("failed to get OTP session by token: %w", err) } return &otpSession, nil } func (r *otpRepository) GetOtpSessionByPhoneAndPurpose(ctx context.Context, phoneNumber string, purpose string) (*entities.OtpSession, error) { var otpSession entities.OtpSession if err := r.db.WithContext(ctx).Where("phone_number = ? AND purpose = ? AND is_used = false AND expires_at > ?", phoneNumber, purpose, time.Now()).Order("created_at DESC").First(&otpSession).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, nil // No active OTP session found } return nil, fmt.Errorf("failed to get OTP session by phone and purpose: %w", err) } return &otpSession, nil } func (r *otpRepository) GetLastOtpSessionByPhoneAndPurpose(ctx context.Context, phoneNumber string, purpose string) (*entities.OtpSession, error) { var otpSession entities.OtpSession if err := r.db.WithContext(ctx).Where("phone_number = ? AND purpose = ?", phoneNumber, purpose).Order("created_at DESC").First(&otpSession).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, nil // No OTP session found } return nil, fmt.Errorf("failed to get last OTP session by phone and purpose: %w", err) } return &otpSession, nil } func (r *otpRepository) GetOtpSessionByRegistrationToken(ctx context.Context, registrationToken string) (*entities.OtpSession, error) { var otpSession entities.OtpSession if err := r.db.WithContext(ctx).Where("metadata->>'registration_token' = ? AND purpose = ?", registrationToken, "registration").First(&otpSession).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, nil // OTP session not found } return nil, fmt.Errorf("failed to get OTP session by registration token: %w", err) } return &otpSession, nil } func (r *otpRepository) UpdateOtpSession(ctx context.Context, otpSession *entities.OtpSession) error { if err := r.db.WithContext(ctx).Save(otpSession).Error; err != nil { return fmt.Errorf("failed to update OTP session: %w", err) } return nil } func (r *otpRepository) DeleteOtpSession(ctx context.Context, token string) error { if err := r.db.WithContext(ctx).Where("token = ?", token).Delete(&entities.OtpSession{}).Error; err != nil { return fmt.Errorf("failed to delete OTP session: %w", err) } return nil } func (r *otpRepository) DeleteExpiredOtpSessions(ctx context.Context) error { if err := r.db.WithContext(ctx).Where("expires_at < ?", time.Now()).Delete(&entities.OtpSession{}).Error; err != nil { return fmt.Errorf("failed to delete expired OTP sessions: %w", err) } return nil } func (r *otpRepository) InvalidateOtpSessionsByPhone(ctx context.Context, phoneNumber string, purpose string) error { if err := r.db.WithContext(ctx).Model(&entities.OtpSession{}).Where("phone_number = ? AND purpose = ? AND is_used = false", phoneNumber, purpose).Update("is_used", true).Error; err != nil { return fmt.Errorf("failed to invalidate OTP sessions: %w", err) } return nil }