Passed
Push — main ( d604fe...01ace3 )
by Acho
01:17
created

  A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nop 2
dl 0
loc 5
rs 10
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
// UpdateStatus of an entities.PhoneNotification
39
func (repository gormPhoneNotificationRepository) UpdateStatus(ctx context.Context, notificationID uuid.UUID, status entities.PhoneNotificationStatus) error {
40
	ctx, span := repository.tracer.Start(ctx)
41
	defer span.End()
42
43
	err := repository.db.
44
		WithContext(ctx).
45
		Model(&entities.PhoneNotification{ID: notificationID}).
46
		Update("status", status).
47
		Error
48
	if err != nil {
49
		msg := fmt.Sprintf("cannot update notification [%s] with status [%s]", notificationID, status)
50
		return repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
51
	}
52
53
	return nil
54
}
55
56
// Schedule a notification to be sent in the future
57
func (repository gormPhoneNotificationRepository) Schedule(ctx context.Context, messagesPerMinute uint, notification *entities.PhoneNotification) error {
58
	ctx, span := repository.tracer.Start(ctx)
59
	defer span.End()
60
61
	if messagesPerMinute == 0 {
62
		return repository.insert(ctx, notification)
63
	}
64
65
	err := crdbgorm.ExecuteTx(ctx, repository.db, nil, func(tx *gorm.DB) error {
66
		lastNotification := new(entities.PhoneNotification)
67
		err := tx.WithContext(ctx).
68
			Where("phone_id = ?", notification.PhoneID).
69
			Order("scheduled_at desc").
70
			First(lastNotification).
71
			Error
72
		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
73
			msg := fmt.Sprintf("cannot fetch last notification with phone ID [%s]", notification.PhoneID)
74
			return stacktrace.Propagate(err, msg)
75
		}
76
77
		notification.ScheduledAt = time.Now().UTC()
78
		if err == nil {
79
			notification.ScheduledAt = repository.maxTime(
80
				time.Now().UTC(),
81
				lastNotification.ScheduledAt.Add(time.Duration(60/messagesPerMinute)*time.Second),
82
			)
83
		}
84
85
		if err = tx.WithContext(ctx).Create(notification).Error; err != nil {
86
			msg := fmt.Sprintf("cannot create new notification with id [%s] and schedule [%s]", notification.ID, notification.ScheduledAt.String())
87
			return stacktrace.Propagate(err, msg)
88
		}
89
		return nil
90
	})
91
	if err != nil {
92
		msg := fmt.Sprintf("cannot schedule phone notification with ID [%s]", notification.ID)
93
		return stacktrace.Propagate(err, msg)
94
	}
95
96
	return nil
97
}
98
99
func (repository *gormPhoneNotificationRepository) maxTime(a, b time.Time) time.Time {
100
	if a.Unix() > b.Unix() {
101
		return a
102
	}
103
	return b
104
}
105
106
func (repository *gormPhoneNotificationRepository) insert(ctx context.Context, notification *entities.PhoneNotification) error {
107
	ctx, span := repository.tracer.Start(ctx)
108
	defer span.End()
109
110
	err := repository.db.WithContext(ctx).Create(notification).Error
111
	if err != nil {
112
		msg := fmt.Sprintf("cannot store notification with id [%s]", notification.ID)
113
		return repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
114
	}
115
	return nil
116
}
117