B
last analyzed

Complexity

Conditions 8

Size

Total Lines 40
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 28
nop 3
dl 0
loc 40
rs 7.3333
c 0
b 0
f 0
1
package repositories
2
3
import (
4
	"context"
5
	"errors"
6
	"fmt"
7
	"time"
8
9
	"github.com/cockroachdb/cockroach-go/v2/crdb/crdbgorm"
10
	"github.com/google/uuid"
11
12
	"github.com/NdoleStudio/httpsms/pkg/entities"
13
	"github.com/NdoleStudio/httpsms/pkg/telemetry"
14
	"github.com/palantir/stacktrace"
15
	"gorm.io/gorm"
16
)
17
18
// gormPhoneNotificationRepository is responsible for persisting entities.PhoneNotification
19
type gormPhoneNotificationRepository struct {
20
	logger telemetry.Logger
21
	tracer telemetry.Tracer
22
	db     *gorm.DB
23
}
24
25
// NewGormPhoneNotificationRepository creates the GORM version of the PhoneNotificationRepository
26
func NewGormPhoneNotificationRepository(
27
	logger telemetry.Logger,
28
	tracer telemetry.Tracer,
29
	db *gorm.DB,
30
) PhoneNotificationRepository {
31
	return &gormPhoneNotificationRepository{
32
		logger: logger.WithService(fmt.Sprintf("%T", &gormHeartbeatRepository{})),
33
		tracer: tracer,
34
		db:     db,
35
	}
36
}
37
38
func (repository *gormPhoneNotificationRepository) DeleteAllForUser(ctx context.Context, userID entities.UserID) error {
39
	ctx, span := repository.tracer.Start(ctx)
40
	defer span.End()
41
42
	if err := repository.db.WithContext(ctx).Where("user_id = ?", userID).Delete(&entities.PhoneNotification{}).Error; err != nil {
43
		msg := fmt.Sprintf("cannot delete all [%T] for user with ID [%s]", &entities.PhoneNotification{}, userID)
44
		return repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
45
	}
46
47
	return nil
48
}
49
50
// UpdateStatus of an entities.PhoneNotification
51
func (repository *gormPhoneNotificationRepository) UpdateStatus(ctx context.Context, notificationID uuid.UUID, status entities.PhoneNotificationStatus) error {
52
	ctx, span := repository.tracer.Start(ctx)
53
	defer span.End()
54
55
	err := repository.db.
56
		WithContext(ctx).
57
		Model(&entities.PhoneNotification{ID: notificationID}).
58
		Update("status", status).
59
		Error
60
	if err != nil {
61
		msg := fmt.Sprintf("cannot update notification [%s] with status [%s]", notificationID, status)
62
		return repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
63
	}
64
65
	return nil
66
}
67
68
// Schedule a notification to be sent in the future
69
func (repository *gormPhoneNotificationRepository) Schedule(ctx context.Context, messagesPerMinute uint, notification *entities.PhoneNotification) error {
70
	ctx, span := repository.tracer.Start(ctx)
71
	defer span.End()
72
73
	if messagesPerMinute == 0 {
74
		return repository.insert(ctx, notification)
75
	}
76
77
	err := crdbgorm.ExecuteTx(ctx, repository.db, nil, func(tx *gorm.DB) error {
78
		lastNotification := new(entities.PhoneNotification)
79
		err := tx.WithContext(ctx).
80
			Where("phone_id = ?", notification.PhoneID).
81
			Order("scheduled_at desc").
82
			First(lastNotification).
83
			Error
84
		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
85
			msg := fmt.Sprintf("cannot fetch last notification with phone ID [%s]", notification.PhoneID)
86
			return stacktrace.Propagate(err, msg)
87
		}
88
89
		notification.ScheduledAt = time.Now().UTC()
90
		if err == nil {
91
			notification.ScheduledAt = repository.maxTime(
92
				time.Now().UTC(),
93
				lastNotification.ScheduledAt.Add(time.Duration(60/messagesPerMinute)*time.Second),
94
			)
95
		}
96
97
		if err = tx.WithContext(ctx).Create(notification).Error; err != nil {
98
			msg := fmt.Sprintf("cannot create new notification with id [%s] and schedule [%s]", notification.ID, notification.ScheduledAt.String())
99
			return stacktrace.Propagate(err, msg)
100
		}
101
		return nil
102
	})
103
	if err != nil {
104
		msg := fmt.Sprintf("cannot schedule phone notification with ID [%s]", notification.ID)
105
		return stacktrace.Propagate(err, msg)
106
	}
107
108
	return nil
109
}
110
111
func (repository *gormPhoneNotificationRepository) maxTime(a, b time.Time) time.Time {
112
	if a.Unix() > b.Unix() {
113
		return a
114
	}
115
	return b
116
}
117
118
func (repository *gormPhoneNotificationRepository) insert(ctx context.Context, notification *entities.PhoneNotification) error {
119
	ctx, span := repository.tracer.Start(ctx)
120
	defer span.End()
121
122
	err := repository.db.WithContext(ctx).Create(notification).Error
123
	if err != nil {
124
		msg := fmt.Sprintf("cannot store notification with id [%s]", notification.ID)
125
		return repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
126
	}
127
	return nil
128
}
129