Merge branch 'master' into mpa-playbooks
Этот коммит содержится в:
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/plugin"
|
||||
"github.com/mattermost/mattermost-server/v6/product"
|
||||
"github.com/mattermost/mattermost-server/v6/services/httpservice"
|
||||
"github.com/mattermost/mattermost-server/v6/services/imageproxy"
|
||||
"github.com/mattermost/mattermost-server/v6/services/remotecluster"
|
||||
@@ -651,6 +652,7 @@ type AppIface interface {
|
||||
GetGroupMemberCount(groupID string, viewRestrictions *model.ViewUsersRestrictions) (int64, *model.AppError)
|
||||
GetGroupMemberUsers(groupID string) ([]*model.User, *model.AppError)
|
||||
GetGroupMemberUsersPage(groupID string, page int, perPage int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, int, *model.AppError)
|
||||
GetGroupMemberUsersSortedPage(groupID string, page int, perPage int, viewRestrictions *model.ViewUsersRestrictions, teammateNameDisplay string) ([]*model.User, int, *model.AppError)
|
||||
GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError)
|
||||
GetGroupSyncables(groupID string, syncableType model.GroupSyncableType) ([]*model.GroupSyncable, *model.AppError)
|
||||
GetGroups(page, perPage int, opts model.GroupSearchOpts, viewRestrictions *model.ViewUsersRestrictions) ([]*model.Group, *model.AppError)
|
||||
@@ -809,7 +811,7 @@ type AppIface interface {
|
||||
GetTopReactionsForUserSince(userID string, teamID string, opts *model.InsightsOpts) (*model.TopReactionList, *model.AppError)
|
||||
GetTopThreadsForTeamSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopThreadList, *model.AppError)
|
||||
GetTopThreadsForUserSince(c request.CTX, teamID, userID string, opts *model.InsightsOpts) (*model.TopThreadList, *model.AppError)
|
||||
GetUploadSession(uploadId string) (*model.UploadSession, *model.AppError)
|
||||
GetUploadSession(c request.CTX, uploadId string) (*model.UploadSession, *model.AppError)
|
||||
GetUploadSessionsForUser(userID string) ([]*model.UploadSession, *model.AppError)
|
||||
GetUser(userID string) (*model.User, *model.AppError)
|
||||
GetUserAccessToken(tokenID string, sanitize bool) (*model.UserAccessToken, *model.AppError)
|
||||
@@ -868,6 +870,7 @@ type AppIface interface {
|
||||
HasPermissionToTeam(askingUserId string, teamID string, permission *model.Permission) bool
|
||||
HasPermissionToUser(askingUserId string, userID string) bool
|
||||
HasSharedChannel(channelID string) (bool, error)
|
||||
HooksManager() *product.HooksManager
|
||||
ImageProxy() *imageproxy.ImageProxy
|
||||
ImageProxyAdder() func(string) string
|
||||
ImageProxyRemover() (f func(string) string)
|
||||
@@ -916,6 +919,7 @@ type AppIface interface {
|
||||
Notification() einterfaces.NotificationInterface
|
||||
NotificationsLog() *mlog.Logger
|
||||
NotifyAndSetWarnMetricAck(warnMetricId string, sender *model.User, forceAck bool, isBot bool) *model.AppError
|
||||
NotifySelfHostedSignupProgress(progress string, userId string)
|
||||
NotifySharedChannelUserUpdate(user *model.User)
|
||||
OpenInteractiveDialog(request model.OpenDialogRequest) *model.AppError
|
||||
OriginChecker() func(*http.Request) bool
|
||||
@@ -1147,7 +1151,7 @@ type AppIface interface {
|
||||
UpdateUserAuth(userID string, userAuth *model.UserAuth) (*model.UserAuth, *model.AppError)
|
||||
UpdateUserRoles(c request.CTX, userID string, newRoles string, sendWebSocketEvent bool) (*model.User, *model.AppError)
|
||||
UpdateUserRolesWithUser(c request.CTX, user *model.User, newRoles string, sendWebSocketEvent bool) (*model.User, *model.AppError)
|
||||
UploadData(c *request.Context, us *model.UploadSession, rd io.Reader) (*model.FileInfo, *model.AppError)
|
||||
UploadData(c request.CTX, us *model.UploadSession, rd io.Reader) (*model.FileInfo, *model.AppError)
|
||||
UploadEmojiImage(c request.CTX, id string, imageData *multipart.FileHeader) *model.AppError
|
||||
UpsertDraft(c *request.Context, draft *model.Draft, connectionID string) (*model.Draft, *model.AppError)
|
||||
UpsertGroupMember(groupID string, userID string) (*model.GroupMember, *model.AppError)
|
||||
@@ -1160,4 +1164,5 @@ type AppIface interface {
|
||||
VerifyUserEmail(userID, email string) *model.AppError
|
||||
ViewChannel(c request.CTX, view *model.ChannelView, userID string, currentSessionId string, collapsedThreadsSupported bool) (map[string]int64, *model.AppError)
|
||||
WriteFile(fr io.Reader, path string) (int64, *model.AppError)
|
||||
WriteFileContext(ctx context.Context, fr io.Reader, path string) (int64, *model.AppError)
|
||||
}
|
||||
|
||||
@@ -168,6 +168,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
|
||||
model.PermissionCreateCustomGroup.Id,
|
||||
model.PermissionEditCustomGroup.Id,
|
||||
model.PermissionDeleteCustomGroup.Id,
|
||||
model.PermissionRestoreCustomGroup.Id,
|
||||
model.PermissionManageCustomGroupMembers.Id,
|
||||
},
|
||||
"system_post_all": {
|
||||
@@ -228,6 +229,7 @@ func TestDoEmojisPermissionsMigration(t *testing.T) {
|
||||
model.PermissionEditCustomGroup.Id,
|
||||
model.PermissionDeleteCustomGroup.Id,
|
||||
model.PermissionManageCustomGroupMembers.Id,
|
||||
model.PermissionRestoreCustomGroup.Id,
|
||||
model.PermissionListPublicTeams.Id,
|
||||
model.PermissionJoinPublicTeams.Id,
|
||||
model.PermissionCreateDirectChannel.Id,
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
const ServerKey product.ServiceKey = "server"
|
||||
|
||||
// licenseSvc is added to act as a starting point for future integrated products.
|
||||
// It has the same signature and functionality with the license related APIs of the plugin-api.
|
||||
type licenseSvc interface {
|
||||
@@ -86,19 +88,24 @@ type Channels struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterProduct("channels", ProductManifest{
|
||||
Initializer: func(s *Server, services map[ServiceKey]any) (Product, error) {
|
||||
return NewChannels(s, services)
|
||||
product.RegisterProduct("channels", product.Manifest{
|
||||
Initializer: func(services map[product.ServiceKey]any) (product.Product, error) {
|
||||
return NewChannels(services)
|
||||
},
|
||||
Dependencies: map[ServiceKey]struct{}{
|
||||
ConfigKey: {},
|
||||
LicenseKey: {},
|
||||
FilestoreKey: {},
|
||||
Dependencies: map[product.ServiceKey]struct{}{
|
||||
ServerKey: {},
|
||||
product.ConfigKey: {},
|
||||
product.LicenseKey: {},
|
||||
product.FilestoreKey: {},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) {
|
||||
func NewChannels(services map[product.ServiceKey]any) (*Channels, error) {
|
||||
s, ok := services[ServerKey].(*Server)
|
||||
if !ok {
|
||||
return nil, errors.New("server not passed")
|
||||
}
|
||||
ch := &Channels{
|
||||
srv: s,
|
||||
imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()),
|
||||
@@ -112,10 +119,10 @@ func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) {
|
||||
// 2. Add the field to *Channels
|
||||
// 3. Add the service key to the slice.
|
||||
// 4. Add a new case in the switch statement.
|
||||
requiredServices := []ServiceKey{
|
||||
ConfigKey,
|
||||
LicenseKey,
|
||||
FilestoreKey,
|
||||
requiredServices := []product.ServiceKey{
|
||||
product.ConfigKey,
|
||||
product.LicenseKey,
|
||||
product.FilestoreKey,
|
||||
}
|
||||
for _, svcKey := range requiredServices {
|
||||
svc, ok := services[svcKey]
|
||||
@@ -124,19 +131,19 @@ func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) {
|
||||
}
|
||||
switch svcKey {
|
||||
// Keep adding more services here
|
||||
case ConfigKey:
|
||||
case product.ConfigKey:
|
||||
cfgSvc, ok := svc.(product.ConfigService)
|
||||
if !ok {
|
||||
return nil, errors.New("Config service did not satisfy ConfigSvc interface")
|
||||
}
|
||||
ch.cfgSvc = cfgSvc
|
||||
case FilestoreKey:
|
||||
case product.FilestoreKey:
|
||||
filestore, ok := svc.(filestore.FileBackend)
|
||||
if !ok {
|
||||
return nil, errors.New("Filestore service did not satisfy FileBackend interface")
|
||||
}
|
||||
ch.filestore = filestore
|
||||
case LicenseKey:
|
||||
case product.LicenseKey:
|
||||
svc, ok := svc.(licenseSvc)
|
||||
if !ok {
|
||||
return nil, errors.New("License service did not satisfy licenseSvc interface")
|
||||
@@ -198,7 +205,7 @@ func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) {
|
||||
}
|
||||
|
||||
ch.routerSvc = newRouterService()
|
||||
services[RouterKey] = ch.routerSvc
|
||||
services[product.RouterKey] = ch.routerSvc
|
||||
|
||||
// Setup routes.
|
||||
pluginsRoute := ch.srv.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter()
|
||||
@@ -206,29 +213,29 @@ func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) {
|
||||
pluginsRoute.HandleFunc("/public/{public_file:.*}", ch.ServePluginPublicRequest)
|
||||
pluginsRoute.HandleFunc("/{anything:.*}", ch.ServePluginRequest)
|
||||
|
||||
services[PostKey] = &postServiceWrapper{
|
||||
services[product.PostKey] = &postServiceWrapper{
|
||||
app: &App{ch: ch},
|
||||
}
|
||||
|
||||
services[PermissionsKey] = &permissionsServiceWrapper{
|
||||
services[product.PermissionsKey] = &permissionsServiceWrapper{
|
||||
app: &App{ch: ch},
|
||||
}
|
||||
|
||||
services[TeamKey] = &teamServiceWrapper{
|
||||
services[product.TeamKey] = &teamServiceWrapper{
|
||||
app: &App{ch: ch},
|
||||
}
|
||||
|
||||
services[BotKey] = &botServiceWrapper{
|
||||
services[product.BotKey] = &botServiceWrapper{
|
||||
app: &App{ch: ch},
|
||||
}
|
||||
|
||||
services[HooksKey] = &hooksService{
|
||||
services[product.HooksKey] = &hooksService{
|
||||
ch: ch,
|
||||
}
|
||||
|
||||
services[UserKey] = &App{ch: ch}
|
||||
services[product.UserKey] = &App{ch: ch}
|
||||
|
||||
services[PreferencesKey] = &preferencesServiceWrapper{
|
||||
services[product.PreferencesKey] = &preferencesServiceWrapper{
|
||||
app: &App{ch: ch},
|
||||
}
|
||||
|
||||
@@ -310,6 +317,10 @@ func (ch *Channels) RequestTrialLicense(requesterID string, users int, termsAcce
|
||||
receiveEmailsAccepted)
|
||||
}
|
||||
|
||||
func (a *App) HooksManager() *product.HooksManager {
|
||||
return a.Srv().hooksManager
|
||||
}
|
||||
|
||||
// Ensure hooksService implements `product.HooksService`
|
||||
var _ product.HooksService = (*hooksService)(nil)
|
||||
|
||||
@@ -318,10 +329,6 @@ type hooksService struct {
|
||||
}
|
||||
|
||||
func (s *hooksService) RegisterHooks(productID string, hooks any) error {
|
||||
if s.ch.pluginsEnvironment == nil {
|
||||
return errors.New("could not find plugins environment")
|
||||
}
|
||||
|
||||
return s.ch.srv.hooksManager.AddProduct(productID, hooks)
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ func TestExportAllUsers(t *testing.T) {
|
||||
defer th2.TearDown()
|
||||
err, i := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, i)
|
||||
assert.EqualValues(t, 0, i)
|
||||
|
||||
users1, err := th1.App.GetUsersFromProfiles(&model.UserGetOptions{
|
||||
Page: 0,
|
||||
@@ -323,7 +323,7 @@ func TestExportDMChannelToSelf(t *testing.T) {
|
||||
// import the exported channel
|
||||
err, i := th2.App.BulkImport(th2.Context, &b, nil, false, 5)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, i)
|
||||
assert.EqualValues(t, 0, i)
|
||||
|
||||
channels, nErr = th2.App.Srv().Store().Channel().GetAllDirectChannelsForExportAfter(1000, "00000000")
|
||||
require.NoError(t, nErr)
|
||||
|
||||
29
app/file.go
29
app/file.go
@@ -161,6 +161,10 @@ func (a *App) MoveFile(oldPath, newPath string) *model.AppError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) WriteFileContext(ctx context.Context, fr io.Reader, path string) (int64, *model.AppError) {
|
||||
return a.Srv().writeFileContext(ctx, fr, path)
|
||||
}
|
||||
|
||||
func (a *App) WriteFile(fr io.Reader, path string) (int64, *model.AppError) {
|
||||
return a.Srv().writeFile(fr, path)
|
||||
}
|
||||
@@ -173,6 +177,30 @@ func (s *Server) writeFile(fr io.Reader, path string) (int64, *model.AppError) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Server) writeFileContext(ctx context.Context, fr io.Reader, path string) (int64, *model.AppError) {
|
||||
type ContextWriter interface {
|
||||
WriteFileContext(context.Context, io.Reader, string) (int64, error)
|
||||
}
|
||||
|
||||
var (
|
||||
fileBackend = s.FileBackend()
|
||||
written int64
|
||||
err error
|
||||
)
|
||||
|
||||
// Check if we can provide a custom context, otherwise just use the default method.
|
||||
if cw, ok := fileBackend.(ContextWriter); ok {
|
||||
written, err = cw.WriteFileContext(ctx, fr, path)
|
||||
} else {
|
||||
written, err = fileBackend.WriteFile(fr, path)
|
||||
}
|
||||
if err != nil {
|
||||
return written, model.NewAppError("WriteFile", "api.file.write_file.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return written, nil
|
||||
}
|
||||
|
||||
func (a *App) AppendFile(fr io.Reader, path string) (int64, *model.AppError) {
|
||||
result, nErr := a.FileBackend().AppendFile(fr, path)
|
||||
if nErr != nil {
|
||||
@@ -796,6 +824,7 @@ func (t *UploadFileTask) postprocessImage(file io.Reader) {
|
||||
_, aerr := t.writeFile(r, path)
|
||||
if aerr != nil {
|
||||
mlog.Error("Unable to upload", mlog.String("path", path), mlog.Err(aerr))
|
||||
r.CloseWithError(aerr) // always returns nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,8 +250,8 @@ func (a *App) GetGroupMemberUsers(groupID string) ([]*model.User, *model.AppErro
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (a *App) GetGroupMemberUsersPage(groupID string, page int, perPage int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, int, *model.AppError) {
|
||||
members, err := a.Srv().Store().Group().GetMemberUsersPage(groupID, page, perPage, viewRestrictions)
|
||||
func (a *App) GetGroupMemberUsersSortedPage(groupID string, page int, perPage int, viewRestrictions *model.ViewUsersRestrictions, teammateNameDisplay string) ([]*model.User, int, *model.AppError) {
|
||||
members, err := a.Srv().Store().Group().GetMemberUsersSortedPage(groupID, page, perPage, viewRestrictions, teammateNameDisplay)
|
||||
if err != nil {
|
||||
return nil, 0, model.NewAppError("GetGroupMemberUsersPage", "app.select_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
@@ -263,6 +263,10 @@ func (a *App) GetGroupMemberUsersPage(groupID string, page int, perPage int, vie
|
||||
return a.sanitizeProfiles(members, false), int(count), nil
|
||||
}
|
||||
|
||||
func (a *App) GetGroupMemberUsersPage(groupID string, page int, perPage int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, int, *model.AppError) {
|
||||
return a.GetGroupMemberUsersSortedPage(groupID, page, perPage, viewRestrictions, model.ShowUsername)
|
||||
}
|
||||
|
||||
func (a *App) GetUsersNotInGroupPage(groupID string, page int, perPage int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
|
||||
members, err := a.Srv().Store().Group().GetNonMemberUsersPage(groupID, page, perPage, viewRestrictions)
|
||||
if err != nil {
|
||||
|
||||
21
app/hosted_customer.go
Обычный файл
21
app/hosted_customer.go
Обычный файл
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func (a *App) NotifySelfHostedSignupProgress(progress string, userId string) {
|
||||
// this is an event only the relevant admin should receive.
|
||||
// If there is no progress, there is nothing to report.
|
||||
// If there is no userId, we do not want to mistakenly broadcast to all users.
|
||||
if progress == "" || userId == "" {
|
||||
return
|
||||
}
|
||||
message := model.NewWebSocketEvent(model.WebsocketEventHostedCustomerSignupProgressUpdated, "", "", userId, nil, "")
|
||||
message.Add("progress", progress)
|
||||
|
||||
a.Srv().Platform().Publish(message)
|
||||
}
|
||||
@@ -6,7 +6,6 @@ package app
|
||||
import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -26,6 +25,7 @@ type ReactionImportData = imports.ReactionImportData // part of the app interfac
|
||||
const (
|
||||
importMultiplePostsThreshold = 1000
|
||||
maxScanTokenSize = 16 * 1024 * 1024 // Need to set a higher limit than default because some customers cross the limit. See MM-22314
|
||||
statusUpdateAfterLines = 8192
|
||||
)
|
||||
|
||||
func stopOnError(c request.CTX, err imports.LineImportWorkerError) bool {
|
||||
@@ -41,7 +41,7 @@ func stopOnError(c request.CTX, err imports.LineImportWorkerError) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func processAttachmentPaths(files *[]imports.AttachmentImportData, basePath string, filesMap map[string]*zip.File) error {
|
||||
func processAttachmentPaths(c request.CTX, files *[]imports.AttachmentImportData, basePath string, filesMap map[string]*zip.File) error {
|
||||
if files == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -61,20 +61,20 @@ func processAttachmentPaths(files *[]imports.AttachmentImportData, basePath stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func processAttachments(line *imports.LineImportData, basePath string, filesMap map[string]*zip.File) error {
|
||||
func processAttachments(c request.CTX, line *imports.LineImportData, basePath string, filesMap map[string]*zip.File) error {
|
||||
var ok bool
|
||||
switch line.Type {
|
||||
case "post", "direct_post":
|
||||
var replies []imports.ReplyImportData
|
||||
if line.Type == "direct_post" {
|
||||
if err := processAttachmentPaths(line.DirectPost.Attachments, basePath, filesMap); err != nil {
|
||||
if err := processAttachmentPaths(c, line.DirectPost.Attachments, basePath, filesMap); err != nil {
|
||||
return err
|
||||
}
|
||||
if line.DirectPost.Replies != nil {
|
||||
replies = *line.DirectPost.Replies
|
||||
}
|
||||
} else {
|
||||
if err := processAttachmentPaths(line.Post.Attachments, basePath, filesMap); err != nil {
|
||||
if err := processAttachmentPaths(c, line.Post.Attachments, basePath, filesMap); err != nil {
|
||||
return err
|
||||
}
|
||||
if line.Post.Replies != nil {
|
||||
@@ -82,7 +82,7 @@ func processAttachments(line *imports.LineImportData, basePath string, filesMap
|
||||
}
|
||||
}
|
||||
for _, reply := range replies {
|
||||
if err := processAttachmentPaths(reply.Attachments, basePath, filesMap); err != nil {
|
||||
if err := processAttachmentPaths(c, reply.Attachments, basePath, filesMap); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,15 @@ func processAttachments(line *imports.LineImportData, basePath string, filesMap
|
||||
}
|
||||
|
||||
func (a *App) bulkImportWorker(c request.CTX, dryRun bool, wg *sync.WaitGroup, lines <-chan imports.LineImportWorkerData, errors chan<- imports.LineImportWorkerError) {
|
||||
workerID := model.NewId()
|
||||
processedLines := uint64(0)
|
||||
|
||||
c.Logger().Info("Started new bulk import worker", mlog.String("bulk_import_worker_id", workerID))
|
||||
defer func() {
|
||||
wg.Done()
|
||||
c.Logger().Info("Bulk import worker finished", mlog.String("bulk_import_worker_id", workerID), mlog.Uint64("processed_lines", processedLines))
|
||||
}()
|
||||
|
||||
postLines := []imports.LineImportWorkerData{}
|
||||
directPostLines := []imports.LineImportWorkerData{}
|
||||
for line := range lines {
|
||||
@@ -143,6 +152,11 @@ func (a *App) bulkImportWorker(c request.CTX, dryRun bool, wg *sync.WaitGroup, l
|
||||
errors <- imports.LineImportWorkerError{Error: err, LineNumber: line.LineNumber}
|
||||
}
|
||||
}
|
||||
|
||||
processedLines++
|
||||
if processedLines%statusUpdateAfterLines == 0 {
|
||||
c.Logger().Info("Worker progress", mlog.String("bulk_import_worker_id", workerID), mlog.Uint64("processed_lines", processedLines))
|
||||
}
|
||||
}
|
||||
|
||||
if len(postLines) > 0 {
|
||||
@@ -155,7 +169,6 @@ func (a *App) bulkImportWorker(c request.CTX, dryRun bool, wg *sync.WaitGroup, l
|
||||
errors <- imports.LineImportWorkerError{Error: err, LineNumber: errLine}
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func (a *App) BulkImport(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int) (*model.AppError, int) {
|
||||
@@ -194,15 +207,17 @@ func (a *App) bulkImport(c request.CTX, jsonlReader io.Reader, attachmentsReader
|
||||
}
|
||||
|
||||
for scanner.Scan() {
|
||||
decoder := json.NewDecoder(bytes.NewReader(scanner.Bytes()))
|
||||
lineNumber++
|
||||
if lineNumber%statusUpdateAfterLines == 0 {
|
||||
c.Logger().Info("Reader progress", mlog.Int("processed_lines", lineNumber))
|
||||
}
|
||||
|
||||
var line imports.LineImportData
|
||||
if err := decoder.Decode(&line); err != nil {
|
||||
if err := json.Unmarshal(scanner.Bytes(), &line); err != nil {
|
||||
return model.NewAppError("BulkImport", "app.import.bulk_import.json_decode.error", nil, "", http.StatusBadRequest).Wrap(err), lineNumber
|
||||
}
|
||||
|
||||
if err := processAttachments(&line, importPath, attachedFiles); err != nil {
|
||||
if err := processAttachments(c, &line, importPath, attachedFiles); err != nil {
|
||||
c.Logger().Warn("Error while processing import attachments. Objects might be broken.", mlog.Err(err))
|
||||
}
|
||||
|
||||
@@ -222,6 +237,12 @@ func (a *App) bulkImport(c request.CTX, jsonlReader io.Reader, attachmentsReader
|
||||
if line.Type != lastLineType {
|
||||
// Only clear the worker queue if is not the first data entry
|
||||
if lineNumber != 2 {
|
||||
c.Logger().Info(
|
||||
"Finished parsing segment, waiting for workers to finish",
|
||||
mlog.String("old_segment", lastLineType),
|
||||
mlog.String("new_segment", line.Type),
|
||||
)
|
||||
|
||||
// Changing type. Clear out the worker queue before continuing.
|
||||
close(linesChan)
|
||||
wg.Wait()
|
||||
@@ -235,6 +256,13 @@ func (a *App) bulkImport(c request.CTX, jsonlReader io.Reader, attachmentsReader
|
||||
}
|
||||
}
|
||||
|
||||
c.Logger().Info(
|
||||
"Starting workers for new segment",
|
||||
mlog.String("old_segment", lastLineType),
|
||||
mlog.String("new_segment", line.Type),
|
||||
mlog.Int("workers", workers),
|
||||
)
|
||||
|
||||
// Set up the workers and channel for this type.
|
||||
lastLineType = line.Type
|
||||
linesChan = make(chan imports.LineImportWorkerData, workers)
|
||||
@@ -290,7 +318,7 @@ func (a *App) importLine(c request.CTX, line imports.LineImportData, dryRun bool
|
||||
if line.Scheme == nil {
|
||||
return model.NewAppError("BulkImport", "app.import.import_line.null_scheme.error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
return a.importScheme(line.Scheme, dryRun)
|
||||
return a.importScheme(c, line.Scheme, dryRun)
|
||||
case line.Type == "team":
|
||||
if line.Team == nil {
|
||||
return model.NewAppError("BulkImport", "app.import.import_line.null_team.error", nil, "", http.StatusBadRequest)
|
||||
@@ -315,7 +343,7 @@ func (a *App) importLine(c request.CTX, line imports.LineImportData, dryRun bool
|
||||
if line.Emoji == nil {
|
||||
return model.NewAppError("BulkImport", "app.import.import_line.null_emoji.error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
return a.importEmoji(line.Emoji, dryRun)
|
||||
return a.importEmoji(c, line.Emoji, dryRun)
|
||||
default:
|
||||
return model.NewAppError("BulkImport", "app.import.import_line.unknown_line_type.error", map[string]any{"Type": line.Type}, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/logr/v2"
|
||||
"github.com/mattermost/mattermost-server/v6/app/imports"
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/app/teams"
|
||||
@@ -25,13 +26,16 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
)
|
||||
|
||||
//
|
||||
// -- Bulk Import Functions --
|
||||
// These functions import data directly into the database. Security and permission checks are bypassed but validity is
|
||||
// still enforced.
|
||||
//
|
||||
func (a *App) importScheme(c request.CTX, data *imports.SchemeImportData, dryRun bool) *model.AppError {
|
||||
var fields []logr.Field
|
||||
if data != nil && data.Name != nil {
|
||||
fields = append(fields, mlog.String("schema_name", *data.Name))
|
||||
}
|
||||
c.Logger().Info("Validating schema", fields...)
|
||||
|
||||
func (a *App) importScheme(data *imports.SchemeImportData, dryRun bool) *model.AppError {
|
||||
if err := imports.ValidateSchemeImportData(data); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -41,6 +45,8 @@ func (a *App) importScheme(data *imports.SchemeImportData, dryRun bool) *model.A
|
||||
return nil
|
||||
}
|
||||
|
||||
c.Logger().Info("Importing schema", fields...)
|
||||
|
||||
scheme, err := a.GetSchemeByName(*data.Name)
|
||||
if err != nil {
|
||||
scheme = new(model.Scheme)
|
||||
@@ -68,12 +74,12 @@ func (a *App) importScheme(data *imports.SchemeImportData, dryRun bool) *model.A
|
||||
|
||||
if scheme.Scope == model.SchemeScopeTeam {
|
||||
data.DefaultTeamAdminRole.Name = &scheme.DefaultTeamAdminRole
|
||||
if err := a.importRole(data.DefaultTeamAdminRole, dryRun, true); err != nil {
|
||||
if err := a.importRole(c, data.DefaultTeamAdminRole, dryRun, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data.DefaultTeamUserRole.Name = &scheme.DefaultTeamUserRole
|
||||
if err := a.importRole(data.DefaultTeamUserRole, dryRun, true); err != nil {
|
||||
if err := a.importRole(c, data.DefaultTeamUserRole, dryRun, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -83,19 +89,19 @@ func (a *App) importScheme(data *imports.SchemeImportData, dryRun bool) *model.A
|
||||
}
|
||||
}
|
||||
data.DefaultTeamGuestRole.Name = &scheme.DefaultTeamGuestRole
|
||||
if err := a.importRole(data.DefaultTeamGuestRole, dryRun, true); err != nil {
|
||||
if err := a.importRole(c, data.DefaultTeamGuestRole, dryRun, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if scheme.Scope == model.SchemeScopeTeam || scheme.Scope == model.SchemeScopeChannel {
|
||||
data.DefaultChannelAdminRole.Name = &scheme.DefaultChannelAdminRole
|
||||
if err := a.importRole(data.DefaultChannelAdminRole, dryRun, true); err != nil {
|
||||
if err := a.importRole(c, data.DefaultChannelAdminRole, dryRun, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data.DefaultChannelUserRole.Name = &scheme.DefaultChannelUserRole
|
||||
if err := a.importRole(data.DefaultChannelUserRole, dryRun, true); err != nil {
|
||||
if err := a.importRole(c, data.DefaultChannelUserRole, dryRun, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -105,7 +111,7 @@ func (a *App) importScheme(data *imports.SchemeImportData, dryRun bool) *model.A
|
||||
}
|
||||
}
|
||||
data.DefaultChannelGuestRole.Name = &scheme.DefaultChannelGuestRole
|
||||
if err := a.importRole(data.DefaultChannelGuestRole, dryRun, true); err != nil {
|
||||
if err := a.importRole(c, data.DefaultChannelGuestRole, dryRun, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -113,8 +119,15 @@ func (a *App) importScheme(data *imports.SchemeImportData, dryRun bool) *model.A
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) importRole(data *imports.RoleImportData, dryRun bool, isSchemeRole bool) *model.AppError {
|
||||
func (a *App) importRole(c request.CTX, data *imports.RoleImportData, dryRun bool, isSchemeRole bool) *model.AppError {
|
||||
var fields []logr.Field
|
||||
if data != nil && data.Name != nil {
|
||||
fields = append(fields, mlog.String("role_name", *data.Name))
|
||||
}
|
||||
|
||||
if !isSchemeRole {
|
||||
c.Logger().Info("Validating role", fields...)
|
||||
|
||||
if err := imports.ValidateRoleImportData(data); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -125,6 +138,8 @@ func (a *App) importRole(data *imports.RoleImportData, dryRun bool, isSchemeRole
|
||||
return nil
|
||||
}
|
||||
|
||||
c.Logger().Info("Importing role", fields...)
|
||||
|
||||
role, err := a.GetRoleByName(context.Background(), *data.Name)
|
||||
if err != nil {
|
||||
role = new(model.Role)
|
||||
@@ -160,6 +175,12 @@ func (a *App) importRole(data *imports.RoleImportData, dryRun bool, isSchemeRole
|
||||
}
|
||||
|
||||
func (a *App) importTeam(c request.CTX, data *imports.TeamImportData, dryRun bool) *model.AppError {
|
||||
var fields []logr.Field
|
||||
if data != nil && data.Name != nil {
|
||||
fields = append(fields, mlog.String("team_name", *data.Name))
|
||||
}
|
||||
c.Logger().Info("Validating team", fields...)
|
||||
|
||||
if err := imports.ValidateTeamImportData(data); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -169,6 +190,8 @@ func (a *App) importTeam(c request.CTX, data *imports.TeamImportData, dryRun boo
|
||||
return nil
|
||||
}
|
||||
|
||||
c.Logger().Info("Importing team", fields...)
|
||||
|
||||
var team *model.Team
|
||||
team, err := a.Srv().Store().Team().GetByName(*data.Name)
|
||||
|
||||
@@ -228,6 +251,12 @@ func (a *App) importTeam(c request.CTX, data *imports.TeamImportData, dryRun boo
|
||||
}
|
||||
|
||||
func (a *App) importChannel(c request.CTX, data *imports.ChannelImportData, dryRun bool) *model.AppError {
|
||||
var fields []logr.Field
|
||||
if data != nil && data.Name != nil {
|
||||
fields = append(fields, mlog.String("channel_name", *data.Name))
|
||||
}
|
||||
c.Logger().Info("Validating channel", fields...)
|
||||
|
||||
if err := imports.ValidateChannelImportData(data); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -237,6 +266,8 @@ func (a *App) importChannel(c request.CTX, data *imports.ChannelImportData, dryR
|
||||
return nil
|
||||
}
|
||||
|
||||
c.Logger().Info("Importing channel", fields...)
|
||||
|
||||
team, err := a.Srv().Store().Team().GetByName(*data.Team)
|
||||
if err != nil {
|
||||
return model.NewAppError("BulkImport", "app.import.import_channel.team_not_found.error", map[string]any{"TeamName": *data.Team}, "", http.StatusBadRequest).Wrap(err)
|
||||
@@ -293,6 +324,12 @@ func (a *App) importChannel(c request.CTX, data *imports.ChannelImportData, dryR
|
||||
}
|
||||
|
||||
func (a *App) importUser(c request.CTX, data *imports.UserImportData, dryRun bool) *model.AppError {
|
||||
var fields []logr.Field
|
||||
if data != nil && data.Username != nil {
|
||||
fields = append(fields, mlog.String("user_name", *data.Username))
|
||||
}
|
||||
c.Logger().Info("Validating user", fields...)
|
||||
|
||||
if err := imports.ValidateUserImportData(data); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -302,6 +339,8 @@ func (a *App) importUser(c request.CTX, data *imports.UserImportData, dryRun boo
|
||||
return nil
|
||||
}
|
||||
|
||||
c.Logger().Info("Importing user", fields...)
|
||||
|
||||
// We want to avoid database writes if nothing has changed.
|
||||
hasUserChanged := false
|
||||
hasNotifyPropsChanged := false
|
||||
@@ -1214,6 +1253,8 @@ func (a *App) importAttachment(c request.CTX, data *imports.AttachmentImportData
|
||||
defer zipFile.Close()
|
||||
name = data.Data.Name
|
||||
file = zipFile.(io.Reader)
|
||||
|
||||
c.Logger().Info("Preparing file upload from ZIP", mlog.String("file_name", name), mlog.Uint64("file_size", data.Data.UncompressedSize64))
|
||||
} else {
|
||||
realFile, err := os.Open(*data.Path)
|
||||
if err != nil {
|
||||
@@ -1222,6 +1263,12 @@ func (a *App) importAttachment(c request.CTX, data *imports.AttachmentImportData
|
||||
defer realFile.Close()
|
||||
name = realFile.Name()
|
||||
file = realFile
|
||||
|
||||
fields := []logr.Field{mlog.String("file_name", name)}
|
||||
if info, err := realFile.Stat(); err != nil {
|
||||
fields = append(fields, mlog.Int64("file_size", info.Size()))
|
||||
}
|
||||
c.Logger().Info("Preparing file upload from file system", fields...)
|
||||
}
|
||||
|
||||
timestamp := utils.TimeFromMillis(post.CreateAt)
|
||||
@@ -1241,7 +1288,8 @@ func (a *App) importAttachment(c request.CTX, data *imports.AttachmentImportData
|
||||
if oldFile.Name != path.Base(name) || oldFile.Size != int64(len(fileData)) {
|
||||
continue
|
||||
}
|
||||
// check md5
|
||||
|
||||
// check sha1
|
||||
newHash := sha1.Sum(fileData)
|
||||
oldFileData, err := a.getFileIgnoreCloudLimit(oldFile.Id)
|
||||
if err != nil {
|
||||
@@ -1260,7 +1308,7 @@ func (a *App) importAttachment(c request.CTX, data *imports.AttachmentImportData
|
||||
|
||||
fileInfo, appErr := a.DoUploadFile(c, timestamp, teamID, post.ChannelId, post.UserId, name, fileData)
|
||||
if appErr != nil {
|
||||
mlog.Error("Failed to upload file:", mlog.Err(appErr))
|
||||
mlog.Error("Failed to upload file", mlog.Err(appErr), mlog.String("file_name", name))
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
@@ -1358,6 +1406,8 @@ func (a *App) importMultiplePostLines(c request.CTX, lines []imports.LineImportW
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
c.Logger().Info("Validating post lines", mlog.Int("count", len(lines)), mlog.Int("first_line", lines[0].LineNumber))
|
||||
|
||||
for _, line := range lines {
|
||||
if err := imports.ValidatePostImportData(line.Post, a.MaxPostSize()); err != nil {
|
||||
return line.LineNumber, err
|
||||
@@ -1369,6 +1419,8 @@ func (a *App) importMultiplePostLines(c request.CTX, lines []imports.LineImportW
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
c.Logger().Info("Importing post lines", mlog.Int("count", len(lines)), mlog.Int("first_line", lines[0].LineNumber))
|
||||
|
||||
usernames := []string{}
|
||||
teamNames := make([]string, len(lines))
|
||||
postsData := make([]*imports.PostImportData, len(lines))
|
||||
@@ -1855,7 +1907,13 @@ func (a *App) importMultipleDirectPostLines(c request.CTX, lines []imports.LineI
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (a *App) importEmoji(data *imports.EmojiImportData, dryRun bool) *model.AppError {
|
||||
func (a *App) importEmoji(c request.CTX, data *imports.EmojiImportData, dryRun bool) *model.AppError {
|
||||
var fields []logr.Field
|
||||
if data != nil && data.Name != nil {
|
||||
fields = append(fields, mlog.String("emoji_name", *data.Name))
|
||||
}
|
||||
c.Logger().Info("Validating emoji", fields...)
|
||||
|
||||
aerr := imports.ValidateEmojiImportData(data)
|
||||
if aerr != nil {
|
||||
if aerr.Id == "model.emoji.system_emoji_name.app_error" {
|
||||
@@ -1870,6 +1928,8 @@ func (a *App) importEmoji(data *imports.EmojiImportData, dryRun bool) *model.App
|
||||
return nil
|
||||
}
|
||||
|
||||
c.Logger().Info("Importing emoji", fields...)
|
||||
|
||||
var emoji *model.Emoji
|
||||
|
||||
emoji, err := a.Srv().Store().Emoji().GetByName(context.Background(), *data.Name, true)
|
||||
|
||||
@@ -65,7 +65,7 @@ func TestImportImportScheme(t *testing.T) {
|
||||
Description: ptrStr("description"),
|
||||
}
|
||||
|
||||
err := th.App.importScheme(&data, true)
|
||||
err := th.App.importScheme(th.Context, &data, true)
|
||||
require.NotNil(t, err, "Should have failed to import.")
|
||||
|
||||
_, nErr := th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -74,7 +74,7 @@ func TestImportImportScheme(t *testing.T) {
|
||||
// Try importing a valid scheme in dryRun mode.
|
||||
data.DisplayName = ptrStr("display name")
|
||||
|
||||
err = th.App.importScheme(&data, true)
|
||||
err = th.App.importScheme(th.Context, &data, true)
|
||||
require.Nil(t, err, "Should have succeeded.")
|
||||
|
||||
_, nErr = th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -83,7 +83,7 @@ func TestImportImportScheme(t *testing.T) {
|
||||
// Try importing an invalid scheme.
|
||||
data.DisplayName = nil
|
||||
|
||||
err = th.App.importScheme(&data, false)
|
||||
err = th.App.importScheme(th.Context, &data, false)
|
||||
require.NotNil(t, err, "Should have failed to import.")
|
||||
|
||||
_, nErr = th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -92,7 +92,7 @@ func TestImportImportScheme(t *testing.T) {
|
||||
// Try importing a valid scheme with all params set.
|
||||
data.DisplayName = ptrStr("display name")
|
||||
|
||||
err = th.App.importScheme(&data, false)
|
||||
err = th.App.importScheme(th.Context, &data, false)
|
||||
require.Nil(t, err, "Should have succeeded.")
|
||||
|
||||
scheme, nErr := th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -149,7 +149,7 @@ func TestImportImportScheme(t *testing.T) {
|
||||
data.DisplayName = ptrStr("new display name")
|
||||
data.Description = ptrStr("new description")
|
||||
|
||||
err = th.App.importScheme(&data, false)
|
||||
err = th.App.importScheme(th.Context, &data, false)
|
||||
require.Nil(t, err, "Should have succeeded: %v", err)
|
||||
|
||||
scheme, nErr = th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -205,7 +205,7 @@ func TestImportImportScheme(t *testing.T) {
|
||||
// Try changing the scope of the scheme and reimporting.
|
||||
data.Scope = ptrStr("channel")
|
||||
|
||||
err = th.App.importScheme(&data, false)
|
||||
err = th.App.importScheme(th.Context, &data, false)
|
||||
require.NotNil(t, err, "Should have failed to import.")
|
||||
|
||||
scheme, nErr = th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -252,7 +252,7 @@ func TestImportImportSchemeWithoutGuestRoles(t *testing.T) {
|
||||
Description: ptrStr("description"),
|
||||
}
|
||||
|
||||
err := th.App.importScheme(&data, true)
|
||||
err := th.App.importScheme(th.Context, &data, true)
|
||||
require.NotNil(t, err, "Should have failed to import.")
|
||||
|
||||
_, nErr := th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -261,7 +261,7 @@ func TestImportImportSchemeWithoutGuestRoles(t *testing.T) {
|
||||
// Try importing a valid scheme in dryRun mode.
|
||||
data.DisplayName = ptrStr("display name")
|
||||
|
||||
err = th.App.importScheme(&data, true)
|
||||
err = th.App.importScheme(th.Context, &data, true)
|
||||
require.Nil(t, err, "Should have succeeded.")
|
||||
|
||||
_, nErr = th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -270,7 +270,7 @@ func TestImportImportSchemeWithoutGuestRoles(t *testing.T) {
|
||||
// Try importing an invalid scheme.
|
||||
data.DisplayName = nil
|
||||
|
||||
err = th.App.importScheme(&data, false)
|
||||
err = th.App.importScheme(th.Context, &data, false)
|
||||
require.NotNil(t, err, "Should have failed to import.")
|
||||
|
||||
_, nErr = th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -279,7 +279,7 @@ func TestImportImportSchemeWithoutGuestRoles(t *testing.T) {
|
||||
// Try importing a valid scheme with all params set.
|
||||
data.DisplayName = ptrStr("display name")
|
||||
|
||||
err = th.App.importScheme(&data, false)
|
||||
err = th.App.importScheme(th.Context, &data, false)
|
||||
require.Nil(t, err, "Should have succeeded.")
|
||||
|
||||
scheme, nErr := th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -336,7 +336,7 @@ func TestImportImportSchemeWithoutGuestRoles(t *testing.T) {
|
||||
data.DisplayName = ptrStr("new display name")
|
||||
data.Description = ptrStr("new description")
|
||||
|
||||
err = th.App.importScheme(&data, false)
|
||||
err = th.App.importScheme(th.Context, &data, false)
|
||||
require.Nil(t, err, "Should have succeeded: %v", err)
|
||||
|
||||
scheme, nErr = th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -392,7 +392,7 @@ func TestImportImportSchemeWithoutGuestRoles(t *testing.T) {
|
||||
// Try changing the scope of the scheme and reimporting.
|
||||
data.Scope = ptrStr("channel")
|
||||
|
||||
err = th.App.importScheme(&data, false)
|
||||
err = th.App.importScheme(th.Context, &data, false)
|
||||
require.NotNil(t, err, "Should have failed to import.")
|
||||
|
||||
scheme, nErr = th.App.Srv().Store().Scheme().GetByName(*data.Name)
|
||||
@@ -414,7 +414,7 @@ func TestImportImportRole(t *testing.T) {
|
||||
Name: &rid1,
|
||||
}
|
||||
|
||||
err := th.App.importRole(&data, true, false)
|
||||
err := th.App.importRole(th.Context, &data, true, false)
|
||||
require.NotNil(t, err, "Should have failed to import.")
|
||||
|
||||
_, nErr := th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
|
||||
@@ -423,7 +423,7 @@ func TestImportImportRole(t *testing.T) {
|
||||
// Try importing the valid role in dryRun mode.
|
||||
data.DisplayName = ptrStr("display name")
|
||||
|
||||
err = th.App.importRole(&data, true, false)
|
||||
err = th.App.importRole(th.Context, &data, true, false)
|
||||
require.Nil(t, err, "Should have succeeded.")
|
||||
|
||||
_, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
|
||||
@@ -432,7 +432,7 @@ func TestImportImportRole(t *testing.T) {
|
||||
// Try importing an invalid role.
|
||||
data.DisplayName = nil
|
||||
|
||||
err = th.App.importRole(&data, false, false)
|
||||
err = th.App.importRole(th.Context, &data, false, false)
|
||||
require.NotNil(t, err, "Should have failed to import.")
|
||||
|
||||
_, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
|
||||
@@ -443,7 +443,7 @@ func TestImportImportRole(t *testing.T) {
|
||||
data.Description = ptrStr("description")
|
||||
data.Permissions = &[]string{"invite_user", "add_user_to_team"}
|
||||
|
||||
err = th.App.importRole(&data, false, false)
|
||||
err = th.App.importRole(th.Context, &data, false, false)
|
||||
require.Nil(t, err, "Should have succeeded.")
|
||||
|
||||
role, nErr := th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
|
||||
@@ -461,7 +461,7 @@ func TestImportImportRole(t *testing.T) {
|
||||
data.Description = ptrStr("description")
|
||||
data.Permissions = &[]string{"use_slash_commands"}
|
||||
|
||||
err = th.App.importRole(&data, false, true)
|
||||
err = th.App.importRole(th.Context, &data, false, true)
|
||||
require.Nil(t, err, "Should have succeeded. %v", err)
|
||||
|
||||
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
|
||||
@@ -480,7 +480,7 @@ func TestImportImportRole(t *testing.T) {
|
||||
DisplayName: ptrStr("new display name again"),
|
||||
}
|
||||
|
||||
err = th.App.importRole(&data2, false, false)
|
||||
err = th.App.importRole(th.Context, &data2, false, false)
|
||||
require.Nil(t, err, "Should have succeeded.")
|
||||
|
||||
role, nErr = th.App.Srv().Store().Role().GetByName(context.Background(), rid1)
|
||||
@@ -1384,7 +1384,7 @@ func TestImportImportUser(t *testing.T) {
|
||||
Description: ptrStr("description"),
|
||||
}
|
||||
|
||||
appErr = th.App.importScheme(teamSchemeData, false)
|
||||
appErr = th.App.importScheme(th.Context, teamSchemeData, false)
|
||||
assert.Nil(t, appErr)
|
||||
|
||||
teamScheme, nErr := th.App.Srv().Store().Scheme().GetByName(*teamSchemeData.Name)
|
||||
@@ -4151,7 +4151,7 @@ func TestImportImportEmoji(t *testing.T) {
|
||||
testImage := filepath.Join(testsDir, "test.png")
|
||||
|
||||
data := imports.EmojiImportData{Name: ptrStr(model.NewId())}
|
||||
appErr := th.App.importEmoji(&data, true)
|
||||
appErr := th.App.importEmoji(th.Context, &data, true)
|
||||
assert.NotNil(t, appErr, "Invalid emoji should have failed dry run")
|
||||
|
||||
emoji, nErr := th.App.Srv().Store().Emoji().GetByName(context.Background(), *data.Name, true)
|
||||
@@ -4159,35 +4159,35 @@ func TestImportImportEmoji(t *testing.T) {
|
||||
assert.Error(t, nErr)
|
||||
|
||||
data.Image = ptrStr(testImage)
|
||||
appErr = th.App.importEmoji(&data, true)
|
||||
appErr = th.App.importEmoji(th.Context, &data, true)
|
||||
assert.Nil(t, appErr, "Valid emoji should have passed dry run")
|
||||
|
||||
data = imports.EmojiImportData{Name: ptrStr(model.NewId())}
|
||||
appErr = th.App.importEmoji(&data, false)
|
||||
appErr = th.App.importEmoji(th.Context, &data, false)
|
||||
assert.NotNil(t, appErr, "Invalid emoji should have failed apply mode")
|
||||
|
||||
data.Image = ptrStr("non-existent-file")
|
||||
appErr = th.App.importEmoji(&data, false)
|
||||
appErr = th.App.importEmoji(th.Context, &data, false)
|
||||
assert.NotNil(t, appErr, "Emoji with bad image file should have failed apply mode")
|
||||
|
||||
data.Image = ptrStr(testImage)
|
||||
appErr = th.App.importEmoji(&data, false)
|
||||
appErr = th.App.importEmoji(th.Context, &data, false)
|
||||
assert.Nil(t, appErr, "Valid emoji should have succeeded apply mode")
|
||||
|
||||
emoji, nErr = th.App.Srv().Store().Emoji().GetByName(context.Background(), *data.Name, true)
|
||||
assert.NotNil(t, emoji, "Emoji should have been imported")
|
||||
assert.NoError(t, nErr, "Emoji should have been imported without any error")
|
||||
|
||||
appErr = th.App.importEmoji(&data, false)
|
||||
appErr = th.App.importEmoji(th.Context, &data, false)
|
||||
assert.Nil(t, appErr, "Second run should have succeeded apply mode")
|
||||
|
||||
data = imports.EmojiImportData{Name: ptrStr("smiley"), Image: ptrStr(testImage)}
|
||||
appErr = th.App.importEmoji(&data, false)
|
||||
appErr = th.App.importEmoji(th.Context, &data, false)
|
||||
assert.Nil(t, appErr, "System emoji should not fail")
|
||||
|
||||
largeImage := filepath.Join(testsDir, "large_image_file.jpg")
|
||||
data = imports.EmojiImportData{Name: ptrStr(model.NewId()), Image: ptrStr(largeImage)}
|
||||
appErr = th.App.importEmoji(&data, false)
|
||||
appErr = th.App.importEmoji(th.Context, &data, false)
|
||||
require.NotNil(t, appErr)
|
||||
require.ErrorIs(t, appErr.Unwrap(), utils.SizeLimitExceeded)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app/imports"
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
"github.com/mattermost/mattermost-server/v6/utils/fileutils"
|
||||
)
|
||||
@@ -238,7 +240,7 @@ func TestImportBulkImport(t *testing.T) {
|
||||
{"type": "user", "user": {"username": "` + username + `", "email": "` + username + `@example.com", "teams": [{"name": "` + teamName + `","theme": "` + teamTheme1 + `", "channels": [{"name": "` + channelName + `"}]}]}}
|
||||
{"type": "post", "post": {"team": "` + teamName + `", "channel": "` + channelName + `", "user": "` + username + `", "message": "Hello World", "create_at": 123456789012, "attachments":[{"path": "` + testImage + `"}], "props":{"attachments":[{"id":0,"fallback":"[February 4th, 2020 2:46 PM] author: fallback","color":"D0D0D0","pretext":"","author_name":"author","author_link":"","title":"","title_link":"","text":"this post has props","fields":null,"image_url":"","thumb_url":"","footer":"Posted in #general","footer_icon":"","ts":"1580823992.000100"}]}}}
|
||||
{"type": "direct_channel", "direct_channel": {"members": ["` + username + `", "` + username + `"]}}
|
||||
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username + `"], "user": "` + username + `", "message": "Hello Direct Channel to myself", "create_at": 123456789014, "props":{"attachments":[{"id":0,"fallback":"[February 4th, 2020 2:46 PM] author: fallback","color":"D0D0D0","pretext":"","author_name":"author","author_link":"","title":"","title_link":"","text":"this post has props","fields":null,"image_url":"","thumb_url":"","footer":"Posted in #general","footer_icon":"","ts":"1580823992.000100"}]}}}}`
|
||||
{"type": "direct_post", "direct_post": {"channel_members": ["` + username + `", "` + username + `"], "user": "` + username + `", "message": "Hello Direct Channel to myself", "create_at": 123456789014, "props":{"attachments":[{"id":0,"fallback":"[February 4th, 2020 2:46 PM] author: fallback","color":"D0D0D0","pretext":"","author_name":"author","author_link":"","title":"","title_link":"","text":"this post has props","fields":null,"image_url":"","thumb_url":"","footer":"Posted in #general","footer_icon":"","ts":"1580823992.000100"}]}}}`
|
||||
|
||||
err, line := th.App.BulkImport(th.Context, strings.NewReader(data6), nil, false, 2)
|
||||
require.Nil(t, err, "BulkImport should have succeeded")
|
||||
@@ -285,6 +287,9 @@ func AssertFileIdsInPost(files []*model.FileInfo, th *TestHelper, t *testing.T)
|
||||
}
|
||||
|
||||
func TestProcessAttachments(t *testing.T) {
|
||||
logger, _ := mlog.NewLogger()
|
||||
c := request.EmptyContext(logger)
|
||||
|
||||
genAttachments := func() *[]imports.AttachmentImportData {
|
||||
return &[]imports.AttachmentImportData{
|
||||
{
|
||||
@@ -333,10 +338,11 @@ func TestProcessAttachments(t *testing.T) {
|
||||
Path: model.NewString("somedir/file.jpg"),
|
||||
},
|
||||
}
|
||||
err := processAttachments(&line, "", nil)
|
||||
|
||||
err := processAttachments(c, &line, "", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, line.Post.Attachments)
|
||||
err = processAttachments(&line2, "", nil)
|
||||
err = processAttachments(c, &line2, "", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, line2.DirectPost.Attachments)
|
||||
})
|
||||
@@ -352,27 +358,27 @@ func TestProcessAttachments(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("post attachments", func(t *testing.T) {
|
||||
err := processAttachments(&line, "/tmp", nil)
|
||||
err := processAttachments(c, &line, "/tmp", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, line.Post.Attachments)
|
||||
})
|
||||
|
||||
t.Run("direct post attachments", func(t *testing.T) {
|
||||
err := processAttachments(&line2, "/tmp", nil)
|
||||
err := processAttachments(c, &line2, "/tmp", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, line2.DirectPost.Attachments)
|
||||
})
|
||||
|
||||
t.Run("profile image", func(t *testing.T) {
|
||||
expected := "/tmp/profile.jpg"
|
||||
err := processAttachments(&userLine, "/tmp", nil)
|
||||
err := processAttachments(c, &userLine, "/tmp", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, *userLine.User.ProfileImage)
|
||||
})
|
||||
|
||||
t.Run("emoji", func(t *testing.T) {
|
||||
expected := "/tmp/emoji.png"
|
||||
err := processAttachments(&emojiLine, "/tmp", nil)
|
||||
err := processAttachments(c, &emojiLine, "/tmp", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, *emojiLine.Emoji.Image)
|
||||
})
|
||||
@@ -383,11 +389,11 @@ func TestProcessAttachments(t *testing.T) {
|
||||
filesMap := map[string]*zip.File{
|
||||
"/tmp/file.jpg": nil,
|
||||
}
|
||||
err := processAttachments(&line, "", filesMap)
|
||||
err := processAttachments(c, &line, "", filesMap)
|
||||
require.Error(t, err)
|
||||
|
||||
filesMap["/tmp/somedir/file.jpg"] = nil
|
||||
err = processAttachments(&line, "", filesMap)
|
||||
err = processAttachments(c, &line, "", filesMap)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
@@ -395,11 +401,11 @@ func TestProcessAttachments(t *testing.T) {
|
||||
filesMap := map[string]*zip.File{
|
||||
"/tmp/file.jpg": nil,
|
||||
}
|
||||
err := processAttachments(&line2, "", filesMap)
|
||||
err := processAttachments(c, &line2, "", filesMap)
|
||||
require.Error(t, err)
|
||||
|
||||
filesMap["/tmp/somedir/file.jpg"] = nil
|
||||
err = processAttachments(&line2, "", filesMap)
|
||||
err = processAttachments(c, &line2, "", filesMap)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
@@ -407,11 +413,11 @@ func TestProcessAttachments(t *testing.T) {
|
||||
filesMap := map[string]*zip.File{
|
||||
"/tmp/file.jpg": nil,
|
||||
}
|
||||
err := processAttachments(&userLine, "", filesMap)
|
||||
err := processAttachments(c, &userLine, "", filesMap)
|
||||
require.Error(t, err)
|
||||
|
||||
filesMap["/tmp/profile.jpg"] = nil
|
||||
err = processAttachments(&userLine, "", filesMap)
|
||||
err = processAttachments(c, &userLine, "", filesMap)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
@@ -419,11 +425,11 @@ func TestProcessAttachments(t *testing.T) {
|
||||
filesMap := map[string]*zip.File{
|
||||
"/tmp/file.jpg": nil,
|
||||
}
|
||||
err := processAttachments(&emojiLine, "", filesMap)
|
||||
err := processAttachments(c, &emojiLine, "", filesMap)
|
||||
require.Error(t, err)
|
||||
|
||||
filesMap["/tmp/emoji.png"] = nil
|
||||
err = processAttachments(&emojiLine, "", filesMap)
|
||||
err = processAttachments(c, &emojiLine, "", filesMap)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -32,8 +32,8 @@ type licenseWrapper struct {
|
||||
srv *Server
|
||||
}
|
||||
|
||||
func (w *licenseWrapper) Name() ServiceKey {
|
||||
return LicenseKey
|
||||
func (w *licenseWrapper) Name() product.ServiceKey {
|
||||
return product.LicenseKey
|
||||
}
|
||||
|
||||
func (w *licenseWrapper) GetLicense() *model.License {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/app/platform"
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/product"
|
||||
fmocks "github.com/mattermost/mattermost-server/v6/shared/filestore/mocks"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/i18n"
|
||||
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
|
||||
@@ -1440,7 +1441,7 @@ func TestPushNotificationRace(t *testing.T) {
|
||||
Return(&model.Preference{Value: "test"}, nil)
|
||||
mockStore.On("Preference").Return(&mockPreferenceStore)
|
||||
s := &Server{
|
||||
products: make(map[string]Product),
|
||||
products: make(map[string]product.Product),
|
||||
Router: mux.NewRouter(),
|
||||
}
|
||||
var err error
|
||||
@@ -1449,12 +1450,13 @@ func TestPushNotificationRace(t *testing.T) {
|
||||
}, platform.SetFileStore(&fmocks.FileBackend{}))
|
||||
s.SetStore(mockStore)
|
||||
require.NoError(t, err)
|
||||
serviceMap := map[ServiceKey]any{
|
||||
ConfigKey: s.platform,
|
||||
LicenseKey: &licenseWrapper{s},
|
||||
FilestoreKey: s.FileBackend(),
|
||||
serviceMap := map[product.ServiceKey]any{
|
||||
ServerKey: s,
|
||||
product.ConfigKey: s.platform,
|
||||
product.LicenseKey: &licenseWrapper{s},
|
||||
product.FilestoreKey: s.FileBackend(),
|
||||
}
|
||||
ch, err := NewChannels(s, serviceMap)
|
||||
ch, err := NewChannels(serviceMap)
|
||||
require.NoError(t, err)
|
||||
s.products["channels"] = ch
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/plugin"
|
||||
"github.com/mattermost/mattermost-server/v6/product"
|
||||
"github.com/mattermost/mattermost-server/v6/services/httpservice"
|
||||
"github.com/mattermost/mattermost-server/v6/services/imageproxy"
|
||||
"github.com/mattermost/mattermost-server/v6/services/remotecluster"
|
||||
@@ -6437,6 +6438,28 @@ func (a *OpenTracingAppLayer) GetGroupMemberUsersPage(groupID string, page int,
|
||||
return resultVar0, resultVar1, resultVar2
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetGroupMemberUsersSortedPage(groupID string, page int, perPage int, viewRestrictions *model.ViewUsersRestrictions, teammateNameDisplay string) ([]*model.User, int, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetGroupMemberUsersSortedPage")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1, resultVar2 := a.app.GetGroupMemberUsersSortedPage(groupID, page, perPage, viewRestrictions, teammateNameDisplay)
|
||||
|
||||
if resultVar2 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar2))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1, resultVar2
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetGroupSyncable(groupID string, syncableID string, syncableType model.GroupSyncableType) (*model.GroupSyncable, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetGroupSyncable")
|
||||
@@ -10305,7 +10328,7 @@ func (a *OpenTracingAppLayer) GetTotalUsersStats(viewRestrictions *model.ViewUse
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetUploadSession(uploadId string) (*model.UploadSession, *model.AppError) {
|
||||
func (a *OpenTracingAppLayer) GetUploadSession(c request.CTX, uploadId string) (*model.UploadSession, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetUploadSession")
|
||||
|
||||
@@ -10317,7 +10340,7 @@ func (a *OpenTracingAppLayer) GetUploadSession(uploadId string) (*model.UploadSe
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetUploadSession(uploadId)
|
||||
resultVar0, resultVar1 := a.app.GetUploadSession(c, uploadId)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
@@ -11559,6 +11582,23 @@ func (a *OpenTracingAppLayer) HasSharedChannel(channelID string) (bool, error) {
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) HooksManager() *product.HooksManager {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.HooksManager")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.HooksManager()
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) HubRegister(webConn *platform.WebConn) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.HubRegister")
|
||||
@@ -12632,6 +12672,21 @@ func (a *OpenTracingAppLayer) NotifyAndSetWarnMetricAck(warnMetricId string, sen
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) NotifySelfHostedSignupProgress(progress string, userId string) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.NotifySelfHostedSignupProgress")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
a.app.NotifySelfHostedSignupProgress(progress, userId)
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) NotifySessionsExpired() error {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.NotifySessionsExpired")
|
||||
@@ -18135,7 +18190,7 @@ func (a *OpenTracingAppLayer) UpdateWebConnUserActivity(session model.Session, a
|
||||
a.app.UpdateWebConnUserActivity(session, activityAt)
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) UploadData(c *request.Context, us *model.UploadSession, rd io.Reader) (*model.FileInfo, *model.AppError) {
|
||||
func (a *OpenTracingAppLayer) UploadData(c request.CTX, us *model.UploadSession, rd io.Reader) (*model.FileInfo, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UploadData")
|
||||
|
||||
@@ -18499,6 +18554,28 @@ func (a *OpenTracingAppLayer) WriteFile(fr io.Reader, path string) (int64, *mode
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) WriteFileContext(ctx context.Context, fr io.Reader, path string) (int64, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.WriteFileContext")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.WriteFileContext(ctx, fr, path)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func NewOpenTracingAppLayer(childApp app.AppIface, ctx context.Context) *OpenTracingAppLayer {
|
||||
newApp := OpenTracingAppLayer{
|
||||
app: childApp,
|
||||
|
||||
@@ -994,6 +994,30 @@ func (a *App) getAddCustomUserGroupsPermissions() (permissionsMap, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (a *App) getAddCustomUserGroupsPermissionRestore() (permissionsMap, error) {
|
||||
t := []permissionTransformation{}
|
||||
|
||||
customGroupPermissions := []string{
|
||||
model.PermissionRestoreCustomGroup.Id,
|
||||
}
|
||||
|
||||
t = append(t, permissionTransformation{
|
||||
On: isExactRole(model.SystemUserRoleId),
|
||||
Add: customGroupPermissions,
|
||||
})
|
||||
|
||||
t = append(t, permissionTransformation{
|
||||
On: isExactRole(model.SystemAdminRoleId),
|
||||
Add: customGroupPermissions,
|
||||
})
|
||||
|
||||
t = append(t, permissionTransformation{
|
||||
On: isExactRole(model.SystemCustomGroupAdminRoleId),
|
||||
Add: customGroupPermissions,
|
||||
})
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (a *App) getAddPlaybooksPermissions() (permissionsMap, error) {
|
||||
transformations := []permissionTransformation{}
|
||||
|
||||
@@ -1110,6 +1134,7 @@ func (s *Server) doPermissionsMigrations() error {
|
||||
{Key: model.MigrationKeyAddCustomUserGroupsPermissions, Migration: a.getAddCustomUserGroupsPermissions},
|
||||
{Key: model.MigrationKeyAddPlayboosksManageRolesPermissions, Migration: a.getPlaybooksPermissionsAddManageRoles},
|
||||
{Key: model.MigrationKeyAddProductsBoardsPermissions, Migration: a.getProductsBoardsPermissions},
|
||||
{Key: model.MigrationKeyAddCustomUserGroupsPermissionRestore, Migration: a.getAddCustomUserGroupsPermissionRestore},
|
||||
}
|
||||
|
||||
roles, err := s.Store().Role().GetAll()
|
||||
|
||||
@@ -1255,7 +1255,9 @@ func (api *PluginAPI) UploadData(us *model.UploadSession, rd io.Reader) (*model.
|
||||
}
|
||||
|
||||
func (api *PluginAPI) GetUploadSession(uploadID string) (*model.UploadSession, error) {
|
||||
fi, err := api.app.GetUploadSession(uploadID)
|
||||
// We want to fetch from master DB to avoid a potential read-after-write on the plugin side.
|
||||
api.ctx.SetContext(WithMaster(api.ctx.Context()))
|
||||
fi, err := api.app.GetUploadSession(api.ctx, uploadID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/plugin"
|
||||
@@ -44,7 +45,10 @@ func (d *DriverImpl) Conn(isMaster bool) (string, error) {
|
||||
if !isMaster {
|
||||
dbFunc = d.s.Platform().Store.GetInternalReplicaDB
|
||||
}
|
||||
conn, err := dbFunc().Conn(context.Background())
|
||||
timeout := time.Duration(*d.s.Config().SqlSettings.QueryTimeout) * time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
conn, err := dbFunc().Conn(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
21
app/plugin_db_driver_test.go
Обычный файл
21
app/plugin_db_driver_test.go
Обычный файл
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConnCreateTimeout(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
*th.App.Config().SqlSettings.QueryTimeout = 0
|
||||
|
||||
d := NewDriverImpl(th.Server)
|
||||
_, err := d.Conn(true)
|
||||
require.Error(t, err)
|
||||
}
|
||||
@@ -6,31 +6,20 @@ package app
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/product"
|
||||
)
|
||||
|
||||
type Product interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
type ProductManifest struct {
|
||||
Initializer func(*Server, map[ServiceKey]any) (Product, error)
|
||||
Dependencies map[ServiceKey]struct{}
|
||||
}
|
||||
|
||||
var products = make(map[string]ProductManifest)
|
||||
|
||||
func RegisterProduct(name string, m ProductManifest) {
|
||||
products[name] = m
|
||||
}
|
||||
|
||||
func (s *Server) initializeProducts(
|
||||
productMap map[string]ProductManifest,
|
||||
serviceMap map[ServiceKey]any,
|
||||
productMap map[string]product.Manifest,
|
||||
serviceMap map[product.ServiceKey]any,
|
||||
) error {
|
||||
// create a product map to consume
|
||||
pmap := make(map[string]struct{})
|
||||
for name := range productMap {
|
||||
if !s.shouldStart(name) {
|
||||
continue
|
||||
}
|
||||
pmap[name] = struct{}{}
|
||||
}
|
||||
|
||||
@@ -57,7 +46,7 @@ func (s *Server) initializeProducts(
|
||||
|
||||
// some products can register themselves/their services
|
||||
initializer := manifest.Initializer
|
||||
prod, err := initializer(s, serviceMap)
|
||||
prod, err := initializer(serviceMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing product %q: %w", product, err)
|
||||
}
|
||||
@@ -78,3 +67,11 @@ func (s *Server) initializeProducts(
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) shouldStart(product string) bool {
|
||||
if !s.Config().FeatureFlags.BoardsProduct && product == "boards" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ package app
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app/platform"
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/product"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -16,7 +19,7 @@ const (
|
||||
|
||||
type productA struct{}
|
||||
|
||||
func newProductA(s *Server, m map[ServiceKey]any) (Product, error) {
|
||||
func newProductA(m map[product.ServiceKey]any) (product.Product, error) {
|
||||
m[testSrvKey1] = nil
|
||||
return &productA{}, nil
|
||||
}
|
||||
@@ -26,7 +29,7 @@ func (p *productA) Stop() error { return nil }
|
||||
|
||||
type productB struct{}
|
||||
|
||||
func newProductB(s *Server, m map[ServiceKey]any) (Product, error) {
|
||||
func newProductB(m map[product.ServiceKey]any) (product.Product, error) {
|
||||
m[testSrvKey2] = nil
|
||||
return &productB{}, nil
|
||||
}
|
||||
@@ -35,74 +38,80 @@ func (p *productB) Start() error { return nil }
|
||||
func (p *productB) Stop() error { return nil }
|
||||
|
||||
func TestInitializeProducts(t *testing.T) {
|
||||
ps, err := platform.New(platform.ServiceConfig{ConfigStore: config.NewTestMemoryStore()})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("2 products and no circular dependency", func(t *testing.T) {
|
||||
serviceMap := map[ServiceKey]any{
|
||||
ConfigKey: nil,
|
||||
LicenseKey: nil,
|
||||
FilestoreKey: nil,
|
||||
ClusterKey: nil,
|
||||
serviceMap := map[product.ServiceKey]any{
|
||||
product.ConfigKey: nil,
|
||||
product.LicenseKey: nil,
|
||||
product.FilestoreKey: nil,
|
||||
product.ClusterKey: nil,
|
||||
}
|
||||
|
||||
products := map[string]ProductManifest{
|
||||
products := map[string]product.Manifest{
|
||||
"productA": {
|
||||
Initializer: newProductA,
|
||||
Dependencies: map[ServiceKey]struct{}{
|
||||
ConfigKey: {},
|
||||
LicenseKey: {},
|
||||
FilestoreKey: {},
|
||||
ClusterKey: {},
|
||||
Dependencies: map[product.ServiceKey]struct{}{
|
||||
product.ConfigKey: {},
|
||||
product.LicenseKey: {},
|
||||
product.FilestoreKey: {},
|
||||
product.ClusterKey: {},
|
||||
},
|
||||
},
|
||||
"productB": {
|
||||
Initializer: newProductB,
|
||||
Dependencies: map[ServiceKey]struct{}{
|
||||
ConfigKey: {},
|
||||
testSrvKey1: {},
|
||||
FilestoreKey: {},
|
||||
ClusterKey: {},
|
||||
Dependencies: map[product.ServiceKey]struct{}{
|
||||
product.ConfigKey: {},
|
||||
testSrvKey1: {},
|
||||
product.FilestoreKey: {},
|
||||
product.ClusterKey: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
products: make(map[string]Product),
|
||||
products: make(map[string]product.Product),
|
||||
platform: ps,
|
||||
}
|
||||
|
||||
err := server.initializeProducts(products, serviceMap)
|
||||
err = server.initializeProducts(products, serviceMap)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, server.products, 2)
|
||||
})
|
||||
|
||||
t.Run("2 products and circular dependency", func(t *testing.T) {
|
||||
serviceMap := map[ServiceKey]any{
|
||||
ConfigKey: nil,
|
||||
LicenseKey: nil,
|
||||
FilestoreKey: nil,
|
||||
ClusterKey: nil,
|
||||
serviceMap := map[product.ServiceKey]any{
|
||||
product.ConfigKey: nil,
|
||||
product.LicenseKey: nil,
|
||||
product.FilestoreKey: nil,
|
||||
product.ClusterKey: nil,
|
||||
}
|
||||
|
||||
products := map[string]ProductManifest{
|
||||
products := map[string]product.Manifest{
|
||||
"productA": {
|
||||
Initializer: newProductA,
|
||||
Dependencies: map[ServiceKey]struct{}{
|
||||
ConfigKey: {},
|
||||
LicenseKey: {},
|
||||
FilestoreKey: {},
|
||||
ClusterKey: {},
|
||||
testSrvKey2: {},
|
||||
Dependencies: map[product.ServiceKey]struct{}{
|
||||
product.ConfigKey: {},
|
||||
product.LicenseKey: {},
|
||||
product.FilestoreKey: {},
|
||||
product.ClusterKey: {},
|
||||
testSrvKey2: {},
|
||||
},
|
||||
},
|
||||
"productB": {
|
||||
Initializer: newProductB,
|
||||
Dependencies: map[ServiceKey]struct{}{
|
||||
ConfigKey: {},
|
||||
testSrvKey1: {},
|
||||
FilestoreKey: {},
|
||||
ClusterKey: {},
|
||||
Dependencies: map[product.ServiceKey]struct{}{
|
||||
product.ConfigKey: {},
|
||||
testSrvKey1: {},
|
||||
product.FilestoreKey: {},
|
||||
product.ClusterKey: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
server := &Server{
|
||||
products: make(map[string]Product),
|
||||
products: make(map[string]product.Product),
|
||||
platform: ps,
|
||||
}
|
||||
|
||||
err := server.initializeProducts(products, serviceMap)
|
||||
@@ -110,19 +119,19 @@ func TestInitializeProducts(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("2 products and one w/o any dependency", func(t *testing.T) {
|
||||
serviceMap := map[ServiceKey]any{
|
||||
ConfigKey: nil,
|
||||
LicenseKey: nil,
|
||||
FilestoreKey: nil,
|
||||
ClusterKey: nil,
|
||||
serviceMap := map[product.ServiceKey]any{
|
||||
product.ConfigKey: nil,
|
||||
product.LicenseKey: nil,
|
||||
product.FilestoreKey: nil,
|
||||
product.ClusterKey: nil,
|
||||
}
|
||||
|
||||
products := map[string]ProductManifest{
|
||||
products := map[string]product.Manifest{
|
||||
"productA": {
|
||||
Initializer: newProductA,
|
||||
Dependencies: map[ServiceKey]struct{}{
|
||||
ConfigKey: {},
|
||||
LicenseKey: {},
|
||||
Dependencies: map[product.ServiceKey]struct{}{
|
||||
product.ConfigKey: {},
|
||||
product.LicenseKey: {},
|
||||
},
|
||||
},
|
||||
"productB": {
|
||||
@@ -130,11 +139,32 @@ func TestInitializeProducts(t *testing.T) {
|
||||
},
|
||||
}
|
||||
server := &Server{
|
||||
products: make(map[string]Product),
|
||||
products: make(map[string]product.Product),
|
||||
platform: ps,
|
||||
}
|
||||
|
||||
err := server.initializeProducts(products, serviceMap)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, server.products, 2)
|
||||
})
|
||||
|
||||
t.Run("boards product to be blocked", func(t *testing.T) {
|
||||
products := map[string]product.Manifest{
|
||||
"productA": {
|
||||
Initializer: newProductA,
|
||||
},
|
||||
"boards": {
|
||||
Initializer: newProductB,
|
||||
},
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
products: make(map[string]product.Product),
|
||||
platform: ps,
|
||||
}
|
||||
|
||||
err := server.initializeProducts(products, map[product.ServiceKey]any{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, server.products, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -75,30 +75,6 @@ import (
|
||||
// declaring this as var to allow overriding in tests
|
||||
var SentryDSN = "placeholder_sentry_dsn"
|
||||
|
||||
type ServiceKey string
|
||||
|
||||
const (
|
||||
ChannelKey ServiceKey = "channel"
|
||||
ConfigKey ServiceKey = "config"
|
||||
LicenseKey ServiceKey = "license"
|
||||
FilestoreKey ServiceKey = "filestore"
|
||||
FileInfoStoreKey ServiceKey = "fileinfostore"
|
||||
ClusterKey ServiceKey = "cluster"
|
||||
CloudKey ServiceKey = "cloud"
|
||||
PostKey ServiceKey = "post"
|
||||
TeamKey ServiceKey = "team"
|
||||
UserKey ServiceKey = "user"
|
||||
PermissionsKey ServiceKey = "permissions"
|
||||
RouterKey ServiceKey = "router"
|
||||
BotKey ServiceKey = "bot"
|
||||
LogKey ServiceKey = "log"
|
||||
HooksKey ServiceKey = "hooks"
|
||||
KVStoreKey ServiceKey = "kvstore"
|
||||
StoreKey ServiceKey = "storekey"
|
||||
SystemKey ServiceKey = "systemkey"
|
||||
PreferencesKey ServiceKey = "preferenceskey"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
// RootRouter is the starting point for all HTTP requests to the server.
|
||||
RootRouter *mux.Router
|
||||
@@ -160,7 +136,7 @@ type Server struct {
|
||||
|
||||
tracer *tracing.Tracer
|
||||
|
||||
products map[string]Product
|
||||
products map[string]product.Product
|
||||
|
||||
hooksManager *product.HooksManager
|
||||
}
|
||||
@@ -187,7 +163,7 @@ func NewServer(options ...Option) (*Server, error) {
|
||||
RootRouter: rootRouter,
|
||||
LocalRouter: localRouter,
|
||||
timezones: timezones.New(),
|
||||
products: make(map[string]Product),
|
||||
products: make(map[string]product.Product),
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
@@ -263,24 +239,25 @@ func NewServer(options ...Option) (*Server, error) {
|
||||
var _ product.UserService = (*App)(nil)
|
||||
|
||||
app := New(ServerConnector(s.Channels()))
|
||||
serviceMap := map[ServiceKey]any{
|
||||
ChannelKey: &channelsWrapper{srv: s, app: app},
|
||||
ConfigKey: s.platform,
|
||||
LicenseKey: s.licenseWrapper,
|
||||
FilestoreKey: s.platform.FileBackend(),
|
||||
FileInfoStoreKey: &fileInfoWrapper{srv: s},
|
||||
ClusterKey: s.platform,
|
||||
UserKey: New(ServerConnector(s.Channels())),
|
||||
LogKey: s.platform.Log(),
|
||||
CloudKey: &cloudWrapper{cloud: s.Cloud},
|
||||
KVStoreKey: s.platform,
|
||||
StoreKey: store.NewStoreServiceAdapter(s.Store()),
|
||||
SystemKey: &systemServiceAdapter{server: s},
|
||||
serviceMap := map[product.ServiceKey]any{
|
||||
ServerKey: s,
|
||||
product.ChannelKey: &channelsWrapper{srv: s, app: app},
|
||||
product.ConfigKey: s.platform,
|
||||
product.LicenseKey: s.licenseWrapper,
|
||||
product.FilestoreKey: s.platform.FileBackend(),
|
||||
product.FileInfoStoreKey: &fileInfoWrapper{srv: s},
|
||||
product.ClusterKey: s.platform,
|
||||
product.UserKey: New(ServerConnector(s.Channels())),
|
||||
product.LogKey: s.platform.Log(),
|
||||
product.CloudKey: &cloudWrapper{cloud: s.Cloud},
|
||||
product.KVStoreKey: s.platform,
|
||||
product.StoreKey: store.NewStoreServiceAdapter(s.Store()),
|
||||
product.SystemKey: &systemServiceAdapter{server: s},
|
||||
}
|
||||
|
||||
// Step 4: Initialize products.
|
||||
// Depends on s.httpService.
|
||||
err = s.initializeProducts(products, serviceMap)
|
||||
err = s.initializeProducts(product.GetProducts(), serviceMap)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to initialize products")
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func (a *App) genFileInfoFromReader(name string, file io.ReadSeeker, size int64)
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (a *App) runPluginsHook(c *request.Context, info *model.FileInfo, file io.Reader) *model.AppError {
|
||||
func (a *App) runPluginsHook(c request.CTX, info *model.FileInfo, file io.Reader) *model.AppError {
|
||||
filePath := info.Path
|
||||
// using a pipe to avoid loading the whole file content in memory.
|
||||
r, w := io.Pipe()
|
||||
@@ -93,6 +93,7 @@ func (a *App) runPluginsHook(c *request.Context, info *model.FileInfo, file io.R
|
||||
if fileErr := a.RemoveFile(tmpPath); fileErr != nil {
|
||||
mlog.Warn("Failed to remove file", mlog.Err(fileErr))
|
||||
}
|
||||
r.CloseWithError(err) // always returns nil
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -154,8 +155,8 @@ func (a *App) CreateUploadSession(c request.CTX, us *model.UploadSession) (*mode
|
||||
return us, nil
|
||||
}
|
||||
|
||||
func (a *App) GetUploadSession(uploadId string) (*model.UploadSession, *model.AppError) {
|
||||
us, err := a.Srv().Store().UploadSession().Get(uploadId)
|
||||
func (a *App) GetUploadSession(c request.CTX, uploadId string) (*model.UploadSession, *model.AppError) {
|
||||
us, err := a.Srv().Store().UploadSession().Get(c.Context(), uploadId)
|
||||
if err != nil {
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
@@ -179,7 +180,7 @@ func (a *App) GetUploadSessionsForUser(userID string) ([]*model.UploadSession, *
|
||||
return uss, nil
|
||||
}
|
||||
|
||||
func (a *App) UploadData(c *request.Context, us *model.UploadSession, rd io.Reader) (*model.FileInfo, *model.AppError) {
|
||||
func (a *App) UploadData(c request.CTX, us *model.UploadSession, rd io.Reader) (*model.FileInfo, *model.AppError) {
|
||||
// prevent more than one caller to upload data at the same time for a given upload session.
|
||||
// This is to avoid possible inconsistencies.
|
||||
a.ch.uploadLockMapMut.Lock()
|
||||
@@ -202,7 +203,8 @@ func (a *App) UploadData(c *request.Context, us *model.UploadSession, rd io.Read
|
||||
}()
|
||||
|
||||
// fetch the session from store to check for inconsistencies.
|
||||
if storedSession, err := a.GetUploadSession(us.Id); err != nil {
|
||||
c.SetContext(WithMaster(c.Context()))
|
||||
if storedSession, err := a.GetUploadSession(c, us.Id); err != nil {
|
||||
return nil, err
|
||||
} else if us.FileOffset != storedSession.FileOffset {
|
||||
return nil, model.NewAppError("UploadData", "app.upload.upload_data.concurrent.app_error",
|
||||
|
||||
@@ -1877,7 +1877,6 @@ func TestSendSubscriptionHistoryEvent(t *testing.T) {
|
||||
CreateAt: 1000000000,
|
||||
Seats: 10,
|
||||
DNS: "some.dns.server",
|
||||
IsPaidTier: "false",
|
||||
}
|
||||
|
||||
subscriptionHistory := &model.SubscriptionHistory{
|
||||
|
||||
Ссылка в новой задаче
Block a user