Skip to content

Commit 05126f6

Browse files
committed
Add API to update the user notification settings
1 parent 89e3cc3 commit 05126f6

5 files changed

Lines changed: 107 additions & 13 deletions

File tree

api/pkg/emails/notification_email_factory.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type NotificationEmailFactory interface {
1414
// MessageFailed sends an email when the user's message is failed
1515
MessageFailed(user *entities.User, messageID uuid.UUID, owner, contact, content, reason string) (*Email, error)
1616

17-
// DiscordMessageFailed sends an email when the user's discord message is failed
17+
// DiscordSendFailed sends an email when the user's discord message is failed
1818
DiscordSendFailed(user *entities.User, payload *events.DiscordSendFailedPayload) (*Email, error)
1919

2020
// WebhookSendFailed sends an email when the user's webhook message is failed

api/pkg/entities/user.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,21 @@ const SubscriptionName20KYearly = SubscriptionName("20k-yearly")
5757

5858
// User stores information about a user
5959
type User struct {
60-
ID UserID `json:"id" gorm:"primaryKey;type:string;" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`
61-
Email string `json:"email" example:"name@email.com"` // gorm:"uniqueIndex"
62-
APIKey string `json:"api_key" example:"xyz"` // gorm:"uniqueIndex"
63-
Timezone string `json:"timezone" example:"Europe/Helsinki" gorm:"default:Africa/Accra"`
64-
ActivePhoneID *uuid.UUID `json:"active_phone_id" gorm:"type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
65-
SubscriptionName SubscriptionName `json:"subscription_name" example:"free"`
66-
SubscriptionID *string `json:"subscription_id" example:"8f9c71b8-b84e-4417-8408-a62274f65a08"`
67-
SubscriptionStatus *string `json:"subscription_status" example:"on_trial"`
68-
SubscriptionRenewsAt *time.Time `json:"subscription_renews_at" example:"2022-06-05T14:26:02.302718+03:00"`
69-
SubscriptionEndsAt *time.Time `json:"subscription_ends_at" example:"2022-06-05T14:26:02.302718+03:00"`
70-
CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"`
71-
UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"`
60+
ID UserID `json:"id" gorm:"primaryKey;type:string;" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`
61+
Email string `json:"email" example:"name@email.com"`
62+
APIKey string `json:"api_key" example:"xyz"`
63+
Timezone string `json:"timezone" example:"Europe/Helsinki" gorm:"default:Africa/Accra"`
64+
ActivePhoneID *uuid.UUID `json:"active_phone_id" gorm:"type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
65+
SubscriptionName SubscriptionName `json:"subscription_name" example:"free"`
66+
SubscriptionID *string `json:"subscription_id" example:"8f9c71b8-b84e-4417-8408-a62274f65a08"`
67+
SubscriptionStatus *string `json:"subscription_status" example:"on_trial"`
68+
SubscriptionRenewsAt *time.Time `json:"subscription_renews_at" example:"2022-06-05T14:26:02.302718+03:00"`
69+
SubscriptionEndsAt *time.Time `json:"subscription_ends_at" example:"2022-06-05T14:26:02.302718+03:00"`
70+
NotificationMessageStatusEnabled bool `json:"notification_message_status_enabled" gorm:"default:true" example:"true"`
71+
NotificationWebhookEnabled bool `json:"notification_webhook_enabled" gorm:"default:true" example:"true"`
72+
NotificationHeartbeatEnabled bool `json:"notification_heartbeat_enabled" gorm:"default:true" example:"true"`
73+
CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"`
74+
UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"`
7275
}
7376

7477
// IsOnProPlan checks if a user is on the pro plan

api/pkg/handlers/user_handler.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func NewUserHandler(
4141
func (h *UserHandler) RegisterRoutes(router fiber.Router) {
4242
router.Get("/users/me", h.Show)
4343
router.Put("/users/me", h.Update)
44+
router.Put("/users/:userID/notifications", h.UpdateNotifications)
4445
router.Get("/users/subscription-update-url", h.subscriptionUpdateURL)
4546
router.Delete("/users/subscription", h.cancelSubscription)
4647
}
@@ -119,6 +120,43 @@ func (h *UserHandler) Update(c *fiber.Ctx) error {
119120
return h.responseOK(c, "user updated successfully", user)
120121
}
121122

123+
// UpdateNotifications an entities.User
124+
// @Summary Update notification settings
125+
// @Description Update the email notification settings for a user
126+
// @Security ApiKeyAuth
127+
// @Tags Users
128+
// @Accept json
129+
// @Produce json
130+
// @Param payload body requests.UserNotificationUpdate true "Payload of user notification details to update"
131+
// @Success 200 {object} responses.UserResponse
132+
// @Failure 400 {object} responses.BadRequest
133+
// @Failure 401 {object} responses.Unauthorized
134+
// @Failure 422 {object} responses.UnprocessableEntity
135+
// @Failure 500 {object} responses.InternalServerError
136+
// @Router /users/{userID}/notifications [put]
137+
func (h *UserHandler) UpdateNotifications(c *fiber.Ctx) error {
138+
ctx, span := h.tracer.StartFromFiberCtx(c)
139+
defer span.End()
140+
141+
ctxLogger := h.tracer.CtxLogger(h.logger, span)
142+
143+
var request requests.UserNotificationUpdate
144+
if err := c.BodyParser(&request); err != nil {
145+
msg := fmt.Sprintf("cannot marshall params [%s] into %T", c.OriginalURL(), request)
146+
ctxLogger.Warn(stacktrace.Propagate(err, msg))
147+
return h.responseBadRequest(c, err)
148+
}
149+
150+
user, err := h.service.UpdateNotificationSettings(ctx, h.userIDFomContext(c), request.ToUserNotificationUpdateParams())
151+
if err != nil {
152+
msg := fmt.Sprintf("cannot update notification for [%T] with ID [%s]", user, h.userIDFomContext(c))
153+
ctxLogger.Error(h.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)))
154+
return h.responseInternalServerError(c)
155+
}
156+
157+
return h.responseOK(c, "user notification settings updated successfully", user)
158+
}
159+
122160
// subscriptionUpdateURL returns the subscription update URL for the authenticated entities.User
123161
// @Summary Currently authenticated user subscription update URL
124162
// @Description Fetches the subscription URL of the authenticated user.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package requests
2+
3+
import (
4+
"github.com/NdoleStudio/httpsms/pkg/services"
5+
)
6+
7+
// UserNotificationUpdate is the payload for updating a phone
8+
type UserNotificationUpdate struct {
9+
request
10+
MessageStatusEnabled bool `json:"message_status_enabled" example:"true"`
11+
WebhookEnabled bool `json:"webhook_enabled" example:"true"`
12+
HeartbeatEnabled bool `json:"heartbeat_enabled" example:"true"`
13+
}
14+
15+
// ToUserNotificationUpdateParams converts UserNotificationUpdate to services.UserNotificationUpdateParams
16+
func (input *UserNotificationUpdate) ToUserNotificationUpdateParams() *services.UserNotificationUpdateParams {
17+
return &services.UserNotificationUpdateParams{
18+
MessageStatusEnabled: input.MessageStatusEnabled,
19+
WebhookEnabled: input.WebhookEnabled,
20+
HeartbeatEnabled: input.HeartbeatEnabled,
21+
}
22+
}

