mattermost-community-enterp.../channels/app/remote_cluster.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
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>
2025-12-17 23:59:07 +09:00

233 lines
8.0 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"database/sql"
"encoding/base64"
"net/http"
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/v8/channels/store/sqlstore"
"github.com/mattermost/mattermost/server/v8/platform/services/remotecluster"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
)
func (a *App) RegisterPluginForSharedChannels(rctx request.CTX, opts model.RegisterPluginOpts) (remoteID string, err error) {
// check for pluginID already registered
rc, err := a.Srv().Store().RemoteCluster().GetByPluginID(opts.PluginID)
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
// anything other than not_found is unrecoverable
return "", err
}
}
// if plugin is already registered then treat this as an update.
if rc != nil {
// plugin was deleted at some point
if rc.DeleteAt != 0 {
rctx.Logger().Debug("Restoring plugin registration for Shared Channels",
mlog.String("plugin_id", opts.PluginID),
mlog.String("remote_id", rc.RemoteId),
)
rc.DeleteAt = 0
} else {
rctx.Logger().Debug("Plugin already registered for Shared Channels",
mlog.String("plugin_id", opts.PluginID),
mlog.String("remote_id", rc.RemoteId),
)
}
rc.DisplayName = opts.Displayname
rc.Options = opts.GetOptionFlags()
if _, err = a.Srv().Store().RemoteCluster().Update(rc); err != nil {
return "", err
}
return rc.RemoteId, nil
}
rc = &model.RemoteCluster{
Name: opts.Displayname,
DisplayName: opts.Displayname,
SiteURL: model.SiteURLPlugin + opts.PluginID, // require a unique siteurl
Token: model.NewId(),
CreatorId: opts.CreatorID,
PluginID: opts.PluginID,
Options: opts.GetOptionFlags(),
}
rcSaved, err := a.Srv().Store().RemoteCluster().Save(rc)
if err != nil {
return "", err
}
rctx.Logger().Debug("Registered new plugin for Shared Channels",
mlog.String("plugin_id", opts.PluginID),
mlog.String("remote_id", rcSaved.RemoteId),
)
// ping the plugin remote immediately if the service is running
// If the service is not available the ping will happen once the
// service starts. This is expected since plugins start before the
// service.
rcService, _ := a.GetRemoteClusterService()
if rcService != nil {
rcService.PingNow(rcSaved)
}
return rcSaved.RemoteId, nil
}
func (a *App) UnregisterPluginForSharedChannels(pluginID string) error {
rc, err := a.Srv().Store().RemoteCluster().GetByPluginID(pluginID)
if err != nil {
return err
}
if rc.DeleteAt != 0 {
// plugin already unregistered, nothing to do
return nil
}
_, appErr := a.DeleteRemoteCluster(rc.RemoteId)
if appErr != nil {
return appErr
}
return nil
}
func (a *App) AddRemoteCluster(rc *model.RemoteCluster) (*model.RemoteCluster, *model.AppError) {
rc, err := a.Srv().Store().RemoteCluster().Save(rc)
if err != nil {
if sqlstore.IsUniqueConstraintError(errors.Cause(err), []string{sqlstore.RemoteClusterSiteURLUniqueIndex}) {
return nil, model.NewAppError("AddRemoteCluster", "api.remote_cluster.save_not_unique.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return nil, model.NewAppError("AddRemoteCluster", "api.remote_cluster.save.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return rc, nil
}
func (a *App) PatchRemoteCluster(rcId string, patch *model.RemoteClusterPatch) (*model.RemoteCluster, *model.AppError) {
rc, err := a.GetRemoteCluster(rcId, false)
if err != nil {
return nil, err
}
rc.Patch(patch)
updatedRC, err := a.UpdateRemoteCluster(rc)
if err != nil {
return nil, err
}
return updatedRC, nil
}
func (a *App) UpdateRemoteCluster(rc *model.RemoteCluster) (*model.RemoteCluster, *model.AppError) {
rc, err := a.Srv().Store().RemoteCluster().Update(rc)
if err != nil {
if sqlstore.IsUniqueConstraintError(errors.Cause(err), []string{sqlstore.RemoteClusterSiteURLUniqueIndex}) {
return nil, model.NewAppError("UpdateRemoteCluster", "api.remote_cluster.update_not_unique.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return nil, model.NewAppError("UpdateRemoteCluster", "api.remote_cluster.update.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return rc, nil
}
func (a *App) DeleteRemoteCluster(remoteClusterId string) (bool, *model.AppError) {
deleted, err := a.Srv().Store().RemoteCluster().Delete(remoteClusterId)
if err != nil {
return false, model.NewAppError("DeleteRemoteCluster", "api.remote_cluster.delete.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return deleted, nil
}
func (a *App) GetRemoteCluster(remoteClusterId string, includeDeleted bool) (*model.RemoteCluster, *model.AppError) {
rc, err := a.Srv().Store().RemoteCluster().Get(remoteClusterId, includeDeleted)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, model.NewAppError("GetRemoteCluster", "api.remote_cluster.get.not_found", nil, "", http.StatusNotFound).Wrap(err)
default:
return nil, model.NewAppError("GetRemoteCluster", "api.remote_cluster.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
}
return rc, nil
}
func (a *App) GetAllRemoteClusters(page, perPage int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) {
list, err := a.Srv().Store().RemoteCluster().GetAll(page*perPage, perPage, filter)
if err != nil {
return nil, model.NewAppError("GetAllRemoteClusters", "api.remote_cluster.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return list, nil
}
func (a *App) UpdateRemoteClusterTopics(remoteClusterId string, topics string) (*model.RemoteCluster, *model.AppError) {
rc, err := a.Srv().Store().RemoteCluster().UpdateTopics(remoteClusterId, topics)
if err != nil {
return nil, model.NewAppError("UpdateRemoteClusterTopics", "api.remote_cluster.save.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return rc, nil
}
func (a *App) SetRemoteClusterLastPingAt(remoteClusterId string) *model.AppError {
err := a.Srv().Store().RemoteCluster().SetLastPingAt(remoteClusterId)
if err != nil {
return model.NewAppError("SetRemoteClusterLastPingAt", "api.remote_cluster.save.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return nil
}
func (a *App) GetRemoteClusterService() (remotecluster.RemoteClusterServiceIFace, *model.AppError) {
service := a.Srv().GetRemoteClusterService()
if service == nil {
return nil, model.NewAppError("GetRemoteClusterService", "api.remote_cluster.service_not_enabled.app_error", nil, "", http.StatusNotImplemented)
}
return service, nil
}
func (a *App) CreateRemoteClusterInvite(remoteId, siteURL, token, password string) (string, *model.AppError) {
invite := &model.RemoteClusterInvite{
RemoteId: remoteId,
SiteURL: siteURL,
Token: token,
Version: 3,
}
if err := invite.IsValid(); err != nil {
return "", model.NewAppError("CreateRemoteClusterInvite", "api.remote_cluster.create_invite_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
encrypted, err := invite.Encrypt(password)
if err != nil {
return "", model.NewAppError("CreateRemoteClusterInvite", "api.remote_cluster.encrypt_invite_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return base64.URLEncoding.EncodeToString(encrypted), nil
}
func (a *App) DecryptRemoteClusterInvite(inviteCode, password string) (*model.RemoteClusterInvite, *model.AppError) {
decoded, err := base64.URLEncoding.DecodeString(inviteCode)
if err != nil {
return nil, model.NewAppError("DecryptRemoteClusterInvite", "api.remote_cluster.base64_decode_error", nil, "", http.StatusBadRequest).Wrap(err)
}
invite := &model.RemoteClusterInvite{}
if dErr := invite.Decrypt(decoded, password); dErr != nil {
return nil, model.NewAppError("DecryptRemoteClusterInvite", "api.remote_cluster.invite_decrypt_error", nil, "", http.StatusBadRequest).Wrap(dErr)
}
return invite, nil
}