Skip to content

Commit

Permalink
Add API to update the user notification settings
Browse files Browse the repository at this point in the history
  • Loading branch information
AchoArnold committed Oct 12, 2023
1 parent 89e3cc3 commit 05126f6
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 13 deletions.
2 changes: 1 addition & 1 deletion api/pkg/emails/notification_email_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type NotificationEmailFactory interface {
// MessageFailed sends an email when the user's message is failed
MessageFailed(user *entities.User, messageID uuid.UUID, owner, contact, content, reason string) (*Email, error)

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

// WebhookSendFailed sends an email when the user's webhook message is failed
Expand Down
27 changes: 15 additions & 12 deletions api/pkg/entities/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,21 @@ const SubscriptionName20KYearly = SubscriptionName("20k-yearly")

// User stores information about a user
type User struct {
ID UserID `json:"id" gorm:"primaryKey;type:string;" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`
Email string `json:"email" example:"[email protected]"` // gorm:"uniqueIndex"
APIKey string `json:"api_key" example:"xyz"` // gorm:"uniqueIndex"
Timezone string `json:"timezone" example:"Europe/Helsinki" gorm:"default:Africa/Accra"`
ActivePhoneID *uuid.UUID `json:"active_phone_id" gorm:"type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
SubscriptionName SubscriptionName `json:"subscription_name" example:"free"`
SubscriptionID *string `json:"subscription_id" example:"8f9c71b8-b84e-4417-8408-a62274f65a08"`
SubscriptionStatus *string `json:"subscription_status" example:"on_trial"`
SubscriptionRenewsAt *time.Time `json:"subscription_renews_at" example:"2022-06-05T14:26:02.302718+03:00"`
SubscriptionEndsAt *time.Time `json:"subscription_ends_at" example:"2022-06-05T14:26:02.302718+03:00"`
CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"`
UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"`
ID UserID `json:"id" gorm:"primaryKey;type:string;" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`
Email string `json:"email" example:"[email protected]"`
APIKey string `json:"api_key" example:"xyz"`
Timezone string `json:"timezone" example:"Europe/Helsinki" gorm:"default:Africa/Accra"`
ActivePhoneID *uuid.UUID `json:"active_phone_id" gorm:"type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
SubscriptionName SubscriptionName `json:"subscription_name" example:"free"`
SubscriptionID *string `json:"subscription_id" example:"8f9c71b8-b84e-4417-8408-a62274f65a08"`
SubscriptionStatus *string `json:"subscription_status" example:"on_trial"`
SubscriptionRenewsAt *time.Time `json:"subscription_renews_at" example:"2022-06-05T14:26:02.302718+03:00"`
SubscriptionEndsAt *time.Time `json:"subscription_ends_at" example:"2022-06-05T14:26:02.302718+03:00"`
NotificationMessageStatusEnabled bool `json:"notification_message_status_enabled" gorm:"default:true" example:"true"`
NotificationWebhookEnabled bool `json:"notification_webhook_enabled" gorm:"default:true" example:"true"`
NotificationHeartbeatEnabled bool `json:"notification_heartbeat_enabled" gorm:"default:true" example:"true"`
CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"`
UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"`
}

// IsOnProPlan checks if a user is on the pro plan
Expand Down
38 changes: 38 additions & 0 deletions api/pkg/handlers/user_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func NewUserHandler(
func (h *UserHandler) RegisterRoutes(router fiber.Router) {
router.Get("/users/me", h.Show)
router.Put("/users/me", h.Update)
router.Put("/users/:userID/notifications", h.UpdateNotifications)
router.Get("/users/subscription-update-url", h.subscriptionUpdateURL)
router.Delete("/users/subscription", h.cancelSubscription)
}
Expand Down Expand Up @@ -119,6 +120,43 @@ func (h *UserHandler) Update(c *fiber.Ctx) error {
return h.responseOK(c, "user updated successfully", user)
}

// UpdateNotifications an entities.User
// @Summary Update notification settings
// @Description Update the email notification settings for a user
// @Security ApiKeyAuth
// @Tags Users
// @Accept json
// @Produce json
// @Param payload body requests.UserNotificationUpdate true "Payload of user notification details to update"
// @Success 200 {object} responses.UserResponse
// @Failure 400 {object} responses.BadRequest
// @Failure 401 {object} responses.Unauthorized
// @Failure 422 {object} responses.UnprocessableEntity
// @Failure 500 {object} responses.InternalServerError
// @Router /users/{userID}/notifications [put]
func (h *UserHandler) UpdateNotifications(c *fiber.Ctx) error {
ctx, span := h.tracer.StartFromFiberCtx(c)
defer span.End()

ctxLogger := h.tracer.CtxLogger(h.logger, span)

var request requests.UserNotificationUpdate
if err := c.BodyParser(&request); err != nil {
msg := fmt.Sprintf("cannot marshall params [%s] into %T", c.OriginalURL(), request)
ctxLogger.Warn(stacktrace.Propagate(err, msg))
return h.responseBadRequest(c, err)
}

user, err := h.service.UpdateNotificationSettings(ctx, h.userIDFomContext(c), request.ToUserNotificationUpdateParams())
if err != nil {
msg := fmt.Sprintf("cannot update notification for [%T] with ID [%s]", user, h.userIDFomContext(c))
ctxLogger.Error(h.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)))
return h.responseInternalServerError(c)
}

return h.responseOK(c, "user notification settings updated successfully", user)
}

