Skip to content

Commit 374bae2

Browse files
committed
adds List and Get methods to alerts client
The Get endpoint already exists on the service, so only the List endpoint needed to be added there. BACK-2554
1 parent 6523dec commit 374bae2

File tree

5 files changed

+110
-9
lines changed

5 files changed

+110
-9
lines changed

alerts/client.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/tidepool-org/platform/auth"
1111
"github.com/tidepool-org/platform/client"
12+
"github.com/tidepool-org/platform/errors"
1213
platformlog "github.com/tidepool-org/platform/log"
1314
"github.com/tidepool-org/platform/log/null"
1415
"github.com/tidepool-org/platform/platform"
@@ -43,6 +44,8 @@ type PlatformClient interface {
4344
requestBody interface{}, responseBody interface{}, inspectors ...request.ResponseInspector) error
4445
}
4546

47+
// TokenProvider retrieves session tokens for calling the alerts API.
48+
//
4649
// client.External is one implementation
4750
type TokenProvider interface {
4851
// ServerSessionToken provides a server-to-server API authentication token.
@@ -51,12 +54,12 @@ type TokenProvider interface {
5154

5255
// request performs common operations before passing a request off to the
5356
// underlying platform.Client.
54-
func (c *Client) request(ctx context.Context, method, url string, body any) error {
57+
func (c *Client) request(ctx context.Context, method, url string, reqBody, resBody any) error {
5558
// Platform's client.Client expects a logger to exist in the request's
5659
// context. If it doesn't exist, request processing will panic.
5760
loggingCtx := platformlog.NewContextWithLogger(ctx, c.logger)
5861
// Make sure the auth token is injected into the request's headers.
59-
return c.requestWithAuth(loggingCtx, method, url, body)
62+
return c.requestWithAuth(loggingCtx, method, url, reqBody, resBody)
6063
}
6164

6265
// requestWithAuth injects an auth token before calling platform.Client.RequestData.
@@ -65,24 +68,48 @@ func (c *Client) request(ctx context.Context, method, url string, body any) erro
6568
// platform.Client. It might be nice to be able to use a mutator, but the auth
6669
// is specifically handled by the platform.Client via the context field, and
6770
// if left blank, platform.Client errors.
68-
func (c *Client) requestWithAuth(ctx context.Context, method, url string, body any) error {
71+
func (c *Client) requestWithAuth(ctx context.Context, method, url string, reqBody, resBody any) error {
6972
authCtx, err := c.ctxWithAuth(ctx)
7073
if err != nil {
7174
return err
7275
}
73-
return c.client.RequestData(authCtx, method, url, nil, body, nil)
76+
return c.client.RequestData(authCtx, method, url, nil, reqBody, resBody)
7477
}
7578

7679
// Upsert updates cfg if it exists or creates it if it doesn't.
7780
func (c *Client) Upsert(ctx context.Context, cfg *Config) error {
7881
url := c.client.ConstructURL("v1", "users", cfg.FollowedUserID, "followers", cfg.UserID, "alerts")
79-
return c.request(ctx, http.MethodPost, url, cfg)
82+
return c.request(ctx, http.MethodPost, url, cfg, nil)
8083
}
8184

8285
// Delete the alerts config.
8386
func (c *Client) Delete(ctx context.Context, cfg *Config) error {
8487
url := c.client.ConstructURL("v1", "users", cfg.FollowedUserID, "followers", cfg.UserID, "alerts")
85-
return c.request(ctx, http.MethodDelete, url, nil)
88+
return c.request(ctx, http.MethodDelete, url, nil, nil)
89+
}
90+
91+
// Get a user's alerts configuration for the followed user.
92+
func (c *Client) Get(ctx context.Context, followedUserID, userID string) (*Config, error) {
93+
url := c.client.ConstructURL("v1", "users", followedUserID, "followers", userID, "alerts")
94+
cfg := &Config{}
95+
err := c.request(ctx, http.MethodGet, url, nil, cfg)
96+
if err != nil {
97+
return nil, errors.Wrap(err, "Unable to request alerts config")
98+
}
99+
return cfg, nil
100+
}
101+
102+
// List the alerts configurations that follow the given user.
103+
//
104+
// This method should only be called via an authenticated service session.
105+
func (c *Client) List(ctx context.Context, followedUserID string) ([]*Config, error) {
106+
url := c.client.ConstructURL("v1", "users", followedUserID, "followers", "alerts")
107+
configs := []*Config{}
108+
err := c.request(ctx, http.MethodGet, url, nil, &configs)
109+
if err != nil {
110+
return nil, errors.Wrap(err, "Unable to request alerts configs list")
111+
}
112+
return configs, nil
86113
}
87114

88115
// ctxWithAuth injects a server session token into the context.

alerts/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ type Repository interface {
234234
Get(ctx context.Context, conf *Config) (*Config, error)
235235
Upsert(ctx context.Context, conf *Config) error
236236
Delete(ctx context.Context, conf *Config) error
237+
List(ctx context.Context, userID string) ([]*Config, error)
237238

238239
EnsureIndexes() error
239240
}

data/service/api/v1/alerts.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func AlertsRoutes() []service.Route {
2424
service.Get("/v1/users/:userId/followers/:followerUserId/alerts", GetAlert, api.RequireAuth),
2525
service.Post("/v1/users/:userId/followers/:followerUserId/alerts", UpsertAlert, api.RequireAuth),
2626
service.Delete("/v1/users/:userId/followers/:followerUserId/alerts", DeleteAlert, api.RequireAuth),
27+
service.Get("/v1/users/:userId/followers/alerts", ListAlerts, api.RequireServer),
2728
}
2829
}
2930

@@ -134,6 +135,42 @@ func UpsertAlert(dCtx service.Context) {
134135
}
135136
}
136137

138+
func ListAlerts(dCtx service.Context) {
139+
r := dCtx.Request()
140+
ctx := r.Context()
141+
authDetails := request.GetAuthDetails(ctx)
142+
repo := dCtx.AlertsRepository()
143+
lgr := log.LoggerFromContext(ctx)
144+
145+
if err := checkAuthentication(authDetails); err != nil {
146+
lgr.Debug("authentication failed")
147+
dCtx.RespondWithError(platform.ErrorUnauthorized())
148+
return
149+
}
150+
151+
pathsUserID := r.PathParam("userId")
152+
if err := checkUserIDConsistency(authDetails, pathsUserID); err != nil {
153+
lgr.WithFields(log.Fields{"path": pathsUserID, "auth": authDetails.UserID()}).
154+
Debug("user id consistency failed")
155+
dCtx.RespondWithError(platform.ErrorUnauthorized())
156+
return
157+
}
158+
159+
alerts, err := repo.List(ctx, pathsUserID)
160+
if err != nil {
161+
dCtx.RespondWithInternalServerFailure("listing alerts configs", err)
162+
lgr.WithError(err).Error("listing alerts config")
163+
return
164+
}
165+
if len(alerts) == 0 {
166+
dCtx.RespondWithError(ErrorUserIDNotFound(pathsUserID))
167+
lgr.Debug("no alerts configs found")
168+
}
169+
170+
responder := request.MustNewResponder(dCtx.Response(), r)
171+
responder.Data(http.StatusOK, alerts)
172+
}
173+
137174
// checkUserIDConsistency verifies the userIDs in a request.
138175
//
139176
// For safety reasons, if these values don't agree, return an error.

data/service/api/v1/alerts_test.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,15 @@ var _ = Describe("Alerts endpoints", func() {
160160
})
161161

162162
type mockRepo struct {
163-
UserID string
164-
Error error
163+
UserID string
164+
Error error
165+
AlertsForUserID map[string][]*alerts.Config
165166
}
166167

167168
func newMockRepo() *mockRepo {
168-
return &mockRepo{}
169+
return &mockRepo{
170+
AlertsForUserID: make(map[string][]*alerts.Config),
171+
}
169172
}
170173

171174
func (r *mockRepo) ReturnsError(err error) {
@@ -202,6 +205,18 @@ func (r *mockRepo) Delete(ctx context.Context, conf *alerts.Config) error {
202205
return nil
203206
}
204207

208+
func (r *mockRepo) List(ctx context.Context, userID string) ([]*alerts.Config, error) {
209+
if r.Error != nil {
210+
return nil, r.Error
211+
}
212+
r.UserID = userID
213+
alerts, ok := r.AlertsForUserID[userID]
214+
if !ok {
215+
return nil, nil
216+
}
217+
return alerts, nil
218+
}
219+
205220
func (r *mockRepo) EnsureIndexes() error {
206221
return nil
207222
}

data/store/mongo/mongo_alerts.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"go.mongodb.org/mongo-driver/mongo/options"
1010

1111
"github.com/tidepool-org/platform/alerts"
12+
"github.com/tidepool-org/platform/errors"
1213
structuredmongo "github.com/tidepool-org/platform/store/structured/mongo"
1314
)
1415

@@ -34,6 +35,26 @@ func (r *alertsRepo) Delete(ctx context.Context, cfg *alerts.Config) error {
3435
return nil
3536
}
3637

38+
// List will retrieve any Configs that are defined by followers of the given user.
39+
func (r *alertsRepo) List(ctx context.Context, userID string) ([]*alerts.Config, error) {
40+
filter := bson.D{
41+
{Key: "followedUserId", Value: userID},
42+
}
43+
cursor, err := r.Find(ctx, filter, nil)
44+
if err != nil {
45+
return nil, errors.Wrapf(err, "Unable to list alerts.Config(s) for user %s", userID)
46+
}
47+
defer cursor.Close(ctx)
48+
out := []*alerts.Config{}
49+
if err := cursor.All(ctx, &out); err != nil {
50+
return nil, errors.Wrapf(err, "Unable to decode alerts.Config(s) for user %s", userID)
51+
}
52+
if err := cursor.Err(); err != nil {
53+
return nil, errors.Wrapf(err, "Unexpected error for user %s", userID)
54+
}
55+
return out, nil
56+
}
57+
3758
// Get will retrieve the given Config.
3859
func (r *alertsRepo) Get(ctx context.Context, cfg *alerts.Config) (*alerts.Config, error) {
3960
res := r.FindOne(ctx, r.filter(cfg), nil)

0 commit comments

Comments
 (0)