mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
* MM-30026: Use DB master when getting team members from a session A race condition happens when the read-replica isn't updated yet by the time a session expiry message reaches another node in the cluster. Here is the sequence of events that can cause it: - Server1 gets any request which has to wipe session cache. - The SQL query is written to DB master, and a cluster message is propagated to clear the session cache for that user. - Now before the read-replica is updated with the master’s update, the cluster message reaches Server2. The session cache is wiped out for that user. - _Any random_ request for that user hits Server2. Does NOT have to be the update team name request. The request does not find the value in session cache, because it’s wiped off, and picks it up from the DB. Surprise surprise, it gets the stale value. Sticks it into the cache. By now, the read-replica is updated. But guess what, we aren’t going to ask the DB anymore, because we have it in the cache. And the cache has the stale value. We use a temporary approach for now by introducing a context in the DB calls so that the useMaster information can be easily passed. And this has the added advantage of reusing the same context for future DB calls in case it happens. And we can also add more context keys as needed. A proper approach needs some architectural changes. See the issue for more details. ```release-note Fixed a bug where a session will hold on to a cached value in an HA setup with read-replicas configured. ``` * incorporate review comments Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
1756 lines
53 KiB
Go
1756 lines
53 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha1"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/mattermost/mattermost-server/v5/mlog"
|
|
"github.com/mattermost/mattermost-server/v5/model"
|
|
"github.com/mattermost/mattermost-server/v5/store"
|
|
"github.com/mattermost/mattermost-server/v5/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(data *SchemeImportData, dryRun bool) *model.AppError {
|
|
if err := validateSchemeImportData(data); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If this is a Dry Run, do not continue any further.
|
|
if dryRun {
|
|
return nil
|
|
}
|
|
|
|
scheme, err := a.GetSchemeByName(*data.Name)
|
|
if err != nil {
|
|
scheme = new(model.Scheme)
|
|
} else if scheme.Scope != *data.Scope {
|
|
return model.NewAppError("BulkImport", "app.import.import_scheme.scope_change.error", map[string]interface{}{"SchemeName": scheme.Name}, "", http.StatusBadRequest)
|
|
}
|
|
|
|
scheme.Name = *data.Name
|
|
scheme.DisplayName = *data.DisplayName
|
|
scheme.Scope = *data.Scope
|
|
|
|
if data.Description != nil {
|
|
scheme.Description = *data.Description
|
|
}
|
|
|
|
if len(scheme.Id) == 0 {
|
|
scheme, err = a.CreateScheme(scheme)
|
|
} else {
|
|
scheme, err = a.UpdateScheme(scheme)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if scheme.Scope == model.SCHEME_SCOPE_TEAM {
|
|
data.DefaultTeamAdminRole.Name = &scheme.DefaultTeamAdminRole
|
|
if err := a.importRole(data.DefaultTeamAdminRole, dryRun, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
data.DefaultTeamUserRole.Name = &scheme.DefaultTeamUserRole
|
|
if err := a.importRole(data.DefaultTeamUserRole, dryRun, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
if data.DefaultTeamGuestRole == nil {
|
|
data.DefaultTeamGuestRole = &RoleImportData{
|
|
DisplayName: model.NewString("Team Guest Role for Scheme"),
|
|
}
|
|
}
|
|
data.DefaultTeamGuestRole.Name = &scheme.DefaultTeamGuestRole
|
|
if err := a.importRole(data.DefaultTeamGuestRole, dryRun, true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if scheme.Scope == model.SCHEME_SCOPE_TEAM || scheme.Scope == model.SCHEME_SCOPE_CHANNEL {
|
|
data.DefaultChannelAdminRole.Name = &scheme.DefaultChannelAdminRole
|
|
if err := a.importRole(data.DefaultChannelAdminRole, dryRun, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
data.DefaultChannelUserRole.Name = &scheme.DefaultChannelUserRole
|
|
if err := a.importRole(data.DefaultChannelUserRole, dryRun, true); err != nil {
|
|
return err
|
|
}
|
|
|
|
if data.DefaultChannelGuestRole == nil {
|
|
data.DefaultChannelGuestRole = &RoleImportData{
|
|
DisplayName: model.NewString("Channel Guest Role for Scheme"),
|
|
}
|
|
}
|
|
data.DefaultChannelGuestRole.Name = &scheme.DefaultChannelGuestRole
|
|
if err := a.importRole(data.DefaultChannelGuestRole, dryRun, true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) importRole(data *RoleImportData, dryRun bool, isSchemeRole bool) *model.AppError {
|
|
if !isSchemeRole {
|
|
if err := validateRoleImportData(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// If this is a Dry Run, do not continue any further.
|
|
if dryRun {
|
|
return nil
|
|
}
|
|
|
|
role, err := a.GetRoleByName(*data.Name)
|
|
if err != nil {
|
|
role = new(model.Role)
|
|
}
|
|
|
|
role.Name = *data.Name
|
|
|
|
if data.DisplayName != nil {
|
|
role.DisplayName = *data.DisplayName
|
|
}
|
|
|
|
if data.Description != nil {
|
|
role.Description = *data.Description
|
|
}
|
|
|
|
if data.Permissions != nil {
|
|
role.Permissions = *data.Permissions
|
|
}
|
|
|
|
if isSchemeRole {
|
|
role.SchemeManaged = true
|
|
} else {
|
|
role.SchemeManaged = false
|
|
}
|
|
|
|
if len(role.Id) == 0 {
|
|
_, err = a.CreateRole(role)
|
|
} else {
|
|
_, err = a.UpdateRole(role)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (a *App) importTeam(data *TeamImportData, dryRun bool) *model.AppError {
|
|
if err := validateTeamImportData(data); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If this is a Dry Run, do not continue any further.
|
|
if dryRun {
|
|
return nil
|
|
}
|
|
|
|
var team *model.Team
|
|
team, err := a.Srv().Store.Team().GetByName(*data.Name)
|
|
|
|
if err != nil {
|
|
team = &model.Team{}
|
|
}
|
|
|
|
team.Name = *data.Name
|
|
team.DisplayName = *data.DisplayName
|
|
team.Type = *data.Type
|
|
|
|
if data.Description != nil {
|
|
team.Description = *data.Description
|
|
}
|
|
|
|
if data.AllowOpenInvite != nil {
|
|
team.AllowOpenInvite = *data.AllowOpenInvite
|
|
}
|
|
|
|
if data.Scheme != nil {
|
|
scheme, err := a.GetSchemeByName(*data.Scheme)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if scheme.DeleteAt != 0 {
|
|
return model.NewAppError("BulkImport", "app.import.import_team.scheme_deleted.error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
if scheme.Scope != model.SCHEME_SCOPE_TEAM {
|
|
return model.NewAppError("BulkImport", "app.import.import_team.scheme_wrong_scope.error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
team.SchemeId = &scheme.Id
|
|
}
|
|
|
|
if team.Id == "" {
|
|
if _, err := a.CreateTeam(team); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if _, err := a.updateTeamUnsanitized(team); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) importChannel(data *ChannelImportData, dryRun bool) *model.AppError {
|
|
if err := validateChannelImportData(data); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If this is a Dry Run, do not continue any further.
|
|
if dryRun {
|
|
return nil
|
|
}
|
|
|
|
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]interface{}{"TeamName": *data.Team}, err.Error(), http.StatusBadRequest)
|
|
}
|
|
|
|
var channel *model.Channel
|
|
if result, err := a.Srv().Store.Channel().GetByNameIncludeDeleted(team.Id, *data.Name, true); err == nil {
|
|
channel = result
|
|
} else {
|
|
channel = &model.Channel{}
|
|
}
|
|
|
|
channel.TeamId = team.Id
|
|
channel.Name = *data.Name
|
|
channel.DisplayName = *data.DisplayName
|
|
channel.Type = *data.Type
|
|
|
|
if data.Header != nil {
|
|
channel.Header = *data.Header
|
|
}
|
|
|
|
if data.Purpose != nil {
|
|
channel.Purpose = *data.Purpose
|
|
}
|
|
|
|
if data.Scheme != nil {
|
|
scheme, err := a.GetSchemeByName(*data.Scheme)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if scheme.DeleteAt != 0 {
|
|
return model.NewAppError("BulkImport", "app.import.import_channel.scheme_deleted.error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
if scheme.Scope != model.SCHEME_SCOPE_CHANNEL {
|
|
return model.NewAppError("BulkImport", "app.import.import_channel.scheme_wrong_scope.error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
channel.SchemeId = &scheme.Id
|
|
}
|
|
|
|
if channel.Id == "" {
|
|
if _, err := a.CreateChannel(channel, false); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if _, err := a.UpdateChannel(channel); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) importUser(data *UserImportData, dryRun bool) *model.AppError {
|
|
if err := validateUserImportData(data); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If this is a Dry Run, do not continue any further.
|
|
if dryRun {
|
|
return nil
|
|
}
|
|
|
|
// We want to avoid database writes if nothing has changed.
|
|
hasUserChanged := false
|
|
hasNotifyPropsChanged := false
|
|
hasUserRolesChanged := false
|
|
hasUserAuthDataChanged := false
|
|
hasUserEmailVerifiedChanged := false
|
|
|
|
var user *model.User
|
|
var nErr error
|
|
user, nErr = a.Srv().Store.User().GetByUsername(*data.Username)
|
|
if nErr != nil {
|
|
user = &model.User{}
|
|
user.MakeNonNil()
|
|
user.SetDefaultNotifications()
|
|
hasUserChanged = true
|
|
}
|
|
|
|
user.Username = *data.Username
|
|
|
|
if user.Email != *data.Email {
|
|
hasUserChanged = true
|
|
hasUserEmailVerifiedChanged = true // Changing the email resets email verified to false by default.
|
|
user.Email = *data.Email
|
|
user.Email = strings.ToLower(user.Email)
|
|
}
|
|
|
|
var password string
|
|
var authService string
|
|
var authData *string
|
|
|
|
if data.AuthService != nil {
|
|
if user.AuthService != *data.AuthService {
|
|
hasUserAuthDataChanged = true
|
|
}
|
|
authService = *data.AuthService
|
|
}
|
|
|
|
// AuthData and Password are mutually exclusive.
|
|
if data.AuthData != nil {
|
|
if user.AuthData == nil || *user.AuthData != *data.AuthData {
|
|
hasUserAuthDataChanged = true
|
|
}
|
|
authData = data.AuthData
|
|
password = ""
|
|
} else if data.Password != nil {
|
|
password = *data.Password
|
|
authData = nil
|
|
} else {
|
|
// If no AuthData or Password is specified, we must generate a password.
|
|
password = model.GeneratePassword(*a.Config().PasswordSettings.MinimumLength)
|
|
authData = nil
|
|
}
|
|
|
|
user.Password = password
|
|
user.AuthService = authService
|
|
user.AuthData = authData
|
|
|
|
// Automatically assume all emails are verified.
|
|
emailVerified := true
|
|
if user.EmailVerified != emailVerified {
|
|
user.EmailVerified = emailVerified
|
|
hasUserEmailVerifiedChanged = true
|
|
}
|
|
|
|
if data.Nickname != nil {
|
|
if user.Nickname != *data.Nickname {
|
|
user.Nickname = *data.Nickname
|
|
hasUserChanged = true
|
|
}
|
|
}
|
|
|
|
if data.FirstName != nil {
|
|
if user.FirstName != *data.FirstName {
|
|
user.FirstName = *data.FirstName
|
|
hasUserChanged = true
|
|
}
|
|
}
|
|
|
|
if data.LastName != nil {
|
|
if user.LastName != *data.LastName {
|
|
user.LastName = *data.LastName
|
|
hasUserChanged = true
|
|
}
|
|
}
|
|
|
|
if data.Position != nil {
|
|
if user.Position != *data.Position {
|
|
user.Position = *data.Position
|
|
hasUserChanged = true
|
|
}
|
|
}
|
|
|
|
if data.Locale != nil {
|
|
if user.Locale != *data.Locale {
|
|
user.Locale = *data.Locale
|
|
hasUserChanged = true
|
|
}
|
|
} else {
|
|
if user.Locale != *a.Config().LocalizationSettings.DefaultClientLocale {
|
|
user.Locale = *a.Config().LocalizationSettings.DefaultClientLocale
|
|
hasUserChanged = true
|
|
}
|
|
}
|
|
|
|
if data.DeleteAt != nil {
|
|
if user.DeleteAt != *data.DeleteAt {
|
|
user.DeleteAt = *data.DeleteAt
|
|
hasUserChanged = true
|
|
}
|
|
}
|
|
|
|
var roles string
|
|
if data.Roles != nil {
|
|
if user.Roles != *data.Roles {
|
|
roles = *data.Roles
|
|
hasUserRolesChanged = true
|
|
}
|
|
} else if len(user.Roles) == 0 {
|
|
// Set SYSTEM_USER roles on newly created users by default.
|
|
if user.Roles != model.SYSTEM_USER_ROLE_ID {
|
|
roles = model.SYSTEM_USER_ROLE_ID
|
|
hasUserRolesChanged = true
|
|
}
|
|
}
|
|
user.Roles = roles
|
|
|
|
if data.NotifyProps != nil {
|
|
if data.NotifyProps.Desktop != nil {
|
|
if value, ok := user.NotifyProps[model.DESKTOP_NOTIFY_PROP]; !ok || value != *data.NotifyProps.Desktop {
|
|
user.AddNotifyProp(model.DESKTOP_NOTIFY_PROP, *data.NotifyProps.Desktop)
|
|
hasNotifyPropsChanged = true
|
|
}
|
|
}
|
|
|
|
if data.NotifyProps.DesktopSound != nil {
|
|
if value, ok := user.NotifyProps[model.DESKTOP_SOUND_NOTIFY_PROP]; !ok || value != *data.NotifyProps.DesktopSound {
|
|
user.AddNotifyProp(model.DESKTOP_SOUND_NOTIFY_PROP, *data.NotifyProps.DesktopSound)
|
|
hasNotifyPropsChanged = true
|
|
}
|
|
}
|
|
|
|
if data.NotifyProps.Email != nil {
|
|
if value, ok := user.NotifyProps[model.EMAIL_NOTIFY_PROP]; !ok || value != *data.NotifyProps.Email {
|
|
user.AddNotifyProp(model.EMAIL_NOTIFY_PROP, *data.NotifyProps.Email)
|
|
hasNotifyPropsChanged = true
|
|
}
|
|
}
|
|
|
|
if data.NotifyProps.Mobile != nil {
|
|
if value, ok := user.NotifyProps[model.PUSH_NOTIFY_PROP]; !ok || value != *data.NotifyProps.Mobile {
|
|
user.AddNotifyProp(model.PUSH_NOTIFY_PROP, *data.NotifyProps.Mobile)
|
|
hasNotifyPropsChanged = true
|
|
}
|
|
}
|
|
|
|
if data.NotifyProps.MobilePushStatus != nil {
|
|
if value, ok := user.NotifyProps[model.PUSH_STATUS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.MobilePushStatus {
|
|
user.AddNotifyProp(model.PUSH_STATUS_NOTIFY_PROP, *data.NotifyProps.MobilePushStatus)
|
|
hasNotifyPropsChanged = true
|
|
}
|
|
}
|
|
|
|
if data.NotifyProps.ChannelTrigger != nil {
|
|
if value, ok := user.NotifyProps[model.CHANNEL_MENTIONS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.ChannelTrigger {
|
|
user.AddNotifyProp(model.CHANNEL_MENTIONS_NOTIFY_PROP, *data.NotifyProps.ChannelTrigger)
|
|
hasNotifyPropsChanged = true
|
|
}
|
|
}
|
|
|
|
if data.NotifyProps.CommentsTrigger != nil {
|
|
if value, ok := user.NotifyProps[model.COMMENTS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.CommentsTrigger {
|
|
user.AddNotifyProp(model.COMMENTS_NOTIFY_PROP, *data.NotifyProps.CommentsTrigger)
|
|
hasNotifyPropsChanged = true
|
|
}
|
|
}
|
|
|
|
if data.NotifyProps.MentionKeys != nil {
|
|
if value, ok := user.NotifyProps[model.MENTION_KEYS_NOTIFY_PROP]; !ok || value != *data.NotifyProps.MentionKeys {
|
|
user.AddNotifyProp(model.MENTION_KEYS_NOTIFY_PROP, *data.NotifyProps.MentionKeys)
|
|
hasNotifyPropsChanged = true
|
|
}
|
|
} else {
|
|
user.UpdateMentionKeysFromUsername("")
|
|
}
|
|
}
|
|
|
|
var savedUser *model.User
|
|
var err *model.AppError
|
|
if user.Id == "" {
|
|
if savedUser, err = a.createUser(user); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if hasUserChanged {
|
|
if savedUser, err = a.UpdateUser(user, false); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if hasUserRolesChanged {
|
|
if savedUser, err = a.UpdateUserRoles(user.Id, roles, false); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if hasNotifyPropsChanged {
|
|
if savedUser, err = a.UpdateUserNotifyProps(user.Id, user.NotifyProps); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if len(password) > 0 {
|
|
if err = a.UpdatePassword(user, password); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if hasUserAuthDataChanged {
|
|
if _, nErr := a.Srv().Store.User().UpdateAuthData(user.Id, authService, authData, user.Email, false); nErr != nil {
|
|
var invErr *store.ErrInvalidInput
|
|
switch {
|
|
case errors.As(nErr, &invErr):
|
|
return model.NewAppError("importUser", "app.user.update_auth_data.email_exists.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
default:
|
|
return model.NewAppError("importUser", "app.user.update_auth_data.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if emailVerified {
|
|
if hasUserEmailVerifiedChanged {
|
|
if err := a.VerifyUserEmail(user.Id, user.Email); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if savedUser == nil {
|
|
savedUser = user
|
|
}
|
|
|
|
if data.ProfileImage != nil {
|
|
file, err := os.Open(*data.ProfileImage)
|
|
if err != nil {
|
|
mlog.Error("Unable to open the profile image.", mlog.Any("err", err))
|
|
}
|
|
if err := a.SetProfileImageFromMultiPartFile(savedUser.Id, file); err != nil {
|
|
mlog.Error("Unable to set the profile image from a file.", mlog.Any("err", err))
|
|
}
|
|
}
|
|
|
|
// Preferences.
|
|
var preferences model.Preferences
|
|
|
|
if data.Theme != nil {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: savedUser.Id,
|
|
Category: model.PREFERENCE_CATEGORY_THEME,
|
|
Name: "",
|
|
Value: *data.Theme,
|
|
})
|
|
}
|
|
|
|
if data.UseMilitaryTime != nil {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: savedUser.Id,
|
|
Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
|
|
Name: model.PREFERENCE_NAME_USE_MILITARY_TIME,
|
|
Value: *data.UseMilitaryTime,
|
|
})
|
|
}
|
|
|
|
if data.CollapsePreviews != nil {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: savedUser.Id,
|
|
Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
|
|
Name: model.PREFERENCE_NAME_COLLAPSE_SETTING,
|
|
Value: *data.CollapsePreviews,
|
|
})
|
|
}
|
|
|
|
if data.MessageDisplay != nil {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: savedUser.Id,
|
|
Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
|
|
Name: model.PREFERENCE_NAME_MESSAGE_DISPLAY,
|
|
Value: *data.MessageDisplay,
|
|
})
|
|
}
|
|
|
|
if data.ChannelDisplayMode != nil {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: savedUser.Id,
|
|
Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
|
|
Name: "channel_display_mode",
|
|
Value: *data.ChannelDisplayMode,
|
|
})
|
|
}
|
|
|
|
if data.TutorialStep != nil {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: savedUser.Id,
|
|
Category: model.PREFERENCE_CATEGORY_TUTORIAL_STEPS,
|
|
Name: savedUser.Id,
|
|
Value: *data.TutorialStep,
|
|
})
|
|
}
|
|
|
|
if data.UseMarkdownPreview != nil {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: savedUser.Id,
|
|
Category: model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS,
|
|
Name: "feature_enabled_markdown_preview",
|
|
Value: *data.UseMarkdownPreview,
|
|
})
|
|
}
|
|
|
|
if data.UseFormatting != nil {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: savedUser.Id,
|
|
Category: model.PREFERENCE_CATEGORY_ADVANCED_SETTINGS,
|
|
Name: "formatting",
|
|
Value: *data.UseFormatting,
|
|
})
|
|
}
|
|
|
|
if data.ShowUnreadSection != nil {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: savedUser.Id,
|
|
Category: model.PREFERENCE_CATEGORY_SIDEBAR_SETTINGS,
|
|
Name: "show_unread_section",
|
|
Value: *data.ShowUnreadSection,
|
|
})
|
|
}
|
|
|
|
if data.EmailInterval != nil || savedUser.NotifyProps[model.EMAIL_NOTIFY_PROP] == "false" {
|
|
var intervalSeconds string
|
|
if value := savedUser.NotifyProps[model.EMAIL_NOTIFY_PROP]; value == "false" {
|
|
intervalSeconds = "0"
|
|
} else {
|
|
switch *data.EmailInterval {
|
|
case model.PREFERENCE_EMAIL_INTERVAL_IMMEDIATELY:
|
|
intervalSeconds = model.PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS
|
|
case model.PREFERENCE_EMAIL_INTERVAL_FIFTEEN:
|
|
intervalSeconds = model.PREFERENCE_EMAIL_INTERVAL_FIFTEEN_AS_SECONDS
|
|
case model.PREFERENCE_EMAIL_INTERVAL_HOUR:
|
|
intervalSeconds = model.PREFERENCE_EMAIL_INTERVAL_HOUR_AS_SECONDS
|
|
}
|
|
}
|
|
if intervalSeconds != "" {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: savedUser.Id,
|
|
Category: model.PREFERENCE_CATEGORY_NOTIFICATIONS,
|
|
Name: model.PREFERENCE_NAME_EMAIL_INTERVAL,
|
|
Value: intervalSeconds,
|
|
})
|
|
}
|
|
}
|
|
|
|
if len(preferences) > 0 {
|
|
if err := a.Srv().Store.Preference().Save(&preferences); err != nil {
|
|
return model.NewAppError("BulkImport", "app.import.import_user.save_preferences.error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
return a.importUserTeams(savedUser, data.Teams)
|
|
}
|
|
|
|
func (a *App) importUserTeams(user *model.User, data *[]UserTeamImportData) *model.AppError {
|
|
if data == nil {
|
|
return nil
|
|
}
|
|
|
|
teamNames := []string{}
|
|
for _, tdata := range *data {
|
|
teamNames = append(teamNames, *tdata.Name)
|
|
}
|
|
allTeams, err := a.getTeamsByNames(teamNames)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
teamThemePreferencesByID := map[string]model.Preferences{}
|
|
channels := map[string][]UserChannelImportData{}
|
|
teamsByID := map[string]*model.Team{}
|
|
teamMemberByTeamID := map[string]*model.TeamMember{}
|
|
newTeamMembers := []*model.TeamMember{}
|
|
oldTeamMembers := []*model.TeamMember{}
|
|
rolesByTeamId := map[string]string{}
|
|
isGuestByTeamId := map[string]bool{}
|
|
isUserByTeamId := map[string]bool{}
|
|
isAdminByTeamId := map[string]bool{}
|
|
existingMemberships, nErr := a.Srv().Store.Team().GetTeamsForUser(context.Background(), user.Id)
|
|
if nErr != nil {
|
|
return model.NewAppError("importUserTeams", "app.team.get_members.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
existingMembershipsByTeamId := map[string]*model.TeamMember{}
|
|
for _, teamMembership := range existingMemberships {
|
|
existingMembershipsByTeamId[teamMembership.TeamId] = teamMembership
|
|
}
|
|
for _, tdata := range *data {
|
|
team := allTeams[*tdata.Name]
|
|
|
|
// Team-specific theme Preferences.
|
|
if tdata.Theme != nil {
|
|
teamThemePreferencesByID[team.Id] = append(teamThemePreferencesByID[team.Id], model.Preference{
|
|
UserId: user.Id,
|
|
Category: model.PREFERENCE_CATEGORY_THEME,
|
|
Name: team.Id,
|
|
Value: *tdata.Theme,
|
|
})
|
|
}
|
|
|
|
isGuestByTeamId[team.Id] = false
|
|
isUserByTeamId[team.Id] = true
|
|
isAdminByTeamId[team.Id] = false
|
|
|
|
if tdata.Roles == nil {
|
|
isUserByTeamId[team.Id] = true
|
|
} else {
|
|
rawRoles := *tdata.Roles
|
|
explicitRoles := []string{}
|
|
for _, role := range strings.Fields(rawRoles) {
|
|
if role == model.TEAM_GUEST_ROLE_ID {
|
|
isGuestByTeamId[team.Id] = true
|
|
isUserByTeamId[team.Id] = false
|
|
} else if role == model.TEAM_USER_ROLE_ID {
|
|
isUserByTeamId[team.Id] = true
|
|
} else if role == model.TEAM_ADMIN_ROLE_ID {
|
|
isAdminByTeamId[team.Id] = true
|
|
} else {
|
|
explicitRoles = append(explicitRoles, role)
|
|
}
|
|
}
|
|
rolesByTeamId[team.Id] = strings.Join(explicitRoles, " ")
|
|
}
|
|
|
|
member := &model.TeamMember{
|
|
TeamId: team.Id,
|
|
UserId: user.Id,
|
|
SchemeGuest: user.IsGuest(),
|
|
SchemeUser: !user.IsGuest(),
|
|
SchemeAdmin: team.Email == user.Email && !user.IsGuest(),
|
|
}
|
|
if !user.IsGuest() {
|
|
var userShouldBeAdmin bool
|
|
userShouldBeAdmin, err = a.UserIsInAdminRoleGroup(user.Id, team.Id, model.GroupSyncableTypeTeam)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
member.SchemeAdmin = userShouldBeAdmin
|
|
}
|
|
|
|
if tdata.Channels != nil {
|
|
channels[team.Id] = append(channels[team.Id], *tdata.Channels...)
|
|
}
|
|
if !user.IsGuest() {
|
|
channels[team.Id] = append(channels[team.Id], UserChannelImportData{Name: model.NewString(model.DEFAULT_CHANNEL)})
|
|
}
|
|
|
|
teamsByID[team.Id] = team
|
|
teamMemberByTeamID[team.Id] = member
|
|
if _, ok := existingMembershipsByTeamId[team.Id]; !ok {
|
|
newTeamMembers = append(newTeamMembers, member)
|
|
} else {
|
|
oldTeamMembers = append(oldTeamMembers, member)
|
|
}
|
|
}
|
|
|
|
oldMembers, nErr := a.Srv().Store.Team().UpdateMultipleMembers(oldTeamMembers)
|
|
if nErr != nil {
|
|
var appErr *model.AppError
|
|
switch {
|
|
case errors.As(nErr, &appErr):
|
|
return appErr
|
|
default:
|
|
return model.NewAppError("importUserTeams", "app.team.save_member.save.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
newMembers := []*model.TeamMember{}
|
|
if len(newTeamMembers) > 0 {
|
|
var nErr error
|
|
newMembers, nErr = a.Srv().Store.Team().SaveMultipleMembers(newTeamMembers, *a.Config().TeamSettings.MaxUsersPerTeam)
|
|
if nErr != nil {
|
|
var appErr *model.AppError
|
|
var conflictErr *store.ErrConflict
|
|
var limitExeededErr *store.ErrLimitExceeded
|
|
switch {
|
|
case errors.As(nErr, &appErr): // in case we haven't converted to plain error.
|
|
return appErr
|
|
case errors.As(nErr, &conflictErr):
|
|
return model.NewAppError("BulkImport", "app.import.import_user_teams.save_members.conflict.app_error", nil, nErr.Error(), http.StatusBadRequest)
|
|
case errors.As(nErr, &limitExeededErr):
|
|
return model.NewAppError("BulkImport", "app.import.import_user_teams.save_members.max_accounts.app_error", nil, nErr.Error(), http.StatusBadRequest)
|
|
default: // last fallback in case it doesn't map to an existing app error.
|
|
return model.NewAppError("BulkImport", "app.import.import_user_teams.save_members.error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, member := range append(newMembers, oldMembers...) {
|
|
if member.ExplicitRoles != rolesByTeamId[member.TeamId] {
|
|
if _, err = a.UpdateTeamMemberRoles(member.TeamId, user.Id, rolesByTeamId[member.TeamId]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
a.UpdateTeamMemberSchemeRoles(member.TeamId, user.Id, isGuestByTeamId[member.TeamId], isUserByTeamId[member.TeamId], isAdminByTeamId[member.TeamId])
|
|
}
|
|
|
|
for _, team := range allTeams {
|
|
if len(teamThemePreferencesByID[team.Id]) > 0 {
|
|
pref := teamThemePreferencesByID[team.Id]
|
|
if err := a.Srv().Store.Preference().Save(&pref); err != nil {
|
|
return model.NewAppError("BulkImport", "app.import.import_user_teams.save_preferences.error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
channelsToImport := channels[team.Id]
|
|
if err := a.importUserChannels(user, team, teamMemberByTeamID[team.Id], &channelsToImport); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) importUserChannels(user *model.User, team *model.Team, teamMember *model.TeamMember, data *[]UserChannelImportData) *model.AppError {
|
|
if data == nil {
|
|
return nil
|
|
}
|
|
|
|
channelNames := []string{}
|
|
for _, tdata := range *data {
|
|
channelNames = append(channelNames, *tdata.Name)
|
|
}
|
|
allChannels, err := a.getChannelsByNames(channelNames, team.Id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
channelsByID := map[string]*model.Channel{}
|
|
channelMemberByChannelID := map[string]*model.ChannelMember{}
|
|
newChannelMembers := []*model.ChannelMember{}
|
|
oldChannelMembers := []*model.ChannelMember{}
|
|
rolesByChannelId := map[string]string{}
|
|
channelPreferencesByID := map[string]model.Preferences{}
|
|
isGuestByChannelId := map[string]bool{}
|
|
isUserByChannelId := map[string]bool{}
|
|
isAdminByChannelId := map[string]bool{}
|
|
existingMemberships, nErr := a.Srv().Store.Channel().GetMembersForUser(team.Id, user.Id)
|
|
if nErr != nil {
|
|
return model.NewAppError("importUserChannels", "app.channel.get_members.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
existingMembershipsByChannelId := map[string]model.ChannelMember{}
|
|
for _, channelMembership := range *existingMemberships {
|
|
existingMembershipsByChannelId[channelMembership.ChannelId] = channelMembership
|
|
}
|
|
for _, cdata := range *data {
|
|
channel, ok := allChannels[*cdata.Name]
|
|
if !ok {
|
|
return model.NewAppError("BulkImport", "app.import.import_user_channels.channel_not_found.error", nil, "", http.StatusInternalServerError)
|
|
}
|
|
if _, ok = channelsByID[channel.Id]; ok && *cdata.Name == model.DEFAULT_CHANNEL {
|
|
// town-square membership was in the import and added by the importer (skip the added by the importer)
|
|
continue
|
|
}
|
|
|
|
isGuestByChannelId[channel.Id] = false
|
|
isUserByChannelId[channel.Id] = true
|
|
isAdminByChannelId[channel.Id] = false
|
|
|
|
if cdata.Roles == nil {
|
|
isUserByChannelId[channel.Id] = true
|
|
} else {
|
|
rawRoles := *cdata.Roles
|
|
explicitRoles := []string{}
|
|
for _, role := range strings.Fields(rawRoles) {
|
|
if role == model.CHANNEL_GUEST_ROLE_ID {
|
|
isGuestByChannelId[channel.Id] = true
|
|
isUserByChannelId[channel.Id] = false
|
|
} else if role == model.CHANNEL_USER_ROLE_ID {
|
|
isUserByChannelId[channel.Id] = true
|
|
} else if role == model.CHANNEL_ADMIN_ROLE_ID {
|
|
isAdminByChannelId[channel.Id] = true
|
|
} else {
|
|
explicitRoles = append(explicitRoles, role)
|
|
}
|
|
}
|
|
rolesByChannelId[channel.Id] = strings.Join(explicitRoles, " ")
|
|
}
|
|
|
|
if cdata.Favorite != nil && *cdata.Favorite {
|
|
channelPreferencesByID[channel.Id] = append(channelPreferencesByID[channel.Id], model.Preference{
|
|
UserId: user.Id,
|
|
Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
|
Name: channel.Id,
|
|
Value: "true",
|
|
})
|
|
}
|
|
|
|
member := &model.ChannelMember{
|
|
ChannelId: channel.Id,
|
|
UserId: user.Id,
|
|
NotifyProps: model.GetDefaultChannelNotifyProps(),
|
|
SchemeGuest: user.IsGuest(),
|
|
SchemeUser: !user.IsGuest(),
|
|
SchemeAdmin: false,
|
|
}
|
|
if !user.IsGuest() {
|
|
var userShouldBeAdmin bool
|
|
userShouldBeAdmin, err = a.UserIsInAdminRoleGroup(user.Id, team.Id, model.GroupSyncableTypeTeam)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
member.SchemeAdmin = userShouldBeAdmin
|
|
}
|
|
|
|
if cdata.NotifyProps != nil {
|
|
if cdata.NotifyProps.Desktop != nil {
|
|
member.NotifyProps[model.DESKTOP_NOTIFY_PROP] = *cdata.NotifyProps.Desktop
|
|
}
|
|
|
|
if cdata.NotifyProps.Mobile != nil {
|
|
member.NotifyProps[model.PUSH_NOTIFY_PROP] = *cdata.NotifyProps.Mobile
|
|
}
|
|
|
|
if cdata.NotifyProps.MarkUnread != nil {
|
|
member.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] = *cdata.NotifyProps.MarkUnread
|
|
}
|
|
}
|
|
|
|
channelsByID[channel.Id] = channel
|
|
channelMemberByChannelID[channel.Id] = member
|
|
if _, ok := existingMembershipsByChannelId[channel.Id]; !ok {
|
|
newChannelMembers = append(newChannelMembers, member)
|
|
} else {
|
|
oldChannelMembers = append(oldChannelMembers, member)
|
|
}
|
|
}
|
|
|
|
oldMembers, nErr := a.Srv().Store.Channel().UpdateMultipleMembers(oldChannelMembers)
|
|
if nErr != nil {
|
|
var nfErr *store.ErrNotFound
|
|
var appErr *model.AppError
|
|
switch {
|
|
case errors.As(nErr, &appErr):
|
|
return appErr
|
|
case errors.As(nErr, &nfErr):
|
|
return model.NewAppError("importUserChannels", MISSING_CHANNEL_MEMBER_ERROR, nil, nfErr.Error(), http.StatusNotFound)
|
|
default:
|
|
return model.NewAppError("importUserChannels", "app.channel.get_member.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
newMembers := []*model.ChannelMember{}
|
|
if len(newChannelMembers) > 0 {
|
|
newMembers, nErr = a.Srv().Store.Channel().SaveMultipleMembers(newChannelMembers)
|
|
if nErr != nil {
|
|
var cErr *store.ErrConflict
|
|
var appErr *model.AppError
|
|
switch {
|
|
case errors.As(nErr, &cErr):
|
|
switch cErr.Resource {
|
|
case "ChannelMembers":
|
|
return model.NewAppError("importUserChannels", "app.channel.save_member.exists.app_error", nil, cErr.Error(), http.StatusBadRequest)
|
|
}
|
|
case errors.As(nErr, &appErr):
|
|
return appErr
|
|
default:
|
|
return model.NewAppError("importUserChannels", "app.channel.create_direct_channel.internal_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, member := range append(newMembers, oldMembers...) {
|
|
if member.ExplicitRoles != rolesByChannelId[member.ChannelId] {
|
|
if _, err = a.UpdateChannelMemberRoles(member.ChannelId, user.Id, rolesByChannelId[member.ChannelId]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
a.UpdateChannelMemberSchemeRoles(member.ChannelId, user.Id, isGuestByChannelId[member.ChannelId], isUserByChannelId[member.ChannelId], isAdminByChannelId[member.ChannelId])
|
|
}
|
|
|
|
for _, channel := range allChannels {
|
|
if len(channelPreferencesByID[channel.Id]) > 0 {
|
|
pref := channelPreferencesByID[channel.Id]
|
|
if err := a.Srv().Store.Preference().Save(&pref); err != nil {
|
|
return model.NewAppError("BulkImport", "app.import.import_user_channels.save_preferences.error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) importReaction(data *ReactionImportData, post *model.Post, dryRun bool) *model.AppError {
|
|
if err := validateReactionImportData(data, post.CreateAt); err != nil {
|
|
return err
|
|
}
|
|
|
|
var user *model.User
|
|
var nErr error
|
|
if user, nErr = a.Srv().Store.User().GetByUsername(*data.User); nErr != nil {
|
|
return model.NewAppError("BulkImport", "app.import.import_post.user_not_found.error", map[string]interface{}{"Username": data.User}, nErr.Error(), http.StatusBadRequest)
|
|
}
|
|
|
|
reaction := &model.Reaction{
|
|
UserId: user.Id,
|
|
PostId: post.Id,
|
|
EmojiName: *data.EmojiName,
|
|
CreateAt: *data.CreateAt,
|
|
}
|
|
if _, nErr = a.Srv().Store.Reaction().Save(reaction); nErr != nil {
|
|
var appErr *model.AppError
|
|
switch {
|
|
case errors.As(nErr, &appErr):
|
|
return appErr
|
|
default:
|
|
return model.NewAppError("importReaction", "app.reaction.save.save.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) importReplies(data []ReplyImportData, post *model.Post, teamId string, dryRun bool) *model.AppError {
|
|
var err *model.AppError
|
|
usernames := []string{}
|
|
for _, replyData := range data {
|
|
replyData := replyData
|
|
if err = validateReplyImportData(&replyData, post.CreateAt, a.MaxPostSize()); err != nil {
|
|
return err
|
|
}
|
|
usernames = append(usernames, *replyData.User)
|
|
}
|
|
|
|
users, err := a.getUsersByUsernames(usernames)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
postsWithData := []postAndData{}
|
|
postsForCreateList := []*model.Post{}
|
|
postsForOverwriteList := []*model.Post{}
|
|
|
|
for _, replyData := range data {
|
|
replyData := replyData
|
|
user := users[*replyData.User]
|
|
|
|
// Check if this post already exists.
|
|
replies, nErr := a.Srv().Store.Post().GetPostsCreatedAt(post.ChannelId, *replyData.CreateAt)
|
|
if nErr != nil {
|
|
return model.NewAppError("importReplies", "app.post.get_posts_created_at.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
var reply *model.Post
|
|
for _, r := range replies {
|
|
if r.Message == *replyData.Message && r.RootId == post.Id {
|
|
reply = r
|
|
break
|
|
}
|
|
}
|
|
|
|
if reply == nil {
|
|
reply = &model.Post{}
|
|
}
|
|
reply.UserId = user.Id
|
|
reply.ChannelId = post.ChannelId
|
|
reply.ParentId = post.Id
|
|
reply.RootId = post.Id
|
|
reply.Message = *replyData.Message
|
|
reply.CreateAt = *replyData.CreateAt
|
|
|
|
fileIds, err := a.uploadAttachments(replyData.Attachments, reply, teamId, dryRun)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, fileID := range reply.FileIds {
|
|
if _, ok := fileIds[fileID]; !ok {
|
|
a.Srv().Store.FileInfo().PermanentDelete(fileID)
|
|
}
|
|
}
|
|
reply.FileIds = make([]string, 0)
|
|
for fileID := range fileIds {
|
|
reply.FileIds = append(reply.FileIds, fileID)
|
|
}
|
|
|
|
if len(reply.Id) == 0 {
|
|
postsForCreateList = append(postsForCreateList, reply)
|
|
} else {
|
|
postsForOverwriteList = append(postsForOverwriteList, reply)
|
|
}
|
|
postsWithData = append(postsWithData, postAndData{post: reply, replyData: &replyData})
|
|
}
|
|
|
|
if len(postsForCreateList) > 0 {
|
|
if _, _, err := a.Srv().Store.Post().SaveMultiple(postsForCreateList); err != nil {
|
|
var appErr *model.AppError
|
|
var invErr *store.ErrInvalidInput
|
|
switch {
|
|
case errors.As(err, &appErr):
|
|
return appErr
|
|
case errors.As(err, &invErr):
|
|
return model.NewAppError("importReplies", "app.post.save.existing.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
default:
|
|
return model.NewAppError("importReplies", "app.post.save.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
}
|
|
|
|
if _, _, nErr := a.Srv().Store.Post().OverwriteMultiple(postsForOverwriteList); nErr != nil {
|
|
return model.NewAppError("importReplies", "app.post.overwrite.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
for _, postWithData := range postsWithData {
|
|
a.updateFileInfoWithPostId(postWithData.post)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) importAttachment(data *AttachmentImportData, post *model.Post, teamId string, dryRun bool) (*model.FileInfo, *model.AppError) {
|
|
file, err := os.Open(*data.Path)
|
|
if file == nil || err != nil {
|
|
return nil, model.NewAppError("BulkImport", "app.import.attachment.bad_file.error", map[string]interface{}{"FilePath": *data.Path}, "", http.StatusBadRequest)
|
|
}
|
|
|
|
timestamp := utils.TimeFromMillis(post.CreateAt)
|
|
buf := bytes.NewBuffer(nil)
|
|
_, _ = io.Copy(buf, file)
|
|
// Go over existing files in the post and see if there already exists a file with the same name, size and hash. If so - skip it
|
|
if post.Id != "" {
|
|
oldFiles, err := a.GetFileInfosForPost(post.Id, true)
|
|
if err != nil {
|
|
return nil, model.NewAppError("BulkImport", "app.import.attachment.file_upload.error", map[string]interface{}{"FilePath": *data.Path}, "", http.StatusBadRequest)
|
|
}
|
|
for _, oldFile := range oldFiles {
|
|
if oldFile.Name != path.Base(file.Name()) || oldFile.Size != int64(buf.Len()) {
|
|
continue
|
|
}
|
|
// check md5
|
|
newHash := sha1.Sum(buf.Bytes())
|
|
oldFileData, err := a.GetFile(oldFile.Id)
|
|
if err != nil {
|
|
return nil, model.NewAppError("BulkImport", "app.import.attachment.file_upload.error", map[string]interface{}{"FilePath": *data.Path}, "", http.StatusBadRequest)
|
|
}
|
|
oldHash := sha1.Sum(oldFileData)
|
|
|
|
if bytes.Equal(oldHash[:], newHash[:]) {
|
|
mlog.Info("Skipping uploading of file because name already exists", mlog.Any("file_name", file.Name()))
|
|
return oldFile, nil
|
|
}
|
|
}
|
|
}
|
|
fileInfo, appErr := a.DoUploadFile(timestamp, teamId, post.ChannelId, post.UserId, file.Name(), buf.Bytes())
|
|
if appErr != nil {
|
|
mlog.Error("Failed to upload file:", mlog.Err(appErr))
|
|
return nil, appErr
|
|
}
|
|
|
|
a.HandleImages([]string{fileInfo.PreviewPath}, []string{fileInfo.ThumbnailPath}, [][]byte{buf.Bytes()})
|
|
|
|
mlog.Info("Uploading file with name", mlog.String("file_name", file.Name()))
|
|
return fileInfo, nil
|
|
}
|
|
|
|
type postAndData struct {
|
|
post *model.Post
|
|
postData *PostImportData
|
|
directPostData *DirectPostImportData
|
|
replyData *ReplyImportData
|
|
team *model.Team
|
|
lineNumber int
|
|
}
|
|
|
|
func (a *App) getUsersByUsernames(usernames []string) (map[string]*model.User, *model.AppError) {
|
|
uniqueUsernames := utils.RemoveDuplicatesFromStringArray(usernames)
|
|
allUsers, err := a.Srv().Store.User().GetProfilesByUsernames(uniqueUsernames, nil)
|
|
if err != nil {
|
|
return nil, model.NewAppError("BulkImport", "app.import.get_users_by_username.some_users_not_found.error", nil, err.Error(), http.StatusBadRequest)
|
|
}
|
|
|
|
if len(allUsers) != len(uniqueUsernames) {
|
|
return nil, model.NewAppError("BulkImport", "app.import.get_users_by_username.some_users_not_found.error", nil, "", http.StatusBadRequest)
|
|
}
|
|
|
|
users := make(map[string]*model.User)
|
|
for _, user := range allUsers {
|
|
users[user.Username] = user
|
|
}
|
|
return users, nil
|
|
}
|
|
|
|
func (a *App) getTeamsByNames(names []string) (map[string]*model.Team, *model.AppError) {
|
|
allTeams, err := a.Srv().Store.Team().GetByNames(names)
|
|
if err != nil {
|
|
return nil, model.NewAppError("BulkImport", "app.import.get_teams_by_names.some_teams_not_found.error", nil, err.Error(), http.StatusBadRequest)
|
|
}
|
|
|
|
teams := make(map[string]*model.Team)
|
|
for _, team := range allTeams {
|
|
teams[team.Name] = team
|
|
}
|
|
return teams, nil
|
|
}
|
|
|
|
func (a *App) getChannelsByNames(names []string, teamId string) (map[string]*model.Channel, *model.AppError) {
|
|
allChannels, err := a.Srv().Store.Channel().GetByNames(teamId, names, true)
|
|
if err != nil {
|
|
return nil, model.NewAppError("BulkImport", "app.import.get_teams_by_names.some_teams_not_found.error", nil, err.Error(), http.StatusBadRequest)
|
|
}
|
|
|
|
channels := make(map[string]*model.Channel)
|
|
for _, channel := range allChannels {
|
|
channels[channel.Name] = channel
|
|
}
|
|
return channels, nil
|
|
}
|
|
|
|
func (a *App) getChannelsForPosts(teams map[string]*model.Team, data []*PostImportData) (map[string]*model.Channel, *model.AppError) {
|
|
channels := make(map[string]*model.Channel)
|
|
for _, postData := range data {
|
|
team := teams[*postData.Team]
|
|
if channel, ok := channels[*postData.Channel]; !ok || channel == nil {
|
|
var err error
|
|
channel, err = a.Srv().Store.Channel().GetByName(team.Id, *postData.Channel, true)
|
|
if err != nil {
|
|
return nil, model.NewAppError("BulkImport", "app.import.import_post.channel_not_found.error", map[string]interface{}{"ChannelName": *postData.Channel}, err.Error(), http.StatusBadRequest)
|
|
}
|
|
channels[*postData.Channel] = channel
|
|
}
|
|
}
|
|
return channels, nil
|
|
}
|
|
|
|
// getPostStrID returns a string ID composed of several post fields to
|
|
// uniquely identify a post before it's imported, so it has no ID yet
|
|
func getPostStrID(post *model.Post) string {
|
|
return fmt.Sprintf("%d%s%s", post.CreateAt, post.ChannelId, post.Message)
|
|
}
|
|
|
|
// importMultiplePostLines will return an error and the line that
|
|
// caused it whenever possible
|
|
func (a *App) importMultiplePostLines(lines []LineImportWorkerData, dryRun bool) (int, *model.AppError) {
|
|
if len(lines) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
for _, line := range lines {
|
|
if err := validatePostImportData(line.Post, a.MaxPostSize()); err != nil {
|
|
return line.LineNumber, err
|
|
}
|
|
}
|
|
|
|
// If this is a Dry Run, do not continue any further.
|
|
if dryRun {
|
|
return 0, nil
|
|
}
|
|
|
|
usernames := []string{}
|
|
teamNames := make([]string, len(lines))
|
|
postsData := make([]*PostImportData, len(lines))
|
|
for i, line := range lines {
|
|
usernames = append(usernames, *line.Post.User)
|
|
if line.Post.FlaggedBy != nil {
|
|
usernames = append(usernames, *line.Post.FlaggedBy...)
|
|
}
|
|
teamNames[i] = *line.Post.Team
|
|
postsData[i] = line.Post
|
|
}
|
|
|
|
users, err := a.getUsersByUsernames(usernames)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
teams, err := a.getTeamsByNames(teamNames)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
channels, err := a.getChannelsForPosts(teams, postsData)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
postsWithData := []postAndData{}
|
|
postsForCreateList := []*model.Post{}
|
|
postsForCreateMap := map[string]int{}
|
|
postsForOverwriteList := []*model.Post{}
|
|
postsForOverwriteMap := map[string]int{}
|
|
|
|
for _, line := range lines {
|
|
team := teams[*line.Post.Team]
|
|
channel := channels[*line.Post.Channel]
|
|
user := users[*line.Post.User]
|
|
|
|
// Check if this post already exists.
|
|
posts, nErr := a.Srv().Store.Post().GetPostsCreatedAt(channel.Id, *line.Post.CreateAt)
|
|
if nErr != nil {
|
|
return line.LineNumber, model.NewAppError("importMultiplePostLines", "app.post.get_posts_created_at.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
var post *model.Post
|
|
for _, p := range posts {
|
|
if p.Message == *line.Post.Message {
|
|
post = p
|
|
break
|
|
}
|
|
}
|
|
|
|
if post == nil {
|
|
post = &model.Post{}
|
|
}
|
|
|
|
post.ChannelId = channel.Id
|
|
post.Message = *line.Post.Message
|
|
post.UserId = user.Id
|
|
post.CreateAt = *line.Post.CreateAt
|
|
post.Hashtags, _ = model.ParseHashtags(post.Message)
|
|
|
|
if line.Post.Props != nil {
|
|
post.Props = *line.Post.Props
|
|
}
|
|
|
|
fileIds, appErr := a.uploadAttachments(line.Post.Attachments, post, team.Id, dryRun)
|
|
if appErr != nil {
|
|
return line.LineNumber, appErr
|
|
}
|
|
for _, fileID := range post.FileIds {
|
|
if _, ok := fileIds[fileID]; !ok {
|
|
a.Srv().Store.FileInfo().PermanentDelete(fileID)
|
|
}
|
|
}
|
|
post.FileIds = make([]string, 0)
|
|
for fileID := range fileIds {
|
|
post.FileIds = append(post.FileIds, fileID)
|
|
}
|
|
|
|
if len(post.Id) == 0 {
|
|
postsForCreateList = append(postsForCreateList, post)
|
|
postsForCreateMap[getPostStrID(post)] = line.LineNumber
|
|
} else {
|
|
postsForOverwriteList = append(postsForOverwriteList, post)
|
|
postsForOverwriteMap[getPostStrID(post)] = line.LineNumber
|
|
}
|
|
postsWithData = append(postsWithData, postAndData{post: post, postData: line.Post, team: team, lineNumber: line.LineNumber})
|
|
}
|
|
|
|
if len(postsForCreateList) > 0 {
|
|
if _, idx, nErr := a.Srv().Store.Post().SaveMultiple(postsForCreateList); nErr != nil {
|
|
var appErr *model.AppError
|
|
var invErr *store.ErrInvalidInput
|
|
var retErr *model.AppError
|
|
switch {
|
|
case errors.As(nErr, &appErr):
|
|
retErr = appErr
|
|
case errors.As(nErr, &invErr):
|
|
retErr = model.NewAppError("importMultiplePostLines", "app.post.save.existing.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
default:
|
|
retErr = model.NewAppError("importMultiplePostLines", "app.post.save.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if idx != -1 && idx < len(postsForCreateList) {
|
|
post := postsForCreateList[idx]
|
|
if lineNumber, ok := postsForCreateMap[getPostStrID(post)]; ok {
|
|
return lineNumber, retErr
|
|
}
|
|
}
|
|
return 0, retErr
|
|
}
|
|
}
|
|
|
|
if _, idx, err := a.Srv().Store.Post().OverwriteMultiple(postsForOverwriteList); err != nil {
|
|
if idx != -1 && idx < len(postsForOverwriteList) {
|
|
post := postsForOverwriteList[idx]
|
|
if lineNumber, ok := postsForOverwriteMap[getPostStrID(post)]; ok {
|
|
return lineNumber, model.NewAppError("importMultiplePostLines", "app.post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
return 0, model.NewAppError("importMultiplePostLines", "app.post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
for _, postWithData := range postsWithData {
|
|
postWithData := postWithData
|
|
if postWithData.postData.FlaggedBy != nil {
|
|
var preferences model.Preferences
|
|
|
|
for _, username := range *postWithData.postData.FlaggedBy {
|
|
user := users[username]
|
|
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: user.Id,
|
|
Category: model.PREFERENCE_CATEGORY_FLAGGED_POST,
|
|
Name: postWithData.post.Id,
|
|
Value: "true",
|
|
})
|
|
}
|
|
|
|
if len(preferences) > 0 {
|
|
if err := a.Srv().Store.Preference().Save(&preferences); err != nil {
|
|
return postWithData.lineNumber, model.NewAppError("BulkImport", "app.import.import_post.save_preferences.error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
}
|
|
|
|
if postWithData.postData.Reactions != nil {
|
|
for _, reaction := range *postWithData.postData.Reactions {
|
|
reaction := reaction
|
|
if err := a.importReaction(&reaction, postWithData.post, dryRun); err != nil {
|
|
return postWithData.lineNumber, err
|
|
}
|
|
}
|
|
}
|
|
|
|
if postWithData.postData.Replies != nil && len(*postWithData.postData.Replies) > 0 {
|
|
err := a.importReplies(*postWithData.postData.Replies, postWithData.post, postWithData.team.Id, dryRun)
|
|
if err != nil {
|
|
return postWithData.lineNumber, err
|
|
}
|
|
}
|
|
a.updateFileInfoWithPostId(postWithData.post)
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
// uploadAttachments imports new attachments and returns current attachments of the post as a map
|
|
func (a *App) uploadAttachments(attachments *[]AttachmentImportData, post *model.Post, teamId string, dryRun bool) (map[string]bool, *model.AppError) {
|
|
if attachments == nil {
|
|
return nil, nil
|
|
}
|
|
fileIds := make(map[string]bool)
|
|
for _, attachment := range *attachments {
|
|
attachment := attachment
|
|
fileInfo, err := a.importAttachment(&attachment, post, teamId, dryRun)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fileIds[fileInfo.Id] = true
|
|
}
|
|
return fileIds, nil
|
|
}
|
|
|
|
func (a *App) updateFileInfoWithPostId(post *model.Post) {
|
|
for _, fileId := range post.FileIds {
|
|
if err := a.Srv().Store.FileInfo().AttachToPost(fileId, post.Id, post.UserId); err != nil {
|
|
mlog.Error("Error attaching files to post.", mlog.String("post_id", post.Id), mlog.Any("post_file_ids", post.FileIds), mlog.Err(err))
|
|
}
|
|
}
|
|
}
|
|
func (a *App) importDirectChannel(data *DirectChannelImportData, dryRun bool) *model.AppError {
|
|
var err *model.AppError
|
|
if err = validateDirectChannelImportData(data); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If this is a Dry Run, do not continue any further.
|
|
if dryRun {
|
|
return nil
|
|
}
|
|
|
|
var userIds []string
|
|
userMap, err := a.getUsersByUsernames(*data.Members)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, user := range *data.Members {
|
|
userIds = append(userIds, userMap[user].Id)
|
|
}
|
|
|
|
var channel *model.Channel
|
|
|
|
if len(userIds) == 2 {
|
|
ch, err := a.createDirectChannel(userIds[0], userIds[1])
|
|
if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR {
|
|
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_direct_channel.error", nil, err.Error(), http.StatusBadRequest)
|
|
}
|
|
channel = ch
|
|
} else {
|
|
ch, err := a.createGroupChannel(userIds, userIds[0])
|
|
if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR {
|
|
return model.NewAppError("BulkImport", "app.import.import_direct_channel.create_group_channel.error", nil, err.Error(), http.StatusBadRequest)
|
|
}
|
|
channel = ch
|
|
}
|
|
|
|
var preferences model.Preferences
|
|
|
|
for _, userId := range userIds {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: userId,
|
|
Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
|
|
Name: channel.Id,
|
|
Value: "true",
|
|
})
|
|
}
|
|
|
|
if data.FavoritedBy != nil {
|
|
for _, favoriter := range *data.FavoritedBy {
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: userMap[favoriter].Id,
|
|
Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
|
|
Name: channel.Id,
|
|
Value: "true",
|
|
})
|
|
}
|
|
}
|
|
|
|
if err := a.Srv().Store.Preference().Save(&preferences); err != nil {
|
|
var appErr *model.AppError
|
|
switch {
|
|
case errors.As(err, &appErr):
|
|
appErr.StatusCode = http.StatusBadRequest
|
|
return appErr
|
|
default:
|
|
return model.NewAppError("importDirectChannel", "app.preference.save.updating.app_error", nil, err.Error(), http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
if data.Header != nil {
|
|
channel.Header = *data.Header
|
|
if _, appErr := a.Srv().Store.Channel().Update(channel); appErr != nil {
|
|
return model.NewAppError("BulkImport", "app.import.import_direct_channel.update_header_failed.error", nil, appErr.Error(), http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// importMultipleDirectPostLines will return an error and the line
|
|
// that caused it whenever possible
|
|
func (a *App) importMultipleDirectPostLines(lines []LineImportWorkerData, dryRun bool) (int, *model.AppError) {
|
|
if len(lines) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
for _, line := range lines {
|
|
if err := validateDirectPostImportData(line.DirectPost, a.MaxPostSize()); err != nil {
|
|
return line.LineNumber, err
|
|
}
|
|
}
|
|
|
|
// If this is a Dry Run, do not continue any further.
|
|
if dryRun {
|
|
return 0, nil
|
|
}
|
|
|
|
usernames := []string{}
|
|
for _, line := range lines {
|
|
usernames = append(usernames, *line.DirectPost.User)
|
|
if line.DirectPost.FlaggedBy != nil {
|
|
usernames = append(usernames, *line.DirectPost.FlaggedBy...)
|
|
}
|
|
usernames = append(usernames, *line.DirectPost.ChannelMembers...)
|
|
}
|
|
|
|
users, err := a.getUsersByUsernames(usernames)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
postsWithData := []postAndData{}
|
|
postsForCreateList := []*model.Post{}
|
|
postsForCreateMap := map[string]int{}
|
|
postsForOverwriteList := []*model.Post{}
|
|
postsForOverwriteMap := map[string]int{}
|
|
|
|
for _, line := range lines {
|
|
var userIds []string
|
|
var err *model.AppError
|
|
for _, username := range *line.DirectPost.ChannelMembers {
|
|
user := users[username]
|
|
userIds = append(userIds, user.Id)
|
|
}
|
|
|
|
var channel *model.Channel
|
|
var ch *model.Channel
|
|
if len(userIds) == 2 {
|
|
ch, err = a.GetOrCreateDirectChannel(userIds[0], userIds[1])
|
|
if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR {
|
|
return line.LineNumber, model.NewAppError("BulkImport", "app.import.import_direct_post.create_direct_channel.error", nil, err.Error(), http.StatusBadRequest)
|
|
}
|
|
channel = ch
|
|
} else {
|
|
ch, err = a.createGroupChannel(userIds, userIds[0])
|
|
if err != nil && err.Id != store.CHANNEL_EXISTS_ERROR {
|
|
return line.LineNumber, model.NewAppError("BulkImport", "app.import.import_direct_post.create_group_channel.error", nil, err.Error(), http.StatusBadRequest)
|
|
}
|
|
channel = ch
|
|
}
|
|
|
|
user := users[*line.DirectPost.User]
|
|
|
|
// Check if this post already exists.
|
|
posts, nErr := a.Srv().Store.Post().GetPostsCreatedAt(channel.Id, *line.DirectPost.CreateAt)
|
|
if nErr != nil {
|
|
return line.LineNumber, model.NewAppError("BulkImport", "app.post.get_posts_created_at.app_error", nil, nErr.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
var post *model.Post
|
|
for _, p := range posts {
|
|
if p.Message == *line.DirectPost.Message {
|
|
post = p
|
|
break
|
|
}
|
|
}
|
|
|
|
if post == nil {
|
|
post = &model.Post{}
|
|
}
|
|
|
|
post.ChannelId = channel.Id
|
|
post.Message = *line.DirectPost.Message
|
|
post.UserId = user.Id
|
|
post.CreateAt = *line.DirectPost.CreateAt
|
|
post.Hashtags, _ = model.ParseHashtags(post.Message)
|
|
|
|
if line.DirectPost.Props != nil {
|
|
post.Props = *line.DirectPost.Props
|
|
}
|
|
|
|
fileIds, err := a.uploadAttachments(line.DirectPost.Attachments, post, "noteam", dryRun)
|
|
if err != nil {
|
|
return line.LineNumber, err
|
|
}
|
|
for _, fileID := range post.FileIds {
|
|
if _, ok := fileIds[fileID]; !ok {
|
|
a.Srv().Store.FileInfo().PermanentDelete(fileID)
|
|
}
|
|
}
|
|
post.FileIds = make([]string, 0)
|
|
for fileID := range fileIds {
|
|
post.FileIds = append(post.FileIds, fileID)
|
|
}
|
|
|
|
if len(post.Id) == 0 {
|
|
postsForCreateList = append(postsForCreateList, post)
|
|
postsForCreateMap[getPostStrID(post)] = line.LineNumber
|
|
} else {
|
|
postsForOverwriteList = append(postsForOverwriteList, post)
|
|
postsForOverwriteMap[getPostStrID(post)] = line.LineNumber
|
|
}
|
|
postsWithData = append(postsWithData, postAndData{post: post, directPostData: line.DirectPost, lineNumber: line.LineNumber})
|
|
}
|
|
|
|
if len(postsForCreateList) > 0 {
|
|
if _, idx, err := a.Srv().Store.Post().SaveMultiple(postsForCreateList); err != nil {
|
|
var appErr *model.AppError
|
|
var invErr *store.ErrInvalidInput
|
|
var retErr *model.AppError
|
|
switch {
|
|
case errors.As(err, &appErr):
|
|
retErr = appErr
|
|
case errors.As(err, &invErr):
|
|
retErr = model.NewAppError("importMultiplePostLines", "app.post.save.existing.app_error", nil, invErr.Error(), http.StatusBadRequest)
|
|
default:
|
|
retErr = model.NewAppError("importMultiplePostLines", "app.post.save.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if idx != -1 && idx < len(postsForCreateList) {
|
|
post := postsForCreateList[idx]
|
|
if lineNumber, ok := postsForCreateMap[getPostStrID(post)]; ok {
|
|
return lineNumber, retErr
|
|
}
|
|
}
|
|
return 0, retErr
|
|
}
|
|
}
|
|
if _, idx, err := a.Srv().Store.Post().OverwriteMultiple(postsForOverwriteList); err != nil {
|
|
if idx != -1 && idx < len(postsForOverwriteList) {
|
|
post := postsForOverwriteList[idx]
|
|
if lineNumber, ok := postsForOverwriteMap[getPostStrID(post)]; ok {
|
|
return lineNumber, model.NewAppError("importMultiplePostLines", "app.post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
return 0, model.NewAppError("importMultiplePostLines", "app.post.overwrite.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
for _, postWithData := range postsWithData {
|
|
if postWithData.directPostData.FlaggedBy != nil {
|
|
var preferences model.Preferences
|
|
|
|
for _, username := range *postWithData.directPostData.FlaggedBy {
|
|
user := users[username]
|
|
|
|
preferences = append(preferences, model.Preference{
|
|
UserId: user.Id,
|
|
Category: model.PREFERENCE_CATEGORY_FLAGGED_POST,
|
|
Name: postWithData.post.Id,
|
|
Value: "true",
|
|
})
|
|
}
|
|
|
|
if len(preferences) > 0 {
|
|
if err := a.Srv().Store.Preference().Save(&preferences); err != nil {
|
|
return postWithData.lineNumber, model.NewAppError("BulkImport", "app.import.import_post.save_preferences.error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
}
|
|
|
|
if postWithData.directPostData.Reactions != nil {
|
|
for _, reaction := range *postWithData.directPostData.Reactions {
|
|
reaction := reaction
|
|
if err := a.importReaction(&reaction, postWithData.post, dryRun); err != nil {
|
|
return postWithData.lineNumber, err
|
|
}
|
|
}
|
|
}
|
|
|
|
if postWithData.directPostData.Replies != nil {
|
|
if err := a.importReplies(*postWithData.directPostData.Replies, postWithData.post, "noteam", dryRun); err != nil {
|
|
return postWithData.lineNumber, err
|
|
}
|
|
}
|
|
|
|
a.updateFileInfoWithPostId(postWithData.post)
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
func (a *App) importEmoji(data *EmojiImportData, dryRun bool) *model.AppError {
|
|
if err := validateEmojiImportData(data); err != nil {
|
|
return err
|
|
}
|
|
|
|
// If this is a Dry Run, do not continue any further.
|
|
if dryRun {
|
|
return nil
|
|
}
|
|
|
|
var emoji *model.Emoji
|
|
|
|
emoji, err := a.Srv().Store.Emoji().GetByName(*data.Name, true)
|
|
if err != nil {
|
|
var nfErr *store.ErrNotFound
|
|
if !errors.As(err, &nfErr) {
|
|
return model.NewAppError("importEmoji", "app.emoji.get_by_name.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
alreadyExists := emoji != nil
|
|
|
|
if !alreadyExists {
|
|
emoji = &model.Emoji{
|
|
Name: *data.Name,
|
|
}
|
|
emoji.PreSave()
|
|
}
|
|
|
|
file, err := os.Open(*data.Image)
|
|
if err != nil {
|
|
return model.NewAppError("BulkImport", "app.import.emoji.bad_file.error", map[string]interface{}{"EmojiName": *data.Name}, "", http.StatusBadRequest)
|
|
}
|
|
|
|
if _, err := a.WriteFile(file, getEmojiImagePath(emoji.Id)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !alreadyExists {
|
|
if _, err := a.Srv().Store.Emoji().Save(emoji); err != nil {
|
|
return model.NewAppError("importEmoji", "api.emoji.create.internal_error", nil, err.Error(), http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|