// subscriptionUpdateURL returns the subscription update URL for the authenticated entities.User
// @Summary Currently authenticated user subscription update URL
// @Description Fetches the subscription URL of the authenticated user.
Expand Down
22 changes: 22 additions & 0 deletions api/pkg/requests/user_notification_update_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package requests

import (
"github.com/NdoleStudio/httpsms/pkg/services"
)

// UserNotificationUpdate is the payload for updating a phone
type UserNotificationUpdate struct {
request
MessageStatusEnabled bool `json:"message_status_enabled" example:"true"`
WebhookEnabled bool `json:"webhook_enabled" example:"true"`
HeartbeatEnabled bool `json:"heartbeat_enabled" example:"true"`
}

// ToUserNotificationUpdateParams converts UserNotificationUpdate to services.UserNotificationUpdateParams
func (input *UserNotificationUpdate) ToUserNotificationUpdateParams() *services.UserNotificationUpdateParams {
return &services.UserNotificationUpdateParams{
MessageStatusEnabled: input.MessageStatusEnabled,
WebhookEnabled: input.WebhookEnabled,
HeartbeatEnabled: input.HeartbeatEnabled,
}
}
31 changes: 31 additions & 0 deletions api/pkg/services/user_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,37 @@ func (service *UserService) Update(ctx context.Context, authUser entities.AuthUs
return user, nil
}

// UserNotificationUpdateParams are parameters for updating the notifications of a user
type UserNotificationUpdateParams struct {
MessageStatusEnabled bool
WebhookEnabled bool
HeartbeatEnabled bool
}

// UpdateNotificationSettings for an entities.User
func (service *UserService) UpdateNotificationSettings(ctx context.Context, userID entities.UserID, params *UserNotificationUpdateParams) (*entities.User, error) {
ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
defer span.End()

user, err := service.repository.Load(ctx, userID)
if err != nil {
msg := fmt.Sprintf("could not load [%T] with ID [%s]", user, userID)
return nil, service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
}

user.NotificationWebhookEnabled = params.WebhookEnabled
user.NotificationHeartbeatEnabled = params.HeartbeatEnabled
user.NotificationMessageStatusEnabled = params.MessageStatusEnabled

if err = service.repository.Update(ctx, user); err != nil {
msg := fmt.Sprintf("cannot save user with id [%s] in [%T]", user.ID, service.repository)
return nil, service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
}

ctxLogger.Info(fmt.Sprintf("updated notification settings for [%T] with ID [%s] in the [%T]", user, user.ID, service.repository))
return user, nil
}

// UserSendPhoneDeadEmailParams are parameters for notifying a user when a phone is dead
type UserSendPhoneDeadEmailParams struct {
UserID entities.UserID
Expand Down

0 comments on commit 05126f6

Please sign in to comment.