Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ID-184]Consolidate the information, and make it accessible with a single API call #186

Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Consolidate the information, and make it accessible with a single API call [#184](https://github.com/rokwire/notifications-building-block/issues/184)

## [1.21.1] - 2024-09-06
### Fixed
Expand Down
97 changes: 97 additions & 0 deletions core/app_apis_services.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"errors"
"fmt"
"notifications/core/model"
"sync"
)

func (app *Application) getVersion() string {
Expand Down Expand Up @@ -57,6 +58,102 @@ func (app *Application) unsubscribeToTopic(orgID string, appID string, token str
return err
}

func (app *Application) getUserData(orgID, appID, userID string) (*model.UserDataResponse, error) {
var (
receivedNotifications []model.Message
scheduledNotificationsForMe []model.Message
recipientData []model.MessageRecipient
queueData []model.QueueItem
user *model.User
err error
)

var wg sync.WaitGroup
var mu sync.Mutex
errCh := make(chan error, 3)

// Fetch recipient data concurrently
wg.Add(1)
go func() {
defer wg.Done()
recipientData, err = app.storage.FindMessagesRecipientsByUserID(orgID, appID, userID)
if err != nil {
errCh <- err
}
}()

// Fetch queue data concurrently
wg.Add(1)
go func() {
defer wg.Done()
queueData, err = app.storage.FindQueueDataByUserID(userID)
if err != nil {
errCh <- err
}
}()

// Fetch user data concurrently
wg.Add(1)
go func() {
defer wg.Done()
user, err = app.storage.FindUserByID(orgID, appID, userID)
if err != nil {
errCh <- err
}
}()

wg.Wait()
close(errCh)

// Check for errors
for e := range errCh {
if e != nil {
return nil, e
}
}

// Fetch messages related to recipient data
if recipientData != nil {
for _, rn := range recipientData {
wg.Add(1)
go func(rn model.MessageRecipient) {
defer wg.Done()
rnr, err := app.storage.GetMessage(rn.OrgID, rn.AppID, rn.MessageID)
if err == nil && rnr != nil {
mu.Lock()
receivedNotifications = append(receivedNotifications, *rnr)
mu.Unlock()
}
}(rn)
}
}

// Fetch messages related to queue data
if queueData != nil {
for _, q := range queueData {
wg.Add(1)
go func(q model.QueueItem) {
defer wg.Done()
qr, err := app.storage.GetMessage(q.OrgID, q.AppID, q.MessageID)
if err == nil && qr != nil {
mu.Lock()
scheduledNotificationsForMe = append(scheduledNotificationsForMe, *qr)
mu.Unlock()
}
}(q)
}
}

wg.Wait()

userData := &model.UserDataResponse{
ReceivedNotifications: receivedNotifications,
ScheduledNotificationsForMe: scheduledNotificationsForMe,
Users: *user,
}
return userData, nil
}

func (app *Application) getTopics(orgID string, appID string) ([]model.Topic, error) {
return app.storage.GetTopics(orgID, appID)
}
Expand Down
8 changes: 8 additions & 0 deletions core/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Services interface {
FindUserByID(orgID string, appID string, userID string) (*model.User, error)
UpdateUserByID(orgID string, appID string, userID string, notificationsEnabled bool) (*model.User, error)
DeleteUserWithID(orgID string, appID string, userID string) error
GetUserData(orgID string, appID string, userID string) (*model.UserDataResponse, error)

GetMessagesRecipientsDeep(orgID string, appID string, userID *string, read *bool, mute *bool, messageIDs []string, startDateEpoch *int64, endDateEpoch *int64, filterTopic *string, offset *int64, limit *int64, order *string) ([]model.MessageRecipient, error)

Expand Down Expand Up @@ -74,6 +75,10 @@ func (s *servicesImpl) UnsubscribeToTopic(orgID string, appID string, token stri
return s.app.unsubscribeToTopic(orgID, appID, token, userID, anonymous, topic)
}

func (s *servicesImpl) GetUserData(orgID string, appID string, userID string) (*model.UserDataResponse, error) {
return s.app.getUserData(orgID, appID, userID)
}

func (s *servicesImpl) GetTopics(orgID string, appID string) ([]model.Topic, error) {
return s.app.getTopics(orgID, appID)
}
Expand Down Expand Up @@ -225,6 +230,8 @@ type Storage interface {
FindMessagesRecipientsByMessageAndUsers(messageID string, usersIDs []string) ([]model.MessageRecipient, error)
FindMessagesRecipientsByMessages(messagesIDs []string) ([]model.MessageRecipient, error)
FindMessagesRecipientsDeep(orgID string, appID string, userID *string, read *bool, mute *bool, messageIDs []string, startDateEpoch *int64, endDateEpoch *int64, filterTopic *string, offset *int64, limit *int64, order *string) ([]model.MessageRecipient, error)
FindMessagesRecipientsByUserID(orgID string, appID string, userID string) ([]model.MessageRecipient, error)

InsertMessagesRecipientsWithContext(ctx context.Context, items []model.MessageRecipient) error
DeleteMessagesRecipientsForIDsWithContext(ctx context.Context, ids []string) error
DeleteMessagesRecipientsForMessagesWithContext(ctx context.Context, messagesIDs []string) error
Expand All @@ -250,6 +257,7 @@ type Storage interface {
SaveQueue(queue model.Queue) error

FindQueueData(time *time.Time, limit int) ([]model.QueueItem, error)
FindQueueDataByUserID(userID string) ([]model.QueueItem, error)
DeleteQueueData(ids []string) error
DeleteQueueDataForMessagesWithContext(ctx context.Context, messagesIDs []string) error
DeleteQueueDataForRecipientsWithContext(ctx context.Context, recipientsIDs []string) error
Expand Down
2 changes: 0 additions & 2 deletions core/model/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,3 @@ type MessagesStats struct {
Unread *int64 `json:"not_read_count" bson:"not_read_count"`
UnreadUnmute *int64 `json:"not_read_not_mute" bson:"not_read_not_mute"`
}

stefanvit marked this conversation as resolved.
Show resolved Hide resolved
///
7 changes: 7 additions & 0 deletions core/model/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,10 @@ type DeletedMembership struct {
AccountID string `json:"account_id"`
Context *map[string]interface{} `json:"context,omitempty"`
}

// UserDataResponse represents a user data
type UserDataResponse struct {
stefanvit marked this conversation as resolved.
Show resolved Hide resolved
ReceivedNotifications []Message `json:"received_notifications"`
ScheduledNotificationsForMe []Message `json:"scheduled_notifications_for_me"`
Users User `json:"my_accounts"`
}
32 changes: 32 additions & 0 deletions driven/storage/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,23 @@ func (sa Adapter) InsertMessagesRecipientsWithContext(ctx context.Context, items
return nil
}

// FindMessagesRecipientsByUserID finds messages recipients
func (sa Adapter) FindMessagesRecipientsByUserID(orgID string, appID string, userID string) ([]model.MessageRecipient, error) {
filter := bson.D{
primitive.E{Key: "org_id", Value: orgID},
primitive.E{Key: "app_id", Value: appID},
primitive.E{Key: "user_id", Value: userID},
}

var data []model.MessageRecipient
err := sa.db.messagesRecipients.Find(filter, &data, nil)
if err != nil {
return nil, err
}

return data, nil
}

// DeleteMessagesRecipientsForIDsWithContext deletes messages recipients for ids
func (sa Adapter) DeleteMessagesRecipientsForIDsWithContext(ctx context.Context, ids []string) error {
filter := bson.D{primitive.E{Key: "_id", Value: bson.M{"$in": ids}}}
Expand Down Expand Up @@ -1360,6 +1377,21 @@ func (sa *Adapter) DeleteQueueDataForUsers(ctx context.Context, orgID string, ap
return nil
}

// FindQueueDataByUserID gets all queue data by userID
func (sa Adapter) FindQueueDataByUserID(userID string) ([]model.QueueItem, error) {
filter := bson.D{
primitive.E{Key: "user_id", Value: userID},
}

var queue []model.QueueItem
err := sa.db.queueData.Find(filter, &queue, nil)
if err != nil {
return nil, err
}

return queue, nil
}

func abortTransaction(sessionContext mongo.SessionContext) {
err := sessionContext.AbortTransaction(sessionContext)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions driver/web/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func (we Adapter) Start() {
//mainRouter.HandleFunc("/topic/{topic}/messages", we.wrapFunc(we.apisHandler.GetTopicMessages, we.auth.client.Standard)).Methods("GET")
mainRouter.HandleFunc("/topic/{topic}/subscribe", we.wrapFunc(we.apisHandler.Subscribe, we.auth.client.Standard)).Methods("POST")
mainRouter.HandleFunc("/topic/{topic}/unsubscribe", we.wrapFunc(we.apisHandler.Unsubscribe, we.auth.client.Standard)).Methods("POST")
mainRouter.HandleFunc("/user-data", we.wrapFunc(we.apisHandler.GetUserData, we.auth.client.Standard)).Methods("GET")

// Admin APIs
adminRouter := mainRouter.PathPrefix("/admin").Subrouter()
Expand Down
22 changes: 22 additions & 0 deletions driver/web/apis_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,28 @@ func (h ApisHandler) Unsubscribe(l *logs.Log, r *http.Request, claims *tokenauth
return l.HTTPResponseSuccess()
}

// GetUserData Get the user data
// @Description Get the user data
// @Tags Client
// @ID Unsubscribe
// @Param data body tokenBody true "body json"
// @Success 200
// @Security RokwireAuth UserAuth
// @Router /user-data [get]
func (h ApisHandler) GetUserData(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse {
userData, err := h.app.Services.GetUserData(claims.OrgID, claims.AppID, claims.Subject)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionUpdate, "user", nil, err, http.StatusInternalServerError, true)
}

responseData, err := json.Marshal(userData)
if err != nil {
return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, true)
}

return l.HTTPResponseSuccessJSON(responseData)
}

// TODO - for now all fields but almost all of them will be removed!
type getUserMessageResponse struct {
OrgID string `json:"org_id"`
Expand Down
36 changes: 36 additions & 0 deletions driver/web/docs/gen/def.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,28 @@ paths:
description: Unauthorized
'500':
description: Internal error
/api/user-data:
get:
tags:
- Client
summary: Get the user data
description: |
Get the user data
security:
- bearerAuth: []
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/UserDataResponse'
'400':
description: Bad request
'401':
description: Unauthorized
'500':
description: Internal error
/api/admin/app-versions:
get:
tags:
Expand Down Expand Up @@ -1422,6 +1444,20 @@ components:
type: string
date_updated:
type: string
UserDataResponse:
type: object
properties:
received_notificationsst:
type: array
items:
$ref: '#/components/schemas/Message'
scheduled_notifications_for_me:
type: array
items:
$ref: '#/components/schemas/Message'
users:
items:
$ref: '#/components/schemas/User'
_shared_req_CreateMessages:
type: array
items:
Expand Down
3 changes: 3 additions & 0 deletions driver/web/docs/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ paths:
$ref: "./resources/client/topic/topics-subscribe.yaml"
/api/topic/{topic}/unsubscribe:
$ref: "./resources/client/topic/topics-unsubscribe.yaml"
/api/user-data:
$ref: "./resources/client/user-data.yaml"
#Admin
/api/admin/app-versions:
$ref: "./resources/admin/app-versions.yaml"
Expand Down Expand Up @@ -81,6 +83,7 @@ paths:
$ref: "./resources/bbs/message-id.yaml"
/api/bbs/mail:
$ref: "./resources/bbs/mail.yaml"



components:
Expand Down
21 changes: 21 additions & 0 deletions driver/web/docs/resources/client/user-data.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
get:
tags:
- Client
summary: Get the user data
description: |
Get the user data
security:
- bearerAuth: []
responses:
200:
description: Success
content:
application/json:
schema:
$ref: "../../schemas/application/UserDataResponse.yaml"
400:
description: Bad request
401:
description: Unauthorized
500:
description: Internal error
26 changes: 26 additions & 0 deletions driver/web/docs/schemas/application/QueueItem.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type: object
properties:
_id:
type: string
user_id:
type: string
org_id:
type: string
app_id:
type: string
message_id:
type: string
message_recipient_id:
type: string
subjects:
type: string
body:
type: string
data:
type: object
time:
type: integer
format: int64
priority:
type: integer

14 changes: 14 additions & 0 deletions driver/web/docs/schemas/application/UserDataResponse.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type: object
properties:
received_notificationsst:
type: array
items:
$ref: "./Message.yaml"
scheduled_notifications_for_me:
type: array
items:
$ref: "./Message.yaml"
users:
items:
$ref: "./User.yaml"

3 changes: 2 additions & 1 deletion driver/web/docs/schemas/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ Topic:
$ref: "./application/Topic.yaml"
User:
$ref: "./application/User.yaml"

UserDataResponse:
$ref: "./application/UserDataResponse.yaml"
##### APIs requests and responses - they are at bottom

## SHARED requests and responses
Expand Down
Loading