This commit is contained in:
2026-01-30 11:49:04 +03:00
commit f8bb05a652
48 changed files with 9538 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
package plugin
import (
"encoding/json"
"github.com/gorilla/mux"
"github.com/mattermost/mattermost/server/public/plugin"
"net/http"
"path/filepath"
)
type httpResponse struct {
Status string `json:"status"`
Error string `json:"error,omitempty"`
Data interface{} `json:"data,omitempty"`
}
func (p *Plugin) sendError(w http.ResponseWriter, err string) {
errEnc := json.NewEncoder(w).Encode(httpResponse{
Status: "error",
Error: err,
})
if errEnc != nil {
p.API.LogError("cant encode error response", "error", errEnc)
}
}
func (p *Plugin) sendRes(w http.ResponseWriter, resp httpResponse) {
errEnc := json.NewEncoder(w).Encode(resp)
if errEnc != nil {
p.API.LogError("cant encode error response", "error", errEnc)
}
}
func (p *Plugin) InitApi() {
p.router = mux.NewRouter()
p.router.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if userID := r.Header.Get("Mattermost-User-Id"); userID != "" {
next.ServeHTTP(w, r)
return
}
http.Error(w, "Not authorized", http.StatusUnauthorized)
})
})
// Add your API routes here
p.router.HandleFunc("/example", p.handleExample).Methods("GET")
p.router.HandleFunc("/assets/{fileName}", p.handleAssetFile).Methods("GET")
}
func (p *Plugin) ServeHTTP(_ *plugin.Context, w http.ResponseWriter, r *http.Request) {
p.router.ServeHTTP(w, r)
}
func (p *Plugin) handleAssetFile(w http.ResponseWriter, r *http.Request) {
fileName := mux.Vars(r)["fileName"]
http.ServeFile(w, r, filepath.Join(p.bundlePath, "assets", fileName))
}
func (p *Plugin) handleExample(w http.ResponseWriter, r *http.Request) {
p.sendRes(w, httpResponse{
Status: "OK",
Data: map[string]interface{}{
"message": "Hello from template plugin",
},
})
}
+75
View File
@@ -0,0 +1,75 @@
package plugin
import (
"reflect"
"github.com/pkg/errors"
)
type Configuration struct {
// Add your configuration fields here
}
// Clone shallow copies the Configuration. Your implementation may require a deep copy if
// your Configuration has reference types.
func (c *Configuration) Clone() *Configuration {
var clone = *c
return &clone
}
// GetConfiguration retrieves the active Configuration under lock, making it safe to use
// concurrently. The active Configuration may change underneath the client of this method, but
// the struct returned by this API call is considered immutable.
func (p *Plugin) GetConfiguration() *Configuration {
p.configurationLock.RLock()
defer p.configurationLock.RUnlock()
if p.configuration == nil {
return &Configuration{}
}
return p.configuration
}
// SetConfiguration replaces the active Configuration under lock.
//
// Do not call SetConfiguration while holding the configurationLock, as sync.Mutex is not
// reentrant. In particular, avoid using the plugin API entirely, as this may in turn trigger a
// hook back into the plugin. If that hook attempts to acquire this lock, a deadlock may occur.
//
// This method panics if SetConfiguration is called with the existing Configuration. This almost
// certainly means that the Configuration was modified without being cloned and may result in
// an unsafe access.
func (p *Plugin) SetConfiguration(configuration *Configuration) {
p.configurationLock.Lock()
defer p.configurationLock.Unlock()
p.API.LogInfo("Setting configuration")
if configuration != nil && p.configuration == configuration {
// Ignore assignment if the Configuration struct is empty. Go will optimize the
// allocation for same to point at the same memory address, breaking the check
// above.
if reflect.ValueOf(*configuration).NumField() == 0 {
return
}
p.API.LogInfo("Panic in SetConfiguration")
panic("SetConfiguration called with the existing Configuration")
}
p.configuration = configuration
}
// OnConfigurationChange is invoked when Configuration changes may have been made.
func (p *Plugin) OnConfigurationChange() error {
p.API.LogInfo("OnConfigurationChange")
var configuration = new(Configuration)
// Load the public Configuration fields from the Mattermost server Configuration.
err := p.API.LoadPluginConfiguration(configuration)
if err == nil {
p.SetConfiguration(configuration)
return nil
} else {
return errors.Wrap(err, "failed to load plugin Configuration")
}
}
+56
View File
@@ -0,0 +1,56 @@
package plugin
import (
"sync"
"git.wilix.dev/loop/loop-plugin-starter-template/server/telemetry"
"github.com/gorilla/mux"
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/public/pluginapi"
)
var buildHash string
var rudderWriteKey string
var rudderDataplaneURL string
// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
type Plugin struct {
plugin.MattermostPlugin
IsReady bool
bundlePath string
configurationLock sync.RWMutex
configuration *Configuration
sdk *pluginapi.Client
router *mux.Router
mut sync.RWMutex
telemetry *telemetry.Client
}
func (p *Plugin) OnActivate() error {
p.API.LogInfo("Activating template plugin...")
p.sdk = pluginapi.NewClient(p.API, p.Driver)
if p.router == nil {
p.InitApi()
}
configuration := p.GetConfiguration()
p.configuration = configuration
bundlePath, err := p.API.GetBundlePath()
if err != nil {
return err
}
p.bundlePath = bundlePath
p.IsReady = true
return nil
}
func (p *Plugin) OnDeactivate() error {
p.API.LogInfo("Deactivating template plugin...")
if err := p.uninitTelemetry(); err != nil {
p.API.LogError(err.Error())
}
return nil
}
+3
View File
@@ -0,0 +1,3 @@
package plugin
// Add your store utility functions here
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) 2020-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package plugin
import (
"git.wilix.dev/loop/loop-plugin-starter-template/server/telemetry"
)
func (p *Plugin) uninitTelemetry() error {
p.mut.Lock()
defer p.mut.Unlock()
if p.telemetry == nil {
return nil
}
return p.telemetry.Close()
}
func (p *Plugin) initTelemetry(enableDiagnostics *bool) error {
p.mut.Lock()
defer p.mut.Unlock()
if p.telemetry == nil && enableDiagnostics != nil && *enableDiagnostics {
p.API.LogDebug("Initializing telemetry")
// setup telemetry
client, err := telemetry.NewClient(telemetry.ClientConfig{
WriteKey: rudderWriteKey,
DataplaneURL: rudderDataplaneURL,
DiagnosticID: p.API.GetDiagnosticId(),
DefaultProps: map[string]any{
"ServerVersion": p.API.GetServerVersion(),
"PluginBuild": buildHash,
},
})
if err != nil {
return err
}
p.telemetry = client
} else if p.telemetry != nil && (enableDiagnostics == nil || !*enableDiagnostics) {
p.API.LogDebug("Deinitializing telemetry")
// destroy telemetry
if err := p.telemetry.Close(); err != nil {
return err
}
p.telemetry = nil
}
return nil
}