api/pkg/services/user_service.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,37 @@ func (service *UserService) Update(ctx context.Context, authUser entities.AuthUs
104104
return user, nil
105105
}
106106

107+
// UserNotificationUpdateParams are parameters for updating the notifications of a user
108+
type UserNotificationUpdateParams struct {
109+
MessageStatusEnabled bool
110+
WebhookEnabled bool
111+
HeartbeatEnabled bool
112+
}
113+
114+
// UpdateNotificationSettings for an entities.User
115+
func (service *UserService) UpdateNotificationSettings(ctx context.Context, userID entities.UserID, params *UserNotificationUpdateParams) (*entities.User, error) {
116+
ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
117+
defer span.End()
118+
119+
user, err := service.repository.Load(ctx, userID)
120+
if err != nil {
121+
msg := fmt.Sprintf("could not load [%T] with ID [%s]", user, userID)
122+
return nil, service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
123+
}
124+
125+
user.NotificationWebhookEnabled = params.WebhookEnabled
126+
user.NotificationHeartbeatEnabled = params.HeartbeatEnabled
127+
user.NotificationMessageStatusEnabled = params.MessageStatusEnabled
128+
129+
if err = service.repository.Update(ctx, user); err != nil {
130+
msg := fmt.Sprintf("cannot save user with id [%s] in [%T]", user.ID, service.repository)
131+
return nil, service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
132+
}
133+
134+
ctxLogger.Info(fmt.Sprintf("updated notification settings for [%T] with ID [%s] in the [%T]", user, user.ID, service.repository))
135+
return user, nil
136+
}
137+
107138
// UserSendPhoneDeadEmailParams are parameters for notifying a user when a phone is dead
108139
type UserSendPhoneDeadEmailParams struct {
109140
UserID entities.UserID

0 commit comments

Comments
 (0)