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 }