github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+incompatible/api/post.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package api
     5  
     6  import (
     7  	"net/http"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/gorilla/mux"
    12  
    13  	"github.com/mattermost/mattermost-server/model"
    14  	"github.com/mattermost/mattermost-server/utils"
    15  )
    16  
    17  const OPEN_GRAPH_METADATA_CACHE_SIZE = 10000
    18  
    19  var openGraphDataCache = utils.NewLru(OPEN_GRAPH_METADATA_CACHE_SIZE)
    20  
    21  func (api *API) InitPost() {
    22  	api.BaseRoutes.ApiRoot.Handle("/get_opengraph_metadata", api.ApiUserRequired(getOpenGraphMetadata)).Methods("POST")
    23  
    24  	api.BaseRoutes.NeedTeam.Handle("/posts/search", api.ApiUserRequiredActivity(searchPosts, true)).Methods("POST")
    25  	api.BaseRoutes.NeedTeam.Handle("/posts/flagged/{offset:[0-9]+}/{limit:[0-9]+}", api.ApiUserRequired(getFlaggedPosts)).Methods("GET")
    26  	api.BaseRoutes.NeedTeam.Handle("/posts/{post_id}", api.ApiUserRequired(getPostById)).Methods("GET")
    27  	api.BaseRoutes.NeedTeam.Handle("/pltmp/{post_id}", api.ApiUserRequired(getPermalinkTmp)).Methods("GET")
    28  
    29  	api.BaseRoutes.Posts.Handle("/create", api.ApiUserRequiredActivity(createPost, true)).Methods("POST")
    30  	api.BaseRoutes.Posts.Handle("/update", api.ApiUserRequiredActivity(updatePost, true)).Methods("POST")
    31  	api.BaseRoutes.Posts.Handle("/page/{offset:[0-9]+}/{limit:[0-9]+}", api.ApiUserRequired(getPosts)).Methods("GET")
    32  	api.BaseRoutes.Posts.Handle("/since/{time:[0-9]+}", api.ApiUserRequired(getPostsSince)).Methods("GET")
    33  
    34  	api.BaseRoutes.NeedPost.Handle("/get", api.ApiUserRequired(getPost)).Methods("GET")
    35  	api.BaseRoutes.NeedPost.Handle("/delete", api.ApiUserRequiredActivity(deletePost, true)).Methods("POST")
    36  	api.BaseRoutes.NeedPost.Handle("/before/{offset:[0-9]+}/{num_posts:[0-9]+}", api.ApiUserRequired(getPostsBefore)).Methods("GET")
    37  	api.BaseRoutes.NeedPost.Handle("/after/{offset:[0-9]+}/{num_posts:[0-9]+}", api.ApiUserRequired(getPostsAfter)).Methods("GET")
    38  	api.BaseRoutes.NeedPost.Handle("/get_file_infos", api.ApiUserRequired(getFileInfosForPost)).Methods("GET")
    39  	api.BaseRoutes.NeedPost.Handle("/pin", api.ApiUserRequired(pinPost)).Methods("POST")
    40  	api.BaseRoutes.NeedPost.Handle("/unpin", api.ApiUserRequired(unpinPost)).Methods("POST")
    41  }
    42  
    43  func createPost(c *Context, w http.ResponseWriter, r *http.Request) {
    44  	post := model.PostFromJson(r.Body)
    45  	if post == nil {
    46  		c.SetInvalidParam("createPost", "post")
    47  		return
    48  	}
    49  
    50  	post.UserId = c.Session.UserId
    51  
    52  	hasPermission := false
    53  	if c.App.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_CREATE_POST) {
    54  		hasPermission = true
    55  	} else if channel, err := c.App.GetChannel(post.ChannelId); err == nil {
    56  		// Temporary permission check method until advanced permissions, please do not copy
    57  		if channel.Type == model.CHANNEL_OPEN && c.App.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_CREATE_POST_PUBLIC) {
    58  			hasPermission = true
    59  		}
    60  	}
    61  
    62  	if !hasPermission {
    63  		c.SetPermissionError(model.PERMISSION_CREATE_POST)
    64  		return
    65  	}
    66  
    67  	if post.CreateAt != 0 && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
    68  		post.CreateAt = 0
    69  	}
    70  
    71  	rp, err := c.App.CreatePostAsUser(post)
    72  	if err != nil {
    73  		c.Err = err
    74  		return
    75  	}
    76  
    77  	w.Write([]byte(rp.ToJson()))
    78  }
    79  
    80  func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
    81  	post := model.PostFromJson(r.Body)
    82  
    83  	if post == nil {
    84  		c.SetInvalidParam("updatePost", "post")
    85  		return
    86  	}
    87  
    88  	if !c.App.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_EDIT_POST) {
    89  		c.SetPermissionError(model.PERMISSION_EDIT_POST)
    90  		return
    91  	}
    92  
    93  	post.UserId = c.Session.UserId
    94  
    95  	rpost, err := c.App.UpdatePost(post, true)
    96  	if err != nil {
    97  		c.Err = err
    98  		return
    99  	}
   100  
   101  	w.Write([]byte(rpost.ToJson()))
   102  }
   103  
   104  func saveIsPinnedPost(c *Context, w http.ResponseWriter, r *http.Request, isPinned bool) {
   105  	params := mux.Vars(r)
   106  
   107  	channelId := params["channel_id"]
   108  	if len(channelId) != 26 {
   109  		c.SetInvalidParam("savedIsPinnedPost", "channelId")
   110  		return
   111  	}
   112  
   113  	postId := params["post_id"]
   114  	if len(postId) != 26 {
   115  		c.SetInvalidParam("savedIsPinnedPost", "postId")
   116  		return
   117  	}
   118  
   119  	pchan := c.App.Srv.Store.Post().Get(postId)
   120  
   121  	var oldPost *model.Post
   122  	if result := <-pchan; result.Err != nil {
   123  		c.Err = result.Err
   124  		return
   125  	} else {
   126  		oldPost = result.Data.(*model.PostList).Posts[postId]
   127  		newPost := &model.Post{}
   128  		*newPost = *oldPost
   129  		newPost.IsPinned = isPinned
   130  
   131  		if result := <-c.App.Srv.Store.Post().Update(newPost, oldPost); result.Err != nil {
   132  			c.Err = result.Err
   133  			return
   134  		} else {
   135  			rpost := result.Data.(*model.Post)
   136  
   137  			message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", rpost.ChannelId, "", nil)
   138  			message.Add("post", c.App.PostWithProxyAddedToImageURLs(rpost).ToJson())
   139  
   140  			c.App.Go(func() {
   141  				c.App.Publish(message)
   142  			})
   143  
   144  			c.App.InvalidateCacheForChannelPosts(rpost.ChannelId)
   145  
   146  			w.Write([]byte(rpost.ToJson()))
   147  		}
   148  	}
   149  }
   150  
   151  func pinPost(c *Context, w http.ResponseWriter, r *http.Request) {
   152  	saveIsPinnedPost(c, w, r, true)
   153  }
   154  
   155  func unpinPost(c *Context, w http.ResponseWriter, r *http.Request) {
   156  	saveIsPinnedPost(c, w, r, false)
   157  }
   158  
   159  func getFlaggedPosts(c *Context, w http.ResponseWriter, r *http.Request) {
   160  	params := mux.Vars(r)
   161  
   162  	offset, err := strconv.Atoi(params["offset"])
   163  	if err != nil {
   164  		c.SetInvalidParam("getFlaggedPosts", "offset")
   165  		return
   166  	}
   167  
   168  	limit, err := strconv.Atoi(params["limit"])
   169  	if err != nil {
   170  		c.SetInvalidParam("getFlaggedPosts", "limit")
   171  		return
   172  	}
   173  
   174  	if !c.App.SessionHasPermissionToTeam(c.Session, c.TeamId, model.PERMISSION_VIEW_TEAM) {
   175  		c.SetPermissionError(model.PERMISSION_VIEW_TEAM)
   176  		return
   177  	}
   178  
   179  	if posts, err := c.App.GetFlaggedPostsForTeam(c.Session.UserId, c.TeamId, offset, limit); err != nil {
   180  		c.Err = err
   181  		return
   182  	} else {
   183  		w.Write([]byte(posts.ToJson()))
   184  	}
   185  }
   186  
   187  func getPosts(c *Context, w http.ResponseWriter, r *http.Request) {
   188  	params := mux.Vars(r)
   189  
   190  	id := params["channel_id"]
   191  	if len(id) != 26 {
   192  		c.SetInvalidParam("getPosts", "channelId")
   193  		return
   194  	}
   195  
   196  	offset, err := strconv.Atoi(params["offset"])
   197  	if err != nil {
   198  		c.SetInvalidParam("getPosts", "offset")
   199  		return
   200  	}
   201  
   202  	limit, err := strconv.Atoi(params["limit"])
   203  	if err != nil {
   204  		c.SetInvalidParam("getPosts", "limit")
   205  		return
   206  	}
   207  
   208  	if !c.App.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_CREATE_POST) {
   209  		c.SetPermissionError(model.PERMISSION_CREATE_POST)
   210  		return
   211  	}
   212  
   213  	etag := c.App.GetPostsEtag(id)
   214  
   215  	if c.HandleEtag(etag, "Get Posts", w, r) {
   216  		return
   217  	}
   218  
   219  	if list, err := c.App.GetPosts(id, offset, limit); err != nil {
   220  		c.Err = err
   221  		return
   222  	} else {
   223  		w.Header().Set(model.HEADER_ETAG_SERVER, etag)
   224  		w.Write([]byte(list.ToJson()))
   225  	}
   226  
   227  }
   228  
   229  func getPostsSince(c *Context, w http.ResponseWriter, r *http.Request) {
   230  	params := mux.Vars(r)
   231  
   232  	id := params["channel_id"]
   233  	if len(id) != 26 {
   234  		c.SetInvalidParam("getPostsSince", "channelId")
   235  		return
   236  	}
   237  
   238  	time, err := strconv.ParseInt(params["time"], 10, 64)
   239  	if err != nil {
   240  		c.SetInvalidParam("getPostsSince", "time")
   241  		return
   242  	}
   243  
   244  	if !c.App.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_READ_CHANNEL) {
   245  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   246  		return
   247  	}
   248  
   249  	if list, err := c.App.GetPostsSince(id, time); err != nil {
   250  		c.Err = err
   251  		return
   252  	} else {
   253  		w.Write([]byte(list.ToJson()))
   254  	}
   255  
   256  }
   257  
   258  func getPost(c *Context, w http.ResponseWriter, r *http.Request) {
   259  	params := mux.Vars(r)
   260  
   261  	channelId := params["channel_id"]
   262  	if len(channelId) != 26 {
   263  		c.SetInvalidParam("getPost", "channelId")
   264  		return
   265  	}
   266  
   267  	postId := params["post_id"]
   268  	if len(postId) != 26 {
   269  		c.SetInvalidParam("getPost", "postId")
   270  		return
   271  	}
   272  
   273  	if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) {
   274  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   275  		return
   276  	}
   277  
   278  	if list, err := c.App.GetPostThread(postId); err != nil {
   279  		c.Err = err
   280  		return
   281  	} else if c.HandleEtag(list.Etag(), "Get Post", w, r) {
   282  		return
   283  	} else {
   284  		if !list.IsChannelId(channelId) {
   285  			c.Err = model.NewAppError("getPost", "api.post.get_post.permissions.app_error", nil, "", http.StatusForbidden)
   286  			return
   287  		}
   288  
   289  		w.Header().Set(model.HEADER_ETAG_SERVER, list.Etag())
   290  		w.Write([]byte(list.ToJson()))
   291  	}
   292  }
   293  
   294  func getPostById(c *Context, w http.ResponseWriter, r *http.Request) {
   295  	params := mux.Vars(r)
   296  
   297  	postId := params["post_id"]
   298  	if len(postId) != 26 {
   299  		c.SetInvalidParam("getPostById", "postId")
   300  		return
   301  	}
   302  
   303  	if list, err := c.App.GetPostThread(postId); err != nil {
   304  		c.Err = err
   305  		return
   306  	} else {
   307  		if len(list.Order) != 1 {
   308  			c.Err = model.NewAppError("getPostById", "api.post_get_post_by_id.get.app_error", nil, "", http.StatusInternalServerError)
   309  			return
   310  		}
   311  		post := list.Posts[list.Order[0]]
   312  
   313  		if !c.App.SessionHasPermissionToChannel(c.Session, post.ChannelId, model.PERMISSION_READ_CHANNEL) {
   314  			c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   315  			return
   316  		}
   317  
   318  		if c.HandleEtag(list.Etag(), "Get Post By Id", w, r) {
   319  			return
   320  		}
   321  
   322  		w.Header().Set(model.HEADER_ETAG_SERVER, list.Etag())
   323  		w.Write([]byte(list.ToJson()))
   324  	}
   325  }
   326  
   327  func getPermalinkTmp(c *Context, w http.ResponseWriter, r *http.Request) {
   328  	params := mux.Vars(r)
   329  
   330  	postId := params["post_id"]
   331  	if len(postId) != 26 {
   332  		c.SetInvalidParam("getPermalinkTmp", "postId")
   333  		return
   334  	}
   335  
   336  	var channel *model.Channel
   337  	if result := <-c.App.Srv.Store.Channel().GetForPost(postId); result.Err == nil {
   338  		channel = result.Data.(*model.Channel)
   339  	} else {
   340  		c.SetInvalidParam("getPermalinkTmp", "postId")
   341  		return
   342  	}
   343  
   344  	if channel.Type == model.CHANNEL_OPEN {
   345  		if !c.App.HasPermissionToChannelByPost(c.Session.UserId, postId, model.PERMISSION_JOIN_PUBLIC_CHANNELS) {
   346  			c.SetPermissionError(model.PERMISSION_JOIN_PUBLIC_CHANNELS)
   347  			return
   348  		}
   349  	} else {
   350  		if !c.App.HasPermissionToChannelByPost(c.Session.UserId, postId, model.PERMISSION_READ_CHANNEL) {
   351  			c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   352  			return
   353  		}
   354  	}
   355  
   356  	if list, err := c.App.GetPermalinkPost(postId, c.Session.UserId); err != nil {
   357  		c.Err = err
   358  		return
   359  	} else if c.HandleEtag(list.Etag(), "Get Permalink TMP", w, r) {
   360  		return
   361  	} else {
   362  		w.Header().Set(model.HEADER_ETAG_SERVER, list.Etag())
   363  		w.Write([]byte(list.ToJson()))
   364  	}
   365  }
   366  
   367  func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
   368  	params := mux.Vars(r)
   369  
   370  	channelId := params["channel_id"]
   371  	if len(channelId) != 26 {
   372  		c.SetInvalidParam("deletePost", "channelId")
   373  		return
   374  	}
   375  
   376  	postId := params["post_id"]
   377  	if len(postId) != 26 {
   378  		c.SetInvalidParam("deletePost", "postId")
   379  		return
   380  	}
   381  
   382  	if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_DELETE_POST) {
   383  		c.SetPermissionError(model.PERMISSION_DELETE_POST)
   384  		return
   385  	}
   386  
   387  	if !c.App.SessionHasPermissionToPost(c.Session, postId, model.PERMISSION_DELETE_OTHERS_POSTS) {
   388  		c.SetPermissionError(model.PERMISSION_DELETE_OTHERS_POSTS)
   389  		return
   390  	}
   391  
   392  	if post, err := c.App.DeletePost(postId); err != nil {
   393  		c.Err = err
   394  		return
   395  	} else {
   396  		if post.ChannelId != channelId {
   397  			c.Err = model.NewAppError("deletePost", "api.post.delete_post.permissions.app_error", nil, "", http.StatusForbidden)
   398  			return
   399  		}
   400  
   401  		result := make(map[string]string)
   402  		result["id"] = postId
   403  		w.Write([]byte(model.MapToJson(result)))
   404  	}
   405  }
   406  
   407  func getPostsBefore(c *Context, w http.ResponseWriter, r *http.Request) {
   408  	getPostsBeforeOrAfter(c, w, r, true)
   409  }
   410  
   411  func getPostsAfter(c *Context, w http.ResponseWriter, r *http.Request) {
   412  	getPostsBeforeOrAfter(c, w, r, false)
   413  }
   414  
   415  func getPostsBeforeOrAfter(c *Context, w http.ResponseWriter, r *http.Request, before bool) {
   416  	params := mux.Vars(r)
   417  
   418  	id := params["channel_id"]
   419  	if len(id) != 26 {
   420  		c.SetInvalidParam("getPostsBeforeOrAfter", "channelId")
   421  		return
   422  	}
   423  
   424  	postId := params["post_id"]
   425  	if len(postId) != 26 {
   426  		c.SetInvalidParam("getPostsBeforeOrAfter", "postId")
   427  		return
   428  	}
   429  
   430  	numPosts, err := strconv.Atoi(params["num_posts"])
   431  	if err != nil || numPosts <= 0 {
   432  		c.SetInvalidParam("getPostsBeforeOrAfter", "numPosts")
   433  		return
   434  	}
   435  
   436  	offset, err := strconv.Atoi(params["offset"])
   437  	if err != nil || offset < 0 {
   438  		c.SetInvalidParam("getPostsBeforeOrAfter", "offset")
   439  		return
   440  	}
   441  
   442  	if !c.App.SessionHasPermissionToChannel(c.Session, id, model.PERMISSION_READ_CHANNEL) {
   443  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   444  		return
   445  	}
   446  
   447  	// We can do better than this etag in this situation
   448  	etag := c.App.GetPostsEtag(id)
   449  
   450  	if c.HandleEtag(etag, "Get Posts Before or After", w, r) {
   451  		return
   452  	}
   453  
   454  	if list, err := c.App.GetPostsAroundPost(postId, id, offset, numPosts, before); err != nil {
   455  		c.Err = err
   456  		return
   457  	} else {
   458  		w.Header().Set(model.HEADER_ETAG_SERVER, etag)
   459  		w.Write([]byte(list.ToJson()))
   460  	}
   461  }
   462  
   463  func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) {
   464  	props := model.StringInterfaceFromJson(r.Body)
   465  
   466  	terms := props["terms"].(string)
   467  	if len(terms) == 0 {
   468  		c.SetInvalidParam("search", "terms")
   469  		return
   470  	}
   471  
   472  	isOrSearch := false
   473  	if val, ok := props["is_or_search"]; ok && val != nil {
   474  		isOrSearch = val.(bool)
   475  	}
   476  
   477  	startTime := time.Now()
   478  
   479  	posts, err := c.App.SearchPostsInTeam(terms, c.Session.UserId, c.TeamId, isOrSearch)
   480  
   481  	elapsedTime := float64(time.Since(startTime)) / float64(time.Second)
   482  	metrics := c.App.Metrics
   483  	if metrics != nil {
   484  		metrics.IncrementPostsSearchCounter()
   485  		metrics.ObservePostsSearchDuration(elapsedTime)
   486  	}
   487  
   488  	if err != nil {
   489  		c.Err = err
   490  		return
   491  	}
   492  
   493  	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
   494  	w.Write([]byte(posts.ToJson()))
   495  }
   496  
   497  func getFileInfosForPost(c *Context, w http.ResponseWriter, r *http.Request) {
   498  	params := mux.Vars(r)
   499  
   500  	channelId := params["channel_id"]
   501  	if len(channelId) != 26 {
   502  		c.SetInvalidParam("getFileInfosForPost", "channelId")
   503  		return
   504  	}
   505  
   506  	postId := params["post_id"]
   507  	if len(postId) != 26 {
   508  		c.SetInvalidParam("getFileInfosForPost", "postId")
   509  		return
   510  	}
   511  
   512  	if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) {
   513  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   514  		return
   515  	}
   516  
   517  	if infos, err := c.App.GetFileInfosForPost(postId, false); err != nil {
   518  		c.Err = err
   519  		return
   520  	} else if c.HandleEtag(model.GetEtagForFileInfos(infos), "Get File Infos For Post", w, r) {
   521  		return
   522  	} else {
   523  		if len(infos) > 0 {
   524  			w.Header().Set("Cache-Control", "max-age=2592000, public")
   525  		}
   526  
   527  		w.Header().Set(model.HEADER_ETAG_SERVER, model.GetEtagForFileInfos(infos))
   528  		w.Write([]byte(model.FileInfosToJson(infos)))
   529  	}
   530  }
   531  
   532  func getOpenGraphMetadata(c *Context, w http.ResponseWriter, r *http.Request) {
   533  	if !*c.App.Config().ServiceSettings.EnableLinkPreviews {
   534  		c.Err = model.NewAppError("getOpenGraphMetadata", "api.post.link_preview_disabled.app_error", nil, "", http.StatusNotImplemented)
   535  		return
   536  	}
   537  
   538  	props := model.StringInterfaceFromJson(r.Body)
   539  
   540  	ogJSONGeneric, ok := openGraphDataCache.Get(props["url"])
   541  	if ok {
   542  		w.Write(ogJSONGeneric.([]byte))
   543  		return
   544  	}
   545  
   546  	url := ""
   547  	ok = false
   548  	if url, ok = props["url"].(string); len(url) == 0 || !ok {
   549  		c.SetInvalidParam("getOpenGraphMetadata", "url")
   550  		return
   551  	}
   552  
   553  	og := c.App.GetOpenGraphMetadata(url)
   554  
   555  	ogJSON, err := og.ToJSON()
   556  	openGraphDataCache.AddWithExpiresInSecs(props["url"], ogJSON, 3600) // Cache would expire after 1 hour
   557  	if err != nil {
   558  		w.Write([]byte(`{"url": ""}`))
   559  		return
   560  	}
   561  
   562  	w.Write(ogJSON)
   563  }