Passed
Push — main ( f24a32...53c867 )
by Acho
03:11
created

api/pkg/services/phone_api_key_service.go   A

Size/Duplication

Total Lines 201
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 24
eloc 128
dl 0
loc 201
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A services.*PhoneAPIKeyService.RemovePhone 0 23 4
A services.NewPhoneAPIKeyService 0 11 1
A services.*PhoneAPIKeyService.DeleteAllForUser 0 11 2
A services.*PhoneAPIKeyService.Index 0 12 2
A services.*PhoneAPIKeyService.RemovePhoneByID 0 11 2
A services.*PhoneAPIKeyService.AddPhone 0 23 4
A services.*PhoneAPIKeyService.generateAPIKey 0 3 1
A services.*PhoneAPIKeyService.Delete 0 17 3
A services.*PhoneAPIKeyService.generateRandomBytes 0 8 2
A services.*PhoneAPIKeyService.Create 0 28 3
1
package services
2
3
import (
4
	"context"
5
	"crypto/rand"
6
	"encoding/base64"
7
	"fmt"
8
	"time"
9
10
	"github.com/lib/pq"
11
12
	"github.com/NdoleStudio/httpsms/pkg/entities"
13
	"github.com/NdoleStudio/httpsms/pkg/repositories"
14
	"github.com/NdoleStudio/httpsms/pkg/telemetry"
15
	"github.com/google/uuid"
16
	"github.com/palantir/stacktrace"
17
)
18
19
// PhoneAPIKeyService is responsible for managing entities.PhoneAPIKey
20
type PhoneAPIKeyService struct {
21
	service
22
	logger          telemetry.Logger
23
	tracer          telemetry.Tracer
24
	phoneRepository repositories.PhoneRepository
25
	repository      repositories.PhoneAPIKeyRepository
26
}
27
28
// NewPhoneAPIKeyService creates a new PhoneAPIKeyService
29
func NewPhoneAPIKeyService(
30
	logger telemetry.Logger,
31
	tracer telemetry.Tracer,
32
	phoneRepository repositories.PhoneRepository,
33
	repository repositories.PhoneAPIKeyRepository,
34
) *PhoneAPIKeyService {
35
	return &PhoneAPIKeyService{
36
		logger:          logger.WithService(fmt.Sprintf("%T", &PhoneAPIKeyService{})),
37
		tracer:          tracer,
38
		phoneRepository: phoneRepository,
39
		repository:      repository,
40
	}
41
}
42
43
// Index fetches the entities.Webhook for an entities.UserID
44
func (service *PhoneAPIKeyService) Index(ctx context.Context, userID entities.UserID, params repositories.IndexParams) ([]*entities.PhoneAPIKey, error) {
45
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
46
	defer span.End()
47
48
	phoneAPIKeys, err := service.repository.Index(ctx, userID, params)
49
	if err != nil {
50
		msg := fmt.Sprintf("could not fetch phone API Keys with params [%+#v]", params)
51
		return nil, service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
52
	}
53
54
	ctxLogger.Info(fmt.Sprintf("fetched [%d] phone API Keys with prams [%+#v]", len(phoneAPIKeys), params))
55
	return phoneAPIKeys, nil
56
}
57
58
// Create a new entities.PhoneAPIKey
59
func (service *PhoneAPIKeyService) Create(ctx context.Context, authContext entities.AuthContext, name string) (*entities.PhoneAPIKey, error) {
60
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
61
	defer span.End()
62
63
	apiKey, err := service.generateAPIKey(64)
64
	if err != nil {
65
		return nil, stacktrace.Propagate(err, "cannot generate API key")
66
	}
67
68
	phoneAPIKey := &entities.PhoneAPIKey{
69
		ID:           uuid.New(),
70
		Name:         name,
71
		UserID:       authContext.ID,
72
		UserEmail:    authContext.Email,
73
		PhoneNumbers: pq.StringArray{},
74
		PhoneIDs:     pq.StringArray{},
75
		APIKey:       "pk_" + apiKey,
76
		CreatedAt:    time.Now().UTC(),
77
		UpdatedAt:    time.Now().UTC(),
78
	}
79
80
	if err = service.repository.Create(ctx, phoneAPIKey); err != nil {
81
		msg := fmt.Sprintf("cannot create PhoneAPIKey for user [%s]", authContext.ID)
82
		return nil, service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
83
	}
84
85
	ctxLogger.Info(fmt.Sprintf("created [%T] with ID [%s] for user ID [%s]", phoneAPIKey, phoneAPIKey.ID, authContext.ID))
86
	return phoneAPIKey, nil
87
}
88
89
// Delete an entities.PhoneAPIKey
90
func (service *PhoneAPIKeyService) Delete(ctx context.Context, userID entities.UserID, phoneAPIKeyID uuid.UUID) error {
91
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
92
	defer span.End()
93
94
	phoneAPIKey, err := service.repository.Load(ctx, userID, phoneAPIKeyID)
95
	if err != nil {
96
		msg := fmt.Sprintf("cannot load [%T] with ID [%s] for user [%s]", &entities.PhoneAPIKey{}, phoneAPIKeyID, userID.String())
97
		return stacktrace.Propagate(err, msg)
98
	}
99
100
	if err = service.repository.Delete(ctx, phoneAPIKey); err != nil {
101
		msg := fmt.Sprintf("cannot delete [%T] with ID [%s] for user [%s]", phoneAPIKey, phoneAPIKey.ID, phoneAPIKey.UserID)
102
		return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
103
	}
104
105
	ctxLogger.Info(fmt.Sprintf("deleted [%T] with ID [%s] for user ID [%s]", phoneAPIKey, phoneAPIKey.ID, userID))
106
	return nil
107
}
108
109
// RemovePhone removes the phone from the phone API key
110
func (service *PhoneAPIKeyService) RemovePhone(ctx context.Context, userID entities.UserID, phoneAPIKeyID uuid.UUID, phoneID uuid.UUID) error {
111
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
112
	defer span.End()
113
114
	phone, err := service.phoneRepository.LoadByID(ctx, userID, phoneID)
115
	if err != nil {
116
		msg := fmt.Sprintf("cannot load [%T] with ID [%s] for user [%s]", &entities.Phone{}, phoneID, userID.String())
117
		return stacktrace.Propagate(err, msg)
118
	}
119
120
	phoneAPIKey, err := service.repository.Load(ctx, userID, phoneAPIKeyID)
121
	if err != nil {
122
		msg := fmt.Sprintf("cannot load [%T] with ID [%s] for user [%s]", &entities.PhoneAPIKey{}, phoneAPIKeyID, userID.String())
123
		return stacktrace.Propagate(err, msg)
124
	}
125
126
	if err = service.repository.RemovePhone(ctx, phoneAPIKey, phone); err != nil {
127
		msg := fmt.Sprintf("cannot remove [%T] with ID [%s] from phone API key with ID [%s] for user [%s]", phone, phone.ID, phoneAPIKey.ID, userID)
128
		return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
129
	}
130
131
	ctxLogger.Info(fmt.Sprintf("removed [%T] with ID [%s] from [%T] with ID [%s] for user ID [%s]", phone, phoneID, phoneAPIKey, phoneAPIKeyID, userID))
132
	return nil
133
}
134
135
// RemovePhoneByID removes the phone from the phone API key by phone number and phoneID
136
func (service *PhoneAPIKeyService) RemovePhoneByID(ctx context.Context, userID entities.UserID, phoneID uuid.UUID, phoneNumber string) error {
137
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
138
	defer span.End()
139
140
	if err := service.repository.RemovePhoneByID(ctx, userID, phoneID, phoneNumber); err != nil {
141
		msg := fmt.Sprintf("cannot remove [%T] with ID [%s] and number [%s] for user [%s]", &entities.Phone{}, phoneID, phoneNumber, userID.String())
142
		return stacktrace.Propagate(err, msg)
143
	}
144
145
	ctxLogger.Info(fmt.Sprintf("removed phone with ID [%s] from [%T] for user ID [%s]", phoneID, &entities.PhoneAPIKey{}, userID))
146
	return nil
147
}
148
149
// DeleteAllForUser removes all entities.PhoneAPIKey for a user
150
func (service *PhoneAPIKeyService) DeleteAllForUser(ctx context.Context, userID entities.UserID) error {
151
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
152
	defer span.End()
153
154
	if err := service.repository.DeleteAllForUser(ctx, userID); err != nil {
155
		msg := fmt.Sprintf("cannot delete all [%T] for user ID [%s]", &entities.PhoneAPIKey{}, userID)
156
		return stacktrace.Propagate(err, msg)
157
	}
158
159
	ctxLogger.Info(fmt.Sprintf("deleted all [%T] for user ID [%s]", &entities.PhoneAPIKey{}, userID))
160
	return nil
161
}
162
163
// AddPhone adds a phone to the phone API key
164
func (service *PhoneAPIKeyService) AddPhone(ctx context.Context, userID entities.UserID, phoneAPIKeyID uuid.UUID, phoneID uuid.UUID) error {
165
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
166
	defer span.End()
167
168
	phone, err := service.phoneRepository.LoadByID(ctx, userID, phoneID)
169
	if err != nil {
170
		msg := fmt.Sprintf("cannot load [%T] with ID [%s] for user [%s]", &entities.Phone{}, phoneID, userID.String())
171
		return stacktrace.Propagate(err, msg)
172
	}
173
174
	phoneAPIKey, err := service.repository.Load(ctx, userID, phoneAPIKeyID)
175
	if err != nil {
176
		msg := fmt.Sprintf("cannot load [%T] with ID [%s] for user [%s]", &entities.PhoneAPIKey{}, phoneAPIKeyID, userID.String())
177
		return stacktrace.Propagate(err, msg)
178
	}
179
180
	if err = service.repository.AddPhone(ctx, phoneAPIKey, phone); err != nil {
181
		msg := fmt.Sprintf("cannot add [%T] with ID [%s] to phone API key with ID [%s] for user [%s]", phone, phone.ID, phoneAPIKey.ID, userID)
182
		return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
183
	}
184
185
	ctxLogger.Info(fmt.Sprintf("added [%T] with ID [%s] to [%T] with ID [%s] for user ID [%s]", phone, phone.ID, phoneAPIKey, phoneAPIKeyID, userID))
186
	return nil
187
}
188
189
func (service *PhoneAPIKeyService) generateRandomBytes(n int) ([]byte, error) {
190
	b := make([]byte, n)
191
	// Note that err == nil only if we read len(b) bytes.
192
	if _, err := rand.Read(b); err != nil {
193
		return nil, stacktrace.Propagate(err, fmt.Sprintf("cannot generate [%d] random bytes", n))
194
	}
195
196
	return b, nil
197
}
198
199
func (service *PhoneAPIKeyService) generateAPIKey(n int) (string, error) {
200
	b, err := service.generateRandomBytes(n)
201
	return base64.URLEncoding.EncodeToString(b)[0:n], stacktrace.Propagate(err, fmt.Sprintf("cannot generate [%s] random bytes", n))
202
}
203