// Copyright (c) 2024 Mattermost Community Enterprise // Notification Interface Implementation (ID-loaded push notifications) package notification import ( "net/http" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" "github.com/mattermost/mattermost/server/v8/channels/store" ) // NotificationConfig holds configuration for the notification interface type NotificationConfig struct { Store store.Store Config func() *model.Config Logger mlog.LoggerIFace } // NotificationImpl implements the NotificationInterface type NotificationImpl struct { store store.Store config func() *model.Config logger mlog.LoggerIFace } // NewNotificationInterface creates a new notification interface func NewNotificationInterface(cfg *NotificationConfig) *NotificationImpl { return &NotificationImpl{ store: cfg.Store, config: cfg.Config, logger: cfg.Logger, } } // GetNotificationMessage retrieves the full notification message for an ID-loaded push notification // This is used when a push notification was sent with minimal data (ID-only) and the client // needs to fetch the full message content func (n *NotificationImpl) GetNotificationMessage(rctx request.CTX, ack *model.PushNotificationAck, userID string) (*model.PushNotification, *model.AppError) { if ack == nil { return nil, model.NewAppError("GetNotificationMessage", "notification.ack_nil", nil, "Push notification ack is nil", http.StatusBadRequest) } if ack.PostId == "" { return nil, model.NewAppError("GetNotificationMessage", "notification.post_id_missing", nil, "Post ID is missing from ack", http.StatusBadRequest) } if n.store == nil { return nil, model.NewAppError("GetNotificationMessage", "notification.store_not_available", nil, "Store is not available", http.StatusInternalServerError) } // Get the post post, err := n.store.Post().GetSingle(rctx, ack.PostId, false) if err != nil { return nil, model.NewAppError("GetNotificationMessage", "notification.post_not_found", map[string]any{"PostId": ack.PostId}, err.Error(), http.StatusNotFound) } // Verify the user has access to this post channel, err := n.store.Channel().Get(post.ChannelId, true) if err != nil { return nil, model.NewAppError("GetNotificationMessage", "notification.channel_not_found", map[string]any{"ChannelId": post.ChannelId}, err.Error(), http.StatusNotFound) } // Check if user is a member of the channel member, err := n.store.Channel().GetMember(rctx, post.ChannelId, userID) if err != nil || member == nil { return nil, model.NewAppError("GetNotificationMessage", "notification.user_not_member", map[string]any{"UserId": userID, "ChannelId": post.ChannelId}, "User is not a member of the channel", http.StatusForbidden) } // Get the sender information sender, err := n.store.User().Get(rctx.Context(), post.UserId) if err != nil { n.logger.Warn("Could not get sender for notification", mlog.String("sender_id", post.UserId), mlog.Err(err), ) } // Get team information var teamID string if channel.TeamId != "" { teamID = channel.TeamId } // Build the push notification notification := &model.PushNotification{ PostId: post.Id, ChannelId: post.ChannelId, ChannelName: channel.DisplayName, TeamId: teamID, Message: post.Message, Type: ack.NotificationType, IsIdLoaded: true, } // Add sender info if available if sender != nil { notification.SenderId = sender.Id notification.SenderName = sender.GetDisplayName(model.ShowNicknameFullName) } // Handle root post for threads if post.RootId != "" { notification.RootId = post.RootId } // Set the category based on notification type switch ack.NotificationType { case model.PushTypeMessage: notification.Category = "CAN_REPLY" case model.PushTypeClear: notification.Category = "" case model.PushTypeUpdateBadge: notification.Category = "" default: notification.Category = "CAN_REPLY" } n.logger.Debug("Retrieved notification message", mlog.String("post_id", post.Id), mlog.String("user_id", userID), mlog.String("channel_id", post.ChannelId), ) return notification, nil } // CheckLicense validates that the license allows ID-loaded push notifications // For community enterprise, we always allow this feature func (n *NotificationImpl) CheckLicense() *model.AppError { // In the community enterprise implementation, we don't require a license // The official Mattermost enterprise requires a valid license for this feature n.logger.Debug("CheckLicense called - community enterprise allows ID-loaded notifications without license") return nil }