Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
140 lines
4.6 KiB
Go
140 lines
4.6 KiB
Go
// 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
|
|
}
|