/
manager.go
175 lines (158 loc) · 5.81 KB
/
manager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package subscription
import (
"context"
"encoding/json"
"fmt"
"github.com/artifacthub/hub/internal/hub"
"github.com/artifacthub/hub/internal/util"
"github.com/satori/uuid"
)
const (
// Database queries
addOptOutDBQ = `select add_opt_out($1::jsonb)`
addSubscriptionDBQ = `select add_subscription($1::jsonb)`
deleteOptOutDBQ = `select delete_opt_out($1::uuid, $2::uuid)`
deleteSubscriptionDBQ = `select delete_subscription($1::jsonb)`
getPkgSubscriptorsDBQ = `select get_package_subscriptors($1::uuid, $2::integer)`
getRepoSubscriptorsDBQ = `select get_repository_subscriptors($1::uuid, $2::integer)`
getUserOptOutEntriesDBQ = `select * from get_user_opt_out_entries($1::uuid, $2::int, $3::int)`
getUserPkgSubscriptionsDBQ = `select get_user_package_subscriptions($1::uuid, $2::uuid)`
getUserSubscriptionsDBQ = `select * from get_user_subscriptions($1::uuid, $2::int, $3::int)`
)
var (
// validEventKinds contains the event kinds supported.
validEventKinds = []hub.EventKind{
hub.NewRelease,
hub.SecurityAlert,
}
)
// Manager provides an API to manage subscriptions.
type Manager struct {
db hub.DB
}
// NewManager creates a new Manager instance.
func NewManager(db hub.DB) *Manager {
return &Manager{
db: db,
}
}
// Add adds the provided subscription to the database.
func (m *Manager) Add(ctx context.Context, s *hub.Subscription) error {
userID := ctx.Value(hub.UserIDKey).(string)
s.UserID = userID
if err := validateSubscription(s); err != nil {
return err
}
sJSON, _ := json.Marshal(s)
_, err := m.db.Exec(ctx, addSubscriptionDBQ, sJSON)
return err
}
// AddOptOut adds an opt-out entry to the database.
func (m *Manager) AddOptOut(ctx context.Context, o *hub.OptOut) error {
userID := ctx.Value(hub.UserIDKey).(string)
o.UserID = userID
if err := validateOptOut(o); err != nil {
return err
}
oJSON, _ := json.Marshal(o)
_, err := m.db.Exec(ctx, addOptOutDBQ, oJSON)
return err
}
// Delete removes a subscription from the database.
func (m *Manager) Delete(ctx context.Context, s *hub.Subscription) error {
userID := ctx.Value(hub.UserIDKey).(string)
s.UserID = userID
if err := validateSubscription(s); err != nil {
return err
}
sJSON, _ := json.Marshal(s)
_, err := m.db.Exec(ctx, deleteSubscriptionDBQ, sJSON)
return err
}
// DeleteOptOut deletes an opt-out entry from the database.
func (m *Manager) DeleteOptOut(ctx context.Context, optOutID string) error {
userID := ctx.Value(hub.UserIDKey).(string)
if _, err := uuid.FromString(optOutID); err != nil {
return fmt.Errorf("%w: %s", hub.ErrInvalidInput, "invalid opt out id")
}
_, err := m.db.Exec(ctx, deleteOptOutDBQ, userID, optOutID)
return err
}
// GetByPackageJSON returns the subscriptions the user has for a given package
// as json array of objects.
func (m *Manager) GetByPackageJSON(ctx context.Context, packageID string) ([]byte, error) {
userID := ctx.Value(hub.UserIDKey).(string)
if _, err := uuid.FromString(packageID); err != nil {
return nil, fmt.Errorf("%w: %s", hub.ErrInvalidInput, "invalid package id")
}
return util.DBQueryJSON(ctx, m.db, getUserPkgSubscriptionsDBQ, userID, packageID)
}
// GetByUserJSON returns all the subscriptions of the user doing the request as
// as json array of objects.
func (m *Manager) GetByUserJSON(ctx context.Context, p *hub.Pagination) (*hub.JSONQueryResult, error) {
userID := ctx.Value(hub.UserIDKey).(string)
return util.DBQueryJSONWithPagination(ctx, m.db, getUserSubscriptionsDBQ, userID, p.Limit, p.Offset)
}
// GetOptOutListJSON returns all the opt-out entries of the user doing the
// request as as json array of objects.
func (m *Manager) GetOptOutListJSON(ctx context.Context, p *hub.Pagination) (*hub.JSONQueryResult, error) {
userID := ctx.Value(hub.UserIDKey).(string)
return util.DBQueryJSONWithPagination(ctx, m.db, getUserOptOutEntriesDBQ, userID, p.Limit, p.Offset)
}
// GetSubscriptors returns the users subscribed to receive notifications for
// certain kind of events.
func (m *Manager) GetSubscriptors(ctx context.Context, e *hub.Event) ([]*hub.User, error) {
var dataJSON []byte
var err error
switch e.EventKind {
case hub.NewRelease, hub.SecurityAlert:
err = m.db.QueryRow(ctx, getPkgSubscriptorsDBQ, e.PackageID, e.EventKind).Scan(&dataJSON)
case hub.RepositoryScanningErrors, hub.RepositoryTrackingErrors:
err = m.db.QueryRow(ctx, getRepoSubscriptorsDBQ, e.RepositoryID, e.EventKind).Scan(&dataJSON)
case hub.RepositoryOwnershipClaim:
dataJSON, _ = json.Marshal(e.Data["subscriptors"])
default:
return nil, nil
}
if err != nil {
return nil, err
}
var subscriptors []*hub.User
if err := json.Unmarshal(dataJSON, &subscriptors); err != nil {
return nil, err
}
return subscriptors, nil
}
// validateSubscription checks if the subscription provided is valid to be used
// as input for some database functions calls.
func validateSubscription(s *hub.Subscription) error {
if _, err := uuid.FromString(s.PackageID); err != nil {
return fmt.Errorf("%w: %s", hub.ErrInvalidInput, "invalid package id")
}
if !isValidEventKind(s.EventKind) {
return fmt.Errorf("%w: %s", hub.ErrInvalidInput, "invalid event kind")
}
return nil
}
// validateOptOut checks if the opt-out information provided is valid to be
// used as input for some database functions calls.
func validateOptOut(o *hub.OptOut) error {
if _, err := uuid.FromString(o.RepositoryID); err != nil {
return fmt.Errorf("%w: %s", hub.ErrInvalidInput, "invalid repository id")
}
switch o.EventKind {
case hub.RepositoryScanningErrors, hub.RepositoryTrackingErrors:
default:
return fmt.Errorf("%w: %s", hub.ErrInvalidInput, "invalid event kind")
}
return nil
}
// isValidEventKind checks if the provided event kind is valid.
func isValidEventKind(kind hub.EventKind) bool {
for _, validKind := range validEventKinds {
if kind == validKind {
return true
}
}
return false
}