Mm 67896 manual cherry pick onto release 10.11 (#35989)

* improves time limit checks

* consistently check for presence of patch fields

* fix variable shadowing in test

* allow idempotent pinning operations with time limit expired

* new utility function for post limit time check

* fix style issue

* Add missing E2E CI files and delivery-platform migration for release-10.11

- Add calculate-playwright-results and calculate-cypress-results GitHub Actions
  (referenced by e2e-tests-playwright-template.yml and e2e-tests-cypress-template.yml
  but never backported to release-10.11)
- Add e2e-tests/playwright/merge.config.mjs (required by merge-reports step)
- Add run-specs Makefile target and server.run_specs.sh (required by run-failed-tests job)
- Fix merge-shard-results step: pin @playwright/test version and add fallback
  for when no blob reports exist (json reporter output used directly)
- Remove pull_request trigger from e2e-tests-ci.yml (delivery-platform migration)
- Remove dead e2e-fulltests-ci.yml and e2e-tests-ci-template.yml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Этот коммит содержится в:
Carlos Garcia
2026-04-14 12:38:57 +02:00
коммит произвёл GitHub
родитель e092af7a33
Коммит b21ef30202
38 изменённых файлов: 59606 добавлений и 934 удалений

Просмотреть файл

@@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"slices"
"strconv"
"time"
@@ -977,6 +978,14 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request, teamId stri
}
}
func postEditTimeLimitExpired(cfg *model.Config, post *model.Post) bool {
limit := *cfg.ServiceSettings.PostEditTimeLimit
if limit == -1 {
return false
}
return model.GetMillis() > post.CreateAt+int64(limit)*1000
}
func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequirePostId()
if c.Err != nil {
@@ -1035,6 +1044,21 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
post.FileIds = originalPost.FileIds
}
// passing nil props should not have any effect on a post's props
// so, we restore the original props in this case
if post.Props == nil {
post.Props = originalPost.Props
}
if postEditTimeLimitExpired(c.App.Config(), originalPost) &&
(post.Message != originalPost.Message ||
!slices.Equal(post.FileIds, originalPost.FileIds) ||
model.StringInterfaceToJSON(post.GetProps()) != model.StringInterfaceToJSON(originalPost.GetProps()) ||
post.IsPinned != originalPost.IsPinned) {
c.Err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]any{"timeLimit": *c.App.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest)
return
}
// Check upload_file permission only if update is adding NEW files (not just keeping existing ones)
checkUploadFilePermissionForNewFiles(c, post.FileIds, originalPost)
if c.Err != nil {
@@ -1051,11 +1075,6 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
post.Id = c.Params.PostId
if *c.App.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > originalPost.CreateAt+int64(*c.App.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != originalPost.Message {
c.Err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]any{"timeLimit": *c.App.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest)
return
}
rpost, isMemberForPreviews, err := c.App.UpdatePost(c.AppContext, c.App.PostWithProxyRemovedFromImageURLs(&post), &model.UpdatePostOptions{SafeUpdate: false})
if err != nil {
c.Err = err
@@ -1104,7 +1123,7 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
isMember := postPatchChecks(c, auditRec, post.Message)
isMember := postPatchChecks(c, auditRec, &post)
if c.Err != nil {
return
}
@@ -1140,7 +1159,7 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
func postPatchChecks(c *Context, auditRec *model.AuditRecord, message *string) bool {
func postPatchChecks(c *Context, auditRec *model.AuditRecord, patch *model.PostPatch) bool {
originalPost, err := c.App.GetSinglePost(c.AppContext, c.Params.PostId, false)
if err != nil {
c.SetPermissionError(model.PermissionEditPost)
@@ -1169,7 +1188,7 @@ func postPatchChecks(c *Context, auditRec *model.AuditRecord, message *string) b
return false
}
if *c.App.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > originalPost.CreateAt+int64(*c.App.Config().ServiceSettings.PostEditTimeLimit*1000) && message != nil {
if postEditTimeLimitExpired(c.App.Config(), originalPost) && !patch.IsEmpty() {
c.Err = model.NewAppError("patchPost", "api.post.update_post.permissions_time_limit.app_error", map[string]any{"timeLimit": *c.App.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest)
return isMember
}
@@ -1264,6 +1283,18 @@ func saveIsPinnedPost(c *Context, w http.ResponseWriter, isPinned bool) {
return
}
// Allow no-op requests (e.g. pinning an already-pinned post) regardless of age.
if post.IsPinned == isPinned {
auditRec.Success()
ReturnStatusOK(w)
return
}
if postEditTimeLimitExpired(c.App.Config(), post) {
c.Err = model.NewAppError("saveIsPinnedPost", "api.post.update_post.permissions_time_limit.app_error", map[string]any{"timeLimit": *c.App.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest)
return
}
patch := &model.PostPatch{}
patch.IsPinned = model.NewPointer(isPinned)
@@ -1557,7 +1588,7 @@ func restorePostVersion(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
isMember := postPatchChecks(c, auditRec, &toRestorePost.Message)
isMember := postPatchChecks(c, auditRec, &model.PostPatch{Message: &toRestorePost.Message, FileIds: &toRestorePost.FileIds})
if c.Err != nil {
return
}