2017-04-12 08:27:57 -04:00
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
2017-01-13 13:53:37 -05:00
// See License.txt for license information.
package app
import (
2018-01-22 15:32:50 -06:00
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
2018-12-04 05:08:10 -05:00
"encoding/json"
2017-08-29 16:14:59 -05:00
"fmt"
2017-01-25 09:32:42 -05:00
"net/http"
2017-08-29 16:14:59 -05:00
"strings"
2018-12-17 18:16:57 -05:00
"time"
2017-01-13 13:53:37 -05:00
2018-04-27 12:49:45 -07:00
"github.com/mattermost/mattermost-server/mlog"
2017-09-06 23:05:10 -07:00
"github.com/mattermost/mattermost-server/model"
2018-06-25 12:33:13 -07:00
"github.com/mattermost/mattermost-server/plugin"
2017-09-06 23:05:10 -07:00
"github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/utils"
2017-01-13 13:53:37 -05:00
)
2018-12-17 18:16:57 -05:00
const (
PENDING_POST_IDS_CACHE_SIZE = 25000
PENDING_POST_IDS_CACHE_TTL = 30 * time . Second
)
2018-09-26 16:34:12 +02:00
func ( a * App ) CreatePostAsUser ( post * model . Post , clearPushNotifications bool ) ( * model . Post , * model . AppError ) {
2017-01-25 09:32:42 -05:00
// Check that channel has not been deleted
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Channel ( ) . Get ( post . ChannelId , true )
if result . Err != nil {
2017-09-01 16:42:02 +01:00
err := model . NewAppError ( "CreatePostAsUser" , "api.context.invalid_param.app_error" , map [ string ] interface { } { "Name" : "post.channel_id" } , result . Err . Error ( ) , http . StatusBadRequest )
2017-01-25 09:32:42 -05:00
return nil , err
}
2018-10-19 17:29:39 +02:00
channel := result . Data . ( * model . Channel )
2017-01-25 09:32:42 -05:00
2017-10-09 13:30:48 -04:00
if strings . HasPrefix ( post . Type , model . POST_SYSTEM_MESSAGE_PREFIX ) {
err := model . NewAppError ( "CreatePostAsUser" , "api.context.invalid_param.app_error" , map [ string ] interface { } { "Name" : "post.type" } , "" , http . StatusBadRequest )
return nil , err
}
2017-01-25 09:32:42 -05:00
if channel . DeleteAt != 0 {
2017-09-01 16:42:02 +01:00
err := model . NewAppError ( "createPost" , "api.post.create_post.can_not_post_to_deleted.error" , nil , "" , http . StatusBadRequest )
2017-01-25 09:32:42 -05:00
return nil , err
}
2018-10-19 17:29:39 +02:00
rp , err := a . CreatePost ( post , channel , true )
if err != nil {
2017-01-25 09:32:42 -05:00
if err . Id == "api.post.create_post.root_id.app_error" ||
err . Id == "api.post.create_post.channel_root_id.app_error" ||
err . Id == "api.post.create_post.parent_id.app_error" {
err . StatusCode = http . StatusBadRequest
}
2017-09-01 08:53:55 -05:00
if err . Id == "api.post.create_post.town_square_read_only" {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . User ( ) . Get ( post . UserId )
if result . Err != nil {
2017-09-01 08:53:55 -05:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
user := result . Data . ( * model . User )
2017-09-01 08:53:55 -05:00
T := utils . GetUserTranslations ( user . Locale )
2017-09-27 11:52:34 -05:00
a . SendEphemeralPost (
2017-09-01 08:53:55 -05:00
post . UserId ,
& model . Post {
ChannelId : channel . Id ,
ParentId : post . ParentId ,
RootId : post . RootId ,
UserId : post . UserId ,
Message : T ( "api.post.create_post.town_square_read_only" ) ,
CreateAt : model . GetMillis ( ) + 1 ,
} ,
)
}
2017-01-25 09:32:42 -05:00
return nil , err
2018-10-19 17:29:39 +02:00
}
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
// Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app)
if _ , ok := post . Props [ "from_webhook" ] ; ! ok {
if _ , err := a . MarkChannelsAsViewed ( [ ] string { post . ChannelId } , post . UserId , clearPushNotifications ) ; err != nil {
mlog . Error ( fmt . Sprintf ( "Encountered error updating last viewed, channel_id=%s, user_id=%s, err=%v" , post . ChannelId , post . UserId , err ) )
}
2017-01-25 09:32:42 -05:00
}
2018-10-19 17:29:39 +02:00
return rp , nil
2017-01-25 09:32:42 -05:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) CreatePostMissingChannel ( post * model . Post , triggerWebhooks bool ) ( * model . Post , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Channel ( ) . Get ( post . ChannelId , true )
if result . Err != nil {
2017-08-28 07:08:37 -07:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
channel := result . Data . ( * model . Channel )
2017-08-28 07:08:37 -07:00
2017-09-06 17:12:54 -05:00
return a . CreatePost ( post , channel , triggerWebhooks )
2017-08-28 07:08:37 -07:00
}
2018-12-17 18:16:57 -05:00
// deduplicateCreatePost attempts to make posting idempotent within a caching window.
func ( a * App ) deduplicateCreatePost ( post * model . Post ) ( foundPost * model . Post , err * model . AppError ) {
// We rely on the client sending the pending post id across "duplicate" requests. If there
// isn't one, we can't deduplicate, so allow creation normally.
if post . PendingPostId == "" {
return nil , nil
}
const unknownPostId = ""
// Query the cache atomically for the given pending post id, saving a record if
// it hasn't previously been seen.
value , loaded := a . Srv . seenPendingPostIdsCache . GetOrAdd ( post . PendingPostId , unknownPostId , PENDING_POST_IDS_CACHE_TTL )
// If we were the first thread to save this pending post id into the cache,
// proceed with create post normally.
if ! loaded {
return nil , nil
}
postId := value . ( string )
// If another thread saved the cache record, but hasn't yet updated it with the actual post
// id (because it's still saving), notify the client with an error. Ideally, we'd wait
// for the other thread, but coordinating that adds complexity to the happy path.
if postId == unknownPostId {
return nil , model . NewAppError ( "deduplicateCreatePost" , "api.post.deduplicate_create_post.pending" , nil , "" , http . StatusInternalServerError )
}
// If the other thread finished creating the post, return the created post back to the
// client, making the API call feel idempotent.
actualPost , err := a . GetSinglePost ( postId )
if err != nil {
return nil , model . NewAppError ( "deduplicateCreatePost" , "api.post.deduplicate_create_post.failed_to_get" , nil , err . Error ( ) , http . StatusInternalServerError )
}
mlog . Debug ( "Deduplicated create post" , mlog . String ( "post_id" , actualPost . Id ) , mlog . String ( "pending_post_id" , post . PendingPostId ) )
return actualPost , nil
}
func ( a * App ) CreatePost ( post * model . Post , channel * model . Channel , triggerWebhooks bool ) ( savedPost * model . Post , err * model . AppError ) {
if foundPost , err := a . deduplicateCreatePost ( post ) ; err != nil {
return nil , err
} else if foundPost != nil {
return foundPost , nil
}
// If we get this far, we've recorded the client-provided pending post id to the cache.
// Remove it if we fail below, allowing a proper retry by the client.
defer func ( ) {
if post . PendingPostId == "" {
return
}
if err != nil {
a . Srv . seenPendingPostIdsCache . Remove ( post . PendingPostId )
return
}
a . Srv . seenPendingPostIdsCache . AddWithExpiresInSecs ( post . PendingPostId , savedPost . Id , int64 ( PENDING_POST_IDS_CACHE_TTL . Seconds ( ) ) )
} ( )
2017-10-21 01:38:26 +08:00
post . SanitizeProps ( )
2017-01-13 13:53:37 -05:00
var pchan store . StoreChannel
if len ( post . RootId ) > 0 {
2017-09-06 17:12:54 -05:00
pchan = a . Srv . Store . Post ( ) . Get ( post . RootId )
2017-01-13 13:53:37 -05:00
}
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . User ( ) . Get ( post . UserId )
if result . Err != nil {
2017-09-01 08:53:55 -05:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
user := result . Data . ( * model . User )
2017-09-01 08:53:55 -05:00
2018-02-06 17:25:49 -06:00
if a . License ( ) != nil && * a . Config ( ) . TeamSettings . ExperimentalTownSquareIsReadOnly &&
2017-09-01 08:53:55 -05:00
! post . IsSystemMessage ( ) &&
channel . Name == model . DEFAULT_CHANNEL &&
2018-02-06 15:34:08 +00:00
! a . RolesGrantPermission ( user . GetRoles ( ) , model . PERMISSION_MANAGE_SYSTEM . Id ) {
2017-09-25 13:25:43 +01:00
return nil , model . NewAppError ( "createPost" , "api.post.create_post.town_square_read_only" , nil , "" , http . StatusForbidden )
2017-09-01 08:53:55 -05:00
}
2017-01-13 13:53:37 -05:00
// Verify the parent/child relationships are correct
2017-08-28 07:08:37 -07:00
var parentPostList * model . PostList
2017-01-13 13:53:37 -05:00
if pchan != nil {
2018-10-19 17:29:39 +02:00
result = <- pchan
if result . Err != nil {
2017-09-01 16:42:02 +01:00
return nil , model . NewAppError ( "createPost" , "api.post.create_post.root_id.app_error" , nil , "" , http . StatusBadRequest )
2018-10-19 17:29:39 +02:00
}
parentPostList = result . Data . ( * model . PostList )
if len ( parentPostList . Posts ) == 0 || ! parentPostList . IsChannelId ( post . ChannelId ) {
return nil , model . NewAppError ( "createPost" , "api.post.create_post.channel_root_id.app_error" , nil , "" , http . StatusInternalServerError )
}
2017-01-13 13:53:37 -05:00
2018-10-19 17:29:39 +02:00
if post . ParentId == "" {
post . ParentId = post . RootId
}
2017-01-13 13:53:37 -05:00
2018-10-19 17:29:39 +02:00
if post . RootId != post . ParentId {
parent := parentPostList . Posts [ post . ParentId ]
if parent == nil {
return nil , model . NewAppError ( "createPost" , "api.post.create_post.parent_id.app_error" , nil , "" , http . StatusInternalServerError )
2017-01-13 13:53:37 -05:00
}
}
}
post . Hashtags , _ = model . ParseHashtags ( post . Message )
2017-11-28 15:02:56 -06:00
if err := a . FillInPostProps ( post , channel ) ; err != nil {
return nil , err
}
2018-12-04 05:08:10 -05:00
// Temporary fix so old plugins don't clobber new fields in SlackAttachment struct, see MM-13088
if attachments , ok := post . Props [ "attachments" ] . ( [ ] * model . SlackAttachment ) ; ok {
jsonAttachments , err := json . Marshal ( attachments )
if err == nil {
attachmentsInterface := [ ] interface { } { }
err = json . Unmarshal ( jsonAttachments , & attachmentsInterface )
post . Props [ "attachments" ] = attachmentsInterface
}
if err != nil {
mlog . Error ( "Could not convert post attachments to map interface, err=%s" + err . Error ( ) )
}
}
2018-11-20 08:52:51 -05:00
if pluginsEnvironment := a . GetPluginsEnvironment ( ) ; pluginsEnvironment != nil {
2018-08-03 13:15:51 -04:00
var rejectionError * model . AppError
2018-12-05 10:46:08 -08:00
pluginContext := a . PluginContext ( )
2018-11-20 08:52:51 -05:00
pluginsEnvironment . RunMultiPluginHook ( func ( hooks plugin . Hooks ) bool {
2018-08-03 13:15:51 -04:00
replacementPost , rejectionReason := hooks . MessageWillBePosted ( pluginContext , post )
if rejectionReason != "" {
rejectionError = model . NewAppError ( "createPost" , "Post rejected by plugin. " + rejectionReason , nil , "" , http . StatusBadRequest )
return false
}
if replacementPost != nil {
post = replacementPost
}
return true
2018-06-25 12:33:13 -07:00
} , plugin . MessageWillBePostedId )
2018-08-03 13:15:51 -04:00
if rejectionError != nil {
return nil , rejectionError
2018-05-15 13:33:47 -07:00
}
}
2018-10-19 17:29:39 +02:00
result = <- a . Srv . Store . Post ( ) . Save ( post )
if result . Err != nil {
2017-01-13 13:53:37 -05:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
rpost := result . Data . ( * model . Post )
2017-01-13 13:53:37 -05:00
2018-12-17 18:16:57 -05:00
// Update the mapping from pending post id to the actual post id, for any clients that
// might be duplicating requests.
a . Srv . seenPendingPostIdsCache . AddWithExpiresInSecs ( post . PendingPostId , rpost . Id , int64 ( PENDING_POST_IDS_CACHE_TTL . Seconds ( ) ) )
2018-11-20 08:52:51 -05:00
if pluginsEnvironment := a . GetPluginsEnvironment ( ) ; pluginsEnvironment != nil {
2018-11-07 10:20:07 -08:00
a . Srv . Go ( func ( ) {
2018-12-05 10:46:08 -08:00
pluginContext := a . PluginContext ( )
2018-11-20 08:52:51 -05:00
pluginsEnvironment . RunMultiPluginHook ( func ( hooks plugin . Hooks ) bool {
2018-07-06 06:07:09 -07:00
hooks . MessageHasBeenPosted ( pluginContext , rpost )
2018-06-25 12:33:13 -07:00
return true
} , plugin . MessageHasBeenPostedId )
2018-05-15 13:33:47 -07:00
} )
}
2017-09-19 18:31:35 -05:00
esInterface := a . Elasticsearch
2017-10-18 15:36:43 -07:00
if esInterface != nil && * a . Config ( ) . ElasticsearchSettings . EnableIndexing {
2018-11-07 10:20:07 -08:00
a . Srv . Go ( func ( ) {
2017-10-03 10:53:53 -05:00
esInterface . IndexPost ( rpost , channel . TeamId )
} )
2017-05-18 16:26:52 +01:00
}
2017-09-19 18:31:35 -05:00
if a . Metrics != nil {
a . Metrics . IncrementPostCreate ( )
2017-01-13 13:53:37 -05:00
}
if len ( post . FileIds ) > 0 {
// There's a rare bug where the client sends up duplicate FileIds so protect against that
post . FileIds = utils . RemoveDuplicatesFromStringArray ( post . FileIds )
for _ , fileId := range post . FileIds {
2017-09-06 17:12:54 -05:00
if result := <- a . Srv . Store . FileInfo ( ) . AttachToPost ( fileId , post . Id ) ; result . Err != nil {
2018-04-27 12:49:45 -07:00
mlog . Error ( fmt . Sprintf ( "Encountered error attaching files to post, post_id=%s, user_id=%s, file_ids=%v, err=%v" , post . Id , post . FileIds , post . UserId , result . Err ) , mlog . String ( "post_id" , post . Id ) )
2017-01-13 13:53:37 -05:00
}
}
2017-09-19 18:31:35 -05:00
if a . Metrics != nil {
a . Metrics . IncrementPostFileAttachment ( len ( post . FileIds ) )
2017-01-13 13:53:37 -05:00
}
}
2018-11-23 10:20:02 -05:00
// Normally, we would let the API layer call PreparePostForClient, but we do it here since it also needs
// to be done when we send the post over the websocket in handlePostEvents
rpost = a . PreparePostForClient ( rpost )
2017-09-06 17:12:54 -05:00
if err := a . handlePostEvents ( rpost , user , channel , triggerWebhooks , parentPostList ) ; err != nil {
2018-12-17 18:16:57 -05:00
mlog . Error ( "Failed to handle post events" , mlog . Err ( err ) )
2017-01-13 13:53:37 -05:00
}
return rpost , nil
}
2017-11-28 15:02:56 -06:00
// FillInPostProps should be invoked before saving posts to fill in properties such as
// channel_mentions.
//
// If channel is nil, FillInPostProps will look up the channel corresponding to the post.
func ( a * App ) FillInPostProps ( post * model . Post , channel * model . Channel ) * model . AppError {
channelMentions := post . ChannelMentions ( )
channelMentionsProp := make ( map [ string ] interface { } )
if len ( channelMentions ) > 0 {
if channel == nil {
result := <- a . Srv . Store . Channel ( ) . GetForPost ( post . Id )
2017-12-05 13:18:45 -06:00
if result . Err != nil {
2017-11-28 15:02:56 -06:00
return model . NewAppError ( "FillInPostProps" , "api.context.invalid_param.app_error" , map [ string ] interface { } { "Name" : "post.channel_id" } , result . Err . Error ( ) , http . StatusBadRequest )
}
channel = result . Data . ( * model . Channel )
}
mentionedChannels , err := a . GetChannelsByNames ( channelMentions , channel . TeamId )
if err != nil {
return err
}
for _ , mentioned := range mentionedChannels {
if mentioned . Type == model . CHANNEL_OPEN {
channelMentionsProp [ mentioned . Name ] = map [ string ] interface { } {
"display_name" : mentioned . DisplayName ,
}
}
}
}
if len ( channelMentionsProp ) > 0 {
post . AddProp ( "channel_mentions" , channelMentionsProp )
} else if post . Props != nil {
delete ( post . Props , "channel_mentions" )
}
return nil
}
2017-09-06 17:12:54 -05:00
func ( a * App ) handlePostEvents ( post * model . Post , user * model . User , channel * model . Channel , triggerWebhooks bool , parentPostList * model . PostList ) * model . AppError {
2017-01-13 13:53:37 -05:00
var team * model . Team
2018-10-19 17:29:39 +02:00
if len ( channel . TeamId ) > 0 {
result := <- a . Srv . Store . Team ( ) . Get ( channel . TeamId )
if result . Err != nil {
2017-02-13 10:52:50 -05:00
return result . Err
}
2018-10-19 17:29:39 +02:00
team = result . Data . ( * model . Team )
2017-01-13 13:53:37 -05:00
} else {
2017-02-13 10:52:50 -05:00
// Blank team for DMs
team = & model . Team { }
2017-01-13 13:53:37 -05:00
}
2017-09-06 17:12:54 -05:00
a . InvalidateCacheForChannel ( channel )
a . InvalidateCacheForChannelPosts ( channel . Id )
2017-01-27 14:07:34 -05:00
2017-09-06 17:12:54 -05:00
if _ , err := a . SendNotifications ( post , team , channel , user , parentPostList ) ; err != nil {
2017-01-20 09:43:14 -05:00
return err
}
2017-01-13 13:53:37 -05:00
if triggerWebhooks {
2018-11-07 10:20:07 -08:00
a . Srv . Go ( func ( ) {
2017-09-06 17:12:54 -05:00
if err := a . handleWebhookEvents ( post , team , channel , user ) ; err != nil {
2018-04-27 12:49:45 -07:00
mlog . Error ( err . Error ( ) )
2017-01-13 13:53:37 -05:00
}
2017-10-03 10:53:53 -05:00
} )
2017-01-13 13:53:37 -05:00
}
return nil
}
2017-09-27 11:52:34 -05:00
func ( a * App ) SendEphemeralPost ( userId string , post * model . Post ) * model . Post {
2017-01-13 13:53:37 -05:00
post . Type = model . POST_EPHEMERAL
// fill in fields which haven't been specified which have sensible defaults
if post . Id == "" {
post . Id = model . NewId ( )
}
if post . CreateAt == 0 {
post . CreateAt = model . GetMillis ( )
}
if post . Props == nil {
post . Props = model . StringInterface { }
}
message := model . NewWebSocketEvent ( model . WEBSOCKET_EVENT_EPHEMERAL_MESSAGE , "" , post . ChannelId , userId , nil )
2018-11-19 13:26:40 -05:00
message . Add ( "post" , a . PreparePostForClient ( post ) . ToJson ( ) )
2018-03-02 10:49:18 -06:00
a . Publish ( message )
2017-01-13 13:53:37 -05:00
return post
}
2017-01-25 09:32:42 -05:00
2017-09-06 17:12:54 -05:00
func ( a * App ) UpdatePost ( post * model . Post , safeUpdate bool ) ( * model . Post , * model . AppError ) {
2017-10-21 01:38:26 +08:00
post . SanitizeProps ( )
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . Get ( post . Id )
if result . Err != nil {
2017-01-25 09:32:42 -05:00
return nil , result . Err
2018-10-19 17:29:39 +02:00
}
oldPost := result . Data . ( * model . PostList ) . Posts [ post . Id ]
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
if oldPost == nil {
err := model . NewAppError ( "UpdatePost" , "api.post.update_post.find.app_error" , nil , "id=" + post . Id , http . StatusBadRequest )
return nil , err
}
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
if oldPost . DeleteAt != 0 {
err := model . NewAppError ( "UpdatePost" , "api.post.update_post.permissions_details.app_error" , map [ string ] interface { } { "PostId" : post . Id } , "" , http . StatusBadRequest )
return nil , err
}
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
if oldPost . IsSystemMessage ( ) {
err := model . NewAppError ( "UpdatePost" , "api.post.update_post.system_message.app_error" , nil , "id=" + post . Id , http . StatusBadRequest )
return nil , err
}
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
if a . License ( ) != nil {
if * a . Config ( ) . ServiceSettings . PostEditTimeLimit != - 1 && model . GetMillis ( ) > oldPost . CreateAt + int64 ( * a . Config ( ) . ServiceSettings . PostEditTimeLimit * 1000 ) && post . Message != oldPost . Message {
err := model . NewAppError ( "UpdatePost" , "api.post.update_post.permissions_time_limit.app_error" , map [ string ] interface { } { "timeLimit" : * a . Config ( ) . ServiceSettings . PostEditTimeLimit } , "" , http . StatusBadRequest )
return nil , err
2017-01-25 09:32:42 -05:00
}
}
newPost := & model . Post { }
* newPost = * oldPost
2017-07-04 15:17:54 -04:00
if newPost . Message != post . Message {
newPost . Message = post . Message
newPost . EditAt = model . GetMillis ( )
newPost . Hashtags , _ = model . ParseHashtags ( post . Message )
}
2017-04-04 15:17:47 -04:00
if ! safeUpdate {
newPost . IsPinned = post . IsPinned
newPost . HasReactions = post . HasReactions
newPost . FileIds = post . FileIds
newPost . Props = post . Props
}
2017-01-25 09:32:42 -05:00
2017-11-28 15:02:56 -06:00
if err := a . FillInPostProps ( post , nil ) ; err != nil {
return nil , err
}
2018-11-20 08:52:51 -05:00
if pluginsEnvironment := a . GetPluginsEnvironment ( ) ; pluginsEnvironment != nil {
2018-06-25 12:33:13 -07:00
var rejectionReason string
2018-12-05 10:46:08 -08:00
pluginContext := a . PluginContext ( )
2018-11-20 08:52:51 -05:00
pluginsEnvironment . RunMultiPluginHook ( func ( hooks plugin . Hooks ) bool {
2018-07-06 06:07:09 -07:00
newPost , rejectionReason = hooks . MessageWillBeUpdated ( pluginContext , newPost , oldPost )
2018-06-25 12:33:13 -07:00
return post != nil
} , plugin . MessageWillBeUpdatedId )
if newPost == nil {
return nil , model . NewAppError ( "UpdatePost" , "Post rejected by plugin. " + rejectionReason , nil , "" , http . StatusBadRequest )
2018-05-15 13:33:47 -07:00
}
}
2018-10-19 17:29:39 +02:00
result = <- a . Srv . Store . Post ( ) . Update ( newPost , oldPost )
if result . Err != nil {
2017-01-25 09:32:42 -05:00
return nil , result . Err
2018-10-19 17:29:39 +02:00
}
rpost := result . Data . ( * model . Post )
2018-05-15 13:33:47 -07:00
2018-11-20 08:52:51 -05:00
if pluginsEnvironment := a . GetPluginsEnvironment ( ) ; pluginsEnvironment != nil {
2018-11-07 10:20:07 -08:00
a . Srv . Go ( func ( ) {
2018-12-05 10:46:08 -08:00
pluginContext := a . PluginContext ( )
2018-11-20 08:52:51 -05:00
pluginsEnvironment . RunMultiPluginHook ( func ( hooks plugin . Hooks ) bool {
2018-10-19 17:29:39 +02:00
hooks . MessageHasBeenUpdated ( pluginContext , newPost , oldPost )
return true
} , plugin . MessageHasBeenUpdatedId )
} )
}
esInterface := a . Elasticsearch
if esInterface != nil && * a . Config ( ) . ElasticsearchSettings . EnableIndexing {
2018-11-07 10:20:07 -08:00
a . Srv . Go ( func ( ) {
2018-10-19 17:29:39 +02:00
rchannel := <- a . Srv . Store . Channel ( ) . GetForPost ( rpost . Id )
if rchannel . Err != nil {
mlog . Error ( fmt . Sprintf ( "Couldn't get channel %v for post %v for Elasticsearch indexing." , rpost . ChannelId , rpost . Id ) )
return
}
esInterface . IndexPost ( rpost , rchannel . Data . ( * model . Channel ) . TeamId )
} )
}
2017-05-18 16:26:52 +01:00
2018-12-13 09:47:30 -05:00
rpost = a . PreparePostForClient ( rpost )
2018-10-19 17:29:39 +02:00
a . sendUpdatedPostEvent ( rpost )
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
a . InvalidateCacheForChannelPosts ( rpost . ChannelId )
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
return rpost , nil
2017-01-25 09:32:42 -05:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) PatchPost ( postId string , patch * model . PostPatch ) ( * model . Post , * model . AppError ) {
post , err := a . GetSinglePost ( postId )
2017-03-30 00:06:51 +09:00
if err != nil {
return nil , err
}
post . Patch ( patch )
2017-09-06 17:12:54 -05:00
updatedPost , err := a . UpdatePost ( post , false )
2017-03-30 00:06:51 +09:00
if err != nil {
return nil , err
}
return updatedPost , nil
}
2017-09-27 11:52:34 -05:00
func ( a * App ) sendUpdatedPostEvent ( post * model . Post ) {
2017-03-30 00:06:51 +09:00
message := model . NewWebSocketEvent ( model . WEBSOCKET_EVENT_POST_EDITED , "" , post . ChannelId , "" , nil )
2018-12-13 09:47:30 -05:00
message . Add ( "post" , post . ToJson ( ) )
2018-03-02 10:49:18 -06:00
a . Publish ( message )
2017-03-30 00:06:51 +09:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetPostsPage ( channelId string , page int , perPage int ) ( * model . PostList , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . GetPosts ( channelId , page * perPage , perPage , true )
if result . Err != nil {
2017-02-13 10:52:50 -05:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
return result . Data . ( * model . PostList ) , nil
2017-02-13 10:52:50 -05:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetPosts ( channelId string , offset int , limit int ) ( * model . PostList , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . GetPosts ( channelId , offset , limit , true )
if result . Err != nil {
2017-01-25 09:32:42 -05:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
return result . Data . ( * model . PostList ) , nil
2017-01-25 09:32:42 -05:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetPostsEtag ( channelId string ) string {
return ( <- a . Srv . Store . Post ( ) . GetEtag ( channelId , true ) ) . Data . ( string )
2017-01-25 09:32:42 -05:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetPostsSince ( channelId string , time int64 ) ( * model . PostList , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . GetPostsSince ( channelId , time , true )
if result . Err != nil {
2017-01-25 09:32:42 -05:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
return result . Data . ( * model . PostList ) , nil
2017-01-25 09:32:42 -05:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetSinglePost ( postId string ) ( * model . Post , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . GetSingle ( postId )
if result . Err != nil {
2017-01-25 09:32:42 -05:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
return result . Data . ( * model . Post ) , nil
2017-01-25 09:32:42 -05:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetPostThread ( postId string ) ( * model . PostList , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . Get ( postId )
if result . Err != nil {
2017-01-25 09:32:42 -05:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
return result . Data . ( * model . PostList ) , nil
2017-01-25 09:32:42 -05:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetFlaggedPosts ( userId string , offset int , limit int ) ( * model . PostList , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . GetFlaggedPosts ( userId , offset , limit )
if result . Err != nil {
2017-01-25 09:32:42 -05:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
return result . Data . ( * model . PostList ) , nil
2017-01-25 09:32:42 -05:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetFlaggedPostsForTeam ( userId , teamId string , offset int , limit int ) ( * model . PostList , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . GetFlaggedPostsForTeam ( userId , teamId , offset , limit )
if result . Err != nil {
2017-03-31 12:25:39 -04:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
return result . Data . ( * model . PostList ) , nil
2017-03-31 12:25:39 -04:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetFlaggedPostsForChannel ( userId , channelId string , offset int , limit int ) ( * model . PostList , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . GetFlaggedPostsForChannel ( userId , channelId , offset , limit )
if result . Err != nil {
2017-04-06 05:18:23 +09:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
return result . Data . ( * model . PostList ) , nil
2017-04-06 05:18:23 +09:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetPermalinkPost ( postId string , userId string ) ( * model . PostList , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . Get ( postId )
if result . Err != nil {
2017-01-25 09:32:42 -05:00
return nil , result . Err
2018-10-19 17:29:39 +02:00
}
list := result . Data . ( * model . PostList )
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
if len ( list . Order ) != 1 {
return nil , model . NewAppError ( "getPermalinkTmp" , "api.post_get_post_by_id.get.app_error" , nil , "" , http . StatusNotFound )
}
post := list . Posts [ list . Order [ 0 ] ]
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
channel , err := a . GetChannel ( post . ChannelId )
if err != nil {
return nil , err
}
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
if err = a . JoinChannel ( channel , userId ) ; err != nil {
return nil , err
2017-01-25 09:32:42 -05:00
}
2018-10-19 17:29:39 +02:00
return list , nil
2017-01-25 09:32:42 -05:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetPostsBeforePost ( channelId , postId string , page , perPage int ) ( * model . PostList , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . GetPostsBefore ( channelId , postId , perPage , page * perPage )
if result . Err != nil {
2017-03-24 16:46:11 -04:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
return result . Data . ( * model . PostList ) , nil
2017-03-24 16:46:11 -04:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetPostsAfterPost ( channelId , postId string , page , perPage int ) ( * model . PostList , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . GetPostsAfter ( channelId , postId , perPage , page * perPage )
if result . Err != nil {
2017-03-24 16:46:11 -04:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
return result . Data . ( * model . PostList ) , nil
2017-03-24 16:46:11 -04:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetPostsAroundPost ( postId , channelId string , offset , limit int , before bool ) ( * model . PostList , * model . AppError ) {
2017-01-25 09:32:42 -05:00
var pchan store . StoreChannel
if before {
2017-09-06 17:12:54 -05:00
pchan = a . Srv . Store . Post ( ) . GetPostsBefore ( channelId , postId , limit , offset )
2017-01-25 09:32:42 -05:00
} else {
2017-09-06 17:12:54 -05:00
pchan = a . Srv . Store . Post ( ) . GetPostsAfter ( channelId , postId , limit , offset )
2017-01-25 09:32:42 -05:00
}
2018-10-19 17:29:39 +02:00
result := <- pchan
if result . Err != nil {
2017-01-25 09:32:42 -05:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
return result . Data . ( * model . PostList ) , nil
2017-01-25 09:32:42 -05:00
}
2018-06-01 12:45:46 -04:00
func ( a * App ) DeletePost ( postId , deleteByID string ) ( * model . Post , * model . AppError ) {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . GetSingle ( postId )
if result . Err != nil {
2017-02-21 07:36:52 -05:00
result . Err . StatusCode = http . StatusBadRequest
2017-01-25 09:32:42 -05:00
return nil , result . Err
2018-10-19 17:29:39 +02:00
}
post := result . Data . ( * model . Post )
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
if result := <- a . Srv . Store . Post ( ) . Delete ( postId , model . GetMillis ( ) , deleteByID ) ; result . Err != nil {
return nil , result . Err
}
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
message := model . NewWebSocketEvent ( model . WEBSOCKET_EVENT_POST_DELETED , "" , post . ChannelId , "" , nil )
2018-11-19 13:26:40 -05:00
message . Add ( "post" , a . PreparePostForClient ( post ) . ToJson ( ) )
2018-10-19 17:29:39 +02:00
a . Publish ( message )
2017-01-25 09:32:42 -05:00
2018-11-07 10:20:07 -08:00
a . Srv . Go ( func ( ) {
2018-10-19 17:29:39 +02:00
a . DeletePostFiles ( post )
} )
2018-11-07 10:20:07 -08:00
a . Srv . Go ( func ( ) {
2018-10-19 17:29:39 +02:00
a . DeleteFlaggedPosts ( post . Id )
} )
esInterface := a . Elasticsearch
if esInterface != nil && * a . Config ( ) . ElasticsearchSettings . EnableIndexing {
2018-11-07 10:20:07 -08:00
a . Srv . Go ( func ( ) {
2018-10-19 17:29:39 +02:00
esInterface . DeletePost ( post )
2017-10-03 10:53:53 -05:00
} )
2018-10-19 17:29:39 +02:00
}
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
a . InvalidateCacheForChannelPosts ( post . ChannelId )
2017-01-25 09:32:42 -05:00
2018-10-19 17:29:39 +02:00
return post , nil
2017-01-25 09:32:42 -05:00
}
2017-09-06 17:12:54 -05:00
func ( a * App ) DeleteFlaggedPosts ( postId string ) {
if result := <- a . Srv . Store . Preference ( ) . DeleteCategoryAndName ( model . PREFERENCE_CATEGORY_FLAGGED_POST , postId ) ; result . Err != nil {
2018-04-27 12:49:45 -07:00
mlog . Warn ( fmt . Sprintf ( "Unable to delete flagged post preference when deleting post, err=%v" , result . Err ) )
2017-01-25 09:32:42 -05:00
return
}
}
2017-09-06 17:12:54 -05:00
func ( a * App ) DeletePostFiles ( post * model . Post ) {
2018-11-14 18:01:44 +00:00
if len ( post . FileIds ) == 0 {
2017-01-25 09:32:42 -05:00
return
}
2017-09-06 17:12:54 -05:00
if result := <- a . Srv . Store . FileInfo ( ) . DeleteForPost ( post . Id ) ; result . Err != nil {
2018-04-27 12:49:45 -07:00
mlog . Warn ( fmt . Sprintf ( "Encountered error when deleting files for post, post_id=%v, err=%v" , post . Id , result . Err ) , mlog . String ( "post_id" , post . Id ) )
2017-01-25 09:32:42 -05:00
}
}
2018-09-27 16:15:41 +02:00
func ( a * App ) parseAndFetchChannelIdByNameFromInFilter ( channelName , userId , teamId string , includeDeleted bool ) ( * model . Channel , error ) {
if strings . HasPrefix ( channelName , "@" ) && strings . Contains ( channelName , "," ) {
var userIds [ ] string
users , err := a . GetUsersByUsernames ( strings . Split ( channelName [ 1 : ] , "," ) , false )
if err != nil {
return nil , err
}
for _ , user := range users {
userIds = append ( userIds , user . Id )
}
channel , err := a . GetGroupChannel ( userIds )
if err != nil {
return nil , err
}
return channel , nil
}
if strings . HasPrefix ( channelName , "@" ) && ! strings . Contains ( channelName , "," ) {
user , err := a . GetUserByUsername ( channelName [ 1 : ] )
if err != nil {
return nil , err
}
2018-11-28 18:01:49 +01:00
channel , err := a . GetOrCreateDirectChannel ( userId , user . Id )
2018-09-27 16:15:41 +02:00
if err != nil {
return nil , err
}
return channel , nil
}
channel , err := a . GetChannelByName ( channelName , teamId , includeDeleted )
if err != nil {
return nil , err
}
return channel , nil
}
2018-09-26 07:27:04 -07:00
func ( a * App ) SearchPostsInTeam ( terms string , userId string , teamId string , isOrSearch bool , includeDeletedChannels bool , timeZoneOffset int , page , perPage int ) ( * model . PostSearchResults , * model . AppError ) {
2018-08-28 13:09:32 -04:00
paramsList := model . ParseSearchParams ( terms , timeZoneOffset )
2018-08-22 20:12:51 +01:00
includeDeleted := includeDeletedChannels && * a . Config ( ) . TeamSettings . ExperimentalViewArchivedChannels
2017-01-25 09:32:42 -05:00
2017-09-19 18:31:35 -05:00
esInterface := a . Elasticsearch
2018-10-19 17:29:39 +02:00
license := a . License ( )
if esInterface != nil && * a . Config ( ) . ElasticsearchSettings . EnableSearching && license != nil && * license . Features . Elasticsearch {
2017-05-18 16:26:52 +01:00
finalParamsList := [ ] * model . SearchParams { }
for _ , params := range paramsList {
params . OrTerms = isOrSearch
// Don't allow users to search for "*"
if params . Terms != "*" {
// Convert channel names to channel IDs
for idx , channelName := range params . InChannels {
2018-09-27 16:15:41 +02:00
channel , err := a . parseAndFetchChannelIdByNameFromInFilter ( channelName , userId , teamId , includeDeletedChannels )
if err != nil {
2018-04-27 12:49:45 -07:00
mlog . Error ( fmt . Sprint ( err ) )
2018-09-27 16:15:41 +02:00
continue
2017-05-18 16:26:52 +01:00
}
2018-09-27 16:15:41 +02:00
params . InChannels [ idx ] = channel . Id
2017-05-18 16:26:52 +01:00
}
// Convert usernames to user IDs
for idx , username := range params . FromUsers {
2017-09-06 17:12:54 -05:00
if user , err := a . GetUserByUsername ( username ) ; err != nil {
2018-04-27 12:49:45 -07:00
mlog . Error ( fmt . Sprint ( err ) )
2017-05-18 16:26:52 +01:00
} else {
params . FromUsers [ idx ] = user . Id
}
}
finalParamsList = append ( finalParamsList , params )
}
2017-01-25 09:32:42 -05:00
}
2017-07-31 19:45:35 +01:00
// If the processed search params are empty, return empty search results.
if len ( finalParamsList ) == 0 {
2018-06-19 05:46:29 -04:00
return model . MakePostSearchResults ( model . NewPostList ( ) , nil ) , nil
2017-07-31 19:45:35 +01:00
}
2017-05-18 16:26:52 +01:00
// We only allow the user to search in channels they are a member of.
2018-07-30 15:06:08 -04:00
userChannels , err := a . GetChannelsForUser ( teamId , userId , includeDeleted )
2017-05-18 16:26:52 +01:00
if err != nil {
2018-04-27 12:49:45 -07:00
mlog . Error ( fmt . Sprint ( err ) )
2017-05-18 16:26:52 +01:00
return nil , err
}
2018-09-26 07:27:04 -07:00
postIds , matches , err := a . Elasticsearch . SearchPosts ( userChannels , finalParamsList , page , perPage )
2017-05-18 16:26:52 +01:00
if err != nil {
return nil , err
}
// Get the posts
postList := model . NewPostList ( )
2017-10-09 18:14:27 +01:00
if len ( postIds ) > 0 {
2018-10-19 17:29:39 +02:00
presult := <- a . Srv . Store . Post ( ) . GetPostsByIds ( postIds )
if presult . Err != nil {
2017-10-09 18:14:27 +01:00
return nil , presult . Err
2018-10-19 17:29:39 +02:00
}
for _ , p := range presult . Data . ( [ ] * model . Post ) {
if p . DeleteAt == 0 {
postList . AddPost ( p )
postList . AddOrder ( p . Id )
2017-10-09 18:14:27 +01:00
}
2017-05-18 16:26:52 +01:00
}
2017-01-25 09:32:42 -05:00
}
2018-06-19 05:46:29 -04:00
return model . MakePostSearchResults ( postList , matches ) , nil
2018-10-19 17:29:39 +02:00
}
2017-10-09 10:16:14 -07:00
2018-10-19 17:29:39 +02:00
if ! * a . Config ( ) . ServiceSettings . EnablePostSearch {
return nil , model . NewAppError ( "SearchPostsInTeam" , "store.sql_post.search.disabled" , nil , fmt . Sprintf ( "teamId=%v userId=%v" , teamId , userId ) , http . StatusNotImplemented )
}
2018-09-27 12:11:19 -07:00
2018-10-19 17:29:39 +02:00
// Since we don't support paging we just return nothing for later pages
if page > 0 {
return model . MakePostSearchResults ( model . NewPostList ( ) , nil ) , nil
}
2017-05-18 16:26:52 +01:00
2018-10-19 17:29:39 +02:00
channels := [ ] store . StoreChannel { }
for _ , params := range paramsList {
params . IncludeDeletedChannels = includeDeleted
params . OrTerms = isOrSearch
// don't allow users to search for everything
if params . Terms != "*" {
for idx , channelName := range params . InChannels {
if strings . HasPrefix ( channelName , "@" ) {
channel , err := a . parseAndFetchChannelIdByNameFromInFilter ( channelName , userId , teamId , includeDeletedChannels )
if err != nil {
mlog . Error ( fmt . Sprint ( err ) )
continue
2018-09-27 16:15:41 +02:00
}
2018-10-19 17:29:39 +02:00
params . InChannels [ idx ] = channel . Name
2018-09-27 16:15:41 +02:00
}
2017-05-18 16:26:52 +01:00
}
2018-10-19 17:29:39 +02:00
channels = append ( channels , a . Srv . Store . Post ( ) . Search ( teamId , userId , params ) )
2017-05-18 16:26:52 +01:00
}
2018-10-19 17:29:39 +02:00
}
2017-05-18 16:26:52 +01:00
2018-10-19 17:29:39 +02:00
posts := model . NewPostList ( )
for _ , channel := range channels {
result := <- channel
if result . Err != nil {
return nil , result . Err
2017-05-18 16:26:52 +01:00
}
2018-10-19 17:29:39 +02:00
data := result . Data . ( * model . PostList )
posts . Extend ( data )
}
2017-05-18 16:26:52 +01:00
2018-10-19 17:29:39 +02:00
posts . SortByCreateAt ( )
2017-10-24 19:27:15 +01:00
2018-10-19 17:29:39 +02:00
return model . MakePostSearchResults ( posts , nil ) , nil
2017-01-25 09:32:42 -05:00
}
2018-11-26 05:47:01 -05:00
func ( a * App ) GetFileInfosForPostWithMigration ( postId string ) ( [ ] * model . FileInfo , * model . AppError ) {
2017-09-06 17:12:54 -05:00
pchan := a . Srv . Store . Post ( ) . GetSingle ( postId )
2017-01-25 09:32:42 -05:00
2018-11-26 05:47:01 -05:00
infos , err := a . GetFileInfosForPost ( postId )
if err != nil {
return nil , err
2017-01-25 09:32:42 -05:00
}
if len ( infos ) == 0 {
// No FileInfos were returned so check if they need to be created for this post
2018-10-19 17:29:39 +02:00
result := <- pchan
if result . Err != nil {
2017-01-25 09:32:42 -05:00
return nil , result . Err
}
2018-10-19 17:29:39 +02:00
post := result . Data . ( * model . Post )
2017-01-25 09:32:42 -05:00
if len ( post . Filenames ) > 0 {
2017-09-06 17:12:54 -05:00
a . Srv . Store . FileInfo ( ) . InvalidateFileInfosForPostCache ( postId )
2017-01-25 09:32:42 -05:00
// The post has Filenames that need to be replaced with FileInfos
2017-09-06 17:12:54 -05:00
infos = a . MigrateFilenamesToFileInfos ( post )
2017-01-25 09:32:42 -05:00
}
}
return infos , nil
}
2018-11-26 05:47:01 -05:00
func ( a * App ) GetFileInfosForPost ( postId string ) ( [ ] * model . FileInfo , * model . AppError ) {
result := <- a . Srv . Store . FileInfo ( ) . GetForPost ( postId , false , true )
if result . Err != nil {
return nil , result . Err
}
return result . Data . ( [ ] * model . FileInfo ) , nil
}
2018-01-22 15:32:50 -06:00
func ( a * App ) PostWithProxyAddedToImageURLs ( post * model . Post ) * model . Post {
if f := a . ImageProxyAdder ( ) ; f != nil {
return post . WithRewrittenImageURLs ( f )
}
return post
}
func ( a * App ) PostWithProxyRemovedFromImageURLs ( post * model . Post ) * model . Post {
if f := a . ImageProxyRemover ( ) ; f != nil {
return post . WithRewrittenImageURLs ( f )
}
return post
}
func ( a * App ) PostPatchWithProxyRemovedFromImageURLs ( patch * model . PostPatch ) * model . PostPatch {
if f := a . ImageProxyRemover ( ) ; f != nil {
return patch . WithRewrittenImageURLs ( f )
}
return patch
}
func ( a * App ) imageProxyConfig ( ) ( proxyType , proxyURL , options , siteURL string ) {
cfg := a . Config ( )
if cfg . ServiceSettings . ImageProxyURL == nil || cfg . ServiceSettings . ImageProxyType == nil || cfg . ServiceSettings . SiteURL == nil {
return
}
proxyURL = * cfg . ServiceSettings . ImageProxyURL
proxyType = * cfg . ServiceSettings . ImageProxyType
siteURL = * cfg . ServiceSettings . SiteURL
if proxyURL == "" || proxyType == "" {
return "" , "" , "" , ""
}
if proxyURL [ len ( proxyURL ) - 1 ] != '/' {
proxyURL += "/"
}
2018-02-09 15:41:06 -06:00
if siteURL == "" || siteURL [ len ( siteURL ) - 1 ] != '/' {
siteURL += "/"
}
2018-01-22 15:32:50 -06:00
if cfg . ServiceSettings . ImageProxyOptions != nil {
options = * cfg . ServiceSettings . ImageProxyOptions
}
return
}
func ( a * App ) ImageProxyAdder ( ) func ( string ) string {
proxyType , proxyURL , options , siteURL := a . imageProxyConfig ( )
if proxyType == "" {
return nil
}
return func ( url string ) string {
2018-02-09 20:08:39 -06:00
if url == "" || url [ 0 ] == '/' || strings . HasPrefix ( url , siteURL ) || strings . HasPrefix ( url , proxyURL ) {
2018-01-22 15:32:50 -06:00
return url
}
switch proxyType {
case "atmos/camo" :
mac := hmac . New ( sha1 . New , [ ] byte ( options ) )
mac . Write ( [ ] byte ( url ) )
digest := hex . EncodeToString ( mac . Sum ( nil ) )
return proxyURL + digest + "/" + hex . EncodeToString ( [ ] byte ( url ) )
}
return url
}
}
func ( a * App ) ImageProxyRemover ( ) ( f func ( string ) string ) {
proxyType , proxyURL , _ , _ := a . imageProxyConfig ( )
if proxyType == "" {
return nil
}
return func ( url string ) string {
switch proxyType {
case "atmos/camo" :
if strings . HasPrefix ( url , proxyURL ) {
if slash := strings . IndexByte ( url [ len ( proxyURL ) : ] , '/' ) ; slash >= 0 {
if decoded , err := hex . DecodeString ( url [ len ( proxyURL ) + slash + 1 : ] ) ; err == nil {
return string ( decoded )
}
}
}
}
return url
}
}
Relax 4k post message limit (#8478)
* MM-9661: rename POST_MESSAGE_MAX_RUNES to \0_v1
* MM-9661: s/4000/POST_MESSAGE_MAX_RUNES_V1/ in tests
* MM-9661: introduce POST_MESSAGE_MAX_RUNES_V2
* MM-9661: migrate Postgres Posts.Message column to TEXT from VARCHAR(4000)
This is safe to do in a production instance since the underyling type is
not changing. We explicitly don't do this automatically for MySQL, but
also don't need to since the ORM would have already created a TEXT column
for MySQL in that case.
* MM-9661: emit MaxPostSize in client config
This value remains unconfigurable at this time, but exposes the current
limit to the client. The limit remains at 4k in this commit.
* MM-9661: introduce and use SqlPostStore.GetMaxPostSize
Enforce a byte limitation in the database, and use 1/4 of that value as
the rune count limitation (assuming a worst case UTF-8 representation).
* move maxPostSizeCached, lastPostsCache and lastPostTimeCache out of the global context and onto the SqlPostStore
* address feedback from code review:
* ensure sqlstore unit tests are actually being run
* move global caches into SqlPostStore
* leverage sync.Once to address a race condition
* modify upgrade semantics to match new db semantics
gorp's behaviour on creating columns with a maximum length on Postgres
differs from MySQL:
* Postgres
* gorp uses TEXT for string columns without a maximum length
* gorp uses VARCHAR(N) for string columns with a maximum length of N
* MySQL
* gorp uses TEXT for string columns with a maximum length >= 256
* gorp uses VARCHAR(N) for string columns with a maximum length of N
* gorp defaults to a maximum length of 255, implying VARCHAR(255)
So the Message column has been TEXT on MySQL but VARCHAR(4000) on
Postgres. With the new, longer limits of 65535, and without changes to
gorp, the expected behaviour is TEXT on MySQL and VARCHAR(65535) on
Postgres. This commit makes the upgrade semantics match the new database
semantics.
Ideally, we'd revisit the gorp behaviour at a later time.
* allow TestMaxPostSize test cases to actually run in parallel
* default maxPostSizeCached to POST_MESSAGE_MAX_RUNES_V1 in case the once initializer panics
* fix casting error
* MM-9661: skip the schema migration for Postgres
It turns out resizing VARCHAR requires a rewrite in some versions of
Postgres, but migrating VARCHAR to TEXT does not. Given the increasing
complexity, let's defer the migration to the enduser instead.
2018-03-26 17:55:35 -04:00
func ( a * App ) MaxPostSize ( ) int {
2018-10-19 17:29:39 +02:00
result := <- a . Srv . Store . Post ( ) . GetMaxPostSize ( )
if result . Err != nil {
2018-04-27 12:49:45 -07:00
mlog . Error ( fmt . Sprint ( result . Err ) )
2018-10-19 17:29:39 +02:00
return model . POST_MESSAGE_MAX_RUNES_V1
Relax 4k post message limit (#8478)
* MM-9661: rename POST_MESSAGE_MAX_RUNES to \0_v1
* MM-9661: s/4000/POST_MESSAGE_MAX_RUNES_V1/ in tests
* MM-9661: introduce POST_MESSAGE_MAX_RUNES_V2
* MM-9661: migrate Postgres Posts.Message column to TEXT from VARCHAR(4000)
This is safe to do in a production instance since the underyling type is
not changing. We explicitly don't do this automatically for MySQL, but
also don't need to since the ORM would have already created a TEXT column
for MySQL in that case.
* MM-9661: emit MaxPostSize in client config
This value remains unconfigurable at this time, but exposes the current
limit to the client. The limit remains at 4k in this commit.
* MM-9661: introduce and use SqlPostStore.GetMaxPostSize
Enforce a byte limitation in the database, and use 1/4 of that value as
the rune count limitation (assuming a worst case UTF-8 representation).
* move maxPostSizeCached, lastPostsCache and lastPostTimeCache out of the global context and onto the SqlPostStore
* address feedback from code review:
* ensure sqlstore unit tests are actually being run
* move global caches into SqlPostStore
* leverage sync.Once to address a race condition
* modify upgrade semantics to match new db semantics
gorp's behaviour on creating columns with a maximum length on Postgres
differs from MySQL:
* Postgres
* gorp uses TEXT for string columns without a maximum length
* gorp uses VARCHAR(N) for string columns with a maximum length of N
* MySQL
* gorp uses TEXT for string columns with a maximum length >= 256
* gorp uses VARCHAR(N) for string columns with a maximum length of N
* gorp defaults to a maximum length of 255, implying VARCHAR(255)
So the Message column has been TEXT on MySQL but VARCHAR(4000) on
Postgres. With the new, longer limits of 65535, and without changes to
gorp, the expected behaviour is TEXT on MySQL and VARCHAR(65535) on
Postgres. This commit makes the upgrade semantics match the new database
semantics.
Ideally, we'd revisit the gorp behaviour at a later time.
* allow TestMaxPostSize test cases to actually run in parallel
* default maxPostSizeCached to POST_MESSAGE_MAX_RUNES_V1 in case the once initializer panics
* fix casting error
* MM-9661: skip the schema migration for Postgres
It turns out resizing VARCHAR requires a rewrite in some versions of
Postgres, but migrating VARCHAR to TEXT does not. Given the increasing
complexity, let's defer the migration to the enduser instead.
2018-03-26 17:55:35 -04:00
}
2018-10-19 17:29:39 +02:00
return result . Data . ( int )
Relax 4k post message limit (#8478)
* MM-9661: rename POST_MESSAGE_MAX_RUNES to \0_v1
* MM-9661: s/4000/POST_MESSAGE_MAX_RUNES_V1/ in tests
* MM-9661: introduce POST_MESSAGE_MAX_RUNES_V2
* MM-9661: migrate Postgres Posts.Message column to TEXT from VARCHAR(4000)
This is safe to do in a production instance since the underyling type is
not changing. We explicitly don't do this automatically for MySQL, but
also don't need to since the ORM would have already created a TEXT column
for MySQL in that case.
* MM-9661: emit MaxPostSize in client config
This value remains unconfigurable at this time, but exposes the current
limit to the client. The limit remains at 4k in this commit.
* MM-9661: introduce and use SqlPostStore.GetMaxPostSize
Enforce a byte limitation in the database, and use 1/4 of that value as
the rune count limitation (assuming a worst case UTF-8 representation).
* move maxPostSizeCached, lastPostsCache and lastPostTimeCache out of the global context and onto the SqlPostStore
* address feedback from code review:
* ensure sqlstore unit tests are actually being run
* move global caches into SqlPostStore
* leverage sync.Once to address a race condition
* modify upgrade semantics to match new db semantics
gorp's behaviour on creating columns with a maximum length on Postgres
differs from MySQL:
* Postgres
* gorp uses TEXT for string columns without a maximum length
* gorp uses VARCHAR(N) for string columns with a maximum length of N
* MySQL
* gorp uses TEXT for string columns with a maximum length >= 256
* gorp uses VARCHAR(N) for string columns with a maximum length of N
* gorp defaults to a maximum length of 255, implying VARCHAR(255)
So the Message column has been TEXT on MySQL but VARCHAR(4000) on
Postgres. With the new, longer limits of 65535, and without changes to
gorp, the expected behaviour is TEXT on MySQL and VARCHAR(65535) on
Postgres. This commit makes the upgrade semantics match the new database
semantics.
Ideally, we'd revisit the gorp behaviour at a later time.
* allow TestMaxPostSize test cases to actually run in parallel
* default maxPostSizeCached to POST_MESSAGE_MAX_RUNES_V1 in case the once initializer panics
* fix casting error
* MM-9661: skip the schema migration for Postgres
It turns out resizing VARCHAR requires a rewrite in some versions of
Postgres, but migrating VARCHAR to TEXT does not. Given the increasing
complexity, let's defer the migration to the enduser instead.
2018-03-26 17:55:35 -04:00
}